Beispiel #1
0
 public bool IsSmallerThan(LinkGene linkGene)
 {
     return(this.InnovationID < linkGene.InnovationID);
 }
        /// <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;
        }
        Genome Crossover(Genome mum, Genome dad)
        {
            // TODO: Test the Crossover in Unity

            //helps make the code clearer
            //first, calculate the genome we will using the disjoint/excess
            //genes from. This is the fittest genome.
            parent_type best;

            //if they are of equal fitness use the shorter (because we want to keep
            //the networks as small as possible)
            if (mum.GetFitnessValue() == dad.GetFitnessValue())
            {
                //if they are of equal fitness and length just choose one at
                //random
                if (mum.GetNumGenes() == dad.GetNumGenes())
                {
                    best = (parent_type)RandomProvider.RND.Next(0, 1);
                }

                else
                {
                    if (mum.GetNumGenes() < dad.GetNumGenes())
                    {
                        best = parent_type.MUM;
                    }

                    else
                    {
                        best = parent_type.DAD;
                    }
                }
            }

            else
            {
                if (mum.GetFitnessValue() > dad.GetFitnessValue())
                {
                    best = parent_type.MUM;
                }

                else
                {
                    best = parent_type.DAD;
                }
            }

            //these vectors will hold the offspring's nodes and genes
            List <NeuronGene> BabyNeurons = new List <NeuronGene>();
            List <LinkGene>   BabyGenes   = new List <LinkGene>();

            //temporary List to store all added node IDs
            List <int> vecNeurons = new List <int>();

            //create iterators so we can step through each parents genes and set
            //them to the first gene of each parent
            LinkGene curMum = mum.GetStartOfGenes();
            LinkGene curDad = dad.GetStartOfGenes();
            //int curMumIndex = 0;
            //int curDadIndex = 0;

            //this will hold a copy of the gene we wish to add at each step
            LinkGene SelectedGene       = null;
            int      mumActualItemCount = mum.Links.Count - 1;
            int      dadActualItemCount = dad.Links.Count - 1;

            //step through each parents genes until we reach the end of both
            //while (!((curMumIndex > mumActualItemCount) && (curDadIndex > dadActualItemCount)))
            for (int curMumIndex = 0, curDadIndex = 0; curMumIndex < mum.Links.Count && curDadIndex < dad.Links.Count;)
            {
                if (curDadIndex > dadActualItemCount)
                {
                    curDadIndex = dadActualItemCount;
                }

                if (curMumIndex > mumActualItemCount)
                {
                    curMumIndex = mumActualItemCount;
                }

                curMum = mum.Links[curMumIndex];
                curDad = dad.Links[curDadIndex];

                //the end of mum's genes have been reached
                if (((curMumIndex >= mumActualItemCount) && (curDadIndex != dadActualItemCount)))
                {
                    //if dad is fittest
                    if (best == parent_type.DAD)
                    {
                        //add dads genes
                        SelectedGene = curDad;
                    }

                    //move onto dad's next gene
                    ++curDadIndex;
                }

                //the end of dads's genes have been reached
                else if ((curDadIndex >= dadActualItemCount) && (curMumIndex != mumActualItemCount))
                {
                    //if mum is fittest
                    if (best == parent_type.MUM)
                    {
                        //add mums genes
                        SelectedGene = curMum;
                    }

                    //move onto mum's next gene
                    ++curMumIndex;
                }

                //if mums innovation number is less than dads
                else if (curMum.InnovationID < curDad.InnovationID)
                {
                    //if mum is fittest add gene
                    if (best == parent_type.MUM)
                    {
                        SelectedGene = curMum;
                    }

                    //move onto mum's next gene
                    ++curMumIndex;
                }

                //if dads innovation number is less than mums
                else if (curDad.InnovationID < curMum.InnovationID)
                {
                    //if dad is fittest add gene
                    if (best == parent_type.DAD)
                    {
                        SelectedGene = curDad;
                    }

                    //move onto dad's next gene
                    ++curDadIndex;
                }

                //if innovation numbers are the same
                else if (curDad.InnovationID == curMum.InnovationID)
                {
                    //grab a gene from either parent
                    if (RandomProvider.RandomFloat() < 0.5f)
                    {
                        SelectedGene = curMum;
                    }

                    else
                    {
                        SelectedGene = curDad;
                    }

                    //move onto next gene of each parent
                    ++curMumIndex;
                    ++curDadIndex;
                }

                if (SelectedGene == null)
                {
                    int x = 0;
                    continue;
                }

                //add the selected gene if not already added
                if (BabyGenes.Count == 0)
                {
                    BabyGenes.Add(SelectedGene);
                }

                else
                {
                    if (BabyGenes[BabyGenes.Count - 1].InnovationID !=
                        SelectedGene.InnovationID)
                    {
                        BabyGenes.Add(SelectedGene);
                    }
                }



                //Check if we already have the nodes referred to in SelectedGene.
                //If not, they need to be added.
                AddNeuronID(SelectedGene.FromNeuron, vecNeurons);
                AddNeuronID(SelectedGene.ToNeuron, vecNeurons);

                if (vecNeurons.Count == 13 && !vecNeurons.Contains(11))
                {
                    int yyy = 0;
                }
            }//end while

            //now create the required nodes. First sort them into order
            vecNeurons.Sort(delegate(int x, int y)
            {
                return(x.CompareTo(y));
            });

            for (int i = 0; i < vecNeurons.Count; i++)
            {
                BabyNeurons.Add(Innovation.CreateNeuronFromID(vecNeurons[i]));
            }

            //finally, create the genome
            Genome babyGenome = new Genome(NextGenomeID++,
                                           BabyNeurons,
                                           BabyGenes,
                                           mum.GetNumInputs(),
                                           mum.GetNumOutputs());

            return(babyGenome);
        }
        /// <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;
        }