我已把程序用到的所有参数存放在文件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 为穿越地图到达出口所需的步数还要多一些。当你学习了以后几章的方法后可以使遗传算法变得更为有效,到时你就能将这个长度减少下来。
历史的注释 遗传算法是 John Holland大脑的产物,早在上个世纪60年代,他已提出了这种想法。但不可思议的是,他没有感到需要在计算机上实际试验出结果,而宁愿利用笔和纸来作 修修补补的工作! 直到后来他的一名学生编写出程序并在一台个人计算机上运行后,才使人们终于看到在软件中利用他的思想能够得到什么。
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)
( //是,就翻转此bit
vecBits[curBit] = !vecBits[curBit];
} }//移到下一个bit
}
就是这些了。你的第一遗传算法程序也就这样完成了!
下面让我花一些时间来说明一下,当你在运行 Pathfinder 程序时,你能看到些什么?
3.4.5 运行找路径者程序 (Running the Pathfinder Program)
当你运行 Pathfinder 程序时,你将看到,程序不是每次都能找到一条通往出口的路径。 Bob 有时会被粘住在一个局部地区不确定地逗来逗去,如同一个喝醉了酒的人在试着寻找他的回家的路。这主要由于群体太快地收敛到一个特殊类型的染色体。这样,由 于群体中的成员变得如此相似, crossover 算子的有益效应这时实际上已经不能发挥作用,所有发生的事情都是靠总量很少的变异 操作在起作用 。但因变异率设置很低,当染色体类型的差异消失后,仅仅依靠变异本身已不足以去发现一个解。另外,由于轮盘赌选择的工作方式,使得任何一代的最合适的染色 体无法保证传到下一代。 这意味着,只要在适当时候,立即杀死这个成员,遗传算法就能在群体中找到一个几乎完全的解,但在这样做时,它将失去它所拥有的所有好的基因!在后面的章节 中,我 将会谈到这些问题,并介绍一些技术来帮助维护基因组的差异性且同时能保留那些较好的基因组。但在这里,我首先需要花费一些时间来考察不同的编码方法,并考 察它们怎样和你可能遇到的问题类型关联在一起。这就是我在下一个章中将要做的事情。