public GeneticAlgorithmFloat(List <DataFP> dataList, int numberOfRules, int totalGenerations, float crossoverRate, int tournamentSize, float mutationRange, float mutationRate, int?populationSize = null) { // Condition length inferred from supplied data. this.boundryLength = dataList[0].cond.Length; // Population defaults to the size of the supplied dataList. this.populationSize = populationSize ?? dataList.Count; this.numberOfRules = numberOfRules; this.totalGenerations = totalGenerations; this.crossoverRate = crossoverRate; this.mutationRate = mutationRate; this.mutationRange = mutationRange; this.mutationRateOriginal = mutationRate; this.mutationRangeOriginal = mutationRange; this.tournamentSize = tournamentSize; this.tournamentSizeOriginal = tournamentSize; // Split up our data into training and evaluation sets. IndividualFP.SetTrainingAndEvaluationData(dataList.Take(1000).ToList(), dataList.Skip(1000).Take(1000).ToList()); generationCounter = 1; population = new List <IndividualFP>(); fitnessLookup = new Dictionary <IndividualFP, int>(); }
/// <summary> /// We perform a bitwise crossover on the supplied population. /// </summary> /// <returns></returns> public List <IndividualFP> CrossoverSinglePoint(List <IndividualFP> population) { Random random = new Random(); var crossoverOutput = new List <IndividualFP>(population); for (int i = 0; i < crossoverOutput.Count; i += 2) { var tempParent1 = new IndividualFP(crossoverOutput[i]); var tempParent2 = new IndividualFP(crossoverOutput[i + 1]); int crossoverPoint = random.Next(numberOfRules); // Check for crossover. if (random.NextDouble() < crossoverRate) { for (int j = crossoverPoint; j < numberOfRules; j++) { tempParent1.Rulebase[j] = crossoverOutput[i + 1].Rulebase[j]; tempParent2.Rulebase[j] = crossoverOutput[i].Rulebase[j]; } } crossoverOutput[i] = tempParent1; crossoverOutput[i + 1] = tempParent2; } return(crossoverOutput); }
/// <summary> /// We generate offspring vis tournament selection. /// </summary> /// <param name="tournamentSize"></param> /// <returns></returns> public List <IndividualFP> TournamentSelection(List <IndividualFP> population) { Random random = new Random(); var offspring = new List <IndividualFP>(); for (int i = 0; i < population.Count; i++) { //Select the parents from the population var parents = new List <IndividualFP>(); for (int j = 0; j < tournamentSize; j++) { //Pick random parents from the population parents.Add(population[random.Next(populationSize)]); } int fitnessMax = 0; var bestParent = new IndividualFP(population[random.Next(populationSize)]); foreach (var parent in parents) { if (fitnessLookup[parent] > fitnessMax) { fitnessMax = fitnessLookup[parent]; bestParent = parent; } } //Select the best parent to be the offspring offspring.Add(new IndividualFP(bestParent)); } return(offspring); }
/// <summary> /// Used for cloning purposes. /// </summary> /// <param name="clonedIndividual"></param> public IndividualFP(IndividualFP clonedIndividual) { Rulebase = new List <RuleFP>(); foreach (var rule in clonedIndividual.Rulebase) { Rulebase.Add(new RuleFP(rule)); } }
/// <summary> /// We perform a uniform crossover on the supplied population. /// </summary> /// <param name="population"></param> /// <returns></returns> public List <IndividualFP> CrossoverUniform(List <IndividualFP> population) { Random random = new Random(); var crossoverOutput = new List <IndividualFP>(population); for (int i = 0; i < crossoverOutput.Count; i += 2) { var tempParent1 = new IndividualFP(crossoverOutput[i]); var tempParent2 = new IndividualFP(crossoverOutput[i + 1]); // Check for crossover. if (random.NextDouble() < crossoverRate) { for (int j = 0; j < numberOfRules; j++) { for (int k = 0; k < boundryLength; k++) { if (random.Next(2) > 0) { if (random.Next(2) > 0) { tempParent1.Rulebase[j].condBoundry[k].high = crossoverOutput[i + 1].Rulebase[j].condBoundry[k].high; tempParent2.Rulebase[j].condBoundry[k].high = crossoverOutput[i].Rulebase[j].condBoundry[k].high; } else { tempParent1.Rulebase[j].condBoundry[k].low = crossoverOutput[i + 1].Rulebase[j].condBoundry[k].low; tempParent2.Rulebase[j].condBoundry[k].low = crossoverOutput[i].Rulebase[j].condBoundry[k].low; } } } if (random.Next(2) > 0) { tempParent1.Rulebase[j].output = crossoverOutput[i + 1].Rulebase[j].output; tempParent2.Rulebase[j].output = crossoverOutput[i].Rulebase[j].output; } } } crossoverOutput[i] = tempParent1; crossoverOutput[i + 1] = tempParent2; } return(crossoverOutput); }
/// <summary> /// We initiate and evolve our GA here until it meets the termination conditions. /// </summary> /// <param name="requiredFitness"></param> /// <returns></returns> public Tuple <List <RuleFP>, int, int, int> RunGA(int requiredFitness) { // For tracking purposes. int[] maximumFitness = new int[totalGenerations + 1]; double[] averageFitness = new double[totalGenerations + 1]; List <double> meanToMaxRatioOverTime = new List <double>(); // These bools are an expansion on the adaptive mutation idea. bool stagnation = false; bool superStagnation = false; // We fill our population with randomly generated chromosomes. InitiatePopulation(); // Each chromosome's fitness is calculated and appended to a lookup table. fitnessToLookup(); // We keep the best individual to ensure it survives the selection process. IndividualFP best = new IndividualFP(FindBestIndividual()); maximumFitness[0] = maxFitness; averageFitness[0] = meanFitness; // We enter the main generationasl loop of the GA. for (int k = 0; k < totalGenerations; k++) { Random random = new Random(); // Tournament selection of offspring. var offspring = TournamentSelection(population); // We switch our crossover strategy to Uniform if gene stagnation is detected. if (stagnation && random.Next(100) < 10) { offspring = CrossoverUniform(offspring); } else { offspring = CrossoverSinglePoint(offspring); } // We mutate our offspring. offspring = Mutate(offspring); // Offspring added to population. population.Clear(); population.AddRange(offspring); // Best fitness individual added back into population. population[random.Next(populationSize)] = best; fitnessToLookup(); best = new IndividualFP(FindBestIndividual()); // Check for termination condition. if (best.fitness >= requiredFitness || k == totalGenerations - 1) { return(new Tuple <List <RuleFP>, int, int, int>(best.Rulebase.OrderByDescending(x => x.fitness).ToList(), generationCounter, best.fitness, best.evaluationFitness)); } offspring.Clear(); generationCounter++; // Getting stats. totalFitness = 0; maxFitness = 0; meanFitness = 0; foreach (var ind in fitnessLookup) { if (ind.Value > maxFitness) { maxFitness = ind.Value; } totalFitness += ind.Value; } meanFitness = (float)totalFitness / (float)populationSize; maximumFitness[k + 1] = maxFitness; averageFitness[k + 1] = meanFitness; double meanToMaxRatio = (double)meanFitness / (double)maxFitness; if (generationCounter % 10 == 0) { Console.WriteLine($"Gen = {generationCounter}, mean fitness = {(int)meanFitness}, max fitness = {(int)maxFitness} MR/MRan = {mutationRate:N3}/{mutationRange:N3} S:{stagnation} MToMRatio:{meanToMaxRatio:N2}"); } // Here we set super stagnation to true if the max fitness of the population hasnt increased in 200 generations. if (k > 200) { if (maxFitness == maximumFitness[k - 200]) { superStagnation = true; } else { superStagnation = false; } } // Here we keep a running average of the change in the mean to max ration over time. meanToMaxRatioOverTime.Add(meanToMaxRatio); // We wait until we have 60 generations worth of data. if (meanToMaxRatioOverTime.Count > 60) { var recentChange = meanToMaxRatioOverTime.Skip(meanToMaxRatioOverTime.Count - 30).Take(30).Average(); // Has the mean to max ratio not changed much? if so set stagnation to true. // Super stagnation allows for a creater gap between maxfitness and meanfitness for exploration. if (meanToMaxRatio > (recentChange - 0.07) && meanToMaxRatio < (recentChange + 0.07) && meanToMaxRatio > (superStagnation?0.90:0.96)) { stagnation = true; } else { stagnation = false; } } // Adaptive mutation. if (stagnation) { tournamentSize = 2; mutationRate += 0.0016f; if (random.NextDouble() < 0.95 || !superStagnation) { mutationRange += (mutationRange / 10); } else { double u1 = 1.0 - random.NextDouble(); //uniform(0,1] random doubles double u2 = 1.0 - random.NextDouble(); double randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); //random normal(0,1) double randNormal = 0.3 * randStdNormal; if (randNormal < 0.0) { randNormal = -randNormal; } // We will randomly shift the mutation range to either very low or a random value to encourage exploration. if (random.Next(2) > 0) { mutationRange = mutationRangeOriginal / 50; } else { mutationRate = mutationRateOriginal / 2; mutationRange = (float)(randNormal); } } if (mutationRate > 1.0f) { mutationRate = 1.0f; } if (mutationRange > 1.0f) { mutationRange = 1.0f; } } else { // If superstagnation is active, we use an agressive adaptive mutation. if (superStagnation) { tournamentSize = 2; mutationRange -= (mutationRange / 10); mutationRate -= 0.0016f; if (mutationRate < mutationRateOriginal) { mutationRate = mutationRateOriginal / 50; } if (mutationRange < mutationRangeOriginal) { mutationRange = mutationRangeOriginal / 50; } } // Otherwise its business as usual. else { tournamentSize = tournamentSizeOriginal; mutationRate = mutationRateOriginal; mutationRange = mutationRangeOriginal; } } } return(null); }