目录
前言
多目标遗传算法
多目标的优化结果
Pareto曲线及分层处理
拥挤距离
代码实现
优化结果
最后的话
在很多的工程实践问题中,往往是多输入多输出的。而且最有意思的事情在于:多个输出指标总是互相矛盾的。把其中一个提高了,另外一个就会受到影响,顾此失彼。基于这样一种应用需求,单目标的遗传算法很明显已经不能满足工程实践的要求了,所以需要开拓多目标的优化算法,多目标的遗传算法就是在这样的背景下,好吧,是我自己需要进行多目标的优化,跟大背景没有太大的关联。本篇以基础知识为主,最后会加入入门级别的代码展示,只不过这个代码是我最近刚学多目标的时候编写的,写得很繁琐冗余,只能说是把多目标优化的功能实现了,代码质量很低,之后我会对代码进行优化,便于大家学习讨论。阅读这篇文章的时候认为大家已经掌握的遗传算法的基本知识了,如果单目标算法还不清楚的,建议阅读我之前的博客。
Python—标准遗传算法求函数最大值代码实现
先来推荐一篇博客,讲得还是比较清楚了:
基于DEAP库的python进化算法-7.多目标遗传算法NSGA-II
我参考着这篇博客的讲解思路,把我对多目标的理解记录一下,大家如果要看关于概念准确的定义,还是建议移步推荐的这篇博客。
首先,需要讨论的是结果的展示问题。在单目标中,由于只有一个目标,所以我们在展示结果的时候,往往是给出的目标值的收敛曲线(有些时候也展示适应度曲线,看个人偏好了)。但是多目标优化过程不是这样的,咱以两个目标为例,分别以两个目标的结果作为坐标,得到的优化结果是什么?是一个点对吧,这一个点对于我们来说很重要,但是,由于是多目标的问题,我们很多时候就会好奇,如果我将其中一个目标做一点让步,就是它不要这么好,那另一个目标会不会变得更好呢?我们想知道它们的变化关系,所以,其实在关心这么一个点的同时,我们还想了解一条线,两个目标都还不错,但又不是最好的那条线。借一张推荐的博客里面的图说明一下这个问题。
这个图展示的就是两个优化目标f1,f2的结果,如果我们想目标值越小越好,那就是坐标上的散点越靠近左下角越好,其实在优化的过程中就会发现,我们能去评判红色圈圈中的四个点到底谁好吗?得结合实际情况去取舍对吧?那算法它没人那么聪明,它只能进行筛选,如果在编写算法的时候,没有这种保留一条最优曲线的思路,那四个点,算法就只给你留一个,最终结果展示的时候,就会遗漏很多的信息了。这个也是为什么多目标优化和单目标优化结果有本质不同的原因。
理解了结果的形式,这条曲线就顺理成章的产生了,简单地说,这条曲线就是最优解的散点连成的线,有些朋友喜欢叫最优解前沿,其实是一个意思。这里要引入分层这个概念。举个例子,比如规定的种群数量NP为50,上面那条最优解曲线上一共是7个点,那对应的7个个体认为是基因表现比较好的7个,但是迭代需要选择50个呀,7个是不够的,我们就把这7个划分为第一层,接着,把这7个剔除了,再去寻找靠近坐标左下角的散点连成第二条曲线,划分为第二层,进行迭代直到所有的散点都分层完毕了,这个过程也就结束了。之后进行选择判断的时候,就从第一层开始挑个体,一直补充到NP数量。
这个概念其实是为曲线服务的,其实理解的最好方式是去跑一遍代码,以有没有拥挤距离这个条件为变量,就知道这家伙是干啥用的了。我先用语言跟大家解释一下。比如第一条Pareto曲线上有十个点,大家先想想我们是希望这十个点靠的近好呢,还是希望它们散开好呢?想想我们画曲线的目的,不就是为了看变化情况嘛,那你这十个点一集中,我还看啥变化情况,不如画一个点得了。就是出于这样朴实无华的目的,我们引入了拥挤距离这个概念。
一句话概括:拥挤距离越大越好。值越大说明这条曲线散得越开,就很符合我们的要求。具体是这样用的,咱还是以NP为50举例,比如优化已经到了后期了,你的第一层上的点有70个,但是你只要取50个呀,多了好苦恼呀,这个时候我们就要加入拥挤距离作为筛选条件,取前五十个拥挤距离大的点,至于后面20个,虽然也是在第一层的,但由于它们离得近,就不要它们了,就这么任性。
拥挤距离的具体定义和求解大家看这篇文章,人家说得比较详细,我就不再赘述了。
多目标优化拥挤距离计算
代码稍微补充一下呀,其实Python现在已经用多目标优化的库DEAP了,不想自己动手的同学可以学学怎么调用,这里以学习多目标遗传算法为主,就自己编写的。大家根据自己情况灵活选择就ok。
求解的问题是这样的,同时优化以下两个函数的最小值:
x取值范围是0到pi/2.
直接上代码了:
import numpy as np
import matplotlib.pyplot as plt
import math as mt
#import seaborn as sns
#sns.set_style('darkgrid')
def funclsin(x):
y=(x-1)**2
return y
def funclcos(x):
y=mt.cos(x)
return y
#%%
pi=mt.pi
NP=70 #初始化种群数
L=10 #二进制位串长度
Pc=0.8 #交叉率
Pm=0.2 #变异率
G=20 #最大遗传代数
Xs=pi/2 #上限
Xx=0 #下限
f=np.random.randint(0,high=2,size=(NP,L)) #生成随机初始种群
x=np.zeros((1,140))[0].tolist()
tempx=np.zeros((1,140))[0].tolist()
f1trace=[] #记录第一个目标函数
f2trace=[] #记录第二个目标函数
#%%遗传算法循环
for i in range(G):
print(i)
f1=[]
f2=[]
nf=f #出新的种群,在其基础上进行交叉、变异
for M in range(0,NP,2):
p=np.random.rand() #交叉
if p
简单说一下需要注意的地方:
for j in range(len(f1)):
fs.append(f1[j])
fs.append(f2[j])
fs=np.array(fs)
fs=fs.reshape(len(f1),2) #把求解得到的适应度函数转换为多目标散点的形式
fs=fs.tolist()
ps=np.zeros((len(f1),len(f1))) #i,j=1,i支配j,我们希望点的值越小越好
for k in range(0,len(f1)):
for j in range(0,len(f1)):
if fs[k][0]
这里的ps是一个比较矩阵(用二维列表写的),它的作用是对每个点进行比较,如果是在比较之后,处于被支配的点(比如在右上角的)我们就把它置1,这样在获得ps之后,统计每个个体1的数目,1越少,个体就越优秀。
f=[]
for j in range(len(front)):
if len(f)==NP:
# print(2)
break
tempf=[]
for k in range(len(front[j])):
tempf.append(newf[front[j][k]]) #把第J层的染色体添加进入tempf中
tempf1=[]
tempf2=[]
for M in range(len(tempf)): #计算第j层染色体的函数值,存在temf1.2中
U=tempf[M]
m=0
for k in range(L):
m=U[k]*(2**(k-1))+m #将二进制解码为定义域范围内十进制
tempx[M]=Xx+m*(Xs-Xx)/(2**(L-1))
tempf1.append(funclsin(tempx[M]))
tempf2.append(funclcos(tempx[M]))
#对f1计算其拥挤距离,此时与f2无关。
tempf1index=np.argsort(tempf1) #tempf1升序排序
distance=np.zeros(len(tempf1))
#将两端位置值置为正负无穷
for k in range(len(tempf1)):
if tempf1[k]==max(tempf1):
distance[k]=float('inf')
elif tempf1[k]==min(tempf1):
distance[k]=float('inf')
#计算每个点在f1下的拥挤距离
for k in range(len(tempf1index)):
if abs(distance[tempf1index[k]])<10: #把极值点排除在外
distance[tempf1index[k]]=(tempf1[tempf1index[k+1]]-tempf1[tempf1index[k-1]])/(max(tempf1)-min(tempf1))
else:
continue
#对f2计算拥挤距离,与f1无关
tempf1index1=np.argsort(tempf2) #tempf2升序排序
distance1=np.zeros(len(tempf1))
#将两端位置值置为正负无穷
for k in range(len(tempf2)):
if tempf2[k]==max(tempf2):
distance1[k]=float('inf')
elif tempf2[k]==min(tempf2):
distance1[k]=float('inf')
#计算每个点在f2下的拥挤距离
for k in range(len(tempf1index1)):
if abs(distance1[tempf1index1[k]])<10: #把极值点排除在外
distance1[tempf1index1[k]]=(tempf2[tempf1index1[k+1]]-tempf2[tempf1index1[k-1]])/(max(tempf2)-min(tempf2))
else:
continue
sumdis=[] #当收敛到一个点时,拥挤距离就失去意义了
这一段代码是计算拥挤距离用的,拥挤距离是针对两个目标函数分别计算,最后再把距离相加进行比较。
结果比较简单,因为咱这选的函数比较简单,主要是学习为主嘛。
下面是第一代的种群两个目标函数值的关系图:(迭代一次了)
之后是迭代15次的结果:
收敛速度和计算速度都是相当快的,大家可以更换测试函数再进行测验。
在代码调试的过程中,有时会出现收敛到单点的情况(迭代次数大了之后),我不知道这个是不是跟Python的初始种群随机生成的机制有关系,了解的朋友可以私信我讨论一下。谢谢大家。