/// <summary> /// Determine the species. /// </summary> public void SpeciateAndCalculateSpawnLevels() { // calculate compatibility between genomes and species AdjustCompatibilityThreshold(); // assign genomes to species (if any exist) foreach (IGenome g in Population.Genomes) { NEATGenome genome = (NEATGenome)g; bool added = false; foreach (ISpecies s in Population.Species) { double compatibility = genome .GetCompatibilityScore((NEATGenome)s.Leader); if (compatibility <= ParamCompatibilityThreshold) { AddSpeciesMember(s, genome); genome.SpeciesID = s.SpeciesID; added = true; break; } } // if this genome did not fall into any existing species, create a // new species if (!added) { Population.Species.Add( new BasicSpecies(this.Population, genome, Population .AssignSpeciesID())); } } AdjustSpeciesScore(); foreach (IGenome g in Population.Genomes) { NEATGenome genome = (NEATGenome)g; totalFitAdjustment += genome.AdjustedScore; } averageFitAdjustment = totalFitAdjustment / Population.Genomes.Count; foreach (IGenome g in Population.Genomes) { NEATGenome genome = (NEATGenome)g; double toSpawn = genome.AdjustedScore / averageFitAdjustment; genome.AmountToSpawn = toSpawn; } foreach (ISpecies species in Population.Species) { species.CalculateSpawnAmount(); } }
/// <summary> /// Setup for training. /// </summary> private void Init() { // default values ParamActivationMutationRate = 0.1; ParamChanceAddLink = 0.07; ParamChanceAddNode = 0.04; ParamChanceAddRecurrentLink = 0.05; ParamCompatibilityThreshold = 0.26; ParamCrossoverRate = 0.7; ParamMaxActivationPerturbation = 0.1; ParamMaxNumberOfSpecies = 0; ParamMaxPermittedNeurons = 100; ParamMaxWeightPerturbation = 0.5; ParamMutationRate = 0.2; ParamNumAddLinkAttempts = 5; ParamNumGensAllowedNoImprovement = 15; ParamNumTrysToFindLoopedLink = 5; ParamNumTrysToFindOldLink = 5; ParamProbabilityWeightReplaced = 0.1; NeatActivationFunction = new ActivationSigmoid(); OutputActivationFunction = new ActivationLinear(); // NEATGenome genome = (NEATGenome)Population.Genomes[0]; Population.Innovations = new NEATInnovationList(Population, genome.Links, genome.Neurons); splits = Split(null, 0, 1, 0); if (CalculateScore.ShouldMinimize) { bestEverScore = double.MaxValue; } else { bestEverScore = double.MinValue; } ResetAndKill(); SortAndRecord(); SpeciateAndCalculateSpawnLevels(); }
/// <summary> /// Calculate the network depth for the specified genome. /// </summary> /// <param name="genome">The genome to calculate.</param> private void CalculateNetDepth(NEATGenome genome) { int maxSoFar = 0; for (int nd = 0; nd < genome.Neurons.Genes.Count; ++nd) { foreach (SplitDepth split in splits) { if ((genome.GetSplitY(nd) == split.Value) && (split.Depth > maxSoFar)) { maxSoFar = split.Depth; } } } genome.NetworkDepth = maxSoFar + 2; }
/// <summary> /// Construct a NEAT training class. /// </summary> /// <param name="score">How to score the networks.</param> /// <param name="network">The network to base this on.</param> /// <param name="population">The population to use.</param> public NEATTraining(ICalculateScore score, BasicNetwork network, IPopulation population) { ILayer inputLayer = network.GetLayer(BasicNetwork.TAG_INPUT); ILayer outputLayer = network.GetLayer(BasicNetwork.TAG_OUTPUT); this.CalculateScore = new GeneticScoreAdapter(score); this.Comparator = new GenomeComparator(CalculateScore); this.inputCount = inputLayer.NeuronCount; this.outputCount = outputLayer.NeuronCount; this.Population = population; foreach (IGenome obj in population.Genomes) { NEATGenome neat = (NEATGenome)obj; neat.GA = this; } Init(); }
/// <summary> /// Construct a genome by copying another. /// </summary> /// <param name="other">The other genome.</param> public NEATGenome(NEATGenome other) : base(other.GA) { neuronsChromosome = new Chromosome(); linksChromosome = new Chromosome(); this.Chromosomes.Add(neuronsChromosome); this.Chromosomes.Add(linksChromosome); GenomeID = other.GenomeID; networkDepth = other.networkDepth; Score = other.Score; AdjustedScore = other.AdjustedScore; AmountToSpawn = other.AmountToSpawn; inputCount = other.inputCount; outputCount = other.outputCount; speciesID = other.speciesID; // copy neurons foreach (IGene gene in other.Neurons.Genes) { NEATNeuronGene oldGene = (NEATNeuronGene)gene; NEATNeuronGene newGene = new NEATNeuronGene(oldGene.NeuronType , oldGene.Id, oldGene.SplitX, oldGene.SplitY, oldGene.Recurrent, oldGene .ActivationResponse); this.neuronsChromosome.Genes.Add(newGene); } // copy links foreach (IGene gene in other.Links.Genes) { NEATLinkGene oldGene = (NEATLinkGene)gene; NEATLinkGene newGene = new NEATLinkGene(oldGene .FromNeuronID, oldGene.ToNeuronID, oldGene .Enabled, oldGene.InnovationId, oldGene .Weight, oldGene.IsRecurrent); Links.Genes.Add(newGene); } }
/// <summary> /// Construct neat training with a predefined population. /// </summary> /// <param name="calculateScore">The score object to use.</param> /// <param name="population">The population to use.</param> public NEATTraining(ICalculateScore calculateScore, IPopulation population) { if (population.Genomes.Count < 1) { throw new TrainingError("Population can not be empty."); } NEATGenome genome = (NEATGenome)population.Genomes[0]; this.CalculateScore = new GeneticScoreAdapter(calculateScore); this.Population = population; this.inputCount = genome.InputCount; this.outputCount = genome.OutputCount; foreach (IGenome obj in population.Genomes) { NEATGenome neat = (NEATGenome)obj; neat.GA = this; } Init(); }
/// <summary> /// Get the compatibility score with another genome. Used to determine species. /// </summary> /// <param name="genome">The other genome.</param> /// <returns>The score.</returns> public double GetCompatibilityScore(NEATGenome genome) { double numDisjoint = 0; double numExcess = 0; double numMatched = 0; double weightDifference = 0; int g1 = 0; int g2 = 0; while ((g1 < linksChromosome.Genes.Count - 1) || (g2 < linksChromosome.Genes.Count - 1)) { if (g1 == linksChromosome.Genes.Count - 1) { g2++; numExcess++; continue; } if (g2 == genome.Links.Genes.Count - 1) { g1++; numExcess++; continue; } // get innovation numbers for each gene at this point long id1 = ((NEATLinkGene)linksChromosome.Genes[g1]) .InnovationId; long id2 = ((NEATLinkGene)genome.Links.Genes[g2]) .InnovationId; // innovation numbers are identical so increase the matched score if (id1 == id2) { g1++; g2++; numMatched++; // get the weight difference between these two genes weightDifference += Math.Abs(((NEATLinkGene)linksChromosome.Genes[g1]).Weight - ((NEATLinkGene)genome.Links.Genes[g2]) .Weight); } // innovation numbers are different so increment the disjoint score if (id1 < id2) { numDisjoint++; g1++; } if (id1 > id2) { ++numDisjoint; ++g2; } } int longest = genome.NumGenes; if (NumGenes > longest) { longest = NumGenes; } double score = (NEATGenome.TWEAK_EXCESS * numExcess / longest) + (NEATGenome.TWEAK_DISJOINT * numDisjoint / longest) + (NEATGenome.TWEAK_MATCHED * weightDifference / numMatched); return score; }
/// <summary> /// Get the compatibility score with another genome. Used to determine species. /// </summary> /// <param name="genome">The other genome.</param> /// <returns>The score.</returns> public double GetCompatibilityScore(NEATGenome genome) { double numDisjoint = 0; double numExcess = 0; double numMatched = 0; double weightDifference = 0; int g1 = 0; int g2 = 0; while ((g1 < linksChromosome.Genes.Count - 1) || (g2 < linksChromosome.Genes.Count - 1)) { if (g1 == linksChromosome.Genes.Count - 1) { g2++; numExcess++; continue; } if (g2 == genome.Links.Genes.Count - 1) { g1++; numExcess++; continue; } // get innovation numbers for each gene at this point long id1 = ((NEATLinkGene)linksChromosome.Genes[g1]) .InnovationId; long id2 = ((NEATLinkGene)genome.Links.Genes[g2]) .InnovationId; // innovation numbers are identical so increase the matched score if (id1 == id2) { g1++; g2++; numMatched++; // get the weight difference between these two genes weightDifference += Math.Abs(((NEATLinkGene)linksChromosome.Genes[g1]).Weight - ((NEATLinkGene)genome.Links.Genes[g2]) .Weight); } // innovation numbers are different so increment the disjoint score if (id1 < id2) { numDisjoint++; g1++; } if (id1 > id2) { ++numDisjoint; ++g2; } } int longest = genome.NumGenes; if (NumGenes > longest) { longest = NumGenes; } double score = (NEATGenome.TWEAK_EXCESS * numExcess / longest) + (NEATGenome.TWEAK_DISJOINT * numDisjoint / longest) + (NEATGenome.TWEAK_MATCHED * weightDifference / numMatched); return(score); }
/// <summary> /// Perform one training iteration. /// </summary> public override void Iteration() { this.iteration++; IList <NEATGenome> newPop = new List <NEATGenome>(); int numSpawnedSoFar = 0; foreach (ISpecies s in Population.Species) { if (numSpawnedSoFar < Population.Genomes.Count) { int numToSpawn = (int)Math.Round(s.NumToSpawn); bool bChosenBestYet = false; while ((numToSpawn--) > 0) { NEATGenome baby = null; if (!bChosenBestYet) { baby = (NEATGenome)s.Leader; bChosenBestYet = true; } else { // if the number of individuals in this species is only // one // then we can only perform mutation if (s.Members.Count == 1) { // spawn a child baby = new NEATGenome((NEATGenome)s.ChooseParent()); } else { NEATGenome g1 = (NEATGenome)s.ChooseParent(); if (ThreadSafeRandom.NextDouble() < ParamCrossoverRate) { NEATGenome g2 = (NEATGenome)s.ChooseParent(); int NumAttempts = 5; while ((g1.GenomeID == g2.GenomeID) && ((NumAttempts--) > 0)) { g2 = (NEATGenome)s.ChooseParent(); } if (g1.GenomeID != g2.GenomeID) { baby = PerformCrossover(g1, g2); } } else { baby = new NEATGenome(g1); } } if (baby != null) { baby.GenomeID = Population.AssignGenomeID(); if (baby.Neurons.Genes.Count < ParamMaxPermittedNeurons) { baby.AddNeuron(ParamChanceAddNode, ParamNumTrysToFindOldLink); } // now there's the chance a link may be added baby.AddLink(ParamChanceAddLink, ParamChanceAddRecurrentLink, ParamNumTrysToFindLoopedLink, ParamNumAddLinkAttempts); // mutate the weights baby.MutateWeights(ParamMutationRate, ParamProbabilityWeightReplaced, ParamMaxWeightPerturbation); baby.MutateActivationResponse( ParamActivationMutationRate, ParamMaxActivationPerturbation); } } if (baby != null) { // sort the baby's genes by their innovation numbers baby.SortGenes(); if (newPop.Contains(baby)) { throw new EncogError("add"); } newPop.Add(baby); ++numSpawnedSoFar; if (numSpawnedSoFar == Population.Genomes.Count) { numToSpawn = 0; } } } } } while (newPop.Count < Population.Genomes.Count) { NEATGenome newOne = TournamentSelection(Population.Genomes.Count / 5); newPop.Add(newOne); } Population.Clear(); for (int i = 0; i < newPop.Count; i++) { Population.Add(newPop[i]); } ResetAndKill(); SortAndRecord(); IGenome genome = Population.GetBest(); double currentBest = genome.Score; if (this.Comparator.IsBetterThan(currentBest, bestEverScore)) { bestEverScore = currentBest; this.bestEverNetwork = ((BasicNetwork)genome.Organism); } SpeciateAndCalculateSpawnLevels(); }
/// <summary> /// Perform the crossover. /// </summary> /// <param name="mom">The mother.</param> /// <param name="dad">The father.</param> /// <returns>The child.</returns> public NEATGenome PerformCrossover(NEATGenome mom, NEATGenome dad) { NEATParent best; // first determine who is more fit, the mother or the father? if (mom.Score == dad.Score) { if (mom.NumGenes == dad.NumGenes) { if (ThreadSafeRandom.NextDouble() > 0) { best = NEATParent.Mom; } else { best = NEATParent.Dad; } } else { if (mom.NumGenes < dad.NumGenes) { best = NEATParent.Mom; } else { best = NEATParent.Dad; } } } else { if (Comparator.IsBetterThan(mom.Score, dad.Score)) { best = NEATParent.Mom; } else { best = NEATParent.Dad; } } Chromosome babyNeurons = new Chromosome(); Chromosome babyGenes = new Chromosome(); List <long> vecNeurons = new List <long>(); int curMom = 0; int curDad = 0; NEATLinkGene momGene; NEATLinkGene dadGene; NEATLinkGene selectedGene = null; while ((curMom < mom.NumGenes) || (curDad < dad.NumGenes)) { if (curMom < mom.NumGenes) { momGene = (NEATLinkGene)mom.Links.Genes[curMom]; } else { momGene = null; } if (curDad < dad.NumGenes) { dadGene = (NEATLinkGene)dad.Links.Genes[curDad]; } else { dadGene = null; } if ((momGene == null) && (dadGene != null)) { if (best == NEATParent.Dad) { selectedGene = dadGene; } curDad++; } else if ((dadGene == null) && (momGene != null)) { if (best == NEATParent.Mom) { selectedGene = momGene; } curMom++; } else if (momGene.InnovationId < dadGene.InnovationId) { if (best == NEATParent.Mom) { selectedGene = momGene; } curMom++; } else if (dadGene.InnovationId < momGene.InnovationId) { if (best == NEATParent.Dad) { selectedGene = dadGene; } curDad++; } else if (dadGene.InnovationId == momGene.InnovationId) { if (ThreadSafeRandom.NextDouble() < 0.5f) { selectedGene = momGene; } else { selectedGene = dadGene; } curMom++; curDad++; } if (babyGenes.Genes.Count == 0) { babyGenes.Genes.Add(selectedGene); } else { if (babyGenes.Genes[babyGenes.Genes.Count - 1] .InnovationId != selectedGene.InnovationId) { babyGenes.Genes.Add(selectedGene); } } // Check if we already have the nodes referred to in SelectedGene. // If not, they need to be added. AddNeuronID(selectedGene.FromNeuronID, vecNeurons); AddNeuronID(selectedGene.ToNeuronID, vecNeurons); } // now create the required nodes. First sort them into order vecNeurons.Sort(); for (int i = 0; i < vecNeurons.Count; i++) { babyNeurons.Genes.Add(this.Innovations.CreateNeuronFromID( vecNeurons[i])); } // finally, create the genome NEATGenome babyGenome = new NEATGenome(this, Population .AssignGenomeID(), babyNeurons, babyGenes, mom.InputCount, mom.OutputCount); return(babyGenome); }
/// <summary> /// Perform one training iteration. /// </summary> public override void Iteration() { this.iteration++; IList<NEATGenome> newPop = new List<NEATGenome>(); int numSpawnedSoFar = 0; foreach (ISpecies s in Population.Species) { if (numSpawnedSoFar < Population.Genomes.Count) { int numToSpawn = (int)Math.Round(s.NumToSpawn); bool bChosenBestYet = false; while ((numToSpawn--) > 0) { NEATGenome baby = null; if (!bChosenBestYet) { baby = (NEATGenome)s.Leader; bChosenBestYet = true; } else { // if the number of individuals in this species is only // one // then we can only perform mutation if (s.Members.Count == 1) { // spawn a child baby = new NEATGenome((NEATGenome)s.ChooseParent()); } else { NEATGenome g1 = (NEATGenome)s.ChooseParent(); if (ThreadSafeRandom.NextDouble() < ParamCrossoverRate) { NEATGenome g2 = (NEATGenome)s.ChooseParent(); int NumAttempts = 5; while ((g1.GenomeID == g2.GenomeID) && ((NumAttempts--) > 0)) { g2 = (NEATGenome)s.ChooseParent(); } if (g1.GenomeID != g2.GenomeID) { baby = PerformCrossover(g1, g2); } } else { baby = new NEATGenome(g1); } } if (baby != null) { baby.GenomeID = Population.AssignGenomeID(); if (baby.Neurons.Genes.Count < ParamMaxPermittedNeurons) { baby.AddNeuron(ParamChanceAddNode, ParamNumTrysToFindOldLink); } // now there's the chance a link may be added baby.AddLink(ParamChanceAddLink, ParamChanceAddRecurrentLink, ParamNumTrysToFindLoopedLink, ParamNumAddLinkAttempts); // mutate the weights baby.MutateWeights(ParamMutationRate, ParamProbabilityWeightReplaced, ParamMaxWeightPerturbation); baby.MutateActivationResponse( ParamActivationMutationRate, ParamMaxActivationPerturbation); } } if (baby != null) { // sort the baby's genes by their innovation numbers baby.SortGenes(); if (newPop.Contains(baby)) throw new EncogError("add"); newPop.Add(baby); ++numSpawnedSoFar; if (numSpawnedSoFar == Population.Genomes.Count) { numToSpawn = 0; } } } } } while (newPop.Count < Population.Genomes.Count) { NEATGenome newOne = TournamentSelection(Population.Genomes.Count / 5); newPop.Add(newOne); } Population.Clear(); for (int i = 0; i < newPop.Count; i++) { Population.Add(newPop[i]); } ResetAndKill(); SortAndRecord(); IGenome genome = Population.GetBest(); double currentBest = genome.Score; if (this.Comparator.IsBetterThan(currentBest, bestEverScore)) { bestEverScore = currentBest; this.bestEverNetwork = ((BasicNetwork)genome.Organism); } SpeciateAndCalculateSpawnLevels(); }
/// <summary> /// Perform the crossover. /// </summary> /// <param name="mom">The mother.</param> /// <param name="dad">The father.</param> /// <returns>The child.</returns> public NEATGenome PerformCrossover(NEATGenome mom, NEATGenome dad) { NEATParent best; // first determine who is more fit, the mother or the father? if (mom.Score == dad.Score) { if (mom.NumGenes == dad.NumGenes) { if (ThreadSafeRandom.NextDouble() > 0) { best = NEATParent.Mom; } else { best = NEATParent.Dad; } } else { if (mom.NumGenes < dad.NumGenes) { best = NEATParent.Mom; } else { best = NEATParent.Dad; } } } else { if (Comparator.IsBetterThan(mom.Score, dad.Score)) { best = NEATParent.Mom; } else { best = NEATParent.Dad; } } Chromosome babyNeurons = new Chromosome(); Chromosome babyGenes = new Chromosome(); List<long> vecNeurons = new List<long>(); int curMom = 0; int curDad = 0; NEATLinkGene momGene; NEATLinkGene dadGene; NEATLinkGene selectedGene = null; while ((curMom < mom.NumGenes) || (curDad < dad.NumGenes)) { if (curMom < mom.NumGenes) { momGene = (NEATLinkGene)mom.Links.Genes[curMom]; } else { momGene = null; } if (curDad < dad.NumGenes) { dadGene = (NEATLinkGene)dad.Links.Genes[curDad]; } else { dadGene = null; } if ((momGene == null) && (dadGene != null)) { if (best == NEATParent.Dad) { selectedGene = dadGene; } curDad++; } else if ((dadGene == null) && (momGene != null)) { if (best == NEATParent.Mom) { selectedGene = momGene; } curMom++; } else if (momGene.InnovationId < dadGene.InnovationId) { if (best == NEATParent.Mom) { selectedGene = momGene; } curMom++; } else if (dadGene.InnovationId < momGene.InnovationId) { if (best == NEATParent.Dad) { selectedGene = dadGene; } curDad++; } else if (dadGene.InnovationId == momGene.InnovationId) { if (ThreadSafeRandom.NextDouble() < 0.5f) { selectedGene = momGene; } else { selectedGene = dadGene; } curMom++; curDad++; } if (babyGenes.Genes.Count == 0) { babyGenes.Genes.Add(selectedGene); } else { if (babyGenes.Genes[babyGenes.Genes.Count - 1] .InnovationId != selectedGene.InnovationId) { babyGenes.Genes.Add(selectedGene); } } // Check if we already have the nodes referred to in SelectedGene. // If not, they need to be added. AddNeuronID(selectedGene.FromNeuronID, vecNeurons); AddNeuronID(selectedGene.ToNeuronID, vecNeurons); } // now create the required nodes. First sort them into order vecNeurons.Sort(); for (int i = 0; i < vecNeurons.Count; i++) { babyNeurons.Genes.Add(this.Innovations.CreateNeuronFromID( vecNeurons[i])); } // finally, create the genome NEATGenome babyGenome = new NEATGenome(this, Population .AssignGenomeID(), babyNeurons, babyGenes, mom.InputCount, mom.OutputCount); return babyGenome; }