基于NSGA-II的多目标优化算法(MATLAB实现)

一、NSGA-II算法核心原理

NSGA-II(Non-dominated Sorting Genetic Algorithm II)是多目标优化 的经典算法,通过非支配排序 区分解的优劣,拥挤度计算 保持种群多样性,精英保留策略提升收敛性,有效解决多目标冲突问题(如"成本-效率""重量-强度"权衡)。

1.1 核心概念

  • 多目标优化问题
    min⁡F(x)=[f1(x),f2(x),...,fm(x)]T\min \quad \mathbf{F}(\mathbf{x}) = [f_1(\mathbf{x}), f_2(\mathbf{x}), ..., f_m(\mathbf{x})]^TminF(x)=[f1(x),f2(x),...,fm(x)]T
    s.t.gi(x)≤0,hj(x)=0\text{s.t.} \quad g_i(\mathbf{x}) \leq 0, \quad h_j(\mathbf{x}) = 0s.t.gi(x)≤0,hj(x)=0

    其中 x=[x1,x2,...,xn]T\mathbf{x} = [x_1, x_2, ..., x_n]^Tx=[x1,x2,...,xn]T 为决策变量,mmm 为目标函数个数。

  • Pareto支配 :解 x1\mathbf{x}_1x1 支配 x2\mathbf{x}_2x2(记为 x1≺x2\mathbf{x}_1 \prec \mathbf{x}_2x1≺x2),当且仅当:
    ∀i,fi(x1)≤fi(x2)\forall i, f_i(\mathbf{x}_1) \leq f_i(\mathbf{x}_2)∀i,fi(x1)≤fi(x2) 且 ∃j,fj(x1)<fj(x2)\exists j, f_j(\mathbf{x}_1) < f_j(\mathbf{x}_2)∃j,fj(x1)<fj(x2)。

  • Pareto前沿:所有非支配解构成的集合,代表多目标优化的"最优权衡解"。

1.2 算法流程

满足终止条件
否则
初始化种群
非支配排序
计算拥挤度
选择操作(二元锦标赛)
交叉(SBX)与变异(多项式变异)
合并父代与子代种群
非支配排序+拥挤度计算
精英保留(选择N个个体)
输出Pareto前沿

二、NSGA-II核心步骤与MATLAB实现

2.1 主函数框架

matlab 复制代码
function [pop, F, front, crowding] = nsga2(fun, nVar, varMin, varMax, nPop, maxGen, params)
    % NSGA-II多目标优化主函数
    % 输入:
    %   fun: 目标函数句柄(返回行向量 [f1, f2, ..., fm])
    %   nVar: 变量个数
    %   varMin/varMax: 变量边界(标量或向量)
    %   nPop: 种群大小
    %   maxGen: 最大迭代次数
    %   params: 算法参数(交叉概率pc、变异概率pm等)
    % 输出:
    %   pop: 最终种群(nPop×nVar)
    %   F: 目标函数值(nPop×m)
    %   front: 非支配前沿等级(nPop×1)
    %   crowding: 拥挤度(nPop×1)
    
    % 参数初始化
    if nargin < 7, params = struct(); end
    params = set_default_params(params, nVar);
    
    % 1. 初始化种群
    pop = init_population(nPop, nVar, varMin, varMax);
    
    % 2. 主循环
    for gen = 1:maxGen
        % 2.1 评估目标函数
        F = evaluate_population(fun, pop);
        
        % 2.2 非支配排序
        [front, rank] = non_domination_sort(F);
        
        % 2.3 计算拥挤度
        crowding = crowding_distance(F, front);
        
        % 2.4 选择父代(二元锦标赛)
        parents = tournament_selection(pop, front, crowding, nPop);
        
        % 2.5 交叉与变异
        offspring = genetic_operators(parents, params, varMin, varMax);
        
        % 2.6 合并父代与子代
        combined_pop = [pop; offspring];
        combined_F = [F; evaluate_population(fun, offspring)];
        
        % 2.7 环境选择(精英保留)
        [pop, F, front, crowding] = environmental_selection(combined_pop, combined_F, nPop);
        
        % 显示进度
        if mod(gen, 10) == 0
            fprintf('NSGA-II: Gen %d/%d, Pareto Size: %d\n', gen, maxGen, sum(front==1));
        end
    end
end

2.2 核心子函数实现

2.2.1 参数设置(默认值)
matlab 复制代码
function params = set_default_params(params, nVar)
    if ~isfield(params, 'pc'), params.pc = 0.9; end       % 交叉概率
    if ~isfield(params, 'pm'), params.pm = 1/nVar; end    % 变异概率
    if ~isfield(params, 'eta_c'), params.eta_c = 15; end   % 交叉分布指数
    if ~isfield(params, 'eta_m'), params.eta_m = 20; end   % 变异分布指数
    if ~isfield(params, 'show_progress'), params.show_progress = true; end;
end
2.2.2 种群初始化
matlab 复制代码
function pop = init_population(nPop, nVar, varMin, varMax)
    % 随机初始化种群(实数编码)
    if isscalar(varMin), varMin = repmat(varMin, 1, nVar); end
    if isscalar(varMax), varMax = repmat(varMax, 1, nVar); end
    pop = rand(nPop, nVar) .* (varMax - varMin) + varMin;
end
2.2.3 目标函数评估
matlab 复制代码
function F = evaluate_population(fun, pop)
    % 评估种群目标函数值
    nPop = size(pop, 1);
    F = zeros(nPop, 0);
    for i = 1:nPop
        f = fun(pop(i, :));  % 调用目标函数
        F(i, 1:length(f)) = f;  % 存储为行向量
    end
end
2.2.4 非支配排序
matlab 复制代码
function [front, rank] = non_domination_sort(F)
    % 非支配排序:将种群分为不同前沿(front=1为Pareto前沿)
    nPop = size(F, 1);
    nObj = size(F, 2);
    front = zeros(nPop, 1);  % 前沿等级
    rank = zeros(nPop, 1);   % 个体秩
    dominated_count = zeros(nPop, 1);  % 被支配次数
    dominated_set = cell(nPop, 1);     % 支配的个体集合
    
    % 计算支配关系
    for i = 1:nPop
        for j = 1:nPop
            if i == j, continue; end
            if all(F(i, :) <= F(j, :)) && any(F(i, :) < F(j, :))
                dominated_set{i} = [dominated_set{i}, j];  % i支配j
            elseif all(F(j, :) <= F(i, :)) && any(F(j, :) < F(i, :))
                dominated_count(i) = dominated_count(i) + 1;  % j支配i
            end
        end
        if dominated_count(i) == 0  % 不被任何个体支配,属于第1前沿
            front(i) = 1;
            rank(i) = 1;
        end
    end
    
    % 构建后续前沿
    current_front = 1;
    while any(front == current_front)
        next_front = current_front + 1;
        for i = 1:nPop
            if front(i) == current_front
                for j = dominated_set{i}
                    dominated_count(j) = dominated_count(j) - 1;
                    if dominated_count(j) == 0
                        front(j) = next_front;
                        rank(j) = next_front;
                    end
                end
            end
        end
        current_front = next_front;
    end
end
2.2.5 拥挤度计算
matlab 复制代码
function crowding = crowding_distance(F, front)
    % 计算拥挤度(保持种群多样性)
    nPop = size(F, 1);
    nObj = size(F, 2);
    crowding = zeros(nPop, 1);
    
    for m = 1:nObj  % 对每个目标函数计算
        [sorted_F, sort_idx] = sort(F(:, m));  % 按目标m排序
        sorted_front = front(sort_idx);
        crowding(sort_idx(1)) = Inf;  % 边界点拥挤度设为无穷
        crowding(sort_idx(end)) = Inf;
        
        f_min = sorted_F(1);
        f_max = sorted_F(end);
        if f_max - f_min < 1e-6, continue; end  % 避免除零
        
        for i = 2:nPop-1
            if sorted_front(i) ~= sorted_front(i-1) || sorted_front(i) ~= sorted_front(i+1)
                continue;  % 不同前沿的点不相互影响
            end
            % 归一化距离
            dist = (sorted_F(i+1) - sorted_F(i-1)) / (f_max - f_min);
            crowding(sort_idx(i)) = crowding(sort_idx(i)) + dist;
        end
    end
end
2.2.6 选择操作(二元锦标赛)
matlab 复制代码
function parents = tournament_selection(pop, front, crowding, nPop)
    % 二元锦标赛选择:优先选前沿等级高的,同前沿选拥挤度大的
    n = size(pop, 1);
    parents = zeros(nPop, size(pop, 2));
    for i = 1:nPop
        cand1 = randi(n);
        cand2 = randi(n);
        if front(cand1) < front(cand2)  % 前沿等级高者胜出
            winner = cand1;
        elseif front(cand1) > front(cand2)
            winner = cand2;
        else  % 同前沿,拥挤度大者胜出
            if crowding(cand1) > crowding(cand2)
                winner = cand1;
            else
                winner = cand2;
            end
        end
        parents(i, :) = pop(winner, :);
    end
end
2.2.7 遗传操作(交叉+变异)
matlab 复制代码
function offspring = genetic_operators(parents, params, varMin, varMax)
    % 交叉(SBX)与变异(多项式变异)
    nPop = size(parents, 1);
    nVar = size(parents, 2);
    offspring = zeros(nPop, nVar);
    
    for i = 1:2:nPop
        p1 = parents(i, :);
        p2 = parents(i+1, :);
        
        % 交叉
        if rand < params.pc
            [c1, c2] = sbx_crossover(p1, p2, params.eta_c, varMin, varMax);
        else
            c1 = p1; c2 = p2;
        end
        
        % 变异
        c1 = poly_mutation(c1, params.pm, params.eta_m, varMin, varMax);
        c2 = poly_mutation(c2, params.pm, params.eta_m, varMin, varMax);
        
        offspring(i, :) = c1;
        if i+1 <= nPop, offspring(i+1, :) = c2; end
    end
end

% SBX交叉
function [c1, c2] = sbx_crossover(p1, p2, eta_c, varMin, varMax)
    nVar = length(p1);
    c1 = p1; c2 = p2;
    for i = 1:nVar
        if rand < 0.5
            beta = (2*rand)^(1/(eta_c+1));  % 交叉参数
            c1(i) = 0.5*((1+beta)*p1(i) + (1-beta)*p2(i));
            c2(i) = 0.5*((1-beta)*p1(i) + (1+beta)*p2(i));
            % 边界处理
            c1(i) = min(max(c1(i), varMin(i)), varMax(i));
            c2(i) = min(max(c2(i), varMin(i)), varMax(i));
        end
    end
end

% 多项式变异
function c = poly_mutation(p, pm, eta_m, varMin, varMax)
    nVar = length(p);
    c = p;
    for i = 1:nVar
        if rand < pm
            delta = (2*rand)^(1/(eta_m+1)) - 1;  % 变异步长
            c(i) = p(i) + delta*(varMax(i)-varMin(i));
            c(i) = min(max(c(i), varMin(i)), varMax(i));
        end
    end
end
2.2.8 环境选择(精英保留)
matlab 复制代码
function [new_pop, new_F, new_front, new_crowding] = environmental_selection(combined_pop, combined_F, nPop)
    % 从合并种群(2nPop)中选择nPop个精英个体
    nCombined = size(combined_pop, 1);
    [front, rank] = non_domination_sort(combined_F);
    crowding = crowding_distance(combined_F, front);
    
    % 按前沿等级+拥挤度排序
    [~, sort_idx] = sortrows([rank, -crowding], [1, 2]);
    selected_idx = sort_idx(1:nPop);  % 选择前nPop个个体
    
    new_pop = combined_pop(selected_idx, :);
    new_F = combined_F(selected_idx, :);
    new_front = front(selected_idx);
    new_crowding = crowding(selected_idx);
end

参考代码 基于nsga-2的多目标优化算法 www.youwenfan.com/contentcst/160578.html

三、应用案例:ZDT测试函数优化

3.1 ZDT1测试函数(双目标)

min⁡f1(x)=x1\min \quad f_1(x) = x_1minf1(x)=x1
min⁡f2(x)=g(x)(1−x1g(x))\min \quad f_2(x) = g(x) \left(1 - \sqrt{\frac{x_1}{g(x)}}\right)minf2(x)=g(x)(1−g(x)x1 )
g(x)=1+9∑i=2nxin−1,xi∈[0,1]g(x) = 1 + 9\sum_{i=2}^n \frac{x_i}{n-1}, \quad x_i \in [0,1]g(x)=1+9i=2∑nn−1xi,xi∈[0,1]

Pareto前沿:f2=1−f1,f1∈[0,1]f_2 = 1 - \sqrt{f_1}, f_1 \in [0,1]f2=1−f1 ,f1∈[0,1]。

3.2 MATLAB实现与可视化

matlab 复制代码
%% 案例:NSGA-II优化ZDT1测试函数
clear; clc; close all;

% 1. 参数设置
nVar = 10;          % 变量个数
varMin = 0;         % 变量下界
varMax = 1;         % 变量上界
nPop = 100;         % 种群大小
maxGen = 200;       % 迭代次数
params.pc = 0.9;     % 交叉概率
params.pm = 1/nVar;  % 变异概率

% 2. 目标函数(ZDT1)
zdt1 = @(x) deal(x(1), 1+9*sum(x(2:end))/(nVar-1) * (1 - sqrt(x(1)/(1+9*sum(x(2:end))/(nVar-1)))));

% 3. 运行NSGA-II
[pop, F, front, crowding] = nsga2(zdt1, nVar, varMin, varMax, nPop, maxGen, params);

% 4. 提取Pareto前沿
pareto_idx = find(front == 1);
pareto_F = F(pareto_idx, :);

% 5. 可视化Pareto前沿
figure;
plot(F(:,1), F(:,2), 'ro', 'MarkerSize', 6, 'DisplayName', '所有解');
hold on;
plot(pareto_F(:,1), pareto_F(:,2), 'b*', 'MarkerSize', 8, 'DisplayName', 'Pareto前沿');
xlabel('f1'); ylabel('f2'); title('ZDT1函数Pareto前沿');
legend; grid on;

% 6. 绘制理论Pareto前沿
f1_true = linspace(0, 1, 100);
f2_true = 1 - sqrt(f1_true);
plot(f1_true, f2_true, 'k--', 'LineWidth', 2, 'DisplayName', '理论前沿');
legend;

四、关键技术与性能评估

4.1 参数调优建议

  • 种群大小:50-200(目标函数越多,种群需越大)。
  • 交叉/变异概率 :pc=0.8∼0.95p_c=0.8\sim0.95pc=0.8∼0.95,pm=1/nvar∼1/2p_m=1/n_{\text{var}}\sim1/2pm=1/nvar∼1/2。
  • 分布指数 :ηc=10∼20\eta_c=10\sim20ηc=10∼20(交叉),ηm=20∼100\eta_m=20\sim100ηm=20∼100(变异,值越大变异越温和)。

4.2 性能评估指标

  • 世代距离(GD):衡量解集与真实Pareto前沿的距离(越小越好)。
  • 反向世代距离(IGD):衡量真实Pareto前沿到解集的距离(越小越好)。
  • 超体积(HV):衡量解集的"覆盖范围"(越大越好)。
  • 间距(Spacing):衡量解集分布的均匀性(越小越好)。

4.3 约束处理方法

  • 罚函数法 :将约束违反量加入目标函数,如 F′=F+λ⋅penaltyF' = F + \lambda \cdot \text{penalty}F′=F+λ⋅penalty。
  • 可行解优先:选择时优先保留可行解,可行解间用拥挤度比较。

五、高级功能扩展

5.1 并行计算加速

matlab 复制代码
% 用parfor并行评估目标函数
function F = evaluate_population_parallel(fun, pop)
    nPop = size(pop, 1);
    F = zeros(nPop, 0);
    parfor i = 1:nPop  % 需开启并行池(parpool)
        f = fun(pop(i, :));
        F(i, 1:length(f)) = f;
    end
end

5.2 自适应参数调整

matlab 复制代码
% 动态调整交叉/变异概率
if mod(gen, 50) == 0
    params.pc = 0.9 - 0.1*gen/maxGen;  % 随迭代降低交叉概率
    params.pm = 1/nVar + 0.1*gen/maxGen;  % 随迭代提高变异概率
end

5.3 高维目标处理

  • 降维:用PCA将多目标降为2-3维可视化。
  • 目标分组:将强相关的目标合并,减少目标数量。

六、总结

NSGA-II通过非支配排序拥挤度计算,高效求解多目标优化问题,广泛应用于工程设计、资源调度、机器学习超参数优化等领域。MATLAB实现需注意:

  1. 目标函数需返回行向量;
  2. 合理设置参数(种群大小、交叉/变异概率);
  3. 用可视化(Pareto前沿)评估优化效果。
相关推荐
小碗羊肉2 小时前
【从零开始学Java | 第三十篇】不可变集合
java·开发语言
asdzx672 小时前
C#:通过模板快速生成 Word 文档
开发语言·c#·word
专注VB编程开发20年2 小时前
Delphi 的VCL控件库无法公开给其他编程语言调用
开发语言·delphi
香蕉鼠片2 小时前
第三大的数
数据结构·算法·leetcode
汀、人工智能2 小时前
[特殊字符] 第28课:相交链表
数据结构·算法·链表·数据库架构··相交链表
charlie1145141912 小时前
现代Qt开发——0.1——如何在IDE中配置Qt环境?
开发语言·c++·ide·qt·嵌入式
游乐码2 小时前
c#StringBuilder
开发语言·c#
五阿哥永琪2 小时前
record只读类
java·开发语言
计算机安禾2 小时前
【数据结构与算法】第32篇:交换排序(一):冒泡排序
c语言·数据结构·c++·算法·链表·排序算法·visual studio code