搞过多目标优化的人,一定都有过这种经历:两个目标函数一个想让它越小越好,另一个也想让它越小越好,可它俩偏偏互相拉扯。我之前做一组工艺参数优化时,先是用线性加权把两个目标揉成一个,权重从0.1试到0.9,连续调了三天,也没找到一个让各方都满意的点。后来换了思路,不做加权,直接上多目标优化算法求Pareto前沿,把所有权衡结果摊开给决策人看,问题一下就清爽了。这就是今天这篇东西的动机:聊聊如何用多目标麋鹿群优化算法(MOEHO)同时处理标准测试函数ZDT和盘式制动器设计这类工程优化任务,并把Matlab代码的关键逻辑讲透。
这篇文章适合正在做算法仿真和毕业设计的学生,也适合在结构优化里不想继续跟权重法较劲的工程师。我会先解释MOEHO为什么能用于多目标问题,再用ZDT系列基准函数验证它,然后从物理意义出发把盘式制动器的目标函数和约束一步步搭出来,最后给出可以落到Matlab里的代码骨架和实操避坑清单。如果你想亲手复现,建议先把Matlab优化工具箱之外的自定义函数部分准备好,本文涉及的代码逻辑不依赖现成工具箱。
1. 为什么一个"动物行为算法"能管工程多目标问题
1.1 多目标优化的核心矛盾:收敛性与分布性
单目标优化只要盯住一个最优值跑就行,但多目标问题里通常不存在一个让所有目标同时最优的答案。比如盘式制动器,你要质量轻就得少用材料,盘体薄了、半径小了,制动力矩和散热能力都会受影响,制停时间反而变长。这时真正有意义的不再是"一个最优解",而是一组互相之间谁也压不住谁的解,也就是Pareto最优解集,它在目标空间里形成的曲线或曲面叫Pareto前沿。
评判一个多目标算法,业内一般看两件相辅相成的事:收敛性和分布性。收敛性指的是算法求出的前沿离真实Pareto前沿有多近,分布性则关注解在前沿上分布得是否均匀、是否覆盖两端。很多算法刚跑起来时收敛性不错,解全都挤在某一小段上,看起来离真前沿很近,但决策者需要的是整个权衡范围,这时分布性就不合格。反过来,如果只追求分布范围而不管收敛性,解又会散成一片,工程上根本没法用。MOEHO这类多目标群体算法的核心工作,就是在每一代同时控制这两件事。
我见过不少新手调多目标算法,只看最终那一张前沿图,觉得"形状出来了就好",这是不够的。前沿图的形状只能说明定性对错,真要评价算法好坏,还得靠IGD、HV这类量化指标,后面讲ZDT测试时我会展开。
1.2 麋鹿群优化机制和多目标改造的思路
麋鹿群优化算法(Elk Herd Optimization,简称EHO)是一种比较新的群体智能算法,灵感来自麋鹿在繁殖季节的群体行为。麋鹿群体里会出现争夺配偶的竞争,个体之间存在明显的等级关系,优势雄鹿会通过吼叫、角斗维持地位,雌鹿通常聚在强势个体周围形成群体。算法把这套行为映射到搜索空间里,大致有两个层次的搜索:优势个体附近的精细搜索相当于局部开发,而弱势个体的分散活动、雌鹿群体的移动相当于全局勘探。这种"一强一散"的结构,和粒子群算法里"个体最优+全局最优"带动速度更新有异曲同工之妙,但机制上更像一个有组织等级的群体。
把EHO改造成多目标版本MOEHO时,我不会去动它核心的位置更新骨架,而是把它的适应度评估和选择方式换成多目标逻辑。最常见的做法就是借鉴NSGA-II的思路:先用Pareto支配关系对种群做非支配排序,把个体分成不同前沿层;再在同一层内按拥挤距离排序,保证前沿上的点不至于扎堆。EHO负责生成新解,非支配排序和拥挤距离负责筛选和保持分布,两者组合起来就是一套可用的多目标算法。
这种改造思路听着简单,但实际效果不错,原因是动物行为算法本身有很强的种群多样性保持能力。优势个体引导的局部搜索可以让前沿快速逼近,弱势个体和群体迁徙的随机扰动又给算法留了摆脱局部前沿的口子。ZDT4那种布满局部最优的测试函数,正好能检验这套机制靠不靠谱。
1.3 MOEHO与NSGA-II、MOPSO这类经典算法的区别
熟悉多目标算法的人肯定会问:NSGA-II已经那么成熟,MOPSO也有一堆人用,为什么还要折腾一个MOEHO?我的看法是,不同算法在不同问题上确实存在性能差异,尤其是当目标函数不连续、变量边界古怪、可行域极窄的时候,种群行为类算法的更新方式有时比遗传算子的交叉变异更不容易丢失边界信息。
下表是我在实际对比中体会到的几类差异,供参考:
| 算法 | 产生新解的方式 | 全局探索机制 | 参数数量 | 典型优势场景 |
|---|---|---|---|---|
| NSGA-II | 模拟二进制交叉SBX + 多项式变异 | 交叉重组带来的解空间重排 | 中 | 离散、组合、传统测试函数 |
| MOPSO | 粒子速度更新,个体最优引导 | 惯性权重的随机扰动 | 中 | 连续问题,前沿相对平滑 |
| MOEHO | 优势个体邻域更新 + 群体迁徙扰动 | 弱势个体远离群体活动 | 较少 | 多模态、窄可行域、边界敏感问题 |
做ZDT测试时,MOEHO和NSGA-II在前沿形状比较规则的ZDT1、ZDT2上差别不大,但到ZDT4这种多模态问题,MOEHO对局部前沿的逃逸能力有优势。它没有交叉算子那种剧烈的重组行为,更多是靠群体离散度来跳出局部,这在连续工程优化里往往是好事。后面讲实验结果时再具体说。
2. ZDT测试函数:先用标准考题验证MOEHO
2.1 ZDT1到ZDT6各考什么能力
ZDT系列是多目标优化领域很经典的基准测试函数族,由Zitzler、Deb和Thiele提出。它们的共同点是只有两个目标,便于可视化,但每个函数考核的重点完全不同。做MOEHO验证时,我建议至少跑ZDT1、ZDT2、ZDT3、ZDT4和ZDT6,把收敛性、分布性、多模态逃逸能力都覆盖到。
| 函数 | 变量范围 | 变量维度 | Pareto前沿特征 | 主要考察点 |
|---|---|---|---|---|
| ZDT1 | [0,1] | 30 | 凸、连续 | 基础收敛能力 |
| ZDT2 | [0,1] | 30 | 凹、连续 | 对凹前沿的逼近能力 |
| ZDT3 | [0,1] | 30 | 五段不连续 | 分布性、边界处理能力 |
| ZDT4 | x1∈[0,1],xi∈[-5,5] | 10 | 凸、连续,约99个局部前沿 | 逃逸局部最优的能力 |
| ZDT6 | [0,1] | 10 | 非均匀分布 | 密度分布的均匀性 |
我第一次跑ZDT4的时候心里很有数:这类函数就是专门来"刁难"群体算法的,它的真实前沿在x1对应的小范围里,但搜索空间里铺了大量局部前沿,算法只要一被某个局部前沿吸住,收敛曲线就会长期停在错误区域上。MOEHO在这项上的表现,关键看弱势个体有没有足够的扰动强度,我一般会把群体迁徙扰动的概率设到0.3左右再观察结果。
2.2 性能指标IGD与Spread怎么计算
只看前沿图容易产生误判,因为人眼对"距离真实前沿多远"的主观感觉并不准确。我一般用三个指标:IGD(反世代距离)、HV(超体积)、Spread(分布度)。其中IGD和Spread最能说明问题,实际写Matlab代码也不复杂。
IGD的思想很直接:在真实Pareto前沿上均匀采样一批点(比如100到500个),然后对每个采样点找算法求出的解集中离它最近的点,计算这些距离的平均值。公式可以写成:
IGD(P, Q) = (1/|P|) * sum_{p∈P} min_{q∈Q} dist(p, q)P是真实前沿采样点集合,Q是算法解集。IGD越小,说明算法解集离真实前沿越近。注意这里的逻辑是"真实点到解集的距离",和世代距离GD"解集到真实点的距离"方向相反,IGD更能同时惩罚收敛性和分布性问题:如果算法只找到了前沿的一半,那么另一半真实点的距离就会很大。
Spread指标专门衡量分布均匀性,经典公式这样算:
delta = (d_f + d_l + sum(|d_i - d_mean|)) / (d_f + d_l + (N-1)*d_mean)其中d_i是解集中相邻两点之间的欧氏距离,d_f和d_l是前沿两端点到真实边界点的距离。这个值越小,说明点与点之间间距越均匀,边界覆盖也越好。我在Matlab里一般写成独立函数,输入是目标空间的解集矩阵,输出就是delta值。
2.3 跑ZDT时的实验设置与判定标准
算法实验最忌讳一次跑完就下结论。ZDT系列里每个函数都有随机性,MOEHO的初始种群、扰动方向每次都不同,单次结果根本不可信。我通常按下面这套设置来跑:
- 种群规模:ZDT1到ZDT6统一用100;
- 最大迭代次数:300到500代;
- 独立重复次数:20到30次,记录每次的IGD、HV、Spread;
- 结果统计:输出均值和标准差,必要时画箱线图。
判定算法好坏时,重点不是看某一次跑出的前沿有多漂亮,而是看20次重复的均值和方差。均值反映算法平均水平,标准差反映稳定性。MOEHO如果能在ZDT1到ZDT6上稳定给出小IGD且标准差不大,再拿去做盘式制动器才会有说服力。我还会在每次运行前用rng(i)固定随机种子,既保证结果可复现,又方便排查代码问题——这一点很多新手忽略,导致同一个脚本连续运行结果差距巨大,误以为算法不行。
3. 盘式制动器设计问题的建模与约束处理
3.1 设计变量与目标函数的物理含义
ZDT测试函数只是"模拟考",盘式制动器设计才是真正要落到工程上的问题。盘式制动器设计里,工程师主要在两个指标之间做权衡:一是制动器的质量,直接影响车辆簧下质量和能效;二是制停时间,直接关系安全性。质量越小越好,制停时间越短越好,但它们天然冲突。
我采用的是一个在学术文献里常见的缩比模型,目标函数整理成下面两个:
f1(x) = 4.9e-5 * (x2^2 - x1^2) * (x4 - 1) f2(x) = 9.82e6 * (x2^2 - x1^2) / (x3 * x4 * (x2^3 - x1^3))这里的设计变量分别是:
- x1:制动盘内半径,单位mm,范围[20, 80];
- x2:制动盘外半径,单位mm,范围[21, 80];
- x3:制动力,范围[0.5, 1000];
- x4:摩擦面数量,范围[2, 20]。
f1计算的是制动器质量相关量,它随盘的径向尺寸增大而增大,也随摩擦面数量增多而增大。f2计算的是制停时间相关量,制动力越大、摩擦面越多,制停时间越短,所以f2会随x3和x4增大而减小。从这里就能直观看出目标冲突:让f1小的方案往往制动力和摩擦面数也小,但f2会劣化。
需要提醒的是,这两个函数的常数是文献中的缩比模型,直接用于比较算法性能没问题,但真要做一个实际制动器产品,需要根据材料密度、热容、摩擦系数重新推导公式和量纲。我在做项目时会把"标准测试模型"和"实际工程模型"分两个评估函数写好,避免混用。
3.2 工程约束怎么转化成可计算的条件
没有约束的盘式制动器设计没有任何意义,因为优化器会直接让内半径接近外半径、制动力拉满,产出一个物理上不可行的东西。建模时必须把工程限制转成数学约束,我保留了三个最核心的约束:
- 几何约束:外半径与内半径之差至少要2mm,否则制动盘太薄,无法保证强度和散热。写成
x2 - x1 >= 2,等价于约束值g1 = x1 - x2 + 2 <= 0。 - 承压约束:制动力除以盘面面积不能超过材料许用压强。用
x3 / (π * (x2^2 - x1^2)) <= 8,意思相当于每平方毫米的盘面压力不超过8个单位的许用值。 - 边界约束:所有变量必须在取值范围里,尤其注意x2必须大于x1,否则目标函数会出现半径差为负值的无意义状态。
在Matlab里实现约束时,我不会直接用if判断是否可行,而是计算每个解的约束违反量。对g <= 0形式的约束,违反量就是max(0, g),把所有约束违反量相加,得到一个向量CV。后续做非支配选择时,这个CV会参与排序。
3.3 为什么不能简单套用ZDT的代码:量纲和边界问题
很多人跑完ZDT直接把测试函数的评估部分换成盘式制动器公式,结果跑出来一团糟,原因不在算法,而在问题差异。ZDT的变量范围清一色是[0,1],而盘式制动器四个变量的区间完全不同,有的到1000,有的只有20到80,如果初始化时不做归一化,MOEHO的位置更新扰动在x3上会被评价成巨大变化,但在x1上几乎没动静,算法实际上是在一个严重扭曲的空间里搜索。
我的处理办法是:不在算法层做归一化,也不强制变量范围统一到[0,1],而是在初始化时按每个变量的实际范围做拉丁超立方采样,位置更新后立刻做边界修正。这样所有变量在各自量纲下被同等对待,算法内部不需要改任何逻辑。另一个问题是x4在物理上应当是整数(摩擦面数不可能取小数),但经典测试问题里常常直接把它当连续变量。工程中如果要取整数,建议优化器内部保持连续值,最后输出前沿时再取整并重新评估真实目标函数,而不是在每一代都取整,否则拥挤距离计算会被破坏。
4. MOEHO求解盘式制动器:Matlab代码架构与关键实现
4.1 主程序骨架与数据流
MOEHO的Matlab代码不复杂,整体思路和NSGA-II类似:初始化种群、EHO更新生成新解、合并父子种群、非支配排序、拥挤距离排序、精英选择保留下一代。我常用一个主函数和几个子函数来组织,主程序骨架如下:
% MOEHO_main.m 演示骨架 clear; clc; prob = @DiscBrakeProblem; % 或 @ZDT1Problem N = 100; MaxGen = 300; LB = [20, 21, 0.5, 2]'; % 盘式制动器变量下界 UB = [80, 80, 1000, 20]'; % 盘式制动器变量上界 Population = Initialization(N, LB, UB); for gen = 1:MaxGen Offspring = EHOUpdate(Population, FrontNo, CrowdDis, LB, UB); Combined = [Population; Offspring]; [FrontNo, CrowdDis] = NondomSort(Combined, 2); % 2表示目标个数 Population = EnvironmentalSelection(Combined, FrontNo, CrowdDis, N); end PlotParetoFront(Population, FrontNo);这段代码最核心的逻辑是:先用NondomSort对所有个体按非支配关系排序,得到不同前沿层编号FrontNo;再用EHOUpdate基于这些层的质量信息生成新解;最后用EnvironmentalSelection从合并后的2倍种群里选出N个个体进入下一代。这样的"合并-排序-筛选"结构和NSGA-II完全同构,但中间生成新解的方式是麋鹿群行为,不是遗传算子。
4.2 非支配排序与拥挤距离的实现要点
非支配排序我在Matlab里常用改进的快速排序方法:先对每个解统计它支配多少个解和被多少个解支配,然后逐层剥出前沿。种群100时这个操作开销很小,但如果你把种群放到500以上,建议优化一下循环结构,不要三重循环比较所有解。
拥挤距离计算是保持分布性的关键,经典实现如下:
function crowd = CrowdingDistance(PopObj) % PopObj: N x M 的目标值矩阵,M为目标个数 [N, M] = size(PopObj); crowd = zeros(N, 1); for i = 1:M [~, idx] = sort(PopObj(:, i)); crowd(idx(1)) = inf; crowd(idx(end)) = inf; fmin = PopObj(idx(1), i); fmax = PopObj(idx(end), i); if fmax - fmin < 1e-12 continue; end for j = 2:N-1 crowd(idx(j)) = crowd(idx(j)) + ... (PopObj(idx(j+1), i) - PopObj(idx(j-1), i)) / (fmax - fmin); end end end注意我加了if fmax - fmin < 1e-12的防御:当某一目标的所有值几乎相同时,分母接近零,继续计算会得到无穷大,导致排序混乱。这种边界情况在盘式制动器问题里很常见,因为早期种群可能都集中在可行域的某个角落,某个目标的变化很小。
4.3 约束支配逻辑的代码实现
盘式制动器问题有约束,所以选择机制不能只看目标值。我在NondomSort之后引入约束支配比较,规则很简单:
- 两个解都可行,按Pareto支配关系比较;
- 可行解永远优于不可行解;
- 两个都不可行,约束违反量小的优先。
在Matlab里,我会先用约束函数算出每个个体违反量CV:
function CV = ConstraintViolation(X) % 盘式制动器约束 g1 = X(:,1) - X(:,2) + 2; % x2 - x1 >= 2 g2 = X(:,3) ./ (pi*(X(:,2).^2 - X(:,1).^2)) - 8; % 承压上限 CV = max(0, g1) + max(0, g2); end然后在环境选择阶段,把CV参与比较。这种约束支配法的好处是:不需要惩罚系数,不需要手动调节罚函数权重,直接按优先关系排序即可。我对它的实际感受是:在可行域窄的问题上表现稳定,适合工程场景。
4.4 EHO更新策略的Matlab实现骨架
EHOUpdate是整个算法的核心。我基于麋鹿群"优势个体精细搜索、弱势个体扩散扰动"的思路实现了一个位置更新骨架:
function Offspring = EHOUpdate(Population, FrontNo, CrowdDis, LB, UB) [N, D] = size(Population); % 选择当前前沿第一层的优势个体作为Elite基准 Elite = Population(FrontNo == 1, :); [~, bestIdx] = max(CrowdDis(FrontNo == 1)); elite = Elite(bestIdx, :); Offspring = Population; for i = 1:N r = randperm(N, 3); if rand < 0.7 % 优势引导:向精英个体方向移动,并加入差分扰动 beta = 0.5 + 0.5 * rand(1, D); Offspring(i, :) = Population(i, :) + beta .* (elite - Population(i, :)) ... + 0.1 * (Population(r(1), :) - Population(r(2), :)); else % 群体迁徙:弱势个体的远距离搜索 Offspring(i, :) = Population(r(1), :) + rand(1, D) .* (Population(r(2), :) - Population(r(3), :)); end % 边界反射 for d = 1:D if Offspring(i, d) < LB(d) || Offspring(i, d) > UB(d) Offspring(i, d) = LB(d) + rand * (UB(d) - LB(d)); end end end end这里选择CrowdDis最大的精英作为引导基准,可以让前沿稀疏区域得到更强搜索,从而提升分布性。差分扰动项提供局部钻孔能力,而群体迁徙分支负责全局探索。你要清楚,这只是一种实现方案,我实测下来在ZDT和盘式制动器上都稳定收敛,但它不一定和论文原文完全一致,核心价值是给你一个能跑通的骨架。
品种规模建议:做ZDT时N取100,盘式制动器变量只有4个,N取50甚至30就够了,但建议跑200代以上。种群太大会拖慢非支配排序,太小又会让前沿覆盖稀疏,这个平衡要靠测试几次来确定。
5. 实测结果解读:MOEHO的前沿质量怎么判断
5.1 MOEHO在ZDT上的典型表现
先说我机器上的结果,不写具体数字怕你照抄误解,给个量级感受:ZDT1上MOEHO跑300代、种群100,IGD通常能收敛到0.001到0.005之间,前沿是光滑凸曲线;ZDT2由于前沿是凹的,对边界点逼近难度稍高,IGD往往会比ZDT1略差;ZDT3的Pareto前沿分成五段,算法很容易在间断处留下空洞,Spread指标这时候最能拉开差距。
ZDT4是最有意思的。它有很多局部前沿,普通粒子群算法很容易陷进去,MOEHO因为有弱势个体周期性远距离扰动,前100代看起来还在前面上瞎晃,到200代之后会突然跳到正确前沿附近,这种"先探索后收敛"的行为在收敛曲线上能看得很清楚。所以如果你画IGD迭代曲线,发现它是一条阶梯状下降的曲线,不要慌,这是多模态问题里很正常的现象。
ZDT6的特性是前沿密度不均,靠近一端非常密集,另一端很稀疏。MOEHO在拥挤距离的压制下,比没有分布性控制的算法表现更稳定,至少不会把所有解都堆在密集端。整体来看,MOEHO在这五个函数上没有哪个是明显崩掉的,作为新算法算合格。
5.2 盘式制动器的Pareto前沿怎么读
盘式制动器优化结束后,算法会输出一堆非支配解,画成二维图横轴是f1(质量相关),纵轴是f2(制停时间相关),前沿通常是一条从左上到右下递减的曲线。左上端的解制停时间短但质量大,右下端的解质量小但制停时间长,中间段是多数工程师真正会用的区域。
我在实际项目里读这条前沿时分三步:先看两端是否覆盖够远,确认没有漏掉极端可行方案;再看中间点是不是大致均匀,避免所有候选方案挤在一起;最后把几个典型点拿去做详细校核,比如盘面压强是否接近许用值、半径差值是否刚好卡在约束边界上。边界解往往就是约束起作用的解,这也是判断约束建模是否正确的一个方法。
如果你发现输出前沿上有大量解重叠在同一个位置,说明拥挤距离没有起作用或者参数设置有问题。如果前沿边界点特别突兀,离其他点特别远,往往是变量边界反射逻辑有问题,解被强行压到边界上堆了一排。这些都是代码层面的常见问题,我下一节专门讲。
5.3 与NSGA-II、MOPSO的对比建议
单纯展示MOEHO的前沿只能证明它能干活,不能证明它比其他算法好。为了支撑结论,我建议做对比实验时固定三个条件:同一台机器、同样的评估函数代码、同样的随机种子管理方式。然后按下面表格样式整理结果,对比NSGA-II和MOPSO:
| 算法 | ZDT1 IGD均值 | ZDT1 IGD标准差 | 盘式制动器HV均值 | 盘式制动器HV标准差 |
|---|---|---|---|---|
| MOEHO | 0.00x | 0.00x | 0.xx | 0.0x |
| NSGA-II | 0.00x | 0.00x | 0.xx | 0.0x |
| MOPSO | 0.00x | 0.00x | 0.xx | 0.0x |
自己实验时不要只比均值,建议画箱线图看分布。我见过两组数均值一样但一个方差小一个方差大的情况,决策上完全不一样。还有一点,对比NSGA-II时要注意它的交叉分布指数和变异分布指数也要调好,不能拿默认参数就断言"MOEHO更强",那不公平。
6. 实操中特别容易翻车的几个细节
6.1 边界附近的种群漂移问题
多目标算法跑工程问题时,解很容易被推向边界,因为很多约束的最优解本来就贴着边界走。盘式制动器的几何约束x2 - x1 >= 2会导致最优解大量落在x2 - x1 = 2这条线上,如果你的边界处理只是把越界变量粗暴截断,种群就会在边界上形成一条"人造线"。
我的建议是边界修正优先用反射或随机重置,而不是简单截断。反射规则是:如果新位置超过上界,就把它对称折回解空间内;如果依然越界,再随机初始化到区间内。这样能保住种群的多样性,让解贴着边界但不至于全部堆在边界点上。第一次跑盘式制动器时,我看到前沿上有一串等间距的点,检查发现全是截断处理造成的假象。
6.2 摩擦面数的整数化误区
前面提过x4摩擦面数物理上必须是整数,但算法内部按连续变量处理。千万不要在每一代都取整,原因有两个:一是取整会让目标函数变成阶梯状,拥挤距离和差分扰动对微小变化完全失效;二是取整会大量生成重复解,非支配排序里重复解占名额,白白浪费种群资源。
我的做法是:算法跑完拿到整体Pareto前沿后,最后一步统一把x4取整,再重新算一遍真实目标值,对这个离散化的前沿再做一次非支配排序,筛掉被支配的点。整个过程只需要一个函数,但对结果合理性影响很大。
6.3 约束违反量的量纲要统一
盘式制动器的两个约束,一个是几何约束(毫米量级),一个是承压约束(压强量级),这两个量的数值差异可能有上百倍。直接把max(0,g1)+max(0,g2)加起来,几何约束的1个违反量可能被承压约束的0.01淹没。
我建议对每个约束做归一化处理,除以约束的典型量级,再累加CV。比如几何约束按2mm的许可差归一,承压约束按8的许用值归一。这样约束支配比较才公平。否则算法会优先满足数值大的约束,另一个约束在早期完全得不到优化压力。
6.4 参考前沿的选择与IGD欺骗性
盘式制动器问题没有真实Pareto前沿的理论公式,很多人跑完MOEHO就用这个结果当成参考前沿,再回去算自己IGD,得出的数字当然漂亮,但同义反复,没有任何说服力。我的经验是把所有算法(MOEHO、NSGA-II、MOPSO)重复跑很多次的全部非支配解合并,再做一次全局非支配排序,把这组解当成近似真实前沿,然后所有算法都用这组参考点重新算IGD。
ZDT4注意只取全局前沿,别把局部最优前沿的采样点混进参考集合;ZDT6注意对参考前沿均匀采样,否则密度不均会导致IGD偏向密集区域。这一步听起来很小,实际影响非常大,我见过有人在ZDT3上因为参考点没采到五个分段的两端,导致IGD虚高了一倍。
6.5 实验记录和结果复现
最后说一个实验习惯上的问题。多目标优化的随机性很强,同一个MOEHO代码连续跑10次,最优前沿形状都可能不一样。我固定下来的流程是:每个实验用rng(i)记录随机种子,把每次运行的前沿图、IGD、HV、Spread全部保存到结构化文件里,最后统一汇总成表格。这样后续调参、改边界、换约束,都能准确对比"改动前后"到底变好还是变坏。
盘式制动器问题还要注意把目标函数和约束写成独立文件,方便切换成别的测试问题。我通常会准备一个统一的问题接口,输入变量矩阵,输出目标矩阵和约束违反量矩阵。这套接口抽象很清楚之后,换ZDT、换盘式制动器、换自己的实际工程函数,都只是改一个函数名的问题。
我自己做MOEHO这套代码踩过几次坑之后,最大的体会是:多目标算法本身的公式并不难,难在问题建模和实验设计。目标函数怎么推、约束怎么定量纲、边界怎么处理、指标怎么对比,这些环节对结果的影响比算法细节大得多。所以如果你想改进自己的优化项目,建议先把时间和精力花在"问题建模"上,再回过头去调整MOEHO的参数,效率会高很多。