Pareto解:是非支配解。在多目标任务中,由于目标之间存在冲突,虽然有一个解在某个目标上是最好的,但在其他目标上可能是最差的。在改进这些目标的同时,势必会削弱其他目标的性能。在不削弱其他目标函数性能的前提下能改进至少一个目标的解称为Pareto解。
Pareto最优解:就是所有Pareto解中最好的一个解,能将至少一个目标函数优化到尽可能好而且不会削弱其他目标函数的性能。
支配:A的所有都比B好,称为A支配B。
被支配:B的所有都比A差,称为B被A支配。
非支配:A中有比B好的也有比B差的,称A与B为非支配关系或不相关。
开始写NSGA-II的代码了,先把python复习一遍…(2020.10.2)
-----------------------------------分隔符-----------------------------------
2020.10.4 好的python学完了,开始整代码了(下个anaconda先)
先上个流程图(自己画的,也不晓得对不对):
那就放代码吧,首先是测试用的函数Test_Fun.py
import numpy as np
dimention = 2 #维度
bound = [-1000, 1000] #函数边界
def Func(X):
var = np.array([X[0] * 2 , X[1] / 3] ,ndmin=1)
return var
维度、边界、函数可以自定义,只要输出是个ndarray类型的值就行
然后是个体Individual类的代码Individual.py
import numpy as np
import Test_Fun as fun
class Individual:
def __init__(self):
self.Np = 0 # 支配当前个体的个体数
self.Sp = [] # 支配个体集
self.p_rank = 0 # 排序等级
self.dp = 0 # 拥挤度
self.X = fun.bound[0] + (fun.bound[1] - fun.bound[0]) * np.random.rand(fun.dimention) #个体向量(解)
#np.random.rand(x) 生成一个x维的(0,1)内的随机向量
self.F_value = fun.Func(self.X) #对应函数值
def reset_one(self, one):# 重制one这个个体
one.Np = 0
one.Sp = []
one.p_rank = 0
one.dp = 0
return one
这里生成解向量的时候比较简单,直接用最大边界差来随机生成解,如果改成int型的向量可能会增加运行速度
然后是进化Evolution.py,把交叉和变异放在这里
import numpy as np
import Test_Fun as TF
c_rate = 0.8 #交叉率
m_rate = 0.2 #突变率
idv = None
def cross(p1, p2): #交叉
if np.random.rand() < c_rate:
p2 = idv.reset_one(p2)
r1 = 0.6
r2 = 0.4 #基因46分
x1 = r1 * p1.X + r2 * p2.X
x2 = r2 * p1.X + r1 * p2.X
p1.X = x1
p2.X = x2
p2.F_value = TF.Func(p2.X)
p1.F_value = TF.Func(p1.X) #重新计算交叉后的函数值
return p2
def mutate(p): #变异
gen_len = len(p.X)
if np.random.rand() < m_rate:
p = idv.reset_one(p)
for l in range(gen_len):
j = np.random.randint(0, gen_len)
d = np.random.randint(0, gen_len)
if j == d: #如果基因太少就不变异了
break
p.X[j], p.X[d] = p.X[d], p.X[j]
p.F_value = TF.Func(p.X) #重新计算变异后的函数值
return p
变异的时候直接重新设置了个体,我这里用的双自变量的函数所以大概率是没有变异,如果函数自变量多了的话变异可能会发生。
然后就是种群代码Population.py
from Individual import Individual
from Evolution import *
class Population:
def __init__(self, pop_size):
self.pop_size = pop_size
def Create_Pop(self):
P = []
for i in range(self.pop_size):
idv = Individual()
P.append(idv)
return P
def Next_Pop(self, Pt): #产生下一代
n_P = []
p_size = len(Pt)
for p in Pt:
idv = Individual()
p = idv.reset_one(p)
#因为要将父代与子代放一起再排序
#所以将父代属性重置
idv.X[:] = p.X[:]
idv.F_value[:] = p.F_value[:]
n_P.append(idv)
for i in range(p_size):
j = np.random.randint(0, p_size)
#一定概率与随机节点发生交叉
n_P[i] = cross(n_P[i], n_P[j])
#一定概率发生变异
n_P[i] = mutate(n_P[i])
return n_P
def Pt_reset(self, Pt): #将Pt中个体的属性重置
St = []
for p in Pt:
idv = Individual()
p = idv.reset_one(p)
St.append(p)
return St
创建种群的时候很简单,直接随机创建。至于种群重置也只重置上一代留下来的各种属性,原本的函数值不会变。
最后是最重要的函数NSGA-II.py
import numpy as np
import Test_Fun as tfun
import Evolution as ev
import matplotlib.pyplot as plt
from Population import Population
from Individual import Individual
class NSGA_II:
paretoFront = [] #pareto前沿
Pt = [] #种群列表
Qt = [] #子代列表
Rt = [] #Pt和Qt组合后的种群
def __init__(self, n, ges):
self.populations = Population(n) #种群大小
self.ges = ges #进化轮数
ev.idv = Individual()
def fast_noDominate_sort(self, P): #快速非支配排序
Z = [] #非支配集
i = 1 #Pareto等级
F1 = self.cpt_F1_dominate(P)
Z.append(F1)
while len(F1) != 0:
Q = []
one_q = Individual()
for pi in F1: #这个循环是为了找到所有被非支配个体支配的个体,将它们的被支配数减x后看是否还被支配
p = P[pi]
for q in p.Sp:
one_q = P[q]
#相当于将F1中的个体从P中删除后重新排序,再将新的非支配个体找出来
one_q.Np -= 1
if one_q.Np == 0:
one_q.p_rank = i + 1
Q.append(q)
if not Q and one_q.Np>0:
continue
i = i + 1
F1 = Q #相同Pareto等级的个体之间无序,需根据后面的拥挤度排序
Z.append(F1)
return Z #Z中每行是非支配个体对应的下标列表
def cpt_F1_dominate(self, P):
#遍历每一个个体,计算支配它的个体数和被它支配的个体数,并找到第一批非支配个体
F1 = []
#enumerate(P)将P按序组合成一个元组列表(下标,值)
for j, p in enumerate(P):
p.Np = 0
for i, q in enumerate(P):
if j != i:
if self.is_dominate(p, q):
p.Sp.append(i)
if self.is_dominate(q, p):
p.Np += 1 #若p被q支配,则支配它的个体数加一
if p.Np == 0: #如果p不被任何个体支配,则将它的Pareto等级记为1并将其下标存入F1
p.p_rank = 1
F1.append(j)
return F1 #此时所有个体的支配数和被支配数都已经记录下来,并得到前沿层
def is_dominate(self, a, b):#a是否支配b
a_f = a.F_value[:]
b_f = b.F_value[:]
i = 0
for av, bv in zip(a_f, b_f): #比较a和b每一列的函数值
if av < bv:
i = i + 1
elif av > bv:
return False
if i != 0:
return True
return False
def Crowd_dist(self, Fi): #拥挤度计算, 只计算P内Fi位置部分的拥挤度
FL = len(Fi)
if FL == 1:
return
elif FL == 2:
if np.random.rand() > 0.5: #50%概率后一个进入下一代
Fi[0], Fi[1] = Fi[1], Fi[0]
return
f_max = Fi[0].F_value[:]
f_min = Fi[0].F_value[:] #初始化拥挤边界
f_num = len(f_max) #目标函数值的维数
for p in Fi:
p.dp = 0 #初始化每个个体的拥挤度
for fm in range(f_num): #寻找最大函数值和最小函数值
if p.F_value[fm] > f_max[fm]:
f_max[fm] = p.F_value[fm]
if p.F_value[fm] < f_min[fm]:
f_min[fm] = p.F_value[fm]
for m in range(f_num):
Fi = self.Func_sort(Fi, m)
#将第一个个体和最后一个个体的距离设为无穷大(伪)
Fi[0].dp = 1000000
Fi[FL - 1].dp = 1000000
for i in range(1, FL - 1):
#第i个个体的拥挤度dp为第i+1和第i-1个体的所有目标函数值之差的和
a = Fi[i + 1].F_value[m] - Fi[i - 1].F_value[m]
b = f_max[m] - f_min[m]
Fi[i].dp += a / b
def Func_sort(self, Fi, m):
#对Fi中个体按照第m个函数值排序
FL = len(Fi)
for i in range(FL - 1): #升序冒泡排序
p = Fi[i]
for j in range(i + 1, FL):
q = Fi[j]
if p.F_value[m] > q.F_value[m]:
Fi[i], Fi[j] = Fi[j], Fi[i]
return Fi
def Crowd_sort(self, Fi):
#对Fi中个体按照拥挤度排序
FL = len(Fi)
for i in range(FL - 1): #降序冒泡排序
p = Fi[i]
for j in range(i+1, FL):
q = Fi[j]
if p.dp < q.dp:
Fi[i], Fi[j] = Fi[j], Fi[i]
def run(self):
gen = 0 #从第0代开始
while gen < self.ges:
gen += 1
self.Rt = [] #Rt每一轮都要初始化
self.Qt = self.populations.Next_Pop(self.Pt) #子代
self.append(self.Pt, self.Rt)
self.append(self.Qt, self.Rt)
F = self.fast_noDominate_sort(self.Rt) #对全种群Rt进行快速非支配排序
self.pareto_f = [] #Pareto前沿,画图用
self.inv_append(F[0], self.Rt, self.pareto_f) #将F1放到Pareto前沿中
print('%sth pareto len %s:' %(gen, len(F[0])))
Pt_next = [] #下一代父代种群
i = 0
while (len(Pt_next) + len(F[i])) <= self.populations.pop_size:
#将前沿层最好的一部分个体保留到下一代中
self.inv_append(F[i], self.Rt, Pt_next)
i += 1
Fi = []
self.inv_append(F[i], self.Rt, Fi) #由于Pt_next可能没有满,所以先保留下一层
self.Crowd_dist(Fi) #值计算Fi中每个的个体拥挤度
self.Crowd_sort(Fi) #拥挤度排序
for i in Fi:
if len(Pt_next) >= self.populations.pop_size:
break
Pt_next.append(i)
self.Pt = self.populations.Pt_reset(Pt_next) #下一代父代完成
def append(self, From, To): #将From添加到To里面去
for i in From:
To.append(i)
def inv_append(self, x, P, to): #将P中x索引的个体存入to中
for i in x:
to.append(P[i])
def draw(self):
pf1_data = []
pf2_data = []
for p in self.pareto_f:
pf1_data.append(p.F_value[0])
pf2_data.append(p.F_value[1])
f1_data = []
f2_data = []
for p in self.Rt:
f1_data.append(p.F_value[0])
f2_data.append(p.F_value[1])
plt.xlabel('Function 1', fontsize=15)
plt.ylabel('Function 2', fontsize=15)
plt.title('Test_Fun')
plt.scatter(pf1_data, pf2_data, c='red', s=5)
plt.scatter(f1_data, f2_data, c='black', s=1)
plt.show()
def main():
m = int(input('Population Number:'))
n = int(input('Generations:'))
nsga2 = NSGA_II(m, n)
nsga2.Pt = nsga2.populations.Create_Pop()
nsga2.run()
nsga2.draw()
if __name__ == '__main__':
main()
算法还有一点改进的地方,还在测试,等完成了再回来修改。。。。
最后输出是一张图,黑色表示全部种群,红色表示Pareto前沿(电脑太辣鸡参数不能设太高…淦,老师说100个个体就够了…)