非支配性排序遗传算法 II—NSGA-II
Github 地址
非支配性排序遗传算法 II(NSGA-II)是一种用于解决多目标优化问题的进化算法。它在遗传算法的基础上引入了非支配排序和拥挤距离的概念,以有效地找到多个目标函数的最优解集。以下是NSGA-II的主要特点和步骤:
代码说明:本文示例保留了非支配排序、拥挤距离和精英保留等核心流程,但采用随机父代选择与线性交叉,属于教学化实现;原论文常见实现通常结合二元锦标赛选择,并可使用 SBX 等实数编码算子。
1. 主要特点
- 多目标优化:NSGA-II能够同时优化多个相互冲突的目标函数,例如在设计中可能需要最小化成本和最大化性能。
- 非支配排序:通过对种群中的个体进行非支配排序,将个体分为不同的等级,以选择出 Pareto 前沿的解。
- 拥挤距离:为了保持解的多样性,NSGA-II计算每个个体的拥挤距离,作为选择的一个指标,优先选择拥挤距离大的个体。
- 快速非支配排序:NSGA-II的排序算法能够在较短的时间内处理大规模种群,相比于早期的非支配排序算法具有更高的效率。
2. 算法步骤
以下是NSGA-II的基本步骤:
初始化:
- 随机生成初始种群,个体由决策变量构成,并计算每个个体的目标函数值。
非支配排序:
- 对种群中的个体进行非支配排序,识别出不同等级的解(如Pareto前沿)。
计算拥挤距离:
- 为每个个体计算拥挤距离,评估其在前沿中的稀疏程度,帮助维护解的多样性。
选择、交叉和突变:
- 通过选择操作(如锦标赛选择)从当前种群中选择父代,执行交叉操作生成子代,并进行突变以引入新的基因。
合并种群:
再次进行非支配排序和拥挤距离计算:
- 对合并后的种群进行非支配排序和拥挤距离计算,准备下一代的选择。
更新种群:
- 根据支配等级和拥挤距离选择出下一代的种群,保证种群规模不变。
重复迭代:
- 重复执行选择、交叉、突变、合并和排序步骤,直到达到设定的最大迭代次数或满足终止条件。
3. 应用领域
NSGA-II广泛应用于多个领域,包括但不限于:
- 工程设计(如结构优化、产品设计)
- 交通流优化
- 资源分配
- 生物信息学
- 经济模型优化
4. 优势与挑战
优势:
- 能够有效处理多目标问题。
- 提供解的多样性和覆盖面。
- 算法效率高,适合大规模优化。
挑战:
- 对于某些问题,解的分布可能不均匀,需要调整参数以优化性能。
- 对于高维目标问题,算法的复杂性和计算负担可能增加。
总结
NSGA-II是一种强大的多目标优化算法,通过非支配排序和拥挤距离机制在保证解质量的同时维护解的多样性,适合于解决复杂的优化问题。
CalcCrowdingDistance.m 文件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| function pop=CalcCrowdingDistance(pop,F) nF = numel(F); for k = 1:nF Costs = [pop(F{k}).Cost]; nObj = size(Costs, 1); n = numel(F{k}); d = zeros(n, nObj); for j = 1:nObj [cj, so] = sort(Costs(j,:)); d(so(1), j) = inf; costRange = cj(end) - cj(1); if costRange > 0 for i = 2:n-1 d(so(i), j) = (cj(i+1) - cj(i-1)) / costRange; end end d(so(end), j) = inf; end for i = 1:n pop(F{k}(i)).CrowdingDistance = sum(d(i,:)); end end
end
|
注释要点:
- 拥挤度计算:通过对每个目标函数进行排序,然后计算每个个体的邻居间的差异,来评估个体在当前目标空间中的“拥挤”程度。
- 边界处理:位于目标函数值边界的个体的拥挤度被设为无穷大,以确保边界个体不会被轻易舍弃。
- 多目标适用性:算法可以同时处理多个目标,逐个目标计算拥挤度。
Crossover.m 文件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function [y1, y2] = Crossover(x1, x2)
alpha = rand(size(x1)); y1 = alpha .* x1 + (1 - alpha) .* x2; y2 = alpha .* x2 + (1 - alpha) .* x1; end
|
注释要点:
- 交叉操作:该代码实现了一种简单的线性交叉方法,基于随机系数
alpha,生成两个新个体 y1 和 y2。
- 随机权重:
alpha 的随机性确保了每次交叉生成的子代个体都有不同的基因组合。
- 适应不同维度:该代码能够处理不同长度的解向量,适应性较强。
Dominates.m 文件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function b = Dominates(x, y)
if isstruct(x) x = x.Cost; end
if isstruct(y) y = y.Cost; end
b = all(x <= y) && any(x < y); end
|
注释要点:
- Pareto支配:判断解
x 是否在多目标优化中支配解 y,即 x 在所有目标上都不差于 y,并且至少在一个目标上优于 y。
- 结构体处理:如果
x 或 y 是结构体,该函数会提取它们的 Cost 属性,这通常代表解的目标函数值。
- 支配条件:函数核心是两个条件,分别是目标值的逐项比较和严格不等判断。
main.m 文件非常简洁:
注释要点:
- 主程序:
main.m 文件是整个程序的入口,通过调用 nsga2 函数来运行非支配性排序遗传算法 II (NSGA-II)。
- 简单启动:它仅包含对核心算法函数的调用,具体的算法逻辑将在
nsga2 函数中实现。
MOP2.m 文件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function z = MOP2(x)
n = numel(x); z1 = 1 - exp(-sum((x - 1/sqrt(n)).^2)); z2 = 1 - exp(-sum((x + 1/sqrt(n)).^2)); z = [z1 z2]';
end
|
注释要点:
- 测试问题:
MOP2 是一个标准的多目标优化问题(MOP,Multi-Objective Problem),通常用于测试多目标优化算法的性能。
- 目标函数:
z1 和 z2 分别是两个目标函数,都是基于决策变量 x 的平方和构造的非线性函数。它们的目标是最小化。
- 决策变量维度:目标函数使用决策变量的个数
n 来标准化表达式,确保适用于不同维度的输入。
MOP4.m 文件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function z = MOP4(x)
a = 0.8; b = 3; z1 = sum(-10 * exp(-0.2 * sqrt(x(1:end-1).^2 + x(2:end).^2))); z2 = sum(abs(x).^a + 5 * (sin(x)).^b); z = [z1 z2]';
end
|
注释要点:
- 目标函数1:
z1 是一个非线性函数,依赖于相邻决策变量的平方和,通过指数函数的方式构造,用于创建复杂的搜索空间。
- 目标函数2:
z2 包含决策变量的绝对值以及正弦函数的高次幂,这种组合构造了不同特性的目标函数,增加了多样性。
- 多目标优化:
MOP4 是一个用于测试多目标优化算法的函数,含有两个目标函数,需要同时最小化。
Mutate.m 文件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function y = Mutate(x, mu, sigma)
nVar = numel(x); nMu = ceil(mu * nVar); j = randsample(nVar, nMu); if numel(sigma) > 1 sigma = sigma(j); end y = x; y(j) = x(j) + sigma .* randn(size(j)); end
|
注释要点:
- 突变操作:该函数通过为某些随机选择的决策变量添加高斯噪声,实现遗传算法中的个体突变。突变增加了解空间的多样性,有助于避免局部最优。
- 突变比例:
mu 控制了要突变的决策变量的比例,nMu 是实际要突变的变量数量。
- 突变强度:
sigma 控制了突变的强度,函数支持对不同变量使用不同的突变幅度。
- 高斯噪声:突变操作通过正态分布随机数
randn 生成噪声,调整变量的值。
NonDominatedSorting.m 文件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| function [pop, F] = NonDominatedSorting(pop)
nPop = numel(pop);
for i = 1:nPop pop(i).DominationSet = []; pop(i).DominatedCount = 0; end F{1} = [];
for i = 1:nPop for j = i + 1:nPop p = pop(i); q = pop(j); if Dominates(p, q) p.DominationSet = [p.DominationSet j]; q.DominatedCount = q.DominatedCount + 1; end if Dominates(q.Cost, p.Cost) q.DominationSet = [q.DominationSet i]; p.DominatedCount = p.DominatedCount + 1; end pop(i) = p; pop(j) = q; end if pop(i).DominatedCount == 0 F{1} = [F{1} i]; pop(i).Rank = 1; end end k = 1;
while true Q = []; for i = F{k} p = pop(i); for j = p.DominationSet q = pop(j); q.DominatedCount = q.DominatedCount - 1; if q.DominatedCount == 0 Q = [Q j]; q.Rank = k + 1; end pop(j) = q; end end if isempty(Q) break; end F{k + 1} = Q; k = k + 1; end
end
|
注释要点:
- 非支配排序:该函数实现了多目标优化中的非支配排序,确定种群中的个体属于哪个非支配前沿。前沿越靠前的个体被认为越优。
- 支配关系的确定:通过两两比较种群中的个体,判断一个个体是否支配另一个个体,支配关系的判断基于
Dominates 函数。
- 非支配前沿生成:通过迭代,函数不断生成新的非支配前沿,直到种群中的所有个体都被排序。
nsga2.m 文件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
| clc; clear; close all;
CostFunction = @(x) MOP2(x);
nVar = 3;
VarSize = [1 nVar];
VarMin = -5; VarMax = 5;
nObj = numel(CostFunction(unifrnd(VarMin, VarMax, VarSize)));
MaxIt = 100;
nPop = 50;
pCrossover = 0.7; nCrossover = 2 * round(pCrossover * nPop / 2);
pMutation = 0.4; nMutation = round(pMutation * nPop);
mu = 0.02;
sigma = 0.1 * (VarMax - VarMin);
empty_individual.Position = []; empty_individual.Cost = []; empty_individual.Rank = []; empty_individual.DominationSet = []; empty_individual.DominatedCount = []; empty_individual.CrowdingDistance = [];
pop = repmat(empty_individual, nPop, 1);
for i = 1:nPop pop(i).Position = unifrnd(VarMin, VarMax, VarSize); pop(i).Cost = CostFunction(pop(i).Position); end
[pop, F] = NonDominatedSorting(pop);
pop = CalcCrowdingDistance(pop, F);
[pop, F] = SortPopulation(pop);
for it = 1:MaxIt popc = repmat(empty_individual, nCrossover/2, 2); for k = 1:nCrossover/2 i1 = randi([1 nPop]); p1 = pop(i1); i2 = randi([1 nPop]); p2 = pop(i2); [popc(k,1).Position, popc(k,2).Position] = Crossover(p1.Position, p2.Position); popc(k,1).Cost = CostFunction(popc(k,1).Position); popc(k,2).Cost = CostFunction(popc(k,2).Position); end popc = popc(:); popm = repmat(empty_individual, nMutation, 1); for k = 1:nMutation i = randi([1 nPop]); p = pop(i); popm(k).Position = Mutate(p.Position, mu, sigma); popm(k).Position = min(max(popm(k).Position, VarMin), VarMax); popm(k).Cost = CostFunction(popm(k).Position); end pop = [pop; popc; popm]; [pop, F] = NonDominatedSorting(pop);
pop = CalcCrowdingDistance(pop, F);
[pop, F] = SortPopulation(pop); pop = pop(1:nPop); [pop, F] = NonDominatedSorting(pop); pop = CalcCrowdingDistance(pop, F); [pop, F] = SortPopulation(pop); F1 = pop(F{1}); disp(['Iteration ' num2str(it) ': Number of F1 Members = ' num2str(numel(F1))]); figure(1); PlotCosts(F1); pause(0.01); end
|
注释要点:
- 问题定义:设置目标函数和决策变量的上下界,定义多目标优化问题。
- NSGA-II 参数:设置遗传算法的相关参数,如种群大小、交叉和突变的概率、突变率等。
- 初始化:生成初始种群,计算每个个体的目标函数值,并进行非支配排序和拥挤距离计算。
- 主循环:在每一代中,执行交叉、突变、合并种群、非支配排序、拥挤距离计算和种群截断操作。
- 结果展示:每次迭代后显示当前最优前沿(F1)的个体数量,并绘制其目标函数值。
PlotCosts.m 文件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function PlotCosts(pop)
Costs = [pop.Cost];
plot(Costs(1,:), Costs(2,:), 'r*', 'MarkerSize', 8); xlabel('1^{st} Objective'); ylabel('2^{nd} Objective'); title('Non-dominated Solutions (F_{1})'); grid on; end
|
注释要点:
- 函数目的:该函数用于绘制非支配解的目标函数值,便于可视化优化结果。
- 输入参数:
pop 是包含个体的种群结构,其中每个个体都有目标函数值。
- 目标函数值提取:从种群中提取所有个体的目标函数值,以便进行绘图。
- 绘图:使用
plot 函数绘制目标函数值,设置图标样式为红色星形标记,标记大小为8。
- 轴标签和标题:设置X轴和Y轴的标签,并为图表添加标题,以清楚表明图表的含义。
- 网格显示:启用网格,使图表更易于阅读。
SortPopulation.m 文件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function [pop, F] = SortPopulation(pop)
[~, CDSO] = sort([pop.CrowdingDistance], 'descend'); pop = pop(CDSO); [~, RSO] = sort([pop.Rank]); pop = pop(RSO); Ranks = [pop.Rank]; MaxRank = max(Ranks); F = cell(MaxRank, 1); for r = 1:MaxRank F{r} = find(Ranks == r); end end
|
注释要点:
- 函数目的:该函数用于对种群进行排序,主要根据个体的拥挤距离和支配等级,以便选择最优个体。
- 输入参数:
pop 是包含个体的种群结构,每个个体有其拥挤距离和支配等级。
- 基于拥挤距离排序:使用
sort 函数对个体的拥挤距离进行降序排序,并根据排序结果更新种群。
- 基于支配等级排序:对更新后的种群根据支配等级进行升序排序,进一步筛选出最优个体。
- 更新前沿集合:通过遍历支配等级,构建一个单元格数组
F,其中每个元素包含相同支配等级的个体索引,表示不同等级的个体集合。
参考资料