Chinaunix首页 | 论坛 | 博客
  • 博客访问: 683165
  • 博文数量: 90
  • 博客积分: 1631
  • 博客等级: 上尉
  • 技术积分: 1413
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-15 22:43
文章分类
文章存档

2017年(8)

2016年(9)

2015年(11)

2014年(10)

2013年(9)

2012年(9)

2010年(2)

2009年(10)

2008年(22)

我的朋友

分类: C/C++

2017-03-30 17:00:12

遗传算法引擎――GenAlg

         

[cpp] view plain copy
  1. "font-size:16px;">/遗传算法  
  2. class GenAlg      
  3.     {  
  4.       
  5.     public:  
  6.       
  7.     //这个容器将储存每一个个体的染色体  
  8.       
  9.     vector     vecPop;  
  10.       
  11.     //人口(种群)数量  
  12.       
  13.     int popSize;  
  14.       
  15.     //每一条染色体的基因的总数目  
  16.       
  17.     int chromoLength;  
  18.       
  19.     //所有个体对应的适应性评分的总和  
  20.       
  21.     double totalFitness;  
  22.       
  23.     //在所有个体当中最适应的个体的适应性评分  
  24.       
  25.     double bestFitness;  
  26.       
  27.     //所有个体的适应性评分的平均值  
  28.       
  29.     double averageFitness;  
  30.       
  31.     //在所有个体当中最不适应的个体的适应性评分  
  32.       
  33.     double worstFitness;  
  34.       
  35.     //最适应的个体在m_vecPop容器里面的索引号  
  36.       
  37.     Genome fittestGenome;  
  38.       
  39.     //基因突变的概率,一般介于0.05和0.3之间  
  40.       
  41.     double mutationRate;  
  42.       
  43.     //基因交叉的概率一般设为0.7  
  44.       
  45.     double crossoverRate;  
  46.       
  47.     //代数的记数器  
  48.       
  49.     int generation;  
  50.   
  51.     //最大变异步长  
  52.   
  53.     double maxPerturbation;  
  54.   
  55.     double leftPoint;  
  56.   
  57.     double rightPoint;  
  58.       
  59.     //构造函数  
  60.       
  61.     GenAlg();  
  62.       
  63.     //初始化变量  
  64.       
  65.     void Reset();  
  66.       
  67.     //初始化函数  
  68.   
  69.     void init(int popsize, double MutRate, double CrossRate, int GenLenght,double LeftPoint,double RightPoint);  
  70.       
  71.     //计算TotalFitness, BestFitness, WorstFitness, AverageFitness等变量  
  72.       
  73.     void CalculateBestWorstAvTot();  
  74.       
  75.     //轮盘赌选择函数  
  76.       
  77.     Genome GetChromoRoulette();  
  78.   
  79.   
  80.     //基因变异函数  
  81.       
  82.     void Mutate(vector<double> &chromo);  
  83.       
  84.     //这函数产生新一代基因  
  85.       
  86.     void Epoch(vector &vecNewPop);  
  87.       
  88.     Genome GetBestFitness();  
  89.   
  90.     double GetAverageFitness();  
  91.     };  


        其中Reset()函数,init()函数和CalculateBestWorstAvTot()函数都比较简单,读者查看示例程序的代码就能明白了。而下面分别介绍init函数和Epoch函数。

类的初始化函数――init函数

        init函数主要充当CGenAlg类的初始化工作,把一些成员变量都变成可供重新开始遗传算法的状态。(为什么我不在构造函数里面做这些工作呢?因为我的程序里面CGenAlg类是View类的成员变量,只会构造一次,所以需要另外的初始化函数。)下面是init函数的代码:

        

[cpp] view plain copy
  1. void GenAlg::init(int popsize, double MutRate, double CrossRate, int GenLenght,double LeftPoint,double RightPoint)  
  2.   
  3. {  
  4.   
  5.     popSize = popsize;  
  6.   
  7.     mutationRate = MutRate;  
  8.   
  9.     crossoverRate = CrossRate;  
  10.   
  11.     chromoLength = GenLenght;  
  12.   
  13.     totalFitness = 0;  
  14.   
  15.     generation = 0;  
  16.   
  17.     //fittestGenome = 0;  
  18.   
  19.     bestFitness = 0.0;  
  20.   
  21.     worstFitness = 99999999;  
  22.   
  23.     averageFitness = 0;  
  24.   
  25.     maxPerturbation=0.004;  
  26.   
  27.     leftPoint=LeftPoint;  
  28.   
  29.     rightPoint=RightPoint;  
  30.   
  31.     //清空种群容器,以初始化  
  32.   
  33.     vecPop.clear();  
  34.   
  35.     for (int i=0; i
  36.   
  37.     {       
  38.   
  39.         //类的构造函数已经把适应性评分初始化为0  
  40.   
  41.         vecPop.push_back(Genome());  
  42.   
  43.         //把所有的基因编码初始化为函数区间内的随机数。  
  44.   
  45.         for (int j=0; j
  46.   
  47.         {  
  48.               
  49.             vecPop[i].vecGenome.push_back(random() *   
  50.   
  51.                 (rightPoint - leftPoint) + leftPoint);  
  52.   
  53.         }  
  54.   
  55.     }  
  56.   
  57. }  


         恩,正如我之前说的,我们这个程序不但要应付基因编码只有一个浮点数的“袋鼠跳”问题的情况,还希望以后在处理一串浮点数编码的时候也一样适用,所以从这里开始我们就把基因当成串来对待。

开创新的纪元――Epoch函数

        现在万事具备了,只差把所有现成的“零件”装配起来而已。而Epoch函数就正好充当这个职能。下面是这个函数的实现:

[cpp] view plain copy
  1. void GenEngine:: OnStartGenAlg()  
  2.       
  3. {  
  4.       
  5.   
  6.     //产生随机数  
  7.   
  8.     srand( (unsigned)time( NULL ) );  
  9.   
  10.     //初始化遗传算法引擎  
  11.   
  12.     genAlg.init(g_popsize, g_dMutationRate, g_dCrossoverRate, g_numGen,g_LeftPoint,g_RightPoint);  
  13.   
  14.     //清空种群容器  
  15.   
  16.     m_population.clear();  
  17.   
  18.     //种群容器装进经过随机初始化的种群  
  19.   
  20.     m_population = genAlg.vecPop;  
  21.   
  22.     //定义两个容器,以装进函数的输入与及输出(我们这个函数是单输入单输出的,但是以后往往不会那么简单,所以我们这里先做好这样的准备。)  
  23.   
  24.     vector <double> input;  
  25.     double output;  
  26.   
  27.     input.push_back(0);  
  28.   
  29.     for(int Generation = 0;Generation <= g_Generation;Generation++)  
  30.   
  31.     {  
  32.   
  33.         //里面是对每一条染色体进行操作  
  34.   
  35.         for(int i=0;i
  36.   
  37.         {  
  38.   
  39.             input = m_population[i].vecGenome;  
  40.   
  41.             //为每一个个体做适应性评价,如之前说的,评价分数就是函数值。其  
  42.   
  43.             //Function函数的作用是输入自变量返回函数值,读者可以参考其代码。  
  44.   
  45.             output = (double)curve.function(input);  
  46.           
  47.             m_population[i].fitness = output;  
  48.   
  49.         }  
  50.   
  51.         //由父代种群进化出子代种群(长江后浪退前浪。)  
  52.   
  53.         genAlg.Epoch(m_population);  
  54.   
  55.           
  56.         //if(genAlg.GetBestFitness().fitness>=bestFitness)  
  57.         bestSearch=genAlg.GetBestFitness().vecGenome[0];  
  58.         bestFitness=genAlg.GetBestFitness().fitness;  
  59.         averageFitness=genAlg.GetAverageFitness();  
  60.         //cout<  
  61.         report(Generation+1);  
  62.     }  
  63.   
  64.     //return bestSearch;  
  65.   
  66. }  

         恩,到这里“袋鼠跳”的主要代码就完成了。(其它一些代码,比如图形曲线的显示,和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。

          


运行程序:

      程序运行后请选择菜单项:控制->让袋鼠们开始跳吧,开始遗传算法的过程。其中蓝色的线条是函数曲线(恩,那就是喜玛拉雅山脉。其中最高的波峰,就是珠穆朗玛峰。)绿色的点是一只只袋鼠。上方的黑色曲线图表是对每一代最优的个体的适应性评分的统计图表。下方的黑色曲线图表是对每一代所有个体的平均适应性评分的统计图表。(如果你认为它们阻碍了你的视线,你可以在参数设置里面取消掉。)如图2-7所示。另外还可以用键盘的上下左右键来控制视窗的移动,加减键控制函数曲线的放缩。

         

           刚开始的时候,袋鼠分布得比较分散它们遍布了各个山岭,有的在高峰上,有的在深谷里。(如图2-8)

           

             经过了几代的进化后,一些海拔高度比较低的都被我们射杀了,而海拔较高的袋鼠却不断的生儿育女。(如图2-9)

           


          最后整个袋鼠种群就只出现在最高峰上面(最优解上)。(如图2-10)

            

      

            当然,袋鼠不是每一次都能跳到珠穆朗玛峰的,如图2-11所 示。(就是说不是每次都能收敛到最优解)也许它们跳到了某一个山峰,就自大的认为它们已经“会当凌绝顶”了。(当然,事实上是因为不管它们向前还是向后跳都只能得到更小的适应度,所以不等它们跳过山谷,再跳到旁边更高的山峰,就被我们射杀了。)所以,我们为了使到袋鼠每次都尽可能的攀到珠穆朗玛峰,而不是 留恋在某一个低一些的山峰,我们有两个改进的办法,其一是初始人口数目更多一些,以使最好有一些袋鼠一开始就降落到最高峰的附近,但是这种方法对于搜索空间非常大的问题往往是无能为力的。我们常常采用的方法是使袋鼠有一定的概率跳一个很大的步长,以使袋鼠有可能跳过一个山谷到更高的山峰去。这些改进的方法 留给读者自己去实现。

       

           另外,如果把变异的机率调得比较高,那么就会出现袋鼠跳得比较活跃的局面,但是很可能丢失了最优解;而如果变异的机率比较低的话,袋鼠跳得不太活跃,找到最优解的速度就会慢一些,这也留给读者自己去体验。

          作为一个寻找大值的程序,这个的效率还 很低。我希望留给初学者更多改进的空间,大家不必受限于现有的方法,大可以发挥丰富的想象力,自己想办法去提高程序的效率,然后自己去实现它,让事实去验证你的想法是否真的能提高效率,抑或刚好相反。恩,在这个过程当中,大家不知不觉地走进了遗传算法的圣殿了,胜于一切繁复公式的摆设和教条式的讲解。

        博主后记:由于原作者未知加上本人学习繁忙,所以MFC版本源码未整理,以下为自己整理实现的控制台演示版本:

        

         最后结果是一样的,附上下载:

        
阅读(524) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~