什么是遗传算法

如果你去百度百科搜的话,他会给你讲一堆遗传算法的概念,其实说白了,遗传算法就是计算机科学家从自然界获得灵感总结的一套优化方法的总称。

遗传算法是用来解决优化问题的。很早之前,我看到过一个视频,介绍使用遗传算法来让计算机设计能走的很远的蛋白质结构。emmm,可能你会不知道我在说什么,那就去看看这个视频吧,这个视频介绍的是用遗传算法来设计小车的结构。

【boxcar2D】进化小车三个多小时的进化过程


遗传算法相比于其他优化算法的优势在于,他是一种弱方法,所谓 弱方法就是说在各个领域几乎都能用,并且你在优化的时候不需要过多的领域知识。并且,有的时候使用遗传算法可以设计出超越人类知识边界的模式


上图就是NASA使用遗传算法设计出的天线,是不是感觉很厉害。

遗传算法的基本流程

java 遗传算法求解车辆路径问题程序源码 java遗传算法编程pdf_java

遗传算法的常用术语

搜索空间:在处理优化问题的时候我们通常是要搜索解空间,以便寻找最优解,所有的解构成的集合就称为搜索空间,我们举一个非常非常简单的模型。

java 遗传算法求解车辆路径问题程序源码 java遗传算法编程pdf_spring_02


上图就是优化一个参数的例子,我们假设这个参数是连续的,那么解空间就是无限大,我们需要找到一个适应度值最大的解作为我们算法的结果。


使用遗传算法搜索解空间通常都是由于解空间太大了,逐个遍历不切实际。

种群规模:种群规模就是遗传算法中任意一个种群的个体数。在设计遗传算法的时候我们通常需要找到一个平衡,因为太多的大部分算法在个体数达到某个值的时候可以取得效果和效率上的平衡,超过这个值算法的效率就会降低,与此同时搜索的效果可能并不会变好。

交叉率:交叉率指的是在执行交叉方法的时候,两个亲代交换基因的概率。

变异率:字面意思,就是在进化过程中基因变异的概率。

基因表示:这可以说是遗传算法设计中的核心问题,不同的问题应该使用不同的编码,这样才能使算法效率最大化。

精英主义:一个种群中通常都会有适应度比较好的个体,如果在这些个体在进化的过程中也进行交叉和变异,那么好的性状可能得不到保留,换一种说法就是,算法就不收敛了。所以通常我们需要保护这些个体,让他们的基因能够安全地流传下去。

实现一个简单的遗传算法

终于到了最激动人心的实现环节了,看了刚刚的介绍,你是不是也跃跃欲试了呢?下面我给大家介绍使用java编写的简单的遗传算法,为什么用java?因为java真的很好用
算法设计的目标:我们使用0和1来编码基因组,目的是找到一个全为1的基因编码。
算法伪代码

generation=0;
//初始化种群
population[generation]=initializePopulation(populationSize);
//评估种群的适应度
evaluatePopulation(population[generation])
while isTerminationConditionMet()==false do
	//选择亲代
	parents=selectParent(population[generation]);
	//交叉
	population[generation+1]=crossover(parents);
	//变异
	population[generation+1]=mutate(population[generation+1]);
	//评估种群适应度
	evaluatePopulation(population[generation]);
	//代数加一
	generation++;
End Loop;

代码示例
我们的代码将包含你下面的四个类
GeneticAlgorithm:所有遗传算法用到的算法都写在这里面
Individual:个体的属性和方法
Population:群体的属性和方法,里面集成了很多个体
AllOnesGA:类似于main函数
首先是Individual.java

package SimpleGA;


//这是个体类
public class Individual {

    private int[] chromosome;       //染色体数组
    private double fitness = -1;    //适应度

    //含有染色体数组的构造函数
    public Individual(int[] chromosome){
        this.chromosome = chromosome;
    }

    //含有染色体长度的构造函数,随机初始化染色体
    public Individual(int chromosomeLength){
        this.chromosome = new int[chromosomeLength];
        for(int gene = 0; gene <  chromosomeLength; gene++){
            if(Math.random() > 0.5){
                this.setGene(gene, 1);
            } else {
                this.setGene(gene, 0);
            }
        }
    }

    public int[] getChromosome(){
        return this.chromosome;
    }

    public int getChromosomeLength(){
        return this.chromosome.length;
    }

    public void setGene(int offset, int gene){
        this.chromosome[offset] = gene;
    }

    public int getGene(int offset){
        return this.chromosome[offset];
    }

    public void setFitness(double fitness){
        this.fitness = fitness;
    }

    public double getFitness(){
        return this.fitness;
    }

    public String toString(){
        String output = "";
        for(int gene = 0; gene < this.chromosome.length; gene++){
            output += this.chromosome[gene];
        }
        return output;
    }


}

接着是Population.java

package SimpleGA;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;


//群体类
public class Population {
    private Individual population[];
    private double populationFitness = -1;  //群体的适应度

    public Population(int populationSize){
        this.population = new Individual[populationSize];
    }

    public Population(int populationSize, int chromosomeLength){
        this.population = new Individual[populationSize];

        for(int individualCount = 0; individualCount < populationSize; individualCount++){
            Individual individual = new Individual(chromosomeLength);   //这通过长度创建个体
            this.population[individualCount] = individual;  //加入到群体中
        }
    }

    public Individual[] getIndividuals(){
        return this.population;
    }

    //将个体按照适应度排序,适应度越高索引值越低
    //排序后根据索引值获得个体
    public Individual getFittest(int offset){
        Arrays.sort(this.population, new Comparator<Individual>() {
            @Override
            public int compare(Individual o1, Individual o2) {
                if(o1.getFitness() > o2.getFitness()){
                    return -1;  //前面比后面高就返回-1
                } else if(o1.getFitness() < o2.getFitness()){
                    return 1;   //前天比后面第就返会1,此时交换两个元素
                } else {
                    return 0;   //相等返回0
                }
            }
        });
        return this.population[offset];
    }

    public void setPopulationFitness(double fitness){
        this.populationFitness = fitness;
    }

    public double getPopulationFitness(){
        return this.populationFitness;
    }

    //得到群体中的个体数
    public int size(){
        return this.population.length;
    }

    //将个体加入到群体中的某个位置
    public Individual setIndividual(int offset, Individual individual){
        return population[offset] = individual;
    }

    public Individual getIndividual(int offset){
        return this.population[offset];
    }

    //对于每一个个体,都将它与其他任意个体随机交换
    public void shuffle(){
        Random rnd = new Random();
        for(int i=population.length-1; i>0; i--){
            int index = rnd.nextInt(i+1);   //随机获得[0, i+1)的一个数
            Individual a = population[index];       //进行交换
            population[index] = population[i];
            population[i] = a;
        }
    }

}

然后是GeneticAlgorithm.java

package SimpleGA;


//这个类写遗传算法的通用算法
public class GeneticAlgorithm {

    private int populationSize;     //搜索范围
    private double mutationRate;    //变异率
    private double crossoverRate;   //交叉率
    private int elitismCount;       //精英数量

    //构造函数
    public GeneticAlgorithm(int populationSize, double mutationRate, double crossoverRate, int elitismCount) {
        this.populationSize = populationSize;
        this.mutationRate = mutationRate;
        this.crossoverRate = crossoverRate;
        this.elitismCount = elitismCount;
    }

    //初始化群体
    public Population initPopulation(int chromosomeLength){
        Population population = new Population(this.populationSize, chromosomeLength);
        return population;
    }

    //评估适应度
    public double calcFitness(Individual individual){
        //记录为1的染色体数量
        int correctGenes = 0;
        //扫描所有染色体
        for(int geneIndex =0; geneIndex<individual.getChromosomeLength(); geneIndex++){
            if(individual.getGene(geneIndex)==1){
                correctGenes++;
            }
        }

        //适应度为二进制编码中为1的编码占编码总数的比例
        double fitness = (double)correctGenes / individual.getChromosomeLength();

        //存储适应度
        individual.setFitness(fitness);

        return fitness;
    }

    //评估群体适应度,其值就是每个个体适应度加和
    public void evalPopulation(Population population){
        double populationFitness = 0;   //群体的适应度
        for(Individual individual : population.getIndividuals()){
            populationFitness += calcFitness(individual);
        }
        population.setPopulationFitness(populationFitness);
    }

    //终止检查
    public boolean isTerminationConditionMet(Population population){
        //只要群体中有一个个体的适应度为1就终止检查
        for(Individual individual : population.getIndividuals()){
            if(individual.getFitness()==1){
                return true;
            }
        }
        return false;
    }

    //选择方法
    //这个方法比较难理解,有点像概率里面的分布函数
    //我们使用轮盘赌的方式实现交叉,就是古典概型,适应度高的个体被选中染色体的概率就高
    public Individual selectParent(Population population){
        //获得个体
        Individual individuals[] = population.getIndividuals();

        double populationFitness = population.getPopulationFitness();
        double rouletteWheelPosition = Math.random()*populationFitness;
        //Math.random() 返回[0, 1)的随机数

        //找到亲代
        double spinWheel = 0;
        for(Individual individual: individuals){
            spinWheel += individual.getFitness();
            if(spinWheel >= rouletteWheelPosition){
                return individual;
            }
        }
        return individuals[population.size()-1];    //返回群体中的最后一个个体
    }

    //交叉方法
    public Population crossoverPopulation(Population population){
        //创建新种群
        Population newPopulation = new Population(population.size());

        //根据种群的适应度遍历种群
        for(int populationIndex=0; populationIndex<population.size(); populationIndex++){
            Individual parent1 = population.getFittest(populationIndex);

            //如果概率上达到了交叉的条件,并且不是精英就交叉(精英的性状直接保留至下一代)
            if(this.crossoverRate>Math.random() && populationIndex>this.elitismCount){
                //初始化后代
                Individual offspring = new Individual(parent1.getChromosomeLength());

                //找到第二个亲代
                //形状越好找被选中的概率越大
                Individual parent2 = selectParent(population);
                //开始交叉操作
                for(int geneIndex=0; geneIndex<parent1.getChromosomeLength(); geneIndex++){
                    //交叉的时候有一半的概率使用p1的基因,一半的概率使用p2的基因
                    if(Math.random() < 0.5){    //使用p1的基因
                        offspring.setGene(geneIndex, parent1.getGene(geneIndex));
                    } else {                    //使用p2的基因
                        offspring.setGene(geneIndex, parent2.getGene(geneIndex));
                    }
                }

                //将子代加入到种群中,实际上就是将原来的parent1替换成offspring了
                newPopulation.setIndividual(populationIndex, offspring);
            } else {
                //直接将个体添加到种群中
                newPopulation.setIndividual(populationIndex, parent1);
            }
        }
        return newPopulation;
    }

    public Population mutatePopulation(Population population){
        //初始化新种群
        Population newPopulation = new Population(this.populationSize);

        //根据适应度遍历种群
        for(int populationIndex=0; populationIndex<population.size(); populationIndex++){
            Individual individual = population.getFittest(populationIndex);
            //遍历个体的基因
            for(int geneIndex=0; geneIndex<individual.getChromosomeLength(); geneIndex++){
                //精英就不用变异了(富人靠科技,穷人靠变异)
                if(populationIndex >= this.elitismCount){
                    if(this.mutationRate > Math.random()){  //达到了变异的条件
                        int newGene = 1;
                        if(individual.getGene(geneIndex) == 1){
                            newGene = 0;    //既然是变异,那么就应该在相应位置将基因取反
                        }
                        //将变异后的基因插入
                        individual.setGene(geneIndex, newGene);
                    }
                }
            }
            //添加个体到群体中
            newPopulation.setIndividual(populationIndex, individual);
        }

        //返回变异后的群体
        return newPopulation;
    }


}

最后是AllOnesGA.java

package SimpleGA;


//引导类,用于初始化遗传算法
public class AllOnesGA {

    public static void main(String[] args){
        //实例化一个遗传算法对象
        GeneticAlgorithm ga = new GeneticAlgorithm(100, 0.01, 0.95, 3);

        //初始化群体,染色体长度为50
        Population population = ga.initPopulation(50);

        //下面进行进化计算
        ga.evalPopulation(population);
        int generation = 1; //进化的代数

        while(ga.isTerminationConditionMet(population) == false){
            //打印群体中适应度最高的个体
            System.out.println("Best solution: "+population.getFittest(0).toString() +" generation: "+generation);

            //交叉
            //更新种群
            population = ga.crossoverPopulation(population);

            //变异
            population = ga.mutatePopulation(population);

            //评估群体
            ga.evalPopulation(population);

            //代数加一
            generation++;
        }

        //打印最终结果
        System.out.println("Found solution in " + generation + "generations");
        System.out.println("Best solution: "+population.getFittest(0).toString());
    }
}

你可以在设置不同的初始值来计算,你会发现,几乎每一次计算的结果都不一样,并且如果精英数设置为0的话,算法可能就不收敛了,我使用上面代码的参数跑的结果如下

java 遗传算法求解车辆路径问题程序源码 java遗传算法编程pdf_spring_03

总结

这只是遗传算法一个很简单的示例,遗传算法还可以解决很多很复杂的问题,总的来说还是挺有意思的哈哈。