恩,正如我之前说的,我们这个程序不但要应付基因编码只有一个浮点数的“袋鼠跳”问题的情况,还希望以后在处理一串浮点数编码的时候也一样适用,所以从这里开始我们就把基因当成串来对待。
开创新的纪元――Epoch函数
现在万事具备了,只差把所有现成的“零件”装配起来而已。而Epoch函数就正好充当这个职能。下面是这个函数的实现:
1. //此函数产生新的一代,见证着整个进化的全过程. 2.
3. //以父代种群的基因组容器作为参数传进去,该函数将往该容器里放入新一代的基因
组(当然是经过了优胜劣汰的) 4.
5. void Epoch(vector
9. //用类的成员变量来储存父代的基因组(在此之前m_vecPop储存的是不带估值的
所有基因组) 10.
11. m_vecPop = vecNewPop; 12.
13. //初始化相关变量 14.
15. Reset(); 16.
17. //为相关变量赋值 18.
19. CalculateBestWorstAvTot(); 20.
21. //清空装载新种群的容器 22.
23. vecNewPop.clear(); 24.
25. //产生新一代的所有基因组 26.
27. while (vecNewPop.size() < m_iPopSize) 28.
29. { 30.
31. //转盘随机抽出两个基因 32.
33. CGenome mum = GetChromoRoulette();
34.
35. CGenome dad = GetChromoRoulette(); 36.
37. //创建两个子代基因组 38.
39. vector
41. //先把他们分别设置成父方和母方的基因 42.
43. baby1 = mum.vecWeights; 44.
45. baby2 = dad.vecWeights; 46.
47. //使子代基因发生基因突变 48.
49. Mutate(baby1); 50.
51. Mutate(baby2); 52.
53. //把两个子代基因组放到新的基因组容器里面 54.
55. vecNewPop.push_back( CGenome(baby1, 0) ); 56.
57. vecNewPop.push_back( CGenome(baby2, 0) ); 58.
59. }//子代产生完毕 60.
61. //如果你设置的人口总数非单数的话,就会出现报错 62.
63. if(vecNewPop.size() != m_iPopSize) 64.
65. { 66.
67. AfxMessageBox(\你的人口数目不是单数!!!\68.
69. return; 70.
71. } 72. 73. }
呵呵,现在我们可以为袋鼠传宗接代了。(细心的读者会发现,上面每次处理两个基因个体其实是没必要的,恩,那也是为了以后能够使用交叉函数而准备的,因为交叉函数需要两个相异的个体参与。)接下来,我们要把命令袋鼠跳正式开始的函数(大家注意,这个函数非CGenAlg类的成员函数,而是
CSearchMaxView类的成员函数,因为这个命令并非CGenAlg类自发的,而是由你“通知”CSearchMaxView类,然后再由CSearchMaxView类通知CGenAlg类的。)也一并实现:
上帝的一声令下――OnStartGenAlg函数
下面将列出OnStartGenAlg函数的主要代码(为了不要太占版面,只列出那些关键性的代码及其解释。),读者要注意里面的适应度评价是怎么实现的。
1. void CSearchMaxView::OnStartGenAlg()
2. 3. { 4.
5. //产生随机数 6.
7. srand( (unsigned)time( NULL ) ); 8.
9. //初始化遗传算法引擎 10.
11. GenAlg.init(g_popsize, g_dMutationRate, g_dCrossoverRate, g_numGen); 12.
13. //清空种群容器 14.
15. m_population.clear(); 16.
17. //种群容器装进经过随机初始化的种群 18.
19. m_population = GenAlg.m_vecPop; 20.
21. //定义两个容器,以装进函数的输入与及输出(我们这个函数是单输入单输出的,
但是以后往往不会那么简单,所以我们这里先做好这样的准备。) 22.
23. vector
25. input.push_back(0); 26.
27. for(int Generation = 0;Generation <= g_Generation;Generation++) 28.
29. { 30.
31. //里面是对每一条染色体进行操作 32.
33. for(int i=0;i 35. { 36. 37. input = m_population[i].vecWeights; 38. 39. //为每一个个体做适应性评价,如之前说的,评价分数就是函数值。其 40. 41. //Function函数的作用是输入自变量返回函数值,读者可以参考其代码。 42. 43. output = Curve.Function(input); 44. 45. m_population[i].dFitness = output[0]; 46. 47. } 48. 49. //由父代种群进化出子代种群(长江后浪退前浪。) 50. 51. GenAlg.Epoch(m_population); 52. 53. } 54. 55. } 恩,到这里“袋鼠跳”的主要代码就完成了。(其它一些代码,比如图形曲线的显示,和MFC的相关代码在这就不作介绍了,建议初学者不必理会那些代码,只要读懂算法引擎部分就可以了。)下面就只等着我们下达命令了! 让袋鼠在你的电脑里进化――程序的运行 我 想没有什么别的方法比自己亲手写一个程序然后通过修改相关参数不断调试程序,更能理解并且掌握一种算法了。不知道你还记不记得你初学程序的日子,我想你上 机动手写程序比坐在那里看一本厚厚的程序开发指南效率不知高上多少倍,兴趣也特命浓厚,激情也特别高涨。恩,你就是需要那样的感觉,学遗传算法也是一样 的。你需要把自己的代码运行起来,然后看看程序是否按照你所想象的去运行,如果没有,你就要思考原因,按照你的想法去改善代码,试着去弄清其中的内在联 系。这是一个思维激活的过程,你大脑中的神经网络正在剧烈抖动(呵呵,或许学到后面你就知道你大脑的神经网络是如何“抖动”的。),试图去接受这新鲜而有 趣的知识。遗传算法(包括以后要学到的人工神经网络)包含大量的可控参数,比如进化代数、人口数目、选择概率、交叉概 率、变异概率、变异的步长还有以后学 到的很多。这些参数之间的搭配关系,不能指望别人用“灌输”的方式让你被动接受,这需要你自己在不断的尝试,不断的调整中去形成一种“感觉”的。很多时候 一个参数的量变在整个算法中会表现出质的变化。而算法的效果又能从宏观上反映参数的设置。 现在就让我们来对这个程序做简单的说明。 参数的设置: 这个程序有很多的需要预先设置好的参数,为了方便修改,我把它们都定义为全局变量,定义和初始化都放在Parameter.h的头文件里面。下面对几个主要参数的说明: 1. //目标函数的左右区间,目前的设置是[-1,2] 2. 3. double g_LeftPoint = -1; 4. 5. double g_RightPoint = 2; 6. 7. ////遗传算法相关参数//// 8. 9. int g_numGen = 1; //每条染色体的编码个数,这里是1个 10. 11. int g_Generation = 1000; //进化的代数 12. 13. int g_popsize = 50; //种群的人口数目(就是说你要放多少只袋鼠到山上) 14. 15. double g_dMutationRate = 0.8; //基因变异的概率 16. 17. double g_dMaxPerturbation = 0.005; //基因变异的步长(袋鼠跳的最大距离) 当然,一些主要的参数在程序运行后可以通过参数设置选项进行设置。(其中缓冲时间是每进化一代之后,暂停的时间,单位为毫秒)如图2-6。