前言
本文整理自卓金武《MATLAB在数学建模中的应用》
遗传算法
用以解决求目标最小(大)值的问题
1. SGA实现步骤
SGA(simple genetic algorithm,简单遗传算法)
1. 编码
编码符合计算机处理信息的原理,也方便了对染色体进行遗传、编译、突变等操作。
设某一参数的取值范围是( , ),使用长度为 的二进制编码表示该参数,则它共有 2 种不同的编码
该编码的对应关系为:
0000000000000000000 = 0 →
0000000000000000001 = 1 →
+ δ
0000000000000000010 = 2 →
+ 2δ
……….
1111111111111111111 = 2 -1 →
则:
2. 解码
解码的目的是为了将不直观的二进制数据串还原成十进制。
设某一个体的二进制编码为 b b b ….b b b ,则对应的解码公式为:
例如,设有参数 ∈ [2,4],现用 5 位二进制数对 进行编码,可得 2 = 32 条染色体:
00000,00001,00010,00011,00100,00101,00110,00111,
01000,01001,01010,01011,01100,01101,01110,01111,
10000,10001,10010,10011,10100,10101,10110,10111,
11000,11001,11010,11011,11100,11101,11110,11111,
对于任意二进制数据串只要代入译码公式,就可以得到对应的解码,
如 = 10101,它对应的十进制为:
则对应参数的 值为:
遗传算法的编码和解码在宏观上可以对应生物的基因型和表现型,在微观上可以对应 DNA 的转录和翻译两个过程
3. 交配
“交配运算”是使用单点或多点进行交叉的算子。
首先用随机数产生一个或多个交配点位置,然后两个个体在交配点位置交换部分基因码,形成两个子个体。
例如,有两条染色体 S = 01001011,S = 10010101 交换其后 4 位基因,则交换后的 S’ = 01000101,S’ = 10011011 可以被看成是原染色体 S 和 S 的子代染色体
4. 突变
“突变运算”是使用基本位进行基因突变。
为了避免在算法迭代后期出现种群过早收敛,对于二进制的基因码组成的个体种群,实现基因码的小几率翻转,对于二进制编码即 0 变为 1,而 1 边变为 0。
例如,将染色体 S = 11001101 第 3 位上的 0 变为 1,即 S‘ = 11101101。S’ 可以被看做是原染色体 S 的子代染色体
5. 倒位
倒位是指一个染色体某区段正常排列顺序发生 180° 的颠倒,造成染色体内的 DNA 序列重新排列。染色体上的区段可能发生一次又一次的倒位,且通过自交出现不同的倒位纯合体,致使它们与其原来的物种不能交配,形成生殖隔离,结果产生新族群或变种。
倒位运算是与倒位概念相似的运算规则,例如,染色体 S’ 就是 S 经过倒位运算后得到的染色体基因编码,S = 11110000,S’ = 00001111
6. 个体适应度评估
自然界中能够适应环境的生物有更多的机会存活下来,遗传算法依照与个体适应度成正比的几率决定当前种群中各个个体遗传到下一代群体中的机会。个体适应度大的个体更容易被遗传到下一代。
通常,求目标函数最大值的问题可以直接把目标函数作为检测个体适应度大小的函数
7. 复制
复制运算根据个体适应度大小决定其下代遗传的可能性。
若设种群中个数总数为 N,个体 的适应度为 ,则个体 被选取的几率为:
当个体复制的几率决定后,再产生 [0,1] 区间的均匀随机数来决定哪些个体参加复制。若个体适应度高,则被选取几率 就大,则可能被多次选中,它的遗传基因就会在种群中扩散;若个体的复制几率小,则会被逐渐淘汰。
2. 程序设计流程
1. 遗传算法的程序设计伪代码
begin
t = 0; % 遗传代数
初始化 P(t); % 初始化种群或染色体
计算 P(t) 的适应值;
while (不满足停止准则) do
begin
t = t+1;
从 P(t-1) 中选择 P(t); % 选择
重组 P(t); % 交叉和变异
计算 P(t) 的适应度;
end
end
2. 遗传算法的参数设计原则
为了避免种群的早熟,参数的设计一般遵从以下原则:
种群的规模
当种群规模太小,会出现近亲交配产生病态基因,且妨碍种群中有效模式的正确传播,使得种群进化不能按照模式定理产生所预测的期望数量;种群规模太大,结果难以收敛且浪费资源,稳健性下降。建议值为 0~100
变异概率
当变异概率太小时,种群的多样性下降太快,容易导致有效基因的迅速丢失且不容易修复;当变异概率太大,高阶模式被破坏的概率也随之增大。变异概率一般取0.0001~0.2
交配概率
交配概率太大容易破坏已有的有利模式,随机性增大,容易错失最优个体;交配概率太小不能有效更新种群。交配概率一般取0.4~0.99
进化代数
进化代数太小,算法不容易收敛,种群还没有成熟;进化代数太大,算法已经熟练或者种群过于早熟增加时间开支和资源浪费。进化代数一般取 100~500
种群初始化
初始种群的生成是随机的。在初始种群的赋予之前,尽量搜索一个大概的区间估计,以免初始种群分布在远离全局最优解的编码空间,导致遗传算法的搜索范围受到限制,同时也为算法减轻负担
3. 适应度函数的调整
在遗传算法运行的初期阶段
希望在遗传算法运行的初期阶段,算法能够对一些适应度较高的个体进行控制,降低其适应度与其他个体适应度之间的差异程度,从而限制其复制数量,以维护群体的多样性
在遗传算法运行的后期阶段
希望在遗传算法运行的后期阶段,算法能对个体的适应度进行适当的放大,扩大最佳个体适应度与其他个体适应度之间的差异程度,以提高个体之间的竞争性
3. 例题
1. 源程序实现
%主程序:用遗传算法求解y=200*exp(-0.05*x).*sin(x)在[-2 2]区间上的最大值
clc;
clear all;
close all;
global BitLength
global boundsbegin
global boundsend
bounds=[-2 2];%一维自变量的取值范围
precision=0.0001; %运算精度
boundsbegin=bounds(:,1);
boundsend=bounds(:,2);
%计算如果满足求解精度至少需要多长的染色体
BitLength=ceil(log2((boundsend-boundsbegin)' ./ precision));
popsize=50; %初始种群大小
Generationnmax=12; %最大代数
pcrossover=0.90; %交配概率
pmutation=0.09; %变异概率
%产生初始种群
population=round(rand(popsize,BitLength));
%计算适应度,返回适应度Fitvalue和累积概率cumsump
[Fitvalue,cumsump]=fitnessfun(population);
Generation=1;
while Generation<Generationnmax+1
for j=1:2:popsize
%选择操作
seln=selection(population,cumsump);
%交叉操作
scro=crossover(population,seln,pcrossover);
scnew(j,:)=scro(1,:);
scnew(j+1,:)=scro(2,:);
%变异操作
smnew(j,:)=mutation(scnew(j,:),pmutation);
smnew(j+1,:)=mutation(scnew(j+1,:),pmutation);
end
population=smnew; %产生了新的种群
%计算新种群的适应度
[Fitvalue,cumsump]=fitnessfun(population);
%记录当前代最好的适应度和平均适应度
[fmax,nmax]=max(Fitvalue);
fmean=mean(Fitvalue);
ymax(Generation)=fmax;
ymean(Generation)=fmean;
%记录当前代的最佳染色体个体
x=transform2to10(population(nmax,:));
%自变量取值范围是[-2 2],需要把经过遗传运算的最佳染色体整合到[-2 2]区间
xx=boundsbegin+x*(boundsend-boundsbegin)/(power((boundsend),BitLength)-1);
xmax(Generation)=xx;
Generation=Generation+1
end
Generation=Generation-1;
Bestpopulation=xx
Besttargetfunvalue=targetfun(xx)
%绘制经过遗传运算后的适应度曲线。一般地,如果进化过程中种群的平均适应度与最大适
%应度在曲线上有相互趋同的形态,表示算法收敛进行得很顺利,没有出现震荡;在这种前
%提下,最大适应度个体连续若干代都没有发生进化表明种群已经成熟。
figure(1);
hand1=plot(1:Generation,ymax);
set(hand1,'linestyle','-','linewidth',1.8,'marker','*','markersize',6)
hold on;
hand2=plot(1:Generation,ymean);
set(hand2,'color','r','linestyle','-','linewidth',1.8,...
'marker','h','markersize',6)
xlabel('进化代数');ylabel('最大/平均适应度');xlim([1 Generationnmax]);
legend('最大适应度','平均适应度');
box off;hold off;
%子程序:新种群交叉操作,函数名称存储为crossover.m
function scro=crossover(population,seln,pc);
BitLength=size(population,2);
pcc=IfCroIfMut(pc); %根据交叉概率决定是否进行交叉操作,1则是,0则否
if pcc==1
chb=round(rand*(BitLength-2))+1; %在[1,BitLength-1]范围内随机产生一个交叉位
scro(1,:)=[population(seln(1),1:chb) population(seln(2),chb+1:BitLength)];
scro(2,:)=[population(seln(2),1:chb) population(seln(1),chb+1:BitLength)];
else
scro(1,:)=population(seln(1),:);
scro(2,:)=population(seln(2),:);
end
%子程序:计算适应度函数, 函数名称存储为fitnessfun
function [Fitvalue,cumsump]=fitnessfun(population);
global BitLength
global boundsbegin
global boundsend
popsize=size(population,1); %有popsize个个体
for i=1:popsize
x=transform2to10(population(i,:)); %将二进制转换为十进制
%转化为[-2,2]区间的实数
xx=boundsbegin+x*(boundsend-boundsbegin)/(power((boundsend),BitLength)-1);
Fitvalue(i)=targetfun(xx); %计算函数值,即适应度
end
%给适应度函数加上一个大小合理的数以便保证种群适应值为正数
Fitvalue=Fitvalue'+230;
%计算选择概率
fsum=sum(Fitvalue);
Pperpopulation=Fitvalue/fsum;
%计算累积概率
cumsump(1)=Pperpopulation(1);
for i=2:popsize
cumsump(i)=cumsump(i-1)+Pperpopulation(i);
end
cumsump=cumsump';
%子程序:新种群变异操作,函数名称存储为mutation.m
function snnew=mutation(snew,pmutation);
BitLength=size(snew,2);
snnew=snew;
pmm=IfCroIfMut(pmutation); %根据变异概率决定是否进行变异操作,1则是,0则否
if pmm==1
chb=round(rand*(BitLength-1))+1; %在[1,BitLength]范围内随机产生一个变异位
snnew(chb)=abs(snew(chb)-1);
end
%子程序:判断遗传运算是否需要进行交叉或变异, 函数名称存储为IfCroIfMut.m
function pcc=IfCroIfMut(mutORcro);
test(1:100)=0;
l=round(100*mutORcro);
test(1:l)=1;
n=round(rand*99)+1;
pcc=test(n);
%子程序:新种群选择操作, 函数名称存储为selection.m
function seln=selection(population,cumsump);
%从种群中选择两个个体
for i=1:2
r=rand; %产生一个随机数
prand=cumsump-r;
j=1;
while prand(j)<0
j=j+1;
end
seln(i)=j; %选中个体的序号
end
%子程序:将2进制数转换为10进制数,函数名称存储为transform2to10.m
function x=transform2to10(Population);
BitLength=size(Population,2);
x=Population(BitLength);
for i=1:BitLength-1
x=x+Population(BitLength-i)*power(2,i);
end
%子程序:对于优化最大值或极大值函数问题,目标函数可以作为适应度函数
%函数名称存储为targetfun.m
function y=targetfun(x); %目标函数
y=200*exp(-0.05*x).*sin(x);
可得结论:
- 最大适应度值在进化过程中变化幅度不大
- 平均适应度值在进化过程中快速增加
2. 工具箱实现
%主程序:本程序采用遗传算法接力进化,
%将上次进化结束后得到的最终种群作为下次输入的初始种群
clc;
close all;
clear all;
%进化的代数
T=100;
optionsOrigin=gaoptimset('Generations',T/2);
[x,fval,reason,output,finnal_pop]=ga(@ch14_2f,2,optionsOrigin);
%进行第二次接力进化
options1=gaoptimset('Generations',T/2,'InitialPopulation',finnal_pop,...
'PlotFcns',@gaplotbestf);
[x,fval,reason,output,finnal_pop]=ga(@ch14_2f,2,options1);
Bestx=x
BestFval=fval
%子函数:适应度函数同时也是目标函数,函数存储名称为ch14_2f.m
function f=ch14_2f(x)
g1=1.5+x(1)*x(2)-x(1)-x(2);
g2=-x(1)*x(2);
if(g1>0|g2>10)
f=100;
else
f=exp(x(1))*(4*x(1)^2+2*x(2)^2+4*x(1)*x(2)+2*x(2)+1);
end
4. 工具箱详解
1. 函数 ga
语法格式为:
[x,fval,reason] = ga(@fitnessfun,nvars,options);
其中:
- x 为经过遗传进化以后自变量最佳染色体返回值
- fval 为最佳染色体的适应度
- reason 为算法停止的原因
- @fitnessfun 为适应度句柄函数
- nvars 为目标函数自变量的个数
- options 为算法的属性设置,该属性通过 gaoptimset 赋予
2. 函数 gaoptimset
语法格式为:
options = gaoptimset('attribute1','value1','attribute2','value2',...);
gaoptimset 实现的功能为,设置遗传算法的参数和句柄函数,下表列出了常用的 11 种属性
属性名 | 默认值 | 实现功能 |
---|---|---|
PopInitRange | [0,1] | 初始种群生成区间 |
PopulationSize | 20 | 种群规模 |
CrossoverFraction | 0.8 | 交配概率 |
MigrationFraction | 0.2 | 变异概率 |
Generations | 100 | 超过进化代数时算法停止 |
TimeLimit | Inf | 超过运算时间限制时算法停止 |
FitnessLimit | -Inf | 最佳个体等于或小于适应度阈值时算法停止 |
StallGenLimit | 50 | 超过连续代数不进化则算法停止 |
StallTimeLimit | 20 | 超过连续时间不进化则算法停止 |
InitialPopulation | [ ] | 初始化种群 |
PlotFcns | [ ] | 绘图函数,可供选择的有 @gaplotbestf 等 |
由于遗传算法本质是一种启发式的随机运算,算法程序经常重复运行多次才能得到理想结果。
因此可以将前一次运算得到的最后种群作为下一次运算的初始种群
[x,fval,reason,output,final_pop] = ga(@fitnessfun,nvars);
options = gaoptimset('InitialPopulation',final_pop);
[x,fval,reason,output,final_pop2] = ga(@fitnessfun,nvars,options);
最后一个输出变量 final_pop 就是上次运行得到的最后种群,再将其作为函数 ga 的初始种群
3. 适应度函数设计
1.当求解目标函数最小值的问题,可直接令目标函数为适应度函数
function f = fitnessfcn(x);
f = f(x);
2.如果有约束条件,求解函数的最小值问题,若目标函数作为适应度函数,则最终得到的目标函数值为 fval
function f = fitnessfcn(x);
if (x <= -1 |x > 3) % 假设约束条件是 x<= -1 且 x>3
f = inf;
else
f = f(x);
end
3.如果有约束条件,求解函数的最大值问题,若目标函数作为适应度函数,则最终得到的目标函数值为 -fval,而不是 fval
function f = fitnessfcn(x);
if (x <= -1 |x > 3) % 假设约束条件是 x<= -1 且 x>3
f = inf;
else
f = -f(x);
end