MOEA/D 的python代码实现

MOEA/D 的python代码实现
主要是参考了:
《MOEA/D: A Multiobjective Evolutionary Algorithm Based on Decomposition》
和《Multiobjective Optimization Problems With
Complicated Pareto Sets, MOEA/D and NSGA-II》两篇文章
主要是把第一篇文章用的SBX交叉算子换成了第二篇文章的差分进化和高斯变异,(但是也没有完全实现第二篇文章提到的做法)。
测试函数用的ZDT1测试函数 ,如果想测试别的还得自己按照我给的格式慢慢弄了。
代码只实现了二维的权重向量初始化方式,三维的还没有想好怎么等…
别的就是基本上参考了作者给出的.m代码实现啦,还给出了相应的解释。

import numpy as np
import matplotlib.pyplot as plt






class MOEAD():
    def __init__(self,objDim,parDim):

        self.objDim = objDim                # 问题的维数是多少
        self.parDim = parDim                # 输入变量的维数是多少
        self.popSize = 100                  # 种群的大小
        self.niche = 30                     # 邻居的个数
        self.iteration = 100                # 迭代次数
        self.F = 0.5                        # 变异概率
        self.CR = 0.5                       # 交叉概率


        self.idealPoint = np.full((self.objDim),np.inf)       # 初始化理想点
        self.weightVct = np.zeros((self.popSize,self.objDim))   # 权重向量
        self.nicheIndex = np.zeros((self.popSize,self.niche),dtype=int)   # 每个权重向量对应的邻居
        self.population = np.zeros((self.popSize,self.parDim))  # 初始种群
        self.fit = np.full((self.popSize,self.objDim),np.inf)   # 种群的适应度


    # 对每个个体计算适应度
    def cal_fitness(self,fitness_function,flag = 1,chrome = None):
        # 初始化的时候 直接计算
        if(flag == 1):
            for i in range(0,self.popSize):
                self.fit[i] = fitness_function(self.population[i].copy())
        # 需要单独对某个个体计算时
        else:
            return fitness_function(chrome)

    
    # 可能可以改进的地方
    def Init_Pop(self,lb,ub,fitness_function):
        delta = ub - lb
        self.population = np.random.rand(self.popSize, self.parDim) * delta + lb
        self.cal_fitness(fitness_function=fitness_function)


    # index:对应的要计算权重向量的下标
    def init_neighbors(self,index):
        distances = np.linalg.norm(self.weightVct - self.weightVct[index], axis=1)
        return np.argsort(distances)[:self.niche]


    # 自己手动写
    # 这个论文里每一个weight是(i / popsize,(popsize - i)/popsize)
    # 很显然三维就不行了..
    def init_weight_vector(self):
        if(self.objDim == 2):
            # 计算二维权重向量
            for i in range(0,self.popSize):
                self.weightVct[i][0] = i / self.popSize
                self.weightVct[i][1] = (self.popSize - i) / self.popSize
            # 找到对应的邻居
            for i in range(0,self.popSize):
                self.nicheIndex[i] = self.init_neighbors(index=i)
        else:
            raise "还没考虑二维以上问题写法"
        
    # 生成参考点 z1,z2,z3...
    # 在初始化之后找到每一维问题的最小值
    def init_reference_point(self):
        # 找到 arr1 中每一列的最小值
        min_values_arr1 = np.min(self.fit, axis=0)
        # 将 arr2 与 min_values_arr1 比较,保留更小的值
        self.idealPoint = np.minimum(min_values_arr1, self.idealPoint)


    # 计算切比雪夫距离
    # weight指的是:对应的权重向量
    # ref_point:对应的参考点
    def chebyshev_aggregation(self,x, weight, ref_point):
        return np.max(weight * np.abs(x - ref_point))
    

    # 更新index的子问题对应的邻居的解
    def update_neighbors_solution(self,index,offspring,offspring_eval):
        for j in self.nicheIndex[index]:
            if self.chebyshev_aggregation(offspring_eval, self.weightVct[j], self.idealPoint) < \
                self.chebyshev_aggregation(self.fit[j], self.weightVct[j], self.idealPoint):
                    print("找到了")
                    self.population[j] = offspring.copy()
                    self.fit[j] = offspring_eval.copy()
    # 加权求和的方法:论文中没有用到 后续可以用
    # def ws():pass

    # 交叉 - 变异

    # index:表示现在在优化的子问题序号
    # F_Scale:缩放因子(DE算法独有的参数)
    def crossover_DE(self,index,lb,ub,F_Scale=0.5):
        # 1. 首先找到3个 index的邻居:
        # 原文要求:不要包括index本身 且3个数不重复
        # 第0个数肯定是本身下标,因为欧氏距离是0
        select_indices =  np.random.choice(range(1,self.niche), size=3, replace=False)
        # 选中的染色体的下标
        select_chrome_index = self.nicheIndex[index][select_indices]
        # 2.生成新的染色体
        new_chrome = self.population[select_chrome_index[0]] + \
                    F_Scale * (self.population[select_chrome_index[1]] - self.population[select_chrome_index[2]])
        
        # 3.对染色体的每个维度进行变异
        random_sequence = np.random.rand(self.parDim)
        # 根据随机序列生成新数组
        random_sequence = (random_sequence <= self.F).astype(int)
        
        # 如果新数组全为0,则随机将一个位置置为1
        if np.all(random_sequence == 0):
            random_index = np.random.randint(0, self.parDim)
            random_sequence[random_index] = 1
        
        # 对数组进行限制
        clipped_array = np.clip(self.population[index] * random_sequence + \
                                new_chrome * (1 - random_sequence),lb, ub)
        return clipped_array

    # 高斯变异
    def mutation_Gauniss(self,chrome, lb, ub, prob):
        
        mutated_chromosome = chrome.copy()  # 复制染色体
        # 生成与染色体相同大小的随机矩阵,其中的元素服从均匀分布
        random_matrix = np.random.rand(*chrome.shape)
        # 根据变异概率和随机矩阵来确定哪些元素发生变异
        mutation_indices = random_matrix < prob
        # 对需要变异的元素应用高斯变异
        # 对需要变异的元素应用高斯变异,均值为当前染色体在该位置的值
        mutation_means = mutated_chromosome[mutation_indices]
        mutation_values = np.random.normal(mutation_means, (ub - lb) / 20)
        mutated_chromosome[mutation_indices] = mutation_values
        
        # 确保变异后的值在定义域范围内
        mutated_chromosome = np.clip(mutated_chromosome, lb, ub)
        
        return mutated_chromosome

    # 计算每一个DE得到的子问题的结果
    def evalute(self,fitness_function,index,lb,ub,F_Scale=0.5,mut_prob = 0):
        # 交叉
        cross_chrome = self.crossover_DE(index = index,lb=lb,ub=ub,F_Scale=F_Scale)
        #变异
        new_chrome = self.mutation_Gauniss(chrome=cross_chrome,lb=lb,ub=ub,prob=1/self.parDim)
        fit = self.cal_fitness(fitness_function=fitness_function,flag=0,chrome=new_chrome)
        return new_chrome,fit
    
    # 支配的计算
    def is_dominated(self, a, b):
        return np.all(a >= b) and np.any(a > b)

    # 找到当前的前沿
    def find_pareto_front(self):
        pareto_front = []
        for i in range(self.popSize):
            dominated = False
            for j in range(self.popSize):
                if self.is_dominated(self.fit[i], self.fit[j]):
                    dominated = True
                    break
            if not dominated:
                pareto_front.append(self.fit[i])
        return np.array(pareto_front)

    def plot_pareto_front(self, gen, pareto_front):
        plt.clf()
        plt.scatter(self.fit[:, 0], self.fit[:, 1], label='Population')
        plt.scatter(pareto_front[:, 0], pareto_front[:, 1], color='red', label='Pareto Front')
        plt.xlabel('Objective 1')
        plt.ylabel('Objective 2')
        plt.title(f'Pareto Front at Generation {gen}')
        # Plot ideal Pareto front
        f1_values = np.linspace(0, 1, 100)
        f2_values = 1 - np.sqrt(f1_values)
        plt.plot(f1_values, f2_values, 'g--', label='Ideal Pareto Front')
        plt.legend()
        plt.pause(0.1)  # Pause to update the plot

    def run(self,fitness_function,lb,ub):

        self.Init_Pop(fitness_function=fitness_function,lb=lb,ub=ub)    # 随机初始化种群并计算适应度
        self.init_weight_vector()       # 初始化权重向量和对应的邻居
        self.init_reference_point()     # 初始化参考点

        # 进行迭代
        plt.ion()  # Turn on interactive mode for dynamic plotting
        for i in range(self.iteration):
            for j in range(self.popSize):
                new_chrom,new_fit = self.evalute(fitness_function=fitness_function,index=j,
                                                    lb=lb,ub=ub)
                # 是否可以更新参考点
                self.idealPoint = np.minimum(self.idealPoint,new_fit)
                # 更新他的邻居
                self.update_neighbors_solution(index=j,offspring=new_chrom,offspring_eval=new_fit)
            pareto_front = self.find_pareto_front()
            self.plot_pareto_front(i, pareto_front)
        
        plt.ioff()  # Turn off interactive mode
        plt.show()

# objDIM:2
# parDim: >= 2
def ZDT1(chromosome):
    n = len(chromosome)
    f1 = chromosome[0]
    g = 1 + 9 * np.sum(chromosome[1:]) / (n - 1)
    h = 1 - np.sqrt(f1 / g)
    f2 = g * h
    return np.array([f1, f2])

if __name__ == "__main__":
    MOEA_ZDT = MOEAD(objDim = 2,parDim = 30)
    MOEA_ZDT.run(fitness_function=ZDT1,lb=0,ub=1)
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MOEA/D (Multi-Objective Evolutionary Algorithm Based on Decomposition)是一种多目标优化算法。它将多目标优化问题转化为多个单目标优化问题,并通过分解技术来解决这些单目标优化问题。下面是用Python实现MOEA/D的简单步骤: 1. 定义目标函数和变量范围。 2. 初始化种群,并为每个个体分配权重向量。 3. 计算每个个体的适应度值,并根据适应度值对种群进行排序。 4. 选择父代,并根据父代生成子代。 5. 对生成的子代进行修剪,保留最优的子代。 6. 更新参考点和权重向量,并将新的参考点和权重向量用于下一代进化。 下面是一个简单的Python代码示例: ```python import numpy as np # 定义目标函数 def obj_func(x): f1 = x**2 + x**2 f2 = (x-1)**2 + x**2 return [f1, f2] # 定义变量范围 x_min = np.array([-5, -5]) x_max = np.array([5, 5]) # 初始化种群和权重向量 pop_size = 100 num_obj = 2 pop = np.random.uniform(low=x_min, high=x_max, size=(pop_size, len(x_min))) weights = np.random.uniform(low=0, high=1, size=(pop_size, num_obj)) weights = weights / np.sum(weights, axis=1)[:, None] # 进行进化迭代 num_generations = 100 for gen in range(num_generations): # 计算适应度值并排序 fitness = np.array([obj_func(x) for x in pop]) ranks = np.argsort(np.argsort(-fitness.dot(weights.T), axis=0), axis=0) # 选择父代并生成子代 parents = np.random.choice(pop_size, size=pop_size) children = np.zeros_like(pop) for i in range(pop_size): parent1 = parents[i] parent2 = parents[(i+1) % pop_size] child = pop[parent1] + 0.5*(pop[parent2]-pop[parent1]) child += 0.01*np.random.normal(size=len(x_min)) child = np.clip(child, x_min, x_max) children[i] = child # 修剪子代并更新参考点和权重向量 fitness_children = np.array([obj_func(x) for x in children]) ranks_children = np.argsort(np.argsort(-fitness_children.dot(weights.T), axis=0), axis=0) pop_new = np.zeros_like(pop) for i in range(pop_size): if ranks_children[i] <= ranks[i]: pop_new[i] = children[i] else: pop_new[i] = pop[i] pop = pop_new weights = np.clip(weights + 0.01*np.random.normal(size=(pop_size, num_obj)), 0, 1) weights = weights / np.sum(weights, axis=1)[:, None] ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值