通过一个简单例子看懂遗传算法,附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神经网络的算法实现(附算法介绍与代码详解)

遗传算法

基于Matlab用遗传算法求一元函数最值问题(附源码)

基于遗传算法的BP神经网络在汇率预测中的应用研究(Matlab代码实现)

优化算法量子遗传优化算法含Matlab源码 1123期

物理应用基于matlab遗传算法LQR控制器含Matlab源码 1121期