news 2026/10/21 3:21:53

NSGA-III MATLAB完整实现:多目标优化与Pareto前沿求解指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NSGA-III MATLAB完整实现:多目标优化与Pareto前沿求解指南

简介:非支配排序遗传算法第三代是对第二代算法的重要改进,本资源提供在MATLAB环境下稳定运行的完整实现,面向需要求解多目标优化问题的科研人员、工程技术人员和算法学习者,适用于工程设计、决策分析与机器学习等场景。压缩包为zip格式,仅含1个m脚本文件,包体大小约3KB,代码精简,便于直接嵌入相关项目或教学演示。目前已有2769人学习/下载,热度可见一斑。代码集中呈现了种群初始化、非支配排序、基于参考点的选择、交叉变异等核心步骤,运行后可以生成并展示帕累托前沿,帮助读者直观理解参考点机制如何提升解的均匀性与多样性;同时也可作为与第二代算法对比、计算等性能指标的后续研究基础。整个脚本结构清晰,适合作为算法理解、参数调优和二次开发的简洁范本。

1. NSGA-III完美运行MATLAB:一份多目标优化代码包如何让Pareto前沿不再难跑

做过多目标优化的人都有这种经验:成本和性能互相打架,常规的加权求和调一遍权重根本看不见全局Pareto前沿,尤其当目标数到3个以上时,结果往往挤在一个局部区域里。这个标着“NSGA-III完美运行MATLAB”的工程包,就是针对这个痛点来的。它把完整的NSGA-III算法、参考点生成、环境选择、DTLZ系列测试函数和一键绘图脚本都整理好了,解压后按顺序调用两个函数,就能跑出分布均匀的Pareto解集。适合刚接触多目标遗传算法、想快速验证算法效果,或是要把NSGA-III移植到业务优化场景的同学。下面每一段代码都是我实际跑通过之后拆出来的,照着复制能复现结果,修改也有迹可循。

2. 项目结构与技术选型:一个能直接跑起来的MATLAB工程长什么样

2.1 目录结构:主函数、参考点、环境选择、测试问题分开摆

解压这份资源后,第一件事是看目录组织。常见的做法是把算法逻辑、测试问题和工具函数拆成独立文件,每个文件只做一件事,方便单独调试。我跑的这个版本基本是下面这种结构:

nsga3_matlab/ ├── NSGAIII_main.m # 入口:设置参数、启动进化循环 ├── envselect.m # 环境选择:从 2N 个个体里选出下一代 ├── generate_ref_points.m # Das-Dennis 方法生成参考点 ├── nondominated_sort.m # 非支配排序,按支配关系分层 ├── associate_points.m # 归一化个体与参考点关联 ├── normalize_objectives.m # 基于理想点和极值点的归一化 ├── variation.m # SBX 交叉 + 多项式变异 ├── evaluate_DTLZ2.m # DTLZ2 目标函数,3 目标测试题 ├── evaluate_ZDT1.m # ZDT1 目标函数,2 目标快速验证 ├── plot_front.m # 画 3D / 2D Pareto 前沿 └── compute_igd.m # 计算 IGD 指标,量化收敛质量

这个文件清单的好处是模块边界非常清楚。入口脚本负责配置参数,其他全是纯函数,输入输出都是矩阵,不依赖全局变量。如果你以后想换成自己的业务目标,只要改评估函数那一行就行,其他部分不用动。

整个包的调用关系是一条线:NSGAIII_main 创建初始种群 → variation 交叉变异生成子代 → 父子合并后计算目标值 → envselect 选出下一代 → 循环。里面最关键、也最容易看不懂的是 envselect,因为 NSGA-III 和 NSGA-II 的分水岭就藏在这里。我用表格把每个文件的关键输入输出列出来,方便后面对照代码阅读:

文件职责关键输入关键输出
NSGAIII_main.m组装算法目标个数 M、剖分数 H、代数最终种群、目标值
envselect.m环境选择合并种群、目标值、参考点下一代种群
generate_ref_points.m生成参考点M、H参考点矩阵
nondominated_sort.m非支配排序目标矩阵 F分层结果
associate_points.m关联参考点归一化目标、参考点最近参考点编号与距离

把这个结构在脑子里过一遍,后面定位问题会快很多。代码报错在哪一层,就直接跳到对应的文件看,不用从入口一路往下追。

2.2 技术选型:为什么用 MATLAB 而不是 Python

很多人在第一眼看到这份资源时会问:现在 Python 的 pymoo、DEAP 那么成熟,为什么还要用 MATLAB 实现?我的实际感受是,项目中“算法验证”和“业务仿真”经常在同一个软件生态里。团队里做仿真的人大多数用 MATLAB,如果优化算法也写在 MATLAB 里,可以直接把最优解传回 Simulink 或数据处理脚本,连数据格式转换都省了。这种情况在毕设、横向课题里尤其常见,指导方会直接要求“以 MATLAB 为平台”,那就得有一份能跑通的 MATLAB 代码。

从算法实现角度,MATLAB 有几个天然优势。一是矩阵运算语法接近数学公式,nchoosek、sortrows、min 都是直接用的,不需要像 Python 那样到处 import numpy。二是调试体验好,断点打进去后,工作区里能直接展开每个矩阵的行列,观察 rank、niche 这些向量,哪个个体被选进下一代一眼就能看到。三是绘图成本低,plot3、scatter3 一行就出三维散点图,用来检查 Pareto 前沿分布非常方便。

这份实现里选用的核心算子是模拟二进制交叉 SBX 和多项式变异。常见参数配置是:交叉概率 pc=1.0,分布指数 eta_c=30;变异概率 pm=1/D,D 是决策变量个数,分布指数 eta_m=20。参考点用 Das-Dennis 生成,剖分数 H 是算法多样性的关键旋钮。以 3 目标问题为例:

H参考点数适用场景
628快速调试、看趋势
1291标准 3 目标实验
20231追求更密前沿
310双目标近似、算力紧张时

默认实验里我设 H=12,种群规模直接取参考点数 91。NSGA-III 对种群规模并没有强制魔数,但工程上最省心的做法就是把种群规模设成参考点数,这样环境选择不需要做“最后一层再削掉几个个体”的额外处理。如果参考点在环境选择时需要补层,种群规模可以设为参考点数的 1.2 倍,这也是论文里常见的变体。

2.3 运行环境与版本兼容:不用装额外工具箱

这份代码不依赖 MATLAB 的 Optimization Toolbox 或 Global Optimization Toolbox,唯一的硬性要求是 MATLAB 本身。我分别在 R2019a 和 R2021b 上跑过,R2018a 之后的版本基本没有兼容问题。要注意的是,代码里用了 vecnorm 这个相对较新的函数,如果手边还是 R2016a 这种老版本,建议写成下面这种兼容写法:

% 兼容 R2017b 以下版本 if exist('vecnorm', 'builtin') W_norm = vecnorm(W, 2, 2); else W_norm = sqrt(sum(W.^2, 2)); end W = W ./ W_norm;

解压后第一步是进入源码目录,把当前文件夹和所有子文件夹加入 MATLAB 搜索路径,在命令窗口执行:

cd '你的解压路径/nsga3_matlab' addpath(genpath(pwd));

snapath(genpath(pwd))会把当前目录下所有子目录一次性加入搜索路径。很多第一次跑的人,双击 NSGAIII_main.m 后直接报 UndefinedFunction,原因就是没执行这一步。这种问题我后面还会专门给你列出排查方法。路径设置好之后,这份资源就能在干净 MATLAB 环境下跑起来,不用再装任何第三方算法库。

3. 核心模块拆解:一个NSGA-III个体从出生到进入下一代的完整链路

3.1 进化主循环:每一代到底做了什么

现在进到核心代码。NSGAIII_main.m 是整个工程的入口,它做的事非常直接:定义问题、生成参考点、初始化种群,然后跑 max_gen 轮进化。核心循环代码如下:

% NSGAIII_main.m 核心循环 function result = NSGAIII_main() % 参数区 M = 3; % 目标数量 D = 12; % 决策变量数量 H = 12; % 参考点剖分数 max_gen = 500; % 最大迭代代数 pc = 1.0; eta_c = 30; % SBX 交叉参数 pm = 1/D; eta_m = 20; % 多项式变异参数 % 固定随机种子,保证每次运行可复现 rng(1); % 生成参考点并决定种群规模 ref_points = generate_ref_points(M, H); N = size(ref_points, 1); % 初始化种群:在 [0,1] 决策变量空间里随机采样 pop = rand(N, D); f = evaluate_DTLZ2(pop, M, D); for gen = 1:max_gen % 1. 交叉变异生成子代 offspring = variation(pop, f, pc, eta_c, pm, eta_m); % 2. 计算子代目标值 off_f = evaluate_DTLZ2(offspring, M, D); % 3. 环境选择:从 2N 个个体里选回 N 个 [pop, f] = envselect([pop; offspring], [f; off_f], M, ref_points); end result.pop = pop; result.f = f; end

逻辑说明和参数说明

这里的 main 函数把每一代浓缩成三个动作:交叉变异生成子代、合并父子两代、环境选择压缩回 N 个个体。核心是 envselect 这一行,它决定了 NSGA-III 与 NSGA-II 的真正差异:NSGA-II 靠拥挤距离保持多样性,NSGA-III 则靠参考点的小生境计数。每次循环结束后,pop 都是上一代里表现最好的 N 个个体,f 是对应的目标值矩阵。

参数里需要注意的是 pm 写成 1/D,意思是每个变量有 1/D 的概率发生变异,这是多项式变异最常见的设置。如果问题有 20 个变量,pm 就是 0.05。eta_c 和 eta_m 控制子代和父代的相似程度,值越大,子代越接近父代,搜索越局部;值越小,实验性越强,探索越激进。我一般从 eta_c=30、eta_m=20 起步,结果不理想再往大调。还有固定随机种子这个习惯,对做实验对比的人来说很重要,否则同一份代码连续跑两遍 IGD 都不一样,后面没法写进报告。

3.2 参考点生成:Das-Dennis 怎么铺出一张均匀的多样性网格

参考点是 NSGA-III 的全局地图。每个个体最终都要找到离自己最近的参考点,所以参考点分布是否均匀,直接决定 Pareto 前沿是否均匀。generate_ref_points.m 用的是 Das-Dennis 方法,核心逻辑是枚举所有满足和为 H 的非负整数组合。一份可运行的递归实现如下:

function W = generate_ref_points(M, H) % M: 目标个数 % H: 每个目标方向的剖分数 % 返回参考点矩阵,每一行是一个单位参考向量 if M == 1 W = H; return; end W = []; for s = 0:H % 先固定第一个维度的分量 s,再递归生成剩余 M-1 维 sub = generate_ref_points(M - 1, H - s); part = [s * ones(size(sub, 1), 1), sub]; W = [W; part]; end W = W / H; % 各分量和为 1,落在超平面上 W = W ./ vecnorm(W, 2, 2); % 归一化到单位球面 end

逻辑说明和参数说明

这段代码的原理是把 H 个单位“切分”到 M 个目标方向上,得到一组等间隔点。M=3、H=12 时一共生成 91 个点;H=20 时生成 231 个点。把 W 的每行看成一个从原点出发的向量,种群里的个体在进化过程中会沿着这些向量的方向扩散。如果参考点数量不够,种群的多样性就没有着力点,结果容易扎堆。

这段代码里有两个关键点。第一,递归深度是 M,对 3 目标问题非常轻松;如果目标数到 7、8 个,参考点数量会指数级增长,那时候就要考虑两层权重生成边界参考点和内部参考点,而不是简单枚举。第二,注释里写着“归一化到单位球面”,这步可以加强数值稳定性。关联个体到参考点时,参考向量是否归一化都不影响方向,但统一单位长度能避免某些参考向量模长不同导致距离计算偏差的玄学问题。

3.3 非支配排序与归一化:在淘汰之前先把候选分层

拿到父子合并后的 2N 个个体,第一件事是判断谁支配谁。非支配排序函数 nondominated_sort.m 的实现如下:

function [rank, layers] = nondominated_sort(f) % f: N × M 目标矩阵 % rank: 每个个体所在非支配层编号 % layers: 每一层的个体下标,layers{1} 最优 N = size(f, 1); dominated_count = zeros(N, 1); dominate_set = cell(N, 1); % 两两比较,统计每个个体被多少个体支配,以及支配谁 for i = 1:N-1 for j = i+1:N if dominates(f(i,:), f(j,:)) dominated_count(j) = dominated_count(j) + 1; dominate_set{i} = [dominate_set{i}, j]; elseif dominates(f(j,:), f(i,:)) dominated_count(i) = dominated_count(i) + 1; dominate_set{j} = [dominate_set{j}, i]; end end end % 第一层是支配计数为 0 的个体,然后逐层解除支配 rank = zeros(N, 1); layers = {}; layer = 1; current = find(dominated_count == 0); while ~isempty(current) layers{layer} = current; rank(current) = layer; next = []; for idx = current for k = dominate_set{idx} dominated_count(k) = dominated_count(k) - 1; if dominated_count(k) == 0 next = [next, k]; end end end current = next; layer = layer + 1; end end function res = dominates(a, b) % a 在所有目标上不劣于 b,且至少一个目标严格优于 b res = all(a <= b) && any(a < b); end

逻辑说明和参数说明

这段代码会返回每个个体所在的非支配层,layers{1} 是当前种群里最好的解,不会同时存在被其他解支配的情况。环境选择时,从第一层开始逐个加入下一代,直到凑满种群规模。比如要选 91 个个体,第一层有 40 个、第二层有 40 个、第三层有 30 个,那前两层 80 个直接进,第三层再做一轮“小生境选择”决定哪些进,哪些被淘汰。

这里的支配比较是 O(N²) 的,对 N=91、500 代完全够用;但如果把种群规模放大到 1000 以上,两两比较会成为性能瓶颈,那时要改用基于排序的快速非支配排序。目标函数值之间的尺度差异也需要处理,这就是 normalize_objectives.m 要做的事。如果目标 1 范围是 0 到 10 万,目标 2 范围是 0 到 1,直接把原始目标值拿来关联参考点,目标 1 会完全主导距离计算,结果就是种群全偏向目标 1。归一化函数按理想点平移,再按每个目标的极值点做缩放,把不同目标拉到同一个尺度上,这一步不做,后面关联参考点全是错的。

3.4 环境选择与参考点关联:NSGA-III 和 NSGA-II 的真正分歧

整个包最值得研究的函数是 envselect.m。前面非支配排序只是粗筛,最后一层怎么选,体现了 NSGA-III 的多样性保持策略。核心过程是先看哪些参考点附近个体少,然后优先从这些“空缺点”附近补人。

function [next_pop, next_f] = envselect(pop, f, M, W) N = size(W, 1); [~, layers] = nondominated_sort(f); % 先取前 K 层个体,直到无法完整容纳下一层 selected = []; selected_f = []; current_layer = 1; while size(selected, 1) + size(layers{current_layer}, 2) <= N && current_layer <= numel(layers) idx = layers{current_layer}; selected = [selected; pop(idx, :)]; selected_f = [selected_f; f(idx, :)]; current_layer = current_layer + 1; end if size(selected, 1) == N next_pop = selected; next_f = selected_f; return; end % 处理最后一层:归一化后关联参考点,再小生境选择补人 last_idx = layers{current_layer}; last_f_norm = normalize_objectives(f(last_idx, :)); [niche, dist] = associate_points(last_f_norm, W); % 统计已保留个体在各参考点的小生境数 sel_f_norm = normalize_objectives(selected_f); [~, niche_exist] = associate_points(sel_f_norm, W); rho = zeros(size(W, 1), 1); for k = 1:size(W, 1) rho(k) = sum(niche_exist == k); end while size(selected, 1) < N [~, j] = min(rho); % 找小生境数最少的参考点 cand = find(niche == j); % 最后一层中关联到这个参考点的个体 if isempty(cand) rho(j) = inf; continue; end [~, best] = min(dist(cand)); % 距离参考线最近者留下 sel_idx = last_idx(cand(best)); selected(end+1, :) = pop(sel_idx, :); %#ok<AGROW> selected_f(end+1, :) = f(sel_idx, :); %#ok<AGROW> rho(j) = rho(j) + 1; % 从待选列表里移除已选个体,下一轮重新关联 last_idx(cand(best)) = []; last_f_norm = normalize_objectives(f(last_idx, :)); [niche, dist] = associate_points(last_f_norm, W); end next_pop = selected; next_f = selected_f; end

逻辑说明和参数说明

这段代码里最关键的是 rho 数组,它记录每个参考点附近已经有多少个体。循环每次都找 rho 最小的参考点,意味着优先让个体往“稀薄”区域填充。这个机制替代了 NSGA-II 的拥挤距离排序,能够更稳定地保持高维目标空间中的分布均匀性。如果某个参考点附近已经没人可选,就把 rho 置为 inf,跳到下一个空缺参考点,保证最后一代不会出现大面积的空洞。

代码里有几个容易踩坑的细节。首先,last_idx 每轮都要同步删减,从 pop 里取实际个体用 pop(sel_idx, :),不要直接对 last_f 操作。其次,归一化要放在关联之前,不能拿原始目标值直接关联。第三,循环里每次重新关联会带来额外计算量,但这是为了保证从更新后的待选集合里找真正合适的个体,不能贪图省事省略。

4. 运行与验证:把DTLZ2跑通,再用IGD判断代码有没有写对

4.1 实操:在MATLAB里跑通并画出三维前沿

现在把代码跑起来。先按第 2 章讲的方式把路径设置好,然后在命令窗口执行:

rng(42); res = NSGAIII_main();

等待结果。3 目标、12 个变量、91 个个体、500 代,在常规笔记本上大约一两分钟就能跑完。跑完后画图:

% 画 NSGA-III 求得的近似前沿 figure; scatter3(res.f(:,1), res.f(:,2), res.f(:,3), 20, res.f(:,1), 'filled'); xlabel('f1'); ylabel('f2'); zlabel('f3'); title('NSGA-III on DTLZ2');

如果一切正常,你会看到一个分布均匀的三维三角曲面,靠近 f1+f2+f3=1 的那片空间。观察这几点:前沿边界是否完整,中间有没有空洞,三代对比下点是不是越来越密。DTLZ2 的真实前沿是单位球面第一象限的一部分,所以点云应该在一个扇形球面上慢慢展开。

一个非常直观的验证方法是把种群规模小一点、代数减到 80 代跑一次,再和 500 代的结果对比。代数越少,点越稀疏;500 代以后,点应该稳定铺满整个曲面。如果代数增加但结果还是挤在一个角落,问题大概率出在归一化或参考点关联上,而不是进化代数不够。

4.2 用IGD指标量化收敛:不靠肉眼,靠数字

肉眼看三维图只能大概判断分布,真要判断“代码写没写对”,还要用指标。最常用的指标是 IGD,反向世代距离,度量算法得到的近似前沿和真实前沿之间的平均距离。IGD 越小,说明既收敛又分布均匀。计算函数如下:

function igd = compute_igd(P, Q) % P: 算法得到的 Pareto 前沿近似集,大小为 N×M % Q: 真实 Pareto 前沿的均匀采样点,大小为 R×M % IGD: 每个真实点到最近近似点的平均距离 n = size(Q, 1); dist = zeros(n, 1); for i = 1:n d = vecnorm(P - Q(i, :), 2, 2); dist(i) = min(d); end igd = mean(dist); end

真实前沿的采样点可以直接用 generate_ref_points 生成高密度参考点,再归一化到 DTLZ2 的真实曲面上。DTLZ2 的真实前沿满足 sum(f_i^2)=1,把参考点模长归一到 1 即可近似采样。

% 采样真实前沿 ref = generate_ref_points(3, 40); true_pf = ref ./ vecnorm(ref, 2, 2); % 计算进化最终结果的 IGD igd_value = compute_igd(res.f, true_pf); disp(['IGD = ', num2str(igd_value)]);

逻辑说明和参数说明

compute_igd 的核心思想是:在真实前沿上撒很多均匀点,然后看每个真实点离算法解集中的最近点有多远。如果算法解集在真实前沿附近,距离自然小;如果解集缺了某一片区域,对应真实点就找不到近邻,距离变大,IGD 一下子反映出来。这就是为什么 IGD 比单纯看 HV 超体积更容易定位“收敛了但分布不全”的问题。

跑完以后可以对照这个经验值:500 代、3 目标 DTLZ2,IGD 达到 10^-3 量级说明实现没有大错,能达到 10^-4 说明归一化和关联逻辑都比较稳。如果 IGD 停在 10^-1 级别,说明种群里大量个体的目标值还远离真实前沿,优先检查变异步长是否过大、代数是否太少。要注意不同 DTLZ 问题的难度差异,DTLZ1 有超平面约束,收敛难度更高,IGD 达到 10^-2 也算可用,不必拿不同题目的指标互相排名。

4.3 参数调整参考:从快速验证到高精度实验

我整理了一份参数设置对照表,给不同场景用:

场景H种群规模代数IGD 预期范围
快速调试6281001e-2 量级
标准实验12915001e-3 量级
高精度验证2023110001e-4 量级

快速调试时把 H 和代数调低,主要是为了让代码跑得足够快,盯着环境选择逻辑有没有明显报错;标准实验才是论文里常见的配置;高精度验证适合最终出图,计算时间会明显增加。从快速调试切到标准实验时,如果 IGD 没有显著下降,问题不在参数,而在代码本身,建议回到第 3 章的关联函数逐行检查。

另外还可以用双目标 ZDT1 做交叉验证。NSGA-III 在双目标情况下,参考点退化成一条直线上的等分点,关联机制和 NSGA-II 的拥挤距离行为接近。如果双目标能跑出一条完整的 Pareto 曲线,说明交叉、变异、支配排序这些基础链路是通的;再上 3 目标 DTLZ2 时,把注意力集中在参考点小生境选择上就好。

5. 常见问题与排查:五个“程序没报错但结果不对”的真实教训

5.1 运行期报错的三个典型问题

问题一:调用 NSGAIII_main 时报 UndefinedFunction

现象:命令窗口提示Undefined function 'generate_ref_points' for input arguments of type 'double'。原因:源码目录没有加入 MATLAB 搜索路径,MATLAB 找不到子函数文件。解决:执行addpath(genpath(pwd)),并且把当前文件夹切换到源码根目录。我第一次跑这包时就没做这步,直接在别的工作目录里调 main,浪费了半小时。

问题二:nchoosek报错或组合数计算溢出

现象:当目标数 M 和剖分数 H 设得比较大时,参考点生成函数计算特别慢,甚至内存不够。原因:参考点数量是组合数关系,M=5、H=20 时参考点数量会到一万级别,环境选择每代都要做关联计算,慢是必然的。解决:对高目标数问题,不要追求特别大的 H,把 H 控制在 6 到 10 之间;如果必须高分辨率,改用两层参考点生成策略,边界一层、内部一层。这也算一个常见的坑,不要指望普通笔记本跑高目标数加高剖分还能很快。

问题三:环境选择循环里索引越界

现象:报错Index exceeds the number of array elements (0),位置在 envselect 的 while 循环里。原因:最后一层的个体在反复删除后,候选列表已经空掉,但 rho 还没填满所有空缺。常见于某个参考点附近只有一个个体,这个个体被选走后,再删就无元素可选。解决:在候选列表为空时把对应 rho 置为 inf 并 continue,代码里已经写了这个保护;如果你自己改过 envselect,注意每次从 last_idx 删除后,要同步重新计算 niche 和 dist,不能继续用旧的关联结果。

5.2 结果不对劲的两种情况

问题四:所有最终解挤在一个参考点附近

现象:运行结束后,scatter3 画出来的点云集中在一个小区域,整个前沿没有铺开。原因:归一化函数里极值点坐标可能为 0,导致缩放时除零,目标向量被无限放大;另一个原因是种群规模远小于参考点数量,多样性被环境选择直接剪掉了。解决:在 normalize_objectives 里给极值加一个下限,比如extreme(j) = max(extreme(j), 1e-6);检查种群规模是否等于参考点数量,如果手动改成 50 个个体却有 91 个参考点,结果一定不会好。

问题五:IGD 曲线中途回升,先降后涨

现象:每 50 代打印一次 IGD,发现前期降得很好,到 300 代后反而变大。原因:环境选择没有保留历史最优解,突变算子步长过大,后期把已经收敛的个体又踢出了前沿。解决:做精英保留,在每一代用上一代最优的几个个体直接替换掉最差个体;或者把 eta_m 从 20 调到 50,让变异算子更局部,减少对收敛区域的破坏。这个回升问题容易被忽略,因为最终结果看起来还行,但中间过程的非单调性说明算法稳定性有隐患。做实验记录时一定要把整条 IGD 曲线存下来,而不是只取最后一个点。

6. 二次开发:把测试函数换成自己的业务多目标模型

这份源码最实用的地方,是替换评估函数就能从测试题过渡到真实业务优化。假设你的业务有两个目标,一个是最小化成本,一个是最大化寿命,决策变量是某些连续参数。你要做的事就是新建一个 evaluate_myproblem.m:

function f = evaluate_myproblem(x, M) % x: N×D 的矩阵,取值范围 [0,1],需要反归一化到业务范围 % M: 目标个数 % 业务变量真实范围 lb = [10, 0.5, 1]; % 下限 ub = [100, 5, 20]; % 上限 X = lb + (ub - lb) .* x; % 反归一化 N = size(X, 1); cost = zeros(N, 1); life = zeros(N, 1); for i = 1:N cost(i) = price_model(X(i, :)); life(i) = lifetime_model(X(i, :)); end % 两个目标都按最小化方向统一 f = [cost(:), -life(:)]; end

然后在 NSGAIII_main.m 里把评估函数换掉:

% f = evaluate_DTLZ2(pop, M, D); f = evaluate_myproblem(pop, M);

换完以后有两点要特别留意。第一,算法内部所有决策变量都假设在 [0,1] 区间,所以真实业务变量一定要先做反归一化,否则种群初始化直接偏离业务可行域。第二,如果寿命要最大化,在目标函数里取负号,把最大化问题转成最小化问题,NSGA-III 内部只做最小化判断。

验证方法也有一个可靠套路:先拿一个你知道最优解的小规模业务例子跑一遍,看进化曲线能不能收敛到已知最优值附近;确认通过后,再用随机业务模型和真实业务模型各跑 3 个不同随机种子,对比目标均值和 IGD,判断改造后的逻辑是否稳定。这个流程能有效避免“改了评估函数但算法内部参数没跟上”的问题。

从那以后,我每次换问题前都强制在 DTLZ2 上先跑一遍,确认算法本身没问题,再无缝替换成自己的评估函数。这个习惯帮我避开了不少把参数问题误当成业务问题的翻车现场,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 3:20:50

策略梯度到PPO:降低方差、步长约束与连续控制全解析

学策略梯度的时候&#xff0c;很多人都会经历同一个阶段&#xff1a;看公式觉得理所当然&#xff0c;REINFORCE 就两行&#xff0c;策略梯度定理也清清楚楚&#xff0c;但一旦自己动手训练一个智能体&#xff0c;奖励曲线抖得像心律失常。我当初跟这门强化学习公开课的时候&…

作者头像 李华
网站建设 2026/10/21 3:20:07

Linux命令行重定向与管道原理及实战应用

1. 这不是“命令行玄学”&#xff0c;而是每天都在用的底层逻辑你有没有过这样的经历&#xff1a;在终端里敲完一条命令&#xff0c;屏幕瞬间刷出几百行日志&#xff0c;眼睛根本跟不上滚动速度&#xff1b;或者想把某个程序的错误信息单独保存下来分析&#xff0c;却只能眼睁睁…

作者头像 李华
网站建设 2026/10/21 3:19:32

7B模型金融智能体架构演进:从RAG到多Agent编排

1. 项目背景&#xff1a;为什么非要用7B模型做金融智能体做金融智能体这个项目&#xff0c;是从一次需求评审开始的。业务那边提了个很实际的需求&#xff1a;让投研人员用自然语言直接查公司财报、算财务指标、对比行业数据&#xff0c;不用再手动翻PDF和Excel。听起来不复杂&…

作者头像 李华
网站建设 2026/10/21 3:19:23

STM32 L298N电机驱动代码详解:PWM调速与方向控制避坑指南

简介&#xff1a;针对STM32与L298N电机控制应用场景&#xff0c;代码包提供可直接运行的Keil工程代码&#xff0c;适合正在学习嵌入式电机驱动、需要用按键调速调向的开发者。压缩包共143个文件&#xff0c;以h头文件、c源文件、o目标文件为主&#xff0c;另有hex烧录文件、uvp…

作者头像 李华
网站建设 2026/10/21 3:18:48

触摸键检测芯片TS223

一、概述TS223是 触 摸 键 检 测 IC&#xff0c; 提 供 1个 触 摸 键 。 触 摸 检 测 IC是 为 了用 可 变 面 积 的 键 取 代 传 统 的 按 钮 键 而 设 计 的 。低 功 耗 和 宽 工 作 电 压是 触 摸 键 的 DC和 AC特 点 。TS223采 用 SSOP16、 SOT23-6的 封 装 形 式 封 装 。二…

作者头像 李华
网站建设 2026/10/21 3:17:28

Linux性能测试与内存泄漏排查:从命令组合到负载模拟

做Linux性能测试&#xff0c;最忌讳的就是一拿到机器就到处敲命令&#xff0c;top看一眼、free扫一行、再随手压一把负载&#xff0c;半小时过去&#xff0c;除了几个飘忽的数字什么都没留下来。我这些年跟核心命令、负载模拟和内存泄漏排查打交道&#xff0c;最深的体会是&…

作者头像 李华