NSGA-II 遗传多目标算法(python示例)

news2024/9/24 19:25:35

一、前言

        最近在准备毕业论文,研究了一下主流的多目标算法,对于NSGA-II,网上大部分代码是全部是面向过程来实现的,本人更喜欢采用面向对象的方式,故采用python面向对象实现了一个示例,实现了对于二元多目标问题的求解。

二、算法基本流程

三、核心思想

1、非支配排序

这个简单的例子说明了帕累托最优的概念。上面我们有4个成员A, B, C和D,有两个特征:身高和工资。现在,如果我们同时比较他们的身高和薪水,我们会发现这不是很直观,因为他们有多个目标。

既然这两个目标越大越好,我们可以简单地对它们进行比较。首先,我们观察到A和B都比C和D多,所以我们说A和B在身高和薪水上“支配”C和D。同理,C支配D,D可被A,B,C支配。

A和B呢?A比B高,但是工资低。相反,B面临着同样的情况。我们称这种情况为“非支配”。 如果我们能找到一组解它们不互相支配,也不受其他解支配,我们称之为"帕累托最优"解。在上面的例子中,A和B都在帕累托最优前沿

几个概念:

非支配解:假设任何二解S1 及S2 对所有目标而言,S1均优于S2,则我们称S1 支配S2,若S1 的解没有被其他解所支配,则S1 称为非支配解(不受支配解),也称Pareto解(帕雷托解)

支配解:若解S2的所有目标均劣于S1,则称S1优于S2,也称S1支配S2,S2为受支配解。

Pareto前沿面:找到所有Pareto解之后,这些解组成的平面叫做Pareto前沿面(Non-dominated front)。在目标函数较多时,前沿面通常为超曲面。

2、拥挤度

通俗的来讲,当需要舍弃某一rank平面的部分节点时,由于同一平面中的所有节点rank相同,不能通过rank来舍弃,而是要通过拥挤度来舍弃,以上就是拥挤度的作用。

算法更倾向于稀疏的点,也就是让节点更可能的分散,可以有效地方式早熟和过拟合现象

3、精英选择策略

每次都将父类与子类想结合,依次采用非支配排序、计算拥挤度来选择父代。

四、python实现

github:   源代码地址

"""
author: pym
time: 2023.10.29
ide: pycharm2
"""

from collections import defaultdict
import numpy as np
import random
import matplotlib.pyplot as plt
import math

class Individual(object):
    def __init__(self):
        self.solution = None    # 实际为nparray类型,方便四则运算
        self.objective = defaultdict()

        self.n = 0              # 解p被几个解支配
        self.rank = 0           # 解p所在层数
        self.S = []             # 解p支配解的集合
        self.distance = 0       # 拥挤度距离

    def bound_process(self, bound_min, bound_max):
        """
        对解向量 solution 中的每个分量进行定义域判断;超过最大值赋为最大值
        :param bound_min: 定义域下限
        :param bound_max: 定义域上限
        :return:
        """
        for i, item in enumerate(self.solution):
            if item > bound_max:
                self.solution[i] = bound_max
            elif item < bound_min:
                self.solution[i] = bound_min

    def calculate_objective(self, objective_fun):
        """
        计算目标值
        :param objective_fun: 目标函数
        :return:
        """
        self.objective = objective_fun(self.solution)

    def __lt__(self, other):
        """
        重载小于号,只有当solution中全部小于对方,才判断小于
        :param other: 比较的个体
        :return: 1:小于 0:大于
        """
        v1 = list(self.objective.values())
        v2 = list(other.objective.values())
        for i in range(len(v1)):
            if v1[i] > v2[i]:
                return 0
        return 1

def fast_non_dominated_sort(P):
    """
    非支配排序
    :param P: 种群P
    :return: F:分层结果,返回值类型为dict,键为层号,值为list(该层中的个体)
    """
    F = defaultdict(list)

    for p in P:
        p.S = []
        p.n = 0
        for q in P:
            if p < q:       # p支配q
                p.S.append(q)
            elif q < p:     # q支配p
                p.n += 1
        if p.n == 0:
            p.rank = 1
            F[1].append(p)
    i = 1
    while F[i]:
        Q = []
        for p in F[i]:
            for q in p.S:
                q.n -= 1
                if q.n == 0:
                    q.rank = i + 1
                    Q.append(q)
        i += 1
        F[i] = Q
    return F

def crowding_distance_assignment(L):
    """
    计算拥挤度
    :param L: F[i],是个list,为第i层的节点集合
    :return:
    """
    l = len(L)
    # 初始化距离
    for i in range(l):
        L[i].distance = 0
    # 遍历每个目标方向(有几个优化目标,就有几个目标方向)
    for m in L[0].objective.keys():
        L.sort(key=lambda x: x.objective[m])    # 使用objective值排序
        L[0].distance = float('inf')
        L[l - 1].distance = float('inf')
        f_max = L[l - 1].objective[m]
        f_min = L[0].objective[m]
        # 当某一个目标方向上的最大值和最小值相同时,会出现除0错误
        try:
            for i in range(1, l - 1):
                L[i].distance = L[i].distance + (L[i + 1].objective[m] - L[i - 1].objective[m]) / (f_max - f_min)
        except Exception:
            print(str(m) + "目标方向上,最大值为:" + str(f_max) + " 最小值为:" + str(f_min))

def binary_tornament(ind1, ind2):
    """
    二元锦标赛:先选非支配排序靠前的,再选拥挤度低(即距离远);如果都不行,则随机
    :param ind1: 个体1
    :param ind2: 个体1
    :return: 返回较优的个体
    """
    if ind1.rank != ind2.rank:
        return ind1 if ind1.rank < ind2.rank else ind2
    elif ind1.distance != ind2.distance:
        return ind1 if ind1.distance > ind2.distance else ind2
    else:
        return ind1

def crossover_mutation(parent1, parent2, eta, bound_min, bound_max, objective_fun):
    """
    交叉:二进制交叉算子(SBX),变异:多项式变异(PM)
    :param parent1: 父代1
    :param parent2: 父代2
    :param eta: 变异参数,越大则后代个体越逼近父代
    :return:
    """
    poplength = len(parent1.solution)   # 解向量维数
    # 初始化两个后代个体
    offspring1 = Individual()
    offspring2 = Individual()
    offspring1.solution = np.empty(poplength)
    offspring2.solution = np.empty(poplength)
    # 二进制交叉
    for i in range(poplength):
        rand = random.random()
        if rand < 0.5:
            beta = (rand * 2) ** (1 / (eta + 1))
        else:
            beta = (1 / (2 * (1 - rand)))**(1 / (eta + 1))
        offspring1.solution[i] = 0.5 * ((1 + beta) * parent1.solution[i] + (1 - beta) * parent2.solution[i])
        offspring2.solution[i] = 0.5 * ((1 - beta) * parent1.solution[i] + (1 + beta) * parent2.solution[i])
    # 多项式变异
    for i in range(poplength):
        mu = random.random()
        if mu < 0.5:
            delta = 2 * mu ** (1 / (eta + 1))
        else:
            delta = (1 - (2 * (1 - mu)) ** (1 / (eta + 1)))
        # 只变异一个
        offspring1.solution[i] = offspring1.solution[i] + delta
    offspring1.bound_process(bound_min, bound_max)
    offspring2.bound_process(bound_min, bound_max)
    offspring1.calculate_objective(objective_fun)
    offspring2.calculate_objective(objective_fun)
    return [offspring1, offspring2]

def make_new_pop(P, eta, bound_min, bound_max, objective_fun):
    """
    选择交叉变异获得新后代
    :param P: 父代种群
    :param eta: 变异参数,越大则后代个体越逼近父代
    :param bound_min: 定义域下限
    :param bound_max: 定义域上限
    :param objective_fun: 目标函数
    :return: 子代种群
    """
    popnum = len(P)     # 种群个数
    Q = []
    # 二元锦标赛选择
    for i in range(int(popnum / 2)):
        # 从种群中随机选择两个个体,进行二元锦标赛,选择一个parent
        i = random.randint(0, popnum - 1)
        j = random.randint(0, popnum - 1)
        parent1 = binary_tornament(P[i], P[j])
        parent2 = parent1
        while (parent1.solution == parent2.solution).all():     # 小细节all
            i = random.randint(0, popnum - 1)
            j = random.randint(0, popnum - 1)
            parent2 = binary_tornament(P[i], P[j])
        Two_offspring = crossover_mutation(parent1, parent2, eta, bound_min, bound_max, objective_fun)
        Q.append(Two_offspring[0])
        Q.append(Two_offspring[1])
    return Q

def KUR(x):
    """
    计算各个目标方向上的目标值
    :param x: 解向量
    :return: 字典:各个方向上的目标值(key:目标方向;value:目标值)
    """
    f = defaultdict(float)
    poplength = len(x)
    f[1] = 0
    f[2] = 0
    for i in range(poplength - 1):
        f[1] = f[1] + (-10) * math.exp((-0.2) * (x[i] ** 2 + x[i + 1] ** 2) ** 0.5)
    for i in range(poplength):
        f[2] = f[2] + abs(x[i]) ** 0.8 + 5 * math.sin(x[i] ** 3)
    return f

def plot_P(P):
    """
    给种群绘图
    :param P: 种群集合
    :return:
    """
    X = []
    Y = []
    for ind in P:
        X.append(ind.objective[1])
        Y.append(ind.objective[2])
    plt.xlabel('F1')
    plt.ylabel('F2')
    plt.scatter(X, Y)

def main():
    # 初始化参数
    generations = 250   # 迭代次数
    popnum = 100        # 种群大小
    eta = 1             # 变异分布参数
    poplength = 3       # 单个个体解向量的维数
    bound_min = -5
    bound_max = 5
    objective_fun = KUR

    # 生成第一代种群
    P = []
    for i in range(popnum):
        P.append(Individual())
        P[i].solution = np.random.rand(poplength) * (bound_max - bound_min) + bound_min
        P[i].bound_process(bound_min, bound_max)    # 越界处理
        P[i].calculate_objective(objective_fun)     # 计算目标值

    # 快速非支配排序
    fast_non_dominated_sort(P)
    Q = make_new_pop(P, eta, bound_min, bound_max, objective_fun)
    P_t = P     # 当前这一代的父代种群
    Q_t = Q     # 当前这一代的子代种群
    for gen_cur in range(generations):
        R_t = P_t + Q_t
        F = fast_non_dominated_sort(R_t)
        P_n = []    # 即为P_t+1,表示下一代的父代
        i = 1
        # 依次将最高级别的支配平面中的节点放入到P_n中,之后更新非支配,直到达到要求的规模
        while len(P_n) + len(F[i]) < popnum:
            crowding_distance_assignment(F[i])
            P_n += F[i]
            i += 1
        # 按照支配排序选完之后,再按照拥挤度来选择
        F[i].sort(key=lambda x: x.distance)
        P_n = P_n + F[i][:popnum - len(P_n)]
        Q_n = make_new_pop(P_n, eta, bound_min, bound_max, objective_fun)

        # 将下一届的父代和子代成为当前的父代和子代
        P_t = P_n
        Q_t = Q_n

        # 可视化
        plt.clf()
        plt.title("current generation: " + str(gen_cur + 1))
        plot_P(P_t)
        plt.pause(0.1)

    plt.show()
    return 0



if __name__ == "__main__":
    main()






本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1152131.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1400*C. Element Extermination(贪心规律)

Problem - 1375C - Codeforces 解析&#xff1a; 可以发现&#xff0c;最左端的数字&#xff0c;无论删除自己还是下一个&#xff0c;这个位置的值都不会变小。 同理&#xff0c;最右端位置的值都不会变大。 所以当最后剩余两个数字的时候&#xff0c;只有左端小于右端数字&…

STM32—PWM开发SG90舵机

目录 PWM介绍 PWM输出模式&#xff1a; ​编辑PWM占空比&#xff1a; PWM周期与频率公式&#xff1a;​编辑 SG90舵机介绍 1. 什么是舵机 2. 怎么控制舵机 SG90舵机介绍实战 1. 在 SYS 选项里&#xff0c;将 Debug 设为 Serial Wire​编辑 2. 将 RCC 里的 HSE 设置为 …

Vue的快速入门

Vue的快速入门 下载并安装vue.js Vue是一个基于JavaScript实现的框架, 要使用它就需要从[Vue官网]((https://cn.vuejs.org/)下载 vue.js 文件 第一步&#xff1a;打开Vue2官网&#xff0c;点击下图所示的“起步” 第二步&#xff1a;继续点击下图所示的“安装” 第三步&…

二叉树的遍历+二叉树的基本操作

文章目录 二叉树的操作一、 二叉树的存储1.二叉树的存储结构 二、 二叉树的基本操作1.前置创建一棵二叉树&#xff1a;1. 定义结点 2.简单的创建二叉树 2.二叉数的遍历1.前序遍历2.中序遍历3.后序遍历4.层序遍历 3.二叉树的操作1.获取树中节点的个数2.获取叶子节点的个数3.获取…

PHP的Excel导出与导入

下载地址&#xff08;注意php版本大于7.3可能会报错&#xff09; GitHub - PHPOffice/PHPExcel: ARCHIVED 解压 1、导出 Excel $data[[name>a,age>11],[name>b,age>22],[name>d,age>33], ]; $fileds["name">"名称","age"…

在Java和PostgreSQL枚举之间进行转换的通用方法

枚举类型&#xff08;enum&#xff09;是一种方便的数据类型&#xff0c;允许我们指定一个常量列表&#xff0c;对象字段或数据库列可以设置为该列表中的值。 枚举的美妙之处在于我们可以通过提供人类可读格式的枚举常量来确保数据完整性。因此&#xff0c;Java和PostgreSQL原…

MySQL 8.2 支持读写分离!

我们一直在等待的 MySQL 读/写分离功能 现在终于可以使用了&#xff01; 在规模上&#xff0c;我们在副本之间分配读取&#xff0c;但这必须在应用程序中以某种方式进行管理&#xff1a;指向在某个地方写入并在其他地方读取。 在 MySQL 8.2 中&#xff0c;MySQL Router 现在能…

SSH 无密登录设置

1 &#xff09; 配置 ssh &#xff08;1&#xff09;基本语法 ssh 另一台电脑的 IP 地址&#xff08;2&#xff09;ssh 连接时出现 Host key verification failed 的解决方法 [libaihadoop102 ~]$ ssh hadoop103 ➢ 如果出现如下内容 Are you sure you want to continue c…

设计模式(20)职责链模式

一、介绍&#xff1a; 1、定义&#xff1a;责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为设计模式&#xff0c;使多个对象都有机会处理请求&#xff0c;从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链&#xff0c;并沿着…

SpringSecurity6从入门到上天系列第二篇:搭建SpringSecurity6的入门级别程序!

文章目录 前言 1&#xff1a;环境要求 2&#xff1a;技术要求 一&#xff1a;搭建SpringBoot环境 1&#xff1a;创建空项目 2&#xff1a;创建SpringBoot项目 3&#xff1a;编写一个简单的controller 二&#xff1a;整合SpringSecurity 1&#xff1a;引入依赖 2&…

微信小程序设计之目录结构其他文件介绍

一、新建一个项目 首先&#xff0c;下载微信小程序开发工具&#xff0c;具体下载方式可以参考文章《微信小程序开发者工具下载》。 然后&#xff0c;注册小程序账号&#xff0c;具体注册方法&#xff0c;可以参考文章《微信小程序个人账号申请和配置详细教程》。 在得到了测…

Linux基础环境开发工具的使用(yum,vim,gcc,g++)

Linux基础环境开发工具的使用[yum,vim,gcc,g] 一.yum1.yum的快速入门1.yum安装软件2.yum卸载软件 2.yum的生态环境1.操作系统的分化2.四个问题1.服务器是谁提供的呢?2.服务器上的软件是谁提供的呢?3.为什么要提供呢?4.yum是如何得知目标服务器的地址和下载链接呢?5.软件源 …

XML教学视频(黑马程序员精讲 XML 知识!)笔记

第一章XML概述 1.1认识XML XML数据格式&#xff1a; 不是html但又和html有点相似 XML数据格式最主要的功能就是数据传输&#xff08;一个服务器到另一个服务器&#xff0c;一个网站到另一个网站&#xff09;配置文件、储存数据当做小型数据可使用、规范数据格式让数据具有结…

TypeScript深度剖析:TypeScript 中接口的应用场景?

一、是什么 接口是一系列抽象方法的声明&#xff0c;是一些方法特征的集合&#xff0c;这些方法都应该是抽象的&#xff0c;需要由具体的类去实现&#xff0c;然后第三方就可以通过这组抽象方法调用&#xff0c;让具体的类执行具体的方法 简单来讲&#xff0c;一个接口所描述…

PHP自定义文件缓存实现

文件缓存&#xff1a;可以将PHP脚本的执行结果缓存到文件中。当一个PHP脚本被请求时&#xff0c;先查看是否存在缓存文件&#xff0c;如果存在且未过期&#xff0c;则直接读取缓存文件内容返回给客户端&#xff0c;而无需执行脚本 1、文件缓存写法一&#xff0c;每个文件缓存一…

优化改进YOLOv5算法:加入SPD-Conv模块,让小目标无处遁形——(超详细)

1 SPD-Conv模块 论文:https://arxiv.org/pdf/2208.03641v1.pdf 摘要:卷积神经网络(CNNs)在计算即使觉任务中如图像分类和目标检测等取得了显著的成功。然而,当图像分辨率较低或物体较小时,它们的性能会灾难性下降。这是由于现有CNN常见的设计体系结构中有缺陷,即使用卷积…

对PySide6 say Hello(包含环境配置) ——PyQt

前言 一直想学一下python&#xff0c;特别是十一前抢票时达到顶峰。我正好是Qter&#xff0c;所以在网上找了一个教程直接学PyQt。 配置PyQt环境 当前环境 Win10Qt5.15.2 python3.11 之前安装python时好像自动安装了python的包管理工具pip&#xff0c;配置pyqt环境所需要安装…

css:transform实现平移、旋转、缩放、倾斜元素

目录 文档语法示例旋转元素 transform-rotate旋转过渡旋转动画 参考文章 文档 https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform 语法 /* Keyword values */ transform: none;/* Function values */ transform: matrix(1, 2, 3, 4, 5, 6); transform: translate…

c/c++程序的内存开辟时 的内存情况

我们写的代码都是要存放在内存空间中的&#xff0c;我们经常说堆区&#xff0c;静态区&#xff0c;还有栈区&#xff0c;相信很多人不是很明白&#xff0c;在今天这篇博客中让大家对它们有一个粗略的认识 1.栈区&#xff08;static&#xff09; 在执行函数时&#xff0c;函数内…

H5web微信刮一刮效果

H5web微信刮一刮效果 <!DOCTYPE html> <html> <head> <meta charset"utf-8" /> <title>千万别来南宁</title> <meta name"apple-touch-fullscreen" content"yes" /> <meta name"format-detec…