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; }