PlatEMO v2.0实操指南:多目标优化算法、测试问题与指标一站式对比
简介面向多目标优化研究的 PlatEMO v2.02019-3以 Matlab 源码形式提供适用于科研人员、工程师及高年级学生进行算法设计、性能对比与教学演示。包内共 593 个文件其中 579 个 m 源码文件构成算法库、问题库及实验管理模块并配有 md/txt 说明、mat 示例数据、pdf 指导材料等压缩包仅 1.45MB轻量且易于部署。平台集成 NSGA-II、NSGA-III、MOEA/D、SMS-EMOA 等经典与前沿算法支持自定义新算法、可视化帕累托前沿和统计图表也提供标准测试问题集模块化的 GUI 便于交互式操作结合实验配置、运行管理与结果分析功能便于开展大规模对比和复现实验并可用于工程设计、资源分配、能源管理等领域。2019-3 版本继续改进性能与易用性目前已有 697 人学习下载对搭建自己的多目标优化实验环境、快速验证算法想法具有直接帮助适合入门与进阶研究。1. PlatEMO v2.0把多目标优化的算法、测试问题、评估指标打包成一套可跑的实验框架做多目标优化的实验时最消耗耐心的地方往往不在算法本身而在重复搭实验环境要手写 NSGAII 的排序模块要接上 ZDT 还是 DTLZ 测试集的读取还要自己算 IGD、HV 这些指标最后画帕累托前沿。PlatEMO v2.0 这个版本把这三件事合并成了一个整体算法库、测试问题库、性能指标库都挂在同一个框架下用两三行命令就能起一次对比实验。它适合想快速验证多目标进化算法效果的人不管你在做课程设计、科研对比还是工程上初步选型。这篇文章按我拆平台的习惯来写先讲清楚框架怎么组织再把自定义问题和参数讲到可以直接抄的程度最后列出我踩过的坑。2. 框架跑通之前先拆目录入口函数、算法类、问题类与一次实验的生命周期2.1 先认清根目录里有什么ALGORITHM、PROBLEM、METRIC 三个目录各管哪一段PlatEMO 的核心思路可以概括成三层问题层、算法层、评估层。解压之后别急着双击某个文件跑先把目录结构认一遍后续所有自定义文件都会被放进对应文件夹里。PlatEMO/ ├─ ALGORITHM/ # 算法实现每个算法一个 .m 文件 ├─ PROBLEM/ # 测试问题与基准实例 ├─ METRIC/ # 性能指标计算如 IGD、HV、GD ├─ Result/ # 实验输出目录 └─ PlatEMO.m # 老版本常见的入口函数ALGORITHM 目录里每个文件都是一个独立的算法子类文件内实现了种群初始化、交叉变异、环境选择这些步骤。PROBLEM 目录放测试问题每个问题类里定义了决策变量维度、目标数量、上下界和目标函数。METRIC 目录则是纯计算模块输入解集和真实前沿输出数值。这三个目录的分工决定了你怎么使用这个平台想跑内置算法就直接用想换测试问题就把自己的问题文件放进去想改指标就去 METRIC 目录里找对应函数。文件名与类名必须保持一致这是 MATLAB 的类加载机制决定的差一个字母都找不到类。提示下载解压之后第一件事不是打开某个脚本跑 demo而是确认根目录里有这几层文件夹后续自定义文件都会被放到对应位置。2.2 命令行里跑通第一个实验NSGAII 在 ZDT1 上的一次完整执行先把平台目录加进 MATLAB 路径一次然后就能在命令行里跑标准实验。下面这套写法在 v2.0 及其后续版本里通用因为 ALGORITHM 和 PROBLEM 这两个类构造器是框架的稳定接口。addpath(genpath(PlatEMO)); algorithm ALGORITHM(NSGAII, N, 200); problem PROBLEM(ZDT1, M, 2); result algorithm.Solve(problem);代码逻辑分三步第一行addpath(genpath(PlatEMO))把整个平台目录递归加入 MATLAB 路径这样后面的ALGORITHM和PROBLEM类才能被找到。第二行用类名和参数名N, 200构造算法实例N 是种群规模设为 200 表示每一代保留 200 个个体。第三行实例化问题传入问题名称ZDT1和目标维度M, 2因为 ZDT 系列默认是双目标问题。最后一行的Solve方法开始执行整个优化流程返回的 result 结构里包含最终的决策变量和目标值。如果手头版本比较旧没有封装的类构造器也会提供platemo()这类入口函数最终逻辑等价只是参数传递方式从属性对变成结构体。参数含义不受影响。参数作用常见取值N种群规模100 / 200 / 500maxFE最大函数评估次数10000 100000M目标数量2 或 3视问题而定D决策变量数量与问题默认值相关metric需要计算的性能指标{IGD,HV}2.3 一次实验到底做了什么初始化、种群演进、评估与存档的生命周期命令行只写了三行但平台内部完整经历了一个进化周期的全部阶段。理解这个流程很重要因为自定义算法时你就是在改写其中某一步。% 实验生命周期示意伪代码便于理解内部调用关系 problem PROBLEM(ZDT1, M, 2); % 第一步实例化问题载入边界和目标函数 algorithm ALGORITHM(NSGAII, N, 200); % 第二步实例化算法准备种群结构和算子 population algorithm.Initialization(problem); % 初始化在决策空间内生成初始种群 while ~problem.terminated(algorithm.FE) offspring algorithm.Variation(population); % 交叉与变异 population algorithm.EnvironmentalSelection(population, offspring); % 环境选择 end result problem.CalMetric(algorithm, population);初始化阶段会读取问题里设定的 lower 和 upper 边界然后在这个超矩形空间内生成均匀分布的初始种群。之后进入主循环终止条件problem.terminated(algorithm.FE)判断的是函数评估次数FE这是多目标进化算法里最常用的终止方式而不是设定迭代代数因为算子不同、每代评估次数可能不同。循环体内先是Variation负责交叉和变异生成子代然后是EnvironmentalSelection这一步根据非支配排序和拥挤距离从中选择下一代个体。整个循环结束后CalMetric会调用你在 algorithm 里指定的指标模块把最终解集和真实前沿做计算得到结果。平台内部各阶段的具体函数名随版本略有调整但顺序固定先初始化再迭代评估最后算指标。后面自定义问题时你只需要关注问题类的四个属性字段和三组计算方法其余交给框架。3. 把算法接到自己的数据上自定义问题、指标选型与运行参数清单3.1 定义自己的双目标问题从 SetInfo 到 CalObj 的完整类模板内置问题只是起点实际使用场景里往往要换成自己的优化目标比如让某个仿真模型的两个输出互相矛盾把它们当成双目标来优化。这时候就需要写一个自己的问题类。classdef MyBiObj PROBLEM properties coef 0.5; % 自定义参数目标函数里的权重系数 end methods function SetInfo(obj) obj.M 2; % 目标数量两个目标 obj.D 10; % 决策变量数量10 维 obj.lower zeros(1, obj.D); obj.upper ones(1, obj.D); obj.maxFE 10000; % 默认最大函数评估次数 end function PopObj CalObj(obj, PopDec) x PopDec; f1 x(:, 1); g 1 9 * sum(x(:, 2:end), 2) / (obj.D - 1); f2 g .* (1 - (f1 ./ g) .^ 0.5); PopObj [f1, f2]; end function PopCon CalCon(obj, PopDec) PopCon []; % 无约束问题返回空矩阵 end end end这段代码直接保存为MyBiObj.m放进 PROBLEM 目录然后在命令行里执行PROBLEM(MyBiObj)就能被平台识别。SetInfo方法负责告诉平台这个问题的基本盘M是目标数量D是决策变量个数lower和upper定义搜索空间边界。这些字段直接决定初始种群的采样范围和系数评估时的维度校验逻辑。CalObj是目标函数本体输入PopDec是 N 行 D 列的决策变量矩阵输出PopObj是 N 行 M 列的目标值矩阵。这里模仿了 ZDT1 的构造第一个目标只取第一维变量第二个目标通过一个 g 函数把所有其余维度累计起来形成相互冲突的双目标结构。如果实际问题没有解析表达式可以在这个函数里调用外部仿真模型把每行决策变量送进去拿回两个性能值。CalCon返回约束矩阵无约束问题直接返回空数组。如果约束存在返回维度是 N 行乘约束数量平台内部会统一做可行性判断不需要你自己写罚函数。注意类名、文件名、类声明三处必须完全一致MATLAB 对类文件名的检查非常严格。PopCon即使为空也建议显式写出来给后续维护代码的人一个明确信号。3.2 指标选型不靠手感IGD、HV、GD 在什么场景下才可用实验跑完总要评估解集好不好但指标不是越多越好选错指标会让数字完全失去意义。我在一次对比实验里看到有人把 IGD 用在没有真实前沿的自定义问题上结果数值忽高忽低最后重新理解了指标的使用边界。指标需要参考前沿数值偏好适用场景注意点IGD需要越小越好已知问题真实 PF 的横向对比参考前沿质量直接影响数值HV不需要越大越好未知真实前沿时的覆盖评估需要指定参考点参考点选取影响结果GD需要越小越好只关注收敛性不反映分布均匀度SP不需要越小越好辅助看均匀程度一般配合 IGD/HV 使用IGD 度量的是真实帕累托前沿到解集的距离它需要一组高质量的参考前沿点集。平台内置的 ZDT、DTLZ 系列问题通常都带了标准参考前沿所以可以直接用。HV 计算解集在目标空间里覆盖的体积只需要一个参考点不依赖真实前沿适合没有现成参考集的自定义问题。实际选择时我一般这样定对比内置基准问题时用 IGD 加 HV 两个指标一个看收敛一个看覆盖跑自己的工程问题时只用 HV因为拿不到权威参考前沿时 IGD 数值没有解释力。运行时要计算哪些指标在 ALGORITHM 构造器里传入 metric 属性对。algorithm ALGORITHM(NSGAII, N, 100, maxFE, 20000, ... metric, {IGD, HV});传入之后Solve 结束后返回的 result 里就会带上这两个指标的数值。如果所选问题没有注册参考前沿IGD 那一项可能直接被跳过或返回空值所以输出结构体里的字段要检查一下再拿来画图。3.3 运行参数怎么调整种群规模、最大评估次数与随机种子同样的算法跑同样的问题不同参数设置能得到差异很大的结果这部分参数需要根据自己的场景实验着来但有几个基本原则可以先固定下来。rng(42); % 固定随机种子保证后续实验可复现 algorithm ALGORITHM(NSGAII, ... N, 200, ... % 种群规模值越大种群多样性越好但单代耗时上升 maxFE, 40000, ... % 函数评估总次数决定实验终止时机 metric, {IGD, HV}, ... seed, 42); % 传给算法内部的随机种子参数 problem PROBLEM(ZDT1, M, 2); result algorithm.Solve(problem);N的取值建议从默认的 100 起步如果解集明显分布不均匀再加大到 200 或 500。种群规模翻倍单代计算量也会翻倍但对实验结论的影响通常比调整算子概率更直观。maxFE是函数评估总数这里设 40000 意味着平均每个个体大约被评估 200 次对于双目标连续问题已经是比较宽松的预算。如果目标函数来自外部仿真单次评估耗时高可以把这个数字调低到 5000 到 10000 先验证流程。随机种子在实验结果可复现性上至关重要。我一般会在调用平台前先执行一次rng(42)同时把 seed 参数也传给算法双保险。如果平台内部某些算子自己重置了随机流至少外层 rng 能覆盖到初始化阶段。3.4 在自定义问题上批量跑实验并读回结果单次实验跑通没什么难度难的是批量对比时保持一致的条件。我常用的做法是把实验参数集中写在一个脚本里循环跑完所有算法和问题组合结果分开保存。% 批量对比的脚本骨架 rng(42); problems {ZDT1, ZDT2, MyBiObj}; algs {NSGAII, MOEAD, SPEA2}; for p 1:length(problems) for a 1:length(algs) problem PROBLEM(problems{p}, M, 2); algorithm ALGORITHM(algs{a}, N, 200, ... maxFE, 40000, metric, {IGD,HV}); result algorithm.Solve(problem); save(sprintf(result_%s_%s.mat, problems{p}, algs{a}), result); end end这套循环把所有问题与算法组合交叉跑一遍每个结果单独存为一个 .mat 文件。文件名里带上问题和算法名后续读文件时不需要额外的映射表。批量实验最容易翻车的地方是某个算法在某类问题上崩溃所以循环体外建议先最小化试跑一次确认三个问题的 M、D、边界设置都匹配再放开完整规模。4. 避坑与常见问题PlatEMO 使用中容易出错的五个真实案例平台本身封装得比较厚报错信息有时候并不指到根因更多时候是问题类加载失败、维度不匹配、指标选错这三类问题。下面五个坑我几乎每次换新版本或新同学接手时都会碰到一遍写出来供你对照排查。4.1 找不到自定义问题类多数是路径和文件名不一致运行PROBLEM(MyBiObj)报了类似“未定义变量或找不到类”的错误MATLAB 路径下明明已经有这个文件却加载不出来。新手期最容易混淆的一点是文件放在桌面或任意目录里但 MATLAB 的工作路径没有包含那个目录。原因就是类文件没有放进平台根目录下的 PROBLEM 文件夹或者文件名和类名差了一个字符。MATLAB 的类加载机制要求文件名匹配类声明MyBiObj.m里第一行必须是classdef MyBiObj PROBLEM。解决方式是统一操作把文件复制到 PROBLEM 目录下确认文件名和类名完全一致然后执行一次addpath(genpath(PlatEMO))让路径重新加载。执行which MyBiObj检查 MATLAB 实际找到的文件路径如果输出为空或指向其他目录按上面两步排查。4.2 CalObj 返回的目标矩阵维度不对结果不报错但指标全错这个坑隐蔽得多程序完整跑完控制台不报任何错误但最终画出来的帕累托前沿全是 NaN或者目标值范围完全不对。问题类里明明已经写了目标函数为什么出错却不提示。原因是CalObj返回的矩阵维度与SetInfo里设置的M不一致比如 M 设为 2但函数只返回了一列目标值。平台在校验维度时可能自动填充或广播导致矩阵形状错误但不抛异常最终数值全乱。解决方式很简单在CalObj函数里加一行断言让维度问题直接暴露出来。function PopObj CalObj(obj, PopDec) x PopDec; f1 x(:, 1); g 1 9 * sum(x(:, 2:end), 2) / (obj.D - 1); f2 g .* (1 - (f1 ./ g) .^ 0.5); PopObj [f1, f2]; assert(isequal(size(PopObj), [size(PopDec,1), obj.M]), 目标矩阵维度错误); end用assert把维度约束显式写出来出错时第一时间定位到问题类而不是在整个实验脚本里找原因。同时建议写完自定义问题后先用小规模 N5 跑一次并打印PopObj的第一行确认数值方向在合理范围内。4.3 IGD 指标波动异常参考前沿和真实前沿没有对齐同一个算法、同一组参数连续跑三次实验IGD 数值忽大忽小甚至出现两次正常一次 NaN 的情况。如果你用的是自定义问题这个现象几乎可以锁定到参考前沿不匹配。原因是 IGD 指标需要一组真实帕累托前沿的参考点集平台内置问题一般自带参考集所以内置问题上跑指标没有问题。而自定义问题没有注册参考前沿指标模块就会退回用某个默认的近似集合来算本质上是用不匹配的前沿算一个没有意义的距离数值自然飘。解决方式分两种要么在自定义问题上改用 HV 指标HV 不依赖参考前沿逻辑上说得通要么给自定义问题手工生成一个足够密集的近似前沿文件放到平台对应的参考集目录中。工程实践里我会优先选 HV因为权威参考前沿对于没有解析解的工程问题几乎不可能手工构造。4.4 随机种子设置了但多次运行结果仍不稳定有人在脚本开头写了rng(42)认为实验结果就完全可复现了结果重复跑两次目标值曲线仍然有可见差异于是怀疑平台内部有什么随机性没有被覆盖。原因有两个层面。第一平台算法类内部可能自带随机种子参数如果不显式传入它会继承 MATLAB 当前全局随机状态外层 rng 设置之后又被内部代码覆写。第二如果开了并行池每个 worker 有自己的随机流主线程的rng设置不会同步到 worker。解决方式是在构造 ALGORITHM 时把 seed 作为参数显式传入比如seed, 42这是平台预留的随机种子接口。并且在启动任何并行之前先执行rng(42)让主线程状态一致。测试时先用单线程模式确认相同参数下结果能完全一致再放开并行否则并行带来的随机性会让调试变得异常困难。4.5 MATLAB 路径里有同名函数平台加载了错误实现调用某个内置指标或工具函数时计算结果和平台文档对不上甚至错误信息指向的函数来源都不是平台目录。我在一次重新组织文件时遇到过手写了一个IGD.m放在工作目录里用于测试结果平台内部的指标调用也被路由到我的文件上所有 IGD 数值都按照我的错误实现计算。原因是 MATLAB 的函数搜索路径有优先级当前工作目录通常优先于后续路径。当工作目录存在同名IGD.m时平台内部调用IGD(...)命中的是工作目录版本。解决方式先执行which -all IGD列出所有同名文件位置确认平台目录没有被自己的文件遮蔽。然后把平台根目录通过addpath(genpath(PlatEMO))重新加在最前面再把工作目录里的同名文件改名或者移走。5. 结果管理比跑实验更值得花时间保存、批量对比与前沿画图实验本身只占整个流程的小部分真正花时间的是后续处理结果落盘、不同算法间的数值排序、画可读性高的前沿图。我会在每次 Solve 一结束就立刻把结果存成 .mat 文件而不是留在工作区里等会话结束。% 运行完成后把 result 立刻落盘 result algorithm.Solve(problem); save(exp_nsga2_zdt1.mat, result); % 另一会话里恢复并取出目标值做后处理 data load(exp_nsga2_zdt1.mat); PopObj data.result.PopObj;这样每次实验的原始数据都有据可查后面想换指标、换画法都不用重新跑一遍。批量对比时把多个算法的PopObj读出来叠加到同一张图上能直观看到收敛程度和分布范围的差异。figure(Color, [1 1 1]); plot(PopObj(:,1), PopObj(:,2), o, MarkerFaceColor, [0.2 0.6 0.8]); xlabel(f1); ylabel(f2); grid on;画图时注意两点一是不同算法的解集大小可能有差异如果数量悬殊先按非支配排序把前沿解抽出来再画避免只取一部分造成视觉误导二是双目标用二维散点图就够三个目标以上建议投影到二维或者用颜色映射辅助展示不要直接四维图。由于多目标实验涉及大量随机性任何一次数值可能都有运气成分。从那以后我每次批量实验都会强制先跑一个最小规模用例确认路径加载、指标选型、随机种子三条链路全都没问题再放开完整规模的对比。这套检查流程帮我省掉了大半返工希望帮到你。本文还有配套的精品资源点击获取