public Genome() { this.Phenotype = null; this.GenomeID = 0; this.Fitness = 0; this.AdjustedFitness = 0; this.NumberOfInputs = 0; this.NumberOfOutputs = 0; this.AmountToSpawn = 0; this.Species = 0; }
/// <summary> /// Creates a neural network based upon the information in the genome. /// The method that actually creates all the SLinks and SNeurons required for a phenotype /// is Genome::CreatePhenotype.This function iterates through the genome and /// creates any appropriate neurons and all the required links required for pointing to /// those neurons.It then creates an instance of the CNeuralNet class. /// </summary> /// <param name="depth">Returns a pointer to the newly created ANN </param> /// <returns></returns> public NeuralNet CreatePhenotype(int depth) { //first make sure there is no existing phenotype for this genome DeletePhenotype(); //this will hold all the neurons required for the phenotype List <Neuron> phenotypeNeurons = new List <Neuron>(); //first, create all the required neurons for (int i = 0; i < this.Neurons.Count; i++) { Neuron neuron = new Neuron(this.Neurons[i].NeuronType, this.Neurons[i].ID, this.Neurons[i].SplitY, this.Neurons[i].SplitX, this.Neurons[i].ActivationResponse); phenotypeNeurons.Add(neuron); } //now to create the links. for (int cGene = 0; cGene < this.Links.Count; ++cGene) { //make sure the link gene is enabled before the connection is created if (this.Links[cGene].Enabled) { //get the pointers to the relevant neurons int element = GetElementPosition(this.Links[cGene].FromNeuron); Neuron FromNeuron = phenotypeNeurons[element]; element = GetElementPosition(this.Links[cGene].ToNeuron); Neuron ToNeuron = phenotypeNeurons[element]; //create a link between those two neurons and assign the weight stored //in the gene Link tmpLink = new Link(this.Links[cGene].Weight, FromNeuron, ToNeuron, this.Links[cGene].Recurrent); //add new links to neuron FromNeuron.LinksOut.Add(tmpLink); ToNeuron.LinksIn.Add(tmpLink); } } //now the neurons contain all the connectivity information, a neural //network may be created from them. Phenotype = new NeuralNet(phenotypeNeurons, depth); return(Phenotype); }
/// <summary> /// this constructor creates a genome from a vector of LinkGenes a vector of NeuronGenes and an ID number /// </summary> /// <param name="id"></param> /// <param name="neurons"></param> /// <param name="genes"></param> /// <param name="inputs"></param> /// <param name="outputs"></param> public Genome(int id, List <NeuronGene> neurons, List <LinkGene> genes, int inputs, int outputs) { this.Phenotype = null; this.GenomeID = id; this.Fitness = 0; this.AdjustedFitness = 0; this.NumberOfInputs = inputs; this.NumberOfOutputs = outputs; this.AmountToSpawn = 0; this.Species = 0; this.Links = genes; this.Neurons = neurons; }
/// <summary> /// iterates through the population and creates the phenotypes /// cycles through all the members of the population and creates their /// phenotypes. Returns a vector containing pointers to the new phenotypes /// </summary> /// <returns></returns> public List <NeuralNet> CreatePhenotypes() { List <NeuralNet> networks = new List <NeuralNet>(); for (int i = 0; i < this.PopSize; i++) { //calculate max network depth int depth = CalculateNetDepth(Genomes[i]); //create new phenotype NeuralNet net = Genomes[i].CreatePhenotype(depth); networks.Add(net); } return(networks); }
/// <summary> /// this constructor creates a minimal genome where there are output & input neurons and every input neuron is connected to each output neuron /// </summary> /// <param name="id"></param> /// <param name="inputs"></param> /// <param name="outputs"></param> public Genome(int id, int inputs, int outputs) { this.Phenotype = null; this.GenomeID = id; this.Fitness = 0; this.AdjustedFitness = 0; this.NumberOfInputs = inputs; this.NumberOfOutputs = outputs; this.AmountToSpawn = 0; this.Species = 0; this.Neurons = new List <NeuronGene>(); this.Links = new List <LinkGene>(); //create the input neurons double InputRowSlice = 1 / (double)(inputs + 2); for (int i = 0; i < inputs; i++) { this.Neurons.Add(new NeuronGene(NeuronType.Input, i, 0, (i + 2) * InputRowSlice)); } //create the bias this.Neurons.Add(new NeuronGene(NeuronType.Bias, inputs, 0, InputRowSlice)); //create the output neurons double OutputRowSlice = 1 / (double)(outputs + 1); for (int i = 0; i < outputs; i++) { this.Neurons.Add(new NeuronGene(NeuronType.Output, i + inputs + 1, 1, (i + 1) * OutputRowSlice)); } //create the link genes, connect each input neuron to each output neuron and //assign a random weight -1 < w < 1 for (int i = 0; i < inputs + 1; i++) { for (int j = 0; j < outputs; j++) { this.Links.Add(new LinkGene(this.Neurons[i].ID, this.Neurons[inputs + j + 1].ID, true, inputs + outputs + 1 + this.GetNumGenes(), RandomProvider.RandomClamped())); } } }
/// <summary> /// This function performs one epoch of the genetic algorithm and returns a vector of pointers to the new phenotypes /// </summary> /// <param name="FitnessScores"></param> /// <returns></returns> public List <NeuralNet> Epoch(List <double> FitnessScores) { // 21.10.2015 TODO: IF THE FITNESS IF ZERO THE GA WILL NOT WORK AND BEHAVE UN EXPECTEDLY THIS NEEDS FAIL SAFE MACHANISM //first check to make sure we have the correct amount of fitness scores if (FitnessScores.Count != this.Genomes.Length) { Debug.Log("Cga::Epoch(scores/ genomes mismatch)!"); } //Debug.Log(" Befiore ResetAndKill"); //reset appropriate values and kill off the existing phenotypes and //any poorly performing species ResetAndKill(); //Debug.Log("ResetAndKill"); /* * First of all, any phenotypes created during the previous generation are deleted. The * program then examines each species in turn and deletes all of its members apart * from the best performing one. (You use this individual as the genome to be tested * against when the compatibility distances are calculated). If a species hasn’t made * any fitness improvement in CParams::iNumGensAllowedNoImprovement generations, the * species is killed off.*/ //update the genomes with the fitnesses scored in the last run // BUG HERE WITH C# value not set??? for (int gen = 0; gen < this.Genomes.Length; ++gen) { this.Genomes[gen].SetFitness(FitnessScores[gen]); } //sort genomes and keep a record of the best performers SortAndRecord(); //separate the population into species of similar topology, adjust //fitnesses and calculate spawn levels SpeciateAndCalculateSpawnLevels(); //Debug.Log("SpeciateAndCalculateSpawnLevels"); /* * SpeciateAndCalculateSpawnLevels commences by calculating the compatibility distance * of each genome against the representative genome from each live species. If the * value is within a set tolerance, the individual is added to that species. If no species * match is found, then a new species is created and the genome added to that. * When all the genomes have been assigned to a species * SpeciateAndCalculateSpawnLevels calls the member function AdjustSpeciesFitnesses to * adjust and share the fitness scores as discussed previously. * * Next, SpeciateAndCalculateSpawnLevels calculates how many offspring each individual * is predicted to spawn into the new generation. This is a floating-point value calculated * by dividing each genome’s adjusted fitness score with the average adjusted * fitness score for the entire population. For example, if a genome had an adjusted * fitness score of 4.4 and the average is 8.0, then the genome should spawn 0.525 * offspring. Of course, it’s impossible for an organism to spawn a fractional part of * itself, but all the individual spawn amounts for the members of each species are * summed to calculate an overall spawn amount for that species. Table 11.3 may help * clear up any confusion you may have with this process. It shows typical spawn values * for a small population of 20 individuals. The epoch function can now simply iterate * through each species and spawn the required amount of offspring. */ //this will hold the new population of genomes Genome[] NewPop = new Genome[this.PopSize]; int currentNewGenomeIndex = 0; //request the offspring from each species. The number of children to //spawn is a double which we need to convert to an int. int NumSpawnedSoFar = 0; Genome baby = null; //now to iterate through each species selecting offspring to be mated and //mutated for (int spc = 0; spc < this.Species.Count; ++spc) { //because of the number to spawn from each species is a double //rounded up or down to an integer it is possible to get an overflow //of genomes spawned. This statement just makes sure that doesn't //happen if (NumSpawnedSoFar < NeuralNetworkParams.UnityInstantiatedNumSweepers) { //this is the amount of offspring this species is required to // spawn. Rounded simply rounds the double up or down. int NumToSpawn = Mathf.RoundToInt((float)this.Species[spc].NumToSpawn()); bool bChosenBestYet = false; while (NumToSpawn-- > 0) { //first grab the best performing genome from this species and transfer //to the new population without mutation. This provides per species //elitism if (!bChosenBestYet) { baby = this.Species[spc].GetLeader(); bChosenBestYet = true; } else { //if the number of individuals in this species is only one //then we can only perform mutation if (this.Species[spc].NumMembers() == 1) { //spawn a child baby = this.Species[spc].Spawn(); } //if greater than one we can use the crossover operator else { //spawn1 Genome g1 = this.Species[spc].Spawn(); if (RandomProvider.RandomFloat() < NeuralNetworkParams.CrossoverRate) { //spawn2, make sure it's not the same as g1 Genome g2 = this.Species[spc].Spawn(); //number of attempts at finding a different genome int NumAttempts = 5; while ((g1.GetID() == g2.GetID()) && (NumAttempts-- >= 0)) { g2 = this.Species[spc].Spawn(); } if (g1.GetID() != g2.GetID()) { baby = Crossover(g1, g2); } /*Because the number of individuals in a species may be small and because only the * best 20% (default value) are retained to be parents, it is sometimes impossible (or * slow) to find a second genome to mate with. The code shown here tries five times to * find a different genome and then aborts.*/ } else { baby = g1; } } ++this.NextGenomeID; baby.SetID(this.NextGenomeID); //now we have a spawned child lets mutate it! First there is the //chance a neuron may be added if (baby.GetNumNeurons() < NeuralNetworkParams.MaxPermittedNeurons) { baby.AddNeuron(NeuralNetworkParams.ChanceAddNode, Innovation, NeuralNetworkParams.NumTrysToFindOldLink); } ////now there's the chance a link may be added baby.AddLink(NeuralNetworkParams.ChanceAddLink, NeuralNetworkParams.ChanceAddRecurrentLink, Innovation, NeuralNetworkParams.NumTrysToFindLoopedLink, NeuralNetworkParams.NumAddLinkAttempts); //mutate the weights baby.MutateWeights(NeuralNetworkParams.MutationRate, NeuralNetworkParams.ProbabilityWeightReplaced, NeuralNetworkParams.MaxWeightPerturbation); baby.MutateActivationResponse(NeuralNetworkParams.ActivationMutationRate, NeuralNetworkParams.MaxActivationPerturbation); } //sort the babies genes by their innovation numbers baby.SortLinkGenes(); //add to new pop NewPop[currentNewGenomeIndex] = baby; currentNewGenomeIndex++; ++NumSpawnedSoFar; if (NumSpawnedSoFar == NeuralNetworkParams.UnityInstantiatedNumSweepers) { NumToSpawn = 0; } } //end while } //end if } //next species //Debug.Log("SPECIES COMPLETE"); //if there is an underflow due to the rounding error and the amount //of offspring falls short of the population size additional children //need to be created and added to the new population. This is achieved //simply, by using tournament selection over the entire population. if (NumSpawnedSoFar < NeuralNetworkParams.UnityInstantiatedNumSweepers) { //calculate amount of additional children required int Rqd = NeuralNetworkParams.UnityInstantiatedNumSweepers - NumSpawnedSoFar; //grab them while (Rqd-- > 0) { NewPop[currentNewGenomeIndex] = TournamentSelection(this.PopSize / 5); currentNewGenomeIndex++; } } //Debug.Log("numspawned"); //replace the current population with the new one // BUG HERE //this.Genomes = NewPop.ToArray(); //create the new phenotypes List <NeuralNet> new_phenotypes = new List <NeuralNet>(); for (int gen = 0; gen < this.Genomes.Length; ++gen) { //calculate max network depth int depth = CalculateNetDepth(this.Genomes[gen]); NeuralNet phenotype = this.Genomes[gen].CreatePhenotype(depth); new_phenotypes.Add(phenotype); } //Debug.Log("before gen counter"); //increase generation counter ++this.Generation; return(new_phenotypes); }
/// <summary> /// delete the neural network /// </summary> public void DeletePhenotype() { this.Phenotype = null; }