Пример #1
0
        /// <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);
        }
Пример #2
0
    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));
    }
Пример #3
0
    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);
        }
    }
Пример #4
0
        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);
        }
Пример #5
0
        /// <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);
        }
Пример #6
0
    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]);
        }
    }
Пример #9
0
        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);
        }
Пример #10
0
        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);
        }
Пример #11
0
        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);
        }
Пример #12
0
    /// <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);
    }
Пример #13
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);
        }
Пример #14
0
        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));
        }
Пример #15
0
        /// <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);
        }
Пример #16
0
        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));
        }
Пример #17
0
        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));
        }
Пример #18
0
        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));
        }
Пример #19
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));
        }
Пример #20
0
        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));
        }
Пример #21
0
        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);
        }
Пример #22
0
        /// <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;
            }
        }
Пример #23
0
    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));
    }
Пример #24
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);
    }
Пример #25
0
    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);
    }
Пример #26
0
        /// <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;
                }
            }
        }
Пример #27
0
    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));
    }
Пример #28
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?
        }
Пример #29
0
        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++;
                    }
                }
            }
        }
Пример #30
0
 /// <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);
 }