Python_多目标遗传算法_入门学习+代码实现

目录

前言

多目标遗传算法

多目标的优化结果

Pareto曲线及分层处理

拥挤距离

代码实现

优化结果

最后的话


前言

在很多的工程实践问题中,往往是多输入多输出的。而且最有意思的事情在于:多个输出指标总是互相矛盾的。把其中一个提高了,另外一个就会受到影响,顾此失彼。基于这样一种应用需求,单目标的遗传算法很明显已经不能满足工程实践的要求了,所以需要开拓多目标的优化算法,多目标的遗传算法就是在这样的背景下,好吧,是我自己需要进行多目标的优化,跟大背景没有太大的关联。本篇以基础知识为主,最后会加入入门级别的代码展示,只不过这个代码是我最近刚学多目标的时候编写的,写得很繁琐冗余,只能说是把多目标优化的功能实现了,代码质量很低,之后我会对代码进行优化,便于大家学习讨论。阅读这篇文章的时候认为大家已经掌握的遗传算法的基本知识了,如果单目标算法还不清楚的,建议阅读我之前的博客。

Python—标准遗传算法求函数最大值代码实现

多目标遗传算法

先来推荐一篇博客,讲得还是比较清楚了:

基于DEAP库的python进化算法-7.多目标遗传算法NSGA-II

我参考着这篇博客的讲解思路,把我对多目标的理解记录一下,大家如果要看关于概念准确的定义,还是建议移步推荐的这篇博客。

多目标的优化结果

首先,需要讨论的是结果的展示问题。在单目标中,由于只有一个目标,所以我们在展示结果的时候,往往是给出的目标值的收敛曲线(有些时候也展示适应度曲线,看个人偏好了)。但是多目标优化过程不是这样的,咱以两个目标为例,分别以两个目标的结果作为坐标,得到的优化结果是什么?是一个点对吧,这一个点对于我们来说很重要,但是,由于是多目标的问题,我们很多时候就会好奇,如果我将其中一个目标做一点让步,就是它不要这么好,那另一个目标会不会变得更好呢?我们想知道它们的变化关系,所以,其实在关心这么一个点的同时,我们还想了解一条线,两个目标都还不错,但又不是最好的那条线。借一张推荐的博客里面的图说明一下这个问题。

Python_多目标遗传算法_入门学习+代码实现_第1张图片

这个图展示的就是两个优化目标f1,f2的结果,如果我们想目标值越小越好,那就是坐标上的散点越靠近左下角越好,其实在优化的过程中就会发现,我们能去评判红色圈圈中的四个点到底谁好吗?得结合实际情况去取舍对吧?那算法它没人那么聪明,它只能进行筛选,如果在编写算法的时候,没有这种保留一条最优曲线的思路,那四个点,算法就只给你留一个,最终结果展示的时候,就会遗漏很多的信息了。这个也是为什么多目标优化和单目标优化结果有本质不同的原因。

Pareto曲线及分层处理

理解了结果的形式,这条曲线就顺理成章的产生了,简单地说,这条曲线就是最优解的散点连成的线,有些朋友喜欢叫最优解前沿,其实是一个意思。这里要引入分层这个概念。举个例子,比如规定的种群数量NP为50,上面那条最优解曲线上一共是7个点,那对应的7个个体认为是基因表现比较好的7个,但是迭代需要选择50个呀,7个是不够的,我们就把这7个划分为第一层,接着,把这7个剔除了,再去寻找靠近坐标左下角的散点连成第二条曲线,划分为第二层,进行迭代直到所有的散点都分层完毕了,这个过程也就结束了。之后进行选择判断的时候,就从第一层开始挑个体,一直补充到NP数量。

拥挤距离

这个概念其实是为曲线服务的,其实理解的最好方式是去跑一遍代码,以有没有拥挤距离这个条件为变量,就知道这家伙是干啥用的了。我先用语言跟大家解释一下。比如第一条Pareto曲线上有十个点,大家先想想我们是希望这十个点靠的近好呢,还是希望它们散开好呢?想想我们画曲线的目的,不就是为了看变化情况嘛,那你这十个点一集中,我还看啥变化情况,不如画一个点得了。就是出于这样朴实无华的目的,我们引入了拥挤距离这个概念。

一句话概括:拥挤距离越大越好。值越大说明这条曲线散得越开,就很符合我们的要求。具体是这样用的,咱还是以NP为50举例,比如优化已经到了后期了,你的第一层上的点有70个,但是你只要取50个呀,多了好苦恼呀,这个时候我们就要加入拥挤距离作为筛选条件,取前五十个拥挤距离大的点,至于后面20个,虽然也是在第一层的,但由于它们离得近,就不要它们了,就这么任性。

拥挤距离的具体定义和求解大家看这篇文章,人家说得比较详细,我就不再赘述了。

多目标优化拥挤距离计算

代码实现

代码稍微补充一下呀,其实Python现在已经用多目标优化的库DEAP了,不想自己动手的同学可以学学怎么调用,这里以学习多目标遗传算法为主,就自己编写的。大家根据自己情况灵活选择就ok。

求解的问题是这样的,同时优化以下两个函数的最小值:

y=(x-1)^2

y=cos(x)

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=[]                          #当收敛到一个点时,拥挤距离就失去意义了

这一段代码是计算拥挤距离用的,拥挤距离是针对两个目标函数分别计算,最后再把距离相加进行比较。

优化结果

结果比较简单,因为咱这选的函数比较简单,主要是学习为主嘛。

下面是第一代的种群两个目标函数值的关系图:(迭代一次了)

Python_多目标遗传算法_入门学习+代码实现_第2张图片

 之后是迭代15次的结果:

Python_多目标遗传算法_入门学习+代码实现_第3张图片

 收敛速度和计算速度都是相当快的,大家可以更换测试函数再进行测验。

最后的话

在代码调试的过程中,有时会出现收敛到单点的情况(迭代次数大了之后),我不知道这个是不是跟Python的初始种群随机生成的机制有关系,了解的朋友可以私信我讨论一下。谢谢大家。

你可能感兴趣的:(优化算法,python)