/// <summary> /// Copy constructor. /// </summary> /// <param name="copyFrom"></param> public NeuronGene(NeuronGene copyFrom) { this.innovationId = copyFrom.innovationId; this.neuronType = copyFrom.neuronType; this.activationFunction = copyFrom.activationFunction; this.Layer = copyFrom.Layer; }
private static void WriteNeuron(XmlElement xmlNeurons, NeuronGene neuronGene) { XmlElement xmlNeuron = XmlUtilities.AddElement(xmlNeurons, "neuron"); XmlUtilities.AddAttribute(xmlNeuron, "id", neuronGene.InnovationId.ToString()); XmlUtilities.AddAttribute(xmlNeuron, "type", XmlUtilities.GetNeuronTypeString(neuronGene.NeuronType)); XmlUtilities.AddAttribute(xmlNeuron, "activationFunction", neuronGene.ActivationFunction.FunctionId); }
public NewNeuronGeneStruct( NeuronGene newNeuronGene, ConnectionGene newConnectionGene_Input, ConnectionGene newConnectionGene_Output) { this.NewNeuronGene = newNeuronGene; this.NewConnectionGene_Input = newConnectionGene_Input; this.NewConnectionGene_Output = newConnectionGene_Output; }
public NeuronGene(NeuronGene copyFrom, uint _innovationId, double _layer, NeuronType _neuronType, IActivationFunction _activationFunction) { if (copyFrom != null) { this.innovationId = copyFrom.InnovationId; this.neuronType = copyFrom.NeuronType; this.activationFunction = copyFrom.ActivationFunction; this.Layer = copyFrom.Layer; } else { this.innovationId = _innovationId; this.neuronType = _neuronType; this.activationFunction = _activationFunction; this.Layer = _layer; } }
private void Mutate_AddModule(EvolutionAlgorithm ea) { // Find all potential inputs, or quit if there are not enough. // Neurons cannot be inputs if they are dummy input nodes created for another module. NeuronGeneList potentialInputs = new NeuronGeneList(); foreach (NeuronGene n in neuronGeneList) { if (!(n.ActivationFunction is ModuleInputNeuron)) { potentialInputs.Add(n); } } if (potentialInputs.Count < 1) return; // Find all potential outputs, or quit if there are not enough. // Neurons cannot be outputs if they are dummy input or output nodes created for another module, or network input or bias nodes. NeuronGeneList potentialOutputs = new NeuronGeneList(); foreach (NeuronGene n in neuronGeneList) { if (n.NeuronType != NeuronType.Bias && n.NeuronType != NeuronType.Input && !(n.ActivationFunction is ModuleInputNeuron) && !(n.ActivationFunction is ModuleOutputNeuron)) { potentialOutputs.Add(n); } } if (potentialOutputs.Count < 1) return; // Pick a new function for the new module. IModule func = ModuleFactory.GetRandom(); // Choose inputs uniformly at random, with replacement. // Create dummy neurons to represent the module's inputs. // Create connections between the input nodes and the dummy neurons. IActivationFunction inputFunction = ActivationFunctionFactory.GetActivationFunction("ModuleInputNeuron"); List<uint> inputDummies = new List<uint>(func.InputCount); for (int i = 0; i < func.InputCount; i++) { NeuronGene newNeuronGene = new NeuronGene(ea.NextInnovationId, NeuronType.Hidden, inputFunction); neuronGeneList.Add(newNeuronGene); uint sourceId = potentialInputs[Utilities.Next(potentialInputs.Count)].InnovationId; uint targetId = newNeuronGene.InnovationId; inputDummies.Add(targetId); // Create a new connection with a new ID and add it to the Genome. ConnectionGene newConnectionGene = new ConnectionGene(ea.NextInnovationId, sourceId, targetId, (Utilities.NextDouble() * ea.NeatParameters.connectionWeightRange) - ea.NeatParameters.connectionWeightRange / 2.0); // Register the new connection with NewConnectionGeneTable. ConnectionEndpointsStruct connectionKey = new ConnectionEndpointsStruct(sourceId, targetId); ea.NewConnectionGeneTable.Add(connectionKey, newConnectionGene); // Add the new gene to this genome. We have a new ID so we can safely append the gene to the end // of the list without risk of breaking the innovation ID order. connectionGeneList.Add(newConnectionGene); } // Choose outputs uniformly at random, with replacement. // Create dummy neurons to represent the module's outputs. // Create connections between the output nodes and the dummy neurons. IActivationFunction outputFunction = ActivationFunctionFactory.GetActivationFunction("ModuleOutputNeuron"); List<uint> outputDummies = new List<uint>(func.OutputCount); for (int i = 0; i < func.OutputCount; i++) { NeuronGene newNeuronGene = new NeuronGene(ea.NextInnovationId, NeuronType.Hidden, outputFunction); neuronGeneList.Add(newNeuronGene); uint sourceId = newNeuronGene.InnovationId; uint targetId = potentialOutputs[Utilities.Next(potentialOutputs.Count)].InnovationId; outputDummies.Add(sourceId); // Create a new connection with a new ID and add it to the Genome. ConnectionGene newConnectionGene = new ConnectionGene(ea.NextInnovationId, sourceId, targetId, (Utilities.NextDouble() * ea.NeatParameters.connectionWeightRange) - ea.NeatParameters.connectionWeightRange / 2.0); // Register the new connection with NewConnectionGeneTable. ConnectionEndpointsStruct connectionKey = new ConnectionEndpointsStruct(sourceId, targetId); ea.NewConnectionGeneTable.Add(connectionKey, newConnectionGene); // Add the new gene to this genome. We have a new ID so we can safely append the gene to the end // of the list without risk of breaking the innovation ID order. connectionGeneList.Add(newConnectionGene); } // Pick a new ID for the new module and create it. // Modules do not participate in history comparisons, so we will always create a new innovation ID. // We can change this here if it becomes a problem. ModuleGene newModule = new ModuleGene(ea.NextInnovationId, func, inputDummies, outputDummies); moduleGeneList.Add(newModule); }
/// <summary> /// Add a new node to the Genome. We do this by removing a connection at random and inserting /// a new node and two new connections that make the same circuit as the original connection. /// /// This way the new node is properly integrated into the network from the outset. /// </summary> /// <param name="ea"></param> private void Mutate_AddNode(EvolutionAlgorithm ea) { if(connectionGeneList.Count==0) return; // Select a connection at random. int connectionToReplaceIdx = (int)Math.Floor(Utilities.NextDouble() * connectionGeneList.Count); ConnectionGene connectionToReplace = connectionGeneList[connectionToReplaceIdx]; // Delete the existing connection. JOEL: Why delete old connection? //connectionGeneList.RemoveAt(connectionToReplaceIdx); // Check if this connection has already been split on another genome. If so then we should re-use the // neuron ID and two connection ID's so that matching structures within the population maintain the same ID. object existingNeuronGeneStruct = ea.NewNeuronGeneStructTable[connectionToReplace.InnovationId]; NeuronGene newNeuronGene; ConnectionGene newConnectionGene1; ConnectionGene newConnectionGene2; IActivationFunction actFunct; if(existingNeuronGeneStruct==null) { // No existing matching structure, so generate some new ID's. //TODO: DAVID proper random activation function // Replace connectionToReplace with two new connections and a neuron. actFunct=ActivationFunctionFactory.GetRandomActivationFunction(ea.NeatParameters); //newNeuronGene = new NeuronGene(ea.NextInnovationId, NeuronType.Hidden, actFunct); newNeuronGene = new NeuronGene(null, ea.NextInnovationId, (neuronGeneList.GetNeuronById(connectionToReplace.SourceNeuronId).Layer + neuronGeneList.GetNeuronById(connectionToReplace.TargetNeuronId).Layer) / 2.0, NeuronType.Hidden, actFunct); newConnectionGene1 = new ConnectionGene(ea.NextInnovationId, connectionToReplace.SourceNeuronId, newNeuronGene.InnovationId, 1.0); newConnectionGene2 = new ConnectionGene(ea.NextInnovationId, newNeuronGene.InnovationId, connectionToReplace.TargetNeuronId, connectionToReplace.Weight); // Register the new ID's with NewNeuronGeneStructTable. ea.NewNeuronGeneStructTable.Add(connectionToReplace.InnovationId, new NewNeuronGeneStruct(newNeuronGene, newConnectionGene1, newConnectionGene2)); } else { // An existing matching structure has been found. Re-use its ID's //TODO: DAVID proper random activation function // Replace connectionToReplace with two new connections and a neuron. actFunct = ActivationFunctionFactory.GetRandomActivationFunction(ea.NeatParameters); NewNeuronGeneStruct tmpStruct = (NewNeuronGeneStruct)existingNeuronGeneStruct; //newNeuronGene = new NeuronGene(tmpStruct.NewNeuronGene.InnovationId, NeuronType.Hidden, actFunct); newNeuronGene = new NeuronGene(null, tmpStruct.NewNeuronGene.InnovationId, tmpStruct.NewNeuronGene.Layer, NeuronType.Hidden, actFunct); newConnectionGene1 = new ConnectionGene(tmpStruct.NewConnectionGene_Input.InnovationId, connectionToReplace.SourceNeuronId, newNeuronGene.InnovationId, 1.0); newConnectionGene2 = new ConnectionGene(tmpStruct.NewConnectionGene_Output.InnovationId, newNeuronGene.InnovationId, connectionToReplace.TargetNeuronId, connectionToReplace.Weight); } // Add the new genes to the genome. neuronGeneList.Add(newNeuronGene); connectionGeneList.InsertIntoPosition(newConnectionGene1); connectionGeneList.InsertIntoPosition(newConnectionGene2); }
// Schrum: Module Mutation Duplicate creates a new module with // links copying those of another module. private void Module_Mutation_Duplicate(EvolutionAlgorithm ea) { // Push all output neurons together this.neuronGeneList.SortByNeuronOrder(); int numModules = this.outputNeuronCount / this.outputsPerPolicy; // Should evenly divide int randomModule = Utilities.Next(numModules); // Duplicate this module // Because outputs come after inputs. // Although list is 0-indexed, the +1 is needed because the bias does not count as an input double outputLayer = neuronGeneList[1 + inputNeuronCount].Layer; // Create the new module one neuron per loop iteration for (int i = 0; i < outputsPerPolicy; i++) { // The activation function for the output layer IActivationFunction outputActFunction = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid"); NeuronGene newNeuronGene = new NeuronGene(null, ea.NextInnovationId, outputLayer, NeuronType.Output, outputActFunction); neuronGeneList.Add(newNeuronGene); uint randomModuleInnovation = neuronGeneList[1 + inputNeuronCount + (randomModule * outputsPerPolicy) + i].InnovationId; // Copy each connection to the new module neuron int originalLength = ConnectionGeneList.Count; // Don't need to check the newly added connections for (int j = 0; j < originalLength; j++) { ConnectionGene cg = ConnectionGeneList[j]; if (cg.TargetNeuronId == randomModuleInnovation) { // Copy the link ConnectionGene connection = new ConnectionGene(ea.NextInnovationId, cg.SourceNeuronId, newNeuronGene.InnovationId, cg.Weight); connectionGeneList.InsertIntoPosition(connection); } } this.outputNeuronCount++; // Increase number of outputs } }
// Schrum: Module Mutation Random creates a new module with // completely random incoming links. private void Module_Mutation_Random(EvolutionAlgorithm ea) { // Push all output neurons together this.neuronGeneList.SortByNeuronOrder(); int numModules = this.outputNeuronCount / this.outputsPerPolicy; // Should evenly divide int randomModule = Utilities.Next(numModules); // Because outputs come after inputs. // Although list is 0-indexed, the +1 is needed because the bias does not count as an input double outputLayer = neuronGeneList[1 + inputNeuronCount].Layer; // Create the new module one neuron per loop iteration for (int i = 0; i < outputsPerPolicy; i++) { // The activation function for the output layer IActivationFunction outputActFunction = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid"); NeuronGene newNeuronGene = new NeuronGene(null, ea.NextInnovationId, outputLayer, NeuronType.Output, outputActFunction); neuronGeneList.Add(newNeuronGene); // Count links to random output neuron: bias, then inputs, then random module, then neuron within that module uint randomModuleInnovation = neuronGeneList[1 + inputNeuronCount + (randomModule * outputsPerPolicy) + i].InnovationId; int numIncoming = 0; foreach (ConnectionGene cg in this.ConnectionGeneList) { // Count the link if (cg.TargetNeuronId == randomModuleInnovation) numIncoming++; } // Give the new module (up to) the same number of links as some other module for (int j = 0; j < numIncoming; j++) // Will always create ay least one link { uint randomSource = NeuronGeneList[Utilities.Next(NeuronGeneList.Count)].InnovationId; // Magic equation stolen from Mutate_AddConnection below double randomWeight = (Utilities.NextDouble() * ea.NeatParameters.connectionWeightRange/4.0) - ea.NeatParameters.connectionWeightRange/8.0; if (!TestForExistingConnection(randomSource, newNeuronGene.InnovationId)) // Only create each connection once { ConnectionGene connection = new ConnectionGene(ea.NextInnovationId, randomSource, newNeuronGene.InnovationId, randomWeight); connectionGeneList.InsertIntoPosition(connection); } } this.outputNeuronCount++; // Increase number of outputs } }
/// <summary> /// Add a new node to the Genome. We do this by removing a connection at random and inserting /// a new node and two new connections that make the same circuit as the original connection. /// /// This way the new node is properly integrated into the network from the outset. /// </summary> /// <param name="ea"></param> private void Mutate_AddNode(EvolutionAlgorithm ea) { if(connectionGeneList.Count==0) return; for (int attempts = 0; attempts < 5; attempts++) { // Select a connection at random. int connectionToReplaceIdx = (int)Math.Floor(Utilities.NextDouble() * connectionGeneList.Count); ConnectionGene connectionToReplace = connectionGeneList[connectionToReplaceIdx]; // Delete the existing connection. JOEL: Why delete old connection? connectionGeneList.RemoveAt(connectionToReplaceIdx); // Check if this connection has already been split on another genome. If so then we should re-use the // neuron ID and two connection ID's so that matching structures within the population maintain the same ID. object existingNeuronGeneStruct = ea.NewNeuronGeneStructTable[connectionToReplace.InnovationId]; NeuronGene newNeuronGene; ConnectionGene newConnectionGene1; ConnectionGene newConnectionGene2; IActivationFunction actFunct; // JUSTIN: Calculate the layer for this new neuron as halfway between the source and the target neuron layers float sourceLayer = neuronGeneList.GetNeuronById(connectionToReplace.SourceNeuronId).Layer; float targetLayer = neuronGeneList.GetNeuronById(connectionToReplace.TargetNeuronId).Layer; float newLayer = ((sourceLayer + targetLayer) / 2); if (existingNeuronGeneStruct == null) { // No existing matching structure, so generate some new ID's. //TODO: DAVID proper random activation function // Replace connectionToReplace with two new connections and a neuron. // JUSTIN: If using neatBrain, then do NOT get a random actv. function!! actFunct = ea.neatBrain ? ActivationFunctionFactory.GetActivationFunction(HyperNEATParameters.substrateActivationFunction.FunctionId) : ActivationFunctionFactory.GetRandomActivationFunction(ea.NeatParameters); newNeuronGene = new NeuronGene(ea.NextInnovationId, NeuronType.Hidden, actFunct, newLayer); newConnectionGene1 = new ConnectionGene(ea.NextInnovationId, connectionToReplace.SourceNeuronId, newNeuronGene.InnovationId, 1.0); newConnectionGene2 = new ConnectionGene(ea.NextInnovationId, newNeuronGene.InnovationId, connectionToReplace.TargetNeuronId, connectionToReplace.Weight); // Register the new ID's with NewNeuronGeneStructTable. ea.NewNeuronGeneStructTable.Add(connectionToReplace.InnovationId, new NewNeuronGeneStruct(newNeuronGene, newConnectionGene1, newConnectionGene2)); } else { // An existing matching structure has been found. Re-use its ID's //DAVID: Trying to add a node where one already exists, reject, is this good? if (neuronGeneList.GetNeuronById(((NewNeuronGeneStruct)existingNeuronGeneStruct).NewNeuronGene.InnovationId) != null) { continue; } //TODO: DAVID proper random activation function // Replace connectionToReplace with two new connections and a neuron. // JUSTIN: If using neatBrain, then do NOT get a random actv. function!! actFunct = ea.neatBrain ? ActivationFunctionFactory.GetActivationFunction(HyperNEATParameters.substrateActivationFunction.FunctionId) : ActivationFunctionFactory.GetRandomActivationFunction(ea.NeatParameters); NewNeuronGeneStruct tmpStruct = (NewNeuronGeneStruct)existingNeuronGeneStruct; newNeuronGene = new NeuronGene(tmpStruct.NewNeuronGene.InnovationId, NeuronType.Hidden, actFunct, newLayer); newConnectionGene1 = new ConnectionGene(tmpStruct.NewConnectionGene_Input.InnovationId, connectionToReplace.SourceNeuronId, newNeuronGene.InnovationId, (Utilities.NextDouble() * ea.NeatParameters.connectionWeightRange) - ea.NeatParameters.connectionWeightRange / 2.0); newConnectionGene2 = new ConnectionGene(tmpStruct.NewConnectionGene_Output.InnovationId, newNeuronGene.InnovationId, connectionToReplace.TargetNeuronId, (Utilities.NextDouble() * ea.NeatParameters.connectionWeightRange / 8.0) - ea.NeatParameters.connectionWeightRange / 16.0); } // Add the new genes to the genome. //Debug.Assert(); neuronGeneList.Add(newNeuronGene); connectionGeneList.InsertIntoPosition(newConnectionGene1); connectionGeneList.InsertIntoPosition(newConnectionGene2); break; } }
/// <summary> /// Create a default minimal genome that describes a NN with the given number of inputs and outputs. /// </summary> /// <returns></returns> public static IGenome CreateGenome(NeatParameters neatParameters, IdGenerator idGenerator, int inputNeuronCount, int outputNeuronCount, int outputsPerPolicy, float connectionProportion) { IActivationFunction actFunct; NeuronGene neuronGene; // temp variable. NeuronGeneList inputNeuronGeneList = new NeuronGeneList(); // includes bias neuron. NeuronGeneList outputNeuronGeneList = new NeuronGeneList(); NeuronGeneList neuronGeneList = new NeuronGeneList(); ConnectionGeneList connectionGeneList = new ConnectionGeneList(); // IMPORTANT NOTE: The neurons must all be created prior to any connections. That way all of the genomes // will obtain the same innovation ID's for the bias,input and output nodes in the initial population. // Create a single bias neuron. //TODO: DAVID proper activation function change to NULL? actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn"); //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Bias, actFunct); neuronGene = new NeuronGene(null, idGenerator.NextInnovationId, NeuronGene.INPUT_LAYER, NeuronType.Bias, actFunct); inputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); // Create input neuron genes. actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn"); for(int i=0; i<inputNeuronCount; i++) { //TODO: DAVID proper activation function change to NULL? //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Input, actFunct); neuronGene = new NeuronGene(null, idGenerator.NextInnovationId, NeuronGene.INPUT_LAYER, NeuronType.Input, actFunct); inputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); } // Create output neuron genes. //actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn"); for(int i=0; i<outputNeuronCount; i++) { actFunct = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid"); //actFunct = ActivationFunctionFactory.GetRandomActivationFunction(neatParameters); //TODO: DAVID proper activation function //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Output, actFunct); neuronGene = new NeuronGene(null, idGenerator.NextInnovationId, NeuronGene.OUTPUT_LAYER, NeuronType.Output, actFunct); outputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); } // Loop over all possible connections from input to output nodes and create a number of connections based upon // connectionProportion. foreach(NeuronGene targetNeuronGene in outputNeuronGeneList) { foreach(NeuronGene sourceNeuronGene in inputNeuronGeneList) { // Always generate an ID even if we aren't going to use it. This is necessary to ensure connections // between the same neurons always have the same ID throughout the generated population. uint connectionInnovationId = idGenerator.NextInnovationId; if(Utilities.NextDouble() < connectionProportion) { // Ok lets create a connection. connectionGeneList.Add( new ConnectionGene(connectionInnovationId, sourceNeuronGene.InnovationId, targetNeuronGene.InnovationId, (Utilities.NextDouble() * neatParameters.connectionWeightRange ) - neatParameters.connectionWeightRange/2.0)); // Weight 0 +-5 } } } // Don't create any hidden nodes at this point. Fundamental to the NEAT way is to start minimally! // Schrum: Added outputsPerPolicy: If outputsPerPolicy == outputNeuronCount, then behaves like default NEAT return new NeatGenome(idGenerator.NextGenomeId, neuronGeneList, connectionGeneList, inputNeuronCount, outputNeuronCount, outputsPerPolicy); }
public static IGenome CreateGenomePreserveID(NeatParameters neatParameters, IdGenerator idGenerator, int inputNeuronCount, int outputNeuronCount, float connectionProportion) { IActivationFunction actFunct; NeuronGene neuronGene; // temp variable. NeuronGeneList inputNeuronGeneList = new NeuronGeneList(); // includes bias neuron. NeuronGeneList outputNeuronGeneList = new NeuronGeneList(); NeuronGeneList neuronGeneList = new NeuronGeneList(); ConnectionGeneList connectionGeneList = new ConnectionGeneList(); int nodeCount = 0; WINManager win = WINManager.SharedWIN; // IMPORTANT NOTE: The neurons must all be created prior to any connections. That way all of the genomes // will obtain the same innovation ID's for the bias,input and output nodes in the initial population. // Create a single bias neuron. //TODO: DAVID proper activation function change to NULL? actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn"); //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Bias, actFunct); WINNode neuronNode = win.findOrInsertNodeWithProperties(idGenerator, WINNode.NodeWithProperties(nodeCount++, NeuronType.Bias) ); neuronGene = new NeuronGene(null, neuronNode.UniqueID, NeuronGene.INPUT_LAYER, NeuronType.Bias, actFunct); inputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); // Create input neuron genes. actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn"); for (int i = 0; i < inputNeuronCount; i++) { //TODO: DAVID proper activation function change to NULL? //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Input, actFunct); neuronNode = win.findOrInsertNodeWithProperties(idGenerator, WINNode.NodeWithProperties(nodeCount++, NeuronType.Input)); neuronGene = new NeuronGene(null, neuronNode.UniqueID, NeuronGene.INPUT_LAYER, NeuronType.Input, actFunct); inputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); } // Create output neuron genes. //actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn"); for (int i = 0; i < outputNeuronCount; i++) { actFunct = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid"); //actFunct = ActivationFunctionFactory.GetRandomActivationFunction(neatParameters); //TODO: DAVID proper activation function //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Output, actFunct); neuronNode = win.findOrInsertNodeWithProperties(idGenerator, WINNode.NodeWithProperties(nodeCount++, NeuronType.Output)); neuronGene = new NeuronGene(null, neuronNode.UniqueID, NeuronGene.OUTPUT_LAYER, NeuronType.Output, actFunct); outputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); } int currentConnCount = 0; WINConnection winConn; // Loop over all possible connections from input to output nodes and create a number of connections based upon // connectionProportion. foreach (NeuronGene targetNeuronGene in outputNeuronGeneList) { foreach (NeuronGene sourceNeuronGene in inputNeuronGeneList) { // Always generate an ID even if we aren't going to use it. This is necessary to ensure connections // between the same neurons always have the same ID throughout the generated population. //PAUL NOTE: //instead of generating and not using and id, we use the target and connection properties to uniquely identify a connection in WIN //uint connectionInnovationId = idGenerator.NextInnovationId; if (Utilities.NextDouble() < connectionProportion) { // Ok lets create a connection. //first we search or create the winconnection object winConn = win.findOrInsertConnectionWithProperties(idGenerator, WINConnection.ConnectionWithProperties(currentConnCount, sourceNeuronGene.InnovationId, targetNeuronGene.InnovationId)); //our winconn will have our innovationID, and our weight like normal //this will also respect the idgenerator, since it gets sent in as well, for legacy purposes connectionGeneList.Add(new ConnectionGene(winConn.UniqueID, sourceNeuronGene.InnovationId, targetNeuronGene.InnovationId, (Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0) ); //(Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0)); // Weight 0 +-5 } currentConnCount++; } } //WIN will eventually be in control of all the genomes that are created as well, but not quite yet! //TODO: WIN should be generating genomeIDs explicitly // Don't create any hidden nodes at this point. Fundamental to the NEAT way is to start minimally! return(new NeatGenome(idGenerator.NextGenomeId, neuronGeneList, connectionGeneList, inputNeuronCount, outputNeuronCount)); }
public static IGenome CreateGenomePreserveID(NeatParameters neatParameters, IdGenerator idGenerator, int inputNeuronCount, int outputNeuronCount, float connectionProportion) { IActivationFunction actFunct; NeuronGene neuronGene; // temp variable. NeuronGeneList inputNeuronGeneList = new NeuronGeneList(); // includes bias neuron. NeuronGeneList outputNeuronGeneList = new NeuronGeneList(); NeuronGeneList neuronGeneList = new NeuronGeneList(); ConnectionGeneList connectionGeneList = new ConnectionGeneList(); int nodeCount = 0; WINManager win = WINManager.SharedWIN; // IMPORTANT NOTE: The neurons must all be created prior to any connections. That way all of the genomes // will obtain the same innovation ID's for the bias,input and output nodes in the initial population. // Create a single bias neuron. //TODO: DAVID proper activation function change to NULL? actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn"); //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Bias, actFunct); WINNode neuronNode = win.findOrInsertNodeWithProperties(idGenerator, WINNode.NodeWithProperties(nodeCount++, NeuronType.Bias) ); neuronGene = new NeuronGene(null, neuronNode.UniqueID, NeuronGene.INPUT_LAYER, NeuronType.Bias, actFunct); inputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); // Create input neuron genes. actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn"); for (int i = 0; i < inputNeuronCount; i++) { //TODO: DAVID proper activation function change to NULL? //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Input, actFunct); neuronNode = win.findOrInsertNodeWithProperties(idGenerator, WINNode.NodeWithProperties(nodeCount++, NeuronType.Input)); neuronGene = new NeuronGene(null, neuronNode.UniqueID, NeuronGene.INPUT_LAYER, NeuronType.Input, actFunct); inputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); } // Create output neuron genes. //actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn"); for (int i = 0; i < outputNeuronCount; i++) { actFunct = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid"); //actFunct = ActivationFunctionFactory.GetRandomActivationFunction(neatParameters); //TODO: DAVID proper activation function //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Output, actFunct); neuronNode = win.findOrInsertNodeWithProperties(idGenerator, WINNode.NodeWithProperties(nodeCount++, NeuronType.Output)); neuronGene = new NeuronGene(null, neuronNode.UniqueID, NeuronGene.OUTPUT_LAYER, NeuronType.Output, actFunct); outputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); } int currentConnCount = 0; WINConnection winConn; // Loop over all possible connections from input to output nodes and create a number of connections based upon // connectionProportion. foreach (NeuronGene targetNeuronGene in outputNeuronGeneList) { foreach (NeuronGene sourceNeuronGene in inputNeuronGeneList) { // Always generate an ID even if we aren't going to use it. This is necessary to ensure connections // between the same neurons always have the same ID throughout the generated population. //PAUL NOTE: //instead of generating and not using and id, we use the target and connection properties to uniquely identify a connection in WIN //uint connectionInnovationId = idGenerator.NextInnovationId; if (Utilities.NextDouble() < connectionProportion) { // Ok lets create a connection. //first we search or create the winconnection object winConn = win.findOrInsertConnectionWithProperties(idGenerator, WINConnection.ConnectionWithProperties(currentConnCount, sourceNeuronGene.InnovationId, targetNeuronGene.InnovationId)); //our winconn will have our innovationID, and our weight like normal //this will also respect the idgenerator, since it gets sent in as well, for legacy purposes connectionGeneList.Add(new ConnectionGene(winConn.UniqueID, sourceNeuronGene.InnovationId, targetNeuronGene.InnovationId, (Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0) ); //(Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0)); // Weight 0 +-5 } currentConnCount++; } } //WIN will eventually be in control of all the genomes that are created as well, but not quite yet! //TODO: WIN should be generating genomeIDs explicitly // Don't create any hidden nodes at this point. Fundamental to the NEAT way is to start minimally! return new NeatGenome(idGenerator.NextGenomeId, neuronGeneList, connectionGeneList, inputNeuronCount, outputNeuronCount); }
/// <summary> /// Add a new node to the Genome. We do this by removing a connection at random and inserting /// a new node and two new connections that make the same circuit as the original connection. /// /// This way the new node is properly integrated into the network from the outset. /// </summary> /// <param name="ea"></param> private void Mutate_AddNode(NeatParameters neatParameters, IdGenerator idGen, Hashtable NewNeuronGeneStructTable) { if(connectionGeneList.Count==0) return; // Select a connection at random. int connectionToReplaceIdx = (int)Math.Floor(Utilities.NextDouble() * connectionGeneList.Count); ConnectionGene connectionToReplace = connectionGeneList[connectionToReplaceIdx]; // Delete the existing connection. JOEL: Why delete old connection? //connectionGeneList.RemoveAt(connectionToReplaceIdx); // Check if this connection has already been split on another genome. If so then we should re-use the // neuron ID and two connection ID's so that matching structures within the population maintain the same ID. object existingNeuronGeneStruct = NewNeuronGeneStructTable[connectionToReplace.InnovationId]; NeuronGene newNeuronGene; ConnectionGene newConnectionGene1; ConnectionGene newConnectionGene2; IActivationFunction actFunct; WINManager win = WINManager.SharedWIN; WINNode node; WINConnection connection; if(existingNeuronGeneStruct==null) { // No existing matching structure, so generate some new ID's. //TODO: DAVID proper random activation function // Replace connectionToReplace with two new connections and a neuron. actFunct=ActivationFunctionFactory.GetRandomActivationFunction(neatParameters); //newNeuronGene = new NeuronGene(ea.NextInnovationId, NeuronType.Hidden, actFunct); //we should be making a call to WIN anytime we call for a new innovationID //here we create a new node, and two new connections //we don't send in a genomeID here, so we get it set for us! node = win.createWINNode(new PropertyObject() { { WINNode.SNodeString, NeuronType.Hidden.ToString()}}); newNeuronGene = new NeuronGene(null, node.UniqueID, (neuronGeneList.GetNeuronById(connectionToReplace.SourceNeuronId).Layer + neuronGeneList.GetNeuronById(connectionToReplace.TargetNeuronId).Layer) / 2.0, NeuronType.Hidden, actFunct); //we don't send in any uniqueIDs so they are generated for us, and we update our idGen object connection = win.createWINConnection(WINConnection.ConnectionWithProperties(connectionToReplace.SourceNeuronId, newNeuronGene.InnovationId), idGen); newConnectionGene1 = new ConnectionGene(connection.UniqueID, connection.SourceID, connection.TargetID , 1.0); //we don't send in any uniqueIDs so they are generated for us, and we update our idGen object connection = win.createWINConnection(WINConnection.ConnectionWithProperties(newNeuronGene.InnovationId, connectionToReplace.TargetNeuronId), idGen); newConnectionGene2 = new ConnectionGene(connection.UniqueID, connection.SourceID, connection.TargetID, connectionToReplace.Weight); // Register the new ID's with NewNeuronGeneStructTable. NewNeuronGeneStructTable.Add(connectionToReplace.InnovationId, new NewNeuronGeneStruct(newNeuronGene, newConnectionGene1, newConnectionGene2)); } else { // An existing matching structure has been found. Re-use its ID's //Since we don't call idGen.nextinnovationID, we don't have to create anything new //however, we need to note the change in weights override previous weight values //this should be documented in WIN - either explicitly (calling a function to note changes) or implicitly (by scanning the changes in a saved genome) //Probably explicitly, calling a mutate function for a SingleStep (since we are in the SingleStep process of creating new individuals) //TODO: DAVID proper random activation function // Replace connectionToReplace with two new connections and a neuron. actFunct = ActivationFunctionFactory.GetRandomActivationFunction(neatParameters); NewNeuronGeneStruct tmpStruct = (NewNeuronGeneStruct)existingNeuronGeneStruct; //newNeuronGene = new NeuronGene(tmpStruct.NewNeuronGene.InnovationId, NeuronType.Hidden, actFunct); newNeuronGene = new NeuronGene(null, tmpStruct.NewNeuronGene.InnovationId, tmpStruct.NewNeuronGene.Layer, NeuronType.Hidden, actFunct); newConnectionGene1 = new ConnectionGene(tmpStruct.NewConnectionGene_Input.InnovationId, connectionToReplace.SourceNeuronId, newNeuronGene.InnovationId, 1.0); newConnectionGene2 = new ConnectionGene(tmpStruct.NewConnectionGene_Output.InnovationId, newNeuronGene.InnovationId, connectionToReplace.TargetNeuronId, connectionToReplace.Weight); } // Add the new genes to the genome. neuronGeneList.Add(newNeuronGene); connectionGeneList.InsertIntoPosition(newConnectionGene1); connectionGeneList.InsertIntoPosition(newConnectionGene2); }
private void Mutate_AddModule(NeatParameters neatParameters, IdGenerator idGen, Hashtable NewConnectionGeneTable) { // Find all potential inputs, or quit if there are not enough. // Neurons cannot be inputs if they are dummy input nodes created for another module. NeuronGeneList potentialInputs = new NeuronGeneList(); foreach (NeuronGene n in neuronGeneList) { if (!(n.ActivationFunction is ModuleInputNeuron)) { potentialInputs.Add(n); } } if (potentialInputs.Count < 1) return; // Find all potential outputs, or quit if there are not enough. // Neurons cannot be outputs if they are dummy input or output nodes created for another module, or network input or bias nodes. NeuronGeneList potentialOutputs = new NeuronGeneList(); foreach (NeuronGene n in neuronGeneList) { if (n.NeuronType != NeuronType.Bias && n.NeuronType != NeuronType.Input && !(n.ActivationFunction is ModuleInputNeuron) && !(n.ActivationFunction is ModuleOutputNeuron)) { potentialOutputs.Add(n); } } if (potentialOutputs.Count < 1) return; // Pick a new function for the new module. IModule func = ModuleFactory.GetRandom(); WINManager win = WINManager.SharedWIN; WINNode node; WINConnection connection; // Choose inputs uniformly at random, with replacement. // Create dummy neurons to represent the module's inputs. // Create connections between the input nodes and the dummy neurons. IActivationFunction inputFunction = ActivationFunctionFactory.GetActivationFunction("ModuleInputNeuron"); List<long> inputDummies = new List<long>(func.InputCount); for (int i = 0; i < func.InputCount; i++) { //we are calling nextinnovationID, this is the place for WIN! //in reality, win should know the activation function as well, but that's not currently implemented //here we create a new node, and two new connections //we don't send in a genomeID here, so we get it set for us! //do we need to inform it of the activation function? I think so? node = win.createWINNode(new PropertyObject() { { WINNode.SNodeString, NeuronType.Hidden.ToString()}}); NeuronGene newNeuronGene = new NeuronGene(node.UniqueID, NeuronType.Hidden, inputFunction); neuronGeneList.Add(newNeuronGene); long sourceId = potentialInputs[Utilities.Next(potentialInputs.Count)].InnovationId; long targetId = newNeuronGene.InnovationId; inputDummies.Add(targetId); //aha! we must call the innovationID again, we check against win //we don't send in any uniqueIDs so they are generated for us, and we update our idGen object connection = win.createWINConnection( WINConnection.ConnectionWithProperties(sourceId, targetId), idGen); // Create a new connection with a new ID and add it to the Genome. ConnectionGene newConnectionGene = new ConnectionGene(connection.UniqueID, connection.SourceID, connection.TargetID, (Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0 ); // Register the new connection with NewConnectionGeneTable. ConnectionEndpointsStruct connectionKey = new ConnectionEndpointsStruct(sourceId, targetId); NewConnectionGeneTable.Add(connectionKey, newConnectionGene); // Add the new gene to this genome. We have a new ID so we can safely append the gene to the end // of the list without risk of breaking the innovation ID order. connectionGeneList.Add(newConnectionGene); } // Choose outputs uniformly at random, with replacement. // Create dummy neurons to represent the module's outputs. // Create connections between the output nodes and the dummy neurons. IActivationFunction outputFunction = ActivationFunctionFactory.GetActivationFunction("ModuleOutputNeuron"); List<long> outputDummies = new List<long>(func.OutputCount); for (int i = 0; i < func.OutputCount; i++) { node = win.createWINNode(new PropertyObject() { { WINNode.SNodeString, NeuronType.Hidden.ToString() } }); NeuronGene newNeuronGene = new NeuronGene(node.UniqueID, NeuronType.Hidden, outputFunction); neuronGeneList.Add(newNeuronGene); long sourceId = newNeuronGene.InnovationId; long targetId = potentialOutputs[Utilities.Next(potentialOutputs.Count)].InnovationId; outputDummies.Add(sourceId); connection = win.createWINConnection( WINConnection.ConnectionWithProperties(sourceId, targetId), idGen); // Create a new connection with a new ID and add it to the Genome. ConnectionGene newConnectionGene = new ConnectionGene(connection.UniqueID, connection.SourceID, connection.TargetID, (Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0 ); //new ConnectionGene(idGen.NextInnovationId, sourceId, targetId, //(Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0); // Register the new connection with NewConnectionGeneTable. ConnectionEndpointsStruct connectionKey = new ConnectionEndpointsStruct(sourceId, targetId); NewConnectionGeneTable.Add(connectionKey, newConnectionGene); // Add the new gene to this genome. We have a new ID so we can safely append the gene to the end // of the list without risk of breaking the innovation ID order. connectionGeneList.Add(newConnectionGene); } // Pick a new ID for the new module and create it. // Modules do not participate in history comparisons, so we will always create a new innovation ID. // We can change this here if it becomes a problem. //TODO: Paul check win conditions here //this is confusing from a WIN perspective, I don't know if I'm going to support modules //I can create a generic NextInnovationID object, but don't know if it's worth it ModuleGene newModule = new ModuleGene(idGen.NextInnovationId, func, inputDummies, outputDummies); moduleGeneList.Add(newModule); }
/// <summary> /// Create a default minimal genome that describes a NN with the given number of inputs and outputs. /// </summary> /// <returns></returns> public static IGenome CreateGenome(NeatParameters neatParameters, IdGenerator idGenerator, int inputNeuronCount, int outputNeuronCount, float connectionProportion, bool neatBrain) { IActivationFunction actFunct; NeuronGene neuronGene; // temp variable. NeuronGeneList inputNeuronGeneList = new NeuronGeneList(); // includes bias neuron. NeuronGeneList outputNeuronGeneList = new NeuronGeneList(); NeuronGeneList neuronGeneList = new NeuronGeneList(); ConnectionGeneList connectionGeneList = new ConnectionGeneList(); // IMPORTANT NOTE: The neurons must all be created prior to any connections. That way all of the genomes // will obtain the same innovation ID's for the bias,input and output nodes in the initial population. // Create a single bias neuron. //TODO: DAVID proper activation function change to NULL? actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn"); neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Bias, actFunct, 0f); inputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); // Create input neuron genes. for (int i = 0; i < inputNeuronCount; i++) { //TODO: DAVID proper activation function change to NULL? neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Input, actFunct, 0f); inputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); } // Create output neuron genes. if (neatBrain) { actFunct = ActivationFunctionFactory.GetActivationFunction("SteepenedSigmoidApproximation"); } else { actFunct = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid"); } for (int i = 0; i < outputNeuronCount; i++) { //actFunct = ActivationFunctionFactory.GetRandomActivationFunction(neatParameters); //TODO: DAVID proper activation function neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Output, actFunct, 1f); outputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); } // Loop over all possible connections from input to output nodes and create a number of connections based upon // connectionProportion. foreach (NeuronGene targetNeuronGene in outputNeuronGeneList) { foreach (NeuronGene sourceNeuronGene in inputNeuronGeneList) { // Always generate an ID even if we aren't going to use it. This is necessary to ensure connections // between the same neurons always have the same ID throughout the generated population. uint connectionInnovationId = idGenerator.NextInnovationId; if (Utilities.NextDouble() < connectionProportion) { // Ok lets create a connection. connectionGeneList.Add(new ConnectionGene(connectionInnovationId, sourceNeuronGene.InnovationId, targetNeuronGene.InnovationId, (Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0)); // Weight 0 +-5 } } } // Don't create any hidden nodes at this point. Fundamental to the NEAT way is to start minimally! return(new NeatGenome(idGenerator.NextGenomeId, neuronGeneList, connectionGeneList, inputNeuronCount, outputNeuronCount)); }
/// <summary> /// Copy constructor. /// </summary> /// <param name="copyFrom"></param> public NeuronGene(NeuronGene copyFrom) { this.innovationId = copyFrom.innovationId; this.neuronType = copyFrom.neuronType; this.activationFunction = copyFrom.activationFunction; }
// Schrum: Simple form of Module Mutation, MM(P) private void Module_Mutation_Previous(EvolutionAlgorithm ea) { // Push all output neurons together this.neuronGeneList.SortByNeuronOrder(); int numModules = this.outputNeuronCount / this.outputsPerPolicy; // Should evenly divide int randomModule = Utilities.Next(numModules); // Because outputs come after inputs. // Although list is 0-indexed, the +1 is needed because the bias does not count as an input double outputLayer = neuronGeneList[1 + inputNeuronCount].Layer; // Create the new module for (int i = 0; i < outputsPerPolicy; i++) { IActivationFunction outputActFunction = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid"); NeuronGene newNeuronGene = new NeuronGene(null, ea.NextInnovationId, outputLayer, NeuronType.Output, outputActFunction); neuronGeneList.Add(newNeuronGene); // Link to the new neuron: bias, then inputs, then appropriate module, then neuron within that module uint sourceNeuron = neuronGeneList[1 + inputNeuronCount + (randomModule * outputsPerPolicy) + i].InnovationId; ConnectionGene connection = new ConnectionGene(ea.NextInnovationId, sourceNeuron, newNeuronGene.InnovationId, 1.0); connectionGeneList.InsertIntoPosition(connection); this.outputNeuronCount++; // Increase number of outputs } // Schrum: Debugging //Console.WriteLine("MM(P): outputNeuronCount=" + outputNeuronCount); // Schrum: More debugging /* this.neuronGeneList.SortByInnovationId(); XmlDocument doc = new XmlDocument(); XmlGenomeWriterStatic.Write(doc, (NeatGenome)this); FileInfo oFileInfo = new FileInfo("MMPNet.xml"); doc.Save(oFileInfo.FullName); */ }