做智能优化算法的人,一定绕不开PSO、GWO、WOA这几张老面孔。但如果只把粒子群跑通就算掌握了优化算法,那后面的路会很难走。今天分享的是我在Matlab里做的一次9种智能优化算法横向对比实验,包括CGO、SCA、GWO、CSA、SSA、HHO、WOA、PSO、TSO这9个算法的统一测试框架、完整代码逻辑和实测过程中踩过的坑。适合正在做算法选型、写论文对比实验,或者刚接触元启发式算法、想知道“到底该用哪个算法”的朋友。
这个对比实验的初衷很直接:很多论文里喜欢随便抓几个算法来比,功率不齐、评价次数不一样、边界处理方式不同,最后得出的结论自然站不住脚。所以这次我把9种算法放在同一个测试平台里跑,统一种群大小、统一最大函数评价次数、统一边界处理逻辑,30次独立运行取统计结果。只有这样的对比结果,拿去写报告或者做决策才有实际参考价值。
1. 9种算法横向对比的整体设计思路
1.1 为什么是这9种算法
我选的9种算法,不是随机凑数的,它们基本覆盖了元启发式算法里几种典型的机制设计思路,在公开发表的时间跨度上也拉得比较开。
先看这些算法的出生时间:
| 算法 | 全称 | 核心思想 | 提出年份 |
|---|---|---|---|
| PSO | Particle Swarm Optimization | 模拟鸟群觅食,速度-位置模型 | 1995 |
| GWO | Grey Wolf Optimizer | 模拟灰狼等级分工与包围狩猎 | 2014 |
| CSA | Crow Search Algorithm | 模拟乌鸦藏食、偷窃和反偷窃 | 2015 |
| SCA | Sine Cosine Algorithm | 利用正弦余弦震荡平衡探索与开发 | 2016 |
| WOA | Whale Optimization Algorithm | 模拟座头鲸气泡网螺旋捕食 | 2016 |
| HHO | Harris Hawks Optimization | 模拟哈里斯鹰包围-惊扰-俯冲捕猎 | 2019 |
| SSA | Sparrow Search Algorithm | 模拟麻雀觅食中的生产者、加入者、警戒者分工 | 2020 |
| CGO | Chaos Game Optimization | 基于混沌博弈理论的空间映射更新 | 2021 |
| TSO | Tuna Swarm Optimization | 模拟金枪鱼群螺旋觅食与合作围捕 | 2021 |
从1995年的PSO到2021年的CGO和TSO,这十几年间优化算法的设计语言发生了明显变化。早期的PSO走的是简单物理类比路线,靠速度惯性加个体/全局记忆来更新位置。中期的GWO、WOA开始引入“领导者-跟随者”的等级结构和更复杂的搜索路径。到了HHO和TSO这一代,算法普遍采用多策略并行、能量驱动的模式切换机制,不再依赖单一搜索方程。
把这样一批机制差异明显的算法放在同一个平台上比较,才能真正看出“哪种搜索策略在高维多峰问题上更可靠”,而不是在几个“近亲算法”之间比来比去,比半天全是噪声。
1.2 对比框架怎么搭:公平性是第一原则
做算法对比最容易翻车的地方就是“不公平”。我见过不少初学朋友拿A算法迭代500代,拿B算法迭代500代,然后直接比较收敛曲线,其实完全不合理。因为不同算法每代的函数评价次数不同,有的算法每个个体每代算一次目标函数,有的算法要算两次甚至三次,拿“迭代代数”当横轴,本质上是在拿不同算力水平下的结果做对比。
所以这次我统一用“最大函数评价次数”MaxFEs作为终止条件,所有算法吃同样多的目标函数预算。具体设置为:
- 种群大小:N = 30
- 问题维度:dim = 30
- 最大函数评价次数:MaxFEs = 30000
- 独立运行次数:30次
- 统计指标:最优值、平均值、标准差、收敛历史
所有算法共用同一套边界处理策略:越界后截断到边界。随机数种子不固定,通过30次独立重复来平均掉随机性带来的影响。
这里有一个容易被忽略的细节:测试函数的搜索范围各不相同。比如Sphere函数的经典范围是[-100, 100],Rastrigin是[-5.12, 5.12]。如果所有算法强行用同一个边界去跑所有函数,那评判的是边界处理的容错能力,而不是算法本身的搜索能力。正确的做法是每个测试函数用各自的推荐边界,初始化种群时在每个函数自己的范围内均匀随机生成。
只有这样统一之后,跑出来的数据才具备可解释性:算法A比算法B好,是A的搜索策略在这个函数地形上的确更有效,而不是A的初始化范围恰好更有利。
2. 核心算法原理拆解与对比维度
2.1 经典派三巨头:PSO、GWO、WOA
这三个算法我认为值得放在一起看,因为它们代表了三种完全不同的种群协作方式。
PSO的核心是“速度共享”。每个粒子记录自己历史上最好的位置pbest,整个种群共享一个全局最好位置gbest。更新时用两个随机权重组合pbest和gbest的信息,惯性权重w控制对旧速度的保持程度。这个机制的优点是实现极其简单,参数只有w、c1、c2三个,能快速上手。缺点是后期容易陷入局部极值,尤其是面对Rastrigin这类大量等距局部极值点的函数时,粒子群经常“抱团”在一个局部坑里出不来。
GWO模拟的灰狼社会有严格的等级:alpha狼带领,beta和delta辅助,其余omega狼跟随。位置更新不是直接向最优个体移动,而是取alpha、beta、delta三个“领导层”狼位置的平均方向。这种“多领导加权”的机制其实是一种隐式的多样性保护——哪怕alpha陷入了局部极值,beta和delta如果还在不同区域,平均值也能把个体拉出来一部分。但GWO在算法后期也有一个常见问题:随着a线性递减,A系数的震荡幅度越来越小,种群收敛速度过快,在多峰函数上容易早熟。
WOA是这三者里路径最独特的,它引入了螺旋气泡网更新:个体不是直线靠近最优位置,而是围绕最优位置画螺旋线。同时用随机概率切换包围和螺旋两种策略。这种非线性路径在一部分高维问题上效果很好,但WOA对初始种群的质量非常敏感,如果初始解离真实最优太远,螺旋搜索经常变成原地打转,收敛速度偏慢。
从我这轮实测来看,三者在这里选定的5个测试函数上各有胜负,没有哪个绝对全能。PSO在Sphere这种单峰函数上收敛最稳,GWO在Ackley这种多峰函数上有更好鲁棒性,WOA则在Rosenbrock这种狭长“香蕉形”谷地地形里表现相对稳定。
2.2 用数学函数驱动搜索的:SCA、CGO、TSO
SCA、CGO、TSO这三个算法的共同特征,是它们的搜索路径高度依赖于某种数学映射关系,而不是纯自然语义的模仿。
SCA的实现逻辑非常直白,它直接利用正弦和余弦函数的值域震荡特性:当r1的绝对值大于1时,个体向外探索;当r1小于1时,个体向内收敛。整个搜索过程就像让每个个体围绕当前最优解做摆锤运动。SCA的优势是代码极短、参数极少,几分钟就能写完,特别适合作为多个算法对比时的基线方法。但它的劣势也很明显,由于搜索路径的结构相对简单,面对山区地形复杂的高维多峰函数时,易陷入局部极值。
CGO是我这次比较关注的一个新算法,它的数学基础是混沌博弈理论,也就是分形几何里面的迭代函数系统思想。在实现上,CGO会把种群里的个体两两或多个组合构造成临时“三角形顶点”,然后让当前个体以混沌博弈的方式向某个顶点映射更新。这种基于“位置组合池”的生成策略和传统的“向最优解移动”完全不同,它的新个体不是直接来自最优方向,而是来自个体之间的几何关系重组。效果上,CGO多峰函数的探索能力比较强,但实现逻辑相对绕,参数和分支组合多,写的时候要特别小心维度的对应关系。
TSO模拟金枪鱼群的合作觅食,主要包含两种捕食策略:螺旋式觅食和抛物线式合作觅食。螺旋模式里个体沿螺旋轨迹向群体最优或随机个体靠近;抛物线模式则通过一个抛物线方程控制个体与最优解的相对位置关系。TSO还有一个特点,当部分个体追不上最优解时会启动“重新初始化”,这种随机重置策略对跳出局部极值的帮助,实际测下来确实有效果,但也带来一个副作用:迭代后期重置过多会拖慢精细搜索阶段的收敛速度。
2.3 靠分工与博弈策略的:SSA、HHO、CSA
这三类算法更强调种群内部的“角色分化和信息博弈”,和前面几类有本质上的差异。
SSA把麻雀分成三类角色:发现者负责在较大范围内寻找食物,加入者跟随发现者获取食物,警戒者负责监测危险,一旦发现危险就会向安全区域跳跃。这个机制的巧妙之处在于它通过角色比例来实现全局和局部搜索的平衡。但这里有个隐蔽坑:警戒者的比例不能设置太高,否则整个种群会被频繁随机跳跃打乱,收敛曲线后期会明显抖动,我在测试里试过把警戒者比例提高到0.4,结果30次运行里有相当一部分提前停滞了。
HHO是这里面结构最复杂的一个,它的核心是逃逸能量E的演化:E在[-2, 2]之间波动,当|E|大时进入探索阶段,当|E|小时转入开发阶段。开发阶段又分软包围、硬包围、渐进式快速俯冲等多种模式,根据随机数切换。这种多阶段、多模式的级联设计让HHO在应对不同函数地形时有很强的适应能力。不过结构复杂也意味着超参数多,如果只做默认参数对比,HHO的优势不一定能充分发挥,实际调试时需要单独确认各阶段判定的阈值。
CSA模拟的是乌鸦藏食和偷食行为。每只乌鸦记忆一个隐藏食物的位置,每轮随机选择另一只乌鸦作为跟踪目标。如果目标乌鸦没发现被跟踪,就靠近它;如果发现了,就以随机位置把你“甩掉”。CSA的机制优势在于“被跟踪者跳走”这个反制规则,天然引入了一种扰动,能有效防止种群过度聚集。但它在很多测试函数上的收敛速度偏慢,适合需要稳定搜索而不是快速压榨的场景,比如求解资源调度问题时经常能看到它的身影。
2.4 直观对比表:搜索机制与适用场景
光看文字描述还不够直观,我整理了一个对比表,方便快速查找:
| 算法 | 主要搜索机制 | 核心参数 | 探索能力 | 开发能力 | 典型适用场景 | 常见短板 |
|---|---|---|---|---|---|---|
| PSO | 速度惯性+个体/全局记忆 | w, c1, c2 | 中 | 强 | 连续函数优化、工程参数标定 | 易早熟,多峰函数偏弱 |
| GWO | 三层领导加权包围 | a系数 | 中 | 强 | 高维连续优化、路径规划 | 后期多样性下降快 |
| WOA | 螺旋气泡网+随机包围 | b, a | 强 | 中 | 复杂地形全局搜索 | 对初始种群敏感 |
| SCA | 正弦余弦震荡 | a | 中 | 中 | 基线对比、低维优化 | 搜索路径简单,复杂地形乏力 |
| CGO | 混沌博弈空间映射 | 更新策略分支 | 强 | 中 | 多峰高维问题 | 实现逻辑复杂,代码易错 |
| SSA | 角色分工(生产者/加入者/警戒者) | 比例参数 | 中 | 中 | 工程优化、离散问题 | 警戒者比例需调优 |
| HHO | 能量驱动多阶段切换 | E, 概率阈值 | 强 | 强 | 多模态问题综合求解 | 结构复杂,超参多 |
| CSA | 记忆跟踪+随机反制 | AP感知概率 | 强 | 弱 | 资源调度、稳健寻优 | 收敛速度偏慢 |
| TSO | 螺旋+抛物线合作 | a, p | 强 | 中 | 高维连续优化 | 后期重置可能拖慢收敛 |
这张表的价值在于,它能帮你从机制层面快速判断一个算法适合什么任务。比如你做一个精度要求高但地形不复杂的参数标定问题,PSO和GWO这种开发能力强的算法是首选;如果问题是高维多峰、陷阱极多,CGO、HHO、TSO这种探索能力强的算法更值得优先尝试。
3. Matlab实现:统一测试平台与核心代码解析
3.1 目录结构与主脚本设计
我实际用的Matlab工程目录结构是这样的,这个结构能帮你避免出现“算法改了边界处理,但测试函数列表里忘了同步修改”之类的低级问题:
opt_compare/ ├─ main_comparison.m % 主脚本:跑全部对比 ├─ run_single_test.m % 单函数单算法测试封装 ├─ functions/ │ ├─ sphere.m │ ├─ rastrigin.m │ ├─ ackley.m │ ├─ rosenbrock.m │ └─ griewank.m ├─ algorithms/ │ ├─ PSO.m │ ├─ GWO.m │ ├─ WOA.m │ ├─ SCA.m │ ├─ CGO.m │ ├─ HHO.m │ ├─ SSA.m │ ├─ CSA.m │ └─ TSO.m └─ results/ ├─ convergence_history.mat % 保存收敛历史 └─ final_results.mat % 保存每轮最终结果主脚本的骨架代码是这样:
%% main_comparison.m clc; clear; close all; %% 全局统一参数 N = 30; % 种群大小 dim = 30; % 问题维度 MaxFEs = 30000; % 最大函数评价次数 runTimes = 30; % 独立运行次数 %% 测试函数及其边界范围(经典推荐取值) funcNames = {'Sphere', 'Rastrigin', 'Ackley', 'Rosenbrock', 'Griewank'}; handleList = {@sphere, @rastrigin, @ackley, @rosenbrock, @griewank}; lbList = [-100, -5.12, -32, -30, -60]; ubList = [100, 5.12, 32, 30, 60]; %% 算法列表 algoNames = {'PSO', 'GWO', 'WOA', 'SCA', 'CGO', 'HHO', 'SSA', 'CSA', 'TSO'}; handleAlgo = {@PSO, @GWO, @WOA, @SCA, @CGO, @HHO, @SSA, @CSA, @TSO}; %% 数据容器 finalVal = zeros(length(handleAlgo), length(handleList), runTimes); histories = cell(length(handleAlgo), length(handleList), runTimes); %% 主循环 for f = 1:length(handleList) for r = 1:runTimes for a = 1:length(handleAlgo) [gbest, gbestval, hist] = handleAlgo{a}(... handleList{f}, dim, lbList(f), ubList(f), MaxFEs, N); finalVal(a, f, r) = gbestval; histories{a, f, r} = hist; end end end注意这里每个算法函数的输入输出签名完全一致,这是统一测试框架能跑起来的前提。我建议你在写自己的版本时就固定这个接口:传入测试函数句柄、维度、上下界、最大评价次数、种群大小,返回全局最优位置、最优值和收敛历史。
3.2 基准测试函数的选择逻辑
我选了5个经典测试函数,每个函数都代表一类典型的“地形问题”。
- Sphere函数:单峰光滑凸函数,用来检验算法的基本收敛速度,如果连Sphere都收敛得慢,说明算法的基础寻优能力有问题。
- Rosenbrock函数:单峰但有一个极狭长的抛物线形谷地,算法很容易顺着“香蕉谷”缓慢爬行,用来检验算法在病态地形下的精细搜索能力。
- Rastrigin函数:多峰函数,局部极值点数量极多,号称考研生物搜索算法“是否会早熟”的经典关卡。此函数上如果算法前期探索不足,后面一定会卡在局部极值附近。
- Ackley函数:多峰且有大量次极小值,地形向外围呈指数下降趋势,用来综合检验算法的探索与开发平衡能力。
- Griewank函数:带有周期性干扰的多峰函数,高维下局部极值非常多,适合检验算法在高维空间的稳定性。
这5个函数的组合覆盖了单峰、病态单峰、浅多峰、深多峰、高维多峰几种典型地形,用来区分不同算法的特性已经足够。我不会再加更多函数,因为对比实验的维度一旦过多,结果矩阵会很难分析,论文里也容易被审稿人质疑“变量太多,结论发散”。
3.3 核心代码逐段解析
所有算法代码里,我先挑两个有代表性的讲清楚,其余算法逻辑类似,只是在更新方程上不同。
第一个是PSO的完整实现,它虽然简单,但代码结构是所有算法的模板:
function [gbest, gbestval, hist] = PSO(fhd, dim, lb, ub, MaxFEs, N) % 初始化 X = lb + (ub - lb) .* rand(N, dim); V = zeros(N, dim); pbest = X; pbestval = zeros(N, 1); for i = 1:N pbestval(i) = feval(fhd, X(i, :)); end [gbestval, idx] = min(pbestval); gbest = pbest(idx, :); FEs = N; % 参数初始化 w = 0.9; c1 = 2; c2 = 2; hist = zeros(MaxFEs, 1); hist(FEs) = gbestval; while FEs < MaxFEs for i = 1:N r1 = rand(1, dim); r2 = rand(1, dim); V(i, :) = w * V(i, :) + c1 * r1 .* (pbest(i, :) - X(i, :)) + c2 * r2 .* (gbest - X(i, :)); X(i, :) = X(i, :) + V(i, :); % 越界截断 X(i, :) = max(min(X(i, :), ub), lb); val = feval(fhd, X(i, :)); FEs = FEs + 1; if val < pbestval(i) pbestval(i) = val; pbest(i, :) = X(i, :); end if val < gbestval gbestval = val; gbest = X(i, :); end hist(FEs) = gbestval; if FEs >= MaxFEs, break; end end % 惯性权重线性下降 w = 0.9 - 0.5 * (FEs / MaxFEs); end end这里要特别提两个细节。第一个是越界处理,我直接用了“截断到边界”,这是最保守的做法,保证种群始终在合法搜索空间内。第二种常见做法是随机重置到搜索空间内,第三种是边界吸收后加一个微小的随机扰动。我通常只推荐截断法,因为它实现简单且不会破坏种群结构,非常适合横向对比。第二个细节是惯性权重w的线性下降,它让算法前期保持较大探索步长、后期转入精细开发,这是PSO在统一测试中不容易提前停滞的关键。
第二个例子是CGO。CGO的完整版更新规则有多个分支,比较复杂,我这里给出一个思路等价、但代码结构更清晰的核心版本:
function [gbest, gbestval, hist] = CGO(fhd, dim, lb, ub, MaxFEs, N) X = lb + (ub - lb) .* rand(N, dim); evalVal = zeros(N, 1); for i = 1:N evalVal(i) = feval(fhd, X(i, :)); end [gbestval, gbestIdx] = min(evalVal); gbest = X(gbestIdx, :); FEs = N; hist = zeros(MaxFEs, 1); hist(FEs) = gbestval; while FEs < MaxFEs for i = 1:N % 随机选两个不同个体的索引 j = randi(N); while j == i, j = randi(N); end k = randi(N); while k == i || k == j, k = randi(N); end % 由三个样本点构造三个临时“顶点” P1 = (X(i,:) + X(j,:)) / 2; P2 = (X(i,:) + X(k,:)) / 2; P3 = (X(j,:) + X(k,:)) / 2; % 混沌博弈映射:随机选择向某个顶点移动 r = rand; if r < 0.33 Xnew = X(i,:) + 2 * rand(1, dim) .* (P1 - X(i,:)); elseif r < 0.66 Xnew = X(i,:) + 2 * rand(1, dim) .* (P2 - X(i,:)); else Xnew = X(i,:) + 2 * rand(1, dim) .* (P3 - X(i,:)); end Xnew = max(min(Xnew, ub), lb); val = feval(fhd, Xnew); FEs = FEs + 1; if val < evalVal(i) evalVal(i) = val; X(i,:) = Xnew; if val < gbestval gbestval = val; gbest = Xnew; end end hist(FEs) = gbestval; if FEs >= MaxFEs, break; end end end end这段代码最核心的地方在于:CGO的新个体生成不是依赖当前最优解,而是依赖个体之间的几何关系。实际跑下来,CGO在高维Rastrigin上的探索能力确实比PSO强不少,但由于分支判断较多,循环内部的if-else开销也大,运行时间会比PSO慢。
3.4 结果可视化与统计分析
统一平台跑完后,我一般会生成三样东西:收敛曲线图、箱线图、统计结果表。收敛曲线画的是30次运行中,每次运行达到的最优值随函数评价次数变化的平均值(因为每次运行最优值会波动)。示例代码:
figure; semilogy(meanHist(1:1000:end), 'LineWidth', 1.5); xlabel('函数评价次数'); ylabel('目标函数最优值'); legend(algoNames); grid on;注意如果最优值出现0,log坐标会报错或画不出。所以实际画图时我会加一个保护偏移:plot(max(meanHist, eps)),避免除以0或取log时出现inf。
统计表我用的是Matlab自带的统计工具或者直接打印矩阵:
%% 保存最终统计结果 meanVal = mean(finalVal, 3); stdVal = std(finalVal, 0, 3); bestVal = min(finalVal, [], 3);这里我强烈建议保存三份数据而不是只看平均值。平均值反映一般水平,标准差反映稳定性,最小值反映算法单次运行的上限能力。在实际对比报告中经常出现这样的情况:算法A的平均值比算法B差,但A的最小值却优于B,这说明A的理论上限更高,只是不够稳定,那么它在某些可靠性要求不高的探索场景里仍然有价值,不能一票否决。
4. 实操过程与问题排查
4.1 从零跑通整套代码的详细步骤
我把跑通整套流程的实际操作步骤拆解一下,照着做基本不会卡壳。
第一步,准备测试函数目录,把5个函数的m文件全部写好。这里有个小技巧:把所有测试函数写成统一接口,输入是一个行向量x,输出是目标函数值。这样算法端只需要用fhd(x)传参即可,不用为每个函数写特判。
第二步,写算法函数。每个算法文件接收相同签名的入口,内部使用相同的边界截断方式。为了减少重复代码,建议把公共的初始化、越界处理直接复制到每个算法文件里,宁可稍显冗余也尽量不要用“工具箱式”的公共函数。原因很简单:横向对比实验中,每个算法文件越独立,越不容易因为某个公共函数被无意改写而影响结果。
第三步,调通第一个算法PSO,跑完测试函数并画出收敛曲线。确认没有报错后,再依次添加GWO、WOA、SCA等。千万不要一口气把9个算法全部写完再调试,那样一旦出问题,定位错误非常痛苦。
第四步,跑完整对比。我实测中9个算法乘以5个测试函数再乘以30次运行,在普通笔记本上大约需要几十分钟到一两个小时。
4.2 边界处理与维度陷阱
边界处理影响极大,同一算法在“越界截断”“越界随机重置”“越界反弹”三种模式下,结果能差出几个数量级。我在统一测试里用的是截断方式,因为它实现最简单、对算法内部逻辑影响最小。随机重置会引入额外的随机性,在对比多峰函数时容易干扰收敛曲线的平滑性;反弹处理需要额外记录速度或方向,代码复杂,而且某些算法的位置更新方程本身不带速度概念,强行反弹反而破坏了公式推导。
维度问题上,最容易踩的坑是:用rand(N, dim)初始化后,在算法内部更新时用了rand(1, N)之类的错误维度,导致Matlab自动广播运算后出现维度不匹配或隐性错误。建议在写每个算法时都加一行断言:
assert(size(X, 2) == dim, '维度错误:X的列数必须等于dim');这种断言在编码阶段很烦,但遇到问题排查时能节省大量时间。
4.3 收敛异常的分析与处理
我在跑完整个实验后,遇到的现象主要有三类。第一类是某算法在某个函数上收敛曲线完全没有下降,这通常是初始化范围错误或者目标函数句柄传递错误,先检查边界是否匹配。第二类是收敛曲线骤降后又反弹,这是最优值记录逻辑的问题,多半是全局最优gbest在某个分支里没有被正确更新,导致后期“最优点丢失”。第三类是后期曲线几乎水平,这是所有算法都会出现的正常现象,但如果希望延后停滞点,可以考虑适当增大种群规模。
另外还有一个容易被忽视的问题:收敛历史曲线的横轴是函数评价次数还是迭代代数。有些算法代码里一次循环内会调用多次目标函数,如果你按迭代代数记录收敛历史,就会得到一条“假平滑”的曲线。建议严格按FEs计数保存最优值,保证所有算法画图时在同一横轴尺度下比较。
5. 常见问题排查速查表与调试心得
5.1 高频问题清单
我在这次对比实验中整理了几个高频问题的对照表,遇到类似情况可以直接按这个清单排查:
| 现象 | 可能原因 | 排查建议 |
|---|---|---|
| 收敛曲线完全没有下降 | 测试函数句柄传错或边界范围不符 | 单独测试单个算法在Sphere上的表现 |
| 最优值出现NaN | 更新公式中出现除零或log(0) | 在目标函数入口检查输入是否含NaN |
| 不同算法运行时间差异巨大 | if分支多、评价次数不均 | 检查是否所有算法都在同一MaxFEs终止 |
| 结果标准差过大 | 随机数种子影响或种群规模太小 | 增加独立运行次数,检查是否有极端值 |
| 收敛曲线在中途甩尾反弹 | 全局最优记录更新逻辑不完整 | 检查每个更新分支是否都同步了gbest |
| 箱线图出现大量离群点 | 某次运行陷入极差局部极值 | 分析离群点对应的算法运行日志 |
5.2 调试技巧与经验补充
调试这类算法对比程序,我个人的习惯是先跑一个“小规模冒烟测试”:N = 10,dim = 5,MaxFEs = 200,跑1次,打开断点检查每个算法的内部状态。这种测试能快速暴露初始化错误、维度错误和越界处理错误。等冒烟测试全部通过,再把参数恢复到正式值跑全量。
另外一个非常有用的小技巧:在算法内部把当前最优值的实时变化用drawnow实时打印出来,或者每10000次评价输出一个调试日志。在实际跑全量前,先盯着这几个日志跑一遍,能直观看到算法是否在正常收敛。这比盲目等待全部结果后才发现数据异常要高效得多。
最后,如果跑出来的结果和网上论文里报告的经典结果差异很大,先别急着怀疑算法实现,先检查参数。很多算法在新版本论文里都给出了推荐的参数调试范围,比如PSO的c1=c2=2是常用值,但针对某些函数可能调整到1.4更好。横向对比实验的价值不在于证明哪个算法“天下无敌”,而在于告诉你:在统一预算和统一环境下,谁更稳、谁更快、谁更适合你的问题。这9个算法跑下来,我的体会是:没有银弹。你手里的问题函数长什么样,在哪个维度、哪个地形上困难,决定了你应该选谁当主力,选谁当验证基线。
如果真的需要给一个初次上手的建议,我会说先从PSO和GWO入手,因为它们逻辑简单、不容易写错,适合作为所有算法的“基准参照”。等把统一测试平台跑熟,再逐步把CGO、HHO这种新算法加进来,你会发现,算法对比这件事本身,比单学某个算法要更有价值。