func TopologyOrWeightMutator(cortex *ng.Cortex) (success bool, result MutateResult) { randomNumber := ng.RandomIntInRange(0, 100) didMutate := false var mutators []CortexMutator if randomNumber > 90 { mutators = []CortexMutator{MutateActivation} } else if randomNumber > 80 { mutators = []CortexMutator{MutateAllWeightsBellCurve} } else if randomNumber > 20 { // apply topological mutation includeNonTopological := false mutators = CortexMutatorsNonRecurrent(includeNonTopological) } else { mutators = CortexMutatorsNonTopological() } // before we mutate the cortex, we need to init it, // otherwise things like Outsplice will fail because // there are no DataChan's. cortex.Init() for i := 0; i <= 100; i++ { randInt := RandomIntInRange(0, len(mutators)) mutator := mutators[randInt] didMutate, _ = mutator(cortex) if !didMutate { logg.LogTo("NEURVOLVE", "Mutate didn't work, retrying...") continue } break } logg.LogTo("NEURVOLVE", "did mutate: %v", didMutate) success = didMutate result = "nothing" return }
func findDownstreamNodeId(cortex *ng.Cortex, layerMap ng.LayerToNodeIdMap, fromLayer float64) *ng.NodeId { numAttempts := len(cortex.AllNodeIds()) * 5 for i := 0; i < numAttempts; i++ { downstreamNodeId := layerMap.ChooseNodeIdFollowingLayer(fromLayer) if downstreamNodeId == nil { log.Printf("findDownstreamNodeId unable to find downstream neuron, cannot add neuron") return nil } if downstreamNodeId.NodeType == ng.ACTUATOR { // make sure it has capacity for new incoming actuator := cortex.FindActuator(downstreamNodeId) if actuator.CanAddInboundConnection() == false { continue } } return downstreamNodeId } return nil }
func (scape XnorScapeTwoPlayer) FitnessAgainst(cortex *ng.Cortex, opponent *ng.Cortex) float64 { cortexFitness := cortex.Fitness(scape.examples) opponentFitness := opponent.Fitness(scape.examples) logg.LogTo("DEBUG", "Cortex fitness: %v vs. Opponent: %v", cortexFitness, opponentFitness) // return cortexFitness - opponentFitness return cortexFitness }
func ReattemptingNeuronMutator(c *ng.Cortex, mutator NeuronMutator) (bool, MutateResult) { numAttempts := len(c.AllNodeIds()) * 5 for i := 0; i < numAttempts; i++ { neuron := randomNeuron(c) ok, mutateResult := mutator(neuron) if ok { return ok, mutateResult } } return false, nil }
func AddNeuronNonRecurrent(cortex *ng.Cortex) (bool, MutateResult) { numAttempts := len(cortex.AllNodeIds()) * 5 for i := 0; i < numAttempts; i++ { nodeIdLayerMap := cortex.NodeIdLayerMap() neuronLayerMap := cortex.NeuronLayerMap() randomLayer := neuronLayerMap.ChooseRandomLayer() upstreamNodeId := nodeIdLayerMap.ChooseNodeIdPrecedingLayer(randomLayer) if upstreamNodeId == nil { continue } downstreamNodeId := findDownstreamNodeId(cortex, nodeIdLayerMap, randomLayer) if downstreamNodeId == nil { continue } neuron := cortex.CreateNeuronInLayer(randomLayer) neuronAddInlinkFrom(neuron, upstreamNodeId) neuronAddOutlinkTo(neuron, downstreamNodeId) return true, neuron } return false, nil }
func renderSVG(cortex *ng.Cortex) { filename := "out.svg" outfile, err := os.Create(filename) if err != nil { panic(err) } defer func() { if err := outfile.Close(); err != nil { panic(err) } }() cortex.RenderSVG(outfile) logg.LogTo("DEBUG", "svg available here: %v", filename) }
// Find a nodeId suitable for use as an outbound node for a newly created // neuron. This can either be a either another neuron node (including // the new neuron itself), or an actuator (if it has space), but it cannot // be a sensor node func findRecurrentOutboundNodeId(cortex *ng.Cortex, layerMap ng.LayerToNodeIdMap, fromLayer float64) *ng.NodeId { numAttempts := len(cortex.AllNodeIds()) * 5 keys := layerMap.Keys() sensorLayer := keys[0] for i := 0; i < numAttempts; i++ { chosenNodeId := layerMap.ChooseNodeIdFollowingLayer(sensorLayer) if chosenNodeId.NodeType == ng.ACTUATOR { // make sure it has capacity for new incoming actuator := cortex.FindActuator(chosenNodeId) if actuator.CanAddInboundConnection() == false { continue } } return chosenNodeId } return nil }
func AddNeuronRecurrent(cortex *ng.Cortex) (bool, MutateResult) { numAttempts := len(cortex.AllNodeIds()) * 5 for i := 0; i < numAttempts; i++ { nodeIdLayerMap := cortex.NodeIdLayerMap() neuronLayerMap := cortex.NeuronLayerMap() randomLayer := neuronLayerMap.ChooseRandomLayer() inboundNodeId := findRecurrentInboundNodeId(cortex, nodeIdLayerMap, randomLayer) if inboundNodeId == nil { log.Printf("Warn: unable to find inbound node id") continue } if randomLayer == inboundNodeId.LayerIndex { continue } neuron := cortex.CreateNeuronInLayer(randomLayer) outboundNodeId := findRecurrentOutboundNodeId(cortex, nodeIdLayerMap, randomLayer) if outboundNodeId == nil { log.Printf("Warn: unable to find outbound node id") continue } neuronAddInlinkFrom(neuron, inboundNodeId) neuronAddOutlinkTo(neuron, outboundNodeId) return true, neuron } logg.LogTo("NEURVOLVE", "return false, nil") return false, nil }
func (shc *StochasticHillClimber) Train(cortex *ng.Cortex, scape Scape) (resultNeuralNet *ng.Cortex, fitness float64, succeeded bool) { shc.validate() numAttempts := 0 fittestNeuralNet := cortex.Copy() resultNeuralNet = cortex // Apply NN to problem and save fitness fitness = scape.Fitness(fittestNeuralNet) logg.LogTo("MAIN", "Initial fitness: %v", fitness) if fitness > shc.FitnessThreshold { succeeded = true return } for i := 0; ; i++ { // Save the genotype candidateNeuralNet := fittestNeuralNet.Copy() // Perturb synaptic weights and biases PerturbParameters(candidateNeuralNet, shc.WeightSaturationRange) // Re-Apply NN to problem candidateFitness := scape.Fitness(candidateNeuralNet) logg.LogTo("DEBUG", "candidate fitness: %v", fitness) // If fitness of perturbed NN is higher, discard original NN and keep new // If fitness of original is higher, discard perturbed and keep old. if candidateFitness > fitness { logg.LogTo("MAIN", "i: %v candidateFitness: %v > fitness: %v", i, candidateFitness, fitness) i = 0 fittestNeuralNet = candidateNeuralNet resultNeuralNet = candidateNeuralNet.Copy() fitness = candidateFitness } if candidateFitness > shc.FitnessThreshold { logg.LogTo("MAIN", "candidateFitness: %v > Threshold. Success at i=%v", candidateFitness, i) succeeded = true break } if ng.IntModuloProper(i, shc.MaxIterationsBeforeRestart) { logg.LogTo("MAIN", "** restart hill climber. fitness: %f i/max: %d/%d", fitness, numAttempts, shc.MaxAttempts) numAttempts += 1 i = 0 shc.resetParametersToRandom(fittestNeuralNet) ng.SeedRandom() } if numAttempts >= shc.MaxAttempts { succeeded = false break } } return }
func Outsplice(cortex *ng.Cortex, chooseOutbound OutboundChooser) (bool, *ng.Neuron) { numAttempts := len(cortex.AllNodeIds()) * 5 for i := 0; i < numAttempts; i++ { neuronA := randomNeuron(cortex) outbound := chooseOutbound(neuronA) if outbound == nil { continue } if neuronA.NodeId.UUID == outbound.NodeId.UUID { continue } nodeIdB := outbound.NodeId // figure out which layer neuronK will go in nodeIdLayerMap := cortex.NodeIdLayerMap() layerA := neuronA.NodeId.LayerIndex layerB := nodeIdB.LayerIndex layerK := nodeIdLayerMap.LayerBetweenOrNew(layerA, layerB) // create neuron K neuronK := cortex.CreateNeuronInLayer(layerK) // disconnect neuronA <-> nodeB nodeBConnector := cortex.FindInboundConnector(nodeIdB) ng.DisconnectOutbound(neuronA, nodeIdB) ng.DisconnectInbound(nodeBConnector, neuronA) // connect neuronA -> neuronK weights := randomWeights(1) ng.ConnectOutbound(neuronA, neuronK) ng.ConnectInboundWeighted(neuronK, neuronA, weights) // connect neuronK -> nodeB switch nodeIdB.NodeType { case ng.NEURON: neuronB := cortex.FindNeuron(nodeIdB) ng.ConnectOutbound(neuronK, neuronB) ng.ConnectInboundWeighted(nodeBConnector, neuronK, weights) case ng.ACTUATOR: actuatorB := cortex.FindActuator(nodeIdB) ng.ConnectOutbound(neuronK, actuatorB) ng.ConnectInbound(nodeBConnector, neuronK) } return true, neuronK } return false, nil }
func dumpCompactDescription(cortex *ng.Cortex) { logg.LogTo("DEBUG", "Compact: %v", cortex.StringCompact()) }
func (scape FakeScapeTwoPlayer) Fitness(cortex *ng.Cortex) float64 { cortexFitness := cortex.Fitness(scape.examples) return cortexFitness }
func (scape TrainingSampleScape) Fitness(cortex *ng.Cortex) float64 { return cortex.Fitness(scape.examples) }
func (tmt *TopologyMutatingTrainer) Train(cortex *ng.Cortex, scape Scape) (fittestCortex *ng.Cortex, succeeded bool) { ng.SeedRandom() shc := tmt.StochasticHillClimber includeNonTopological := false mutators := CortexMutatorsNonRecurrent(includeNonTopological) originalCortex := cortex.Copy() currentCortex := cortex // Apply NN to problem and save fitness logg.LogTo("MAIN", "Get initial fitness") fitness := scape.Fitness(currentCortex) logg.LogTo("MAIN", "Initial fitness: %v", fitness) if fitness > shc.FitnessThreshold { succeeded = true return } for i := 0; ; i++ { logg.LogTo("MAIN", "Before mutate. i/max: %d/%d", i, tmt.MaxAttempts) // before we mutate the cortex, we need to init it, // otherwise things like Outsplice will fail because // there are no DataChan's. currentCortex.Init() // mutate the network randInt := RandomIntInRange(0, len(mutators)) mutator := mutators[randInt] ok, _ := mutator(currentCortex) if !ok { logg.LogTo("MAIN", "Mutate didn't work, retrying...") continue } isValid := currentCortex.Validate() if !isValid { logg.LogPanic("Cortex did not validate") } filenameJson := fmt.Sprintf("cortex-%v.json", i) currentCortex.MarshalJSONToFile(filenameJson) filenameSvg := fmt.Sprintf("cortex-%v.svg", i) currentCortex.RenderSVGFile(filenameSvg) logg.LogTo("MAIN", "Post mutate cortex svg: %v json: %v", filenameSvg, filenameJson) logg.LogTo("MAIN", "Run stochastic hill climber..") // memetic step: call stochastic hill climber and see if it can solve it fittestCortex, _, succeeded = shc.Train(currentCortex, scape) logg.LogTo("MAIN", "stochastic hill climber finished. succeeded: %v", succeeded) if succeeded { succeeded = true break } if i >= tmt.MaxAttempts { succeeded = false break } if ng.IntModuloProper(i, tmt.MaxIterationsBeforeRestart) { logg.LogTo("MAIN", "** Restart . i/max: %d/%d", i, tmt.MaxAttempts) currentCortex = originalCortex.Copy() isValid := currentCortex.Validate() if !isValid { currentCortex.Repair() // TODO: remove workaround isValid = currentCortex.Validate() if !isValid { logg.LogPanic("Cortex could not be repaired") } } } } return }