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