博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《Java遗传算法编程》—— 2.8 交叉实现
阅读量:6999 次
发布时间:2019-06-27

本文共 11016 字,大约阅读时间需要 36 分钟。

本节书摘来异步社区《Java遗传算法编程》一书中的第2章,第2.8节,作者: 【英】Lee Jacobson(雅各布森) , 【美】Burak Kanber(坎贝尔),更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.8 交叉实现

为了实现轮盘赌选择,在GeneticAlgorithm类的任意位置增加一个selectParent( )方法。

public Individual selectParent(Population population) {       // Get individuals       Individual individuals[] = population.getIndividuals();       // Spin roulette wheel       double populationFitness = population.getPopulationFitness();       double rouletteWheelPosition = Math.random() * populationFitness;       // Find parent       double spinWheel = 0;       for (Individual individual : individuals) {              spinWheel += individual.getFitness();              if (spinWheel >= rouletteWheelPosition) {                     return individual;              }       }       return individuals[population.size() - 1];}

selectParent( )方法基本上是反着玩轮盘赌。在赌场,轮盘上已经有标记,然后旋转的轮盘,等待球落入位置。但在这里,我们先选择一个随机位置,然后反向工作,弄清楚哪个个体位于该位置。在算法上,这样更容易实现。选择一个介于0和种群总适应度的随机数,然后逐个检查每个个体,同时累加它们的适应度值,直到达到起初选择的随机位置。

既然已经添加了选择方法,下一步就是创建一个交叉方法,利用这个selectParent()方法来选择交叉伙伴。首先,将下面的交叉方法添加到GeneticAlgorithm类。

public Population crossoverPopulation(Population population) {       // Create new population       Population newPopulation = new Population(population.size());       // Loop over current population by fitness       for (int populationIndex = 0; populationIndex < population.size();       populationIndex++) {              Individual parent1 = population.getFittest(populationIndex);              // Apply crossover to this individual?              if (this.crossoverRate > Math.random() && populationIndex >              this.elitismCount) {                     // Initialize offspring                     Individual offspring = new Individual(parent1.                     getChromosomeLength());                     // Find second parent                     Individual parent2 = selectParent(population);                     // Loop over genome                     for (int geneIndex = 0; geneIndex < parent1.                     getChromosomeLength(); geneIndex++) {                            // Use half of parent1's genes and half of                               parent2's genes                            if (0.5 > Math.random()) {                                   offspring.setGene(geneIndex,                                   parent1.getGene(geneIndex));                            } else {                                   offspring.setGene(geneIndex,                                   parent2.getGene(geneIndex));                            }                     }                     // Add offspring to new population                     newPopulation.setIndividual(populationIndex,                     offspring);              } else {                     // Add individual to new population without applying                     crossover                     newPopulation.setIndividual                     (populationIndex, parent1);              }       }       return newPopulation;}

在crossoverPopulation()方法的第一行,为下一代创建一个新的空种群。接下来,遍历种群,利用交叉率来考虑每个个体的交叉(这里还有一个神秘的术语“精英主义”,我们将在下一节讨论)。如果个体不经过交叉,它就直接加入下一个种群,否则就创建一个新个体。后代染色体的填充方法是遍历亲代染色体,随机从每个亲代选择基因,加入后代的染色体。针对种群的每个个体完成这个交叉过程后,交叉方法返回下一代的种群。

至此,我们可在以AllOnesGA类的main方法中,实现交叉的功能。整个AllOnesGA类和main方法打印如下,但和以前比,唯一的变化是在“Apply crossover”注释下面,加入一行crossoverPopulation()调用。

package chapter2;public class AllOnesGA {       public static void main(String[] args) {              // Create GA object              GeneticAlgorithm ga = new GeneticAlgorithm(100, 0.001, 0.95, 0);              // Initialize population              Population population = ga.initPopulation(50);              // Evaluate population              ga.evalPopulation(population);              // Keep track of current generation              int generation = 1;              while (ga.isTerminationConditionMet(population) == false) {                     // Print fittest individual from population                     System.out.println("Best solution: " + population.                     getFittest(0).toString());                     // Apply crossover                     population = ga.crossoverPopulation(population);                     // Apply mutation                     // TODO                     // Evaluate population                     ga.evalPopulation(population);                     // Increment the current generation                     generation++;              }              System.out.println("Found solution in " + generation + "              generations");              System.out.println("Best solution: " + population.              getFittest(0).toString());       }}

现在,运行程序应该能工作,并返回一个有效的解!你可以尝试一下,点击Eclipse中的Run按钮,观察控制台中的结果。

正如你所看到的,单独的交叉足以进化种群。然而,如果没有变异,遗传算法很容易陷入局部最优而不能找到全局最优。在这样简单的问题中,我们不会看到这种现象,但在更复杂的问题领域,我们需要某种机制,将一个种群推离局部最优,试试看是否有其他更好的解。这就是变异的随机性发挥作用的地方:如果一个解停滞在局部最优,随机事件可能将它踢向正确的方向,让它朝着更好的解前进。

2.8.1 精英主义

在讨论变异之前,先来看看我们在交叉方法中引入的elitismCount参数。

由于交叉和变异算子的效果,基本遗传算法往往会在世代之间失去种群中最好的个体。然而,我们需要这些算子来找到更好的解。要在实战中看到这个问题,只需编辑遗传算法的代码,针对每一代打印出最适应个体的适应度。你会发现,尽管它通常会增长,有时候在交叉和变异时最优解会丢失,代之以非最优解。

解决这个问题的一个简单的优化技术,就是始终让最适合的个体不作改变,添加到下一代的种群中。这样,最优个体不会在世代之间丢失。虽然这些个体没有进行交叉操作,但它们仍会被选为另一个个体的亲代,让它们的遗传信息仍然可以和种群中的其他个体分享。将最优解保留到下一代的过程称为“精英主义”。

通常情况下,群体中“精英”个体的最佳数目只占种群总规模的很小一部分。原因是如果该值太高,就会减缓遗传算法搜索过程,因为保留太多个体导致缺乏遗传多样性。和以前讨论的其他参数类似,为最佳表现找到一种平衡,这是非常重要的。

实现精英主义在交叉和变异中都很简单。让我们重新检查crossover Population( ),看看是否应该应用交叉:

// Apply crossover to this individual?if (this.crossoverRate > Math.random() && populationIndex >= this.elitismCount) {       // ...}

仅当交叉条件满足且个体不是精英,才交叉。

个体如何才算精英?此时,种群中的个体已经按它们的适应度排序,因此最强大的个体索引值最小。因此,如果我们需要3个精英个体,应该跳过索引0~2。这将保留最强大的个体,让它们保持不变,传递到下一代。在接下来的变异代码中,我们将使用完全相同的条件。

2.8.2 变异

要完成进化过程,最后要添加的是变异。像交叉一样,有许多不同的变异方法可供选择。如果使用二进制串,一种比较常见的方法是所谓的“位翻转”变异。你可能已经猜到,位翻转变异就是根据位的初始值,将它从1翻转为0,或从0翻转到1。如果染色体使用另外某种形式编码,通常会采用不同的变异方法,以便更好地利用这种编码。

在选择变异和交叉方法时,有一点非常重要,即确保你选择的方法仍然得到一个有效解。我们将后续章节中看到这一概念的实战,但对于这个问题,我们只需要确保基因变异只能产生0和1。例如,基因变异为7将给出一个无效的解。

在本章中,这个建议似乎没有实际意义,而且过于明显,但请考虑另一个简单的问题,如果你需要不重复地从1到6排序(即得到“123456”)。变异算法如果简单地选择1到6之间的随机数,可能产生“126456”,使用“6”两次,这将是一个无效的解,因为每个数字只能用一次。正如你所看到的,即使简单的问题,有时也需要复杂的技术。

与交叉类似,变异基于变异率应用于个体。如果变异率设定为0.1,那么每个基因在变异阶段发生变异的机会是10%。

让我们继续,为GeneticAlgorithm类添加变异的功能。可以将它添加到任何位置:

public Population mutatePopulation(Population population) {       // Initialize new population       Population newPopulation = new Population(this.populationSize);       // Loop over current population by fitness       for (int populationIndex = 0; populationIndex < population.size();       populationIndex++) {              Individual individual = population.              getFittest(populationIndex);              // Loop over individual's genes              for (int geneIndex = 0; geneIndex < individual.              getChromosomeLength(); geneIndex++) {                     // Skip mutation if this is an elite individual                     if (populationIndex >= this.elitismCount) {                            // Does this gene need mutation?                            if (this.mutationRate > Math.random()) {                                   // Get new gene                                   int newGene = 1;                                   if (individual.getGene(geneIndex) == 1) {                                                newGene = 0;                                   }                                   // Mutate gene                                   individual.setGene(geneIndex, newGene);                            }                     }              }              // Add individual to population              newPopulation.setIndividual(populationIndex, individual);       }       // Return mutated population       return newPopulation;}

mutatePopulation( )方法开始为变异的个体创建一个新的空种群,然后开始遍历当前种群。然后,遍历每个个体的染色体,基于变异率,考虑每个基因是否进行位翻转变异。个体的整个染色体遍历完成后,该个体就被加入新的变异种群。所有的个体都通过变异过程后,返回该变异种群。

现在,我们可以完成进化循环的最后一步,将变异功能加到main方法。完成的main方法如下。与前面相比有两处不同:首先,我们在“Apply mutation”注释的下面添加了mutatePopulation()调用。此外,既然已经明白了精英主义的工作原理,我们在new GeneticAlgorithm构造方法中,将elitismCount参数从0改为2。

package chapter2;public class AllOnesGA {       public static void main(String[] args) {              // Create GA object              GeneticAlgorithm ga = new GeneticAlgorithm(100, 0.001, 0.95, 2);              // Initialize population              Population population = ga.initPopulation(50);              // Evaluate population              ga.evalPopulation(population);              // Keep track of current generation              int generation = 1;              while (ga.isTerminationConditionMet(population) == false) {                     // Print fittest individual from population                     System.out.println("Best solution: " + population.                     getFittest(0).toString());                     // Apply crossover                     population = ga.crossoverPopulation(population);                     // Apply mutation                     population = ga.mutatePopulation(population);                     // Evaluate population                     ga.evalPopulation(population);                     // Increment the current generation                     generation++;              }              System.out.println("Found solution in " + generation + "              generations");              System.out.println("Best solution: " + population.              getFittest(0).toString());       }}

2.8.3 执行

现在,你已经完成了第一个遗传算法。Individual和Population类在本章前面已经完全打印,你的这些类应该看起来就像上面一样。最后的AllOnesGA执行类(引导和运行算法的类)直接在上面提供。

GeneticAlgorithm类相当长,你一块一块地创建了它,所以此时,请检查是否已经实现了以下属性和方法。为了节省篇幅,这里省略了所有的注释和方法体(我只是显示了类的折叠视图),但要确保你的类有这些方法,像前面介绍的一样实现。

package chapter2;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) { }       public Population initPopulation(int chromosomeLength) { }       public double calcFitness(Individual individual) { }       public void evalPopulation(Population population) { }       public boolean isTerminationConditionMet(Population population) { }       public Individual selectParent(Population population) { }       public Population crossoverPopulation(Population population) { }       public Population mutatePopulation(Population population) { }}

如果你使用Eclipse IDE,现在可以运行该算法,打开AllOnesGA文件,然后点击“Run”按钮,该按钮通常在IDE的顶部菜单中。

在运行时,该算法将信息打印到控制台,点击Run时,控制台会自动出现在Eclipse中。因为每一个遗传算法的随机性,每次运行看起来有点不同,但输出看起来可能如下所示:

Best solution: 11001110100110111111010111001001100111110011111111Best solution: 11001110100110111111010111001001100111110011111111Best solution: 11001110100110111111010111001001100111110011111111[ ... Lots of lines omitted here ... ]Best solution: 11111111111111111111111111111011111111111111111111Best solution: 11111111111111111111111111111011111111111111111111Found solution in 113 generationsBest solution: 11111111111111111111111111111111111111111111111111

此时,你应该尝试给GeneticAlgorithm构造方法提供不同的参数:populationSize、mutationRate、crossoverRate和elitismCount。不要忘记,统计决定了遗传算法的表现,所以不能通过运行一次来评价一个算法或一种设置的表现:每种不同的设置至少要尝试10次,才能判断其表现。

转载地址:http://trdvl.baihongyu.com/

你可能感兴趣的文章
php 常用 常量集合
查看>>
PyQt5学习-day1 -4 退出按钮
查看>>
使用Parallel.Invoke并行你的代码
查看>>
有状态服务还是无状态服务?
查看>>
python --- 异常处理
查看>>
Linux-Rsync命令参数详解
查看>>
java--xml文件读取(JDOM&DOM4J)
查看>>
Iterator<Entry<String,String>> iter=map.entrySet().iterator(); 是什么意思
查看>>
CUDA笔记(11)
查看>>
Fancybox丰富的弹出层效果
查看>>
口袋笔记VS松鼠笔记
查看>>
silverlight 将chart图倒入到excel
查看>>
IE 下JS和CSS 阻塞后面内容总结
查看>>
Oracle数据库常用操作脚本
查看>>
LeetCode – Refresh – Word Search
查看>>
清理messages提示-bash: /var/log/messages: Operation not permitted的处理
查看>>
flask蓝图的简单使用
查看>>
数据科学家公司生存指南TOP30秘诀
查看>>
ADO.NET笔记——使用Connection连接数据库,使用Command对象的ExecuteReader()方法创建DataReader对象返回多行数据...
查看>>
go第三方日志系统-seelog-使用文档
查看>>