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