深入理解NSGA-II算法:原理、案例与MATLAB实现
一、引言
在现实世界的众多优化问题中,往往需要同时考虑多个相互冲突的目标,这就是多目标优化问题(Multi-Objective Optimization Problems, MOPs)。传统的单目标优化算法无法直接应用于此类问题,因此各种多目标优化算法应运而生。非支配排序遗传算法II(Non-dominated Sorting Genetic Algorithm II, NSGA-II)是其中一种经典且高效的算法,由Kalyanmoy Deb等人于2002年提出。它在第一代NSGA的基础上进行了改进,具有更快的运行速度和更好的收敛性,被广泛应用于工程设计、经济决策等多个领域。
二、基本概念与原理
2.1 支配关系
在多目标优化中,支配关系是核心概念之一。设存在两个解向量 x 1 \mathbf{x}_1 x1 和 x 2 \mathbf{x}_2 x2,对应的目标函数向量分别为 F ( x 1 ) = ( f 1 ( x 1 ) , f 2 ( x 1 ) , ⋯ , f m ( x 1 ) ) \mathbf{F}(\mathbf{x}_1)=(f_1(\mathbf{x}_1), f_2(\mathbf{x}_1), \cdots, f_m(\mathbf{x}_1)) F(x1)=(f1(x1),f2(x1),⋯,fm(x1)) 和 F ( x 2 ) = ( f 1 ( x 2 ) , f 2 ( x 2 ) , ⋯ , f m ( x 2 ) ) \mathbf{F}(\mathbf{x}_2)=(f_1(\mathbf{x}_2), f_2(\mathbf{x}_2), \cdots, f_m(\mathbf{x}_2)) F(x2)=(f1(x2),f2(x2),⋯,fm(x2)),其中 m m m 是目标函数的数量。如果满足以下两个条件:
- 对于所有的 i = 1 , 2 , ⋯ , m i = 1, 2, \cdots, m i=1,2,⋯,m,都有 f i ( x 1 ) ≤ f i ( x 2 ) f_i(\mathbf{x}_1) \leq f_i(\mathbf{x}_2) fi(x1)≤fi(x2);
- 至少存在一个 j j j,使得 f j ( x 1 ) < f j ( x 2 ) f_j(\mathbf{x}_1) < f_j(\mathbf{x}_2) fj(x1)<fj(x2)。
则称解 x 1 \mathbf{x}_1 x1 支配解 x 2 \mathbf{x}_2 x2,记作 x 1 ≺ x 2 \mathbf{x}_1 \prec \mathbf{x}_2 x1≺x2。
2.2 Pareto最优解与Pareto前沿
- Pareto最优解:如果在解空间中不存在一个解 x \mathbf{x} x 能够支配解 x ∗ \mathbf{x}^* x∗,则称 x ∗ \mathbf{x}^* x∗ 为Pareto最优解。
- Pareto前沿:所有Pareto最优解对应的目标函数值构成的集合称为Pareto前沿(Pareto Front, PF)。
2.3 非支配排序
非支配排序是NSGA-II的关键步骤之一,其目的是将种群中的个体划分为不同的非支配等级。具体步骤如下:
- 初始化所有个体的支配计数器 n p = 0 n_p = 0 np=0 和支配集合 S p = ∅ S_p = \varnothing Sp=∅。
- 对于种群中的每一对个体
p
p
p 和
q
q
q,判断它们之间的支配关系:
- 如果 p ≺ q p \prec q p≺q,则将 q q q 加入 p p p 的支配集合 S p S_p Sp,并将 q q q 的支配计数器 n q n_q nq 加1。
- 如果 q ≺ p q \prec p q≺p,则将 p p p 的支配计数器 n p n_p np 加1。
- 找出所有支配计数器 n p = 0 n_p = 0 np=0 的个体,这些个体构成第一级非支配集 F 1 F_1 F1。
- 对于 F 1 F_1 F1 中的每个个体 p p p,将其支配集合 S p S_p Sp 中的每个个体 q q q 的支配计数器 n q n_q nq 减1。如果 n q n_q nq 变为0,则将 q q q 加入下一级非支配集 F 2 F_2 F2。
- 重复步骤4,直到所有个体都被分配到某个非支配等级。
2.4 拥挤度距离
拥挤度距离用于衡量种群中个体的分布性。对于每个非支配等级中的个体,计算其拥挤度距离 d i d_i di。具体步骤如下:
- 对于每个目标函数 f k f_k fk,将该非支配等级中的个体按照 f k f_k fk 的值从小到大排序。
- 初始化边界个体的拥挤度距离为无穷大,即 d 1 = d n = ∞ d_1 = d_n = \infty d1=dn=∞,其中 n n n 为该非支配等级中的个体数。
- 对于中间的个体
i
=
2
,
3
,
⋯
,
n
−
1
i = 2, 3, \cdots, n - 1
i=2,3,⋯,n−1,计算其拥挤度距离:
[d_i = d_i+\frac{f_k(x_{i + 1}) - f_k(x_{i - 1})}{f_k{max}-f_k{min}}]
其中, f k m a x f_k^{max} fkmax 和 f k m i n f_k^{min} fkmin 分别为该非支配等级中所有个体在目标函数 f k f_k fk 上的最大值和最小值。
2.5 锦标赛选择
NSGA-II采用锦标赛选择方法来选择参与交叉和变异的个体。具体过程为:从种群中随机选择 k k k 个个体(通常 k = 2 k = 2 k=2),比较它们的非支配等级和拥挤度距离,选择非支配等级较低的个体;如果非支配等级相同,则选择拥挤度距离较大的个体。
三、NSGA-II算法流程
- 初始化种群:随机生成初始种群 P 0 P_0 P0,并计算每个个体的目标函数值。
- 非支配排序:对种群 P 0 P_0 P0 进行非支配排序,得到不同的非支配等级。
- 计算拥挤度距离:对每个非支配等级中的个体计算拥挤度距离。
- 选择操作:使用锦标赛选择方法从种群 P 0 P_0 P0 中选择个体,组成交配池。
- 交叉和变异操作:对交配池中的个体进行交叉和变异操作,生成子代种群 Q 0 Q_0 Q0。
- 合并种群:将父代种群 P 0 P_0 P0 和子代种群 Q 0 Q_0 Q0 合并为新的种群 R 0 = P 0 ∪ Q 0 R_0 = P_0 \cup Q_0 R0=P0∪Q0。
- 非支配排序和选择:对种群 R 0 R_0 R0 进行非支配排序,根据非支配等级和拥挤度距离选择个体,组成新的父代种群 P 1 P_1 P1,使其规模与初始种群相同。
- 终止条件判断:如果满足终止条件(如达到最大迭代次数),则输出当前种群中的非支配个体作为Pareto最优解;否则,返回步骤4。
四、案例分析
考虑一个经典的双目标优化问题:ZDT1问题。其数学表达式如下:
[
\begin{cases}
f_1(x_1)=x_1\
f_2(x)=g(x)\left[1 - \sqrt{\frac{f_1(x)}{g(x)}}\right]\
g(x)=1 + \frac{9}{n - 1}\sum_{i = 2}^{n}x_i\
x_i\in[0, 1], i = 1, 2, \cdots, n
\end{cases}
]
其中,
n
n
n 是决策变量的数量,通常取
n
=
30
n = 30
n=30。
4.1 MATLAB代码实现
% 参数设置
pop_size = 100; % 种群规模
max_gen = 200; % 最大迭代次数
var_num = 30; % 变量个数
var_min = 0; % 变量下限
var_max = 1; % 变量上限
pc = 0.8; % 交叉概率
pm = 0.1; % 变异概率
% 初始化种群
pop = rand(pop_size, var_num) * (var_max - var_min) + var_min;
% 迭代优化
for gen = 1:max_gen
% 计算目标函数值
f1 = pop(:, 1);
g = 1 + (9 / (var_num - 1)) * sum(pop(:, 2:end), 2);
f2 = g .* (1 - sqrt(f1 ./ g));
obj = [f1, f2];
% 非支配排序
[fronts, ranks] = non_dominated_sort(obj);
% 计算拥挤度距离
crowding_dist = crowding_distance_assignment(obj, fronts);
% 锦标赛选择
selected_indices = tournament_selection(pop_size, fronts, ranks, crowding_dist);
selected_pop = pop(selected_indices, :);
% 交叉和变异操作
offspring = crossover(selected_pop, pc);
offspring = mutation(offspring, pm, var_min, var_max);
% 合并种群
pop = [pop; offspring];
% 再次进行非支配排序和选择
f1 = pop(:, 1);
g = 1 + (9 / (var_num - 1)) * sum(pop(:, 2:end), 2);
f2 = g .* (1 - sqrt(f1 ./ g));
obj = [f1, f2];
[fronts, ranks] = non_dominated_sort(obj);
crowding_dist = crowding_distance_assignment(obj, fronts);
[~, sorted_indices] = sortrows([ranks, -crowding_dist]);
pop = pop(sorted_indices(1:pop_size), :);
end
% 计算最终的目标函数值
f1 = pop(:, 1);
g = 1 + (9 / (var_num - 1)) * sum(pop(:, 2:end), 2);
f2 = g .* (1 - sqrt(f1 ./ g));
obj = [f1, f2];
% 绘制Pareto前沿
figure;
plot(obj(:, 1), obj(:, 2), 'ro');
xlabel('f_1(x)');
ylabel('f_2(x)');
title('Pareto Front of ZDT1 Problem');
% 非支配排序函数
function [fronts, ranks] = non_dominated_sort(obj)
pop_size = size(obj, 1);
ranks = zeros(pop_size, 1);
fronts = {};
S = cell(pop_size, 1);
n = zeros(pop_size, 1);
for p = 1:pop_size
S{p} = [];
n[p] = 0;
for q = 1:pop_size
if dominates(obj(p, :), obj(q, :))
S{p} = [S{p}, q];
elseif dominates(obj(q, :), obj(p, :))
n[p] = n[p] + 1;
end
end
if n[p] == 0
ranks[p] = 1;
if isempty(fronts{1})
fronts{1} = p;
else
fronts{1} = [fronts{1}, p];
end
end
end
i = 1;
while ~isempty(fronts{i})
Q = [];
for p = fronts{i}
for q = S{p}
n[q] = n[q] - 1;
if n[q] == 0
ranks[q] = i + 1;
Q = [Q, q];
end
end
end
i = i + 1;
fronts{i} = Q;
end
end
% 判断支配关系函数
function result = dominates(a, b)
result = all(a <= b) && any(a < b);
end
% 拥挤度距离计算函数
function crowding_dist = crowding_distance_assignment(obj, fronts)
pop_size = size(obj, 1);
num_obj = size(obj, 2);
crowding_dist = zeros(pop_size, 1);
for i = 1:length(fronts)
front = fronts{i};
front_size = length(front);
if front_size > 2
for j = 1:num_obj
[~, sorted_indices] = sort(obj(front, j));
sorted_front = front(sorted_indices);
crowding_dist(sorted_front(1)) = Inf;
crowding_dist(sorted_front(end)) = Inf;
f_max = max(obj(sorted_front, j));
f_min = min(obj(sorted_front, j));
if f_max ~= f_min
for k = 2:front_size - 1
crowding_dist(sorted_front(k)) = crowding_dist(sorted_front(k)) + (obj(sorted_front(k + 1), j) - obj(sorted_front(k - 1), j)) / (f_max - f_min);
end
end
end
end
end
end
% 锦标赛选择函数
function selected_indices = tournament_selection(pop_size, fronts, ranks, crowding_dist)
selected_indices = [];
for i = 1:pop_size
idx1 = randi(pop_size);
idx2 = randi(pop_size);
if ranks[idx1] < ranks[idx2]
selected_indices = [selected_indices, idx1];
elseif ranks[idx1] > ranks[idx2]
selected_indices = [selected_indices, idx2];
else
if crowding_dist[idx1] > crowding_dist[idx2]
selected_indices = [selected_indices, idx1];
else
selected_indices = [selected_indices, idx2];
end
end
end
end
% 交叉操作函数
function offspring = crossover(pop, pc)
pop_size = size(pop, 1);
offspring = zeros(pop_size, size(pop, 2));
for i = 1:2:pop_size
if rand < pc
alpha = rand;
offspring(i, :) = alpha * pop(i, :) + (1 - alpha) * pop(i + 1, :);
offspring(i + 1, :) = (1 - alpha) * pop(i, :) + alpha * pop(i + 1, :);
else
offspring(i, :) = pop(i, :);
offspring(i + 1, :) = pop(i + 1, :);
end
end
end
% 变异操作函数
function offspring = mutation(offspring, pm, var_min, var_max)
[pop_size, var_num] = size(offspring);
for i = 1:pop_size
for j = 1:var_num
if rand < pm
offspring(i, j) = rand * (var_max - var_min) + var_min;
end
end
end
end
4.2 结果分析
运行上述代码后,会得到ZDT1问题的Pareto前沿的可视化结果。从图中可以看到,NSGA-II算法能够找到一组分布较为均匀的Pareto最优解,这些解在两个目标函数之间达到了某种平衡,体现了算法在多目标优化中的有效性。
五、参考文献
- Deb, K., Pratap, A., Agarwal, S., & Meyarivan, T. (2002). A fast and elitist multiobjective genetic algorithm: NSGA-II. IEEE transactions on evolutionary computation, 6(2), 182 - 197.
- Coello Coello, C. A., Van Veldhuizen, D. A., & Lamont, G. B. (2007). Evolutionary algorithms for solving multi - objective problems (Vol. 5). Springer Science & Business Media.
通过以上内容,我们全面介绍了NSGA-II算法的基本原理、算法流程、案例分析和MATLAB代码实现。NSGA-II算法在多目标优化领域具有重要的地位,能够有效地解决多个相互冲突的目标优化问题。