首页 > 专题系列 > 算法分析 > 遗传算法-入门(Java demo)
2014
05-03

遗传算法-入门(Java demo)

本文用简单的demo介绍遗传算法,适合初学者入门。实际案例参考:旅行商(TSP)问题-遗传算法

遗传算法介绍

遗传算法 ( GA , Genetic Algorithm ) ,也称进化算法 。遗传算法是计算机科学人工智能领域中用于解决最优化的一种搜索启发式算法,是进化算法的一种。这种启发式通常用来生成有用的解决方案来优化和搜索问题。进化算法最初是借鉴了进化生物学中的一些现象而发展起来的,这些现象包括遗传、突变、自然选择以及杂交等。遗传算法通常实现方式为一种计算机模拟。对于一个最优化问题,一定数量的候选解(称为个体)的抽象表示(称为染色体)的种群向更好的解进化。传统上,解用二进制表示(即0和1的串),但也可以用其他表示方法。进化从完全随机个体的种群开始,之后一代一代发生。在每一代中,整个种群的适应度被评价,从当前种群中随机地选择多个个体(基于它们的适应度),通过自然选择和突变产生新的生命种群,该种群在算法的下一次迭代中成为当前种群。

遗传算法是受达尔文的进化论的启发,借鉴生物进化过程而提出的一种启发式搜索算法。因此在介绍遗传算法前有必要简单的介绍生物进化知识。

种群(Population):生物的进化以群体的形式进行,这样的一个群体称为种群。
个体:组成种群的单个生物。
基因 ( Gene ) :一个遗传因子。
染色体 ( Chromosome ) :包含一组的基因。
生存竞争,适者生存:对环境适应度高的、牛B的个体参与繁殖的机会比较多,后代就会越来越多。适应度低的个体参与繁殖的机会比较少,后代就会越来越少。
遗传与变异:新个体会遗传父母双方各一部分的基因,同时有一定的概率发生基因变异。

遗传算法是如何运行的

程遗传算法的基本过是:

  1. 初始化 – 创建一个初始种群。这种群通常是随机生成的,种群的大小根据实际情况而定。(demo中大小为50,个体随机生成)。
  2. 评价 – 计算每一个个体的适应值适应值是根据我们所期望的要求进行计算。这些要求可以是简单的“更快的算法更好”,或复杂的 “坚固的材料更好,但不要太重”。(demo中getFitness函数根据设定的预期结果solution计算个体的适应值)。
  3. 选择-我们要不断提高种群的整体适应值。我们选择当前这一代种群中较优秀的,即适应值高的。有几个不同的选择方法,但基本思想是一样的。(demo中tournamentSelection函数进行选择)
  4. 交叉-交叉过程中,利用上面一步选择到的优秀个体进行交叉(暂且理解为make love的过程吧~)。主要目的是通过由两个或两个以上优秀的个体,来创建下一代,让下一代继承一些优秀的特征,即所谓虎父无犬子。(demo中crossover函数进行交叉操作)
  5. 突变- 突变是一个小概率的事件,我们需要为算法添加一些随机性。(demo中mutate进行突变操作)
  6. 重复2-5,直到达到我们期望的结果。

限制

想象一下,你被告知要戴上眼罩,然后你被放置在同一座山的底部,让你找最高峰 。你是唯一的选择就是不断爬山,直到你发现你不能在往上爬了。你可能会宣布你已经找到了高峰,但你怎么知道?看下面这个图,Local optimum就是局部最优,Peak就是全局最优。虽然遗传算法往往可以避免掉最优最优,当不是我们不能保证一定能找到全局最优解。遗传算法是属于近似算法,适合解决复杂问题

localopt

代码实现

下面是java代码的实现。源代码已经放在github:https://github.com/gaotong2055/Advanced-Algorithm

下面使用的面向对象的设计,主要的类如下:

  • Population – 一个种群,管理所有的个体
  • Individual – 个体,包含一个基因序列
  • Algorithm – 算法类,进行遗传算法的主要操作
  • FitnessCalc – 辅助类,计算个体的适应值等
  • MainTest- 测试类

Population.java

package simpleGa;

public class Population {
    Individual[] individuals;
    // 创建一个种群
    public Population(int populationSize, boolean initialise) {
        individuals = new Individual[populationSize];
        // 初始化种群
        if (initialise) {
            for (int i = 0; i < size(); i++) {
                Individual newIndividual = new Individual();
                newIndividual.generateIndividual();
                saveIndividual(i, newIndividual);
            }
        }
    }

    public Individual getIndividual(int index) {
        return individuals[index];
    }

    public Individual getFittest() {
        Individual fittest = individuals[0];
        // Loop through individuals to find fittest
        for (int i = 0; i < size(); i++) {
            if (fittest.getFitness() <= getIndividual(i).getFitness()) {
                fittest = getIndividual(i);
            }
        }
        return fittest;
    }

    // Get population size
    public int size() {
        return individuals.length;
    }

    // Save individual
    public void saveIndividual(int index, Individual indiv) {
        individuals[index] = indiv;
    }
}

Individual.java

package simpleGa;

public class Individual {

    static int defaultGeneLength = 64;
    //基因序列
    private byte[] genes = new byte[defaultGeneLength];
    // 个体的 适应值
    private int fitness = 0;

    // 创建一个随机的 基因个体
    public void generateIndividual() {
        for (int i = 0; i < size(); i++) {
            byte gene = (byte) Math.round(Math.random());
            genes[i] = gene;
        }
    }

    // Use this if you want to create individuals with different gene lengths
    public static void setDefaultGeneLength(int length) {
        defaultGeneLength = length;
    }

    public byte getGene(int index) {
        return genes[index];
    }

    public void setGene(int index, byte value) {
        genes[index] = value;
        fitness = 0;
    }

    /* Public methods */
    public int size() {
        return genes.length;
    }

    public int getFitness() {
        if (fitness == 0) {
            fitness = FitnessCalc.getFitness(this);
        }
        return fitness;
    }

    @Override
    public String toString() {
        String geneString = "";
        for (int i = 0; i < size(); i++) {
            geneString += getGene(i);
        }
        return geneString;
    }
}

Algorithm.java

package simpleGa;

public class Algorithm {

    /* GA 算法的参数 */
    private static final double uniformRate = 0.5; //交叉概率
    private static final double mutationRate = 0.015; //突变概率
    private static final int tournamentSize = 5; //淘汰数组的大小
    private static final boolean elitism = true; //精英主义

    // 进化一个种群
    public static Population evolvePopulation(Population pop) {
    	// 存放新一代的种群
        Population newPopulation = new Population(pop.size(), false);

        // 把最优秀的那个 放在第一个位置. 
        if (elitism) {
            newPopulation.saveIndividual(0, pop.getFittest());
        }

        // Crossover population
        int elitismOffset;
        if (elitism) {
            elitismOffset = 1;
        } else {
            elitismOffset = 0;
        }
        // Loop over the population size and create new individuals with
        // crossover
        for (int i = elitismOffset; i < pop.size(); i++) {
        	//随机选择两个 优秀的个体
            Individual indiv1 = tournamentSelection(pop);
            Individual indiv2 = tournamentSelection(pop);
            //进行交叉
            Individual newIndiv = crossover(indiv1, indiv2);
            newPopulation.saveIndividual(i, newIndiv);
        }

        // Mutate population  突变
        for (int i = elitismOffset; i < newPopulation.size(); i++) {
            mutate(newPopulation.getIndividual(i));
        }

        return newPopulation;
    }

    // 进行两个个体的交叉 (暂且想象为make love的过程吧)。 交叉的概率为uniformRate
    private static Individual crossover(Individual indiv1, Individual indiv2) {
        Individual newSol = new Individual();
        // 随机的从 两个个体中选择 
        for (int i = 0; i < indiv1.size(); i++) {
            if (Math.random() <= uniformRate) {
                newSol.setGene(i, indiv1.getGene(i));
            } else {
                newSol.setGene(i, indiv2.getGene(i));
            }
        }
        return newSol;
    }

    // 突变个体。 突变的概率为 mutationRate
    private static void mutate(Individual indiv) {
        for (int i = 0; i < indiv.size(); i++) {
            if (Math.random() <= mutationRate) {
                // 生成随机的 0 或 1
                byte gene = (byte) Math.round(Math.random());
                indiv.setGene(i, gene);
            }
        }
    }

    // 随机选择一个较优秀的个体,用了进行交叉
    private static Individual tournamentSelection(Population pop) {
        // Create a tournament population
        Population tournamentPop = new Population(tournamentSize, false);
        //随机选择 tournamentSize 个放入 tournamentPop 中
        for (int i = 0; i < tournamentSize; i++) {
            int randomId = (int) (Math.random() * pop.size());
            tournamentPop.saveIndividual(i, pop.getIndividual(randomId));
        }
        // 找到淘汰数组中最优秀的
        Individual fittest = tournamentPop.getFittest();
        return fittest;
    }
}

FitnessCalc.java

package simpleGa;

public class FitnessCalc {

    static byte[] solution = new byte[64];

    /* Public methods */
    // 设置候选结果为一个 byte array
    public static void setSolution(byte[] newSolution) {
        solution = newSolution;
    }

    // 就是把01 字符串转换为 01数组, 放在 solution中
    static void setSolution(String newSolution) {
        solution = new byte[newSolution.length()];
        // Loop through each character of our string and save it in our byte 
        for (int i = 0; i < newSolution.length(); i++) {
            String character = newSolution.substring(i, i + 1);
            if (character.contains("0") || character.contains("1")) {
                solution[i] = Byte.parseByte(character);
            } else {
                solution[i] = 0;
            }
        }
    }

    // 通过和solution比较 ,计算个体的适应值
    static int getFitness(Individual individual) {
        int fitness = 0;
        for (int i = 0; i < individual.size() && i < solution.length; i++) {
            if (individual.getGene(i) == solution[i]) {
                fitness++;
            }
        }
        return fitness;
    }

    //最优的适应值,即为基因序列的长度
    static int getMaxFitness() {
        int maxFitness = solution.length;
        return maxFitness;
    }
}

下面开始测试:

MainTest.java

package simpleGa;

public class MainTest {
	public static void main(String[] args) {

		// 选择一个期望的基因序列。这个是由自己任意定
		FitnessCalc
				.setSolution("1111000000000000000000000000000000000000000000000000000000001111");

		// 初始化一个种群
		Population myPop = new Population(50, true);

		// 不段迭代,进行进化操作。 直到找到期望的基因序列
		int generationCount = 0;
		while (myPop.getFittest().getFitness() < FitnessCalc.getMaxFitness()) {
			generationCount++;
			System.out.println("Generation: " + generationCount + " Fittest: "
					+ myPop.getFittest().getFitness());
			myPop = Algorithm.evolvePopulation(myPop);
		}
		System.out.println("Solution found!");
		System.out.println("Generation: " + generationCount);
		System.out.println("Final Fittest Genes:");
		System.out.println(myPop.getFittest());

	}
}

输出的解应该是类似这样的:

Generation: 1 Fittest: 39
Generation: 2 Fittest: 45
Generation: 3 Fittest: 46
Generation: 4 Fittest: 48
Generation: 5 Fittest: 52
Generation: 6 Fittest: 55
Generation: 7 Fittest: 58
Generation: 8 Fittest: 60
Generation: 9 Fittest: 62
Generation: 10 Fittest: 63
Generation: 11 Fittest: 63
Solution found!
Generation: 11
Final Fittest Genes:
1111000000000000000000000000000000000000000000000000000000001111

注意,每次的运行结果会不一样,因为里面有很多随机因素。

本文代码参考自国外网站:http://www.theprojectspot.com/tutorial-post/creating-a-genetic-algorithm-for-beginners/3


  1. 题目需要求解的是最小值,而且没有考虑可能存在环,比如
    0 0 0 0 0
    1 1 1 1 0
    1 0 0 0 0
    1 0 1 0 1
    1 0 0 0 0
    会陷入死循环