经验教训:遗传算法入门






4.97/5 (15投票s)
遗传算法入门,简要提及生物学,并举例说明如何找到复杂数学方程的一个解。
C# 和 C++ 示例均使用 MS Visual Studio Community 2015 创建。Java 示例使用 Eclipse neon.3 创建。源代码兼容不同平台。
引言
本文旨在介绍遗传算法,该算法广泛应用于机器学习和人工智能等现代技术中。本文面向初学者,作为主课程的补充阅读材料,旨在激励他们学习更高级的主题。对于所有希望提高知识和技能的人来说,本文也可能是有益的。
作者力求使本文及源代码尽可能简单,并希望读者在阅读文本和运行代码时能有所收获。作者非常乐意收到读者的任何反馈,以改进本文。
目录
背景
动机
作为巴库国立大学的讲师,作者观察到学生们的编程技能和天赋水平各不相同。年轻专业人才的培养不仅需要知识的传递,还需要学习和自我发展的动力。提供补充阅读材料以拓宽学生的视野、加深对信息技术的理解变得尤为重要。这些补充阅读材料并非强制性,但强烈推荐。有才华和技能的学生可以通过实践获得经验,而其他人则可以丰富他们的知识。
如何阅读本文
本文包含两个主要部分:“生物学背景”和“算法实现”。“生物学背景”章节以获取具有所需品质的狗的个体为例。这个例子非常简单。真实的生物过程要复杂得多。但这个例子的主要目的是解释遗传算法中使用的概念。对于经验丰富的专业人士来说,这一章可能显得不必要且令人厌烦,但对于新手来说,这个介绍可能至关重要。
“算法实现”章节解释了如何编程和运行遗传算法。本章使用三种编程语言描述了算法的实现:C#、Java 和 C++。源代码可供下载(参见文章顶部的链接)。
生物学背景
遗传算法的理念来源于自然。所有生物都能通过世代的繁衍而改变,从而适应环境的变化。人们一直利用这一特性来培育具有期望品质的新品种动植物。我们来考虑一个简单的例子。这个例子将揭示过程如何进行以及一些术语的含义。
假设有一群灰色的狗,需要得到白色的狗。下图显示了现有的狗群。
如果在观察狗群时,读者会发现一些个体并非完全是灰色的,而是带有小的白色斑点。这是因为它们在繁殖时发生了突变(这是自然规律)。“突变”和“繁殖”这两个术语在遗传算法中也同样使用。因此,这些概念将在后续的描述中使用。术语“种群”也将被用来代替“狗群”,原因相同。
下一步,我们从种群中选择带有白色斑点的个体。“选择”也是遗传算法(以及生物学和农业)中使用的术语。选择的个体如下图所示。
如果选择的个体进行繁殖,就会产生新的种群。虽然新个体(后代)的品质是其父母品质的组合,但在繁殖过程中(这是自然规律。科学家利用辐射来增强突变),品质会发生偶然变化,称为“突变”。由于突变是随机的,最终的种群将包含三种类型的个体:
- 带有少量或没有白色斑点的个体
- 具有相同大小白色斑点的个体(大多数情况下。我们可以说这些个体没有发生突变。)
- 具有更大白色斑点的个体
下图显示了新的种群。读者可以将其与第一个种群进行比较,以查看差异。
下一步是选择具有更大白色斑点的个体。下图显示了选定的个体。读者也可以将此选择与之前的选择进行比较。
然后繁殖它们以获得下一个种群,如下所示。
选择“更好”的个体,繁殖它们形成新种群,然后一遍又一遍地重复这个过程,直到出现“更好”的个体,直到出现完全白色的(“最佳”)个体。下图展示了过程。
第三次选择。
第三次选择后形成的种群。
第四次选择。
第四次选择后形成的种群。
第五次选择。
第五次选择后形成的种群。
对于这个种群,最终出现了一些完全白色的个体。问题解决了。但如果继续这个过程,“白色”种群就会出现。但由于突变,一些物种会带有灰色的斑点。尽管继续这个过程在生物学和农业中可能很有用,但许多实际的遗传算法会在找到解决方案后停止。只有少量特定情况需要遗传算法继续进行(例如,模拟连续过程的 AI 解决方案)。
“白色”选择。
“白色”种群。
章节总结
本章的总结如下:
- 术语“个体”(specimen)表示一个实例/项。
- 术语“种群”(population)表示一群个体,通常是同一代。
- 术语“选择”(selection)表示一组根据期望标准从种群中选出的个体,用于未来的繁殖。
- 术语“繁殖”(reproduction)表示从上一次选择中获得新的种群(新一代),以增加具有通过组合其父母创建的新个体而产生的个体数量。
- 术语“基因”(genes)表示个体的内部结构,负责形成其所有者的品质。
- 术语“突变”(mutation)表示个体品质的偶然变化。换句话说,突变是基因的偶然变化。
- 持续的选择、带偶然突变的繁殖,可以获得具有期望品质的个体。
算法实现
在实现遗传算法之前,清晰的任务陈述至关重要。问题完全明确后,必须构建模型。模型必须反映适用于计算的生物过程。模型需要阐明以下内容:
- 什么是“个体”?
- 什么是“基因”?
- 个体如何繁殖以形成种群?
- 如何实现突变?
- 种群的大小是多少?
- 选择的大小是多少?
- 如何量化评估个体与解决方案的亲和度?
- 何时可以认为个体是解决方案?
对于简单的问题,模型可以直接编写到计算机程序中。复杂的问题可能需要进行数学建模、分析,并使用专用软件工具进行设计。本例很简单,读者可以在文本和源文件中找到模型的元素。
任务陈述
让我们考虑以下方程:
19.39281272 * X5 + 7.82018991 * X4 + 35.12849546 * X3 - 28.09127103 * X2 + 3.30129489 * X = 20351.07006276
这是一个五次方程,很难用简单的数学方法求解。这是一个很好的起点来学习遗传算法,因为它易于理解、建模和编程。
实现
遗传算法使用面向对象编程来实现。尽管使用了三种编程语言,但主要的类、属性和方法却相当相似。下面对源代码的描述将有助于详细理解模型(源代码可通过文章顶部的链接下载)。
Specimen 类
Class Specimen 代表方程的一个实例。所要求的方程可以表示为如下形式:
F = 19.39281272 * X5 + 7.82018991 * X4 + 35.12849546 * X3 - 28.09127103 * X2 + 3.30129489 * X - 20351.07006276
在这种情况下,方程被指定为“个体”,其中F与零的距离表示X与解的亲和度。由于X是变量,因此选择X作为基因。
基因的初始值设置为 10(参见源代码),因为 10 的 5 次方是 100000,这在范围上接近 20351.07006276。这个基因值并不重要——它只影响迭代次数和计算时间(即使从黑狗群也能得到白狗)。对于当前示例,它可以设置为任何值,而不是 0(当前的突变函数很简单,无法突变零基因)。读者可以尝试将基因设置为不同的值,运行示例,并观察算法的工作原理。
Class Specimen 包含方法 CalculateAffinity(),该方法实现当前基因值的方程计算。CalculateAffinity() 将计算值设置到成员 Affinity 中,该成员用于在 Class SpecimenWorkshop 中对种群进行排序,以便后续选择。在排序之前计算 Affinity 显著减少了计算时间。
C++ 版本的 Class Specimen 包含方法 Clone(),该方法使内存管理代码更易读。C# 和 Java 具有垃圾回收器,因此在这种简单情况下无需担心内存管理(一些内存管理技术可以提高多线程高负载解决方案的性能,即使对于 C# 或 Java 也是如此)。
using System;
namespace L2L_I2GA
{
    // Class that represents the Specimen
    public class Specimen
    {
        // The genes
        public double[] Genes;
        // Affinity to the solution
        public double Affinity;
        // Constructor that creates the Specimen
        // instance with initial genes
        public Specimen()
        {
            // Assume one double value as the genes
            Genes = new double[1];
            // Set up initial value to the genes
            Genes[0] = 10;
        }
        // Calculates affinity of this instance to the solution
        // Contains the equation formula
        public void CalculateAffinity()
        {
            double s1 = Math.Pow(Genes[0], 5) * 19.39281272;
            double s2 = Math.Pow(Genes[0], 4) *  7.82018991;
            double s3 = Math.Pow(Genes[0], 3) * 35.12849546;
            double s4 = Math.Pow(Genes[0], 2) * 28.09127103;
            double s5 = Genes[0] * 3.30129489;
            double f = s1 + s2 + s3 - s4 + s5;
            // Need positive value to evaluate proximity
            // 0 is the best value
            Affinity = Math.Abs(f - 20351.07006276);
        }
    }
}
// Class that represents the Specimen
public class Specimen
{
    // The genes
    public double[] Genes;
    // Affinity to the solution
    public double Affinity;
    // Constructor that creates the Specimen
    // instance with initial genes
    public Specimen()
    {
        // Assume one double value as the genes
        Genes = new double[1];
        // Set up initial value to the genes
        Genes[0] = 10;
    }
    // Calculates affinity of this instance to the solution
    // Contains the equation formula
    public void CalculateAffinity()
    {
        double s1 = Math.pow(Genes[0], 5) * 19.39281272;
        double s2 = Math.pow(Genes[0], 4) *  7.82018991;
        double s3 = Math.pow(Genes[0], 3) * 35.12849546;
        double s4 = Math.pow(Genes[0], 2) * 28.09127103;
        double s5 = Genes[0] * 3.30129489;
        double f = s1 + s2 + s3 - s4 + s5;
        // Need positive value to evaluate proximity
        // 0 is the best value
        Affinity = Math.abs(f - 20351.07006276);
    }
}
#ifndef H_SPECIMEN
#define H_SPECIMEN
// Class that represents the Specimen
class Specimen
{
public:
    // The genes
    double* Genes;
    // Affinity to the solution
    double Affinity;
    // Constructor that creates the Specimen
    // instance with initial genes
    Specimen();
    // Destructor. Frees memory for the Genes
    ~Specimen();
    // Clone the Specimen for simple memory management
    Specimen* Clone();
    // Calculates affinity of this instance to the solution
    // Contains the equation formula
    void CalculateAffinity();
};
#include "Specimen.h"
#include <math.h>
#include <cmath>
// Constructor that creates the Specimen
// instance with initial genes
Specimen::Specimen()
{
    // Assume one double value as the genes
    Genes = new double[1];
    // Set up initial value to the genes
    Genes[0] = 10;
}
// Destructor. Frees memory for the Genes
Specimen::~Specimen()
{
    delete[] Genes;
}
// Clone the Specimen for simple memory management
Specimen* Specimen::Clone()
{
    Specimen* s = new Specimen();
    s->Genes[0] = Genes[0];
    s->Affinity = Affinity;
    return s;
}
// Calculates affinity of this instance to the solution
// Contains the equation formula
void Specimen::CalculateAffinity()
{
    double s1 = pow(Genes[0], 5) * 19.39281272;
    double s2 = pow(Genes[0], 4) *  7.82018991;
    double s3 = pow(Genes[0], 3) * 35.12849546;
    double s4 = pow(Genes[0], 2) * 28.09127103;
    double s5 = Genes[0] * 3.30129489;
    double f = s1 + s2 + s3 - s4 + s5;
    // Need positive value to evaluate proximity
    // 0 is the best value
    Affinity = std::abs(f - 20351.07006276);
}
SpecimenWorkshop 类
Class SpecimenWorkshop 设计用于处理个体、它们的种群和选择。个体的繁殖和突变是随机过程。因此,C# 和 Java 版本的类都包含随机数生成器。C++ 版本则使用 rand() 函数。PopulationSize 和 SelectionSize 分别表示种群和选择的大小。MaxMutationPercent 是基因变化的程度。它可以设置为任何值。MutationLikelyhoodPercent 分配了突变的可能性,允许只有一部分个体发生突变,而另一部分保持不变。Epsilon 用于设置亲和度与目标值(表示找到解的值)比较时的精度。遗传算法包含大量的计算。提高精度(可以通过减小 Epsilon 来设置)会显著增加计算时间。读者可以更改该值并观察示例的工作原理。
该类实现两个 GeneratePopulation 方法。一个方法构建初始种群。对初始种群中所有个体的突变会产生差异最大的个体,从而增加了获得接近解的个体的几率。否则,种群将包含相似的个体,这对未来的计算毫无用处(从所有个体都有白色斑点的狗群中选择比从大多数个体没有白色斑点的狗群中选择要好)。
第二个 GeneratePopulation 方法基于从选择中随机选择的配对繁殖来产生新的种群。本例中使用的繁殖算法很简单但效果很好。它随机选择两个亲本并为它们创建一个后代。其他繁殖策略可以改进算法。有许多不同的策略可以使用。例如,第一个(最佳)个体与其他个体配对繁殖,以及后代的重复繁殖。将选择中的个体保留在新种群中或将其移除也是两种不同的策略。有经验的读者可以测试不同的策略。
方法 ReproduceNew 为两个亲本创建一个新的“后代”个体。它将后代的基因设置为两个亲本基因的平均值。创建后,基因会根据 MutationLikelyhoodPercent 和 MaxMutationPercent 的值发生突变(或不发生)。
方法 Select 通过执行两个主要操作来实现。首先,该方法以使数组包含最佳个体在前、最差个体在后的方式对种群进行排序。其次,最佳个体被复制到选择中以供将来计算。
using System;
namespace L2L_I2GA
{
    // Class that performs main actions with the specimens and the population
    public static class SpecimenWorkshop
    {
        // Random generator
        static Random rnd = new Random();
        // Number of specimens in the population
        public static int PopulationSize;
        // Number of specimens in the selection 
        public static int SelectionSize;
        // Power of mutations
        // 0 - no mutations
        // 100 - mutations up to whole gene value
        // 200 - mutations up to twice gene value
        public static double MaxMutationPercent;
        // Likelihood of mutation
        // 0 - no mutations, 100 - mutations for all cases
        public static int MutationLikelyhoodPercent;
        // Maximal affinity that is considered 
        // for the solution found
        public static double Epsilon;
        // Generate initial population
        public static Specimen[] GeneratePopulation()
        {
            // Creates array representing the population
            Specimen[] p = new Specimen[PopulationSize];
            // Creates specimens
            // Mutation of all specimens in initial population
            // increases variance that increases chance to
            // get better instance.
            for (int i = 0; i < PopulationSize; i++)
            {
                p[i] = new Specimen();
                Mutate(p[i]);
                // Calculate Affinity for new specimens
                p[i].CalculateAffinity();
            }
            return p;
        }
        // Generate population by reproduction of selection
        public static Specimen[] GeneratePopulation(Specimen[] selection)
        {
            // Creates array representing the population
            Specimen[] p = new Specimen[PopulationSize];
            // Copy instances from the selection to keep them
            // in new generation of population
            for (int i = 0; i < SelectionSize; i++)
            {
                p[i] = selection[i];
            }
            // Creates new specimens by reproducing two parents
            // Parents are selected randomly from the selection.
            int chield_index = SelectionSize;
            int parent1_index;
            int parent2_index;
            while(chield_index < PopulationSize)
            {
                // Select two parents randomly in way
                // they are different instances
                do
                {
                    parent1_index = rnd.Next(selection.Length);
                    parent2_index = rnd.Next(selection.Length);
                } while (parent1_index == parent2_index);
                // Creates new specimen
                p[chield_index] = ReproduceNew(
                          selection[parent1_index], 
                          selection[parent2_index]);
                chield_index++;
            }
            return p;
        }
        // Reproduce new specimen on base of two parents
        public static Specimen ReproduceNew(Specimen a, Specimen b)
        {
            Specimen s= new Specimen();
            // Inherit genes as the average oh the parents' genes
            s.Genes[0] = (a.Genes[0] + b.Genes[0]) / 2;
            // Mutate if likelihood allows
            int ml = rnd.Next(101);
            if (ml <= MutationLikelyhoodPercent)
            {
                Mutate(s);
            }
            // Calculate Affinity for new specimen
            s.CalculateAffinity();
            return s;
        }
        // Select the best specimens from the population
        public static Specimen[] Select(Specimen[] population)
        {
            // Sort population by increasing the affinity
            // The best specimens are moving to start of the array
            Sort(population);
            // Create set of selected specimens
            Specimen[] selected = new Specimen[SelectionSize];
            // Copy best specimens into the selection
            for (int i = 0; i < SelectionSize; i++)
            {
                selected[i] = population[i];
            }
            return selected;
        }
        // Sort the population
        public static void Sort(Specimen[] population)
        {
            // Use standard procedure
            Array.Sort<specimen>(population, CompareSpecimens);
        }
        // Comparison function for the standard Sort procedure
        public static int CompareSpecimens(Specimen a, Specimen b)
        {
            if(a.Affinity < b.Affinity)
            {
                return -1;
            } else
            if (a.Affinity > b.Affinity)
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }
        // Mutate the specimen
        public static void Mutate(Specimen sp)
        {
            // Calculate Mutation Factor that is random value between 0 and MaxMutationPercent
            // calculates as ratio between 0 and 1
            double MutationFactor = (MaxMutationPercent / 100.0) * (rnd.Next(1001) / 1000.0);
            // Set mutation to negative with 50% likelihood
            if (rnd.Next(10) < 5)
            {
                MutationFactor = -MutationFactor;
            }
            // Calculate new gene
            sp.Genes[0] = sp.Genes[0] * (1 + MutationFactor);
        }
    }
}
import java.util.Random;
// Class that performs main actions with the specimens and the population
public class SpecimenWorkshop
{
    // Random generator
    static Random rnd = new Random();
    // Number of specimens in the population
    public static int PopulationSize;
    // Number of specimens in the selection 
    public static int SelectionSize;
    // Power of mutations
    // 0 - no mutations
    // 100 - mutations up to whole gene value
    // 200 - mutations up to twice gene value
    public static double MaxMutationPercent;
    // Likelihood of mutation
    // 0 - no mutations, 100 - mutations for all cases
    public static int MutationLikelyhoodPercent;
    // Maximal affinity that is considered 
    // for the solution found
    public static double Epsilon;
    // Generate initial population
    public static Specimen[] GeneratePopulation()
    {
        // Creates array representing the population
        Specimen[] p = new Specimen[PopulationSize];
        // Creates specimens
        // Mutation of all specimens in initial population
        // increases variance that increases chance to
        // get better instance.
        for (int i = 0; i < PopulationSize; i++)
        {
            p[i] = new Specimen();
            Mutate(p[i]);
            // Calculate Affinity for new specimens
            p[i].CalculateAffinity();
        }
        return p;
    }
    // Generate population by reproduction of selection
    public static Specimen[] GeneratePopulation(Specimen[] selection)
    {
        // Creates array representing the population
        Specimen[] p = new Specimen[PopulationSize];
        // Copy instances from the selection to keep them
        // in new generation of population
        for (int i = 0; i < SelectionSize; i++)
        {
            p[i] = selection[i];
        }
        // Creates new specimens by reproducing two parents
        // Parents are selected randomly from the selection.
        int chield_index = SelectionSize;
        int parent1_index;
        int parent2_index;
        while(chield_index < PopulationSize)
        {
            // Select two parents randomly in way
            // they are different instances
            do
            {
                parent1_index = rnd.nextInt(selection.length);
                parent2_index = rnd.nextInt(selection.length);
            } while (parent1_index == parent2_index);
            // Creates new specimen
            p[chield_index] = ReproduceNew(selection[parent1_index], selection[parent2_index]);
            chield_index++;
        }
        return p;
    }
    // Reproduce new specimen on base of two parents
    public static Specimen ReproduceNew(Specimen a, Specimen b)
    {
        Specimen s= new Specimen();
        // Inherit genes as the average oh the parents' genes
        s.Genes[0] = (a.Genes[0] + b.Genes[0]) / 2;
        // Mutate if likelihood allows
        int ml = rnd.nextInt(101);
        if (ml <= MutationLikelyhoodPercent)
        {
            Mutate(s);
        }
        // Calculate Affinity for new specimen
        s.CalculateAffinity();
        return s;
    }
    // Select the best specimens from the population
    public static Specimen[] Select(Specimen[] population)
    {
        // Sort population by increasing the affinity
        // The best specimens are moving to start of the array
        Sort(population);
        // Create set of selected specimens
        Specimen[] selected = new Specimen[SelectionSize];
        // Copy best specimens into the selection
        for (int i = 0; i < SelectionSize; i++)
        {
            selected[i] = population[i];
        }
        return selected;
    }
    // Sort the population
    public static void Sort(Specimen[] population)
    {
        Specimen temp;
        for (int i = 0; i < PopulationSize; i++)
        {
        	for (int j = 0; j < PopulationSize; j++)
        	{
        		if (population[i].Affinity < population[j].Affinity)
        		{
        			temp = population[i];
        			population[i] = population[j];
        			population[j] = temp;
        		}
        	}
        }        	
    }
    // Mutate the specimen
    public static void Mutate(Specimen sp)
    {
        // Calculate Mutation Factor that is random value between 0 and MaxMutationPercent
        // calculates as ratio between 0 and 1
        double MutationFactor = (MaxMutationPercent / 100.0) * (rnd.nextInt(1001) / 1000.0);
        // Set mutation to negative with 50% likelihood
        if (rnd.nextInt(10) < 5)
        {
            MutationFactor = -MutationFactor;
        }
        // Calculate new gene
        sp.Genes[0] = sp.Genes[0] * (1 + MutationFactor);
    }
}
#ifndef H_SPECIMENWORKSHOP
#define H_SPECIMENWORKSHOP
#include "Specimen.h"
// Class that performs main actions with the specimens and the population
class SpecimenWorkshop
{
public:
	// Number of specimens in the population
	static int PopulationSize;
	// Number of specimens in the selection 
	static int SelectionSize;
	// Power of mutations
	// 0 - no mutations
	// 100 - mutations up to whole gene value
	// 200 - mutations up to twice gene value
	static double MaxMutationPercent;
	// Likelihood of mutation
	// 0 - no mutations, 100 - mutations for all cases
	static int MutationLikelyhoodPercent;
	// Maximal affinity that is considered 
	// for the solution found
	static double Epsilon;
	// Generate initial population
	static Specimen** GeneratePopulation();
	// Generate population by reproduction of selection
	static Specimen** GeneratePopulation(Specimen** selection);
	// Reproduce new specimen on base of two parents
	static Specimen* ReproduceNew(Specimen* a, Specimen* b);
	// Select the best specimens from the population
	static Specimen** SpecimenWorkshop::Select(Specimen** population);
	// Sort the population
	static void SpecimenWorkshop::Sort(Specimen** population);
	// Mutate the specimen
	static void SpecimenWorkshop::Mutate(Specimen* sp);
};
#endif
#include "SpecimenWorkshop.h"
#include <stdlib.h>
// Number of specimens in the population
int SpecimenWorkshop::PopulationSize;
// Number of specimens in the selection 
int SpecimenWorkshop::SelectionSize;
// Power of mutations
// 0 - no mutations
// 100 - mutations up to whole gene value
// 200 - mutations up to twice gene value
double SpecimenWorkshop::MaxMutationPercent;
// Likelihood of mutation
// 0 - no mutations, 100 - mutations for all cases
int SpecimenWorkshop::MutationLikelyhoodPercent;
// Maximal affinity that is considered 
// for the solution found
double SpecimenWorkshop::Epsilon;
// Generate initial population
Specimen** SpecimenWorkshop::GeneratePopulation()
{
	// Creates array representing the population
	Specimen** p = new Specimen*[PopulationSize];
	// Creates specimens
	// Mutation of all specimens in initial population
	// increases variance that increases chance to
	// get better instance.
	for (int i = 0; i < PopulationSize; i++)
	{
		p[i] = new Specimen();
		Mutate(p[i]);
		// Calculate Affinity for new specimens
		p[i]->CalculateAffinity();
	}
	return p;
}
// Generate population by reproduction of selection
Specimen** SpecimenWorkshop::GeneratePopulation(Specimen** selection)
{
	// Creates array representing the population
	Specimen** p = new Specimen*[PopulationSize];
	// Copy instances from the selection to keep them
	// in new generation of population
	for (int i = 0; i < SelectionSize; i++)
	{
		p[i] = selection[i]->Clone();
	}
	// Creates new specimens by reproducing two parents
	// Parents are selected randomly from the selection.
	int chield_index = SelectionSize;
	int parent1_index;
	int parent2_index;
	while (chield_index < PopulationSize)
	{
		// Select two parents randomly in way
		// they are different instances
		do
		{
			parent1_index = rand() % SelectionSize;
			parent2_index = rand() % SelectionSize;
		} while (parent1_index == parent2_index);
		// Creates new specimen
		p[chield_index] = ReproduceNew(selection[parent1_index], selection[parent2_index]);
		chield_index++;
	}
	return p;
}
// Reproduce new specimen on base of two parents
Specimen* SpecimenWorkshop::ReproduceNew(Specimen* a, Specimen* b)
{
	Specimen* s = new Specimen();
	// Inherit genes as the average oh the parents' genes
	s->Genes[0] = (a->Genes[0] + b->Genes[0]) / 2;
	// Mutate if likelihood allows
	int ml = rand() % 101;
	if (ml <= MutationLikelyhoodPercent)
	{
		Mutate(s);
	}
	// Calculate Affinity for new specimen
	s->CalculateAffinity();
	return s;
}
// Select the best specimens from the population
Specimen** SpecimenWorkshop::Select(Specimen** population)
{
	// Sort population by increasing the affinity
	// The best specimens are moving to start of the array
	Sort(population);
	// Create set of selected specimens
	Specimen** selected = new Specimen*[SelectionSize];
	// Copy best specimens into the selection
	for (int i = 0; i < SelectionSize; i++)
	{
		selected[i] = population[i]->Clone();
	}
	return selected;
}
// Sort the population
void SpecimenWorkshop::Sort(Specimen** population)
{
	Specimen* temp;
	for (int i = 0; i < PopulationSize; i++)
	{
		for (int j = 0; j < PopulationSize; j++)
		{
			if (population[i]->Affinity < population[j]->Affinity)
			{
				temp = population[i];
				population[i] = population[j];
				population[j] = temp;
			}
		}
	}
}
// Mutate the specimen
void SpecimenWorkshop::Mutate(Specimen* sp)
{
	// Calculate Mutation Factor that is random value between 0 and MaxMutationPercent
	// calculates as ratio between 0 and 1
	double MutationFactor = (MaxMutationPercent / 100.0) * (rand() % 1001 / 1000.0);
	// Set mutation to negative with 50% likelihood
	if ((rand() % 10) < 5)
	{
		MutationFactor = -MutationFactor;
	}
	// Calculate new gene
	sp->Genes[0] = sp->Genes[0] * (1 + MutationFactor);
}
</stdlib.h>
Solver 类
Class Solver 在最高抽象级别上代表遗传算法。方法 Initialize() 通过设置选项和生成初始种群来初始化算法。方法 Run() 运行计算循环直到找到解。计算循环包含两个主要步骤——从当前种群中选择最佳个体,并通过繁殖选定的个体来更新当前种群。如果选择中的最佳个体的亲和度足够接近零(对于本例),循环就会停止。该个体的基因包含方程的解。C++ 版本的类还包含内存管理代码(删除旧对象)。
using System;
namespace L2L_I2GA
{
    // Class that implements the Genetic Algorithm
    // at the highest abstraction level
    public static class Solver
    {
        // Current Population
        static Specimen[] Current_Population;
        // Current Selection
        static Specimen[] Current_Selection;
        // Current Proximity
        static double Current_Proximity;
        // Current Iteration
        static int Current_Iteration;
        // Initialize the algorithm
        public static void Initialize()
        {
            // Set up options for the algorithm
            SpecimenWorkshop.PopulationSize = 10000;
            SpecimenWorkshop.SelectionSize = 1000;
            SpecimenWorkshop.MaxMutationPercent = 500;
            SpecimenWorkshop.MutationLikelyhoodPercent = 90;
            SpecimenWorkshop.Epsilon = 0.000000001;
            // Generate initial population
            Current_Population = SpecimenWorkshop.GeneratePopulation();
            Current_Iteration = 0;
        }
        // Run the algorithm
        public static void Run()
        {
            // Set Current_Proximity to the biggest value
            Current_Proximity = double.MaxValue;
            // Loop while Current_Proximity is not less than the Epsilon
            while (SpecimenWorkshop.Epsilon <= Current_Proximity)
            {
                // Select the best specimens
                Current_Selection = SpecimenWorkshop.Select(Current_Population);
                // Calculate proximity for the top-selected (the best) specimen
                Current_Proximity = Current_Selection[0].Affinity;
                // Report proximity and found solution for the current iteration
                Console.WriteLine(
                                         "{0}\t{1}\t{2}",
                                         Current_Iteration,
                                         Current_Proximity,
                                         Current_Selection[0].Genes[0]);
                // End the calculations if Current_Proximity is less than the Epsilon
                if (Current_Proximity < SpecimenWorkshop.Epsilon)
                {
                    break;
                }
                // Generate new population by reproducing specimens from the selection
                Current_Population =
                                     SpecimenWorkshop.GeneratePopulation(Current_Selection);
                Current_Iteration++;                
            }
        }
    }
}
// Class that implements the Genetic Algorithm
// at the highest abstraction level
public class Solver
{
    // Current Population
    static Specimen[] Current_Population;
    // Current Selection
    static Specimen[] Current_Selection;
    // Current Proximity
    static double Current_Proximity;
    // Current Iteration
    static int Current_Iteration;
    // Initialize the algorithm
    public static void Initialize()
    {
        // Set up options for the algorithm
        SpecimenWorkshop.PopulationSize = 10000;
        SpecimenWorkshop.SelectionSize = 1000;
        SpecimenWorkshop.MaxMutationPercent = 500;
        SpecimenWorkshop.MutationLikelyhoodPercent = 90;
        SpecimenWorkshop.Epsilon = 0.000000001;
        // Generate initial population
        Current_Population = SpecimenWorkshop.GeneratePopulation();
        Current_Iteration = 0;
    }
    // Run the algorithm
    public static void Run()
    {
        // Set Current_Proximity to the biggest value
        Current_Proximity = Double.MAX_VALUE;
        // Loop while Current_Proximity is not less than the Epsilon
        while (SpecimenWorkshop.Epsilon <= Current_Proximity)
        {
            // Select the best specimens
            Current_Selection = SpecimenWorkshop.Select(Current_Population);
            // Calculate proximity for the top-selected (the best) specimen
            Current_Proximity = Current_Selection[0].Affinity;
            // Report proximity and found solution for the current iteration
            System.out.println(
             		Current_Iteration + "\t" + 
                    Current_Proximity + "\t" + 
              		Current_Selection[0].Genes[0]);
            // End the calculations if Current_Proximity is less than the Epsilon
            if (Current_Proximity < SpecimenWorkshop.Epsilon)
            {
                break;
            }
            // Generate new population by reproducing specimens from the selection
            Current_Population = SpecimenWorkshop.GeneratePopulation(Current_Selection);
            Current_Iteration++;                
        }
    }
}
#ifndef H_SOLVER
#define H_SOLVER
#include "Specimen.h"
// Class that implements the Genetic Algorithm
// at the highest abstraction level
class Solver
{
public:
	// Current Population
	static Specimen** Current_Population;
	// Current Selection
	static Specimen** Current_Selection;
	// Current Proximity
	static double Current_Proximity;
	// Current Iteration
	static int Current_Iteration;
	static void Solver::Initialize();
	
	static void Solver::Run();
};
#endif
#include "Solver.h"
#include "SpecimenWorkshop.h"
#include <iostream>
// Current Population
Specimen** Solver::Current_Population;
// Current Selection
Specimen** Solver::Current_Selection;
// Current Proximity
double Solver::Current_Proximity;
// Current Iteration
int Solver::Current_Iteration;
// Initialize the algorithm
void Solver::Initialize()
{
	// Set up options for the algorithm
	SpecimenWorkshop::PopulationSize = 10000;
	SpecimenWorkshop::SelectionSize = 1000;
	SpecimenWorkshop::MaxMutationPercent = 500;
	SpecimenWorkshop::MutationLikelyhoodPercent = 90;
	SpecimenWorkshop::Epsilon = 0.000000001;
	// Generate initial population
	Current_Population = SpecimenWorkshop::GeneratePopulation();
	// Set Current_Selection to zero for correct work delete[] operator
	Current_Selection = 0;
	Current_Iteration = 0;
}
// Run the algorithm
void Solver::Run()
{
	// Set Current_Proximity to the biggest value
	Current_Proximity = 1.7976931348623157E+308;
	// Loop while Current_Proximity is not less than the Epsilon
	while (SpecimenWorkshop::Epsilon <= Current_Proximity)
	{
		// Delete old selection
		if (Current_Selection != 0)
		{
			for (int i = 0; i < SpecimenWorkshop::SelectionSize; i++)
			{
				// Calculate Affinity for new specimens
				delete Current_Selection[i];
			}
			delete[] Current_Selection;
		}
		// Select the best specimens
		Current_Selection = SpecimenWorkshop::Select(Current_Population);
		// Calculate proximity for the top-selected (the best) specimen
		Current_Proximity = Current_Selection[0]->Affinity;
		// Report proximity and found solution for the current iteration
		std::cout << Current_Iteration << '\t';
		std::cout << Current_Proximity << '\t';
		std::cout << Current_Selection[0]->Genes[0] << '\t' << std::endl;;
		// End the calculations if Current_Proximity is less than the Epsilon
		if (Current_Proximity < SpecimenWorkshop::Epsilon)
		{
			break;
		}
		// Delete old population
		for (int i = 0; i < SpecimenWorkshop::PopulationSize; i++)
		{
			// Calculate Affinity for new specimens
			delete Current_Population[i];
		}
		delete[] Current_Population;
		// Generate new population by reproducing specimens from the selection
		Current_Population = SpecimenWorkshop::GeneratePopulation(Current_Selection);
		Current_Iteration++;
	}
}
</iostream>
Main 方法
方法 Main 非常简单。它只包含调用 Solver 类的相关方法以及按下 **Enter** 键后关闭控制台窗口的命令。
using System;
namespace L2L_I2GA
{
    class Program
    {
        static void Main(string[] args)
        {
            Solver.Initialize();
            Solver.Run();
            Console.WriteLine("Press Enter to exit.");
            Console.ReadLine();
        }
    }
}
import java.io.IOException;
public class Program {
    public static void main(String[] args) throws IOException {
        Solver.Initialize();
        Solver.Run();
		System.out.println("Press Enter to exit.");
		System.in.read();
    }
}
#include <iostream>
#include "Solver.h"
int main()
{
	Solver::Initialize();
	Solver::Run();
	std::cout << "Press Enter to exit." << std::endl;
	std::cin.ignore(100000, '\n');
    return 0;
}
</iostream>
输出
遗传算法包含许多随机操作。因此,每次运行的输出都会不同。一次运行的输出如下所示。
可能的缺点
遗传算法包含模糊和随机计算。尽管它可以解决非常困难的问题,但它可能不稳定并陷入无限循环。本章“输出”中显示的图片表明,迭代 [1, 2, 3]、[7, 8]、[9, 10]、[13, 14]、[17, 18]、[19, 20] 和 [25, 26] 包含相同的最佳解决方案(看起来像是复制品)。这表明在某些情况下,繁殖和突变没有产生任何比现有个体更好的个体,也没有使过程更接近解决方案。换句话说,27 个繁殖种群中有 8 个是无用的,并且处理这些种群的计算时间被浪费了。
通过更改 Class Solver 的 Initialize() 方法中的算法选项,读者可以研究示例的不同工作模式。例如,减小 PopulationSize 和 SelectionSize,以及减小 MaxMutationPercent 和 MutationLikelyhoodPercent,可能会导致算法陷入无限循环。
幸运的是,可以通过尝试运行示例并观察其工作原理来设置选项值。其他实际问题(其他方程等)可能更容易或更难解决,并需要相应地选择其他选项。也存在一些无法解决的问题。遗传算法非常灵活,可以适应以更好地解决特定类型的问题(请参阅我的另一篇文章“展望未来 - 机器人自动生成源代码”)。
可以使用数学方法来评估特定问题的可解性并为此算法进行调整。但这超出了本文的范围。
家庭作业
本章的标题可能会引起一些笑声。我只是借用了教育领域的一个术语。但本章可以看作是提醒读者,他们不仅可以阅读文本并运行示例,还可以思考“兔子洞有多深”并尝试自己进行一些额外的研究。
首先,读者可以回顾并找出源代码与生物过程之间的等同之处。作者已经描述了主要的理念,但生物过程和遗传算法本身非常多样且深刻,可以从中发现一些未提及的有趣之处。
其次,读者可以尝试使用不同的选项和不同的方程来运行源代码。这样的练习有助于他获得实践经验。
那些在编程方面有专长的人可以尝试其他繁殖和突变策略。他们还可以尝试解决具有多个变量的方程组,换句话说,具有多个基因(示例使用基因数组,提供了一定的可扩展性)。
我的文章“展望未来 - 机器人自动生成源代码”(在新窗口中打开)描述了使用遗传算法根据要求自动生成新函数。尽管本文看起来相当高级,但它展示了遗传算法的一些有趣能力。
历史
- 2018 年 12 月 15 日:首次发布
















