多目标优化算法MOEA/D算法的实现

关于MOEA/D算法的论文精读介绍笔记
【读论文-1】MOEA/D: A Multiobjective Evolutionary Algorithm Based on Decomposition(一种基于分解的多目标进化算法) - (jianshu.com)
【读论文-2】MOEA/D: A Multiobjective Evolutionary Algorithm Based on Decomposition(一种基于分解的多目标进化算法) - (jianshu.com)
代码仓库链接:https://github.com/425776024/MOEAD

(1)MOEA/D算法的流程

输入:
•多目标优化
•停止标准;
•N: MOEA/D考虑的子问题的数量
•N个权重向量的均匀分布: λ1,…,λN;
•T:每个权重向量的附近的权重向量的数量
输出:EP

步骤1) 初始化:
第1.1 步)创建一个外部种群(EP)用于存储过程优秀个体,初始为空
第1.2 步)计算任何两个权重向量之间的欧氏距离,然后计算出每个权重向量的最近权重向量T。对于每个i=1,…,N,设置
B(i)={i1,…,iT},其中λi1,…,λiT是λi的最近T权重向量
第1.3 步)生成初始数量的随机的x1,…,xN或特定问题的方法。设置FVi=F(xi)
第1.4 步)由特定于问题的方法初始化z=(z1,…,zm)T。

步骤2) 更新:
对于i =1,…,N
步骤2.1) 复制: 从B(i)随机选择两个索引k,l,然后通过使用差分进化从xk和xl生成一个新的解决方案y。
步骤2.2) 改进: 应用特定于问题的修复/改进启发式由y 产生y’
步骤2.3) 更新Z:对于每一个j=1,…,m,判断y是否可能替换原有极值,如果zj 步骤2.4)更新领域解B(i),对于领域中每个权值向量λj,如果得到优化,则更新;
步骤2.5) 更新EP:从EP中删除所有被F(y’)支配的向量。如果外部种群 (EP)中没有支配F(y’)的向量,则将F(y’)添加至EP

步骤3) 停止条件:
如果满足停止条件,则停止和输出EP。否则,重复步骤 2
在初始化中, B(i)包含λi的T最近向量。我们使用欧几里德距离来测量任何两个权重向量之间的接近程度。因此,λi的最近向量是它自己,其中i∈B(i)。如果j∈B(i),则第j个子问题可以看作是子问题i的近邻。

ZDT1 - 二目标优化问题

注:图中青色的线代表的是该权重向量和帕累托前沿面上的点上的约束关系。

主体代码:

import time
import problem.KUR as KUR
import problem.ZDT1 as ZDT1
import problem.ZDT2 as ZDT2
import problem.ZDT3 as ZDT3
import problem.ZDT4 as ZDT4
from utils import Utils


class MOEAD:
    # 0表示最小化目标求解,1最大化目标求解。(约定)
    problem_type = 0
    # problem_type=1
    
    Test_fun = ZDT1# 测试函数
    name = 'ZDT1' # 动态展示的时候的title名称
    # 使用那种方式、DE/GA 作为进化算法
    # GA_DE_Utils = Utils.DE_Utils
    GA_DE_Utils = Utils.GA_Utils

    # 种群大小,取决于vector_csv_file/下的xx.csv
    Pop_size = -1
    # 最大迭代次数
    max_gen = 50
    # 邻居设定(只会对邻居内的相互更新、交叉)
    T_size = 5
    # 支配前沿ID
    EP_X_ID = []
    # 支配前沿 的 函数值
    EP_X_FV = []

    # 种群
    Pop = []
    # 种群计算出的函数值
    Pop_FV = []
    # 权重
    W = []
    # 权重的T个邻居。比如:T=2,(0.1,0.9)的邻居:(0,1)、(0.2,0.8)。永远固定不变
    W_Bi_T = []
    # 理想点。(比如最小化,理想点是趋于0)
    # ps:实验结论:如果你知道你的目标,比如是极小化了,且理想极小值(假设2目标)是[0,0],
    # 那你就一开始的时候就写死moead.Z=[0,0]吧
    Z = []
    # 权重向量存储目录
    csv_file_path = 'vector_csv_file'
    # 当前迭代代数
    gen = 0
    # 是否动态展示
    # need_dynamic = False
    need_dynamic = True
    # 是否画出权重图
    draw_w = True
    # 用于绘图:当前进化种群中,哪个,被,正在 进化。draw_w=true的时候可见
    now_y = []

    # draw_w=True

    def __init__(self):
        self.Init_data()

    def Init_data(self):
        # 加载权重
        Utils.Load_W(self)
        # 计算每个权重Wi的T个邻居
        Utils.cpt_W_Bi_T(self)
        # 创建种群
        self.GA_DE_Utils.Creat_Pop(self)
        # 初始化Z集,最小问题0,0
        Utils.cpt_Z(self)

    def show(self):
        if self.draw_w:
            Utils.draw_W(self)
        Utils.draw_MOEAD_Pareto(self, moead.name + "num:" + str(self.max_gen) + "")
        Utils.show()

    def run(self):
        t = time.time()
        # EP_X_ID:支配前沿个体解,的ID。在上面数组:Pop,中的序号
        # envolution开始进化
        EP_X_ID = self.GA_DE_Utils.envolution(self)
        print('你拿以下序号到上面数组:Pop中找到对应个体,就是多目标优化的函数的解集啦!')
        print("支配前沿个体解,的ID(在上面数组:Pop,中的序号):", EP_X_ID)
        dt = time.time() - t
        self.show()


if __name__ == '__main__':
    # np.random.seed(1)
    moead = MOEAD()
    moead.run()


(2)ZDT1的二目标优化问题

首先,ZDT1是一个二目标函数。函数维度设置为30.目标函数数量设置为2个。(两个目标的维度也有可能会不一样,那么我们就需要根据具体问题去定义)。还需设置函数的边界:[0, 1]

Pop种群最后的最优解是0,每一个pop解的维度是30. 种群大小是权重的大小。
EP, 前沿支配集合的数量会随着碎发的更新逐渐增加。

'''
求解问题部分
'''
import numpy as np

Dimention = 30 # 函数变量维度(目标维度不一致的自行编写目标函数)
Func_num = 2 # 函数 目标个数
Bound = [0, 1] # 函数边界

def Func(X):
    f1 = F1(X)
    gx = g(X)
    f2 = F2(gx, X)
    return [f1, f2]

def F1(X):
    return X[0]

def F2(gx, X):
    x = X[0]
    f2 = gx * (1 - np.sqrt(x / gx))
    return f2

def g(X):
    g = 1 + 9 * (np.sum(X[1:], axis=0) / (X.shape[0] - 1))
    return g

(3)MOEA/D 类

在算法里面我们使用GA/DE作为进化算法。

首先:我们需要了解代码中的很多变量的具体含义:

  • Pop:种群
  • Pop_size:种群大小
  • Pop_FV:种群计算出的函数值
  • W:权重,也就是论文中的λ
  • W_Bi_T:每一个权重的T个邻居
  • gen:当前迭代次数
  • Z:最优理想点
  • max_gen :最大迭代次数
  • T-size:邻居设定
  • EP_X_ID:支配前沿的ID ??????
  • EP_X_FV:支配前沿的函数值?????

概念:

  • 支配(Dominance) :在多目标优化问题中,如果个体p至少有一个目标比个体q好,而且个体p的所有目标都不比q差;那么称个体p支配个体q;
    ①支配:假设小明9岁,50斤,小红8岁,45斤,小明无论是岁数还是体重都比小红大,所以小明支配小红。
    ②互不支配:假设小明7岁,50斤,小红8岁,45斤,小明岁数比小红小,但体重比小红大,所以小明和小红互不支配。
    ③非支配排序:将一组解分成n个集合:rank1,rank2…rankn,每个集合中所有的解都互不支配,但ranki中的任意解支配rankj中的任意解(i
a)初始化数据

初始化函数中包括了:权重加载、计算相邻的邻居、创建种群、初始化Z值。

    def Init_data(self):
        # 加载权重
        Utils.Load_W(self)
        # 计算每个权重Wi的T个邻居
        Utils.cpt_W_Bi_T(self)
        # 创建种群
        self.GA_DE_Utils.Creat_Pop(self)
        # 初始化Z集,最小问题0,0
        Utils.cpt_Z(self)

其中的具体内容如下:

  • 加载权重
def Load_W(moead):
    file = moead.name + '.csv' # 构建csv文件名
    path = moead.csv_file_path + '/' + file # 拼接csv文件路径
    if os.path.exists(path) == False: # 文件不存在就创建
        print('not exists')
        mv = Mean_vector(moead.h, moead.Test_fun.Func_num, path) # 生成一个均匀分布的MOP问题
        mv.generate() # 生成文件
        print('created')
    W = np.loadtxt(fname=path) # 加载文件
    moead.Pop_size = W.shape[0] # 看有多少行数据
    moead.W = W # 将权重值返回
    return W
  • 计算每个权重的T个邻居
# 计算T个邻居
def cpt_W_Bi_T(moead):
    # 计算权重的T个邻居
    if moead.T_size < 1: # 不能小于1个邻居
        return -1
    for bi in range(moead.W.shape[0]): # 循环获取所有的w值
        Bi = moead.W[bi]
        DIS = np.sum((moead.W - Bi) ** 2, axis=1) # 计算所有值和Bi的距离
        B_T = np.argsort(DIS) # 然后对所有值进行排序,返回的是下标值
        # 第0个是自己(距离永远最小)
        B_T = B_T[1:moead.T_size + 1] # 选择T到T+1个
        moead.W_Bi_T.append(B_T) # 把每个临近值都加入到W_Bi_T中去
  • 创建种群
def Creat_child(moead):
    # 创建一个个体
    child = moead.Test_fun.Bound[0] + (moead.Test_fun.Bound[1] - moead.Test_fun.Bound[0]) * np.random.rand(moead.Test_fun.Dimention)
    return child

def Creat_Pop(moead):
    # 创建moead.Pop_size个种群
    Pop = [] # 种群数组
    Pop_FV = [] # 种群计算出的值
    if moead.Pop_size < 1: # 种群数量不能小于1
        print('error in creat_Pop')
        return -1
    while len(Pop) != moead.Pop_size: # 添加种群
        X = Creat_child(moead) # 创建一个child
        Pop.append(X) # 将x添加到Pop里面去
        Pop_FV.append(moead.Test_fun.Func(X)) # 将计算出的函数值存到Pop_FV里面去
    moead.Pop, moead.Pop_FV = Pop, Pop_FV # 保存变量
    return Pop, Pop_FV
  • 初始化Z集,理想极小值
    如果你知道你的目标,比如是极小化了,且理想极小值(假设2目标)是[0,0]
def cpt_Z(moead):
    # 初始化Z集,最小问题0,0,..。
    # ps:实验结论:如果你知道你的目标,比如是极小化了,且理想极小值(假设2目标)是[0,0],
    # 那你就一开始的时候就写死moead.Z=[0,0]吧,就不用这个函数进行设置了。
    # 这里极小化全部初始化为[0,0,...0],极大化:[10,10,....10]
    Z = []
    for _ in range(moead.Test_fun.Func_num): # 多目标优化问题,有多少个目标z就是多少维度的
        z_i = -1
        if moead.problem_type == 0: # 极小值问题,设置为0
            z_i = 0
        if moead.problem_type == 1: # 极大值问题,设置为1
            z_i = 10
        Z.append(z_i)
    moead.Z = Z
    return Z

MOEA/D问题初始化

b)运行MOEA/D算法
  • 运行evolution进化算法
def run(self):
        t = time.time()
        # EP_X_ID:支配前沿个体解,的ID。在上面数组:Pop,中的序号
        # envolution开始进化
        EP_X_ID = self.GA_DE_Utils.envolution(self)
        print('你拿以下序号到上面数组:Pop中找到对应个体,就是多目标优化的函数的解集啦!')
        print("支配前沿个体解,的ID(在上面数组:Pop,中的序号):", EP_X_ID)
        dt = time.time() - t
        self.show()
  • 动态展示
def show(self):
        if self.draw_w:
            Utils.draw_W(self)
        Utils.draw_MOEAD_Pareto(self, moead.name + "num:" + str(self.max_gen) + "")
        Utils.show()
  • evolution进化函数
    关于更新支配前沿,因为其计算量较大,所以选择只有在,新的个体个原来个体的差异大于d的时候才进行EP的更新。
def envolution(moead):
    # 进化,开始进化moead.max_gen轮
    for gen in range(moead.max_gen): # max_gen最大迭代次数
        # 用于图像展示的时候告诉它,现在在第几轮了
        moead.gen = gen
        # 对数组moead.Pop中的每一个个体,开始拿出来,你们要被进化了!
        # 个体序号pi,个体p
        for pi, p in enumerate(moead.Pop): # 种群中的所有个体(和权重的数量一致)
            # 第pi号个体的T个邻居
            Bi = moead.W_Bi_T[pi]
            # 随机选一个T内的数,作为pi的邻居。
            # (邻居你可以想象成:物种,你总不能人狗杂交吧?所以个体pi只能与他的T个前后的邻居权重,管的个体杂交进化)
            # 比如:T=2,权重(0.1,0.9)约束的个体的邻居是:权重(0,1)、(0.2,0.8)约束的个体。永远固定不变
            k = np.random.randint(moead.T_size)
            l = np.random.randint(moead.T_size)
            # 随机从邻居内选2个个体,产生新解
            ik = Bi[k]
            il = Bi[l]
            Xi = moead.Pop[pi]
            Xk = moead.Pop[ik]
            Xl = moead.Pop[il]
            # 进化下一代个体。基于自身Xi+邻居中随机选择的2个Xk,Xl 还考虑gen 去进化下一代
            Y = generate_next(moead, gen, pi, Xi, Xk, Xl)
            # 计算当前Xi,不进化前的切比雪夫距离
            cbxf_i = MOEAD_Utils.cpt_tchbycheff(moead, pi, Xi)
            # 计算当前Xi,进化后的切比雪夫距离。(比较进化更好?那就保留)
            cbxf_y = MOEAD_Utils.cpt_tchbycheff(moead, pi, Y)
            # 不能随随便便一点点好就要了(自己的策略设计)。超过d才更新
            d = 0.001
            # 开始比较是否进化出了更好的下一代,这样才保留
            if cbxf_y < cbxf_i:
                # 用于绘图:当前进化种群中,哪个,被,正在 进化。draw_w=true的时候可见
                moead.now_y = pi
                # 计算下一代的函数值
                F_Y = moead.Test_fun.Func(Y)[:]
                # 更新函数值到moead.EP_X_FV中。都进化出更好切比雪夫下一代了,自然要更新多目标中的目标的函数值
                MOEAD_Utils.update_EP_By_ID(moead, pi, F_Y)
                # 都进化出更好切比雪夫下一代了,有可能有更好的理想点,尝试更新理想点
                MOEAD_Utils.update_Z(moead, Y)
                if abs(cbxf_y - cbxf_i) > d:
                    # 超过d才更新。更新支配前沿。红色点那些
                    MOEAD_Utils.update_EP_By_Y(moead, pi)
            # 更新邻居节点
            MOEAD_Utils.update_BTX(moead, Bi, Y)
        # 是否需要动态展示
        if moead.need_dynamic:
            Draw_Utils.plt.cla()
            if moead.draw_w:
                Draw_Utils.draw_W(moead)
            Draw_Utils.draw_MOEAD_Pareto(moead, moead.name + ",gen:" + str(gen) + "")
            Draw_Utils.plt.pause(0.001)
        print('迭代 %s,支配前沿个体数量len(moead.EP_X_ID) :%s,moead.Z:%s' % (gen, len(moead.EP_X_ID), moead.Z))
    return moead.EP_X_ID
  • 产生下一代
    这里的Y1,Y2最终会生成一个值。也就是新一代。
def generate_next(moead, gen, wi, p0, p1, p2):
    # 进化下一代个体。基于自身Xi+邻居中随机选择的2个Xk,Xl 还考虑gen 去进化下一代
    qbxf_p0 = MOEAD_Utils.cpt_tchbycheff(moead, wi, p0)
    qbxf_p1 = MOEAD_Utils.cpt_tchbycheff(moead, wi, p1)
    qbxf_p2 = MOEAD_Utils.cpt_tchbycheff(moead, wi, p2)

    qbxf = np.array([qbxf_p0, qbxf_p1, qbxf_p2])
    best = np.argmin(qbxf)
    # 选中切比雪夫距离最小(最好的)个体
    Y1 = [p0, p1, p2][best]
    # 需要深拷贝成独立的一份
    n_p0, n_p1, n_p2 = np.copy(p0), np.copy(p1), np.copy(p2)

    if gen % 10 == 0:
        # 每隔10代,有小概率进行EO优化(效果好,但是复杂度高)
        if np.random.rand() < 0.1:
            n_p0 = EO(moead, wi, n_p0)
    # 交叉
    n_p0, n_p1 = cross_mutation(moead, n_p0, n_p1)
    n_p1, n_p2 = cross_mutation(moead, n_p1, n_p2)
    # 交叉后的切比雪夫距离
    qbxf_np0 = MOEAD_Utils.cpt_tchbycheff(moead, wi, n_p0)
    qbxf_np1 = MOEAD_Utils.cpt_tchbycheff(moead, wi, n_p1)
    qbxf_np2 = MOEAD_Utils.cpt_tchbycheff(moead, wi, n_p2)

    qbxf = np.array([qbxf_p0, qbxf_p1, qbxf_p2, qbxf_np0, qbxf_np1, qbxf_np2])
    best = np.argmin(qbxf)
    # 选中切比雪夫距离最小(最好的)个体
    Y2 = [p0, p1, p2, n_p0, n_p1, n_p2][best]

    # 随机选中目标中的某一个目标进行判断,目标太多,不要贪心,随机选一个目标就好
    fm = np.random.randint(0, moead.Test_fun.Func_num)
    # 如果是极小化目标求解,以0.5的概率进行更详细的判断。(返回最优解策略不能太死板,否则容易陷入局部最优)
    if moead.problem_type == 0 and np.random.rand() < 0.5:
        FY1 = moead.Test_fun.Func(Y1)
        FY2 = moead.Test_fun.Func(Y2)
        # 如果随机选的这个目标Y2更好,就返回Y2的
        if FY2[fm] < FY1[fm]:
            return Y2
        else:
            return Y1
    return Y2
  • 计算切比雪夫距离
def Tchebycheff_dist(w, f, z):
    # 计算切比雪夫距离
    return w * abs(f - z)


def cpt_tchbycheff(moead, idx, X):
    # idx:X在种群中的位置
    # 计算X的切比雪夫距离(与理想点Z的)
    max = moead.Z[0]
    ri = moead.W[idx]
    F_X = moead.Test_fun.Func(X)
    for i in range(moead.Test_fun.Func_num):
        fi = Tchebycheff_dist(ri[i], F_X[i], moead.Z[i])
        if fi > max:
            max = fi
    return max
  • 更新支配前沿
def update_EP_By_ID(moead, id, F_Y):
    # 如果id存在,则更新其对应函数集合的值
    if id in moead.EP_X_ID:
        # 拿到所在位置
        position_pi = moead.EP_X_ID.index(id)
        # 更新函数值
        moead.EP_X_FV[position_pi][:] = F_Y[:]

def update_EP_By_Y(moead, id_Y):
    # 根据Y更新前沿
    # 根据Y更新EP
    i = 0
    # 拿到id_Y的函数值
    F_Y = moead.Pop_FV[id_Y]
    # 需要被删除的集合
    Delet_set = []
    # 支配前沿集合,的数量
    Len = len(moead.EP_X_FV)
    for pi in range(Len):
        # F_Y是否支配pi号个体,支配?哪pi就完了,被剔除。。
        if is_dominate(moead, F_Y, moead.EP_X_FV[pi]):
            # 列入被删除的集合
            Delet_set.append(pi)
            break
        if i != 0:
            break
        if is_dominate(moead, moead.EP_X_FV[pi], F_Y):
            # 它有被别人支配!!记下来能支配它的个数
            i += 1
    # 新的支配前沿的ID集合,种群个体ID,
    new_EP_X_ID = []
    # 新的支配前沿集合的函数值
    new_EP_X_FV = []
    for save_id in range(Len):
        if save_id not in Delet_set:
            # 不需要被删除,那就保存
            new_EP_X_ID.append(moead.EP_X_ID[save_id])
            new_EP_X_FV.append(moead.EP_X_FV[save_id])
    # 更新上面计算好的新的支配前沿
    moead.EP_X_ID = new_EP_X_ID
    moead.EP_X_FV = new_EP_X_FV
    # 如果i==0,意味着没人支配id_Y
    # 没人支配id_Y?太好了,加进支配前沿呗
    if i == 0:
        # 不在里面直接加新成员
        if id_Y not in moead.EP_X_ID:
            moead.EP_X_ID.append(id_Y)
            moead.EP_X_FV.append(F_Y)
        else:
            # 本来就在里面的,更新它
            idy = moead.EP_X_ID.index(id_Y)
            moead.EP_X_FV[idy] = F_Y[:]
    # over
    return moead.EP_X_ID, moead.EP_X_FV

你可能感兴趣的:(多目标优化算法MOEA/D算法的实现)