版权声明:本文为博主原创文章,转载请注明出处。
本周的任务一个是搞JavaWeb的学习,一个是搞演化计算的学习,可惜的是JavaWeb的东西没搞出来,现在还是一团乱麻,演化计算终于是写出个小程序,但是也算是对演化计算有了初步的了解,所以这次就把演化计算的心得拿出来说说,用以本周总结和搞日后的回顾。
一、演化计算的基本步骤
- 根据需求制定编码方案,我的理解中编码其实就是确定染色体的形式,这个编码方案是整个计算的基础,变异杂交都是基于编码(染色体)之上的,所以首先就应该制定合理的编码方案。
编码方案有很多种,最简单的是二进制编码,也是目前我唯一能理解的编码形式。所谓二进制编码就是用一串01的序列作为染色体,这种编码某种意义上讲和自然界真实的染色体是很想相似的,可以看做01是染色体颗粒,非常方便进行变异和交叉。二进制编码是一种很自然的编码方式。
还有gray编码,实数编码,有序位串编码结构式编码等等其他形式,编码会影响到之后的效率,其他的编码形式日后再讲。
2.确定适应函数。适应函数就是描述一个个体的好坏,用以进行选择,适应函数也分原始适应函数和标准适应函数。
3.确定选择策略。选择策略是重中之重,因为这决定了进化的方向,选择函数就是自然界中的各种灾难和竞争,只有实力强大的和幸运的(?)才能存留下来,看来果然运气也是实力的一部分。
选择策略也是多种多样的,但是关注的点不太一样,包括进化的速度,是否会陷入局部最优,群体的数目大小等等,具体的策略有繁殖池选择,转轮盘选择,线性排名选择,非线性排名选择锦标赛选择等等。
4.控制参数的选择。就是根据具体问题决定算法中种群的规模,最大代数,一些遗传操作的概率和辅助性的参数的设置,看起来是比较微小的东西,但是像秤砣一样左右着整个计算的结果。
5.遗传算子的设计。遗传算子主要包括繁殖,杂交,变异,是对于自然的进一步模拟,能够优化算法的性能,通过引入随机性来克服程序化的问题。
6.确定终止的准则。
二、一个简单的实例
f(x1,x2)=x1^2+x2^2,x1和x2是属于0~7的正整数,求f的最大值。
这种弱智问题当然一眼就可以看出答案,但是就是要用遗传算法来实现,用以熟悉相关的操作和步骤,下面开始介绍我的想法。(哦,这是用C++写的)
1.编码:这个0~7简直就是为了二进制编码设计的,对x1,x2分别用三位二进制数来表示就可以了
2.适应函数:由于是求f的最大值,就是说f越大越好,所以就直接拿f来做适应函数来评价x1,x2.
3.选择策略:本例中我用的是轮盘赌的策略,所谓轮盘赌就是让每一个个体根据某种和为1的概率被挑选,挑中了就留下,否则就淘汰,这个概率就是该个体适应值占总适应值的比例。也就是说适应值越高的越有可能被选中(当然,个体越多的也越容易被选中),但是够幸运的个体即使概率很低也有机会存留下来。被挑中存留的个体才有机会参加之后的繁殖杂交突变等等,其实就相当于自然界中的种群越是厉害的越有机会留下来,但是厉害的也可能阴差阳错就被淘汰了(比如因为站得太高被雷劈死的头狼?),比较差的也可能碰巧没被淘汰。
4.控制参数:我设置的是最多一百代,0.6的概率会交叉,0.006的概率会突变,这个突变必须控制在较低的范围,否则是无法完成进化的,因为选择了半天,留下了适应度很高的染色体,但是染色体突变率很高,执行突变时又全都给你变没了,又恢复到随机的一种情况之下,进化就相当于没有发生。
5.遗传算子:我这里有突变和交叉两个算子,突变是每一条染色体的每一个基因都有Pm的概率取反,交叉是随机设置交叉点,两个染色体有一定的概率发生交叉(单点交叉)。
1 #include<iostream> 2 #include<string> 3 #include<time.h> 4 #include<sstream> 5 using namespace std; 6 7 8 //种群总数 9 const int popSize=100; 10 //染色体长度 11 const int chromosomeSize=6; 12 //变异概率 13 const double Pm=0.001; 14 //最多代数 15 const int MaxGen=100; 16 //变异概率 17 const double Pc=0.1; 18 19 20 21 22 23 //遗传个体类 24 class individual 25 { 26 public: 27 //个体目标值 28 double ObjectValue; 29 //个体适应值 30 double FitValue; 31 //染色体编码 32 string Chromosome; 33 //构造函数 34 individual() 35 { 36 ObjectValue=0; 37 FitValue=0; 38 Chromosome="000000"; 39 } 40 }; 41 42 //进化处理类 43 class Evaluation 44 { 45 private: 46 47 //种群 48 individual Population[popSize]; 49 //进行选择 50 void SelectPop(); 51 //进行变异 52 void VaryPop(); 53 //进行杂交 54 void CrossPop(); 55 //优化 56 void OptimizePop(); 57 //初始化种群,随机构造一个群体 58 void Initialization(); 59 //找出最优和最差及平均值 60 void Statistics(); 61 //评价种群 62 void EvaluatePop(); 63 //最好个体 64 individual Best; 65 //最坏个体 66 individual Worst; 67 //最坏个体下标 68 int WorstIndex; 69 //历史最佳 70 individual HistoryBest; 71 //平均值 72 double avg; 73 74 public: 75 //构造函数,调用初始化函数 76 Evaluation(); 77 //产生下一代 78 void NextPopulation(); 79 //打印 80 void output(); 81 //代数 82 int generation; 83 }; 84 85 //构造函数,调用初始化函数 86 Evaluation::Evaluation() 87 { 88 Initialization(); 89 generation=0; 90 91 } 92 93 //初始化构造初始种群 94 void Evaluation::Initialization() 95 { 96 //对染色体进行初始化设置,逐颗粒随机赋值 97 char temp; 98 int Index=0,bitIndex=0; 99 for(;Index<popSize;Index++) 100 { 101 for(bitIndex=0;bitIndex<chromosomeSize;bitIndex++) 102 { 103 int r=rand()%2; 104 if (r==0) 105 temp='0'; 106 else 107 temp='1'; 108 Population[Index].Chromosome[bitIndex]=temp; 109 } 110 } 111 //初始化时设置历史最佳为第一个 112 HistoryBest=Population[0]; 113 //调用目标值和适应值的初始化函数,评价种群 114 EvaluatePop(); 115 Statistics(); 116 } 117 118 //评价函数,就是用函数f(x1,x2)=x1^2+x2^2,因为是数值函数,所以适应值和目标值是一致的 119 void Evaluation::EvaluatePop() 120 { 121 string num1,num2; 122 int value1,value2; 123 int Index=0; 124 for(;Index<popSize;Index++) 125 { 126 num1=Population[Index].Chromosome.substr(0,3); 127 num2=Population[Index].Chromosome.substr(3,3); 128 //二进制转化为十进制 129 stringstream ss; 130 ss<<num1; 131 ss>>value1; 132 //清空缓冲区 133 ss.clear(); 134 ss.str(""); 135 ss<<num2; 136 ss>>value2; 137 138 int a,b,c; 139 a=value1/100; 140 b=(value1-a*100)/10; 141 c=value1-a*100-b*10; 142 value1=a*4+b*2+c; 143 a=value2/100; 144 b=(value2-a*100)/10; 145 c=value2-a*100-b*10; 146 value2=a*4+b*2+c; 147 //计算适应值和目标值 148 Population[Index].FitValue=value1*value1+value2*value2; 149 Population[Index].ObjectValue=value1*value1+value2*value2; 150 } 151 } 152 153 //生成下一代 154 void Evaluation::NextPopulation() 155 { 156 SelectPop(); 157 VaryPop(); 158 CrossPop(); 159 EvaluatePop(); 160 Statistics(); 161 OptimizePop(); 162 EvaluatePop(); 163 Statistics(); 164 generation++; 165 } 166 167 //选择算子,轮盘赌 168 void Evaluation::SelectPop() 169 { 170 double FitSum=0,selection[popSize]; 171 individual newPopulation[popSize]; 172 int index=0,popindex=0; 173 //求适应值的总和 174 for(;index<popSize;index++) 175 { 176 FitSum+=Population[index].FitValue; 177 } 178 179 //确定轮盘分布 180 for(index=0;index<popSize;index++) 181 { 182 selection[index]=Population[index].FitValue/FitSum; 183 } 184 for(index=1;index<popSize;index++) 185 { 186 selection[index]=selection[index]+selection[index-1]; 187 } 188 //用轮盘进行随机选取,形成新的种群 189 for(popindex=0;popindex<popSize;popindex++) 190 { 191 double p= (rand()%100); 192 p/=100; 193 index=0; 194 while(p>selection[index]) 195 index++; 196 newPopulation[popindex]=Population[index]; 197 } 198 //将刚产生的群体替换为系统的群体 199 for(index=0;index<popSize;index++) 200 { 201 Population[index]=newPopulation[index]; 202 } 203 204 } 205 206 //杂交算子,随机选取交叉点 207 void Evaluation::CrossPop() 208 { 209 int Localtion; 210 int index=0; 211 string str1,str2,str3,str4; 212 //打乱顺序 213 for(;index<popSize;index++) 214 { 215 individual temp; 216 int r=rand()%popSize; 217 temp=Population[index]; 218 Population[index]=Population[r]; 219 Population[r]=temp; 220 } 221 //随机选取交叉点,将染色体分裂,然后交叉,得到新的染色体 222 for(index=0;index<popSize;index+=2) 223 { 224 double temp=rand()%1000/1000.0; 225 if(temp<Pc) 226 { 227 Localtion=rand()%chromosomeSize; 228 str1=Population[index].Chromosome.substr(0,Localtion); 229 str2=Population[index].Chromosome.substr(Localtion); 230 str3=Population[index+1].Chromosome.substr(0,Localtion); 231 str4=Population[index+1].Chromosome.substr(Localtion); 232 Population[index].Chromosome=str1+str4; 233 Population[index+1].Chromosome=str3+str2; 234 } 235 } 236 } 237 238 //变异算子,对所有染色体每一位的随机位置以变异概率进行变异 239 void Evaluation::VaryPop() 240 { 241 int index=0,bitindex=0; 242 string str1="0"; 243 string str2="1"; 244 245 for(;index<popSize;index++) 246 { 247 for(bitindex=0;bitindex<chromosomeSize;bitindex++) 248 { 249 double r=rand()%1000; 250 r/=1000; 251 if(r<Pm) 252 { 253 if(Population[index].Chromosome[bitindex]==str1[0]) 254 Population[index].Chromosome[bitindex]=str2[0]; 255 else 256 Population[index].Chromosome[bitindex]=str1[0]; 257 } 258 } 259 } 260 } 261 262 //优化 263 void Evaluation::OptimizePop() 264 { 265 Population[WorstIndex] = HistoryBest; 266 } 267 268 //统计 269 void Evaluation::Statistics() 270 { 271 Best=Population[0]; 272 Worst=Population[0]; 273 int index=0; 274 double sum=0; 275 for(;index<popSize;index++) 276 { 277 if(Best.FitValue<Population[index].FitValue) 278 Best=Population[index]; 279 if(Worst.FitValue>Population[index].FitValue) 280 { 281 Worst=Population[index]; 282 WorstIndex=index; 283 } 284 sum+=Population[index].FitValue; 285 } 286 if(HistoryBest.FitValue<Best.FitValue) 287 HistoryBest=Best; 288 avg=sum/popSize; 289 290 } 291 292 293 //输出文本 294 void Evaluation::output() 295 { 296 cout<<"Generation:"<<generation<<" Average:"<<avg<<" Best:"<<Best.FitValue<<" Chromosome"<<Best.Chromosome<<endl; 297 } 298 299 300 int main() 301 { 302 Evaluation eva; 303 eva.output(); 304 while(eva.generation<MaxGen) 305 { 306 eva.NextPopulation(); 307 eva.output(); 308 } 309 310 311 }
结果:
写完这个小程序之后我就在思考选择,交叉,突变的意义是什么,目前的想法是这样:选择的目的是在当前种群中寻求当前最优的,只考虑现状,留下来的趋向于当前群体的最优,但是是不是整体最优是未知的,所以又引入了交叉和突变。
交叉可以让优质基因和劣质基因在种群中变得更加均匀,方便于选择(就是留下好的去除坏的,因为好的和坏的都可能因此显得更加突出了,而平庸的在选择中并没有优势),同时也会产生新的个体。
而变异就是为了产生新个体才引入的,事实上我觉得他会降低选择的速度,因为选择出的好的个体经过突变很可能就没有之前那么好了,但是引入新的个体的意义是显然的,这可以避免局部最优。
为了更好的说明,我们走两个极端,一是只选择不变异不交叉,很快这个种群就会被目前最好的个体占据,二是选择交叉,百分百变异,那么得到的群体几乎就是一个全新的群体了,选择已经失去了留下优质基因淘汰劣质基因的能力因为很快又会到原点,选了半天和没选一样。所以我目前认为选择是为了获得局部最优,交叉和变异是为了一定程度上扩充这个群体的基因库,努力向全局最优靠近,交叉和突变都会降低选择的速度,因为被选择留下的个体发生了变化。
附上不同变异概率的进化结果:
Pm=0
Pm=0.001
Pm=0.01
Pm=0.1
Pm=0.3
0.01以下的还算是进化了,但是随着Pm的增大,进化到最优的状态需要的代数也越来越多(即进化速度变慢了),0.1之后的基本上已经陷入无序的状态,选择已经没有用了。
改变了Pc(交叉概率),没有什么太大的影响。
下周的话尽量了解其他的编码方式,并尝试不同的选择策略,本次总结就到此为止。
时间:2016/10/22