赞
踩
在对多约束、非线性问题的求解上,传统线性规划等方法往往无法有效求解(求解时间过长、无法处理非线性约束等。 进化算法是一类强有力的工具,已经在多个领域有了较为成功的应用。然而,在利用遗传算法、粒子群等等进化算法求解实际的优化问题时,还存在许多困难,具体表现为:
博主在对许多文章进行复现之后,针对上面提到的问题也是很难受。为了加快复现进度,同时也是给广大研究工作者总结一般的代码编写套路,因此发布一下我经常使用的单目标进化算法框架以及MATLAB代码。本人的研究主要集中在进化多目标优化领域,对于单目标优化问题只是个人兴趣,如有不当,可友好交流讨论。多目标领域的优化之后也会发布一些文章。
考虑一下下面的优化问题:
m
i
n
y
=
x
1
2
+
x
2
2
min\ y = x_1^2 + x_2^2
min y=x12+x22
约束条件为:
x
1
+
x
2
>
1
x
1
−
x
2
<
2
x
1
,
x
2
∈
[
−
10
,
10
]
x_1 + x_2 > 1 \\ x_1 - x_2 <2 \\ x_1,x_2 \in [-10,10]
x1+x2>1x1−x2<2x1,x2∈[−10,10]
显然,这是一个非常简单的带约束优化问题,一般只能在例子里面见到。经典的遗传算法没有考虑问题存在约束的情况。因此在求解这类带有约束的优化问题时,就需要对遗传算法进行改进,从而提高算法的能力。针对上面提到的几个问题,我们依次进行改进。
在设计种群的数据结构时,以前的遗传算法将个体编码、目标函数值、违反约束的情况其他需要记录的数据单独储存。这样就会程序写起来就会很麻烦,经常出错。并且不好进行调试。
因此,我们参考PlatEMO的数据结构,进行小小的改进。Population里面储存全部的数据。这样,在各个函数之间传递数值时,只需要传递一个struct就够了。创建struct的代码如下:
// Init.m
function Population = Init(PopSize)
% 本函数用于构造初始解
pop.decs=[];
pop.obj=[];
pop.con=[];
pop.detail=[];
Population = repmat(pop,1,PopSize);
for i=1:PopSize
tmp = 10.*rands(1,2);
Population(i).decs = tmp;
end
Population = CalObj(Population);
值得注意的是,如果我们想要将里面某一项数据全部取出,需要加中括号。例如:
与传统遗传算法不一样的是,我们精简了程序的流程。主要包括,初始化种群->(挑选父代个体->交叉变异->挑选新一代个体)。括号里面为循环体,进行迭代优化。具体可以看下面的代码。我们将 记录信息、画图等操作集中到RecordInfo()文件中,主程序更加美观。
// Main.m
clear;clc;close all
%% 参数设置
PopSize = 200;
MaxGen = 400;
plt = 1; % 运行过程是否实时画迭代优化图,默认关闭(可极大提高运行速度)
%% 初始化
Population = Init(PopSize);
ConvergenceObj = zeros(2,PopSize);
ConvergenceCon = zeros(2,PopSize);
BestSol = repmat(Population(1),1,MaxGen);
%% 开始优化求解
h = figure();
for gen = 1:MaxGen
MatingPool = TournamentSelection(2,PopSize,[Population.cons],[Population.obj]); %挑选父代
Offspring = GA(Population(MatingPool)); %进行交叉变异操作
Population = EnviornmentalSelection(Population,Offspring,gen/MaxGen); %挑选子代
RecordInfo(); % 记录迭代优化信息
end
我们研究的这个问题比较简单,只包含了两个约束,所以一旦出现什么问题,也比较好解决。但是,现实问题都是包含几十上百的约束,如果不做好管理,很容易出现混乱。因此我们加了一个约束的描述。可以更加方便的修改。具体来说,在计算目标值之后,接着计算这个个体违反约束的情况。下图代码所示:
// CalObj.m
function Population = CalObj(Population)
% 本函数用于计算种群个体的目标函数值
N = length(Population);
for i=1:N
x = Population(i).decs;
%% 计算目标函数值
f = x(1)^2+x(2)^2;
%% 计算约束违反情况
old_con = 0;
con = 0;
detail = "";
if x(1)+x(2) <= 1
con = con + 1;
end
if con ~= old_con
detail = detail+"约束1;";
old_con = con;
end
if x(1)-x(2)>=2
con = con + 1;
end
if con ~= old_con
detail = detail+"约束2;";
old_con = con;
end
if max(x)>10 || min(x)<-10
con = con + 1;
end
if con ~= old_con
detail = detail+"越界;";
old_con = con;
end
%% 封装数据
Population(i).obj = f;
Population(i).con = con;
Population(i).detail = detail;
end
例子里面目标函数和约束都比较简单,不做过多讲解。
值得注意的是,我们在这里只是判断个体是否违反约束,违反了就加1。在某些情况下,我们也可以考虑违反约束的程度。比如约束是
x
<
1
x<1
x<1。那么对于两个个体
x
=
2
x=2
x=2和
x
=
3
x=3
x=3,虽然他们都是违反约束的,但是显然后者违反的程度更深,我们倾向于选择前者。这都是约束优化的一些改进方法。在计算完目标函数之后,我们就可以观察Population的值了。
可以看到,种群决策变量、目标函数、违反约束情况一目了然。
在主程序里面,我们加入了一个父代解的挑选过程。在经典的遗传算法里面,挑选解的过程没有方向性,导致进化效果较差。参考多目标优化算法NSGA-II里面的竞标赛挑选方法,我们在改进的方法里面也同样使用这个方法来挑选父代个体。具体的过程可以描述如下:每一轮挑选中,我们选择k个个体,然后k个个体里面挑最好的,直到挑够N个。直接使用PlatEMO平台的代码,调用方法为:
// An highlighted block
MatingPool = TournamentSelection(2,PopSize,[Population.con],[Population.obj]);
这里我们设置k=2,N=种群大小。挑选准则为,先看违反约束、再看目标值。这样挑出来的个体具有竞争力,后代个体更容易进化。
在个体进化之后,我们需要进行一轮挑选,以产生进化压力,推动算法向最优值进化。这个过程可以简单的描述为优胜劣汰。对于带约束的优化问题,优劣的描述为:1)如果两个个体都违反约束,那么违反程度小的个体较好;2)如果两个个体一个违反、一个满足,那么满足约束的较好;3)如果两个个体都满足约束,那么,目标函数值小的个体较好。
值得注意的是,为了保证种群个体的多样性,如果我们简单的进行替换,那么种群很快便会全部收缩到一个小区域。这对于算法的搜索是不利的,为了解决这个问题,可以使用一对一的替换方法。也就是说,除非后代比相应的父代个体好,否则不替换。具体如下:
// EnviornmentalSelection.m
function New_Population = EnviornmentalSelection(Population,Offspring,state)
% 本函数用来挑选新的种群
N = length(Population);
New_Population = Population;
%% 基本思路如下:为了确保种群的多样性,采用一对一替换机制。只有后代表现强于父代才会发生替换。
for i=1:N
pcv = Population(i).con;
ccv = Offspring(i).con;
pf = Population(i).obj;
cf = Offspring(i).obj;
if (pcv == 0 && ccv == 0) % 采用 feasible rules 挑选新解
if pf < cf
New_Population(i) = Population(i);
else
New_Population(i) = Offspring(i);
end
else
if pcv < ccv
New_Population(i) = Population(i);
else
New_Population(i) = Offspring(i);
end
end
end
对于种群的多样性保持,有很多研究方法。在multimodal研究的基础上,有些思路非常有意思。之后也搞搞这方面的文章。水很深,把握不住啊。
在对实际问题进行求解时,很多时候都是前面大量的迭代搜索一直在找可行解。这对于计算资源有限的问题比较麻烦。并且,有时候搜索到最后都不能获得可行解。那就是求解失败。对于这个问题,可以先进行一个尝试性的求解。比如这个问题,我们运行完看一下决策变量的分布情况
可以看到,解分布在[0.4,0.75],[0.3,0.9]之间,这对于-10~10的决策空间来说,相差非常大。因此我们在生成初始解的时候可以适当的进行贪婪,比如选择50%的个体进行贪婪初始化,让他们分布在更靠近可行解的区域,那么对于求解来说,效率就会大大增加。
问题较为简单,运行就设置比较小的迭代次数和种群大小。
算法稳定收敛。最后是程序文件夹。具体代码可以点开我CSDN空间进行下载。或者私信、留言邮箱。
本次使用的例子非常简单,下期将介绍实战。随机从里面挑选问题进行分析,有兴趣的可以关注一下。
最后,博主专注于论文的复现工作,有兴趣的同学可以私信共同探讨。相关代码已经上传到资源共享,点击我的空间查看分享代码。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。