我有10
个数字W
,而且每个数字都在-1f
到1f
的范围内。我有一个函数:f(W)
,它可以重新计算浮点值。我需要最小化f
W
的变化值。我确实需要一个超级的预设,我希望学会成为0.01
的最小步骤。 f
需要时间,我不需要从多个线程执行它。
如何为这样的系统实现一个简单的GA,其基数W
为浮点数组,GA的最小步长为0.01
,每个W值的限制为[-1, 1]范围,以及一个函数f(W)
,以最小化在AForge / Accord.Net中返回浮点数?
答案 0 :(得分:1)
遗传算法伪代码:
// While the population is not optimized
while (population.isNotOptimized()) {
// Update each member of the population (i.e. do physics, calculations etc.)
population.updateMembers()
// Calculate the new fitnesses of each member of the population
population.calculateMemberFitnesses()
// Create a new, empty population
newPopulation = empty
// While new population size is not the same as current pop size
while (newPopulation.size < population.size) {
// Select 2 members from current pop using roullette selection
mum = roullette(population)
dad = roullette(population)
// Crossover to create child
val child = crossover(mum, dad)
// Mutate child
mutate(child)
// Add child to new pop
newPopulation.add(child)
}
}
根据您的问题,您希望优化神经网络权重。因此,在这种情况下,您的人口将是一组网络权重。以下是我对简单多层网络和相关遗传算法的实现:
package Learning
import Simulation.Configuration
import Utilities.NonDeterminism
private const val LAYER_COUNT = 3
private const val OUTPUT_COUNT = 3
// A simple ANN
// No back prop as we'll train it with a GA
class Network(private val configuration: Configuration) {
private var inputCount = 0
private var hiddenCount = 0
// The networks layers
private val layers = mutableListOf<Layer>()
init {
inputCount = configuration.asteroidsToConsider * 2 + 4
hiddenCount = (inputCount + OUTPUT_COUNT) / 2
layers.clear()
for (layer in 0 until LAYER_COUNT) {
when (layer) {
0 -> { layers.add(Layer(configuration, inputCount, hiddenCount)) }
LAYER_COUNT - 1 -> { layers.add(Layer(configuration, if(LAYER_COUNT == 2) { inputCount } else { hiddenCount }, OUTPUT_COUNT)) }
else -> { layers.add(Layer(configuration, hiddenCount, hiddenCount)) }
}
}
}
fun setWeights(weights: Array<Double>) {
layers.clear()
for (layer in 0 until LAYER_COUNT) {
when (layer) {
0 -> {
layers.add(Layer(configuration, inputCount, hiddenCount, weights.sliceArray(IntRange(0, inputCount * hiddenCount))))
}
LAYER_COUNT - 1 -> {
val startIndex = layers.sumBy { it.weights.size - 1 }
val endIndex = when (LAYER_COUNT) {
2 -> {
startIndex + inputCount * OUTPUT_COUNT
} else -> {
startIndex + hiddenCount * OUTPUT_COUNT
}
}
when (LAYER_COUNT) {
2 -> {
layers.add(Layer(configuration, inputCount, OUTPUT_COUNT, weights.sliceArray(IntRange(startIndex, endIndex))))
} else -> {
layers.add(Layer(configuration, hiddenCount, OUTPUT_COUNT, weights.sliceArray(IntRange(startIndex, endIndex))))
}
}
}
else -> {
val startIndex = layers.sumBy { it.weights.size - 1 }
val endIndex = startIndex + hiddenCount * hiddenCount
layers.add(Layer(configuration, hiddenCount, hiddenCount, weights.sliceArray(IntRange(startIndex, endIndex))))
}
}
}
}
// Run the network against the inputs and return the outputs
fun update(inputs: Array<Double>) : Array<Double> {
var outputs = inputs
layers.forEach { layer ->
outputs = layer.activate(outputs)
}
return outputs
}
// Gets the weights of all the layers as a single array
fun getCopyOfWeights() : Array<Double> {
val weights = mutableListOf<Double>()
layers.forEach { layer ->
weights.addAll(layer.weights)
}
return weights.toTypedArray()
}
// Represents a layer in the artificial neural network
private class Layer(private val configuration: Configuration,
private val inputSize: Int,
private val outputSize: Int,
var weights: Array<Double> = Array((inputSize * outputSize), { _ ->
NonDeterminism.randomNetworkWeight()
})) {
// Activate the layer and return the set of outputs
fun activate(inputs: Array<Double>) : Array<Double> {
// Validate the inputs are the correct size
if (inputs.size >= weights.size)
throw IllegalStateException("There are an incorrect number of inputs")
// Create the output array
val outputs = Array(outputSize, { _ -> 0.0 })
// Foreach output, work out the activation
for (output in 0 until outputs.size) {
// Foreach input, multiply by the corresponding weight
var netInput = 0.0
for (input in 0 until inputs.size) {
netInput += weights[input * output + 1] * inputs[input]
}
// Set the output
outputs[output] = tanh(netInput)
}
return outputs
}
// Calculate the sigmoid derivative of the passed input
private fun sigmoid(input: Double) : Double = 1 / (1 + Math.exp(-input / configuration.activationResponse))
// Calculate the tanh derivative of the passed input
private fun tanh(input: Double) : Double = 2 * sigmoid(input * 2) - 1
}
}
package Learning
import Simulation.Configuration
import Utilities.NonDeterminism
private const val CROSSOVER_RATE = 1.75
private const val MUTATION_CHANCE = 0.01
private const val MAX_PERTURBATION = 0.1
private const val ELITES = 3
// Functions for performing selection, crossover and mutation of a networks weights
class Genetics(private val configuration: Configuration = Configuration()) {
// The population of network members to perform genetic selection on
private val population = mutableListOf<NetworkPopulationMember>()
// Reset the genetics
fun reset() {
population.clear()
}
// Creates a population member with the passed weights
fun addPopulationMember(weights: DoubleArray) {
population.add(NetworkPopulationMember(configuration, weights.toTypedArray()))
}
// Sets the population fitnesses to the passed set of doubles
fun setPopulationFitnesses(fitnesses: Collection<Double>) {
fitnesses.forEachIndexed { index, fitness ->
population.elementAt(index).fitness = fitness
}
}
// Starts a new epoch
fun epoch() : Collection<DoubleArray> {
// Sort the current population by fitness
population.sortByDescending { it.fitness }
// Create a new population
val newPopulation = mutableListOf<NetworkPopulationMember>()
// Add elites (the best one from the current population gets copied into the new population N times)
for (elite in 0 until ELITES) {
val eliteMember = NetworkPopulationMember(configuration, population.first().network.getCopyOfWeights())
newPopulation.add(eliteMember)
}
// While the new population still needs filling
while (newPopulation.size < population.size) {
// Select parents
val dad = rouletteSelection()
val mum = rouletteSelection()
// Create child
val children = dad.crossover(mum)
// Add child to new population
newPopulation.addAll(listOf(children.first, children.second))
}
// Clear the current pop and set to the new one
population.clear()
population.addAll(newPopulation)
// Return a copy of the new population
return newPopulation.map { it.network.getCopyOfWeights().toDoubleArray() }
}
// Selects a member from the population using roullette method
// This means the chance of being selected is directly proportional
// To the fitness of the member in respect to the rest of the population
private fun rouletteSelection() : NetworkPopulationMember {
// Work out the total fitness of the population
var totalPopulationFitness = 0.0
population.forEach { member ->
totalPopulationFitness += member.fitness
}
// Select a random slice point
val slice = NonDeterminism.randomDouble() * totalPopulationFitness
// Keep looping the population until the trackFitness exceeds the slice point
var trackedFitness = 0.0
population.forEach { member ->
trackedFitness += member.fitness
if (trackedFitness > slice) {
// We found the first member after the slice point
// Return it
return member
}
}
// For some reason the slice was greater than than total fitness
// So just return the last member from the population
return population[population.size - 1]
}
}
// Represents a network as a member of an evolving population set
class NetworkPopulationMember(private val configuration: Configuration, val network: Network = Network(configuration)) {
// Constructor for creating a new member with specified set of weights
constructor(configuration: Configuration, weights: Array<Double>) : this(configuration) {
network.setWeights(weights)
}
// Tracks the fitness of this member with respect to the rest of the population
var fitness = 0.0
// Performs crossover with the passed member and returns a new member
fun crossover(with: NetworkPopulationMember) : Pair<NetworkPopulationMember, NetworkPopulationMember> {
// If the crossover rate is not met, or the parents are the same just return a copy of one of them
if (NonDeterminism.randomDouble(2.0) < CROSSOVER_RATE || this == with) {
return Pair(NetworkPopulationMember(configuration, this.network.getCopyOfWeights()),
NetworkPopulationMember(configuration, with.network.getCopyOfWeights()))
}
// Get the weights of the parents
val mumWeights = with.network.getCopyOfWeights()
val dadWeights = this.network.getCopyOfWeights()
// Determine the random crossover point
val crossoverPoint = NonDeterminism.randomCrossoverPoint(mumWeights.size - 1)
// Create the child A weights array
val childWeightsA = mutableListOf<Double>()
childWeightsA.addAll(mumWeights.sliceArray(IntRange(0, crossoverPoint)))
childWeightsA.addAll(dadWeights.sliceArray(IntRange(crossoverPoint, mumWeights.size - 1)))
// Create child B weights array
val childWeightsB = mutableListOf<Double>()
childWeightsB.addAll(dadWeights.sliceArray(IntRange(0, crossoverPoint)))
childWeightsB.addAll(mumWeights.sliceArray(IntRange(crossoverPoint, mumWeights.size - 1)))
for (index in 0 until childWeightsA.size) {
if (NonDeterminism.randomDouble() < MUTATION_CHANCE) {
childWeightsA[index] += if(NonDeterminism.randomBoolean()) MAX_PERTURBATION else - MAX_PERTURBATION
}
if (NonDeterminism.randomDouble() < MUTATION_CHANCE) {
childWeightsB[index] += if(NonDeterminism.randomBoolean()) MAX_PERTURBATION else - MAX_PERTURBATION
}
}
// Create and return the child network
return Pair(NetworkPopulationMember(configuration, childWeightsA.toTypedArray()), NetworkPopulationMember(configuration, childWeightsB.toTypedArray()))
}
}