资讯详情

资讯详情

PlatEMO v2.0实战:多目标进化算法实验与自定义算法接入

简介PlatEMO v2.02019-3是面向多目标优化研究的进化算法平台适合科研人员、工程师以及高校学生用于算法开发、性能对比与教学演示。压缩包共593个文件其中579个m源文件构成完整Matlab工具箱另含md与txt说明文档、mat示例数据及pdf文档整体仅1.45MB。目前已有697人学习/下载。平台内置NSGA-II、MOEA/D等常用算法与标准测试问题库提供可视化界面、实验管理及可扩展接口包内可见GLOBAL.m、newGUI.m、module_experiment.m、module_test.m等核心模块可直接运行标准实验并扩展自定义算法非常适合快速搭建算法对比实验、系统掌握多目标进化算法的实践流程。1. PlatEMO v2.0为什么说它把多目标实验从“手动档”换成了“自动档”PlatEMO v2.02019年3月发布是MATLAB生态里知名度很高的多目标进化算法实验平台。很多人把它当成“带界面的算法集”实际上它最大的价值是把算法、测试问题、性能指标和实验记录这四件事焊成了一个整体。你只需要写好一个算法类剩下的对比实验、指标计算、结果存档全由平台接管。这套设计对两类人特别有用一是做进化计算研究、需要快速验证新机制的论文作者二是想把不同算法拉到同一标准下做横向对比的工程师。v2.0相比早期版本最大的变化是引入了统一的包结构和算法接口这也让它成了后续版本里被沿用最久的骨架。后面所有内容都以这个版本的实际目录结构为准。2. 把 PlatEMO v2.0 跑起来从解压到第一组结果的三个动作2.1 文件结构和本命文件夹先看懂 PlatEMO 是什么下载下来的v2.0是一个完整目录里面比较关键的有三块。第一块是PlatEMO这是MATLAB的包目录。号是包标识文件夹名不能随意改改了类就找不到了。所有算法类、问题类、工具函数都按子包组织比如PlatEMO/Algorithm里放各种算法PlatEMO/Problem里放测试问题。第二块是platemo.m这是整个平台的入口GUI从这里启动批处理也从这里走。第三块是Data目录里面存着测试问题的真实PFPareto前沿文件IGD指标的计算依赖这些文件。这里有个新手常踩的岔路有人说把所有文件夹都addpath就完事结果一运行就报错“Undefined class PlatEMO.Algorithm”。原因是你把某个子目录单独拷出来用了。v2.0的包结构必须保持完整我一般直接把整个PlatEMO根目录用genpath加进去addpath(genpath(D:/tools/PlatEMO)); % 换成你的实际路径 savepath;genpath会递归地把所有子目录加入MATLAB搜索路径savepath把路径保存下来下次打开MATLAB不用重复添加。需要注意如果你的目录名带有中文或特殊符号addpath通常会失败尽量把平台放在纯英文路径下。这一步做完在命令行敲which platemo能返回完整路径说明环境已经就绪。2.2 GUI 首跑选问题、选算法、设种群的 30 秒流程环境就绪后在命令行输入platemo界面启动后是左右两栏布局。左侧是问题面板右侧是算法面板中间是运行控制和结果展示区。第一组实验我建议按最保守的组合来问题选 ZDT1算法选 NSGA-II种群大小设100最大评估次数设10000。这么配置是因为ZDT1是连续无约束两目标问题NSGA-II在v2.0里默认参数足够在它上面稳定收敛适合拿来验证平台本身没问题。GUI里几个设置的对应关系可以记成一张表面板区域作用第一组实验推荐值Problem选择测试问题及其维度ZDT1目标数M2Algorithm选择算法并显示参数NSGA-II默认参数N种群大小100maxFE最大函数评估次数10000指标勾选决定记录哪些指标IGD、HV都勾上点运行后中间会实时显示种群在目标空间的分布右下方是IGD和HV随评估次数变化的曲线。ZDT1的真实前沿是凹曲线NSGA-II跑完后种群会贴在这条曲线附近IGD曲线应该在一万次评估内明显下降并趋于平缓。如果这一步能出这个结果你的环境就是健康的可以开始做自定义实验了。这里提前说一个理解终止条件的角度maxFE是函数评估总次数不是种群代数。种群100、maxFE10000意味着大约100代。你在GUI里看到的所有曲线横轴都是评估次数不是代数后面自己写循环判断时一定要用平台提供的终止判断而不是自己数代。2.3 读回结果.mat 文件里存了什么运行结束后v2.0会把这次实验完整存成一个.mat文件。文件默认放在平台目录下的Results文件夹里命名规则通常是算法名_问题名_日期_序号.mat例如NSGA-II_ZDT1_20190303_1.mat。不要直接双击打开建议用命令行加载res load(Results/NSGA-II_ZDT1_20190303_1.mat); whos(-file, Results/NSGA-II_ZDT1_20190303_1.mat)whos -file会列出.mat文件里所有变量名和维度这是最稳妥的查看方式因为不同小版本里变量名可能会微调。常见会存下来的有算法名、问题名、最终种群、每一代的IGD和HV序列、以及这次运行的基本参数。我习惯把原始.mat当作原始数据存档绘图和分析都用单独的脚本读取不直接在GUI里反复操作这样多组实验之间更好对齐。验证第一组结果是否可信一个很硬的标准是拿IGD数值和平台上内置算法跑出来的参考值对比。v2.0的Data目录里存了各问题已知最优PF你可以加载ZDT1的PF文件把最终种群的obj画在同一个坐标里肉眼确认贴合。这个习惯能避免后面改算法时把问题方向搞反。3. 拆解 v2.0 的三角核心Algorithm、Problem 与 Population 怎么协同3.1 Algorithm 是容器不是流程v2.0里写一个算法不是写一串脚本而是写一个继承PlatEMO.Algorithm的类。这个类的职责不是“跑流程”而是“提供参数和入口”。你在GUI右侧看到的参数面板就是平台读取算法类的ParameterNames和ParameterValues两个属性渲染出来的。一旦在GUI上改了参数平台会把新的值写回ParameterValues然后调用你的算法入口。这个设计的好处是问题怎么评估、种群怎么存储、指标怎么记录算法类都不用操心。算法只需要关心每一步产生什么解、保留哪些解。比如你写一个新算法继承Algorithm类后只需要实现一个solve方法平台会在满足终止条件之前反复调用它内部的控制逻辑。回头看v1.x时代的脚本式写法v2.0等于把“怎么组织实验”和“怎么写算法”彻底分开了。之所以强调“容器”这个说法是因为很多人第一次打开内置算法时会被里面的结构绕晕又是构造函数、又是受保护方法。其实你只需要抓住两个锚点构造函数决定了GUI上展示什么solve方法决定了算法怎么做决策。其他地方都是围绕这两个锚点的辅助代码。理解了这一点你改任何内置算法都不会心虚。3.2 Problem 对象问题参数与评估入口每个测试问题在v2.0里也是一个类统一放在PlatEMO/Problem下而且它们都继承同一个问题基类。基类里约定了算法一定会用到的信息目标数M、决策变量数D、决策变量下界lower和上界upper。算法初始化种群时需要根据Problem.D和边界生成随机决策变量评估个体时通过问题对象提供的评估接口计算目标值。这样就出现了一个很关键的分工算法代码里不应该出现任何“ZDT1的目标函数是 f1 x1, f2 g*(1-sqrt(x1/f1))”这类硬编码。想换问题只要在GUI里换下拉框算法类和问题类通过统一接口完成对接。也是因为这个约定v2.0才能用同一套算法代码在ZDT、DTLZ、WFG、MaF这些风格完全不同的问题系列之间自由切换。当你想往平台里加一个新测试问题时也走同一个套路写一个继承问题基类的子类把M、D、lower、upper填对再实现目标函数评估方法。平台只认接口不认问题名字。换句话说你在v2.0里每加一个算法或一个问题都是在往这套统一接口上挂新节点而不是在改平台本体。3.3 Population 数据交换格式dec、obj、cons 的约定种群在v2.0里是一组Solution对象的数组。每个Solution对象身上有三个主要字段dec是决策变量行向量obj是目标值行向量cons是约束违反量。如果你研究过早期版本会觉得这个结构很眼熟因为它就是从结构体数组进化来的对象化版本。这里有个最容易被新手踩的约定obj不是由算法自己算完塞进去的而是调用问题对象的评估方法后返回的。你如果直接改Population(i).obj平台记录的指标曲线不会更新因为评估计数器没有增加。正确做法是拿到新的dec后调用Problem.CalObj(dec)这类评估接口把返回的目标值写回obj。约束问题同理cons表示约束被违反的程度0是可行大于0就是不可行算法做环境选择时要用这个值来区分可行域内外的个体。我用一段很短的代码说明这个约定的常见写法你后面看内置算法也会反复看到这个模式newDec Problem.lower rand(1, Problem.D) .* (Problem.upper - Problem.lower); newObj Problem.CalObj(newDec); % 评估接口返回目标值 Population(i).dec newDec; Population(i).obj newObj;注意Problem.CalObj的返回维度多数情况下是行向量。如果换成某些旧版工具箱养成的习惯传了列向量后面的非支配排序会报维度不一致这个坑在5.5节还会再提一次。3.4 指标计算模块IGD 与 HV 在 v2.0 内部怎么处理跑完一次实验GUI里会显示IGD和HV曲线。IGD计算需要两个输入算法得到的最终种群以及这个问题已知的最优前沿。v2.0把最优前沿预处理成了.mat文件放在Data目录里指标模块会从中读取并计算所以IGD这一步基本是零成本你不需要自己去采真实前沿。我在做问题对比时经常直接用脚本把真实前沿加载出来和算法种群叠在一张图上检查判断算法是否真的收敛到了正确位置上pf load(Data/PF/ZDT1.mat); % 加载真实前沿 plot(pf.obj(:, 1), pf.obj(:, 2), k-, LineWidth, 1.5); hold on; plot(Population.objs(:, 1), Population.objs(:, 2), ro);pf.obj的字段名在不同小版本里可能有差异先用whos -file看一眼再画。如果算法种群的散点大面积偏离真实前沿IGD数值再好看也是假的这种情况多半是目标数M设置和PF文件不匹配回问题设置里检查。HV的计算形态和IGD不同它需要一个参考点。参考点选取不同HV的绝对值也会不同论文里直接比HV数值时必须保证双方参考点一致。v2.0默认会按各问题的边界和种群目标值动态生成参考点同一问题同一配置下是可比的但跨问题比较HV没有意义。在自定义问题的时候这个参考点的选取要自己格外小心不然HV曲线会给出一个“自欺欺人”的漂亮上升。4. 写一个自己的算法接进 v2.0NSGA-II 加一个局部搜索的完整改法4.1 先读内置算法再动手v2.0 自定义算法的最小框架网上搜PlatEMO自定义算法帖子不少但很多人第一句话就是“继承Algorithm类然后写solve方法”听起来挺简单真动手的时候连文件放哪都不知道。我建议你第一步不是新建文件而是先打开一个内置算法当模板edit(PlatEMO/Algorithm/NSGAII.m)打开后你会看到这个类的基本结构开头是构造函数里面给obj.Name、obj.ParameterNames、obj.ParameterValues赋值接着是一个受保护的solve方法里面是完整的进化循环。你把这个文件另存为MyNSGA2LS.m放在同一个目录里然后全局替换类名这就得到了一个和内置NSGA-II行为完全一致的新算法。这一步的意义在于避开接口细节因为不同小版本里工具函数的名字可能略有出入照抄模板永远比记忆接口可靠。注意文件名必须是MyNSGA2LS.m不能是my_nsga2ls.m或者测试1.m。MATLAB的类加载规则是文件名找类定义文件名和类名不一致时平台在运行阶段才会报类未定义那时候排查起来比现在多花半小时。4.2 solve 方法实现初始化、主循环、终止条件一次说清在模板基础上我们做第一个改动把构造函数里的参数重新声明一遍。模板里默认是100个个体、交叉概率1、交叉分布指数20、变异概率1、变异分布指数20。这些参数会被GUI的参数面板读取改成什么样面板就显示什么样classdef MyNSGA2LS PlatEMO.Algorithm methods function obj MyNSGA2LS() obj.Name MyNSGA2LS; % GUI算法列表里显示的名字 obj.ParameterNames {N, proC, disC, proM, disM}; obj.ParameterValues {100, 1, 20, 1, 20}; end end methods (Access protected) function solve(Algorithm, Problem) % 初始种群直接交给问题对象生成边界和维度它负责 Population Problem.Initialization(Algorithm.ParameterValues{1}); gen 0; while Algorithm.NotTerminated(Population) gen gen 1; % ---- 下面两段原本是内置NSGA-II的选择、交叉、变异 ---- % MatingPool TournamentSelection(2, N, ...); % Offspring OperatorGA(MatingPool, ...); % ---- 在环境选择前插入局部搜索见4.3节 ---- if mod(gen, 10) 0 Population Algorithm.LocalSearch(Problem, Population); end % ---- 下面是环境选择合并父子代、非支配排序、保留N个 ---- end end end end这个结构里有两个点要特别说明。第一ParameterValues{1}对应N也就是种群大小所有参数在ParameterNames里的顺序必须和ParameterValues一一对应平台不做任何自动对齐顺序错一个你调的就是另一个参数。第二NotTerminated(Population)是平台提供的终止判断内部会看当前评估次数是否达到maxFE也会在每一代结束时把当前种群和指标数据记录到结果文件里。你的循环不能自己用for写死代数否则结果文件里不会有完整的收敛过程。我在模板里用了一个本地变量gen来记录迭代次数纯粹是给局部搜索的触发条件用的。不要用平台之外自己维护的计数器来替代终止判断平台的评估计数器和结果记录是绑在一起的绕开它结果文件就不完整。4.3 加一个局部搜索高斯扰动与边界拉回现在给这个类增加一个局部搜索方法。它做的事很朴素每10代对当前种群里的可行个体做一次小步高斯扰动如果扰动后的解目标值更小就替换掉原个体。methods (Access protected) function Population LocalSearch(Algorithm, Problem, Population) % 步长用问题边界跨度的10%不同尺度的问题都能适应 sigma 0.1 * (Problem.upper - Problem.lower); for i 1 : length(Population) % cons 0 表示约束被违反跳过不可行个体 if any(Population(i).cons 0) continue; end newDec Population(i).dec sigma .* randn(1, Problem.D); % 拉回边界越界的坐标直接夹到边界值 newDec min(max(newDec, Problem.lower), Problem.upper); newObj Problem.CalObj(newDec); % 目标值比较多目标严格做法是支配判断这里用求和近似 if sum(newObj) sum(Population(i).obj) Population(i).dec newDec; Population(i).obj newObj; end end end end这个方法的逻辑分四步。第一步sigma按问题边界动态计算决策变量尺度是0.01还是10000扰动幅度都相对合理不用手工调步长。第二步跳过cons 0的不可行个体防止局部搜索把算法带进不可行区域。第三步min(max(newDec, ...))是边界拉回把超出上下界的坐标直接夹回边界这也是进化算法里处理边界约束最常见的方式。第四步比较新旧解时用求和是为了这段代码在单目标和多目标问题里都能跑通严格场景下应该换成“新解支配旧解才接受”多目标问题里求和比较可能会丢掉Pareto前沿上的一些好解。关于步长参数我把0.1写死在代码里了更精细的做法是把它也加进ParameterNames让GUI面板能暴露出来调。这类“算法自身的超参”是v2.0留给使用者最大的自由空间也是新算法能不能超过基线的关键。局部搜索的触发间隔也一样10代只是起始值你完全可以放面板里做成可调参数。4.4 在 GUI 中注册与验证三种该看到的信号文件保存好后回到命令行重新启动GUIplatemo新算法会出现在右侧算法列表里名字就是你在obj.Name里写的MyNSGA2LS。如果列表里看不到先检查文件名是不是MyNSGA2LS.m文件名和类名不一致MATLAB会拒绝加载。这是新手最容易卡住的地方。验证时做三件事。第一跑ZDT1同样100个个体、10000次评估IGD曲线应该比内置NSGA-II略好或者持平——如果差很多问题通常出在你的改动破坏了原模板的某种机制回滚对比一下。第二看最终种群图散点应该贴合真实前沿如果出现一大批点堆在角落或者偏离前沿说明局部搜索的接受准则太宽松了把求和比较换成支配判断。第三把局部搜索间隔从10改成1、5、30各跑一遍观察性能变化确认这个机制在你的问题设置里是正增益。这一步做完你的自定义算法就算正式接入平台了。5. PlatEMO v2.0 避坑指南五个让人想砸电脑的场景与解法5.1 类未定义文件在路径里MATLAB 却说找不到现象自己写的算法文件明明放在算法目录下一运行就报Undefined variable MyNSGA2LS or class MyNSGA2LS.原因绝大多数情况是文件名和类名不一致。MATLAB对类的加载是按文件名找的你把类定义成MyNSGA2LS文件名却是test1.m平台自然找不到。另一个常见原因是文件放进了某个子目录而这个子目录不在MATLAB搜索路径里。package目录的规则比较严格放进PlatEMO/Algorithm内层再套自己的子目录有时也会导致类加载失败。解决类名、文件名、文件所在目录三者对齐。最简单的是把新算法文件放在PlatEMO/Algorithm目录下和内置算法平级。文件保存后重新执行clear classes再重启GUI90%的“找不到类”都能解决。5.2 IGD 曲线横着不走目标值没进 Population现象算法能跑界面上的种群点也在动但IGD曲线几乎是一条水平线数值还特别大。原因你的代码里新个体的目标值没有通过问题对象的评估接口计算或者说算完没写回Population。有的写法是newDec ...之后直接Population(i).obj newObj但newObj是从某个黑匣子函数里拿来的不是Problem.CalObj的返回值评估计数器和结果记录完全没感知到这些个体。平台按自己的计数器记录指标你的实际迭代和它的记录脱节了。解决检查所有新解生成路径确认每一条都经过Problem.CalObj之类的评估接口。还有一个排查技巧在GUI上肉眼数一数如果界面左下角的评估次数增长异常慢说明有部分个体被跳过了评估顺着代码里所有赋值obj的地方排查。5.3 并行开启后结果和单核不一致随机数流的锅现象同一配置单核跑出来的IGD是0.08开8核并行后变成0.15换了台机器结果又不一致。原因并行计算时每个worker有自己的随机数流如果代码里直接用全局rand/randn而没有做随机数流控制不同worker会生成重复或者高度相关的随机序列。这会让种群多样性在某几代里突然消失进化停滞。这是个玄学问题不跑固定种子几乎抓不到规律。解决v2.0的并行内部管理了部分随机逻辑但自定义算法里新增的随机操作要自己做处理。最稳的方法是先在单核下完成算法调试确信逻辑没问题后再开并行并行实验务必固定种子让每个worker的随机数流可复现。批量对比的时候也尽量让所有算法吃同一套随机种子配置不然指标差异里混进了随机性。5.4 参数面板改了没反应ParameterValues 的坑现象GUI里把proC从1改成0.8跑出来的结果和默认值一模一样自己代码里打印ParameterValues发现还是老值。原因ParameterNames和ParameterValues的顺序没对齐或者类型不对。GUI面板按ParameterNames渲染输入框提交后按顺序写回ParameterValues如果你在solve里写死了参数比如proC 1那GUI改什么都没用。另一种情况是面板返回的是字符串你取值后没做类型转换就参与数值计算。解决在solve开头加一行打印实际值来确认传递链路fprintf(N%s proC%s disC%s\n, Algorithm.ParameterValues{1}, ... Algorithm.ParameterValues{2}, Algorithm.ParameterValues{3});凡是留在ParameterValues里的参数代码里一律通过Algorithm.ParameterValues{index}引用不要出现字面量常量。5.5 自定义问题报错边界维度与约束符号各错一遍现象跑内置问题没事换成自己写的问题就报维度错误或者约束问题跑完所有个体都不可行。原因自定义问题类里upper和lower的行向量维度要和D一致很多人定义了D 30边界却写成1×10初始化阶段就会崩。约束方面v2.0的cons约定通常是0表示可行、大于0表示违反如果你在问题里把违反量写成了负数平台里所有默认的约束处理都会方向性失效。解决写自定义问题先在别的算法下跑一遍ZDT1验证环境再逐步换成自己的问题。边界维度用size(Problem.upper)打印确认。约束值统一用“违反量越大数值越大、0为可行”的约定输出并且评估函数里不存在约束时返回全0行向量不要返回空数组。6. 批量实验与后处理用一个小脚本把多组对比跑出可发表的数据单次跑GUI当然方便但论文级对比通常需要“同一批问题 × 同一批算法 × 多次独立运行”的矩阵。v2.0的platemo函数可以直接用键值对参数调用不弹GUI适合写进脚本批量执行% 批量实验MyNSGA2LS 和内置 NSGA-II 对比各跑 5 次独立重复 algorithms {NSGAII, MyNSGA2LS}; problems {ZDT1, DTLZ2, MaF1}; for a 1 : length(algorithms) for p 1 : length(problems) for r 1 : 5 platemo(algorithm, algorithms{a}, ... problem, problems{p}, ... N, 100, maxFE, 10000, ... folder, [Results/Batch_a num2str(a) _p num2str(p)]); end end end这里需要注意platemo的参数键名在不同小版本里略有差异第一次跑之前先help platemo看当前版本的帮助以实际键名为准。folder参数把不同子实验的结果分目录存放比全都堆在默认Results里好整理得多。批量跑的时候建议先把总运行次数控制在半小时内确认脚本没问题再放开规模。结果文件都是.mat后处理时用whos -file看变量名然后把每一代的指标列提出来画平均收敛曲线files dir(Results/Batch_a*_p*/*.mat); allIGD []; for i 1 : length(files) data load(fullfile(files(i).folder, files(i).name)); % 变量名以 whos -file 输出为准这里用最常见的名字 if isfield(data, IGD) allIGD(:, end1) data.IGD; %#okSAGROW end end figure; plot(mean(allIGD, 2), LineWidth, 1.5);跑批量时我有个血泪教训第一版脚本把folder键名写错了结果所有结果都堆在默认目录里小版本命名还带了时间戳最后整理数据时花了两个小时手动分文件。后来形成的习惯是每条批量实验固定一个母目录实验结束当晚就把均值、方差和最优值三个表生成出来数据新鲜的时候最容易发现异常值。v2.0的批量能力不算快但对标准对比实验它已经比手工保存每张图、手工抄IGD数值的流程高效太多了。希望这套流程能帮你在做算法对比时少走一段弯路。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →