遗传算法详解及c++实现
1、什么是遗传算法?
遗传算法是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法。遗传算法是从代表问题可能潜在的解集的一个种群开始的,而一个种群则由经过基因编码的一定数目的个体组成。每个个体实际上是染色体带有特征的实体。染色体作为遗传物质的主要载体,即多个基因的集合,其内部表现(即基因型)是某种基因组合,它决定了个体的形状的外部表现,如黑头发的特征是由染色体中控制这一特征的某种基因组合决定的。因此,在一开始需要实现从表现型到基因型的映射即编码工作。由于仿照基因编码的工作很复杂,我们往往进行简化,如二进制编码,初代种群产生之后,按照适者生存和优胜劣汰的原理,逐代演化产生出越来越好的近似解,在每一代,根据问题域中个体的适应度大小选择比较优秀的个体,并借助于自然遗传学的遗传算子进行交叉和变异,产生出代表新的解集的种群。这个过程将导致种群像自然进化一样的后生代种群比前代更加适应于环境,末代种群中的最优个体经过解码,可以作为问题近似最优解。
2、遗传算法的运算过程
3、遗传算法基本框架
4、相关术语
基因型(genotype):性状染色体的内部表现;
表现型(phenotype):染色体决定的性状的外部表现,或者说,根据基因型形成的个体的外部表现;
进化(evolution):种群逐渐适应生存环境,品质不断得到改良。生物的进化是以种群的形式进行的。
适应度(fitness):度量某个物种对于生存环境的适应程度。
选择(selection):以一定的概率从种群中选择若干个个体。一般,选择过程是一种基于适应度的优胜劣汰的过程。
复制(reproduction):细胞分裂时,遗传物质DNA通过复制而转移到新产生的细胞中,新细胞就继承了旧细胞的基因。
交叉(crossover):两个染色体的某一相同位置处DNA被切断,前后两串分别交叉组合形成两个新的染色体。也称基因重组或杂交;
变异(mutation):复制时可能(很小的概率)产生某些复制差错,变异产生新的染色体,表现出新的性状。
编码(coding):DNA中遗传信息在一个长链上按一定的模式排列。遗传编码可看作从表现型到基因型的映射。
解码(decoding):基因型到表现型的映射。
个体(individual):指染色体带有特征的实体;
种群(population):个体的集合,该集合内个体数称为种群
5、遗传算法的应用实例
现在我以一个多元函数最值问题求解来一步步阐述遗传算法的实现过程。
问题:求一个多元函数的最大值,f(x1,x2) = 21.5+x1*sin(4pi*x1)+x2*sin(20pi*x2),其中-3.0≤x1≤12.1,4.1≤x2≤5.8。
6、设计与实现
(a)前提条件:
const int Po_Size = 50;//种群规模
const int Ev_Algebra = 500;//进化代数
const double Ov_Probability = 0.850; //交叉概率,交叉概率用于判断两两个体是否需要交叉
const double Va_Probability = 0.050;//变异概率,变异概率用于判断任一个体是否需要变异
const int De_Variable = 2;//函数自变量的个数,如果要进行多元函数的最值求解,直接修改自变量数个De_Variable即可
vector<Individual> nowpopulation;//P(t)种群
vector<Individual> midpopulation;//中间种群,存放轮盘选择后的优秀个体
vector<Individual> nextpopulation;//P(t+1)种群
(b)类的实现:
class Individual //定义个体类
{
private:
double Variable[De_Variable];//存放变量x1,x2,x3........
double Fitness;//适应值
double ReFitness;//适应值概率
double SumFitness;//累加概率,为轮盘转做准备
public:
............
};
(c):初始化:
随机生成两个满足条件范围的x1和x2自变量,生成一个个体,初始化种群,得到第一代种群。在这里用一个随机函数生成两个指定范围的小数,然后创建一个对象(个体),加入到初始种群中,直到达到种群规模。
(d)计算适应值,适应值概率,累加概率:
适应值是根据每个个体的自变量进行计算的,这里是求函数的最大值,所以把函数就作为适应值计算函数,适应值概率的计算是每个个体占所有个体适应值总和的百分比,累加概率是为了轮盘法选择做准备。
(e)进行选择,杂交,变异:
最重要的就是这三个算子,选择即就是用轮盘法选出一些相对优秀的个体,在程序中我没有用精英制选择,读者自己可以加以实现。杂交之前需要对自变量进行编码,我在程序中采用的是二进制的编码,用的是c++标准库中的bitset类来进行编码的,杂交的方式我用的是单点杂交,你也可以选择其他杂交方式。变异我用的是基本位变异方式,相对比较简单。
c++code:
GeneticAlgorithm.h文件
1 //功能:求一个多元函数的最大值(这里是求二元函数的最大值:f(x1,x2) = 21.5+x1*sin(4pi*x1)+x2*sin(20pi*x2)) 2 #pragma once//保证文件只被编译一次 3 #include<random> 4 #include<vector> 5 #include<iostream> 6 #include<cmath> 7 #include<ctime> 8 #include <cstdlib> 9 #include <bitset> 10 #include<iomanip> 11 using namespace std; 12 const double PI = 3.141592653589793;//定义一个不可改变的常量值PI 13 const int Po_Size = 50;//种群规模 14 const int Ev_Algebra = 500;//进化代数 15 const double Ov_Probability = 0.850; //交叉概率,交叉概率用于判断两两个体是否需要交叉 16 const double Va_Probability = 0.050;//变异概率,变异概率用于判断任一个体是否需要变异 17 const int De_Variable = 2;//函数自变量的个数,如果要进行多元函数的最值求解,直接修改自变量数个De_Variable即可 18 const int length1 = 24;//精确到6位小数,用24位二进制编码 19 const int length2 = 23;//精确到6位小数,用23位二进制编码 20 class X_Range //自变量取值范围类,适用于多个变量 21 { 22 private: 23 double Upper;//变量的上界取值 24 double Lower;//变量的下界取值 25 public: 26 X_Range(double m_Upper, double m_Lower);//构造函数 27 double GetUpper()const;//获取变量上限 28 double GetLower()const;//获取变量下限 29 }; 30 class Individual //定义个体类 31 { 32 private: 33 double Variable[De_Variable];//存放变量x1,x2,x3........ 34 double Fitness;//适应值 35 double ReFitness;//适应值概率 36 double SumFitness;//累加概率,为轮盘转做准备 37 public: 38 Individual() {}//默认构造函数 39 Individual(double* m_Variable);//构造函数 40 double* GetVariable();//获取变量值 41 void ChaFitness(const double m_fitness);//修改适应值 42 void ChaReFitness(const double m_ReFitness);//修改适应值概率 43 void ChaSumFitness(const double m_SumFitness);//修改累加概率 44 double GetFitness()const;//获取适应值 45 double GetReFitness()const;//获取适应值概率 46 double GetSumFitness()const;//获取累加概率 47 }; 48 void Initialize();//随机初始化种群,得到第一代个体 49 void CaculaFitness();//计算个体的适应值 50 void CaculaReFitness();//计算个体的适应值概率 51 void CalculaSumFitness();//计算累加个体概率 52 void seclect();//种群选择 53 double Scand();//随机产生0到49的随机整数 54 void crossing();//杂交 55 void variating();//变异 56 void genetic_algorithm();//遗传算法
GeneticAlgorithm.cpp文件
1 #include"GeneticAlgorithm.h"//包含头文件 2 //自变量取值范围向量和种群向量定义 3 const X_Range Range[De_Variable] = { X_Range(-3.0,12.1) ,X_Range(4.1,5.8) };//自变量(或者基因)x1,x2的取值范围 4 vector<Individual> nowpopulation;//P(t)种群 5 vector<Individual> midpopulation;//中间种群,存放轮盘选择后的优秀个体 6 vector<Individual> nextpopulation;//P(t+1)种群 7 //X_Range类实现 8 X_Range::X_Range(double m_Lower, double m_Upper) :Lower(m_Lower), Upper(m_Upper){}//X_Range类构造函数实现 9 double X_Range::GetUpper()const//获取变量上限 10 { 11 return Upper; 12 } 13 double X_Range::GetLower()const//获取变量下限 14 { 15 return Lower; 16 } 17 //Individual类实现 18 Individual::Individual(double* m_Variable)//构造函数 19 { 20 for (int i = 0; i < De_Variable; i++)//用for循环自变量逐个赋值 21 { 22 if (m_Variable[i] >= Range[i].GetLower() && m_Variable[i] <= Range[i].GetUpper())//这里要进行自变量取值范围判断 23 { 24 Variable[i] = m_Variable[i];//自变量赋值 25 } 26 else//不满足要求则发出出错警告并返回 27 { 28 cerr << "自变量取值不满足要求" << endl; 29 exit(1);//停止程序,我会以随机函数的方式生成自变量的值(基因值),这里说明基因值不在规定范围内 30 } 31 } 32 //初始化时默认适应值等值为0 33 this->Fitness = 0; 34 this->ReFitness = 0; 35 this->SumFitness = 0; 36 } 37 double* Individual::GetVariable()//获取基因值 38 { 39 return Variable; 40 } 41 double Individual::GetFitness()const//获取适应值 42 { 43 return Fitness; 44 } 45 double Individual::GetReFitness()const //获取适应值概率 46 { 47 return ReFitness; 48 } 49 double Individual::GetSumFitness()const//获取累加概率 50 { 51 return SumFitness; 52 } 53 void Individual::ChaFitness(const double m_fitness)//修改适应值 54 { 55 this->Fitness = m_fitness; 56 } 57 void Individual::ChaReFitness(const double m_ReFitness)//修改适应值概率 58 { 59 this->ReFitness = m_ReFitness; 60 } 61 void Individual::ChaSumFitness(const double m_SumFitness)//修改累加概率 62 { 63 this->SumFitness = m_SumFitness; 64 } 65 //遗传算法的准备工作 66 void Initialize()//随机初始化种群,得到第一代种群 67 { 68 //产生指定范围的随机变量(基因) 69 double X[Po_Size][De_Variable];//为了使程序可以满足多元函数最值的计算,用矩阵保存产生的随机数变量值 70 for (int j = 0; j < De_Variable; j++) 71 { 72 default_random_engine e(time(0));//引擎,生成随机序列 73 uniform_real_distribution<double> u(Range[j].GetLower(), Range[j].GetUpper());//分布 74 for (int i = 0; i < Po_Size; i++)//先按列存储随机数 75 { 76 X[i][j] = u(e);//循环结束时,所有随机值就保存在X矩阵中 77 } 78 } 79 //生成对象(染色体)并加入到初始种群中 80 for (int i = 0; i < Po_Size; i++) 81 { 82 double variable[De_Variable]; 83 for (int j = 0; j < De_Variable; j++) 84 { 85 variable[j] = X[i][j];//按行保存 86 } 87 Individual Indivi(variable);//生成一个对象(染色体) 88 nowpopulation.push_back(Indivi);//加入到种群population中 89 } 90 } 91 void CaculaFitness()//计算个体的适应值 92 { 93 //f(x1,x2) = 21.5+x1*sin(4pi*x1)+x2*sin(20pi*x2))为适应度计算函数 94 double fitness = 0;//临时适应值 95 double x[De_Variable];//临时存储自变量(基因) 96 for (int i = 0; i < Po_Size; i++) 97 { 98 for (int j = 0; j < De_Variable; j++) 99 x[j] = nowpopulation.at(i).GetVariable()[j];//这样更直观 100 fitness = 21.5 + x[0] * sin(4 * PI*x[0]) + 2 * sin(20 * PI*x[1]);//适应度计算 101 nowpopulation.at(i).ChaFitness(fitness);//修改当前染色体的适应值 102 } 103 } 104 void CaculaReFitness()//计算适应值概率 105 { 106 double sum = 0;//适应值累加器 107 double temp = 0; 108 for (int i = 0; i < Po_Size; i++)//计算出适应值之和 109 { 110 sum += nowpopulation.at(i).GetFitness(); 111 } 112 for (int j = 0; j < Po_Size; j++) 113 { 114 temp = nowpopulation.at(j).GetFitness() / sum;//计算概率 115 nowpopulation.at(j).ChaReFitness(temp);//修改个体的适应度概率 116 } 117 } 118 void CalculaSumFitness()//计算累加个体概率 119 { 120 double summation = 0;//累加器 121 for (int k = 0; k < Po_Size; k++) 122 { 123 summation += nowpopulation.at(k).GetReFitness(); 124 nowpopulation.at(k).ChaSumFitness(summation);//当前累加结果赋值 125 } 126 } 127 void seclect() //种群选择 128 { 129 //随机生生成0到1的小数 130 double array[Po_Size];//随机数保存变量 131 default_random_engine e(time(0));//引擎,生成随机序列 132 uniform_real_distribution<double> u(0.0, 1.0); //分布 133 for (int i = 0; i < Po_Size; i++) 134 array[i] = u(e); 135 //轮盘进行选择 136 for (int j = 0; j < Po_Size; j++) 137 { 138 for (int i = 1; i < Po_Size; i++) 139 { 140 if (array[j] < nowpopulation[i - 1].GetSumFitness()) 141 { 142 midpopulation.push_back(nowpopulation.at(i - 1));//加入到中间种群 143 } 144 if (array[j] >= nowpopulation.at(i - 1).GetSumFitness() && array[j] <= nowpopulation.at(i).GetSumFitness()) 145 { 146 midpopulation.push_back(nowpopulation.at(i));//加入到中间种群 147 } 148 } 149 } 150 nowpopulation.clear();//清空nowpopulation 151 } 152 double Scand() //随机产生0到1的小数 153 { 154 int N = rand() % 999; 155 return double(N)/1000.0;;//随机产生0到1的小数 156 } 157 void crossing()//杂交 158 { 159 int num = 0;//记录次数 160 double corss = 0.0;//保存随机产生的概率值 161 srand((unsigned)time(NULL));//根据系统时间设置随机数种子,设置一次随机种子就行 162 double array1[De_Variable], array2[De_Variable];//临时存储父亲和母亲的变量值 163 while (num< Po_Size-1)//个体1与个体2杂交,个体3与个体4杂交......个体i和个体i+1杂交 164 { 165 //判断双亲是否需要杂交,随机生成一个0到1的小数,如果这个数大于杂交概率,则放弃杂交,直接遗传给下一代,否则,对父母体进行杂交 166 corss = Scand(); 167 if (corss <= Ov_Probability)//如果corss小于等于杂交概率Ov_Probability就进行单点杂交 168 { 169 //首先寻找对应下标的个体并且保存 170 for (int i = 0; i < De_Variable; i++) 171 { 172 array1[i] = midpopulation.at(num).GetVariable()[i];//父亲的自变量 173 array2[i] = midpopulation.at(num + 1).GetVariable()[i];//母亲自变量 174 } 175 int localx1, localx2;//记录基因交叉点的位置 176 int corssx1[length1], corssx2[length2];//作为交换基因的数组 177 double newx1[2], newx2[2];//分别用来保存基因交换后所对应自变量值 178 bool p1 = true, p2 = true; 179 //然后对双亲变量进行编码并且进行单点杂交 180 for (int j = 0; j < De_Variable; j++)//array1的x1编码之后和array2的x1编码后进行单点杂交,以此类推 181 { 182 if (j == 0)//x1进行编码并且杂交 183 { 184 bitset<length1> array1b1((array1[j] + 3.0)* pow(10, 6));//加上3.0形成一个unsigaed数之后在进行母体1的x1编码 185 bitset<length1> array2b1((array2[j] + 3.0)* pow(10, 6));//加上3.0形成一个unsigaed数之后在进行母体2的x1编码 186 //现在随机生成0到length1-1的数,确定交叉点的位置 187 localx1 = rand() % length1; 188 //现在进行单点交叉,交换双亲localx1后面的基因 189 for (int i = 0; i < localx1; i++) 190 corssx1[i] = array1b1[i]; 191 for (int k = 0; k < localx1; k++) 192 array1b1[k] = array2b1[k]; 193 for (int s = 0; s < localx1; s++) 194 array2b1[s] = corssx1[s]; 195 //新值保存在newx1数组中,x1基因完成单点杂交操作 196 newx1[0] = double(array1b1.to_ullong()) / pow(10, 6) - 3.0; 197 newx2[0] = double(array2b1.to_ullong()) / pow(10, 6) - 3.0; 198 //对新产生的值进行判断,判断是否超出范围,如果超出范围则不杂交 199 if (newx1[0]< Range[0].GetLower() || newx1[0]>Range[0].GetUpper() || newx2[0]<Range[0].GetLower() || newx2[0]>Range[0].GetUpper()) 200 { 201 p1 = false; 202 break; 203 } 204 } 205 if (j == 1)//x2进行编码并且杂交 206 { 207 bitset<length2> array1b2((array1[j]) * pow(10, 6));//母体1的x2编码 208 bitset<length2> array2b2((array2[j]) * pow(10, 6));//母体2的x2编码 209 //现在随机生成0到length2-1的数,确定交叉点的位置 210 localx2 = rand() % length2; 211 //现在进行单点交叉,交换双亲localx2后面的基因 212 for (int i = 0; i < localx2; i++) 213 corssx2[i] = array1b2[i]; 214 for (int k = 0; k < localx2; k++) 215 array1b2[k] = array2b2[k]; 216 for (int s = 0; s < localx2; s++) 217 array2b2[s] = corssx2[s]; 218 //新值保存在newx2数组中,x2基因完成单点杂交操作 219 newx1[1] = double(array1b2.to_ullong()) / pow(10, 6); 220 newx2[1] = double(array2b2.to_ullong()) / pow(10, 6); 221 //对新产生的值进行判断,判断是否超出范围,如果超出范围则不杂交 222 if (newx1[1]< Range[1].GetLower() || newx1[1]>Range[1].GetUpper() || newx2[1]<Range[1].GetLower() || newx2[1]>Range[1].GetUpper()) 223 { 224 p2 = false; 225 break; 226 } 227 } 228 } 229 if (p1 == true && p2 == true) 230 { 231 Individual newchiled1(newx1); 232 Individual newchiled2(newx2); 233 nextpopulation.push_back(newchiled1); 234 nextpopulation.push_back(newchiled2); 235 } 236 else//将原来的个体遗传给下一代 237 { 238 nextpopulation.push_back(midpopulation.at(num)); 239 nextpopulation.push_back(midpopulation.at(num + 1)); 240 } 241 } 242 else//否则直接遗传给下一代nextpopulation 243 { 244 nextpopulation.push_back(midpopulation.at(num));//生成一个新的个体并且加入到nextpopulation中 245 nextpopulation.push_back(midpopulation.at(num + 1)); 246 } 247 num += 2; 248 } 249 midpopulation.clear();//清空midpopulation 250 } 251 void variating()//变异 252 { 253 int num = 0; 254 while (num<Po_Size) 255 { 256 double variation = Scand();//随机产生一个0到1的小数,用于判断是否进行变异 257 if (variation <= Va_Probability)//如果variation小于变异系数,则需要进行变异 258 { 259 double x[2]; 260 bool p = true; 261 int x1local, x2local; 262 x[0] = nextpopulation.at(num).GetVariable()[0]; 263 x[1] = nextpopulation.at(num).GetVariable()[1]; 264 bitset<length1> array1((x[0]+ 3.0)* pow(10, 6));//x1编码 265 bitset<length2> array2(x[1]*pow(10,6));//x2编码 266 x1local = rand() % length1;//array1该位取反 267 x2local = rand() % length2;//array2该位取反 268 array1.flip(x1local);//改变array1 x1local位的状态 269 array2.flip(x2local);//改变array2 x2local位的状态 270 x[0] = double(array1.to_ullong()) / pow(10, 6) - 3.0; 271 x[1] = double(array2.to_ullong()) / pow(10, 6); 272 //判断是否符合条件 273 if (x[0]< Range[0].GetLower() || x[0]>Range[0].GetUpper() || x[1]<Range[1].GetLower() || x[1]>Range[1].GetUpper()) 274 p = false; 275 if (!p) 276 nowpopulation.push_back(nextpopulation.at(num)); 277 if (p) 278 { 279 Individual newchiled(x); 280 nowpopulation.push_back(newchiled); 281 } 282 } 283 else 284 nowpopulation.push_back(nextpopulation.at(num)); 285 num++; 286 } 287 nextpopulation.clear();//清空nextpopulation 288 } 289 void genetic_algorithm() 290 { 291 Initialize();//初始化种群,随机生成第一代个体 292 //进化500代 293 for (int i = 0; i < Ev_Algebra; i++) 294 { 295 CaculaFitness();//适应度计算 296 CaculaReFitness();//适应度概率计算 297 CalculaSumFitness();//计算累加个体概率 298 seclect();//选择 299 crossing();//杂交 300 variating();//变异 301 } 302 CaculaFitness();//适应度计算 303 double maxfitness=nowpopulation.at(0).GetFitness(); 304 int maxid = 0; 305 int k; 306 for (k = 0; k < Po_Size; k++) 307 { 308 if (maxfitness < nowpopulation.at(k).GetFitness()) 309 { 310 maxfitness = nowpopulation.at(k).GetFitness(); 311 maxid = k; 312 } 313 } 314 //进化500代之后输出 315 cout << "x1"<<setw(10)<<"x2" << setw(15)<<"Fitness" << endl; 316 for (int j = 0; j < Po_Size; j++) 317 cout << nowpopulation.at(j).GetVariable()[0] <<setw(10)<< nowpopulation.at(j).GetVariable()[1] << setw(10) <<nowpopulation.at(j).GetFitness() << endl; 318 cout << "x1=" << nowpopulation.at(maxid).GetVariable()[0] << " ," << "x2=" << nowpopulation.at(maxid).GetVariable()[1] << "时取得最大值:" << maxfitness << endl; 319 }
main.cpp文件
1 #include"GeneticAlgorithm.h" 2 int main() 3 { 4 genetic_algorithm(); 5 system("pause"); 6 return 0; 7 }
结果:
遗传算法的细节我就没有过多的介绍,上面的代码是本人根据f(x1,x2) = 21.5+x1*sin(4pi*x1)+x2*sin(20pi*x2),其中-3.0≤x1≤12.1,4.1≤x2≤5.8写的,如果有不合理的地方,麻烦提出来,谢谢大家!