优化问题一般可以分为两类:线性的、非线性的。
我之前接触到的通过调用Gurobi等求解器来进行求解的优化问题,其实都是线性优化问题,就算原问题不是,也会通过一些线性化的手段加以转换。这类问题理论上是可以得到唯一解的。
如果是一些强非线性的问题,难以通过线性化手段进行转换,那么就有必要利用所谓的智能算法来进行求解了。这类方法可以认为是在按照某种规律不断试探,逐渐逼近最优解,但最终得到的结果其实未必是全局意义上真正的最优。所以我们也常听到“陷入局部最优”等说法。
当我投身于鲁棒优化、随机优化等具体知识的学习中,习惯了文献中采用LR、Benders分解法等方法,最后调用求解器进行求解;忽然看到采用 遗传算法、粒子群算法 的文献,顿时生出不适应的感觉。自问“为什么文献采用智能算法”这个问题,居然回答不上来。所以再一次感叹学习过程中往往容易过于深入细节以至于无法窥见全貌。
虽然久仰遗传算法大名,但实际上最近才开始真正接触。一些基本的概念我相信大家去翻看一些论文、博客都很容易理解,此处就不再赘述。
遗传算法的具体步骤大致分为以下几项:
最后两项是不断循环的,直到满足结束(收敛)条件。
接下来我主要就我在实际应用过程中遇到的一些问题来进行分点阐述。
这个问题似乎有点“傻”,但却是我纠结过的问题(可能是因为之前做的优化都是调用求解器,以至于认为世上所有的优化都得依赖求解器,说到底还是“一叶障目,不见泰山”)。答案是不用,求解过程就是程序的循环过程。
这个是我初学最疑惑的问题。线性优化问题,用MATLAB+yalmip求解的话标准流程就是:
但是用遗传算法我一下子没找到我该往哪里写我的约束条件,经过在各种查询,简单来说可以有两种:
因为我采用的是matpower工具包进行交流潮流计算,而交流潮流方程约束以及电力平衡约束实际上已经在matpower进行潮流计算的过程中加以考虑了,所以在自己编写程序的过程中潮流方程约束和电力平衡约束并没有显式地体现出来,那就只剩下机组出力约束了。利用机组出力约束进行筛选的程序很简单,就是一条 if 语句。
if (sum(results.gen(:,2)>=pgMin)==NG)&&(sum(results.gen(:,2)<=pgMax)==NG)
%计算适应度函数值
else
%适应度函数值置0
这部分我将按照下面四点分别进行。
大家可能对于“二进制编码”较为熟悉,每个个体以二进制数的形式来表示,但是由于我进行的是有功优化,自变量是连续变量,这种方式可能并不适用。
//二进制编码
//变异
0 1 0 1 0 1
——>
0 0 0 1 0 1//第2位基因发生变异
//进化
0 1 0 1 0 1
1 0 1 0 1 0//父代两个个体
——>//后两位基因发生交叉
0 1 0 1 1 0
1 0 1 0 0 1//子代两个个体
所以此处我采用的是 实数编码,直接以机组出力作为自变量。乍一看好像和没编码有点像哈哈哈~
遗传算法具体相关内容大家可以去查询资料,我当时看的是下面这篇论文,应该解答了我的大部分疑问,作为入门参考资料还是不错的。
[1]黄志明. 电力系统有功优化算法研究比较[D].南昌大学,2013.
前面有介绍两种,我采用的是第二种。
我是按照字面上的理解选定适应度函数的:适应度函数值越大,适应能力越强。而有功优化的目标一般都是花费最小,所以在对花费取倒数即可作为适应度函数,具体如下式。
f = 1 ∑ i = 1 n ( a i P i 2 + b i P i + c i ) f = {1 \over {\sum\limits_{i = 1}^n {({a_i}P_i^2 + {b_i}{P_i} + {c_i})} }} f=i=1∑n(aiPi2+biPi+ci)1
但实际上直接以花费作为适应度函数也没问题,只要在后面的编程过程中不要把逻辑搞反就可以。
这就是随机数生成的过程,具体如下图,生成机组最大最小出力范围之间的随机数。
pg=(pgMax-pgMin).*rand(NG,size)+pgMin;
采用锦标赛选择法。随机选择两个个体进行比较,适应值大的个体被选中。这种策略在一定程度上能够避免超级个体对算法的影响,使得搜索过程不会出现过早收敛和停滞现象。
%个体选择——锦标赛选择法
M=zeros(NG+3,size);%用来存储优胜者的中间矩阵
for j=1:size
A=randperm(size,2);%随机获取1~N范围的两个数
if pg(4,A(1))<=pg(4,A(2))%适应度大的保留下来
M(:,j)=pg(:,A(2));
else
M(:,j)=pg(:,A(1));
end
end
pg=M;%把优胜者种群作为新种群
进化的方式本身是多种多样的,不同的进化方式适用于不同的情境,只是简单应用的话,我个人认为随便选取一种可用的进化方式即可,不必过于纠结。
%交叉
proc=0.75;%交叉概率——从所有个体中取75%进行交叉操作
NC=floor(proc*size);%交叉操作的个体数
NC=NC-mod(NC,2);%保证是偶数
tempCross=randperm(size,NC);%执行交叉操作的个体
pointCross=randperm(NG,1);%随机生成交叉点
alpha=rand(pointCross,1);
for k=1:0.5*NC
pgCross1=alpha.*pg(1:pointCross,2*k-1)+(1-alpha).*pg(1:pointCross,2*k);
pgCross2=alpha.*pg(1:pointCross,2*k)+(1-alpha).*pg(1:pointCross,2*k-1);
pg(1:pointCross,2*k-1)=pgCross1;
pg(1:pointCross,2*k)=pgCross2;
end
变异包括选取变异个体,选择变异方式。变异个体的选取一般就是选定变异率,然后随机获取变异个体;对于二进制编码,变异方式一般就是0变1,1变0,对于实数编码,变异方式可以是按照一定步长随机增减。
%变异——每次取一定量个体进行变异
prom=0.05;
NM=floor(prom*size)+1;%变异个体数,floor为取整函数
idM=randperm(size,NM);%变异个体编号
pointMu=randperm(NG,1);%随机生成变异位置
tempM=rand(1);%变异方向随机
for j=idM
if tempM>0.5
pg(pointMu,j)=pg(pointMu,j)+0.01*pgMax(pointMu);
else
pg(pointMu,j)=pg(pointMu,j)-0.01*pgMax(pointMu);
end
end
其实遗传算法本身并没有很多复杂的公式,难以理解的概念,反倒是自己一些固有的想法束缚了自己的思路。下一篇就详细讲解一下具体代码和算例。
t o b e c o n t i n u e d . . . to\ be\ continued... to be continued...