基因算法解析、设计,以解决背包问题和旅行商问题为例

一、算法说明

基因算法

基因算法有一套公共的完整的框架,伪代码如下。

begin
  set time t = 0 # first generation
  initGeneration() # initialize the population P(t)
  while the termination condition isnot met do
  begin

    fitness() # evaluate fitness of each member of the population P(t);
    select() # select members from population P(t) based on fitness;

    # produce the offspring of these pairs using genetic operators(cross,mutate);
    # replace candidates of P(t), with these offspring;
    cross() #
    mutate() #

    set time t = t + 1 # new generation
  end
end

结合以上代码来分析一下基因算法:

  1. 第3行,首先要初始化第一代种群。这里涉及到一个个体是如何编码的,这一点对于不同的具体问题,要做出不同的实现。
  2. 第4~16行,在终止条件到来之前,种群一代代循环进化,具体如下:
    1. 第7行,计算种群个体的适应度,这里可以评估最优秀个体和平均适应度等。这一点上适应度对于不同的具体问题,其个体适应度计算方法不同,要做出不同实现。
    2. 第8行,根据个体的适应度不同,选出优秀的个体。这一点有多种不同的策略,例如轮盘赌策略和随机二选一的策略。
    3. 12~13行,选出优秀的个体之后,我们要用这些个体产生新一代的种群。具体方法则是交叉和变异。这里有两点需注意,一是交叉或变异的个体的选择有多种不同的策略,二是针对于不同的具体问题,交叉或变异的具体方法不同。
    4. 第15行,经过以上操作产生了新的一代,在这里做出标记。
  3. 当达到终止条件时,则结束进化。
代码设计

根据上述描述分析可以看出,基因算法有一套公用的框架。但有两点值得注意,一是选择,交叉,变异的个体选择有多种不同的策略;二是针对于不同的具体问题,个体的基因编码、交叉和变异的具体方式可能不同。所以在设计框架时,要为这两点留出余地,以便:1. 方便复用代码扩展新策略的基因算法,2. 复用代码实现针对于具体问题的基因算法。

设计图如下:

基因算法设计图)

如设计图所示,GeneralGeneticAlgorithm已经实现了基因算法的基本框架,选择,交叉,变异的个体选择都有默认实现。

其默认的选择个体的策略时轮盘赌的策略。
默认的交叉是以一定概率挨个询问个体是否交叉,凑够两个时将两个个体进行交叉。
默认的变异是以一定概率挨个询问个体是否要变异。

其次使用该框架时,必须传入一个继承IGeneticAssistant接口的assistant,assistant决定个体的编码,交叉和变异方式。

使用如下:

IGeneticAssistant assistant = new BasicKnapSack(); //以背包问题为例
GeneralGeneticAlgorithm algorithm = 
            new GeneralGeneticAlgorithm(iterationMax, scale,
            assistant, crossP, mutateP);//其他参数是迭代次数,种群规模,交叉概率,变异概率

algorithm.start(true); //调用此函数开始迭代,true表示迭代过程中打印出每代信息

// 结束后可以取出最好的个体
System.out.println(algorithm.getBestIndividual().toString());

然后也可以继承GeneralGeneticAlgorithm复用代码来实现新策略的基因算法,如图SequenceCross和RandomCross都是复写了父类cross算法,采用不通的策略来选择要交叉的个体。

背包问题

这时候解决背包问题就简单了,只要实现IGeneticAssistant决定个体的编码,适应度评估,交叉,变异和拷贝方式。如上设计图所示的BasicKnapsack类采用如下具体设计。

  1. 个体编码:用一个byte数组表示DNA,个体DNA的长度就是所有物品的数目,然后每一个位置的基因设置为0或1,表示不拿或拿当前的物品
  2. 适应度评估:评估方式很简单,就是将选择的个体的价值都加起来,作为适应度;但如果其总重量超过背包容量,就将其适应度置为0(为防止小概率的种群总适应度为0的情况,可以将此适应度置为接近0,如1e-10)
  3. 交叉:交叉方式是随机选一个起点和终点,然后将两个个体在起点和终点之间的基因段进行交换。
  4. 变异:变异方式是随机选一个起点和终点,然后将该个体在起点和终点之间的基因段中0变1,1变0
  5. 拷贝:由于编码采用数组,所以复制产生新个体时,要将数组中每个值都拷贝一份,才能避免使用相同地址空间。

然后设计图中RandomCrossMute复写交叉和变异的方法:

  1. 交叉:随机生成要改变基因数,然后挑选随机位置的基因进行互换。
  2. 变异:随机生成要改变基因数,然后挑选随机位置的基因0,1倒置。

设计图中的SequenceCrossMute顾名思义就是沿用了默认的方法,将其单独写出来,只是使得结构清新一点。

扩展其它策略的便签是指也可以继承BasicKnapsack来复写特定方法更改策略。

旅行商问题

旅行商问题也是要实现IGeneticAssistant决定个体的编码,评估,交叉,变异和拷贝方式。如上设计图所示的BasicTSP类采用如下具体设计(首先将所有地点存在一个数组里)。

  1. 个体编码:用一个int数组表示DNA,DNA是地点访问顺序的一个序列,也就地点数组下标的一个序列。

  2. 适应度评估:首先计算出旅行商按该个体DNA序列出发再回到起点的总路程,取其倒数作为适应度。也就是距离越短适应度越高

  3. 交叉:交叉的方式比教复杂,举个列子:

    // 这是为交叉之前的两个DNA序列,先随机选取两个点将其分成三份。
    p1 = ( 1 9 2 | 4 6 5 7 | 8 3 )
    p2 = ( 4 5 9 | 1 8 7 6 | 2 3 )
    // 然后将p2从第三部分开始得到临时的新序列tmp2
    tmp2 = 2 3 4 5 9 1 8 7 6
    // 这时候p1的三部分中的中间那部分(记为p12)保持不动,
    // 然后将第一部分和第三部分依此用tmp2中不在p12区间内的元素替换
    // 然后就可以得到p1交叉后的结果c2
    c1 = ( 2 3 9 | 4 6 5 7 | 1 8 )
    // 同样,也可以得到p2交叉后的c2
    c2 = ( 3 9 2 | 1 8 7 6 | 4 5 )
  4. 变异:变异比较简单,举个列子:

    // 同样将待变异p1随机分成三部分
    p1 = ( 1 9 2 | 4 6 5 7 | 8 3 )
    // 然后将中间部分颠倒顺序就得到变异后的c1
    c1 = ( 1 9 2 | 7 5 6 4 | 8 3 )
  5. 拷贝:由于编码采用数组,所以复制产生新个体时,要将数组中每个值都拷贝一份,才能避免使用相同地址空间。

最后扩展其它策略的便签是指也可以继承BasicTSP来复写特定方法更改策略,我没有实现其它策略。

二、背包问题实验分析

实验数据
输入:
30 50 // 30为背包容量,50为物体个数
7.1 2.6 // 物体重量 物体价值
3.4 5.9
8.2 3
2.3 1.9
0.1 6.6
8.8 8.5
1.2 9.4
5.7 0.8
1 0.3
6.8 4.4
0.5 0.5
3.3 0.1
3.7 4.1
6.9 8.2
9.8 7.6
2.4 0.1
2.6 1.2
8.3 8.1
1.6 7.3
2.6 3.2
1.8 7.4
4.3 5.4
5.2 6.2
7.1 4.1
2.2 1.9
6.5 1
6.8 6.5
0.8 5.3
4 5.6
4 5.3
2.4 7
7.2 6.6
1.6 5.8
3.4 2.2
1 7.2
1.9 3.3
2.8 9.6
1.3 8.8
3.4 6.8
9.8 4.5
2.9 4.4
3.1 6.1
7.9 7.8
3.3 7.8
6 0.6
7.4 6.6
4.4 1.1
5.6 5.9
5.4 8.3
1.7 4.8
实验设计
    static double[] mutatePs = {
  0.05,0.1,0.15,0.2,0.3,0.5,0.8};
    static double[] crossPs = {
  0.3,0.5,0.7,0.85};
    static int[] scales = {
  500,1000,1500};

    for (int i = 0; i < crossPs.length; i++) {
      for (int j = 0; j < mutatePs.length; j++) {
        for (int j2 = 0; j2 < scales.length; j2++) {
          doTrain(crossPs[i], mutatePs[j],
                  scales[j2], 10000);
        }
      }
    }

实验设计就像如上代码所示,分别以一定的区间来跑完所有的测试,然后将每组测试输入的到对应的文件中。实验跑完之后,对实验结果进行分析,然后缩小区间再进行实验。

实验输出模式

实验过程中每隔300代输出一次,模式如下:

// 当前代数,当前代最佳适应度,当前代平均适应度,所有代中最佳适应度
g: 9901,best: 106.29999999999998,average: 82.61720000001445,best in total: 106.29999999999998

最终会输出最佳个体,以及最佳个体最早出现的代数

// 代数:适应度,个体编码
351:106.29999999999998,0 1 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 1 0 0 1 0 1 0 1 0 1 1 1 0 0 1 0 1 0 0 0 0 0 1 
实验结果分析

实验结果从以下角度来衡量

  1. 最佳个体适应度:参数优良性
  2. 最佳个体最早出现代数:收敛速度
  3. 最终几代最佳适应度和平均适应度:参数对种群特征的影响。

实验参数会从种群规模,交叉概率,变异概率,交叉变异算法来分析,实验结果见附件部分,具体分析如下

以下结果默认是随机选择交叉个体,具体的交叉变异方法是连续一段的交叉变异。

  1. 种群规模影响

    实验序号 交叉概率 变异概率 种群规模 最佳最早出现代数 最佳个体适应度 最终代最高/平均适应度
    1 0.3 0.05 1000 4985 101.39 96.599/91.689
    2 0.3 0.05 1500 6039 104.69 102.99/98.249
    3 0.3 0.05 500 7199 103.29 99.399/95.099
    4 0.3 0.15 1000 5167 104.69 103.39/88.305
    5 0.3 0.15 1500 2543 106.29 104.69/87.514
    6 0.3 0.15 500 5384 106.29 104.79/85.406
    16 0.3 0.5 1000 4934 106.29 101.69/56.033
    17 0.3 0.5 1500 1759 106.29 102.49/55.495
    18 0.3 0.5 500 5568 106.29 99.699/58.009
    19 0.3 0.8 1000 8131 103.39 93.5/39.687
    20 0.3 0.8 1500 2565 106.29 95.399/38.583
    21 0.3 0.8 500 4590 102.89 88.099/39.505

    从以上数据每三组对比分析,可得

    1. 种群规模较大的1500每次都取得最好的结果,而且除了第1、2、3组之外其它都是种群规模大的更快得到最优的个体。
    2. 但是种群规模大会导致训练速度变慢。
  2. 交叉变异概率的影响

    实验序号 交叉概率 变异概率 种群规模 最佳最早出现代数 最佳个体适应度 最终代最高/平均适应度
    1 0.3 0.05 1000 4985 101.39 96.599/91.689
    2 0.3 0.05 1500 6039 104.69 102.99/98.249
    3 0.3 0.05 500 7199 103.29 99.399/95.099
    19 0.3 0.8 1000 8131 103.39 93.5,a/39.687
    20 0.3 0.8 1500 2565 106.29 95.399/38.583
    21 0.3 0.8 500 4590 102.89 88.099/39.505
    22 0.5 0.05 1000 4222 106.29 103.99/98.414
    23 0.5 0.05 1500 0 1.0E-1 1.0E-1/9.9999
    24 0.5 0.05 500 0 1.0E-1 1.0E-1/1.0000
    43 0.7 0.05 1000 0 1.0E-1 1.0E-1/9.9999
    44 0.7 0.05 1500 0 1.0E-1 1.0E-1/9.9999
    45 0.7 0.05 500 0 1.0E-1 1.0E-1/1.0000
    73 0.85 0.2 1000 3235 106.29 106.29/82.733
    74 0.85 0.2 1500 351 106.29 106.29/82.617
    75 0.85 0.2 500 0 1.0E-1 1.0E-1/1.0000
    76 0.85 0.3 1000 569 106.29 104.79/69.521
    77 0.85 0.3 1500 386 106.29 104.59/71.282
    78 0.85 0.3 500 6896 106.29 104.79/72.296
    79 0.85 0.5 1000 2752 106.29 103.09/56.570
    80 0.85 0.5 1500 340 106.29 101.89/56.360
    81 0.85 0.5 500 6702 106.29 94.599/54.731

    根据以上数据,可以看出交叉变异会严重影响种群,有以下几点

    1. 由于初始值是随机二选一,平均会选一半物品,导致一般一开始都会超过背包容积,这时,如果变异概率很小,会导致种群难以进化,根据上表,可以看到这一点再种群规模小,交叉概率大的时候更加显著。
    2. 交叉变异的概率增大,会使得种群的收敛速度变快,种群1500的基础上,上表74、77、80都是在500次以内收敛,而交叉变异概率小的3、20则收敛较慢。3和20内部对比,后者变异概率大,收敛也相对快许
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值