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);
        }
Beispiel #4
0
 /// <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);
        }