您的位置:首页 > 其它

遗传算法入门(连载之三)

2009-07-09 16:25 225 查看
[b]遗传算法入门(连载之三).扎自<游戏编程中的人工智能技术>第三章  (美)Mat Buckland 著吴祖增 沙 鹰 译.清华大学出版社出版[/b]3.4.2 Epoch (时代) [code]
遗传算法类中最烩灵人口的内容就是 Epoch()方法。这就是我们前面3.3节讲
过的遗传算法的那个循环。它是这个类的工作部门(workhorse)。这一方法与所
有工作或多或少都有连系。下面就让我们来更近距离地考察它 ...

void CgaBob::epoch()
{
UpdateFitnessScores();

在每一个 epoch 循环内所要做的第一件事情,就是测试染色体群中每一个成
员的适应性分数。 UpdateFitnessScores() 是用来对每个基因组的二进制染色体
编码进行译码的函数,而由它再把译码所得到的一系列结果,也就是由代表东、
南、西、北四个方向的整数,发送给 CBobsMap::TestRoute 。后者检查Bob在地
图中游走了多远,并根据Bob离开出口的最终距离,返回一个相应的适应性分数。
让我通过很少几行源码来告诉你怎样计算Bob的适应性分数:

int DiffX = abs(posX - m_iEndX);
int DiffY = abs(posY - m_iEndY);

这里,DiffX和DiffY 就是Bob所在格子相对于迷宫出口的水平和垂直偏离值。
试考察图 3.6 的例子。灰色小格代表Bob通过迷宫的路程,上面写着B的小格是他
最终所到达的地方。在这一位置上,Diffx = 3,而 DiffY = 0。
图 3.6 Bob尝试寻找迷宫出口这最后一行式子就是计算 Bob 的适应性分数。它把 DiffX与DiffY 两个数字加起来然后求倒数。DiffX与DiffY的和数中还加了一个1,这是为了确保除法不会出现一个分母为零的错误,如果 Bob到达出口,Diffx + DiffY = 0。UpdateFitnessScores 也保持对每一代里适应分最高的基因组、以及与所有基因组相关的适应性分数的跟踪。这些数值在执行轮盘赌选择时需要使用。到此,你已经知道了函数UpdateFitnessScores() 所做的全部工作,让我们回到 Epoch 函数的讨论 ...由于在每一个Epoch中都需要创建一个新的基因组群,因此,当它们在创建出来时(每次2个基因组),我们需要寻找一些地方来保存它们。//现在创建一个新的群体int NewBabies = 0;//为婴儿基因组创建存储器vector<SGenome> vecBabyGenomes;现在继续讨论遗传算法循环中所处理的各种事务。while (NewBabies < m_iPopSize){//用轮盘赌法选择 2 个上辈(parents)SGenome mum = RouletteWheelSelection();SGenome dad = RouletteWheelSelection();在每次迭代过程中,我们需要选择 2 个基因组来作为 2 个新生婴儿的染色体的上辈。我今后常喜欢把这2个上辈分别称为 dad (父亲)和 mum (母亲)因为他们将来就是要生孩子的)。你应该回忆得起来,一个基因组的适应性愈强,则由轮盘赌方法选择作为父母的几率也愈大。//杂交操作SGenome baby1, baby2;Crossover(mum.vecBits ,dad.vecBits, baby1.vecBits, baby2.vecBits);以上2行的工作是:创建 2 个空白基因组,这就是2个婴儿;它们与所选的上辈一起传递给杂交函数 Crossover() 。这一函数执行了杂交(需要依赖于所设杂交率m_dCrossoverRate来进行),并把新的染色体的2进制位串存放到2个新生婴儿 baby1和baby2之中。// 变异操作Mutate(baby1.vecBits);Mutate(baby2.vecBits);以上这 2 步是对婴儿实行突变!这听起来可怕,但这对他们是有利的。一个婴儿的位的突变概率依赖于所选的参数 m_dMutationRate(突变率)。// 把2个新生因个婴儿加入新群体vecBabyGenomes.push_back(baby1);vecBabyGenomes.push_back(baby2);NewBabies += 2;}这 2 个新生后代最终要加入到新的群体中,这样就完成了一次 Loop 的迭代过程。这一过程需要不断重复,直到创建出来的后代总量和初始群体的大小相同。// 把所有婴儿复制到初始群体m_vecGenomes = vecBabyGenomes;// 代的计数加1++m_iGeneration;}这里,原有的那个群体由新生一代所组成的群体来代替,并把代的计数器加1,以跟踪当前的代。就是这么一些了!呵呵,不难吧?这一 Epoch函数将无止境地重复,直到染色体收敛到了一个解,或到用户要求停止时为止。下面我将会向你显示上述各种操作(算子)的代码,但在此首先让我们来聊聊,应该如何确定使用的参数值。[/code]
我已把程序用到的所有参数存放在文件defines.h 中了。这些参数中大多数将是一目了然的,但有其中几个我想说明一下,即#define CROSSOVER_RATE 0.7#define MUTATION_RATE  0.001#define POP_SIZE 140#define CHROMO_LENGTH  70。。你可能想了解我是如何知道需要采用这些变量初值?这可是价值百万美元的问题,因至今尚未有快速有效的规则能确定这些值,有的只是一些原则性的指导。而且,选择这些值最终还得归结为每个人对遗传算法所得到的“感觉”,你只能通过自己的编程实践、用各种不同的参数值进行调试、看结果会发生什么,并从中选取适合的值。不同的问题需要不同的值,但是通常来说,如果你在使用二进制编码的染色体,则把杂交率设定为O.7,变异率设为0.001,将是很好的初始缺省值。而确定群体大小的一条有用规则是将基因组的数目取为染色体长度的2倍。。。因 70表示 Bob 的 35步的最大可能移动数目,所以这里选择70作为染色体的长度,它比 Bob 为穿越地图到达出口所需的步数还要多一些。当你学习了以后几章的方法后可以使遗传算法变得更为有效,到时你就能将这个长度减少下来。3.4.4 算子函数(The Operator Functions)。。我们现在从头到尾来考察一遍遗传算法的各种操作(或称算子)函数-选择、杂交、变异-的代码。尽管很简单,但与你一起通读一遍源码能给你重温一次这些函数的机会。这可使你在了解遗传算法的知识时对它们具有更确切的认识。3.4.4.1重温轮盘赌选择(Roulette Whell Selection Revisited )让我们从轮盘赌选择算法开始。请记住,这一个函数的功能是从群体中选择一个基因组,选中的几率正比于基因组的适应性分数。SGenome& CgaBob::RouletteWheelSelection(){double fSlice = RandFloat()*m_dTotalFitnessScore;。。我们从零到整个适应分范围内随机选取了一实数fSlice 。我喜欢把此数看作整个适应性分数饼图中的一块,如早先在图3.4中所示。 [但并不是其中一块,译注]double cfTotal = O;int SelectedGenome = 0;for (int i=O; i<m_iPopSize; ++i){cfTotal += m_vecGenomes[i].dFitness;if (cfTotal > fSlice){SelectedGenome = i;break;}}return m_vecGenomes[SelectedGenome];}。。现在,程序通过循环来考察各基因组,把它们相应的适应性分数一个一个累加起来,直到这一 部分累加和 大于 fSlice 值时,就返回该基因组。就是这样简单。 3.4.4.2 重温杂交操作(Crossover Revisited)。。这一函数要求2个染色体在同一随机位置上断裂开,然后将它们在断开点以后的部分进行互换,以形成 2 个新的染色体 ( 子代 ) 。void CgaBob::Crossover ( const vector<int>  	&mum,const vector<int>  	&dad,vector<int> 		&baby1,vector<int> 		&baby2){这一函数共传入 4 个参数,参数传递均采用引用( reference )方式,其中前2 个传入父辈 parent 的染色体(别忘记 , 染色体只是一个整数型的矢量std::vector ),后 2 个则是用来 copy 子代染色体的空矢量。if ( (RandFloat() > m_dCrossoverRate) || (mum == dad) ){baby1 = mum;baby2 = dad;return;}。。这里,首先是进行检测,看 mum 和 dad 两个上辈是否需要进行杂交。杂交发生的概率是由参数 m_dCrossoverRate 确定。如果不发生杂交,则2个上辈染色体不作任何改变地就直接复制为子代,函数立即返回。int cp = RandInt(0, m_iChromoLength - 1) ;。。沿染色体的长度随机选择一个点来裂开染色体。for (int i=0; i<cp; i++){baby1.push_back(mum[i]);baby2.push_back(dad[i]);}for (i=cp; i<mum.size(); i++){baby1.push_back(dad[i]);baby2.push_back(mum[i]);。。这两个小循环把 2 个 parent 染色体在杂交点( CP,crossover point )以后的所有位进行了互换,并把新的染色体赋给了 2 个子代 : baby1 和 baby2 。3.4.4.3 重温变异操作(Mutation Revisited)。。这一函数所做的工作,不过就是沿着一个染色体的长度,一bit一bit地进行考察,并按m_dMutationRate给定的几率,将其中某些bit实行翻转。void CgaBob::Mutate(vector<int> &vecBits){for (int curBit=0; curBit<vecBits.size(); curBit++){//是否要翻转此bit?If (RandFloat() < m_dMutationRate)(//是,就翻转此bitvecBits[curBit] = !vecBits[curBit];}}//移到下一个bit}。。就是这些了。你的第一遗传算法程序也就这样完成了!
(连载三完)

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: