/// <summary> /// Adds a node by selecting a random connection, disabling it and replacing it by 2 genes. /// The first gene goes from the original input node, to the new node with a weight of 1. /// The second gene goes from the the new node to the old output node with the same weight as the original connection. /// </summary> private void AddNodeMutation() { if (ConnectionGenes.Count == 0) { return; } // Choose a random connection to add a node into ConnectionGene theChosenOne = ConnectionGenes.ElementAt(TrainingRoom.Random.Next(ConnectionGenes.Count)); // Disable it theChosenOne.Enabled = false; // Generate the new node's id uint newNodeId = TrainingRoom.GetAndIncreaseNodeId(); uint oldInId = theChosenOne.InId; uint oldOutId = theChosenOne.OutId; // Create a connectionGene from oldIn (a) to new (c) and from new (c) to oldOut (b) ConnectionGene aToC = new ConnectionGene(Id, oldInId, newNodeId, 1, TrainingRoom.GetInnovationNumber(oldInId, newNodeId)); ConnectionGene cToB = new ConnectionGene(Id, newNodeId, oldOutId, theChosenOne.Weight, TrainingRoom.GetInnovationNumber(newNodeId, oldOutId)); ConnectionGenes.Add(aToC); ConnectionGenes.Add(cToB); _maxInnovation = Math.Max(Math.Max(cToB.InnovationNumber, aToC.InnovationNumber), _maxInnovation); }
private NeatGenome <T> CreateGenomeInner( NeatGenome <T> parent1, NeatGenome <T> parent2, IRandomSource rng) { // Resolve a flag that determines if *all* disjoint genes from the secondary parent will be included in the child genome, or not. // This approach is from SharpNEAT v2.x and is preserved to act as baseline in v4.x, but better strategies may exist. bool includeSecondaryParentGene = DiscreteDistribution.SampleBernoulli(rng, _secondaryParentGeneProbability); // Enumerate over the connection genes in both parents. foreach (var geneIndexPair in EnumerateParentGenes(parent1.ConnectionGenes, parent2.ConnectionGenes)) { // Create a connection gene based on the current position in both parents. ConnectionGene <T>?connGene = CreateConnectionGene( parent1.ConnectionGenes, parent2.ConnectionGenes, geneIndexPair.Item1, geneIndexPair.Item2, includeSecondaryParentGene, rng); if (connGene.HasValue) { // Attempt to add the gene to the child genome we are building. _connGeneListBuilder.TryAddGene(connGene.Value); } } // Convert the genes to the structure required by NeatGenome. ConnectionGenes <T> connGenes = _connGeneListBuilder.ToConnectionGenes(); // Create and return a new genome. return(_genomeBuilder.Create( _genomeIdSeq.Next(), _generationSeq.Peek, connGenes)); }
public void SaveAndLoadGenome() { var metaNeatGenome = new MetaNeatGenome <double>(3, 2, true, new ReLU()); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(metaNeatGenome); // Simple acyclic graph. var connGenes = new ConnectionGenes <double>(6); connGenes[0] = (0, 3, 0.123); connGenes[1] = (1, 3, 1.234); connGenes[2] = (2, 3, -0.5835); connGenes[3] = (2, 4, 5.123456789); connGenes[4] = (2, 5, 2.5); connGenes[5] = (5, 4, 5.4); // Wrap in a genome. NeatGenome <double> genome = genomeBuilder.Create(0, 0, connGenes); // Create a memory stream to save the genome into. using (MemoryStream ms = new(1024)) { // Save the genome. NeatGenomeSaver <double> .Save(genome, ms); // Load the genome. ms.Position = 0; NeatGenomeLoader <double> loader = NeatGenomeLoaderFactory.CreateLoaderDouble(metaNeatGenome); NeatGenome <double> genomeLoaded = loader.Load(ms); // Compare the original genome with the loaded genome. IOTestUtils.CompareGenomes(genome, genomeLoaded); } }
private ConnectionGene <T>?CreateConnectionGene( ConnectionGenes <T> connGenes1, ConnectionGenes <T> connGenes2, int idx1, int idx2, IRandomSource rng) { // Select gene at random if it is present on both parents. if (-1 != idx1 && -1 != idx2) { if (rng.NextBool()) { return(CreateConnectionGene(connGenes1, idx1)); } else { return(CreateConnectionGene(connGenes2, idx2)); } } // Use the primary parent's gene if it has one. if (-1 != idx1) { return(CreateConnectionGene(connGenes1, idx1)); } // Otherwise use the secondary parent's gene stochastically. if (DiscreteDistribution.SampleBernoulli(rng, _secondaryParentGeneProbability)) { return(CreateConnectionGene(connGenes2, idx2)); } return(null); }
/// <summary> /// Adds a node by selecting a random connection, disabling it and replacing it by 2 genes. /// The first gene goes from the original input node, to the new node with a weight of 1. /// The second gene goes from the the new node to the old output node with the same weight as the original connection. /// </summary> /// <param name="trainingRoomSettings">The training room settings.</param> /// <param name="getAndIncreaseNodeIdFunction">The get and increase node id function.</param> /// <param name="innovationFunction">The innovation function.</param> private void AddNodeMutation(TrainingRoomSettings trainingRoomSettings, Func <uint> getAndIncreaseNodeIdFunction, Func <uint, uint, uint> innovationFunction) { // If the connection genes count is 0, then there are not connection genes to mutate. if (ConnectionGenes.Count == 0) { return; } // Find a random connection. ConnectionGene theChosenOne = ConnectionGenes.ElementAt(trainingRoomSettings.Random.Next(ConnectionGenes.Count)); // Disable the connection gene. theChosenOne.Enabled = false; // Generate the new node's id. uint newNodeId = getAndIncreaseNodeIdFunction(); // Get the old in and out node identifiers. uint oldInId = theChosenOne.InNodeIdentifier; uint oldOutId = theChosenOne.OutNodeIdentifier; // Create a connectionGene from oldIn (a) to new (c) and from new (c) to oldOut (b) ConnectionGene aToC = new ConnectionGene(Id, innovationFunction(oldInId, newNodeId), oldInId, newNodeId, 1); ConnectionGene cToB = new ConnectionGene(Id, innovationFunction(newNodeId, oldOutId), newNodeId, oldOutId, theChosenOne.Weight); // Add new connection genes. ConnectionGenes.Add(aToC); ConnectionGenes.Add(cToB); // Update the max innovation. _maxInnovation = Math.Max(Math.Max(cToB.InnovationNumber, aToC.InnovationNumber), _maxInnovation); }
public void TestPartialMatchGenomes() { var connGenes1 = new ConnectionGenes <double>(5); connGenes1[0] = (0, 10, 1.0); connGenes1[1] = (1, 11, 2.0); connGenes1[2] = (2, 12, 3.0); connGenes1[3] = (3, 13, 4.0); connGenes1[4] = (4, 14, 5.0); var connGenes2 = new ConnectionGenes <double>(5); connGenes2[0] = (0, 10, 1.0); connGenes2[1] = (3, 13, 4.0); connGenes2[2] = (100, 11, 2.0); connGenes2[3] = (200, 12, 3.0); connGenes2[4] = (400, 14, 5.0); var distanceMetric = new EuclideanDistanceMetric(); // GetDistance() tests. Assert.Equal(Math.Sqrt(76), distanceMetric.CalcDistance(connGenes1, connGenes2)); Assert.Equal(Math.Sqrt(76), distanceMetric.CalcDistance(connGenes2, connGenes1)); // TestDistance() tests. Assert.True(distanceMetric.TestDistance(connGenes1, connGenes2, Math.Sqrt(76) + 0.001)); Assert.False(distanceMetric.TestDistance(connGenes1, connGenes2, Math.Sqrt(76) - 0.001)); }
private static ConnectionGenes <double> CreateCentroid( Dictionary <DirectedConnection, double> centroidElements, int pointCount) { int length = centroidElements.Count; var connGenes = new ConnectionGenes <double>(length); var connArr = connGenes._connArr; var weightArr = connGenes._weightArr; // Copy the unique coord elements from coordElemTotals into arrays, dividing each element's value // by the total number of coords as we go. double pointCountReciprocol = 1.0 / pointCount; int idx = 0; foreach (var elem in centroidElements) { connArr[idx] = elem.Key; weightArr[idx] = elem.Value * pointCountReciprocol; idx++; } // Sort the connection genes. connGenes.Sort(); return(connGenes); }
private static void AssertConnections( ConnectionGenes <T> connGenes, DirectedGraph digraph, INodeIdMap nodeIndexByIdMap, int[]?connectionIndexMap) { // Connection counts. Debug.Assert(connGenes._connArr.Length == digraph.ConnectionIds.Length); // Connection order. Debug.Assert(SortUtils.IsSortedAscending <DirectedConnection>(connGenes._connArr)); Debug.Assert(IsSortedAscending(digraph.ConnectionIds)); // Connection node ID mappings. DirectedConnection[] connArr = connGenes._connArr; ReadOnlySpan <int> srcIds = digraph.ConnectionIds.GetSourceIdSpan(); ReadOnlySpan <int> tgtIds = digraph.ConnectionIds.GetTargetIdSpan(); for (int i = 0; i < connGenes._connArr.Length; i++) { // Determine the index of the equivalent connection in the digraph. int genomeConnIdx = (connectionIndexMap is null) ? i : connectionIndexMap[i]; Debug.Assert(nodeIndexByIdMap.Map(connArr[genomeConnIdx].SourceId) == srcIds[i]); Debug.Assert(nodeIndexByIdMap.Map(connArr[genomeConnIdx].TargetId) == tgtIds[i]); } }
public static NeatGenome <double> CreateNeatGenome2( INeatGenomeBuilder <double> genomeBuilder) { var connGenes = new ConnectionGenes <double>(12); connGenes[0] = (0, 3, 0.3); connGenes[1] = (0, 4, 0.4); connGenes[2] = (0, 5, 0.5); connGenes[3] = (3, 6, 3.6); connGenes[4] = (4, 7, 4.7); connGenes[5] = (5, 8, 5.8); connGenes[6] = (6, 9, 6.9); connGenes[7] = (7, 10, 7.1); connGenes[8] = (8, 11, 8.11); connGenes[9] = (9, 1, 9.1); connGenes[10] = (10, 1, 10.1); connGenes[11] = (11, 1, 11.1); var genome = genomeBuilder.Create(1, 0, connGenes); return(genome); }
public static NeatGenome <double> CreateNeatGenome( MetaNeatGenome <double> metaNeatGenome, INeatGenomeBuilder <double> genomeBuilder) { var connGenes = new ConnectionGenes <double>(12); connGenes[0] = (0, 3, 0.1); connGenes[1] = (0, 4, 0.2); connGenes[2] = (0, 5, 0.3); connGenes[3] = (3, 6, 0.4); connGenes[4] = (4, 7, 0.5); connGenes[5] = (5, 8, 0.6); connGenes[6] = (6, 9, 0.7); connGenes[7] = (7, 10, 0.8); connGenes[8] = (8, 11, 0.9); connGenes[9] = (9, 1, 1.0); connGenes[10] = (10, 1, 1.1); connGenes[11] = (11, 1, 1.2); var genome = genomeBuilder.Create(0, 0, connGenes); return(genome); }
public void Simple_DefinedNodes() { var metaNeatGenome = new MetaNeatGenome <double>(0, 10, false, new ReLU()); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(metaNeatGenome); // Simple acyclic graph. var connGenes = new ConnectionGenes <double>(4); connGenes[0] = (10, 13, 0.0); connGenes[1] = (11, 13, 1.0); connGenes[2] = (12, 13, 2.0); connGenes[3] = (12, 14, 3.0); // Wrap in a genome. var genome = genomeBuilder.Create(0, 0, connGenes); // Note. The genome builder creates a digraph representation of the genome and attaches/caches it on the genome object. var digraph = genome.DirectedGraph; // The graph should be unchanged from the input connections. CompareConnectionLists(connGenes, digraph.ConnectionIdArrays); // Check the node count. Assert.AreEqual(15, digraph.TotalNodeCount); }
/// <summary> /// Create a digraph from the a set of connection genes. /// </summary> /// <typeparam name="T">Neural net numeric data type.</typeparam> /// <param name="metaNeatGenome">Genome metadata.</param> /// <param name="connGenes">Connection genes.</param> /// <param name="nodeIndexByIdMap">A mapping from node IDs to node indexes.</param> /// <returns>A new instance of <see cref="DirectedGraph"/>.</returns> public static DirectedGraph CreateDirectedGraph <T>( MetaNeatGenome <T> metaNeatGenome, ConnectionGenes <T> connGenes, INodeIdMap nodeIndexByIdMap) where T : struct { // Extract/copy the neat genome connectivity graph into a ConnectionIds structure. // Notes. // The array contents will be manipulated, so copying this avoids modification of the genome's // connection gene list. // The IDs are substituted for node indexes here. CopyAndMapIds( connGenes._connArr, nodeIndexByIdMap, out ConnectionIds connIds); // Construct a new DirectedGraph. var digraph = new DirectedGraph( metaNeatGenome.InputNodeCount, metaNeatGenome.OutputNodeCount, nodeIndexByIdMap.Count, connIds); return(digraph); }
private NeatGenome <double> CreateGenome1(MetaNeatGenome <double> metaNeatGenome) { var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(metaNeatGenome); // Define a genome that matches the one defined in example1.genome. var connGenes = new ConnectionGenes <double>(12); connGenes[0] = (0, 5, 0.5); connGenes[1] = (0, 7, 0.7); connGenes[2] = (0, 3, 0.3); connGenes[3] = (1, 5, 1.5); connGenes[4] = (1, 7, 1.7); connGenes[5] = (1, 3, 1.3); connGenes[6] = (1, 6, 1.6); connGenes[7] = (1, 8, 1.8); connGenes[8] = (1, 4, 1.4); connGenes[9] = (2, 6, 2.6); connGenes[10] = (2, 8, 2.8); connGenes[11] = (2, 4, 2.4); // Ensure the connections are sorted correctly. connGenes.Sort(); // Wrap in a genome. NeatGenome <double> genome = genomeBuilder.Create(0, 0, connGenes); return(genome); }
public void TestMismatchGenomes() { var connGenes1 = new ConnectionGenes <double>(5); connGenes1[0] = (0, 10, 1.0); connGenes1[1] = (1, 11, 2.0); connGenes1[2] = (2, 12, 3.0); connGenes1[3] = (3, 13, 4.0); connGenes1[4] = (4, 14, 5.0); var connGenes2 = new ConnectionGenes <double>(5); connGenes2[0] = (0, 100, 1.0); connGenes2[1] = (1, 110, 2.0); connGenes2[2] = (2, 120, 3.0); connGenes2[3] = (3, 130, 4.0); connGenes2[4] = (4, 140, 5.0); var distanceMetric = new EuclideanDistanceMetric();; // GetDistance() tests. Assert.AreEqual(Math.Sqrt(110), distanceMetric.GetDistance(connGenes1, connGenes2)); Assert.AreEqual(Math.Sqrt(110), distanceMetric.GetDistance(connGenes2, connGenes1)); // TestDistance() tests. Assert.IsTrue(distanceMetric.TestDistance(connGenes1, connGenes2, Math.Sqrt(110) + 0.001)); Assert.IsFalse(distanceMetric.TestDistance(connGenes1, connGenes2, Math.Sqrt(110) - 0.001)); }
/// <summary> /// Mutates the genome by creating a new connection. /// </summary> public void Mutate_Link() { //Get first node to connect. It is random. NodeGene nodeGene_a = NodeGenes.RandomValue(Random).Take(1).ElementAt(0); IEnumerable <NodeGene> temp_subset = NodeGenes.Values.Where(a => a.NodeGenePattern.X > nodeGene_a.NodeGenePattern.X); if (temp_subset.Count() == 0) { return; //TODO handle this too } NodeGene nodeGene_b = temp_subset.ElementAt(Random.Next(temp_subset.Count())); //Get a random gene with a higher X value. ConnectionGene connectionGene = Pedigree.Create_ConnectionGene(nodeGene_a, nodeGene_b, Pedigree.Mutation_WeightRandom * (Random.NextDouble() * 2 - 1), true); if (ConnectionGenes.ContainsKey(connectionGene.ConnectionGenePattern.InnovationNumber)) //Can only happen if it already existed in the tracker. { return; //TODO think of how to handle this, maybe have a retry somewhere? } ConnectionGenes.Add(connectionGene.ConnectionGenePattern.InnovationNumber, connectionGene); }
private NeatGenome <T> CreateGenomeInner(NeatGenome <T> parent1, NeatGenome <T> parent2, IRandomSource rng) { // Randomly select one parent as being the primary parent. if (rng.NextBool()) { VariableUtils.Swap(ref parent1, ref parent2); } // Enumerate over the connection genes in both parents. foreach (var geneIndexPair in EnumerateParentGenes(parent1.ConnectionGenes, parent2.ConnectionGenes)) { // Create a connection gene based on the current position in both parents. ConnectionGene <T>?connGene = CreateConnectionGene( parent1.ConnectionGenes, parent2.ConnectionGenes, geneIndexPair.Item1, geneIndexPair.Item2, rng); if (connGene.HasValue) { // Attempt to add the gene to the child genome we are building. _connGeneListBuilder.TryAddGene(connGene.Value); } } // Convert the genes to the structure required by NeatGenome. ConnectionGenes <T> connGenes = _connGeneListBuilder.ToConnectionGenes(); // Create and return a new genome. return(_genomeBuilder.Create( _genomeIdSeq.Next(), _generationSeq.Peek, connGenes)); }
private NeatGenome <T> LoadInner(StreamReader sr) { // Reset line counter. _lineIdx = 0; // Read node counts. int inputCount = ReadInt32Line(); int outputCount = ReadInt32Line(); ReadEndOfSection(); ValidateNodeCounts(inputCount, outputCount); // Read connections. ReadConnections(out DirectedConnection[] connArr, out T[] weightArr); // Read activation function(s). // Note. For a NeatGenome we expect a single line specifying a single activation function with ID 0, // and a function code that specifies the function. Additional lines in the section are ignored. ReadActivationFunctions(); // Read activation function(s). ValidateActivationFunctions(); // Create a genome object and return. var connGenes = new ConnectionGenes <T>(connArr, weightArr); return(_genomeBuilder.Create(_genomeId++, 0, connGenes)); }
public void TestPythagoras() { var connGenes1 = new ConnectionGenes <double>(2); connGenes1[0] = (0, 3, 0.0); connGenes1[1] = (0, 4, 0.0); var connGenes2 = new ConnectionGenes <double>(2); connGenes2[0] = (0, 3, 3.0); connGenes2[1] = (0, 4, 4.0); var distanceMetric = new ManhattanDistanceMetric(); // GetDistance() tests. Assert.AreEqual(7.0, distanceMetric.GetDistance(connGenes1, connGenes2)); Assert.AreEqual(7.0, distanceMetric.GetDistance(connGenes2, connGenes1)); // TestDistance() tests. Assert.IsTrue(distanceMetric.TestDistance(connGenes1, connGenes2, 7.01)); Assert.IsFalse(distanceMetric.TestDistance(connGenes1, connGenes2, 7.0)); Assert.IsFalse(distanceMetric.TestDistance(connGenes1, connGenes2, 1.0)); Assert.IsFalse(distanceMetric.TestDistance(connGenes1, connGenes2, 0.0)); Assert.IsTrue(distanceMetric.TestDistance(connGenes2, connGenes1, 7.01)); Assert.IsFalse(distanceMetric.TestDistance(connGenes2, connGenes1, 7.0)); Assert.IsFalse(distanceMetric.TestDistance(connGenes2, connGenes1, 1.0)); Assert.IsFalse(distanceMetric.TestDistance(connGenes2, connGenes1, 0.0)); }
/// <summary> /// Create a new child genome from a given parent genome. /// </summary> /// <param name="parent">The parent genome.</param> /// <param name="rng">Random source.</param> /// <returns>A new child genome.</returns> public NeatGenome <T> CreateChildGenome(NeatGenome <T> parent, IRandomSource rng) { // Clone the parent's connection weight array. var weightArr = (T[])parent.ConnectionGenes._weightArr.Clone(); // Apply mutation to the connection weights. _weightMutationScheme.MutateWeights(weightArr, rng); // Create the child genome's ConnectionGenes object. // Note. The parent genome's connection arrays are re-used; these remain unchanged because we are mutating // connection *weights* only, so we can avoid the cost of cloning these arrays. var connGenes = new ConnectionGenes <T>( parent.ConnectionGenes._connArr, weightArr); // Create and return a new genome. // Note. The parent's ConnectionIndexArray and HiddenNodeIdArray can be re-used here because the new genome // has the same set of connections (same neural net structure). return(_genomeBuilder.Create( _genomeIdSeq.Next(), _generationSeq.Peek, connGenes, parent.HiddenNodeIdArray, parent.NodeIndexByIdMap, parent.DirectedGraph, parent.ConnectionIndexMap)); }
public void TestPartialMatchGenomes() { var connGenes1 = new ConnectionGenes <double>(5); connGenes1[0] = (0, 10, 1.0); connGenes1[1] = (1, 11, 2.0); connGenes1[2] = (2, 12, 3.0); connGenes1[3] = (3, 13, 4.0); connGenes1[4] = (4, 14, 5.0); var connGenes2 = new ConnectionGenes <double>(5); connGenes2[0] = (0, 10, 1.0); connGenes2[1] = (3, 13, 4.0); connGenes2[2] = (100, 11, 2.0); connGenes2[3] = (200, 12, 3.0); connGenes2[4] = (400, 14, 5.0); var distanceMetric = new ManhattanDistanceMetric();; // GetDistance() tests. Assert.AreEqual(20, distanceMetric.GetDistance(connGenes1, connGenes2)); Assert.AreEqual(20, distanceMetric.GetDistance(connGenes2, connGenes1)); // TestDistance() tests. Assert.IsTrue(distanceMetric.TestDistance(connGenes1, connGenes2, 20 + 0.001)); Assert.IsFalse(distanceMetric.TestDistance(connGenes1, connGenes2, 20 - 0.001)); }
public void Simple_DefinedNodes_NodeIdGap() { var metaNeatGenome = new MetaNeatGenome <double>(0, 10, false, new ReLU()); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(metaNeatGenome); // Simple acyclic graph. var connGenes = new ConnectionGenes <double>(4); connGenes[0] = (100, 103, 0.0); connGenes[1] = (101, 103, 1.0); connGenes[2] = (102, 103, 2.0); connGenes[3] = (102, 104, 3.0); // Wrap in a genome. var genome = genomeBuilder.Create(0, 0, connGenes); // Note. The genome builder creates a digraph representation of the genome and attaches/caches it on the genome object. var digraph = genome.DirectedGraph; // The gaps in the node IDs should be removed such that node IDs form a contiguous span starting from zero. var connArrExpected = new DirectedConnection[4]; var weightArrExpected = new double[4]; connArrExpected[0] = new DirectedConnection(10, 13); weightArrExpected[0] = 0.0; connArrExpected[1] = new DirectedConnection(11, 13); weightArrExpected[1] = 1.0; connArrExpected[2] = new DirectedConnection(12, 13); weightArrExpected[2] = 2.0; connArrExpected[3] = new DirectedConnection(12, 14); weightArrExpected[3] = 3.0; // The graph should be unchanged from the input connections. CompareConnectionLists(connArrExpected, digraph.ConnectionIdArrays); // Check the node count. Assert.AreEqual(15, digraph.TotalNodeCount); }
/// <summary> /// Adds a new connection between 2 randomly chosen nodes. /// The first node can not be an output. /// The second node cannot be an input node if the first node is an input node, and the layer cannot be the same as the first node. /// /// If the connection that is going to be created already exists, there will be a maximum of 5 attempts to create a new one. /// </summary> /// <param name="trainingRoomSettings">The training room settings.</param> /// <param name="innovationFunction">The innovation function.</param> protected void AddConnectionMutation(TrainingRoomSettings trainingRoomSettings, Func <uint, uint, uint> innovationFunction) { // Set initial attempt counter to 0. int attemptsDone = 0; // Rebuild structure so we get layer information to make sure we don't get circular dependencies. RebuildStructure(); while (true) { // Prepare start and end node. Node startNode; Node endNode; // Set start node to a random node while start note is of type OutputNode. do { startNode = TempNodes[trainingRoomSettings.Random.Next(TempNodes.Count)]; } while (IsNodeAnOutputNode(startNode)); // Set end node to a random node while the layer of the start and end node are the same or // start node is of type InputNode and end node is of type InputNode. do { endNode = TempNodes[trainingRoomSettings.Random.Next(TempNodes.Count)]; } while (endNode.Layer == startNode.Layer || (IsNodeAnInputNode(startNode) && IsNodeAnInputNode(endNode))); // If end node layer is higher than start node layer swap them. if (endNode.Layer > startNode.Layer) { Node temp = endNode; endNode = startNode; startNode = temp; } // If the connection between the selected start and end node already exists continue the loop, until 5 attempts are done, // then return early. if (ConnectionExists(startNode, endNode)) { if (attemptsDone >= 5) { return; } attemptsDone += 1; continue; } // Create a new connection gene from the start and end nodes. ConnectionGene connection = CreateConnectionGene(innovationFunction(startNode.NodeIdentifier, endNode.NodeIdentifier), startNode.NodeIdentifier, endNode.NodeIdentifier, trainingRoomSettings.Random.NextDouble() * 2 - 1); // Add the connection gene. ConnectionGenes.Add(connection); // Update the max innovation. _maxInnovation = Math.Max(connection.InnovationNumber, _maxInnovation); // Break the loop. break; } }
public void TestMatchingGenomes() { var connGenes1 = new ConnectionGenes <double>(5); connGenes1[0] = (0, 10, 1.0); connGenes1[1] = (1, 11, 2.0); connGenes1[2] = (2, 12, 3.0); connGenes1[3] = (3, 13, 4.0); connGenes1[4] = (4, 14, 5.0); var connGenes2 = new ConnectionGenes <double>(5); connGenes2[0] = (0, 10, 1.0); connGenes2[1] = (1, 11, 2.0); connGenes2[2] = (2, 12, 3.0); connGenes2[3] = (3, 13, 4.0); connGenes2[4] = (4, 14, 5.0); var distanceMetric = new ManhattanDistanceMetric(); // GetDistance() tests. Assert.Equal(0.0, distanceMetric.CalcDistance(connGenes1, connGenes2)); Assert.Equal(0.0, distanceMetric.CalcDistance(connGenes2, connGenes1)); // TestDistance() tests. Assert.True(distanceMetric.TestDistance(connGenes1, connGenes2, 1.0)); Assert.True(distanceMetric.TestDistance(connGenes1, connGenes2, 0.001)); Assert.False(distanceMetric.TestDistance(connGenes1, connGenes2, 0.0)); Assert.True(distanceMetric.TestDistance(connGenes2, connGenes1, 5.0)); Assert.True(distanceMetric.TestDistance(connGenes2, connGenes1, 0.001)); Assert.False(distanceMetric.TestDistance(connGenes2, connGenes1, 0.0)); }
private static ConnectionGene <T>?CreateConnectionGene( ConnectionGenes <T> connGenes1, ConnectionGenes <T> connGenes2, int idx1, int idx2, bool includeSecondaryParentGene, IRandomSource rng) { // Select gene at random if it is present on both parents. if (idx1 != -1 && idx2 != -1) { if (rng.NextBool()) { return(CreateConnectionGene(connGenes1, idx1)); } else { return(CreateConnectionGene(connGenes2, idx2)); } } // Use the primary parent's gene if it has one. if (idx1 != -1) { return(CreateConnectionGene(connGenes1, idx1)); } // Otherwise use the secondary parent's gene if the 'includeSecondaryParentGene' flag is set. if (includeSecondaryParentGene) { return(CreateConnectionGene(connGenes2, idx2)); } return(null); }
public void Simple() { var metaNeatGenome = new MetaNeatGenome <double>(3, 2, true, new ReLU()); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(metaNeatGenome); // Simple acyclic graph. var connGenes = new ConnectionGenes <double>(4); connGenes[0] = (0, 3, 0.0); connGenes[1] = (1, 3, 1.0); connGenes[2] = (2, 3, 2.0); connGenes[3] = (2, 4, 3.0); // Wrap in a genome. var genome = genomeBuilder.Create(0, 0, connGenes); // Note. The genome builder creates a digraph representation of the genome and attaches/caches it on the genome object. var acyclicDigraph = (DirectedGraphAcyclic)genome.DirectedGraph; Assert.NotNull(acyclicDigraph); // The graph should be unchanged from the input connections. CompareConnectionLists(connGenes, acyclicDigraph.ConnectionIds, genome.ConnectionIndexMap); // Check the node count. Assert.Equal(5, acyclicDigraph.TotalNodeCount); }
/// <summary> /// Mutates the brain, each time this is called there is a chance to: /// - Add a connection /// - Add a node /// - Change the weight of an existing gene, which can either randomly be reset to a random value or slightly change /// These changes can all happen in one call, but the chance that this happens depends on the training room settings. /// </summary> public void Mutate() { if (TrainingRoom.Random.NextDouble() < TrainingRoom.TrainingRoomSettings.AddConnectionChance) { AddConnectionMutation(); } if (TrainingRoom.Random.NextDouble() < TrainingRoom.TrainingRoomSettings.AddNodeChance) { AddNodeMutation(); } if ((TrainingRoom.Random.NextDouble() < TrainingRoom.TrainingRoomSettings.MutateWeightChance) && ConnectionGenes.Count > 0) { ConnectionGene connectionGene = ConnectionGenes.ElementAt(TrainingRoom.Random.Next(ConnectionGenes.Count)); if (TrainingRoom.Random.NextDouble() < TrainingRoom.TrainingRoomSettings.WeightReassignChance) { connectionGene.Weight = TrainingRoom.Random.NextDouble() * 2 - 1; } else { connectionGene.Weight += (TrainingRoom.Random.NextDouble() * 2 - 1) * 0.1; } } }
public void TestPythagoras() { var connGenes1 = new ConnectionGenes <double>(2); connGenes1[0] = (0, 3, 0.0); connGenes1[1] = (0, 4, 0.0); var connGenes2 = new ConnectionGenes <double>(2); connGenes2[0] = (0, 3, 3.0); connGenes2[1] = (0, 4, 4.0); var distanceMetric = new EuclideanDistanceMetric(); // GetDistance() tests. Assert.Equal(5.0, distanceMetric.CalcDistance(connGenes1, connGenes2)); Assert.Equal(5.0, distanceMetric.CalcDistance(connGenes2, connGenes1)); // TestDistance() tests. Assert.True(distanceMetric.TestDistance(connGenes1, connGenes2, 5.01)); Assert.False(distanceMetric.TestDistance(connGenes1, connGenes2, 5.0)); Assert.False(distanceMetric.TestDistance(connGenes1, connGenes2, 1.0)); Assert.False(distanceMetric.TestDistance(connGenes1, connGenes2, 0.0)); Assert.True(distanceMetric.TestDistance(connGenes2, connGenes1, 5.01)); Assert.False(distanceMetric.TestDistance(connGenes2, connGenes1, 5.0)); Assert.False(distanceMetric.TestDistance(connGenes2, connGenes1, 1.0)); Assert.False(distanceMetric.TestDistance(connGenes2, connGenes1, 0.0)); }
/// <summary> /// Mutates the the organism based on the training room settings. /// </summary> /// <param name="trainingRoomSettings">The training room settings.</param> /// <param name="getAndIncreaseNodeIdFunction">The get and increase node id function.</param> /// <param name="innovationFunction">The innovation function.</param> public void Mutate(TrainingRoomSettings trainingRoomSettings, Func <uint> getAndIncreaseNodeIdFunction, Func <uint, uint, uint> innovationFunction) { // If the random value is lower than the add connection chance add a connection mutation. if (trainingRoomSettings.Random.NextDouble() < trainingRoomSettings.AddConnectionChance) { AddConnectionMutation(trainingRoomSettings, innovationFunction); } // If the random value is lower than the add node chance add a node mutation. if (trainingRoomSettings.Random.NextDouble() < trainingRoomSettings.AddNodeChance) { AddNodeMutation(trainingRoomSettings, getAndIncreaseNodeIdFunction, innovationFunction); } // If the random value is higher than the mutate weight chance or the connection gene count is lower than 0 return early. if (trainingRoomSettings.Random.NextDouble() > trainingRoomSettings.MutateWeightChance || ConnectionGenes.Count <= 0) { return; } // Find a random connection gene. ConnectionGene connectionGene = ConnectionGenes.ElementAt(trainingRoomSettings.Random.Next(ConnectionGenes.Count)); // If the random value is lower than the weight reassign chance set a new weight else add weight. if (trainingRoomSettings.Random.NextDouble() < trainingRoomSettings.WeightReassignChance) { connectionGene.Weight = trainingRoomSettings.Random.NextDouble() * 2 - 1; } else { connectionGene.Weight += (trainingRoomSettings.Random.NextDouble() * 2 - 1) * 0.1; } //TODO: Inputs mutate? }
public override void CreateBrain(BrainComponent brainComp, IMutatorConfig config) { var neatConfig = (NeatMutationConfig)config; // Add nodes for inputs and outputs foreach ((string namedInput, string _) in brainComp.InputMap) { // Note activation function on input nodes is not used. this.AddNamedNode(namedInput, NodeType.INPUT, ActivationFunctionType.SOFTSIGN); } foreach ((string namedOutput, string _) in brainComp.OutputMap) { this.AddNamedNode(namedOutput, NodeType.OUTPUT, (ActivationFunctionType)neatConfig.OutputActivationFunction); } // Increase connection innovation id as we loop int connInnovationId = 0; Random r = new Random(); double chanceToMakeConnection = 1d; foreach (var input in NodeGenes.FindAll((g) => g.NodeType == NodeType.INPUT || g.NodeType == NodeType.BIAS)) { foreach (var output in NodeGenes.FindAll((g) => g.NodeType == NodeType.OUTPUT)) { if (r.NextDouble() < chanceToMakeConnection) { // Add new connection gene ConnectionGenes.Add(new ConnectionGene(connInnovationId, input.InnovationId, output.InnovationId, r.Normal(0, 1.5))); connInnovationId++; } } } }
/// <summary> /// Checks if a brain is the same as this brain. /// 2 brains are the same if all of their genes match, their outputNodes match, their inputNodes match and the input and output counts match. /// </summary> /// <param name="other">The brain to compare against.</param> /// <returns>Returns <c>true</c> if the brain is the same; otherwise, <c>false</c>.</returns> private bool Equals(Brain other) { return(ConnectionGenes.SequenceEqual(other.ConnectionGenes) && _outputNodes.SequenceEqual(other._outputNodes) && _inputNodes.SequenceEqual(other._inputNodes) && TrainingRoom.TrainingRoomSettings.InputCount == other.TrainingRoom.TrainingRoomSettings.InputCount && TrainingRoom.TrainingRoomSettings.OutputCount == other.TrainingRoom.TrainingRoomSettings.OutputCount); }