NSGA-Ⅱ(NSGA2)-多目标优化算法

本文介绍NSGA - Ⅱ算法,它是基于Pareto最优解的多目标优化算法,相比NSGA有诸多优势。文中阐述了快速非支配排序、拥挤度和拥挤比较算子、精英保留策略等概念,给出了快速非支配排序算法和拥挤度距离计算的伪码,最后提及了Python代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NSGA-Ⅱ(Non-dominated Sorting Genetic Algorithms-Ⅱ)算法,即带有精英保留策略快速非支配多目标优化算法,是一种基于Pareto最优解的多目标优化算法。在此感谢 A fast and elitist multiobjective genetic algorithm: NSGA-II 作者及其所著论文。

  • NSGA(Non-dominated Sorting Genetic Algorithms)

NSGA-Ⅱ相比,NSGA

  1. 时间复杂度 O ( M N 3 ) \Omicron(MN^3) O(MN3)
  2. 非精英机制
  3. 需要指定共享参数

NSGA-优势

  1. 提出了快速非支配排序算法 时间复杂度 O ( M N 2 ) \Omicron(MN^2) O(MN2)
  2. 引入精英策略
  3. 在NSGA2中用拥挤比较的方法替换了共享函数方法。要实现这两种方法,首先我们需要定义两个操作:拥挤度和拥挤比较算子

  • Pareto最优解

帕累托(Pareto)最优解其实与上述的支配关系类似,即:假定固有的一群人和可分配的资源,从一种分配状态到另一种状态的变化中,在没有使任何人境况变坏的前提下,使得至少一个人变得更好,这就是帕累托改进,这样迭代下去得到最优解的过程被称为帕累托最优化


  • 快速非支配排序

首先想要单独说说什么是支配性策略,以下是百科解释
        一个博弈当中,无论对手采取什么策略,你若有几个策略,而其中一个策略可以使你得到比采取其他策略更好的结果,那么,这个策略就是你的优势策略。
延伸出来的有一个非常著名的博弈论术语:纳什均衡。简单概括的话,有一个非常常见且现实的例子,就是:价格战。
纳什均衡:
        在一个博弈过程中,无论对方的策略选择如何,当事人一方都会选择某个确定的策略,则该策略被称作支配性策略。如果任意一位参与者在其他所有参与者的策略确定的情况下,其选择的策略是最优的,那么这个组合就被定义为纳什均衡。
        一个策略组合被称为纳什均衡,当每个博弈者的均衡策略都是为了达到自己期望收益的最大值,与此同时,其他所有博弈者也遵循这样的策略。

纳什均衡包括了纯策略纳什均衡 和 混合策略纳什均衡。所谓纯策略是指非0即1的策略概率,即某一策略选择概率为1,则其余策略不被选择,即概率为0。而混合策略是对策略集中的各策略分配了概率,即博弈者根据概率随机选择策略,在某种概率集合下,博弈者 a a a可以达到最优效益(这个最优往往是动态的,因为现实情况是每位博弈者的决策相互影响)。

说回快速非支配排序。所谓的支配,是指在优化问题中,某一个解 x i x_i xi N N N维下的目标函数上均优于 x j x_j xj,这样可以将二者关系称为: x i x_i xi支配 x j x_j xj
∄ ∀ x j \nexists\forall x_j xj优于 x i x_i xi,则标记 x i x_i xi为非支配个体。(即在 N N N维目标函数下均不优于 x i x_i xi
通过上述步骤得到的是非支配个体集是种群的第 n n n级非支配层,然后忽略已标记的非支配个体,再次进行该算法,则会得到第 n + 1 n+1 n+1级非支配层。

  • 拥挤度和拥挤比较算子

拥挤度:指的是某解 x i x_i xi周围附近的解的数量,这个数值以最近邻居作为顶点的长方形周长。通常通过拥挤度来保证种群多样性
具体计算步骤:
①初始化每个点的拥挤度: i d = 0 i_d=0 id=0
②对种群进行非支配排序,令边界的2个个体为无穷大,即: O d = I d = ∞ O_d = I_d = \infty Od=Id=
③对其余 ( i − 2 ) (i-2) (i2)个个体进行拥挤度计算: i d = ∑ j = 1 m ( ∣ f j i + 1 − f j i − 1 ∣ ) i_d = \sum_{j=1}^m (|f_j^{i+1} - f_j^{i-1}|) id=j=1m(fji+1fji1)
其中: i d i_d id表示 i i i点的拥挤度, f j i + 1 f_j^{i+1} fji+1表示 i + 1 i+1 i+1点的第 j j j目标函数值, f j i − 1 f_j^{i-1} fji1表示 i − 1 i-1 i1点的第 j j j目标函数值。

拥挤比较算子:比较算子的前提是经过了快速非支配排序和拥挤度的计算之后,种群中的每个个体 i i i都有两个属性——非支配排序得到的非支配序 i r a n k i_{rank} irank(第几级)和拥挤度 i d i_d id
根据这两个属性,我们就可以定义拥挤比较算子:以个体 i i i与个体 j j j比较,若以下 ∀ \forall 条件成立,则个体 i i i获胜。
i r a n k < j r a n k i_{rank}<j_{rank} irank<jrank,个体 i i i所处的非支配层优于个体 j j j。选择的个体属于较优的非劣等级。
i r a n k = j r a n k ∩ i d > j d i_{rank}=j_{rank} \cap i_d>j_d irank=jrankid>jd,个体 i i i和个体 j j j拥有相同的层级,但个体 i i i有更大的拥挤距离(拥挤度)。即选择较不拥挤区域的个体。

  • 精英保留策略

简要来说就是:对遗传算法来说,能否收敛到全局最优解是其首要问题。

接下来展开说,何为精英策略
将第 t t t代的产生的新种群 Q t Q_t Qt与父代 P t P_t Pt结合组成合集 R t R_t Rt,种族大小为 2 N 2N 2N。然后对 R t R_t Rt进行非支配排序,产生非支配集 Z i Z_i Zi并计算拥挤度。如图所示 Z 1 Z_1 Z1 R t R_t Rt中最优的,直接放入新的父代种群 P t + 1 P_{t+1} Pt+1中;如果新种群 P t + 1 P_{t+1} Pt+1个体数量小于 N N N则继续填充下一级非支配集 Z 2 Z_2 Z2。如图所示, Z 3 Z_3 Z3如果加入则超出 N N N,此时对 Z 3 Z_3 Z3中的个体使用拥挤度比较算子,使得 P t + 1 P_{t+1} Pt+1个体数量达到N然后通过遗传算子(选择、交叉、变异)产生新的子代种群 Q t + 1 Q_{t+1} Qt+1
精英策略流程简述图

上流程图~

Created with Raphaël 2.3.0 开始 初始化第1代种群 计算每个个体的所有目标 快速非支配排序 拥挤度距离计算 迭代次数大于最大代数 输出pareto前沿 结束 选择优秀个体组成新的父代 选择、交叉、变异 父代与子代合并 yes no

算法伪码

快速非支配排序算法

n p n_p np:解 p p p被几个解所支配,是一个数值(左下部分点的个数)
S p S_p Sp:解 p p p支配哪些解,是一个解集合(右上部分点的内容)
F i F_i Fi:第 i i i层的解集合(显然同一层内的解互相都是非支配解)
p r a n k p_{rank} prank:解 p p p所在层的等级(越小越好)

fast-non-dominated-sort( P P P)
for each p ∈ P p \in P pP
     S p = ∅ S_p=\empty Sp=
     n p = 0 n_p=0 np=0
    for each q ∈ P q \in P qP
        if ( p < q p<q p<q) then                if p p p dominates q q q
             S p = S p ∪ { q } S_p=S_p\cup\{q\} Sp=Sp{q}        Add q q q to the set of solutions dominated by p p p
        else if ( q < p q<p q<p) then
             n p = n p + 1 n_p=n_p+1 np=np+1            Increment the domination counter of p p p
    if n p = 0 n_p=0 np=0 then                     p p p belongs to the first front
         p r a n k = 1 p_{rank}=1 prank=1
         F 1 = F 1 ∪ { p } F_1=F_1\cup\{p\} F1=F1{p}
i = 1 i=1 i=1                                      initialize the front counter
while F i ≠ ∅ F_i\ne\empty Fi=
     Q = ∅ Q=\empty Q=                                Used to store the members of the next front
    for each p ∈ F i p \in F_i pFi
        for each q ∈ F i q \in F_i qFi
             n q = n q − 1 n_q=n_q-1 nq=nq1
            if n q = 0 n_q=0 nq=0 then             q q q belongs to the next front
                 q r a n k = i + 1 q_{rank}=i+1 qrank=i+1
                 Q = Q ∪ { q } Q=Q\cup\{q\} Q=Q{q}
i = i + 1 i=i+1 i=i+1
F i = Q F_i=Q Fi=Q

拥挤度距离计算

crowding-distance-assignment( I I I)
l = ∣ I ∣ l = |I| l=I                                                    number of solutions in I I I
for each i i i, set I [ i ] d i s t a n c e = 0 I[i]_{distance}=0 I[i]distance=0              initialize distance
for each objective m m m
     I = I= I=sort ( I , m ) (I,m) (I,m)                                   sort using each objective value
     I [ 1 ] d i s t a n c e = I [ l ] d i s t a n c e = ∞ I[1]_{distance}=I[l]_{distance}=\infty I[1]distance=I[l]distance=      so that boundary points are always selected
    for i = 2 i=2 i=2 to ( l − 1 ) (l-1) (l1)                           for all other points
         I [ i ] d i s t a n c e = I [ i ] d i s t a n c e + ( I [ i + 1 ] ∗ m − I [ i − 1 ] ∗ m ) / ( f m m a x − f m m i n ) I[i]_{distance}=I[i]_{distance}+(I[i+1]*m-I[i-1]*m)/(f_m^{max}-f_m^{min}) I[i]distance=I[i]distance+(I[i+1]mI[i1]m)/(fmmaxfmmin)

Python代码实现

"""
优化目标:
    min(f1(x), f2(x))
        f1(x) = -x^2
        f2(X) = -(x-2)^2
    s.t x~[-55, 55]
pop_size = 20
max_gen  =  921
"""
import math
import random
import matplotlib.pyplot as plt


def function1(x):
    """
    First function to optimize
    :param x: 自变量
    :returns: 函数计算结果
    """
    return -x ** 2


def function2(x):
    """
    Second function to optimize
    :param x: 自变量
    :returns: 函数计算结果
    """
    return -(x - 2) ** 2


def index_of(a, list):
    """
    Function to find index of list
    :param a: 待查找的目标值
    :param list: 列表
    :returns: 列表中对于值的索引值,不存在返回-1
    """
    for i in range(0, len(list)):
        if list[i] == a:
            return i
    return -1


def sort_by_values(list1, values):
    """
    Function to sort by values
    :param list1:
    :param values:
    :returns:
    """
    sorted_list = []
    while len(sorted_list) != len(list1):
        if index_of(min(values), values) in list1:
            sorted_list.append(index_of(min(values), values))
        values[index_of(min(values), values)] = math.inf
    return sorted_list


def fast_non_dominated_sort(values1, values2):
    """
    Function to carry out NSGA-II's fast non-dominated sort
    :param values1: 种群1
    :param values2: 种群2
    :return: 该代的种群集合
    """
    S = [[] for i in range(0, len(values1))]  # 支配解
    front = [[]]  # 集合
    n = [0 for i in range(0, len(values1))]   # 支配个体数
    rank = [0 for i in range(0, len(values1))]  # 支配层级

    for p in range(0, len(values1)):
        S[p] = []
        n[p] = 0
        for q in range(0, len(values1)):
            if (values1[p] > values1[q] and values2[p] > values2[q]) or \
                    (values1[p] >= values1[q] and values2[p] > values2[q]) or \
                    (values1[p] > values1[q] and values2[p] >= values2[q]):
                if q not in S[p]:
                    S[p].append(q)  # p支配q, 把q加入p的支配解中
            elif (values1[q] > values1[p] and values2[q] > values2[p]) or \
                    (values1[q] >= values1[p] and values2[q] > values2[p]) or \
                    (values1[q] > values1[p] and values2[q] >= values2[p]):
                n[p] = n[p] + 1  # 支配解个数+1
        if n[p] == 0:
            rank[p] = 0
            if p not in front[0]:
                front[0].append(p)

    i = 0  # 初始化种群个数
    while front[i]:
        Q = []
        for p in front[i]:
            for q in S[p]:
                n[q] = n[q] - 1  # 支配解个数-1
                if n[q] == 0:
                    rank[q] = i + 1
                    if q not in Q:
                        Q.append(q)
        i = i + 1
        front.append(Q)

    del front[len(front) - 1]
    return front


def crowding_distance(values1, values2, front):
    """
    Function to calculate crowding distance
    :param values1: 种群1
    :param values2: 种群2
    :param front:
    :return:
    """
    distance = [0 for i in range(0, len(front))]
    sorted1 = sort_by_values(front, values1[:])
    sorted2 = sort_by_values(front, values2[:])
    distance[0] = 4444444444444444  # infinite
    distance[len(front) - 1] = 4444444444444444  # infinite
    for k in range(1, len(front) - 1):
        distance[k] = distance[k] + (values1[sorted1[k + 1]] - values2[sorted1[k - 1]]) / (max(values1) - min(values1))
    for k in range(1, len(front) - 1):
        distance[k] = distance[k] + (values1[sorted2[k + 1]] - values2[sorted2[k - 1]]) / (max(values2) - min(values2))
    return distance


def crossover(a, b):
    """
    Function to carry out the crossover
    :param a:
    :param b:
    :return:
    """
    r = random.random()
    if r > 0.5:
        return mutation((a + b) / 2)
    else:
        return mutation((a - b) / 2)


def mutation(solution):
    """
    Function to carry out the mutation operator
    :param solution:
    :return:
    """
    mutation_prob = random.random()
    if mutation_prob < 1:
        solution = min_x + (max_x - min_x) * random.random()
    return solution


# Main program starts here
pop_size = 20  # 种群大小
max_gen = 1821

# Initialization
min_x = -55
max_x = 55
# 随机生成初始种群
solution = [min_x + (max_x - min_x) * random.random()for i in range(0, pop_size)]
gen_no = 0

while gen_no < max_gen:  # 达到最大值停止
    # 自适应度计算
    function1_values = [function1(solution[i]) for i in range(0, pop_size)]  # 根据函数生成 种群数 数量大小的值, 即约束
    function2_values = [function2(solution[i]) for i in range(0, pop_size)]  # 根据函数生成 种群数 数量大小的值, 即约束

    # pareto等级
    non_dominated_sorted_solution = fast_non_dominated_sort(function1_values[:], function2_values[:])
    print("The best front for Generation number ", gen_no, " is")
    for val in non_dominated_sorted_solution[0]:
        print(round(solution[val], 3), end=" ")
    print("\n")
    # 拥挤度距离计算·
    crowding_distance_values = []
    for i in range(0, len(non_dominated_sorted_solution)):
        crowding_distance_values.append(
            crowding_distance(function1_values[:], function2_values[:], non_dominated_sorted_solution[i][:]))
    solution2 = solution[:]  # P+Q
    # Generating offsprings
    while len(solution2) != 2 * pop_size:
        a1 = random.randint(0, pop_size - 1)
        b1 = random.randint(0, pop_size - 1)
        # 交叉变异
        solution2.append(crossover(solution[a1], solution[b1]))
    # 计算 P+Q种群的适应度
    function1_values2 = [function1(solution2[i]) for i in range(0, 2 * pop_size)]
    function2_values2 = [function2(solution2[i]) for i in range(0, 2 * pop_size)]
    # 非支配排序
    non_dominated_sorted_solution2 = fast_non_dominated_sort(function1_values2[:], function2_values2[:])
    # 拥挤度距离计算
    crowding_distance_values2 = []
    for i in range(0, len(non_dominated_sorted_solution2)):
        crowding_distance_values2.append(
            crowding_distance(function1_values2[:], function2_values2[:], non_dominated_sorted_solution2[i][:]))
    # 得到下一代种群P1
    new_solution = []  # index
    for i in range(0, len(non_dominated_sorted_solution2)):
        non_dominated_sorted_solution2_1 = [
            index_of(non_dominated_sorted_solution2[i][j], non_dominated_sorted_solution2[i]) for j in
            range(0, len(non_dominated_sorted_solution2[i]))]
        front22 = sort_by_values(non_dominated_sorted_solution2_1[:], crowding_distance_values2[i][:])
        front = [non_dominated_sorted_solution2[i][front22[j]] for j in
                 range(0, len(non_dominated_sorted_solution2[i]))]
        front.reverse()
        for value in front:
            new_solution.append(value)
            if len(new_solution) == pop_size:
                break
        if len(new_solution) == pop_size:
            break
    solution = [solution2[i] for i in new_solution]
    gen_no = gen_no + 1

# Lets plot the final front now
function1 = [i * -1 for i in function1_values]
function2 = [j * -1 for j in function2_values]
plt.xlabel('Function 1', fontsize=15)
plt.ylabel('Function 2', fontsize=15)
plt.scatter(function1, function2)
plt.show()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值