/// <summary> /// Create a genome with the provided internal state/definition data/objects. /// Overridable method to allow alternative NeatGenome sub-classes to be used. /// </summary> public virtual NeatGenome CreateGenome(uint id, uint birthGeneration, NeuronGeneList neuronGeneList, ConnectionGeneList connectionGeneList, int inputNeuronCount, int outputNeuronCount, bool rebuildNeuronGeneConnectionInfo) { return(new NeatGenome(this, id, birthGeneration, neuronGeneList, connectionGeneList, inputNeuronCount, outputNeuronCount, rebuildNeuronGeneConnectionInfo)); }
/// <summary> /// Sort connection genes into ascending order by their innovation IDs. /// /// Sorts the non-protected connections in the active module. The /// protected connections are sorted by construction, and we do not want /// to interfere with older modules. /// </summary> public void SortByInnovationId() { // Fist we get only the active part in a new List. ConnectionGeneList littleList = new ConnectionGeneList(Count - _firstActiveIndex); for (int i = _firstActiveIndex; i < Count; ++i) { littleList.Add(this[i]); } // Now we sort only the active part... littleList.Sort(__connectionGeneComparer); // And we copy back these values. int small_indx = 0; for (int i = _firstActiveIndex; i < Count; ++i) { this[i] = littleList[small_indx]; ++small_indx; } }
/// <summary> /// Creates a single randomly initialised genome. /// A random set of connections are made form the input to the output neurons, the number of /// connections made is based on the NeatGenomeParameters.InitialInterconnectionsProportion /// which specifies the proportion of all possible input-output connections to be made in /// initial genomes. /// /// The connections that are made are allocated innovation IDs in a consistent manner across /// the initial population of genomes. To do this we allocate IDs sequentially to all possible /// interconnections and then randomly select some proportion of connections for inclusion in the /// genome. In addition, for this scheme to work the innovation ID generator must be reset to zero /// prior to each call to CreateGenome(), and a test is made to ensure this is the case. /// /// The consistent allocation of innovation IDs ensure that equivalent connections in different /// genomes have the same innovation ID, and although this isn't strictly necessary it is /// required for sexual reproduction to work effectively - like structures are detected by comparing /// innovation IDs only. /// </summary> /// <param name="birthGeneration">The current evolution algorithm generation. /// Assigned to the new genome as its birth generation.</param> public NeatGenome CreateGenome(uint birthGeneration) { NeuronGeneList neuronGeneList = new NeuronGeneList(_inputNeuronCount + _outputNeuronCount); NeuronGeneList inputNeuronGeneList = new NeuronGeneList(_inputNeuronCount); // includes single bias neuron. NeuronGeneList outputNeuronGeneList = new NeuronGeneList(_outputNeuronCount); // Create a single bias neuron. uint biasNeuronId = _innovationIdGenerator.NextId; if (0 != biasNeuronId) { // The ID generator must be reset before calling this method so that all generated genomes use the // same innovation ID for matching neurons and structures. throw new SharpNeatException("IdGenerator must be reset before calling CreateGenome(uint)"); } // Note. Genes within nGeneList must always be arranged according to the following layout plan. // Bias - single neuron. Innovation ID = 0 // Input neurons. // Output neurons. // Hidden neurons. NeuronGene neuronGene = CreateNeuronGene(biasNeuronId, NodeType.Bias); inputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); // Create input neuron genes. for (int i = 0; i < _inputNeuronCount; i++) { neuronGene = CreateNeuronGene(_innovationIdGenerator.NextId, NodeType.Input); inputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); } // Create output neuron genes. for (int i = 0; i < _outputNeuronCount; i++) { neuronGene = CreateNeuronGene(_innovationIdGenerator.NextId, NodeType.Output); outputNeuronGeneList.Add(neuronGene); neuronGeneList.Add(neuronGene); } // Define all possible connections between the input and output neurons (fully interconnected). int srcCount = inputNeuronGeneList.Count; int tgtCount = outputNeuronGeneList.Count; ConnectionDefinition[] connectionDefArr = new ConnectionDefinition[srcCount * tgtCount]; for (int srcIdx = 0, i = 0; srcIdx < srcCount; srcIdx++) { for (int tgtIdx = 0; tgtIdx < tgtCount; tgtIdx++) { connectionDefArr[i++] = new ConnectionDefinition(_innovationIdGenerator.NextId, srcIdx, tgtIdx); } } // Shuffle the array of possible connections. SortUtils.Shuffle(connectionDefArr, _rng); // Select connection definitions from the head of the list and convert them to real connections. // We want some proportion of all possible connections but at least one (Connectionless genomes are not allowed). int connectionCount = (int)NumericsUtils.ProbabilisticRound( (double)connectionDefArr.Length * _neatGenomeParamsComplexifying.InitialInterconnectionsProportion, _rng); connectionCount = Math.Max(1, connectionCount); // Create the connection gene list and populate it. ConnectionGeneList connectionGeneList = new ConnectionGeneList(connectionCount); for (int i = 0; i < connectionCount; i++) { ConnectionDefinition def = connectionDefArr[i]; NeuronGene srcNeuronGene = inputNeuronGeneList[def._sourceNeuronIdx]; NeuronGene tgtNeuronGene = outputNeuronGeneList[def._targetNeuronIdx]; ConnectionGene cGene = new ConnectionGene(def._innovationId, srcNeuronGene.InnovationId, tgtNeuronGene.InnovationId, GenerateRandomConnectionWeight()); connectionGeneList.Add(cGene); // Register connection with endpoint neurons. srcNeuronGene.TargetNeurons.Add(cGene.TargetNodeId); tgtNeuronGene.SourceNeurons.Add(cGene.SourceNodeId); } // Ensure connections are sorted. connectionGeneList.SortByInnovationId(); // Create and return the completed genome object. return(CreateGenome(_genomeIdGenerator.NextId, birthGeneration, neuronGeneList, connectionGeneList, _inputNeuronCount, _outputNeuronCount, false)); }
/// <summary> /// Reads a NeatGenome from XML. /// </summary> /// <param name="xr">The XmlReader to read from.</param> /// <param name="nodeFnIds">Indicates if node activation function IDs /// should be read. They are required for HyperNEAT genomes but not for NEAT</param> public static NeatGenome ReadGenome(XmlReader xr, bool nodeFnIds) { // Find <Network>. XmlIoUtils.MoveToElement(xr, false, __ElemNetwork); int initialDepth = xr.Depth; // Read genome ID attribute if present. Otherwise default to zero; // it's the caller's responsibility to check IDs are unique and // in-line with the genome factory's ID generators. string genomeIdStr = xr.GetAttribute(__AttrId); uint genomeId; uint.TryParse(genomeIdStr, out genomeId); // Read birthGeneration attribute if present. Otherwise default to zero. string birthGenStr = xr.GetAttribute(__AttrBirthGeneration); uint birthGen; uint.TryParse(birthGenStr, out birthGen); // Read fitness attribute if present. Otherwise default to zero. string fitnessStr = xr.GetAttribute(__AttrFitness); double fitness; double.TryParse(fitnessStr, out fitness); // Find <Nodes>. XmlIoUtils.MoveToElement(xr, true, __ElemNodes); // Create a reader over the <Nodes> sub-tree. int inputNodeCount = 0; int outputNodeCount = 0; int regulatoryCount = 0; int localInCount = 0; int localOutCount = 0; // Used to count local input and output neurons (which are not // found in the base module = 0) int activeModule = 1; NeuronGeneList nGeneList = new NeuronGeneList(); using (XmlReader xrSubtree = xr.ReadSubtree()) { // Re-scan for the root <Nodes> element. XmlIoUtils.MoveToElement(xrSubtree, false); // Move to first node elem. XmlIoUtils.MoveToElement(xrSubtree, true, __ElemNode); // Read node elements. do { NodeType neuronType = NetworkXmlIO.ReadAttributeAsNodeType(xrSubtree, __AttrType); uint id = XmlIoUtils.ReadAttributeAsUInt(xrSubtree, __AttrId); int functionId = GetFunctionId(neuronType); int module = XmlIoUtils.ReadAttributeAsInt(xrSubtree, __AttrModule); int pandemonium; // If we have a regulatory neuron, we read its pandemonium label if (neuronType == NodeType.Regulatory) { pandemonium = XmlIoUtils.ReadAttributeAsInt(xrSubtree, __AttrPandemonium); } // Otherwise it is simply -1 else { pandemonium = -1; } double[] auxState = null; if (nodeFnIds) { // Read activation fn ID. functionId = XmlIoUtils.ReadAttributeAsInt(xrSubtree, __AttrActivationFunctionId); // Read aux state as comma seperated list of real values. auxState = XmlIoUtils.ReadAttributeAsDoubleArray(xrSubtree, __AttrAuxState); } NeuronGene nGene = new NeuronGene(id, neuronType, functionId, module, pandemonium, auxState); nGeneList.Add(nGene); // Track the number of input and output nodes. switch (neuronType) { case NodeType.Input: ++inputNodeCount; break; case NodeType.Output: ++outputNodeCount; break; case NodeType.Regulatory: ++regulatoryCount; break; case NodeType.Local_Input: if (module == activeModule) { ++localInCount; } else { // Found a new module, discard previous count activeModule = module; localInCount = 1; localOutCount = 0; } break; case NodeType.Local_Output: // Here we do not care about the correct module, // because that has been considered in the local input // count (and local input always comes first). ++localOutCount; break; } }while(xrSubtree.ReadToNextSibling(__ElemNode)); } // Find <Connections>. XmlIoUtils.MoveToElement(xr, false, __ElemConnections); // Create a reader over the <Connections> sub-tree. ConnectionGeneList cGeneList = new ConnectionGeneList(); using (XmlReader xrSubtree = xr.ReadSubtree()) { // Re-scan for the root <Connections> element. XmlIoUtils.MoveToElement(xrSubtree, false); // Move to first connection elem. string localName = XmlIoUtils.MoveToElement(xrSubtree, true); if (localName == __ElemConnection) { // We have at least one connection. // Read connection elements. do { uint id = XmlIoUtils.ReadAttributeAsUInt(xrSubtree, __AttrId); uint srcId = XmlIoUtils.ReadAttributeAsUInt(xrSubtree, __AttrSourceId); uint tgtId = XmlIoUtils.ReadAttributeAsUInt(xrSubtree, __AttrTargetId); double weight = XmlIoUtils.ReadAttributeAsDouble(xrSubtree, __AttrWeight); int module = XmlIoUtils.ReadAttributeAsInt(xrSubtree, __AttrModule); bool protect = XmlIoUtils.ReadAttributeAsBool(xrSubtree, __AttrProtected); ConnectionGene cGene = new ConnectionGene(id, srcId, tgtId, weight, module, protect); cGeneList.Add(cGene); }while(xrSubtree.ReadToNextSibling(__ElemConnection)); } } // Move the reader beyond the closing tags </Connections> and </Network>. do { if (xr.Depth <= initialDepth) { break; } }while(xr.Read()); // Construct and return loaded NeatGenome. We construct it first // so we can access its properties before leaving. bool rebuildConnectivity = true; // Integrity will fail if we attempt to create the genome before // updating some of the statistics (counts for each type of neurons, // etc). It can be done after that step! bool shouldAssertIntegrity = false; NeatGenome genome = new NeatGenome(null, genomeId, birthGen, nGeneList, cGeneList, rebuildConnectivity, shouldAssertIntegrity); if (genomeFactory != null) { // Note genomeFactory is not fully initialized yet, but it is needed to create // an EvaluationInfo structure. genome.GenomeFactory = genomeFactory; genome.EvaluationInfo.SetFitness(fitness); } // We update count variables. While it is true most are static // variables, loading genomes is done only once, so we can afford // to count them for each genome. genome.Input = inputNodeCount; genome.Output = outputNodeCount; genome.Regulatory = regulatoryCount; genome.LocalIn = localInCount; genome.LocalOut = localOutCount; // We use base for neurons InHiddenModules genome.NeuronGeneList.LocateLastBase(); genome.InHiddenModulesFromLoad(); genome.ActiveConnectionsFromLoad(); // Before this was done only once after creating all genomes. However, // we need these values if we want to perform an integrity check. // The performance overhead is negligible (specially in a one-time method) // and reliability is increased this way. genome.NeuronGeneList.LocateLastBase(); genome.NeuronGeneList.LocateFirstIndex(); genome.ConnectionGeneList.LocateFirstId(); Debug.Assert(genome.PerformIntegrityCheck()); return(genome); }
/// <summary> /// Reads a NeatGenome from XML. /// </summary> /// <param name="xr">The XmlReader to read from.</param> /// <param name="nodeFnIds">Indicates if node activation function IDs should be read. They are required /// for HyperNEAT genomes but not for NEAT</param> public static NeatGenome ReadGenome(XmlReader xr, bool nodeFnIds) { // Find <Network>. XmlIoUtils.MoveToElement(xr, false, __ElemNetwork); int initialDepth = xr.Depth; // Read genome ID attribute if present. Otherwise default to zero; it's the caller's responsibility to // check IDs are unique and in-line with the genome factory's ID generators. string genomeIdStr = xr.GetAttribute(__AttrId); uint genomeId; uint.TryParse(genomeIdStr, out genomeId); // Read birthGeneration attribute if present. Otherwise default to zero. string birthGenStr = xr.GetAttribute(__AttrBirthGeneration); uint birthGen; uint.TryParse(birthGenStr, out birthGen); // Find <Nodes>. XmlIoUtils.MoveToElement(xr, true, __ElemNodes); // Create a reader over the <Nodes> sub-tree. int inputNodeCount = 0; int outputNodeCount = 0; NeuronGeneList nGeneList = new NeuronGeneList(); using (XmlReader xrSubtree = xr.ReadSubtree()) { // Re-scan for the root <Nodes> element. XmlIoUtils.MoveToElement(xrSubtree, false); // Move to first node elem. XmlIoUtils.MoveToElement(xrSubtree, true, __ElemNode); // Read node elements. do { NodeType neuronType = NetworkXmlIO.ReadAttributeAsNodeType(xrSubtree, __AttrType); uint id = XmlIoUtils.ReadAttributeAsUInt(xrSubtree, __AttrId); int functionId = 0; double[] auxState = null; if (nodeFnIds) { // Read activation fn ID. functionId = XmlIoUtils.ReadAttributeAsInt(xrSubtree, __AttrActivationFunctionId); // Read aux state as comma separated list of real values. auxState = XmlIoUtils.ReadAttributeAsDoubleArray(xrSubtree, __AttrAuxState); } NeuronGene nGene = new NeuronGene(id, neuronType, functionId, auxState); nGeneList.Add(nGene); // Track the number of input and output nodes. switch (neuronType) { case NodeType.Input: inputNodeCount++; break; case NodeType.Output: outputNodeCount++; break; } }while(xrSubtree.ReadToNextSibling(__ElemNode)); } // Find <Connections>. XmlIoUtils.MoveToElement(xr, false, __ElemConnections); // Create a reader over the <Connections> sub-tree. ConnectionGeneList cGeneList = new ConnectionGeneList(); using (XmlReader xrSubtree = xr.ReadSubtree()) { // Re-scan for the root <Connections> element. XmlIoUtils.MoveToElement(xrSubtree, false); // Move to first connection elem. string localName = XmlIoUtils.MoveToElement(xrSubtree, true); if (localName == __ElemConnection) { // We have at least one connection. // Read connection elements. do { uint id = XmlIoUtils.ReadAttributeAsUInt(xrSubtree, __AttrId); uint srcId = XmlIoUtils.ReadAttributeAsUInt(xrSubtree, __AttrSourceId); uint tgtId = XmlIoUtils.ReadAttributeAsUInt(xrSubtree, __AttrTargetId); double weight = XmlIoUtils.ReadAttributeAsDouble(xrSubtree, __AttrWeight); ConnectionGene cGene = new ConnectionGene(id, srcId, tgtId, weight); cGeneList.Add(cGene); }while(xrSubtree.ReadToNextSibling(__ElemConnection)); } } // Move the reader beyond the closing tags </Connections> and </Network>. do { if (xr.Depth <= initialDepth) { break; } }while(xr.Read()); // Construct and return loaded NeatGenome. return(new NeatGenome(null, genomeId, birthGen, nGeneList, cGeneList, inputNodeCount, outputNodeCount, true)); }