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); }
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); } }
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); }
public void TestAddCyclicConnection() { var pop = CreateNeatPopulation(); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(pop.MetaNeatGenome); var genome = pop.GenomeList[0]; var strategy = new AddCyclicConnectionStrategy <double>( pop.MetaNeatGenome, genomeBuilder, pop.GenomeIdSeq, pop.InnovationIdSeq, pop.GenerationSeq, RandomDefaults.CreateRandomSource()); var nodeIdSet = GetNodeIdSet(genome); var connSet = GetDirectedConnectionSet(genome); for (int i = 0; i < 1000; i++) { var childGenome = strategy.CreateChildGenome(genome); // The child genome should have one more connection than parent. Assert.AreEqual(genome.ConnectionGenes.Length + 1, childGenome.ConnectionGenes.Length); // The child genome's new connection should not be a duplicate of any of the existing/parent connections. var childConnSet = GetDirectedConnectionSet(childGenome); var newConnList = new List <DirectedConnection>(childConnSet.Except(connSet)); Assert.AreEqual(1, newConnList.Count); // The connection genes should be sorted. Assert.IsTrue(SortUtils.IsSortedAscending(childGenome.ConnectionGenes._connArr)); // The child genome should have the same set of node IDs as the parent. var childNodeIdSet = GetNodeIdSet(childGenome); Assert.IsTrue(nodeIdSet.SetEquals(childNodeIdSet)); } }
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); }
public void TestAddNode3() { var pop = CreateNeatPopulation(); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(pop.MetaNeatGenome); var genome = pop.GenomeList[0]; var strategy = new AddNodeStrategy <double>( pop.MetaNeatGenome, genomeBuilder, pop.GenomeIdSeq, pop.InnovationIdSeq, pop.GenerationSeq, pop.AddedNodeBuffer); IRandomSource rng = RandomDefaults.CreateRandomSource(); CircularBuffer <NeatGenome <double> > genomeRing = new CircularBuffer <NeatGenome <double> >(10); genomeRing.Enqueue(genome); for (int i = 0; i < 5000; i++) { NeatGenome <double> childGenome = CreateAndTestChildGenome(genome, strategy, rng); // Add the new child genome to the ring. genomeRing.Enqueue(childGenome); // Take the genome at the tail of the ring for the next parent. genome = genomeRing[0]; } }
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 DepthNodeReorderTest() { var metaNeatGenome = new MetaNeatGenome <double>(2, 2, true, new ReLU()); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(metaNeatGenome); // Define graph connections. var connGenes = new ConnectionGenes <double>(5); connGenes[0] = (0, 4, 0.0); connGenes[1] = (4, 5, 1.0); connGenes[2] = (5, 2, 2.0); connGenes[3] = (1, 2, 3.0); connGenes[4] = (2, 3, 4.0); connGenes.Sort(); // 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); // Simulate the actual weight array that would occur in e.g. a WeightedDirectedGraphAcyclic or NeuralNetAcyclic. double[] weightArrActual = new double[connGenes._weightArr.Length]; for (int i = 0; i < weightArrActual.Length; i++) { weightArrActual[i] = connGenes._weightArr[genome.ConnectionIndexMap[i]]; } // The nodes should have IDs allocated based on depth, i.e. the layer they are in. // And connections should be ordered by source node ID. var connArrExpected = new DirectedConnection[5]; var weightArrExpected = new double[5]; connArrExpected[0] = new DirectedConnection(0, 2); weightArrExpected[0] = 0.0; connArrExpected[1] = new DirectedConnection(1, 4); weightArrExpected[1] = 3.0; connArrExpected[2] = new DirectedConnection(2, 3); weightArrExpected[2] = 1.0; connArrExpected[3] = new DirectedConnection(3, 4); weightArrExpected[3] = 2.0; connArrExpected[4] = new DirectedConnection(4, 5); weightArrExpected[4] = 4.0; // Compare actual and expected connections. CompareConnectionLists(connArrExpected, weightArrExpected, acyclicDigraph.ConnectionIds, weightArrActual); // Test layer info. LayerInfo[] layerArrExpected = new LayerInfo[5]; layerArrExpected[0] = new LayerInfo(2, 2); layerArrExpected[1] = new LayerInfo(3, 3); layerArrExpected[2] = new LayerInfo(4, 4); layerArrExpected[3] = new LayerInfo(5, 5); layerArrExpected[4] = new LayerInfo(6, 5); Assert.Equal(5, acyclicDigraph.LayerArray.Length); // Check the node count. Assert.Equal(6, acyclicDigraph.TotalNodeCount); }
public void TestAddAcyclicConnection() { var pop = CreateNeatPopulation(); var generationSeq = new Int32Sequence(); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(pop.MetaNeatGenome); var genome = pop.GenomeList[0]; var strategy = new AddAcyclicConnectionStrategy <double>( pop.MetaNeatGenome, genomeBuilder, pop.GenomeIdSeq, pop.InnovationIdSeq, generationSeq); IRandomSource rng = RandomDefaults.CreateRandomSource(); var nodeIdSet = GetNodeIdSet(genome); var connSet = GetDirectedConnectionSet(genome); CyclicGraphAnalysis cyclicGraphAnalysis = new CyclicGraphAnalysis(); for (int i = 0; i < 1000;) { var childGenome = strategy.CreateChildGenome(genome, rng); // Note. the strategy will return a null if it cannot find an acyclic connection to add; // test for this and try again. The test will be for N successful mutations rather than N attempts. if (null == childGenome) { continue; } // The child genome should have one more connection than parent. Assert.AreEqual(genome.ConnectionGenes.Length + 1, childGenome.ConnectionGenes.Length); // The child genome's new connection should not be a duplicate of any of the existing/parent connections. var childConnSet = GetDirectedConnectionSet(childGenome); var newConnList = new List <DirectedConnection>(childConnSet.Except(connSet)); Assert.AreEqual(1, newConnList.Count); // The connection genes should be sorted. Assert.IsTrue(SortUtils.IsSortedAscending(childGenome.ConnectionGenes._connArr)); // The child genome should have the same set of node IDs as the parent. var childNodeIdSet = GetNodeIdSet(childGenome); Assert.IsTrue(nodeIdSet.SetEquals(childNodeIdSet)); // The child genome should describe an acyclic graph, i.e. the new connection should not have // formed a cycle in the graph. var digraph = childGenome.DirectedGraph; Assert.IsFalse(cyclicGraphAnalysis.IsCyclic(digraph)); // Increment for successful tests only. i++; } }
public void AddCyclicConnection() { var pop = CreateNeatPopulation(); var generationSeq = new Int32Sequence(); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(pop.MetaNeatGenome); var genome = pop.GenomeList[0]; var strategy = new AddCyclicConnectionStrategy <double>( pop.MetaNeatGenome, genomeBuilder, pop.GenomeIdSeq, generationSeq); IRandomSource rng = RandomDefaults.CreateRandomSource(); var nodeIdSet = GetNodeIdSet(genome); var connSet = GetDirectedConnectionSet(genome); const int loops = 1000; int nullResponseCount = 0; for (int i = 0; i < loops; i++) { var childGenome = strategy.CreateChildGenome(genome, rng); // The strategy may return null if no appropriately connection could be found to add. if (childGenome is null) { nullResponseCount++; continue; } // The child genome should have one more connection than parent. Assert.Equal(genome.ConnectionGenes.Length + 1, childGenome.ConnectionGenes.Length); // The child genome's new connection should not be a duplicate of any of the existing/parent connections. var childConnSet = GetDirectedConnectionSet(childGenome); var newConnList = new List <DirectedConnection>(childConnSet.Except(connSet)); Assert.Single(newConnList); // The connection genes should be sorted. Assert.True(SortUtils.IsSortedAscending <DirectedConnection>(childGenome.ConnectionGenes._connArr)); // The child genome should have the same set of node IDs as the parent. var childNodeIdSet = GetNodeIdSet(childGenome); Assert.True(nodeIdSet.SetEquals(childNodeIdSet)); } // nullResponseProportion will typically be 0, with 1.0% being so unlikely // it probably will never be observed. double nullResponseProportion = nullResponseCount / (double)loops; Assert.True(nullResponseProportion <= 0.01); }
protected NeatGenomeLoader( MetaNeatGenome <T> metaNeatGenome, int connCountEstimate) { _metaNeatGenome = metaNeatGenome ?? throw new ArgumentNullException(nameof(metaNeatGenome)); _genomeBuilder = NeatGenomeBuilderFactory <T> .Create(metaNeatGenome); _activationFnName = metaNeatGenome.ActivationFn.GetType().Name; _connList = new List <DirectedConnection>(connCountEstimate); _weightList = new List <T>(connCountEstimate); _actFnList = new List <ActivationFunctionRow>(); }
public void CreateGenome() { var metaNeatGenome = new MetaNeatGenome <double>( inputNodeCount: 10, outputNodeCount: 20, isAcyclic: true, activationFn: new NeuralNets.Double.ActivationFunctions.ReLU()); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(metaNeatGenome); int count = 100; NeatPopulation <double> pop = NeatPopulationFactory <double> .CreatePopulation(metaNeatGenome, 0.1, count, RandomDefaults.CreateRandomSource()); var generationSeq = new Int32Sequence(); var strategy = new UniformCrossoverReproductionStrategy <double>( pop.MetaNeatGenome.IsAcyclic, 0.02, genomeBuilder, pop.GenomeIdSeq, generationSeq); IRandomSource rng = RandomDefaults.CreateRandomSource(0); var cyclicGraphCheck = new CyclicGraphCheck(); for (int i = 0; i < 1000; i++) { // Randomly select two parents from the population. var genome1 = pop.GenomeList[rng.Next(count)]; var genome2 = pop.GenomeList[rng.Next(count)]; var childGenome = strategy.CreateGenome(genome1, genome2, rng); // The connection genes should be sorted. Assert.True(SortUtils.IsSortedAscending <DirectedConnection>(childGenome.ConnectionGenes._connArr)); // The child genome should describe an acyclic graph, i.e. the new connection should not have // formed a cycle in the graph. var digraph = childGenome.DirectedGraph; Assert.False(cyclicGraphCheck.IsCyclic(digraph)); // The child genome node IDs should be a superset of those from parent1 + parent2. var childNodeIdSet = GetNodeIdSet(childGenome); var parentIdSet = GetNodeIdSet(genome1); parentIdSet.IntersectWith(GetNodeIdSet(genome2)); Assert.True(childNodeIdSet.IsSupersetOf(parentIdSet)); } }
public static NeatPopulation <double> CreateNeatPopulation() { var metaNeatGenome = new MetaNeatGenome <double>( inputNodeCount: 1, outputNodeCount: 1, isAcyclic: false, activationFn: new NeuralNet.Double.ActivationFunctions.ReLU()); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(metaNeatGenome); var genome = CreateNeatGenome(metaNeatGenome, genomeBuilder); var genomeList = new List <NeatGenome <double> >() { genome }; return(new NeatPopulation <double>(metaNeatGenome, genomeList)); }
public void TestAddNode1() { var pop = CreateNeatPopulation(); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(pop.MetaNeatGenome); var genome = pop.GenomeList[0]; var strategy = new AddNodeStrategy <double>( pop.MetaNeatGenome, genomeBuilder, pop.GenomeIdSeq, pop.InnovationIdSeq, pop.GenerationSeq, pop.AddedNodeBuffer, RandomDefaults.CreateRandomSource()); for (int i = 0; i < 10000; i++) { NeatGenome <double> childGenome = CreateAndTestChildGenome(genome, strategy); } }
public void TestAddNode2() { var pop = CreateNeatPopulation(); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(pop.MetaNeatGenome); var genome = pop.GenomeList[0]; var strategy = new AddNodeStrategy <double>( pop.MetaNeatGenome, genomeBuilder, pop.GenomeIdSeq, pop.InnovationIdSeq, pop.GenerationSeq, pop.AddedNodeBuffer, RandomDefaults.CreateRandomSource()); for (int i = 0; i < 2000; i++) { NeatGenome <double> childGenome = CreateAndTestChildGenome(genome, strategy); // Make the child genome the parent in the next iteration. I.e. accumulate add node mutations. genome = childGenome; } }
private NeatPopulationFactory( MetaNeatGenome <T> metaNeatGenome, double connectionsProportion, IRandomSource rng) { _metaNeatGenome = metaNeatGenome; _genomeBuilder = NeatGenomeBuilderFactory <T> .Create(metaNeatGenome); _connectionsProportion = connectionsProportion; // Define the set of all possible connections between the input and output nodes (fully interconnected). int inputCount = metaNeatGenome.InputNodeCount; int outputCount = metaNeatGenome.OutputNodeCount; _connectionDefArr = new DirectedConnection[inputCount * outputCount]; // Notes. // Nodes are assigned innovation IDs. By convention the input nodes are assigned IDs first starting at zero, then the output nodes. // Thus, because all of the evolved networks have a fixed number of inputs and outputs, the IDs of these nodes are always fixed. int firstOutputNodeId = inputCount; for (int srcId = 0, i = 0; srcId < inputCount; srcId++) { for (int tgtIdx = 0; tgtIdx < outputCount; tgtIdx++) { _connectionDefArr[i++] = new DirectedConnection(srcId, firstOutputNodeId + tgtIdx); } } // Init RNG and ID sequences. _rng = rng; _genomeIdSeq = new Int32Sequence(); int nextInnovationId = inputCount + outputCount; _innovationIdSeq = new Int32Sequence(nextInnovationId); // Init random connection weight source. _connWeightDist = ContinuousDistributionFactory.CreateUniformDistribution <T>(_metaNeatGenome.ConnectionWeightRange, true); }
public void TestDeleteConnection() { var pop = CreateNeatPopulation(); var generationSeq = new Int32Sequence(); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(pop.MetaNeatGenome); var genome = pop.GenomeList[0]; var strategy = new DeleteConnectionStrategy <double>( pop.MetaNeatGenome, genomeBuilder, pop.GenomeIdSeq, generationSeq); IRandomSource rng = RandomDefaults.CreateRandomSource(); var nodeIdSet = GetNodeIdSet(genome); var connSet = GetDirectedConnectionSet(genome); for (int i = 0; i < 1000; i++) { var childGenome = strategy.CreateChildGenome(genome, rng); // The child genome should have one less connection than the parent. Assert.AreEqual(genome.ConnectionGenes.Length - 1, childGenome.ConnectionGenes.Length); // The child genome's connections should be a proper subset of the parent genome's. var childConnSet = GetDirectedConnectionSet(childGenome); Assert.IsTrue(childConnSet.IsProperSubsetOf(connSet)); // The connection genes should be sorted. Assert.IsTrue(SortUtils.IsSortedAscending(childGenome.ConnectionGenes._connArr)); // Test that the array of hidden node IDs is correct, i.e. corresponds with the hidden node IDs described by the connections. Assert.IsTrue(ConnectionGenesUtils.ValidateHiddenNodeIds( childGenome.HiddenNodeIdArray, childGenome.ConnectionGenes._connArr, childGenome.MetaNeatGenome.InputOutputNodeCount)); } }
public void TestAddAcyclicConnection_CumulativeAdditions() { var pop = CreateNeatPopulation(); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(pop.MetaNeatGenome); var rootGenome = pop.GenomeList[0]; var strategy = new AddAcyclicConnectionStrategy <double>( pop.MetaNeatGenome, genomeBuilder, pop.GenomeIdSeq, pop.InnovationIdSeq, pop.GenerationSeq); IRandomSource rng = RandomDefaults.CreateRandomSource(); var nodeIdSet = GetNodeIdSet(rootGenome); CyclicGraphAnalysis cyclicGraphAnalysis = new CyclicGraphAnalysis(); AcyclicGraphDepthAnalysis graphDepthAnalysis = new AcyclicGraphDepthAnalysis(); // Run the inner loop test multiple times. // Note. The add-connection mutations are random, thus each loop accumulates a different set of mutations. for (int i = 0; i < 50; i++) { var parentGenome = rootGenome; // Accumulate random mutations for some number of loops. for (int j = 0; j < 20;) { var childGenome = strategy.CreateChildGenome(rootGenome, rng); // Note. the strategy will return a null if it cannot find an acyclic connection to add; // test for this and try again. The test will be for N successful mutations rather than N attempts. if (null == childGenome) { continue; } // The child genome should have one more connection than parent. Assert.AreEqual(rootGenome.ConnectionGenes.Length + 1, childGenome.ConnectionGenes.Length); // The child genome's new connection should not be a duplicate of any of the existing/parent connections. var connSet = GetDirectedConnectionSet(rootGenome); var childConnSet = GetDirectedConnectionSet(childGenome); var newConnList = new List <DirectedConnection>(childConnSet.Except(connSet)); Assert.AreEqual(1, newConnList.Count); // The connection genes should be sorted. Assert.IsTrue(SortUtils.IsSortedAscending(childGenome.ConnectionGenes._connArr)); // The child genome should have the same set of node IDs as the parent. var childNodeIdSet = GetNodeIdSet(childGenome); Assert.IsTrue(nodeIdSet.SetEquals(childNodeIdSet)); // The child genome should describe an acyclic graph, i.e. the new connection should not have // formed a cycle in the graph. var digraph = childGenome.DirectedGraph; Assert.IsFalse(cyclicGraphAnalysis.IsCyclic(digraph)); // Run the acyclic graph depth analysis algorithm. GraphDepthInfo depthInfo = graphDepthAnalysis.CalculateNodeDepths(childGenome.DirectedGraph); // Run again with the alternative algorithm (that uses function recursion). GraphDepthInfo depthInfo2 = AcyclicGraphDepthAnalysisByRecursion.CalculateNodeDepths(childGenome.DirectedGraph); Assert.AreEqual(nodeIdSet.Count, depthInfo._nodeDepthArr.Length); Assert.AreEqual(nodeIdSet.Count, depthInfo2._nodeDepthArr.Length); TestUtils.Compare(depthInfo2._nodeDepthArr, depthInfo._nodeDepthArr); // Set the child genome to be the new parent, thus we accumulate random new connections over time. parentGenome = childGenome; // Increment for successful tests only. j++; } } }
static UniformCrossoverReproductionStrategyTestsUtils() { __metaNeatGenome = CreateMetaNeatGenome(); __genomeBuilder = NeatGenomeBuilderFactory <double> .Create(__metaNeatGenome); }