下面会介绍编写遗传算法的整体流程(附代码与代码解读),同时会提出一些小问题,对于这些问题我自己给出的解答会在文章最后,也只是自己的一点思考。
欢迎大家一起讨论( •̀ ω •́ )✧
1 个体编解码
提出问题,假设有一群袋鼠在这样一座山上生活着,这座山的函数表达式为:
1.1 种群和个体
首先,我们知道进化是以种群为单位的。在遗传算法中,个体通常为某个问题的一个解,并且该解在计算机中被编码为一个向量表示。在例子中我们需要求出最大值,所以该问题的解为一组可能的(x,y)的取值,这就是求最大值问题的一个可能解,也就是遗传算法里的个体,把这样的一组一组可能解的集合叫做种群,比如在这个问题中设置100个这样的x,y的可能取值对,这100个个体就构成了种群。
因此,我们可以先声明一个结构体:
typedef struct Chrom // 结构体类型,为单个染色体的结构;
{
short int bit[6];//一共6bit来对染色体进行编码,其中1位为符号位。取值范围-64~+64
int fit;//当前个体的适应值
double rfit;//当前个体的相对的fit值,即在整体的适应度所占的百分比
double cfit;//当前个体的积累概率,用于轮盘赌选择函数
}chrom;
1.2 对个体进行编码
在上面个体概念中提到个体(也就是一组可能解)在计算机程序中被编码为一个向量表示,而在我们这个问题中,个体是x,y的取值,是两个实数,所以这个问题就可以转化为如何将实数编码为一个向量表示,而编码的目的是为了后续操作(交叉和变异)的方便。
生物的DNA有四种碱基对,分别是ACGT,DNA的编码可以看作是DNA上碱基对的不同排列,不同的排列使得基因的表现出来的性状也不同(如单眼皮双眼皮)。在计算机中,我们可以模仿这种编码,但是碱基对的种类只有两种,分别是0,1。只要我们能够将不同的实数表示成不同的0,1二进制串表示就完成了编码,也就是说我们其实不需要去了解一个实数对应的二进制具体是多少,我们只需要保证有一个映射。
能够将十进制的数编码为二进制即可,至于这个映射是什么,其实可以不必关心。将个体(可能解)编码后的二进制串叫做染色体,染色体(或者有人叫DNA)就是个体(可能解)的二进制编码表示。为什么可以不必关心映射 f(x) 呢?因为其实我们在程序中操纵的都是二进制串,而二进制串生成时可以随机生成。
编码是应用遗传算法时要解决的首要问题,也是设计遗传算法时的一个关键步骤。编码方法影响到交叉算子、变异算子等遗传算子的运算方法,很大程度上决定了遗传进化的效率。
下面介绍两种常用的编码方式:二进制编码法和浮点编码法(当然还有很多千奇百怪的编码方式我没去做过研究就不写了)
1.2.1 二进制编码法
就像人类的基因有AGCT 4中碱基序列一样。不过在这里我们只用了0和1两种碱基,然后将它们串成一条链形成染色体。一个位能表示出2种状态的信息量,因此足够长的二进制染色体便能表示所有的特征,这就是二进制编码:
二进制编码组成的二值符号集,有以下一些优点:
1)编码、解码操作简单易行(其实也不简单,反而我觉的挺麻烦的)
2)交叉、变异等遗传操作便于实现(这一点确实,因为每个基因位只表示两种状态0和1)
3)符合最小字符集编码原则
4)利用模式定理对算法进行理论分析
缺点:对于一些连续函数的优化问题,由于其随机性使得其局部搜索能力交叉,如对于一些高精度的问题,当解迫近于最优解后,由于其变异后表现型变化很大(比如00000变异成10000等),不连续的变异会远离最优解,使得无法达到稳定(一句话概括就是不适合高精度,搜索范围较大的问题,这一点后面也会有说明)。
1.2.2 浮点编码法
二进制编码虽然简单直观,但明显地存在着连续函数离散化时的映射误差。个体长度较短时,可能达不到精度要求,而个体编码长度较长时,虽然能提高精度,但增加了解码的难度,使遗传算法的搜索空间急剧扩大。
A:这一点我做一些说明,我们都知道一个三位的二进制,可以表示0~7(共计8个整数),但是这八个整数之间并不是连续的,而是离散的,而一般我们要求解的函数是连续的,那就必然会存在误差。其次,如果我们想要尽可能使二进制编码法变得连续起来,那么就必然要增加小数位,以最低要求三位小数为例,我们都知道至少需要2^10才能表示完0~1000之间的整数,也就是说要想将二进制编码法的精度精确到小数点后三位,至少需要10个二进制位用于表示小数,这也正是上文所说的搜索空间变大了。
所谓浮点法,是指个体的每个基因值用某一范围内的一个浮点数来表示。在浮点数编码方法中 ,必须保证基因值在给定的区间限制范围内,遗传算法中所使用的交叉、变异等遗传算子也必须保证其运算结果所产生的新个体的基因值也在这个区间的限制范围内。
浮点数编码方法有下面几个优点:
1)适用于在遗传算法中表示范围较大的数
2)适用于精度要求较高的遗传算法
3)便于较大空间的遗传搜索(真的是这样吗?我们暂且按下不表)
4)改善了遗传算法的计算复杂度,提高了运算交率
5)便于遗传算法与经典优化方法的混合使用(确实如此,毕竟二进制编码法在别的算法中用的比较少)
6)便于设计针对问题的专门知识的知识型遗传算子
7)便于处理复杂的决策变量约束条件
下面给出编解码函数,编码函数在初始化函数中,先行给出,后续也会用到:
二进制编码:
for (i = 0;i < 6; i++) // 从染色体的第1个基因位到第6个基因位
{
random = rand(); // 产生一个随机值
random = (random % 2); // 随机产生0或者1
popcurrent[j].bit[i] = random; // 随机产生染色体上每一个基因位的值,或;
}
浮点数编码:
//生成a~b之间均匀分布的数字
double r8_uniform_ab(double a, double b, int& seed)
{
{
int i4_huge = 2147483647;
int k;
double value;
if (seed == 0)
{
std::cerr << "\n";
std::cerr << "R8_UNIFORM_AB - Fatal error!\n";
std::cerr << " Input value of SEED = 0.\n";
exit(1);
}
k = seed / 127773;
seed = 16807 * (seed - k * 127773) - k * 2836;
if (seed < 0)
{
seed = seed + i4_huge;
}
value = (double)(seed) * 4.656612875E-10;
value = a + (b - a) * value;
return value;
}
}
二进制解码:
int x(chrom popcurrent) // 函数:将二进制换算为十进制;
{//此处的染色体长度为,其中个表示符号位
int z;
z = (popcurrent.bit[0] * 1) + (popcurrent.bit[1] * 2) + (popcurrent.bit[2] * 4) + (popcurrent.bit[3] * 8) + (popcurrent.bit[4] * 16);
if (popcurrent.bit[5] == 1) // 考虑到符号;
{
z = z * (-1);
}
return(z);
}
浮点数一般不需要解码,只需要设计相应的目标函数与适应度函数即可。
目前,我们已经大致完成了个体的初始化,二进制编码法和浮点数编码法的介绍,提出两个小问题可以思考一下。
Question1:
如果有一个连续函数求取最大值的问题,你会选择二进制编码法还是浮点数编码法呢?
Question2:
种群大小会对最终的结果产生很大的影响吗?
2 目标函数与适应度函数
目标函数其实就是我们的袋鼠现在生活的那座山的表达式,这很好理解。
适应度函数主要是通过个体特征从而判断个体的适应度。在本例的袋鼠跳中,我们只关心袋鼠的海拔高度,以此来判断是否射杀该袋鼠。这样依赖,该函数就非常简单了。只要输入袋鼠的位置坐标,再通过相应的查找运算,返回袋鼠当前位置的海拔高度就行。
适应度函数也称为评价函数,是根据目标函数确定的用于区分群体中个体好坏的标准。适应度函数总是负的,而目标函数可能有正有负,故需要在目标函数与适应度函数之间进行变换。
评价个体适应度的一般过程为:
1)对个体编码串进行解码处理后,可得到个体的表现型
2)由个体的表现型可计算出对应个体的目标函数值
根据最优化问题的类型,由目标函数值按一定的转换规则求出个体的适应度。适应度函数将直接影响最后结果的接近程度以及求解所耗费的时间,所以设置一个好的适度函数很重要。
下面我们给出目标函数与适应度函数的代码(求最大值问题直接用目标函数就行,适应度函数在这里省略了):
//目标函数计算(需要能能够从外部直接传输函数,加强鲁棒性)
int y(int x)// 函数:求个体的适应度;
{
int y;
y = -(x * x) + 5; // 目标函数:y= - ( x^ 2 ) +5;
return(y);
}
3 选择、交叉和变异函数
从原理上来说这三个函数是整个遗传算法的核心思想,相关的方法和资料数不胜数,所以我就直接附上实现代码(主要是懒……字太多了而且没啥营养,想研究的话推荐可以去看看相关文献):
3.1 选择函数
选择函数(冒泡法):
//选择操作
void* pickchroms(chrom popnext[4]) // 函数:选择个体;
{
int i, j;
chrom temp; // 中间变量
//因此此处设计的是个个体,所以参数是
for (i = 0;i < 3; i++) // 根据个体适应度来排序;(冒泡法)
{
for (j = 0;j < 3 - i; j++)
{
if (popnext[j + 1].fit > popnext[j].fit)
{
temp = popnext[j + 1];
popnext[j + 1] = popnext[j];
popnext[j] = temp;
}
}
}
for (i = 0;i < 4; i++)
{
printf("\nSorting:popnext[%d] fitness=%d", i, popnext[i].fit);
printf("\n");
}
_flushall();/* 清除所有缓冲区 */
return(0);
}
选择函数(轮盘赌方法)
//基于轮盘赌选择方法,进行基因型的选择
void* pickchroms_new(chrom popnext[4])//计算概率
{
int men;
int i;int j;
double p;
double sum = 0.0;
//find the total fitness of the population,计算出种群的整体适应度
for (men = 0; men < 4; men++)
{
sum = sum + popnext[men].fit;
}
//calculate the relative fitness of each member,计算出种群中每个个体在整体适应度中的百分比
for (men = 0; men < 4; men++)
{
popnext[men].rfit = popnext[men].fit / sum;
}
//calculate the cumulative fitness,即计算积累概率
popcurrent[0].cfit = popcurrent[0].rfit;
for (men = 1; men < 4; men++)
{
popnext[men].cfit = popnext[men - 1].cfit + popnext[men].rfit;
}
for (i = 0; i < 4; i++)
{//产生0~1之间的随机数
//p = r8_uniform_ab ( 0, 1, seed );//通过函数生成0~1之间均匀分布的数字
p = rand() % 10;//生成0~10之间均匀分布的数字
p = p / 10;//变成0~1之间均匀分布的数字
if (p < popnext[0].cfit)
{
popcurrent[i] = popnext[0];
}
else
{
for (j = 0; j < 4; j++)
{
if (popnext[j].cfit <= p && p < popnext[j + 1].cfit)
{
popcurrent[i] = popcurrent[j + 1];
}
}
}
}
//Overwrite the old population with the new one.
//用新种群覆盖老种群
for (i = 0; i < 4; i++)
{
popnext[i] = popcurrent[i];
}
return(0);
}
3.2 交叉函数
两点交叉:
//染色体交叉操作
void* crossover(chrom popnext[4]) // 函数:交叉操作;
{
int random;
int i;
//srand(time(0));
random = rand(); // 随机产生交叉点;
random = ((random % 5) + 1); // 交叉点控制在0到5之间;
for (i = 0;i < random;i++)
{
popnext[2].bit[i] = popnext[0].bit[i]; // child 1 cross over
popnext[3].bit[i] = popnext[1].bit[i]; // child 2 cross over
}
for (i = random; i < 6;i++) // crossing the bits beyond the cross point index
{
popnext[2].bit[i] = popnext[1].bit[i]; // child 1 cross over
popnext[3].bit[i] = popnext[0].bit[i]; // chlid 2 cross over
}
for (i = 0;i < 4; i++)
{
popnext[i].fit = y(x(popnext[i])); // 为新个体计算适应度值;
}
for (i = 0;i < 4; i++)
{
printf("\nCrossOver popnext[%d]=%d%d%d%d%d%d value=%d fitness = %d", i, popnext[i].bit[5], popnext[i].bit[4], popnext[i].bit[3], popnext[i].bit[2], popnext[i].bit[1], popnext[i].bit[0], x(popnext[i]), popnext[i].fit);
// 输出新个体;
}
return(0);
}
3.3 变异函数
随机变异:
//染色体变异操作
void* mutation(chrom popnext[4]) // 函数:变异操作;
{
int random;
int row, col, value;
//srand(time(0));
random = rand() % 50; // 随机产生到之间的数;
//变异操作也要遵从一定的概率来进行,一般设置为0到0.5之间
//
if (random == 25) // random==25的概率只有2%,即变异率为,所以是以小概率进行变异!!
{
col = rand() % 6; // 随机产生要变异的基因位号;
row = rand() % 4; // 随机产生要变异的染色体号;
if (popnext[row].bit[col] == 0) // 1变为;
{
popnext[row].bit[col] = 1;
}
else if (popnext[row].bit[col] == 1) // 0变为;
{
popnext[row].bit[col] = 0;
}
popnext[row].fit = y(x(popnext[row])); // 计算变异后的适应度值;
value = x(popnext[row]);
printf("\nMutation occured in popnext[%d] bit[%d]:=%d%d%d%d%d%d value=%d fitness=%d", row, col, popnext[row].bit[5], popnext[row].bit[4], popnext[row].bit[3], popnext[row].bit[2], popnext[row].bit[1], popnext[row].bit[0], value, popnext[row].fit);
// 输出变异后的新个体;
}
return(0);
}
4 完整代码
这部分代码和上面的分布代码存在差异,因为上面的代码是我对这部分代码的改写
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#include <iostream>
typedef struct Chrom // 结构体类型,为单个染色体的结构;
{
short int bit[6];//一共6bit来对染色体进行编码,其中1位为符号位。取值范围-64~+64
int fit ;//适应值
double rfit;//相对的fit值,即所占的百分比
double cfit;//积累概率
}chrom;
//定义将会用到的几个函数;
void *evpop (chrom popcurrent[4]);//进行种群的初始化
int x (chrom popcurrent);
int y (int x);
void *pickchroms (chrom popcurrent[4]);//选择操作
void *pickchroms_new (chrom popcurrent[4]); // 基于概率分布
void *crossover (chrom popnext[4]);//交叉操作
void *mutation (chrom popnext[4]);//突变
double r8_uniform_ab ( double a, double b, int &seed );//生成a~b之间均匀分布的数字
chrom popcurrent [4]; // 初始种群规模为;
chrom popnext [4]; // 更新后种群规模仍为;
void main () // 主函数;
{
int num ; // 迭代次数;
int i ,j, l,Max ,k;
Max=0; // 函数最大值
printf("\nWelcome to the Genetic Algorithm!\n"); //
printf("The Algorithm is based on the function y = -x^2 + 5 to find the maximum value of the function.\n");
enter:printf ("\nPlease enter the no. of iterations\n请输入您要设定的迭代数 : ");
scanf_s("%d" ,&num); // 输入迭代次数,传送给参数 num;
if(num <1)
goto enter ; // 判断输入的迭代次数是否为负或零,是的话重新输入;
//不同的随机数可能结果不同??那是当所设置的迭代次数过少时,染色体的基因型过早地陷入局部最优
srand(time(0));
evpop(popcurrent ); // 随机产生初始种群;
//是否需要指定x的取值范围呢?6bit来表示数字,第一位为符号位,5bit表示数字大小。所以,取值范围为-32~+31
Max = popcurrent[0].fit;//对Max值进行初始化
for(i =0;i< num;i ++) // 开始迭代;
{
printf("\ni = %d\n" ,i); // 输出当前迭代次数;
for(j =0;j<4; j++)
{
popnext[j ]=popcurrent[ j]; // 更新种群;
}
pickchroms(popnext ); // 挑选优秀个体;
crossover(popnext ); // 交叉得到新个体;
mutation(popnext ); // 变异得到新个体;
for(j =0;j<4; j++)
{
popcurrent[j ]=popnext[ j]; // 种群更替;
}
} // 等待迭代终止;
//对于真正随机数是需要注意取较大的迭代次数
for(l =0;l<3; l++)
{
if(popcurrent [l]. fit > Max )
{
Max=popcurrent [l]. fit;
k=x(popcurrent [l]);//此时的value即为所求的x值
}
}
printf("\n 当x等于 %d时,函数得到最大值为: %d ",k ,Max);
printf("\nPress any key to end ! " );
_flushall(); // 清除所有缓冲区;
_getche(); // 从控制台取字符,不以回车为结束;
}
void *evpop (chrom popcurrent[4]) // 函数:随机生成初始种群;
{
int i ,j, value1;
int random ;
double sum=0;
for(j =0;j<4; j++) // 从种群中的第1个染色体到第4个染色体
{
for(i =0;i<6; i++) // 从染色体的第1个基因位到第6个基因位
{
random=rand (); // 产生一个随机值
random=(random %2); // 随机产生0或者1
popcurrent[j ].bit[ i]=random ; // 随机产生染色体上每一个基因位的值,或;
}
value1=x (popcurrent[ j]); // 将二进制换算为十进制,得到一个整数值;
popcurrent[j ].fit= y(value1); // 计算染色体的适应度值
sum = sum + popcurrent[j ].fit;
printf("\n popcurrent[%d]=%d%d%d%d%d%d value=%d fitness = %d",j, popcurrent[j ].bit[5], popcurrent[j ].bit[4], popcurrent[j ].bit[3], popcurrent[j ].bit[2], popcurrent[j ].bit[1], popcurrent[j ].bit[0], value1,popcurrent [j]. fit);
// 输出整条染色体的编码情况,
}
//计算适应值得百分比,该参数是在用轮盘赌选择法时需要用到的
for (j = 0; j < 4; j++)
{
popcurrent[j].rfit = popcurrent[j].fit/sum;
popcurrent[j].cfit = 0;//将其初始化为0
}
return(0);
}
int x (chrom popcurrent) // 函数:将二进制换算为十进制;
{//此处的染色体长度为,其中个表示符号位
int z ;
z=(popcurrent .bit[0]*1)+( popcurrent.bit [1]*2)+(popcurrent. bit[2]*4)+(popcurrent .bit[3]*8)+( popcurrent.bit [4]*16);
if(popcurrent .bit[5]==1) // 考虑到符号;
{
z=z *(-1);
}
return(z );
}
//需要能能够从外部直接传输函数,加强鲁棒性
int y (int x)// 函数:求个体的适应度;
{
int y ;
y=-(x *x)+5; // 目标函数:y= - ( x^ 2 ) +5;
return(y );
}
//基于轮盘赌选择方法,进行基因型的选择
void *pickchroms_new (chrom popnext[4])//计算概率
{
int men;
int i;int j;
double p;
double sum=0.0;
//find the total fitness of the population
for (men = 0; men < 4; men++ )
{
sum = sum + popnext[men].fit;
}
//calculate the relative fitness of each member
for (men = 0; men < 4; men++ )
{
popnext[men].rfit = popnext[men].fit / sum;
}
//calculate the cumulative fitness,即计算积累概率
popcurrent[0].cfit = popcurrent[0].rfit;
for ( men = 1; men < 4; men++)
{
popnext[men].cfit = popnext[men-1].cfit + popnext[men].rfit;
}
for ( i = 0; i < 4; i++ )
{//产生0~1之间的随机数
//p = r8_uniform_ab ( 0, 1, seed );//通过函数生成0~1之间均匀分布的数字
p =rand()%10;//
p = p/10;
if ( p < popnext[0].cfit )
{
popcurrent[i] = popnext[0];
}
else
{
for ( j = 0; j < 4; j++ )
{
if ( popnext[j].cfit <= p && p < popnext[j+1].cfit )
{
popcurrent[i] = popcurrent[j+1];
}
}
}
}
// Overwrite the old population with the new one.
//
for ( i = 0; i < 4; i++ )
{
popnext[i] = popcurrent[i];
}
return(0);
}
void *pickchroms (chrom popnext[4]) // 函数:选择个体;
{
int i ,j;
chrom temp ; // 中间变量
//因此此处设计的是个个体,所以参数是
for(i =0;i<3; i++) // 根据个体适应度来排序;(冒泡法)
{
for(j =0;j<3-i; j++)
{
if(popnext [j+1]. fit>popnext [j]. fit)
{
temp=popnext [j+1];
popnext[j +1]=popnext[ j];
popnext[j ]=temp;
}
}
}
for(i =0;i<4; i++)
{
printf("\nSorting:popnext[%d] fitness=%d" ,i, popnext[i ].fit);
printf("\n" );
}
_flushall();/* 清除所有缓冲区 */
return(0);
}
double r8_uniform_ab( double a, double b, int &seed )
{
{
int i4_huge = 2147483647;
int k;
double value;
if ( seed == 0 )
{
std::cerr << "\n";
std::cerr << "R8_UNIFORM_AB - Fatal error!\n";
std::cerr << " Input value of SEED = 0.\n";
exit ( 1 );
}
k = seed / 127773;
seed = 16807 * ( seed - k * 127773 ) - k * 2836;
if ( seed < 0 )
{
seed = seed + i4_huge;
}
value = ( double ) ( seed ) * 4.656612875E-10;
value = a + ( b - a ) * value;
return value;
}
}
void *crossover (chrom popnext[4]) // 函数:交叉操作;
{
int random ;
int i ;
//srand(time(0));
random=rand (); // 随机产生交叉点;
random=((random %5)+1); // 交叉点控制在0到5之间;
for(i =0;i< random;i ++)
{
popnext[2].bit [i]= popnext[0].bit [i]; // child 1 cross over
popnext[3].bit [i]= popnext[1].bit [i]; // child 2 cross over
}
for(i =random; i<6;i ++) // crossing the bits beyond the cross point index
{
popnext[2].bit [i]= popnext[1].bit [i]; // child 1 cross over
popnext[3].bit [i]= popnext[0].bit [i]; // chlid 2 cross over
}
for(i =0;i<4; i++)
{
popnext[i ].fit= y(x (popnext[ i])); // 为新个体计算适应度值;
}
for(i =0;i<4; i++)
{
printf("\nCrossOver popnext[%d]=%d%d%d%d%d%d value=%d fitness = %d",i, popnext[i ].bit[5], popnext[i ].bit[4], popnext[i ].bit[3], popnext[i ].bit[2], popnext[i ].bit[1], popnext[i ].bit[0], x(popnext [i]), popnext[i ].fit);
// 输出新个体;
}
return(0);
}
void *mutation (chrom popnext[4]) // 函数:变异操作;
{
int random ;
int row ,col, value;
//srand(time(0));
random=rand ()%50; // 随机产生到之间的数;
//变异操作也要遵从一定的概率来进行,一般设置为0到0.5之间
//
if(random ==25) // random==25的概率只有2%,即变异率为,所以是以小概率进行变异!!
{
col=rand ()%6; // 随机产生要变异的基因位号;
row=rand ()%4; // 随机产生要变异的染色体号;
if(popnext [row]. bit[col ]==0) // 1变为;
{
popnext[row ].bit[ col]=1 ;
}
else if (popnext[ row].bit [col]==1) // 0变为;
{
popnext[row ].bit[ col]=0;
}
popnext[row ].fit= y(x (popnext[ row])); // 计算变异后的适应度值;
value=x (popnext[ row]);
printf("\nMutation occured in popnext[%d] bit[%d]:=%d%d%d%d%d%d value=%d fitness=%d", row,col ,popnext[ row].bit [5],popnext[ row].bit [4],popnext[ row].bit [3],popnext[ row].bit [2],popnext[ row].bit [1],popnext[ row].bit [0],value, popnext[row ].fit);
// 输出变异后的新个体;
}
return(0);
}
运行结果如下:
5 Q&A环节
细心的同学肯定会发现这样一个问题,每次运行上述程序出来的结果都不一样……
最大值为4
最大值为1
于是过来问,啪的一下,很快啊:
Question3:帕老师,发生甚么事了,怎么每次结果都不一样?
Answer3:这个问题其实我们在遗传算法(一)中提到过,遗传算法依赖于初始种群,并不保证是最优解
可能很多同学又要问了:不是你说遗传算法寻找的是全局最优吗,怎么现在又改口说不保证了?我只能说,的确遗传算法寻找的是全局最优解,但这里之所以说不保证,是因为种群中的每个个体都是随机生成的,并不能保证你的初始种群,包含了我们认为的全局最优解(在这里就是 x=0),因此遗传算法所寻找到的“全局最优解”,其实是当前种群范围下的“最优”,并不是真正的最优。
下面回收之前的Question2:种群大小会对最终结果产生很大影响吗?
Answer2:我只能说,有影响但影响不大,比较玄学。
以二进制编码法为例的话,假设我生成的二进制数的范围为0~31这32个整数的话,理想情况下考虑符号位,只需要64组不同的个体,就可以认为在全局范围下生成了种群,但是在程序中我们的编码过程是随机的,很难保证这64个个体两两之间互不相同,因此种群大小在比64大的情况下,就可以得到比较好的效果了。如果种群的范围再大下去的话也没有意义了,当然这是在整数解个数有限的前提下才这么说。
同样,我们仍以二进制编码法为例,假设我现在整数范围用6个二进制位来表示-32~31这64个不同的整数,增加10个二进制位用于表示小数点后三位的所有小数,大家可以想想现在的种群大小要多大才能尽可能表示所有的个体呢?
没错,2^16个个体……这还是在两两之间不重复的情况下,如果再多一个未知数呢?
或者用浮点编码法来表示的话,我们知道相邻两个整数之间可以划分出无数个区间,那我们又需要多少个个体来初始化相应种群呢?
因此,种群的大小对最终结果的影响,在可能解的个数有限或者待划分的区间较小时,种群越大划分的越细致,效果越好。过大也会导致整体速度下降。
回收之前的Question1:如果有一个连续函数求取最大值的问题,你会选择二进制编码法还是浮点数编码法呢?
我的建议是浮点编码法会更好,二进制编码法最大的问题就是实在是太麻烦了,需要对每个二进制位进行解码操作,如果是多个未知数的话就更加麻烦。
浮点数编码只需要在给定范围生成随机数即可,无需进行解码操作。
附录:浮点数编码法解决上述问题
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#include <iostream>
#include <math.h>
#include <Windows.h>
#define PI 3.1415926
const int populationNum = 1000; //种群中个体的数量
const int i_DNA_Num = 18; //染色体的基因位数
typedef struct DNA //结构体类型,为单个染色体的结构需先考虑染色体的编解码规则
{
short int bit[i_DNA_Num]; //一共n位来对染色体进行编码,其中1位为符号位
double fit; //根据适应度函数计算当前染色体的适应值
double rfit; //相对的适应值在总适应值中所占的百分比
double cfit; //积累概率
}DNA;
//定义将会用到的几个函数;
void* initialPopulation(DNA popcurrent[populationNum]); //进行种群的初始化函数
int codeDNA(DNA popcurrent[populationNum]); //对染色体进行编码的函数
double decodeDNA(DNA popcurrent); //对染色体进行解码的函数
void* fitnessFuction(DNA popcurrent[populationNum]); //计算染色体适应度的函数
double targetFuction(double x); //计算染色体对应的目标函数(表现型)
void* selectionFuction(DNA popcurrent[populationNum]); //FitnessFuction染色体选择函数
void* selectionFuction_Roulette_Wheel_Selection(DNA popcurrent[populationNum]); // 选择函数(轮盘赌方法)
void* crossoverFuction(DNA popnext[populationNum]); //Crossover染色体交叉函数
void* mutationFuction(DNA popnext[populationNum]); //MutationFuction染色体突变函数
double r8_uniform_ab(double a, double b, int& seed); //生成a~b之间均匀分布的数字
void* outputPopulationInfo(DNA popcurrent[populationNum]); //输出当前种群中每个染色体的基因型(DNA),表现型(value)和适应度(fitness)
DNA popcurrent[populationNum]; //当前种群(父类)
DNA popnext[populationNum]; //更新后种群(子类)
//主函数
void main()
{
DWORD start, end;
start = GetTickCount();
end = GetTickCount();
int num; //迭代次数
int i, j, l;
double k = 0;
double Max = 0; //函数最大值
printf("The Algorithm is based on the function y = -x^2 + 5 to find the maximum value of the function.\n");
enter:printf("\nPlease enter the no. of iterations\n请输入您要设定的迭代数 : ");
scanf_s("%d", &num); //输入迭代次数,传送给参数 num
if (num < 1)
goto enter; //判断输入的迭代次数是否为负或零,是的话重新输入,不同的随机数可能结果不同??那是当所设置的迭代次数过少时,染色体的基因型过早地陷入局部最优
srand(time(0));
initialPopulation(popcurrent); //随机产生初始种群
Max = popcurrent[0].fit; //对Max值进行初始化
for (i = 0;i < num;i++) //开始迭代
{
printf("\ni = %d\n", i); //输出当前迭代次数
for (j = 0;j < populationNum; j++)
{
popnext[j] = popcurrent[j]; //更新种群
}
selectionFuction(popnext); //挑选优秀个体
crossoverFuction(popnext); //交叉得到新个体
mutationFuction(popnext); //变异得到新个体
for (j = 0;j < populationNum; j++)
{
popcurrent[j] = popnext[j]; //种群更替
}
}
for (l = 0;l < populationNum - 1; l++)
{
if (popcurrent[l].fit > Max)
{
Max = popcurrent[l].fit;
k = decodeDNA(popcurrent[l]); //此时的value即为所求的x值
}
}
printf("\n 当x等于 %f时,函数得到最大值为: %f ", k, Max);
printf("\nPress any key to end ! ");
_flushall(); //清除所有缓冲区
_getche(); //从控制台取字符,不以回车为结束
}
//随机初始化种群
void* initialPopulation(DNA popcurrent[populationNum])
{
int j;
double sum = 0;
codeDNA(popcurrent);
fitnessFuction(popcurrent);
outputPopulationInfo(popcurrent);
//计算适应值得百分比,该参数是在用轮盘赌选择法时需要用到的
for (j = 0; j < populationNum; j++)
{
popcurrent[j].rfit = popcurrent[j].fit / sum;
popcurrent[j].cfit = 0; //将其初始化为0
}
return(0);
}
//对染色体进行编码
int codeDNA(DNA popcurrent[populationNum])
{
int i, j;
int random;
for(j = 0;j < populationNum; j++)
{
for (i = 0;i < i_DNA_Num; i++) //从染色体的第1个基因位到第n个基因位
{
random = rand(); //产生一个随机值
random = (random % 2); //随机产生0或者1
popcurrent[j].bit[i] = random; //随机产生染色体上每一个基因位的值
}
}
return(0);
}
//对染色体进行解码
double decodeDNA(DNA popcurrent) //将二进制换算为十进制,该函数需要根据实际情况进行编写
{
int integerPart; //小数位部分
double fractionalPart; //整数位部分
double totalNumber; //转化出的十进制数
integerPart = 1 * popcurrent.bit[9] + 2 * popcurrent.bit[10] + 4 * popcurrent.bit[11] + 8 * popcurrent.bit[12]
+ 16 * popcurrent.bit[13] + 32 * popcurrent.bit[14] + 64 * popcurrent.bit[15] + 128 * popcurrent.bit[16];
fractionalPart = 0.001 * (512 * popcurrent.bit[0] + 256 * popcurrent.bit[1] + 128 * popcurrent.bit[2]
+ 64 * popcurrent.bit[3] + 32 * popcurrent.bit[4] + 16 * popcurrent.bit[5] + 8 * popcurrent.bit[6]
+ 4 * popcurrent.bit[7] + 2 * popcurrent.bit[8]);
totalNumber = integerPart + fractionalPart;
if (popcurrent.bit[17] == 1) //符号位
{
totalNumber = totalNumber * (-1);
}
return(totalNumber);
}
//计算当前种群中染色体的适应度函数
void* fitnessFuction(DNA popcurrent[populationNum])
{
int j;
for(j = 0; j < populationNum; j++)
{
popcurrent[j].fit = targetFuction(decodeDNA(popcurrent[j]));
}
return(0);
}
//目标函数计算(需要能能够从外部直接传输函数,加强鲁棒性)
double targetFuction(double x)
{
double y;
y = -(x * x) + 5; // 目标函数:y= - ( x^ 2 ) +5;
return(y);
}
//基于轮盘赌选择方法,进行基因型的选择
void* selectionFuction_Roulette_Wheel_Selection(DNA popnext[populationNum]) //计算概率
{
int men;
int i;int j;
double p;
double sum = 0.0;
//find the total fitness of the population,计算出种群的整体适应度
for (men = 0; men < populationNum; men++)
{
sum = sum + popnext[men].fit;
}
//calculate the relative fitness of each member,计算出种群中每个个体在整体适应度中的百分比
for (men = 0; men < populationNum; men++)
{
popnext[men].rfit = popnext[men].fit / sum;
}
//calculate the cumulative fitness,即计算积累概率
popcurrent[0].cfit = popcurrent[0].rfit;
for (men = 1; men < populationNum; men++)
{
popnext[men].cfit = popnext[men - 1].cfit + popnext[men].rfit;
}
for (i = 0; i < populationNum; i++)
{
//p = r8_uniform_ab ( 0, 1, seed );//通过函数生成0~1之间均匀分布的数字
p = rand() % 10; //生成0~10之间均匀分布的数字
p = p / 10; //变成0~1之间均匀分布的数字
if (p < popnext[0].cfit)
{
popcurrent[i] = popnext[0];
}
else
{
for (j = 0; j < populationNum; j++)
{
if (popnext[j].cfit <= p && p < popnext[j + 1].cfit)
{
popcurrent[i] = popcurrent[j + 1];
}
}
}
}
//用新种群覆盖老种群
for (i = 0; i < populationNum; i++)
{
popnext[i] = popcurrent[i];
}
return(0);
}
//选择操作
void* selectionFuction(DNA popnext[populationNum])
{
int i, j;
DNA temp;
for (i = 0;i < populationNum - 1; i++) //根据个体适应度来排序(冒泡法)
{
for (j = 0;j < populationNum - 1 - i; j++)
{
if (popnext[j + 1].fit > popnext[j].fit)
{
temp = popnext[j + 1];
popnext[j + 1] = popnext[j];
popnext[j] = temp;
}
}
}
for (i = 0;i < populationNum; i++)
{
printf("\nSorting:popnext[%d] fitness=%f", i, popnext[i].fit);
printf("\n");
}
_flushall();/* 清除所有缓冲区 */
return(0);
}
//生成a~b之间均匀分布的数字
double r8_uniform_ab(double a, double b, int& seed)
{
{
int i4_huge = 2147483647;
int k;
double value;
if (seed == 0)
{
std::cerr << "\n";
std::cerr << "R8_UNIFORM_AB - Fatal error!\n";
std::cerr << " Input value of SEED = 0.\n";
exit(1);
}
k = seed / 127773;
seed = 16807 * (seed - k * 127773) - k * 2836;
if (seed < 0)
{
seed = seed + i4_huge;
}
value = (double)(seed) * 4.656612875E-10;
value = a + (b - a) * value;
return value;
}
}
//染色体交叉操作
void* crossoverFuction(DNA popnext[populationNum])
{
int random;
int i;
//srand(time(0));
random = rand(); //随机产生交叉点
random = ((random % (i_DNA_Num - 1)) + 1); //交叉点控制在0到5之间
for (i = 0;i < random;i++)
{
popnext[2].bit[i] = popnext[0].bit[i]; // child 1 cross over
popnext[3].bit[i] = popnext[1].bit[i]; // child 2 cross over
}
for (i = random; i < i_DNA_Num;i++) // crossing the bits beyond the cross point index
{
popnext[2].bit[i] = popnext[1].bit[i]; // child 1 cross over
popnext[3].bit[i] = popnext[0].bit[i]; // chlid 2 cross over
}
fitnessFuction(popnext);
printf("\nAfter crossover the next population evolve to:");
outputPopulationInfo(popnext);
return(0);
}
//染色体变异操作
void* mutationFuction(DNA popnext[populationNum])
{
int random;
int row, col;
double value;
//srand(time(0));
random = rand() % 50;
//变异操作也要遵从一定的概率来进行,一般设置为0到0.5之间
if (random == 25) //random==25的概率只有2%,即变异率为,所以是以小概率进行变异!!
{
col = rand() % i_DNA_Num; //随机产生要变异的基因位号;
row = rand() % populationNum; //随机产生要变异的染色体号;
if (popnext[row].bit[col] == 0) //1变为0
{
popnext[row].bit[col] = 1;
}
else if (popnext[row].bit[col] == 1) //0变为1
{
popnext[row].bit[col] = 0;
}
popnext[row].fit = targetFuction(decodeDNA(popnext[row])); //计算变异后的适应度值
value = decodeDNA(popnext[row]);
printf("\nMutation occured in popnext[%d] bit[%d]:=%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d value=%f fitness=%f", row, col,
popnext[row].bit[17], popnext[row].bit[16], popnext[row].bit[15], popnext[row].bit[14], popnext[row].bit[13], popnext[row].bit[12],
popnext[row].bit[11], popnext[row].bit[10], popnext[row].bit[9], popnext[row].bit[8], popnext[row].bit[7], popnext[row].bit[6],
popnext[row].bit[5], popnext[row].bit[4], popnext[row].bit[3], popnext[row].bit[2], popnext[row].bit[1], popnext[row].bit[0], value, popnext[row].fit);
}
return(0);
}
//输出当前种群中每个染色体的基因型(DNA),表现型(value)和适应度(fitness)
void* outputPopulationInfo(DNA popcurrent[populationNum])
{
int i, j;
double value = 0;
double fitness = 0;
for(j = 0; j < populationNum;j++)
{
value = decodeDNA(popcurrent[j]);
fitness = popcurrent[j].fit;
printf("\npopulation[%d]=", j);
for (i = 0;i < i_DNA_Num;i++)
{
printf("%d", popcurrent[j].bit[i]);
}
printf("\t value = %f", value);
printf("\t fitness = %f", fitness);
}
return(0);
}
效果比较稳定