简介这是一套面向多目标优化算法研究与初学者的Matlab测试环境围绕NSGA-II、MOPSO等经典多目标进化算法提供完整实现与可视化对比方案适用于算法性能测试、实验复现和教学演示。压缩包共105个文件以87个m脚本为核心涵盖算法主程序、指标计算与辅助函数6个fig和6个png用于展示解集分布与收敛过程2个txt提供说明2个mat保存实验数据另有2个zip便于二次扩展整体仅4.89MB轻量易用。目前已有200人学习下载。借助这套环境读者可以直接运行典型多目标算法观察网格搜索、随机搜索等不同策略下的求解效果结合图形结果快速理解Pareto前沿、解集分布等概念也可基于现有代码修改测试函数或评价指标节省搭建实验环境的时间。1. 解开这个 zip 之前先想清楚你要拿它做什么做多目标优化的人手头几乎都有一个“基准测试包”一份压缩包里放着 ZDT、DTLZ、WFG 等标准测试问题集配上 MATLAB 实现的评估脚本套上自己的算法就能跑出 Pareto 前沿和评价指标。这套东西应用价值明确——新提出的多目标进化算法MOEA要在统一环境里和 NSGA-II、MOEA/D、SPEA2 比出高低审稿人和业务方都认这套流程。标题里“测试环境”就是一个可控的算法评估平台zip 里的 matlab 代码是整套评估的核心工具链。这个包能解决的是两类问题一是你的算法需要一套公认的复现测试场景否则跑一两个私有数据无法令人信服二是你需要一个正确的、可比较的基线代码库避免自己写测试函数时埋入不连续或多峰陷阱。适合的人群包括准备投递论文的研究者、刚接手多目标项目的工程师以及想评估现有算法库性能的架构师。与其拿到压缩包就解压运行不如先把包内结构、测试函数特性和指标含义搞清楚后面调优才不会瞎忙。2. 多目标优化测试环境的基本构成与选型逻辑2.1 多目标优化的核心矛盾牵制解与 Pareto 前沿多目标优化之所以比单目标麻烦在于多个目标之间经常互相干扰不存在一个解同时让所有目标达到最优。用数学语言说给定决策向量 x目标向量为 f(x) (f1(x), f2(x), …, fm(x))当且仅当没有一个解 y 能保证 fi(y) ≤ fi(x) 对所有 i 成立且至少一个严格小于时x 称为非支配解。所有非支配解构成的集合叫 Pareto 最优集映射到目标空间就是 Pareto 前沿。实践中你接触到的多目标优化测试环境本质就是让你在这个前沿附近评估算法的收敛性和多样性。% pareto_front_demo.m % 两个目标的简单示例演示如何从一组解中筛出非支配解 solutions rand(50, 3); % 列1目标1, 列2目标2, 列3解编号 n size(solutions, 1); is_dominated false(n, 1); for i 1:n for j 1:n if i ~ j all(solutions(j, 1:2) solutions(i, 1:2)) ... any(solutions(j, 1:2) solutions(i, 1:2)) is_dominated(i) true; break; end end end pareto_solutions solutions(~is_dominated, :); fprintf(非支配解数量: %d\n, size(pareto_solutions, 1));这段代码先把每个解与全部解逐一比较只有没有被任何其他解在两个目标上同时压制时才保留下来。注意第 5 行的条件序列all判断目标全部不差any保证至少一个目标更好两个条件缺一不可。很多入门资料写成时不区分相等的支配关系容易把重复解误判为互不支配这里建议严格按上述逻辑写。测试环境中的函数集通常在目标数、前沿几何形状和模态上做了区分正是为了让你的算法在多样化的压力下暴露弱点。2.2 测试函数分代的选型理由从 ZDT 到 DTLZ 再到 WFGZDT 系列是最早的一批双目标测试问题适合快速验证算法基本行为但它的前沿形状相对简单多为凸、凹或者离散段部分函数比如 ZDT4 带有大量局部前沿专门考察算法跳出局部陷阱的能力。DTLZ 系列的优点是目标数可以扩展从 3 目标到十几目标都行常用于高维目标空间的压力测试其中 DTLZ1 的前沿是超平面DTLZ2 是球面几何差异明显。WFG 系列则把可变长度决策变量、混合前沿、偏好转化的场景做了系统化适合细粒度分析算法在复杂 Pareto 几何下的表现。选型逻辑很简单如果空间受限优先用 ZDT1、ZDT2、ZDT4 加 DTLZ1、DTLZ2 这五个问题它们覆盖了凸前沿、凹前沿、多峰和可扩展目标数这几类主要特征。如果你的算法主打高维目标优化必须引入 DTLZ5 或 DTLZ7前者是降维前沿后者是非连续前沿这两类能测量算法对前沿连通性的敏感程度。WFG 系列虽然覆盖面广但计算量偏大不适合在开发流程中频繁跑。% ZDT1 目标函数实现 function [f] ZDT1(x) n numel(x); f1 x(1); g 1 9 * sum(x(2:end)) / (n - 1); f2 g * (1 - sqrt(x(1) / g)); endZDT1 的 Pareto 最优解在 g 1 时取得前沿形状为凸决策变量 x1 直接决定 f1剩下变量共同参与 g 的计算。测试环境里往往还会附加真实前沿数据文件一般以pf_zdt1.dat形式存在。如果你发现某个 zip 包只附带目标函数代码没有参考前沿文件可以自己用大种群、多代数的 NSGA-II 逼近一次并把结果保存为参考。这种做法不是最优解但在没有官方数据的测试问题上可作替代。3. 解析 zip 包结构与 MATLAB 代码的复现路径3.1 zip 包内典型目录结构测试集、指标与运行脚本拿到「多目标优化测试环境附matlab代码.zip」后正常情况解压出来体积不大但结构组织直接决定你后续改代码的便捷性。常见的分类方式是problems、metrics、algorithms和run_experiments.m四个部分problems 下按测试问题单独放 m 文件metrics 目录放 HV、IGD、GD 等指标函数algorithms 放待比较的算法代码根目录脚本负责串联全部流程。在 MATLAB 中建议用unzip(多目标优化测试环境附matlab代码.zip)解压后cd进入根目录再执行addpath(genpath(pwd))将所有子目录加入搜索路径。genpath的作用是把当前目录下所有嵌套子目录完整添加避免每次调用测试函数时找不到文件的异常。如果你发现运行脚本时报“Undefined function or variable”十有八九是路径没加全这时先用which ZDT1检查函数是否可见。% run_experiments.m 核心流程 clc; clear; close all; addpath(genpath(pwd)); % 1. 加载测试问题列表 problem_names {ZDT1, ZDT2, ZDT4, DTLZ1, DTLZ2}; n_runs 30; % 2. 为每个问题执行指定算法并记录 for p 1:numel(problem_names) prob str2func(problem_names{p}); for run 1:n_runs [best_solutions, best_obj] run_moea(prob, ... struct(popsize, 100, maxgen, 500)); % 保存结果到 data 文件夹 filename sprintf(results/%s_run%d.mat, problem_names{p}, run); save(filename, best_solutions, best_obj); end end这段脚本把测试环境的使用方式交待得很清楚先通过str2func把字符串变成可调用的函数句柄外层循环挨个处理测试问题内层循环跑多次独立实验。这里设置 30 次运行是业界常规因为多目标算法是随机优化器单次结果波动大必须用统计手段消除随机性。run_moea是算法封装函数需要你根据 zip 包内提供的算法代码对接输入参数用结构体统一传递后续扩展新算法时不需要修改主脚本。3.2 MATLAB 版本兼容性与工具包依赖检查zip 包多数时候只能保证作者当时的运行环境正确不保证你本地也能原样跑通。解压后第一步先看根目录有没有requirements.txt或者 README 列出依赖工具包常见依赖有 Global Optimization Toolbox 和 Statistics and Machine Learning Toolbox。如果你的环境缺少某些工具箱未必要去装完整工具箱很多测试函数和指标可以自己实现比如 IGD 指标计算只要会写距离运算即可不需要额外工具包。% 检查工具包是否可用 if license(test, Optimization_Toolbox) disp(优化工具箱可用); else disp(缺少优化工具箱部分高级函数不可用); end这里license(test, Optimization_Toolbox)返回逻辑真或假用于提前判断哪些代码执行必然会报错。多目标测试环境最致命的问题不是缺少工具箱而是 MATLAB 版本太老导致str2func、arrayfun这类函数的语法兼容性差异。我的经验是 2014a 版之后运行 ZDT/DTLZ 测试集问题不大但如果代码里用了tall、timetable这类新数据类型老旧版本的报错会很隐蔽建议直接升级到 2020a 以上版本。% 检查 MATLAB 版本 v ver(matlab); fprintf(当前 MATLAB 版本: %s\n, v.Release);代码的运行报错大致分三类一是函数不存在二是维度不匹配三是内存不足。对于维度不匹配多目标测试函数最常见的是把目标数 m 和决策变量数 n 搞混比如 DTLZ 系列的决策变量数必须满足 n ≥ m否则部分目标无法正确计算。遇到这类问题请检查测试函数的调用约定特别是 DTLZ2 的输入参数顺序多数实现是 (x, M)其中 M 是目标数。4. 参数应该如何设种群大小、迭代次数与算法参数4.1 种群大小与最大评价次数的经验值对照表多目标测试环境里的算法性能高度依赖参数配置同一算法在不同测试问题上最优参数可能截然不同。但科研评审里习惯固定参数所以你必须知道一套底线配置。下面这张表是我常年在 ZDT/DTLZ 测试集上积累出来的起点值适用性广如果你要用于论文实验建议在此基础上再做小范围扰动试验。测试问题种群大小最大迭代最大评价次数交叉概率变异概率ZDT1/ZDT2100250250000.91/nZDT42005001000000.91/nDTLZ12005001000000.91/nDTLZ2120300360000.91/nWFG62005001000001.01/n变异概率写成1/n表示每个决策变量有 1/n 的概率被扰动n 是决策变量个数。这是基于二进制编码和实数编码两种场景下的折中方案ZDT 家族的决策变量数通常是 30因此变异率约 0.033。交叉概率 0.9 是 SBX模拟二进制交叉算子常用的值分布指数 ηc 一般取 20如果你的 zip 包里用的是差分进化算子那交叉概率 CR 建议在 [0.3, 0.9] 里选测试环境里多做几组对比更稳妥。% 典型 NSGA-II 参数配置测试环境通用版本 params.popsize 100; % 种群大小 params.maxgen 250; % 最大迭代代数 params.pc 0.9; % 交叉概率 params.pm 1 / 30; % 变异概率30 为决策变量数 params.eta_c 20; % SBX 分布指数 params.eta_m 20; % 多项式变异分布指数4.2 最大评价次数的统计口径怎么对齐多目标优化社区对比算法时普遍使用“最大函数评价次数”Max Function Evaluations, MaxFE而非迭代代数作为终止条件。两者差别很关键同一个迭代代数下不同算法的种群大小不同导致实际评价次数天差地别。所以在测试环境里跑对比实验时要统一到 MaxFE。转换公式是 MaxFE popsize × 最大迭代代数。你会发现以上表中 ZDT1 为例250 代对应 25000 次评估这也是大量文献里的常见配置。如果你的 zip 包里的算法代码只接受迭代代数作为终止条件可以手动在外层包一层判断。写一个计数器每次调用目标函数评估时自增当计数达到 MaxFE 就强制终止。这里有个细节多目标算法在每一代里不只是种群个体做评估有些算子比如多项式变异会做边界检查和扰动但不会触发目标函数调用所以计数器只用挂在目标函数里最准确。% 带评估计数器的目标函数封装 function [f] counted_eval(prob, x, counter_handle) counter_handle.count counter_handle.count 1; f prob(x); if counter_handle.count counter_handle.maxeval error(MAXEVAL_REACHED); end end计数器用结构体counter_handle传递MATLAB 里结构体是值类型如果直接作为参数传进函数修改不会带回到外层所以必须用句柄类或者全局变量这里用结构体加handle后缀暗示其为句柄对象。更稳妥的做法是定义成containers.Map或者继承handle类的对象否则计数器只在函数内部生效而无法终止外层循环。这些细节在你拿到陌生 zip 包时才最容易踩坑。5. 评价指标怎么算IGD、HV 与 GD 的 MATLAB 实现5.1 世代距离 GD 与反世代距离 IGD 的原理与代码GD 衡量算法所得解集与真实前沿的逼近程度计算每个解到参考前沿最近点的欧氏距离然后取平均。IGD 恰好反过来从参考前沿上的每个点出发找到解集中距离最近的点再取平均。IGD 同时惩罚收敛性和多样性如果解集漏掉了前沿上某一段那么那段参考点的距离就会很大整体 IGD 值飙升。这一点对测试环境中的算法评估至关重要。用测试环境自带的数据文件时重点检查参考前沿点是否足够密集通常每个测试问题参考前沿应该有数百到数千个点。密度不足会导致指标失真我一般会先跑一遍 IGD如果结果异常偏大先查参考点密度而不是质疑算法。% compute_igd.m function igd compute_igd(pf, approx_set) % pf: 真实Pareto前沿每行一个目标向量 % approx_set: 算法所得非支配解的目标向量 n_pf size(pf, 1); distances zeros(n_pf, 1); for i 1:n_pf diff approx_set - repmat(pf(i, :), size(approx_set, 1), 1); sq_dist sum(diff.^2, 2); distances(i) sqrt(min(sq_dist)); end igd mean(distances); end核心逻辑在第 5 行到第 8 行先用repmat把参考点复制成与解集相同的行数然后整体做差求平方和min取最近距离。这里不建议写成两层 for 循环MATLAB 矩阵运算效率远高于循环特别是解集规模达到 1000 行时差距明显。对于 3 目标以上的问题直接用pdist2(approx_set, pf, euclidean)生成距离矩阵再按列取最小。5.2 超体积 HV 的实现方式与归一化陷阱HV 指标度量解集在目标空间里覆盖的体积大小以参考点通常取目标空间各维度的最差值为边界把解集与参考点之间围成的区域体积算出来。HV 的好处是不需要知道真实前沿因此在真实前沿未知的实际业务场景中依然可用。HV 越大越好因为意味着解集在目标空间中覆盖更多区域且收敛性越好覆盖越完整。HV 计算成本高尤其三维以上目标需要用到蒙特卡洛采样或递归划分逼近算法。% compute_hv.m 使用蒙特卡洛近似计算 HV function hv compute_hv(approx_obj, ref_point, sample_num) % approx_obj: 算法所得非支配解的目标值已全部转化为最小化方向 % ref_point: 参考点通常取各目标上界的 1.1 倍或最大值 dim size(approx_obj, 2); % 在参考点范围内均匀采样 samples rand(sample_num, dim); ranges repmat(ref_point, sample_num, 1); samples samples .* ranges; % 判断样本点是否被解集中的点支配 dominated false(sample_num, 1); for i 1:sample_num diff approx_obj - repmat(samples(i, :), size(approx_obj, 1), 1); if any(all(diff 0, 2) any(diff 0, 2)) dominated(i) true; end end hv_ratio sum(dominated) / sample_num; hv hv_ratio * prod(ref_point); end这个实现用采样点判断是否被支配来近似体积占比。第 8 行到第 11 行是关键样本点如果在目标空间中比某个解差即解向量每个维度都不大于样本且至少一个维度严格小那就认为这个样本点被解集覆盖。这里要求目标都做了最小化处理如果你的测试问题默认求最大化需要先取负号或取倒数否则一切指标失真。参考点的选取同样敏感常用策略是取各目标范围最大值的 1.1 倍既能包住整个解集又不至于引入太多无效体积。高维目标时 1.1 倍会让 HV 数值极大建议改成 1.05 倍或对每个目标单独归一化后再算。6. 把 zip 包变成你自己的算法测试平台的一个技巧测试环境的最大价值在于可扩展你不需要只跑包内预设的算法把自定义算法塞进去才是重点。建议先在 problems 目录里新增一个自己的测试问题函数比如制造一个双目标混合前沿问题观察现成的对比算法能否正确描述前沿形状以此验证平台的灵活性。再写一个适配器脚本把自己的算法封装成和包内算法相同的输入输出接口这样后续跑批量和统计检验时不需要改动任何测试环境代码。% 自定义测试问题双目标前沿为凹形加离散段 function [f] MyProblem(x) f1 x(1); g 1 9 * sum((x(2:end) - 0.5).^2); f2 g * (1 - sqrt(f1 / g) - (f1 / g) * sin(10 * pi * f1)); end这个函数参考了 ZDT3 的非连续前沿结构把正弦项加进去后前沿会分裂成几段测试算法的连通性保持能力。接口保持f myproblem(x)形式就可以被 run_experiments.m 直接调用。输送进指标计算函数前注意把求最小化的目标方向统一最好在函数顶部注释说明哪些目标已转换为最小化。最后做一个对比实验把自定义算法和 NSGA-II 在新增问题上各跑 30 次用signrank做 Wilcoxon 符号秩检验这种统计显著性判定在论文或者技术方案评审中远比只看均值有效。本文还有配套的精品资源点击获取