遗传算法实现
1. 课程题目
给定不受限优化问题:
实现遗传算法,得出结果,并画出寻优曲线。
2. 算法总框架
算法框架的代码如下:
int generation = 0; List<Chromosome> current = Initial(); Fitness(current); while(generation < _maxGeneration) { var crossChildren = Crossover(current); var mutationChildren = Mutation(current); current.AddRange(crossChildren); current.AddRange(mutationChildren); Fitness(current); current = Selection(current); generation++; }
算法整体分成两步:
(1) 初始化(算法第2行)。随机初始化生物种群,current表示当前生物种群,然后计算当前生物种群的适应度。
(2) 进化(算法第5-14行)。算法进化指定迭代最大次数_maxGeneration,每次迭代执行4个操作:
a) 交叉遗传(算法第7行),得到一些子代crossChildren。
b) 变异遗传(算法第8行),得到一些子代mutationChildren。
c) 计算所有子代和父代的适应度(算法第11行)。
d) 从所有子代和父代中选择一些个体形成当前种群(算法第12行)。
下面的小节对每个操作进行详细介绍。
3. 初始化Initial
该操作随机产生_popSize个个体形成种群,并返回。其代码如下所示:
protected List<Chromosome> Initial() { List<Chromosome> result = new List<Chromosome>(); for(int i = 0; i<_popSize; i++) { double num1 = RandomNumber(MIN_X1, MAX_X2); double num2 = RandomNumber(MIN_X2, MAX_X2); result.Add(Encode(num1, num2)); } return result; }
针对每个产生的个体,我们产生两个给定范围的随机数,然后对这两个随机数进行编码,得到该个体的染色体。Encode方法的代码如下:
private Chromosome Encode(double num1, double num2) { num1 = (num1 - MIN_X1) * (Math.Pow(2, _code1Len) - 1) / (MAX_X1 - MIN_X1); num2 = (num2 - MIN_X2) * (Math.Pow(2, _code2Len) - 1) / (MAX_X2 - MIN_X2); var str1 = DecToBinary((int)num1); var str2 = DecToBinary((int)num2); while(str1.Length < _code1Len) { str1 = '0' + str1; } while(str2.Length < _code2Len) { str2 = '0' + str2; } var str = str1 + str2; Chromosome c = new Chromosome(str); return c; }
这里第3行和第4行的_code1Len和_code2Len分别表示用二进制字符串表示x1和x2的给定范围所需要最少位数。程序第3行和第4行将原始数字均匀映射到0到2len-1中的某个数字,然后将所得结果转化成二进制表示的形式。第9至17行是对齐操作,将不足长度的二进制串前面补0。最后得到整个二进制字符串,生成单个个体。变量所需的二进制位数len的计算公式为:
其中max和min分别表示变量的最大和最小的范围,precision表示的精度,若小数点保留5位小数,则precision = 10-5。
4. 适应度计算Fitness
这一步计算给定一代种群的最优值,并保存历史最佳结果。其代码为:
protected void Fitness(List<Chromosome> chs) { double temMaxValue = double.MinValue; double optimalX1 = 0; double optimalX2 = 0; foreach(var ch in chs) { var decode = Decode(ch); var value = ValueFunction(decode.Item1, decode.Item2); if(value > temMaxValue) { temMaxValue = value; optimalX1 = decode.Item1; optimalX2 = decode.Item2; } } _localMaxValue.Add(temMaxValue); if(temMaxValue > _maxValue) { _maxValue = temMaxValue; _optX1 = optimalX1; _optX2 = optimalX2; } _globalMaxValue.Add(_maxValue); }
算法中第6至17行找到当前一代的最优值,第18至24行替换全局最优值。对于某个个体而言,计算它的适应度分成两步:解码(第8行)和值计算(第9行)。
(1)首先对其染色体进行解码,代码如下所示。染色体是一个固定长度的二进制字符串,首先取出每个变量对应的部分(第3和4行),然后将二进制字符串转化成十进制数,注意这个十进制数是0至2len-1对应的数字,我们还需要转化成实际区间内的数(第9和10行)。
private Tuple<double, double> Decode(Chromosome ch) { Chromosome ch1 = ch.SubChromosome(0, _code1Len - 1); Chromosome ch2 = ch.SubChromosome(_code1Len, _code1Len + _code2Len - 1); double num1 = BinaryToDec(ch1.ToString()); double num2 = BinaryToDec(ch2.ToString()); num1 = MIN_X1 + num1 * (MAX_X1 - MIN_X1) / (Math.Pow(2, _code1Len) - 1); num2 = MIN_X2 + num2 * (MAX_X2 - MIN_X2) / (Math.Pow(2, _code2Len) - 1); return Tuple.Create<double, double>(num1, num2); }
(2)值计算是指将得到的变量代入到原方程函数中求得所得的值。
5. 交叉遗传Crossover
交叉遗传是指交换两个个体的部分DNA,形成新的个体。如下图所示,两个个体v1和v2在第17个位置之后的部分交换后,得到新的个体c1和c2。
交叉遗传的代码如下图所示。该算法给定一定数目的父种群,产生相同数目的子种群。_pCrossover表示两个个体发生DNA交换的概率。
protected List<Chromosome> Crossover(List<Chromosome> chs) { List<Chromosome> result = new List<Chromosome>(); for(int k = 0; k < chs.Count / 2; k++) { if(_pCrossover >= RandomNumber(0, 1)) { int i = 0, j = 0; while(i == j) { i = (int)RandomNumber(0, chs.Count - 1); j = (int)RandomNumber(0, chs.Count - 1); } int p = (int)RandomNumber(1, _code1Len + _code2Len - 2); var ch1 = new Chromosome(chs[i].SubChromosome(0, p - 1).ToString() + chs[j].SubChromosome(p, _code1Len + _code2Len - 1).ToString()); var ch2 = new Chromosome(chs[j].SubChromosome(0, p - 1).ToString() + chs[i].SubChromosome(p, _code1Len + _code2Len - 1).ToString()); result.Add(ch1); result.Add(ch2); } } return result; }
6. 突变Mutation
突变是指更改部分DNA,生成新的个体。下面给出了突变的代码,对于每个个体的每个位置的DNA,随机进行翻转,然后形成新的个体。
protected List<Chromosome> Mutation(List<Chromosome> chs) { List<Chromosome> result = new List<Chromosome>(); foreach(var ch in chs) { BitArray genes = new BitArray(ch.Genes); for(int j = 0; j < _code1Len + _code2Len; j++) { if(_pMutation >= RandomNumber(0, 1)) { genes.Set(j, !genes.Get(j)); } } result.Add(new Chromosome(genes)); } return result; }
7. 选择Selection
选择操作从一个群体中,选择一定数目的个体。个体被选择的概率正比于个体适应环境的程度。也就是说,若个体更适应环境,则被选择的概率越大。这相当于乐透游戏(Roulette Game),如下图所示,在一个转盘中有很多区域,每个区域的面积表示适应程度,随机旋转这个转盘,面积越大的区域指向某个区域的概率越大。
选择步骤的代码如下所示。代码第4至9行计算每个个体的适应度;第11至16行计算每个个体的适应度占总适应度的比例;第18至23行计算累计概率密度;第25至37行选择指定数目的个体。
protected List<Chromosome> Selection(List<Chromosome> chs) { List<Chromosome> result = new List<Chromosome>(); List<double> values = new List<double>(); foreach(var ch in chs) { var decode = Decode(ch); values.Add(ValueFunction(decode.Item1, decode.Item2)); } double F = values.Sum(); List<double> ps = new List<double>(); foreach(var value in values) { ps.Add(value / F); } List<double> cdf = new List<double>(); cdf.Add(0); //在开始填上0,方便后面编程 for(int i = 0; i < ps.Count; i++) { cdf.Add(ps[i] + cdf[i]); } for(int i = 0; i < _popSize; i++) { double r = RandomNumber(0, 1); int j = 0; for(j = 0; j < cdf.Count - 1; j++) { if(r >= cdf[j] && r < cdf[j + 1]) { break; } } result.Add(chs[j]); } return result; }
8. 代码实现与结果
8.1 实验代码
实验采用C#语言,共有3个代码文件
(1) Program.cs是程序入口。
(2) GeneticAlgorithmSimple是整个遗传算法的实现类。
(3) Chromosome是个体类。
8.2 实验参数
交叉遗传的概率_pCrossover = 0.25
突变的概率_pMutation = 0.01
一代种群中个体的数目_popSize = 10
最大迭代次数_maxGeneration = 1000
结果的精度PRECISION = 0.0001
8.3 实验结果
结果的进化过程如下所示,其中Local表示单代最优适应度随着代数的变化,Global表示全局适应度随着代数的变化。
大约在第100代时,就已经达到了全局最优,此时:
X1 = 11.6281808020813
X2 = 5.72503128147221
F(X1, X2) = 38.8439131372254
所有源代码在Github中可以获得:https://github.com/IrisLoveLeo/GeneticAlgorithmSimple
0 条评论