Conmajia

Stop stealing sheep!

导航

< 20253 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

🐒 进化计算

© Andrew Kirillov 2006, Conmajia 2012

安德鲁·基里洛夫 著Conmajia

作者简介

安德鲁·基里洛夫是一名高级软件工程师安德鲁是著名的图像数学神经网络编程框架 AForge.NET 的作者

原文链接点击访问

演示DEMO点击下载

源代码点击下载

简介

人们在进化计算领域进行了非常多的研究工作总结出了大量的进化算法研究者对这些方法进行了广泛的钻研并尝试将它们应用到众多不同领域的任务中有一个众所周知的事实那就是许多科研问题使用传统方法都不可能在一个合理的时间范围内得出准确的结果也有许多问题没有一个形式化的解决方法这使得人们很难——甚至是不可能——用传统方法来解决这些问题一个典型的例子就是“旅行商问题”Traveling Salesman ProblemTSPTSP要求在给定数量的城市之间找到一条最短的路径使得旅行商能访问所有的城市并且每个城市只访问一次最后回到出发的城市对于这样的问题很多时候我们可以使用进化计算方法在可以接受的时间范围内得到一个较好的解使用进化计算方法并不能保证得到问题的精确解而是找到一个最接近最佳答案的“足够好”的解这就是为什么这类方法越来越多的被用于解决很多不同的问题——这些问题往往是不能或者很难用传统方法求解的

本文讨论了一个用C#实现的进化计算类库该类库实现了数个流行的进化算法如遗传算法GA遗传编程GP和基因表达式编程GEP该类库可以用于求解多种不同的实际问题其用法通过以下4个例子进行演示

  • 函数优化
  • 符号回归计算
  • 时间序列预测
  • 旅行商问题

 

该类库的设计思想是保证其灵活性和可重用性以便能将其用于解决不同的问题本文不会讨论进化算法的详细内容取而代之地本文简要介绍了相关的算法并在文后提供了一系列参考资料以便感兴趣的读者深入研究

进化计算

遗传算法的历史始于20世纪60年代John Holland在他的工作中首先提出了基于进化的遗传算法GA思想从那时开始许多研究者开始加入到进化计算领域由此产生了很多不同的算法[1]这些算法被广泛地研究并应用于大量不同问题求解到了今时今日人们仍在各自领域中继续研究这些算法也使得这些算法能够解决更多的新的问题

GA算法基于达尔文关于生物繁殖和遗传的自然选择法则如交叉重组和变异该方法处理一定量种群的个体染色体其中每一个都编码有问题的可能解GA染色体是由固定长度的串组成一组二进制位数字等等这使得遗传算子实现起来非常简单染色体初始化数量是随机的但之后就开始用交叉变异选择等遗传算子进行进化

最简单的交叉算子是单点交叉——在两个染色体中随机选择一点进行交叉交换剩余部分

另一个著名的交叉算子是两点交叉——选择染色体中两个随机点并交换两点间的部分事实上根据求解的问题不同除了这两种常用的交叉算子外GA算法中还有很多其他的交叉算子需要注意的一个问题是上述两种经典交叉算子完全不能直接应用于问题求解中应用时需要使用它们各自针对不同问题的特定变体

变异算子处理单一的染色体仅是简单地随机改变该染色体单点变异算子只改变染色体中的一个基因

如同交叉算子变异算子同样拥有大量针对特定问题和类型的变体

所以在初始人口创建之后GA算法的每次迭代都包括有以下步骤

  1.  交叉——选择随机个体并应用交叉算子
  2. 变异——选择随机个体并对其应用变异算子
  3. 计算每个个体的适合度
  4. 选择——为下一代选择个体

该算法可能在指定数量的迭代之后或是找到一个足够好的解之后停止计算染色体的适合度和具体问题相关——适合度表示该染色体“好”的程度染色体适合度越高表示其越“好”也就越有可能被选入下一代生存几率更高——译注

有几种选择算子其主要思想就是给予适合度高的优秀个体更多机会来选择个体进入下一代其中最为著名的是Elitism算法精英算法——选择一定数量的最优染色体进入下一代

1992John Koza提出了一项具有重大意义的新成果——遗传编程GP[2]GP单个人口成员染色体不再像GA中那样是编码了问题可能解的固定长度线性字符串而是可以执行并求解问题的程序这些程序在GP中被表示成不同大小和形状的解析树这样使得这些方法可以更加灵活地应用于求解多种问题染色体的表现差异可以说是GP算法和GA算法最主要甚至是唯一的差别之处GP算法中基本的达尔文适者生存思想仍然相同但在变异算子交叉算子以及适合度计算方面则和GA算法相比有一定的变化GP算法中变异算子并非通过改变某一个体的基因实现而是重新生成一个树节点以此作为染色体树上某一子树交叉算子也是如此——染色体互相交换子树可能在尺寸形状上均不相同而非交换同样长度的两部分然而仍旧需要对染色体进行相同的检查以确保它们不会生长得太长

GP算法中计算适合度并不是仅仅将染色体作为参数传递给某个计算适合度的算法就完了而是执行代表染色体的程序然后根据程序输出来计算适合度

2001Candida Ferreira介绍了另一种被称为基因表达式编程GEP的方法[3]该方法和遗传编程以及遗传算法均有相似之处一方面该方法仍旧采用输出求解结果的程序操作就如GP算法一般但在程序的表现上GEP有所不同染色体在该方法中不再表示成树而是和GA算法一样采用固定长度的线性表示这种染色体表现形式上的变化使得类似变异和交叉这样的遗传算子更加简单但是会使用一些很小的约束来确保算子的安全

     * + / a b c a

染色体表现形式 (a) GP算法  (b) GEP算法

上图展示了GP算法和GEP算法中染色体的不同表现形式两个染色体都编码了相同的程序——算数表达式(a+b)*(c/a)GP算法中是以解析树的形式表示的GEP算法则以从左上到右下的顺序线性表示的解析字符串可以很容易的把GEP字符串转换回一颗解析树然后仍按照从左上到右下的顺序加以填充并确定每个函数的参数数量

使用类库

类库基于灵活可重用的思想设计可以用于求解多种问题类库的代码不依赖于任何特定问题而是实现了进化计算以及遗传算法遗传编程和基因表达式编程等相关算法的通用概念进化计算中的实体如人口染色体选择方法和适合度函数是作为单独的类Class实现的以便能方便的进行组合来求解特定问题大多数情况下类库的使用者只需要为其待求解问题定义一个适合度计算函数然后定义染色体类型选择算法和一些其他参数如人口大小变异和交叉概率等等如果待求解问题需要一些特殊的染色体或遗传算子的变体如变异和交叉使用者可以通过实现IChromosome接口实现自己的染色体类或是通过继承已有的染色体类达到此目的选择算法和适合度计算函数与此类似——通过实现ISelectionMethodIFitnessFunction接口创建自定义选择算法和适合度计算函数使用者通过上述方法创建的自定义类扩展了类库功能和原有类一起用于求解特定问题

为了演示类库的使用方法下面给出4个使用不同进化计算算法的例子 

  • 函数优化遗传算法
  • 符号回归计算遗传编程和基因表达式编程
  • 时间序列预测遗传编程和基因表达式编程
  • 旅行商问题遗传算法

函数优化

函数优化是演示遗传算法的经典问题使用本文介绍的类库求解该类问题你只需要在优化范围内定义一个优化函数然后创建一个遗传种群人口——译注指定进化算法所需的参数

复制代码
 1 // 定义优化函数  
 2 public class UserFunction : OptimizationFunction1D  
 3 {  
 4     public UserFunction( ) :  
 5         base( new DoubleRange( 0, 255 ) ) { }  
 6   
 7     public override double OptimizationFunction( double x )  
 8     {  
 9         return Math.Cos( x / 23 ) * Math.Sin( x / 50 ) + 2;  
10     }  
11 }  
12 ...  
13 // 创建遗传种群  
14 Population population = new Population( 40,  
15     new BinaryChromosome( 32 ),  
16     new UserFunction( ),  
17     new EliteSelection( ) );  
18 // 运行一代  
19 population.RunEpoch( );  
复制代码

上面的例子创建了一个数量为40的染色体种群每个染色体是长度为32bit的二进制串使用了针对一维的精英选择方法和适合度计算函数在上述以及其他的例子中没有针对遗传算法或其他某种算法的明确的区别他们之间具有很多共同之处比如种群创建的方法在所有本文提到的遗传算法中都是相同的染色体是使用何种类型算法的决定因素它定义了问题解的表现形式以及遗传算子的实现方式 

符号回归计算近似解

符号回归问题的目的是找到针对输入数据的最佳近似函数通常人们利用遗传编程或基因表达式编程算法来解决这类问题使用这两种算法都可以找到一个函数该函数以X值和一些常数为参数输出一个接近真实值的Y

实际解题的代码和上一个例子的代码非常相似其中种群类和选择方法类都是一样的很显然由于染色体不同这两个例子唯一不同的部分就是适合度计算函数在解题时如果使用遗传编程算法则在代码中用GPTreeChromosome类创建染色体如果使用基因表达式编程算法则用GEPChromosome

复制代码
 1 // 需要近似求解的函数(输入数据)  
 2 double[,] data = new double[5, 2] {  
 3     {1, 1}, {2, 3}, {3, 6}, {4, 10}, {5, 15} };  
 4 // 创建种群  
 5 Population population = new Population( 100,  
 6     new GPTreeChromosome( new SimpleGeneFunction( 6 ) ),  
 7     new SymbolicRegressionFitness( data, new double[] { 1, 2, 3, 5, 7 } ),  
 8     new EliteSelection( ),  
 9     0.1 );  
10 // 运行一代  
11 population.RunEpoch( );  
复制代码

在上面的例子中需要近似求解的函数是用二位数组来表示(X,Y)值对的一个有趣的现象是上面例子中Population类构造函数的最后一个参数——该参数的值表示10%0.1——译注的新种群由随机的染色体组成而剩余的90%则为当前代的成员 

时间序列预测

时间序列预测问题创建了一个基于函数历史值来预测函数未来值的模型为了完成这个任务使用训练数据来创建训练该模型直到其开始基于训练集产生符合要求的结果该模型用于预测函数未来值

复制代码
 1 // 需要预测的时间序列  
 2 double[] data = new double[13] { 1, 2, 4, 7, 11, 16, 22, 29, 37, 46, 56, 67, 79 };  
 3 // 常数  
 4 double[] constants = new double[10] { 1, 2, 3, 5, 7, 11, 13, 17, 19, 23 };  
 5 // 滑动窗大小  
 6 int windowSize = 5;  
 7 // 创建种群  
 8 Population population = new Population( 100,  
 9 new GPTreeChromosome( new SimpleGeneFunction( windowSize + constants.Length ) ),  
10 new TimeSeriesPredictionFitness( data, windowSize, 1, constants ),  
11 new EliteSelection( ) );  
12 // 运行一代  
13 population.RunEpoch( );  
复制代码

可以看到时间序列预测问题的代码几乎和符号回归计算问题代码完全一致——只是修改了适合度计算函数和算法的一些参数这说明本文提出的类库使用简单具有高度可重用性 

旅行商问题

旅行商问题目标是在城市之间找到一条最短路径使得旅行商从一个城市出发不重复地访问每一个城市最后回到起点这类问题也被称为NP困难问题如果使用传统方法进行求解在城市数量较大的情况下解题可能花费极长的时间然而可以使用遗传算法在合理的时间范围内得到该问题的一个相当接近准确解的结果

1 // 创建种群  
2 Population population = new Population( populationSize,  
3     new PermutationChromosome( citiesCount ),  
4     new TSPFitnessFunction( map ),  
5     new EliteSelection( )  
6     );  

值得注意的是作为类库的组成部分PermutationChromosome类要求创建新的适合度计算函数TSPFitnessFunction并使用现有代码其余部分来求解新的问题然而该算法的性能还有进一步提高的余地默认的通用PermutationChromosome类可以得到问题解但是使用从该类派生的新类并重写交叉算子可以获得更好的性能自定义的TSPChromosome参考源代码实现了所谓的贪婪交叉算法允许在更短时间内找到更好的问题解 

多年来遗传算法总是能为旅行商问题解出最佳的结果旅行商问题非常有名且流行每年还会专门举办求解该问题的竞赛以期找到更好的算法众所周知20世纪90年代末出现了另一种性能更好的旅行商问题求解算法新算法基于蚁群思想同样来自人工智能领域的研究成果参考蚂蚁系统和蚁群系统算法

结论

以上的4个例子展示了本文类库的最初目标——灵活可扩展可重用并且使用简单尽管不能覆盖进化计算的各个方面也仍有许多工作需要完善但是该类库已经可以用于许多不同问题的求解而且非常容易就可以扩展该类库以求解新的问题通过研究设计该类库我不禁对进化算法有了更深入的了解而且帮助我更好的研究了遗传编程和基因表达式编程算法

参考文献

[1] Ajith Abraham, Nadia Nedjah and Luiza de Macedo Mourelle, Evolutionary Computation: from Genetic Algorithms to Genetic Programming // Genetic Systems Programming: Theory and Experiences, volume 13 of Studies in Computational Intelligence, pages 1-20. Springer, Germany, 2006.

[2] John R. Koza, Genetic Programming // Version 2 – Submitted August 18, 1997 for Encyclopedia of Computer Science and Technology.

[3] Ferreira, Gene Expression Programming: A new adaptive algorithm for solving problems // Complex Systems, Vol. 13, No. 2, pp. 87–129, 2001.

历史

[4.8.2012] - 完成翻译

[2.8.2012] - 开始翻译本文

[16.10.2006] - 发表本文

许可证

本文及其附属的任何源代码和文件均以GNU General Public LicenseGPLv3许可证发布

全文完

© Andrew Kirillov 2006, Conmajia 2012

posted on2012-08-04   Conmajia  阅读(3782)  评论(0编辑  收藏  举报

编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示