0. 前言
在现实世界中,许多问题涉及到多个目标的权衡和优化,多目标优化算法(Multi-Objective Optimization Algorithms,MOOA)就是指在满足约束的前提下,同时考虑多个目标,寻找合适的决策方案。
1. Pareto 解
多目标优化问题与单目标优化问题的主要差异在于:当只有一个目标函数时,算法所寻找到的解会优于其他所有解,通常是全局最大或最小,即全局最优解;而当存在多个目标时,不同的目标之间往往无法直接比较,也就是说,一个解对于一个目标函数来说可能是最好的,但对于其他的目标函数可能不是最好的,甚至可能是最差的。
因此,这类问题通常存在一组具有不同取舍的最优解,对于其中任何一个解,都无法在不恶化至少一个其它目标的情况下,进一步改善某个目标。我们将这种在整个可行解空间中不被任何其它可行解支配的解,称为 Pareto 最优解(Pareto optimal solution) ,所有Pareto 最优解构成 Pareto 最优解集(Pareto optimal set) , 它们对应的目标值向量构成 Pareto 前沿(Pareto frontier)。

MOO 问题的常见解法大致分为以下两类:
- 传统标量化方法(将多目标转化为单目标):这类方法的核心思想是通过某种数学变换,将多个目标组合或转换成一个或多个目标来求解。
- 权重法:为每个目标赋予一个权重,然后将多个目标线性加权求和,转换为单目标优化问题
- 字典序法:将所有目标安重要程度严格排序
- 目标规划法、\(\epsilon\)-约束法等
- 多目标进化算法(MOEAs):由于传统方法通常只能通过多次运行求出一组解,而遗传算法(基于种群)具有天然的并行搜索优势,能在单次运行中得到一组均匀分布的 Pareto 最优解集。
- NSGA 系列:包括 NSGA-II 和 NSGA-III
- MOEA/D
- SPEA 2
这里介绍 NSGA-II 算法。
2. 遗传算法
2.1 定义
遗传算法(Genatic Algorithm,GA)是模拟达尔文生物进化论的自然选择和遗传学的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法。通过模拟含有不同编码种群发生交叉、突变的过程来提高解的多样性,遗传算法能够在解空间中探索出不同的解决方案,并构造特定的适应度函数和选择来使解逐渐收敛达到最优,对比传统的优化算法,遗传算法在很多难以给出数学表示以及非多项式时间复杂度的问题中有很好的表现,且更不容易陷入局部最优状态。

2.2 步骤
遗传算法的流程图大致如下:

2.2.1 种群与编码
在遗传算法中,一个候选解可以看作一个个体,多个个体共同构成一个种群。算法并不是只从一个解出发不断改进,而是让一组候选解同时参与搜索,通过种群的不断更新,探索解空间中的不同区域。
在开始搜索之前,首先需要确定如何表示一个解,也就是设计染色体的编码方式。这里的染色体 指一个候选解的编码,基因则是编码中的某个位置。
编码并不局限于二进制,也可以直接采用整数、实数或者排列等形式,具体取决于问题中变量的类型。确定编码后,就可以在变量的取值范围内随机生成一定数量的个体,构成初始种群。
2.2.2 交叉与变异
有了初始种群之后,就需要通过已有的个体产生新的候选解。这里主要涉及两个操作:交叉(crossover) 与变异(mutation),前者重组已有个体的信息,后者为搜索引入新的变化。
交叉: 先选出两个父代,再交换它们编码中的部分内容,得到子代。比如说,对于 01101 和 10110 两条染色体,如果在第二位之后进行切分,并交换后面的部分,就可以得到:
text
父代:01 | 101 10 | 110
↓ 交叉
子代:01 | 110 10 | 101
交叉后产生的 01110 和 10101 对应两个新的候选解。这个操作的目的,是尝试将不同父代中的部分信息组合起来,拓展探索方向。
变异: 在个体的编码中,按照设定的概率随机改变某些位置。对于二进制编码,可以将某一位从 0 翻转为 1,或者从 1 翻转为 0;对于实数编码,则可以对某个变量施加数值扰动。相比于交叉对已有信息的重组,变异能够引入新的变化,避免种群中的个体过于相似,从而保留继续探索的可能性。同样的,变异也是为了拓展探索方向,并且有好有坏。
在实际的设计过程中,交叉和变异往往是同步进行的,通常在交叉过程中会设定一个概率来决定是否发生变异。
2.2.3 适者生存
交叉和变异负责产生新的候选解,而"适者生存"解决的是如何评价这些解,以及哪些个体能够参与后续繁衍、进入下一代。算法需要根据目标函数评价个体的表现,再按照一定的选择规则,让表现较好的个体获得更多机会,从而使搜索逐渐向更好的区域推进。
为此,我们引入 适应度(fitness)函数 ,用来指导个体选择。目标函数规定的是我们希望优化什么,适应度则服务于选择过程,两者不一定完全相同。如果是非负的最大化目标,可以直接用目标函数值作为适应度;如果是最小化问题,或者目标值包含负数,则需要根据所采用的选择方式进行转换或排序,不能一概认为原始目标值越大越好。
一种常见的选择方式是 轮盘赌选择法 :当适应度非负且总和大于零时,个体 \(i\) 被选中的概率为:
\p_i=\\frac{\\operatorname{fitness}_i}{\\sum_{j=1}\^{N}\\operatorname{fitness}_j} \\
其中,\(N\) 是种群中的个体数量。也就是说,适应度越高,被选中作为父代的概率越大,但适应度较低的个体仍有机会参与繁衍。这种方式不是每次只选择最好的几个个体,而是在偏向较好解的同时保留一定的多样性。
除了轮盘赌选择法,常用的选择方法还有 随机遍历抽样法 和 锦标赛选择法 等。
2.2.4 迭代求解
选择父代与筛选下一代是两个相关但不同的环节:前者决定谁参与产生子代,后者决定哪些个体最终留下。为了避免已有的优秀解在交叉、变异过程中丢失,可以采用精英保留机制,直接保留少量优秀个体,或者将父代与子代合并后统一筛选。适应度评价负责判断表现,精英保留则根据评价结果决定保留谁,两者不是同一个概念。
通过反复进行评价、选择、交叉、变异和下一代筛选,种群不断更新,直到达到最大迭代次数、时间限制,或连续多代没有明显改善等停止条件。需要注意,种群更新并不意味着每个新解都会更好,算法停止也不等于已经证明找到了全局最优解。
对于多目标问题,个体之间的比较还需要考虑多个目标的取舍,这也是后面 NSGA-II 引入非支配排序和拥挤距离的原因。
3. NSGA-II算法
非支配排序遗传算法 NSGA (Non-dominated)是一种基于 Pareto 最优概念的遗传算法,它在基本遗传算法的基础上,对选择再生方法进行改造将每个个体按照它们的支配与非支配关系进行分层,再做选择操作。
3.1 支配与非支配关系
支配是指,一个解在所有目标上都不比另一个解差,并且至少在一个目标上更好。为了便于说明,这里假设所有目标都需要最小化,对于两个可行解 \(p\) 和 \(q\),如果满足:
\\\forall k\\in\\{1,\\ldots,m\\},\\quad f_k(p)\\leq f_k(q) \\
并且:
\\\exists k\\in\\{1,\\ldots,m\\},\\quad f_k(p)\
那么就称 \(p\) 支配 \(q\),记作 \(p\prec q\)。其中,\(m\) 是目标函数个数。如果某个目标需要最大化,就需要反过来比较,或者先将其转换为最小化目标。
如果两个解各自在某些目标上有优势,另一些目标上又不如对方,就无法按照上面的规则确定谁更好,也就是说,它们互不支配。如果两个解的所有目标值都相同,由于不满足"至少一个目标更好",它们同样不会互相支配。
这里需要注意,两个解互不支配,并不意味着它们都是 Pareto 最优解,因为可能还有第三个解支配它们。非支配解需要相对于一个明确的集合来判断:如果一个解没有被当前种群中的任何其他解支配,它就是当前种群中的非支配解;只有当它在整个可行解空间中都不被其他可行解支配时,才能称为 Pareto 最优解。因此,算法当前找到的非支配解集,通常只是对真实 Pareto 最优解集的近似。
3.2 NSGA-II
NSGA-II 算法在 NSGA 算法的基础上进一步做出了以下三个改进:
- 提出了快速非支配排序法,将时间复杂度由 \(O(mN^3)\) 降低到 \(O(mN^2)\),其中,m 为目标函数个数,N 为种群大小;
- 提出了拥挤度和拥挤度比较算子作为快速排序后的同级比较中作为胜出标准;
- 引入了精英策略,扩大了采样空间。将父代种群与其产生的子代种群组合,共同竞争产生下一代种群,有利于保持父代中的优良个体进入下一代,并通过对种群中所有个体的分层存放,使得最佳个体不会丢失,以达到迅速提高种群水平的目的。
其大致流程如下:

3.3 快速非支配排序
非支配排序的目的,是根据当前种群中个体之间的支配关系,将它们划分为不同的非支配层。第一层 \(F_1\) 包含当前种群中的所有非支配解,将这一层暂时移除后,剩余个体中的非支配解构成第二层 \(F_2\),依此类推,直到所有个体都被分层。个体所在的层号就是它的非支配等级(rank),层号越小,保留优先级越高。
如果每找出一层,就重新从头比较剩余个体之间的支配关系,会产生较多重复计算。快速非支配排序的做法,是先记录支配关系,再通过更新计数逐层得到结果。具体来说,对于每个个体 \(p\),记录两个信息:
\n_p=\\left\|\\{q:q\\prec p\\}\\right\|,\\qquad S_p=\\{q:p\\prec q\\} \\
其中,\(n_p\) 表示支配 \(p\) 的个体数量,\(S_p\) 表示被 \(p\) 支配的个体集合。通过比较种群中的个体,就可以建立这些记录。对于所有满足 \(n_p=0\) 的个体,由于没有其他个体支配它们,所以直接加入第一层 \(F_1\),并将它们的 rank 设置为 1。
接下来,遍历第一层中的每个个体 \(p\),对于其集合 \(S_p\) 中的每个个体 \(q\),将 \(n_q\) 减一。这相当于暂时移除了第一层对剩余个体的支配影响。如果某个 \(q\) 的计数降为 0,就说明在第一层被移除后,它不再被剩余个体支配,因此可以加入第二层。第一层全部处理完成后,再以同样的方式处理第二层,得到第三层,直到完成所有分层。
这里的"快速"就体现在对支配关系的记录和复用,而不是每一轮重新比较所有个体。设参与排序的个体数量为 \(N\),目标函数个数为 \(m\),快速非支配排序的时间复杂度为 \(O(mN^2)\)。
排序结果提供的是当前候选解之间的优先层级,第一层不一定已经到达真实 Pareto 前沿,第二层也不能直接理解为整个优化问题的"次优解"。对于同一层内部的个体,由于它们互不支配,还需要通过拥挤距离进一步选择。
3.4 拥挤度与拥挤度比较算子
拥挤度在直观上表现为一个个体附近的解分布得有多稀疏,NSGA-II 中通常用 拥挤距离(crowding distance) 来衡量。它是在同一个非支配层内部计算的,关注的是目标空间中各个解的位置,而不是染色体编码是否相似。拥挤距离越大,说明附近的解相对越少,在保留名额有限时,优先留下这样的个体,有利于覆盖更多不同的取舍,避免解集集中在前沿的一小段。

如图所示,可以把个体 \(n\) 的前后邻居在两个目标方向上的间隔,理解为包围它的矩形的宽和高。邻居之间的间隔越大,说明这个位置附近越稀疏。不过,拥挤距离并不是这个矩形的面积,也不是个体到 Pareto 前沿的距离,而是对各目标方向上的邻居间隔进行归一化后求和。
具体计算时,首先将当前层所有个体的拥挤距离初始化为 0,再分别按照每一个目标值从小到大排序。对于排序后的内部个体 \(p\),取它的前一个邻居和后一个邻居,计算两者的目标值之差,再除以当前层在这个目标上的最大值与最小值之差。对所有目标重复这个操作并累加,得到:
\CD(p)=\\sum_{k=1}\^{m}\\frac{f_k(p_k\^+)-f_k(p_k\^-)}{f_k\^{\\max}-f_k\^{\\min}} \\
其中,\(p_k^-\) 和 \(p_k^+\) 是按照目标 \(k\) 排序时,位于 \(p\) 前后的个体,\(f_k^{\max}\) 和 \(f_k^{\min}\) 则是当前非支配层在该目标上的最大值和最小值。需要注意,不同目标对应的排序可能不同,因此同一个个体在不同目标下的前后邻居也可能不同。归一化则是为了减少各个目标量纲和数值范围不同所带来的影响。
对于每个目标排序后的首尾个体,通常将其拥挤距离设为无穷大,以优先保留各目标上的边界位置,维持解集的覆盖范围。如果某个目标在当前层中的取值完全相同,分母就会变成 0,实现时需要做特殊处理,避免除以零,这个目标本身也无法用于区分该层个体的疏密。
有了非支配等级和拥挤距离后,就可以定义拥挤度比较算子,也就是两个个体之间的选择规则:先比较 rank,优先选择 rank 较小的个体;只有 rank 相同时,才比较拥挤距离,优先选择距离较大的个体。如果两个指标都相同,可以随机选择。这里的"算子"就是比较规则本身,并不是另一种距离计算方法。
在筛选下一代时,算法遵循以下规则:
- 按照非支配等级逐层保留个体,优先保留等级低的候选解;
- 如果某一层无法完整放入剩余名额,就按照拥挤距离从大到小截取。
因此,非支配排序负责划分优先层级,拥挤距离负责维持同层解的分布多样性。
