通过一个简单例子看懂遗传算法,附MATLAB代码
Posted 舒泱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通过一个简单例子看懂遗传算法,附MATLAB代码相关的知识,希望对你有一定的参考价值。
目录
基本概念
个体(染色体):一个个体就是一条染色体。一个个体通常代表待求解问题的一个可行解,常用一串数字表示,数字串中的一位对应一个基因。
种群:个体的集合。集合内的个体数称为种群大小。
种群大小 M:一般10<M<200。
交叉概率 Pc:Pc越大,算法在搜索区域的搜索能力越强。Pc取得过大,算法收敛速度慢;Pc取得过小,算法容易陷入局部最优点。一般0.4<Pc<1。
变异概率Pm:Pm过大,容易丢失最优解,甚至使算法趋于纯粹的随机搜索。Pm过小,可能使算法过早地收敛于局部最优点。通常0.005<Pm<0.1
选择算子:优良基因能够有更多机会被遗传复制到下一代。常用的选择算子有轮盘赌选择、排序选择、锦标赛选择、精英保留选择、随机竞争选择。
算法基本思想
初始阶段,遗传算法将问题解转化成一串编码,从一组随机产生的表示问题解的编码初始群体开始搜索,以根据问题特征确定的适应度函数为依据,使用选择策略在当前种群中选择个体,通过交叉和变异来产生下一代种群。如此模仿生命的进化过程一代代演化下去,直到满足期望的终止条件或进化到一定代数为止。
- 第一步,编码。对个体进行编码,根据实际问题选择编码方式,例如二进制编码、十进制编码。个体记为T。
- 第二步,初始化种群。种群大小设为M,种群初始化为P=[T1,T2,……,TM]。初始化交叉概率为Pc,变异概率为Pm。
- 第三步,交叉、变异、种群合并。对种群进行交叉、变异处理,在进行交叉操作时先打乱种群个体顺序,种群以概率Pc进行交叉,以概率Pm进行变异。交叉变异后的子种群记为Q。合并原种群P和子种群Q,合并种群记为C,C=P∪Q。合并种群C中的个体数目为2M。
- 第四步,计算个体适应度值,优选个体组成下一代种群。 针对待求问题的目标确定相应适应度函数,用适应度函数对个体的适应度值进行计算,适应度值记为F。然后采用选择算子,得到下一代种群。
- 第五步,最后判断是否达到迭代结束条件。 如果是,进入第六步,否则进入第三步。
- 第六步,选择当前种群中的最优个体,作为近似最优解。
选择算子
一、轮盘赌选择法(赌轮选择法)
比例选择方法,个体被选择的概率与其适应度值大小成正比。种群中的所有个体按照其适应度值占总适应度值的比例组成面积为1的一个圆盘,指针停止转动后,指向的个体将被复制到下一代适应度值越大的个体越容易被选中。
Pi=Fi/(F1+…+Fi…+Fn),Fi是第i个个体的适应度值
PPi=P1+……+Pi,PPi是累计概率
转轮时,产生一个0~1之间的随机数r,当PPi-1<=r<PPi时,就选择第i个个体。
二、锦标赛选择法
打乱合并种群C中个体的顺序,通过两两比较适应度值F,保留F值大的个体,淘汰F值小的个体,得到下一代种群。
一个最最简单的例子
例子:用遗传算法求出最大的5位二进制数。
问题过于简单,请各位不要嫌弃,这个问题只是为了举例。
分析
显然,最大的5位二进制数是11111,这个我们都知道,如何用遗传算法求出最大的5位二进制数呢?
首先根据问题确定个体编码方式、适应度函数等。
- 个体:5位二进制数,例如00010、10101、01001……
- 编码方式:肯定是二进制编码
- 种群大小、交叉概率、变异概率:这些都是根据经验设置的,这里种群大小M=50,交叉概率Pc=1,变异概率Pm=0.01。
- 选择算子:就用锦标赛选择法吧,但是用其他的也行。打乱合并种群C中个体的顺序,通过两两比较适应度值F,保留F值大的个体,淘汰F值小的个体,得到下一代种群。
- 适应度函数:F=16*a4 + 8*a3 + 4*a2 + 2*a1 + a0,a4是5位二进制数的最高位,依次类推,a0是最低位。一个个体的适应度值就是这个二进制数本身的大小,例如个体“00000”的适应度值F=0,个体“11111”的适应度值F=31,这个二进制数越大,适应度值越大,越有可能遗传复制到下一代。
图解整个过程
编码。上面分析里已经编过了,每个个体都是一串5位二进制数
初始化种群。随机生成50个 5位二进制数,这50个个体作为初始种群。
交叉。 种群共有50个个体,先随机打乱这50个个体的顺序,然后1号个体和2号个体交叉,得到两个新的个体,如图所示,再3号和4号个体交叉,以此类推。最后,父代种群50个旧个体经过两两交叉,就能得到50个新个体。
种群以概率Pc进行交叉的程序实现:随机生成一个0~1之间的数p,如果p<=交叉概率Pc,进行交叉操作,否则不进行交叉操作。
变异。 对上一步得到的交叉后的50个新个体的每位基因进行遍历,从第1个个体的第1位基因开始,以概率Pm进行变异,随机生成一个0~1之间的数p,如果p<=变异概率Pm,该位基因进行变异操作,否则不进行变异操作。二进制编码方式的变异很简单,就是直接取反。以下是某个体的某个基因变异示意图:
种群合并。交叉变异后的子种群记为Q(也就是50个新个体)。合并原种群P(也就是原来50个旧个体)和子种群Q,合并种群记为C,C=P∪Q。合并种群C中的个体数目为100个(包含50个旧个体和50个新个体,新个体是由旧个体交叉变异得来的)。
计算个体适应度值,优选个体组成下一代种群。打乱合并种群C中个体的顺序,通过两两比较适应度值F,保留F值大的个体,淘汰F值小的个体,得到下一代种群。得到下一代种群。
在下图可以看到,F=10的个体 与 F=24的个体比较,F=10的个体被淘汰,F=24的个体被留下来了,合并种群中的100个个体这样两两比较,最后留下来50个个体,这50个个体的适应度值都较大。
这是一次迭代,这50个个体再作为父代种群拿去交叉变异又能得到100个个体,再从100个个体中选50个适应度较大的个体,接着循环这些步骤。每次进化(迭代),适应度值大的个体留下来了,一直迭代,最后种群中适应度大的个体就越来越多。
判断是否达到迭代结束条件。 个体11111被保留下来的概率很大,随着迭代,种群中为11111的个体越来越多,最后,种群中可能90%甚至更多的个体都是11111,当种群中相同的个体达到一定百分比时,可以结束迭代,选出种群中适应度最大的个体,那就是我们求的近似最优解了 。
本例的MATLAB程序
主程序 main.m
%% 用遗传算法求最大的5位二进制数
% date 2021/05/25 ; code by Wang Yuan
% input:无
% output:最大的5位二进制数
%% 清空环境变量
clc
clear
close all;
%% 初始化
dim=5; % 个体包含5位基因
popsize = 50; % 种群大小
genmax = 50; % 最大迭代次数
Pm = 0.01; % 变异概率
Pc = 1; % 交叉概率
% expect_fit = ; % 期望的适应度值,大于该值时结束迭代
Parents = round(rand(popsize,dim)); %随机生成初始种群,popsize个个体,先得到随机数后四舍五入
%% 以下这两是拿来画图用的
every_generation_fitness = zeros(1, genmax+1); % 保存每一代的个体平均适应度值
repeat_num_all = zeros(1, genmax); % 每一代种群中重复个体的数量
current_generation_fitness = zeros(1,popsize);
for g = 1 : popsize
current_generation_fitness(g) = fun_fitness(Parents(g,:));
end
every_generation_fitness(1,1) = sum(current_generation_fitness(1,:))/50;
%% 迭代开始
for t = 1 : genmax
[population]=fun_cross(Parents,Pc); % 交叉
[Children]=fun_mutation(population, Pm); % 变异
Combination = [Parents;Children]; % 合并子代和父代
[Parents,fit_parent]=fun_TournamentSelection(popsize,Combination); % 锦标赛选择,适应度计算在优选内完成
every_generation_fitness(1,t+1) = sum(fit_parent(1,:))/50; % 父代种群适应度和÷种群大小=个体平均适应度值
%以下列出两种结束迭代的条件
% 第一种
% if every_generation_fitness(1,t)>expect_fit
% break; % 若种群所有个体的总适应度>expect_fit,结束迭代
% end
% 第二种
data1=sortrows(Parents); % 种群中相同的个体
[~,r]=unique(data1,'rows');
[dataUnique,r1]=unique(data1,'rows','last');
repeat_num_all(1,t) = max(r1-r+1);
% if repeat_num_all(1,t)>0.8*popsize
% break; % 若种群相同个体个数>80%,结束迭代
% end
end
% 绘图
figure(1);
x =0:1:genmax;
plot( x , every_generation_fitness); % x为向量时,则以x为横坐标,y中元素为纵坐标显示,若x与y为同维矩阵,则将x和y对应位置上的元素作为横纵坐标绘制图像
xlabel('迭代次数'); % 标识横坐标
ylabel('个体平均适应度值'); % 标识横坐标
% 绘图
figure(2);
x2 =1:1:genmax;
plot( x2 , repeat_num_all); % x为向量时,则以x为横坐标,y中元素为纵坐标显示,若x与y为同维矩阵,则将x和y对应位置上的元素作为横纵坐标绘制图像
xlabel('迭代次数'); % 标识横坐标
ylabel('种群中相同个体的个数'); % 标识横坐标
%% 得到最优个体
fit = zeros(1,popsize);
for g = 1 : popsize
fit(g) = fun_fitness(Parents(g,:));
end
[max_p,pos] = max(fit);
max_p % 输出最小的适应度值
best = Parents(pos,:) % 输出当前种群中的最优个体
适应度函数 fun_fitness.m
%% 本函数完成求个体的适应度值
function fitness = fun_fitness(row)
fitness=16*row(1)+8*row(2)+4*row(3)+2*row(4)+row(5);
交叉函数fun_cross.m
function [child]=fun_cross(parent,pcross)
[popSize,dim]=size(parent);
t=randperm(popSize);
parent=parent(t,:); %随机打乱个体位置
child=parent;
temp1=ones(dim);
for i=1:popSize/2
if rand()>pcross
continue;
end
pos=round(rand()*(dim-1))+1; % 随机选择交叉点
temp1(1:pos)=child(2*i,1:pos);
child(2*i,1:pos)=child(2*i-1,1:pos);
child(2*i-1,1:pos)=temp1(1:pos);
end
变异函数fun_mutation.m
function [population]=fun_mutation(population, pm)
[popSize,dim]=size(population);
for i=1:popSize
for j=1:dim
r=rand();
%%对个体某变量进行变异
if r<=pm
population(i,j)=~population(i,j);
end
end
end
选择函数fun_TournamentSelection.m
function [parent,fit_parent]=fun_TournamentSelection(popSize,combine)
t=randperm(popSize*2);
combine=combine(t,:); %随机打乱个体位置
fit = zeros(1,popSize*2);
fit_parent = zeros(1,popSize);
for g = 1 : popSize*2
fit(g) = fun_fitness(combine(g,:));
end
parent = zeros(popSize,size(combine,2)); % 预先分配大小
for i=1:popSize
if fit(2*i-1)>fit(2*i)
parent(i,:)=combine(2*i-1,:);
fit_parent(1,i) = fit(2*i-1);
else
parent(i,:)=combine(2*i,:);
fit_parent(1,i) = fit(2*i);
end
end
程序运行结果:得到的解为11111,这个个体的适应度值为31。
下图是种群个体平均适应度值随着迭代次数的变化曲线,可以看到,迭代到8次左右,算法收敛,种群中个体的适应度值都为31。
下图是种群中相同个体的数量随着迭代次数的变化曲线,可以看到,迭代到8次左右,算法收敛,种群中相同个体数量为50,也就是种群里所有个体都相同。
结束迭代的条件通常有:
- 迭代到一定次数
- 种群个体适应度值到底某个水平
- 种群中相似的个体达到一定百分比,比如90%的个体都相同说明算法收敛了
可以结合实际问题选择结束迭代的条件。
十进制编码的MATLAB程序
有的问题需要用十进制编码,以下附上十进制编码时的交叉、变异函数,写程序时可以复制过去改改直接用。
模拟二进制交叉:
交叉函数 fun_SBXCrossoverSinglePt.m
function [child]=fun_SBXCrossoverSinglePt(parent,pcross,minRealVal,maxRealVal,eta_c)
[PopSize,Dim]=size(parent);
t=randperm(PopSize); %随机打乱个体位置
parent=parent(t,:); %随机打乱个体位置
child=parent;
for i=1:PopSize/2
if rand()>pcross
continue;
end
j=ceil(rand()*Dim);
ylow=minRealVal;
yup=maxRealVal;
y1=parent(2*i-1,j);
y2=parent(2*i,j);
r=rand();
if r<=0.5
beta=(2*r).^(1/(eta_c+1));
else
beta=(0.5/(1-r)).^(1.0/(eta_c+1.0));
end
child1=0.5*((1+beta)*y1+(1-beta)*y2);
child2=0.5*((1-beta)*y1+(1+beta)*y2);
child1=min(max(child1, ylow), yup);
child2=min(max(child2, ylow), yup);
child(2*i-1,j)=round(child1);
child(2*i,j)=round(child2);
child(2*i-1,j+1:Dim)=parent(2*i,j+1:Dim);
child(2*i,j+1:Dim)=parent(2*i-1,j+1:Dim);
end
变异函数 fun_POLmutation.m
function [population]=fun_POLmutation(population, pmut_real, minRealVal, maxRealVal, eta_m)
[PopSize,Dim]=size(population);
for i=1:PopSize
for j=1:Dim
r=rand();
%%对个体某变量进行变异
if r<=pmut_real
y=population(i,j);
ylow=minRealVal ;
yup=maxRealVal ;
delta1=(y-ylow)./(yup-ylow);
delta2=(yup-y)/(yup-ylow);
r=rand();
mut_pow=1.0/(eta_m+1.0);
if r<=0.5
xy=1.0-delta1;
val=2.0*r+(1.0-2.0*r)*(xy.^(eta_m+1.0));
deltaq=val.^mut_pow-1.0;
else
xy=1.0-delta2;
val=2.0*(1.0-r)+2.0*(r-0.5)*(xy.^(eta_m+1.0));
deltaq=1-val.^mut_pow;
end
if rand < 0.5
y=round(abs(y-deltaq*(yup-ylow)));%round(abs(y-deltaq*(yup-ylow)));
% else
% y=y-deltaq*(yup-ylow);
end
y=min(1, max(y, 0));
population(i,j)=y;
end
end
end
以上是关于通过一个简单例子看懂遗传算法,附MATLAB代码的主要内容,如果未能解决你的问题,请参考以下文章
基于Matlab的遗传算法优化BP神经网络的算法实现(附算法介绍与代码详解)