上一篇文章Python遗传/差分进化算法(geatpy库)常见报错总结了2.7版本geatpy库的常见报错,意味着我们终于能够顺利使用geatpy库辣!!但是不同于开发者提供的各种demo,日常科研中碰到的优化问题往往规模庞大,并不单单只有两三个变量、四五个公式。因此本文将进一步介绍利用geatpy库建模、优化的技巧与细节。
本文的代码样例主要为geatpy中给出的soea_demo2
一、算法选择
geatpy库以传统遗传算法为基础,开发并提供了大量的进化算法模板,以常见的遗传算法(Genetic Algorithm, GA)、差分进化算法(Differential Evolution Algorithm, DE)、非支配排序遗传算法(non-dominated sorting genetic algorithm-II, NSGA-II)为例,geatpy包含的模板有:
其中soea/moea字样的表示单目标优化/多目标优化,带有DE/GA/NSGA字样的分别对应遗传算法、差分进化算法、非支配排序遗传算法三类常见算法。不同算法模板的main文件中需要调整的参数不同,比如遗传算法主要调参对象为种群规模、迭代次数、交叉概率、变异概率,而差分进化算法主要对象为种群规模、迭代次数、交叉概率、差分因子。不同算法对不同问题的求解效果不同,可代入不同模板进行对比择优。
二、自定义模型与问题
以soea_demo2为例,自定义的问题需要在Myproblem.py中表述,主要修改的地方如下:
1、确定目标维数(一般选择了soea的话,M就为1);
2、定义目标的min/max,就是定义你的问题是取目标的最小值还是最大值;
3、定义决策变量数量,这里的数量要与步骤4、5中的变量数统一;
4、定义每个决策变量的取值范围,如果没有限制可以输入一个较大的数/较小的数。此外,这里的取值是指决策变量自带的取值范围,与步骤7中的约束属于并集关系,个人建议能在步骤4中约束的就在4里输入;
5、生成决策变量,图中x1、x2、x3被定义为决策变量,可以直接代入到目标函数的构建中。决策变量的排序与步骤4列表中的顺序对应;
6、基于决策变量构建目标函数;
7、输入约束条件。若为不等式约束,则化为 ax1 + bx2 + cx3 + d ≦ 0 的形式,并输入左边的多项式,若为等式约束,则化为 ax1 + bx2 + cx3 + d = 0 的形式,并在np.abs( ) 中输入左边的多项式。
进一步调整main.py文件中的算法参数:
1、选择算法模板;
2、设置种群规模,规模越大,前期解的多样性越高,速度越慢;
3、设置最大进化代数;
4、设置算法参数,可通过调试来选取最佳参数;
5、输出绘图,通过查看ea.optimize()的函数说明可发现,drawing有0、1、2、3四种取值,分别代表不绘图、绘制最终结果图、绘制目标函数动态图、绘制决策变量动态图。
三、大规模问题的简化技巧
geatpy提供的所有demo都是小规模简单问题,因此很多问题的表述较为简单粗暴,当面临大规模优化问题时(例如几百个决策变量、上百个约束条件等),不能死板地按照demo里的表述来构建问题。geatpy对numpy、openpyxl、xlrd等常用库的兼容性较好,可以采用xlsx辅助、多套用循环、构建列表数组等方式来规避重复性表述。
1、在大规模问题中定义变量
假设该问题有100个变量,采用soea_demo2中的变量定义方式则需要手打100行代码,输入包含100个变量的矩阵,可以采用以下模板来简化问题表述:
Dim = 100 #变量维度
varTypes = [1] * 50 + [0] * 50 #初始化varTypes(50个离散变量,50个连续变量)
################建立变量上下界矩阵,50个离散变量范围[0,1],连续变量[0,10]################
lb=[]
for i in range(100):
lb.append(0)
ub=[]
for i in range(50):
ub.append(1)
for i in range(50):
ub.append(10)
################建立变量边界包含情况的矩阵,0表示不包含,1表示包含################
lbin=[]
for i in range(100):
lbin.append(1)
ubin=[]
for i in range(100):
ubin.append(1)
定义决策变量时,假设前50个离散变量归纳为集合A,后50个连续变量归纳为集合B,若目标函数f为A元素间乘积与B元素间叠加的总和,则可用构建循环来简化模型:
def evalVars(self, Vars): # 目标函数
A = [Vars[:, [i]] for i in range(50)]
B = [Vars[:, [i+50]] for i in range(50)]
###################### 调用决策变量 #######################
a = 1
for i in range(len(A)):
a = a * A[i]
b = 0
for i in range(len(B)):
b = b + B[i]
f = a + b
2、在大规模问题中引入约束
大规模问题中的约束条件往往较多,需要进行分类、整合。例如等式约束与不等式约束的表述不同,需要区分。demo中给出的表述是在CV=np.hstack()中构造列表,并逐一输入约束条件,我们可将所有约束先分类,再采用循环进行整合,最后将列表元素传递给CV。
constraints =[]
################### 对集合B引入不等式约束:相邻变量之和不超过10 #####################
for i in range(1,50):
constraints.append(B[i] + B[i-1] - 10)
################### 对集合A引入等式约束:相邻变量不同 #####################
for i in range(1,50):
constraints.append(np.abs(A[i] + A[i-1] - 1))
################### 整合约束至CV #####################
CV = np.hstack(constraints)
四、问题建模过程中的一些坑
1、决策变量个数未知
由于启发式算法能够快速求解大规模复杂问题,geatpy往往被我用来做NP-hard问题的求解,但常规的LP、MIP、IP等问题一般不会考虑用遗传算法,而是用其他精确求解算法或者Gurobi、Cplex去做。因此在geatpy中建模的往往是一些复杂抽象、难以定义类型的问题。例如在geatpy社区中有很多人问的一个问题:
首先这个问题并不属于常见的LP、MIP、IP、MINLP等问题的范畴,在使用geatpy前要先梳理清楚优化问题的特征。本文给出求解这类问题的一种简单思路,可将该问题分解成两个子问题:1、决策变量的个数问题;2、已知变量个数下的问题。由于决策变量个数必然是一个整数,因此只要个数的取值范围不是很大,都可以通过两阶段优化去做,具体求解框架如下:
实际上就是在整个geatpy算法外面套了一层遍历Y的过程,很容易在main.py文件中修改实现。
2、建模中的判断语句
有些优化问题中往往存在判断过程,例如决策变量的不同取值范围会让函数产生差异
可在模型中用if语句来增加判断过程。但是在判断的过程中需要注意,等式判断语句的报错率很高,例如if x == 5,debug后可以发现,决策变量x在算法中是以数组的形式出现的,维度为N×1,N为种群规模,因此这种判断语句容易报错,需要规避。
五、总结
其实用geatpy库就意味着你的问题比较复杂、难以精确求解,所以demo列举的模板往往难以满足我们的建模需求,后续如果还有其他细节可以补充就再更新这个主题了,感谢阅读!