Exemplo n.º 1
0
        /// <summary>
        /// and a neuron. This is used in the structural mutation of NEAT
        /// </summary>
        /// <param name="MutationRate"></param>
        /// <param name="innovation"></param>
        /// <param name="NumTrysToFindOldLink"></param>
        public void AddNeuron(double MutationRate, Innovation innovation, int NumTrysToFindOldLink)
        {
            //just return dependent on mutation rate
            if (RandomProvider.RandomFloat() > MutationRate)
            {
                return;
            }

            //if a valid link is found into which to insert the new neuron
            //this value is set to true.
            bool bDone = false;

            //this will hold the index into m_vecLinks of the chosen link gene
            int ChosenLink = 0;

            //first a link is chosen to split. If the genome is small the code makes
            //sure one of the older links is split to ensure a chaining effect does
            //not occur. Here, if the genome contains less than 5 hidden neurons it
            //is considered to be too small to select a link at random
            int SizeThreshold = this.NumberOfInputs + this.NumberOfOutputs + 5;

            if (this.Links.Count < SizeThreshold)
            {
                while (NumTrysToFindOldLink-- > 0)
                {
                    //choose a link with a bias towards the older links in the genome
                    ChosenLink = RandomProvider.RND.Next(0, GetNumGenes() - 1 - (int)Mathf.Sqrt(GetNumGenes()));

                    //make sure the link is enabled and that it is not a recurrent link
                    //or has a bias input
                    int FromNeuron = this.Links[ChosenLink].FromNeuron;

                    if ((this.Links[ChosenLink].Enabled) &&
                        (!this.Links[ChosenLink].Recurrent) &&
                        (this.Neurons[GetElementPosition(FromNeuron)].NeuronType != NeuronType.Bias))
                    {
                        bDone = true;

                        NumTrysToFindOldLink = 0;
                    }
                }

                if (!bDone)
                {
                    //failed to find a decent link
                    return;
                }
            }

            /*
             *  Early on in the development of the networks, a problem can occur where the same
             *  link is split repeatedly creating a chaining effect.
             *
             *  Obviously, this is undesirable, so the following code checks the number of neurons
             *  in the genome to see if the structure is below a certain size threshold. If it is, measures
             *  are taken to ensure that older links are selected in preference to newer ones.
             */
            else
            {
                int maxExitCounter = this.Links.Count + NeuralNetworkParams.NumTicks;
                int exitCounter    = 0;
                //the genome is of sufficient size for any link to be acceptable
                // 20.10.2015 also there is an exit counter to exit the loop if nothing is found in a certain amount of time. This can happen with very low ticks, the neural network gets to messed up. Reason unknown at the moment
                while (!bDone && exitCounter < maxExitCounter)
                {
                    ChosenLink = RandomProvider.RND.Next(0, GetNumGenes() - 1);

                    //make sure the link is enabled and that it is not a recurrent link
                    //or has a BIAS input
                    int FromNeuron = this.Links[ChosenLink].FromNeuron;

                    if ((this.Links[ChosenLink].Enabled) &&
                        (!this.Links[ChosenLink].Recurrent) &&
                        (this.Neurons[GetElementPosition(FromNeuron)].NeuronType != NeuronType.Bias))
                    {
                        bDone = true;
                    }

                    exitCounter++;
                }
                if (exitCounter >= maxExitCounter)
                {
                    Debug.Log("No proper links where found!");
                }
            }

            //disable this gene
            this.Links[ChosenLink].Enabled = false;

            //grab the weight from the gene (we want to use this for the weight of
            //one of the new links so that the split does not disturb anything the
            //NN may have already learned...
            double OriginalWeight = this.Links[ChosenLink].Weight;

            /*
             *  When a link is disabled and two new links are created, the old weight from the
             *  disabled link is used as the weight for one of the new links, and the weight for the
             *  other link is set to 1. In this way, the addition of a neuron creates as little disruption
             *  as possible to any existing learned behavior
             */

            //identify the neurons this link connects
            int from = this.Links[ChosenLink].FromNeuron;
            int to   = this.Links[ChosenLink].ToNeuron;

            //calculate the depth and width of the new neuron. We can use the depth
            //to see if the link feeds backwards or forwards
            double NewDepth = (this.Neurons[GetElementPosition(from)].SplitY +
                               this.Neurons[GetElementPosition(to)].SplitY) / 2;

            double NewWidth = (this.Neurons[GetElementPosition(from)].SplitX +
                               this.Neurons[GetElementPosition(to)].SplitX) / 2;

            //Now to see if this innovation has been created previously by
            //another member of the population
            int id = innovation.CheckInnovation(from,
                                                to,
                                                InnovationType.NewNeuron);



            /*it is possible for NEAT to repeatedly do the following:
             *
             *  1. Find a link. Lets say we choose link 1 to 5
             *  2. Disable the link,
             *  3. Add a new neuron and two new links
             *  4. The link disabled in Step 2 maybe re-enabled when this genome
             *     is recombined with a genome that has that link enabled.
             *  5  etc etc
             *
             * Therefore, this function must check to see if a neuron ID is already
             * being used. If it is then the function creates a new innovation
             * for the neuron. */


            if (id >= 0)
            {
                int NeuronID = innovation.GetNeuronID(id);

                if (AlreadyHaveThisNeuronID(NeuronID))
                {
                    id = -1;
                }
            }

            /*
             *  AlreadyHaveThisNeuronID returns true if (you guessed it) the genome already has a
             *  neuron with an identical ID. If this is the case, then a new innovation needs to be
             *  created, so id is reset to -1.
             */
            if (id < 0)
            {
                //add the innovation for the new neuron
                int NewNeuronID = innovation.CreateNewInnovation(from,
                                                                 to,
                                                                 InnovationType.NewNeuron,
                                                                 NeuronType.Hidden,
                                                                 NewWidth,
                                                                 NewDepth);

                //create the new neuron gene and add it.
                this.Neurons.Add(new NeuronGene(NeuronType.Hidden,
                                                NewNeuronID,
                                                NewDepth,
                                                NewWidth));

                //Two new link innovations are required, one for each of the
                //new links created when this gene is split.

                //-----------------------------------first link

                //get the next innovation ID
                int idLink1 = innovation.NextNumber();

                //create the new innovation
                innovation.CreateNewInnovation(from,
                                               NewNeuronID,
                                               InnovationType.NewLink);

                //create the new link gene
                LinkGene link1 = new LinkGene(from,
                                              NewNeuronID,
                                              true,
                                              idLink1,
                                              1.0);

                this.Links.Add(link1);

                //-----------------------------------second link

                //get the next innovation ID
                int idLink2 = innovation.NextNumber();

                //create the new innovation
                innovation.CreateNewInnovation(NewNeuronID,
                                               to,
                                               InnovationType.NewLink);

                //create the new gene
                LinkGene link2 = new LinkGene(NewNeuronID,
                                              to,
                                              true,
                                              idLink2,
                                              OriginalWeight);

                this.Links.Add(link2);
            }

            else
            {
                //this innovation has already been created so grab the relevant neuron
                //and link info from the innovation database
                int NewNeuronID = innovation.GetNeuronID(id);

                //get the innovation IDs for the two new link genes.
                int idLink1 = innovation.CheckInnovation(from, NewNeuronID, InnovationType.NewLink);
                int idLink2 = innovation.CheckInnovation(NewNeuronID, to, InnovationType.NewLink);

                //this should never happen because the innovations *should* have already
                //occurred
                if ((idLink1 < 0) || (idLink2 < 0))
                {
                    Debug.Log("Error in CGenome::AddNeuron, Problem!");

                    return;
                }

                //now we need to create 2 new genes to represent the new links
                LinkGene link1 = new LinkGene(from, NewNeuronID, true, idLink1, 1.0);
                LinkGene link2 = new LinkGene(NewNeuronID, to, true, idLink2, OriginalWeight);

                this.Links.Add(link1);
                this.Links.Add(link2);

                //create the new neuron
                NeuronGene NewNeuron = new NeuronGene(NeuronType.Hidden, NewNeuronID, NewDepth, NewWidth);

                //and add it
                this.Neurons.Add(NewNeuron);
            }

            return;
        }
Exemplo n.º 2
0
        /// <summary>
        /// Add a link to the genome dependent upon the mutation rate. This is used in the structural mutation of NEAT
        /// </summary>
        /// <param name="MutationRate"></param>
        /// <param name="ChanceOfRecurrent"></param>
        /// <param name="innovation"></param>
        /// <param name="NumTrysToFindLoop"></param>
        /// <param name="NumTrysToAddLink"></param>
        public void AddLink(double MutationRate, double ChanceOfRecurrent, Innovation innovation, int NumTrysToFindLoop, int NumTrysToAddLink)
        {
            //just return dependent on the mutation rate
            if (RandomProvider.RandomFloat() > MutationRate)
            {
                return;
            }

            //define holders for the two neurons to be linked. If we have find two
            //valid neurons to link these values will become >= 0.
            int ID_neuron1 = -1;
            int ID_neuron2 = -1;

            //flag set if a recurrent link is selected (looped or normal)
            bool bRecurrent = false;

            //first test to see if an attempt shpould be made to create a
            //link that loops back into the same neuron
            if (RandomProvider.RandomFloat() < ChanceOfRecurrent)
            {
                //YES: try NumTrysToFindLoop times to find a neuron that is not an
                //input or bias neuron and that does not already have a loopback
                //connection
                while (NumTrysToFindLoop-- > 0)
                {
                    //grab a random neuron
                    int NeuronPos = RandomProvider.RND.Next(this.NumberOfInputs + 1, this.Neurons.Count - 1);

                    //check to make sure the neuron does not already have a loopback
                    //link and that it is not an input or bias neuron
                    if (!this.Neurons[NeuronPos].Recurrent &&
                        (this.Neurons[NeuronPos].NeuronType != NeuronType.Bias) &&
                        (this.Neurons[NeuronPos].NeuronType != NeuronType.Input))
                    {
                        ID_neuron1 = ID_neuron2 = this.Neurons[NeuronPos].ID;

                        this.Neurons[NeuronPos].Recurrent = true;

                        bRecurrent = true;

                        NumTrysToFindLoop = 0;
                    }
                }
            }

            /*
             *  First, the code checks to see if there is a chance of a looped recurrent link being
             *  added. If so, then it attempts NumTrysToFindLoop times to find an appropriate neuron. If
             *  no neuron is found, the program continues to look for two unconnected neurons.
             */
            else
            {
                //No: try to find two unlinked neurons. Make NumTrysToAddLink
                //attempts

                /*
                 *  Because some networks will already have existing connections between all its available
                 *  neurons, the code has to make sure it doesn’t enter an infinite loop when it
                 *  tries to find two unconnected neurons. To prevent this from happening, the program
                 *  only tries NumTrysToAddLink times to find two unlinked neurons.
                 */
                while (NumTrysToAddLink-- > 0)
                {
                    //choose two neurons, the second must not be an input or a bias
                    ID_neuron1 = this.Neurons[RandomProvider.RND.Next(0, this.Neurons.Count - 1)].ID;

                    ID_neuron2 =
                        this.Neurons[RandomProvider.RND.Next(this.NumberOfInputs + 1, this.Neurons.Count - 1)].ID;

                    if (ID_neuron2 == 2)
                    {
                        continue;
                    }

                    //make sure these two are not already linked and that they are
                    //not the same neuron
                    if (!(DuplicateLink(ID_neuron1, ID_neuron2) ||
                          (ID_neuron1 == ID_neuron2)))
                    {
                        NumTrysToAddLink = 0;
                    }

                    else
                    {
                        ID_neuron1 = -1;
                        ID_neuron2 = -1;
                    }
                }
            }

            //return if unsuccessful in finding a link
            if ((ID_neuron1 < 0) || (ID_neuron2 < 0))
            {
                return;
            }

            //check to see if we have already created this innovation

            /*
             *  Here, the code examines the innovation database to see if this link has already been
             *  discovered by another genome. CheckInnovation returns either the ID number of the
             *  innovation or, if the link is a new innovation, a negative value.
             */
            int id = innovation.CheckInnovation(ID_neuron1, ID_neuron2, InnovationType.NewLink);

            //is this link recurrent?
            if (this.Neurons[GetElementPosition(ID_neuron1)].SplitY >
                this.Neurons[GetElementPosition(ID_neuron2)].SplitY)
            {
                bRecurrent = true;
            }

            /*
             * Here, the split values for the two neurons are compared to see if the link feeds
             * forward or backward.
             */
            if (id < 0)
            {
                //we need to create a new innovation
                innovation.CreateNewInnovation(ID_neuron1, ID_neuron2, InnovationType.NewLink);

                //then create the new gene
                id = innovation.NextNumber() - 1;

                /*
                 *  If the program enters this section of code, then the innovation is a new one. Before
                 *  the new gene is created, the innovation is added to the database and an identification
                 *  number is retrieved. The new gene will be tagged with this identification
                 *  number.
                 */
                LinkGene NewGene = new LinkGene(ID_neuron1,
                                                ID_neuron2,
                                                true,
                                                id,
                                                RandomProvider.RandomClamped(),
                                                bRecurrent);

                this.Links.Add(NewGene);
            }

            else
            {
                //the innovation has already been created so all we need to
                //do is create the new gene using the existing innovation ID
                LinkGene NewGene = new LinkGene(ID_neuron1,
                                                ID_neuron2,
                                                true,
                                                id,
                                                RandomProvider.RandomClamped(),
                                                bRecurrent);

                this.Links.Add(NewGene);
            }

            return;
        }