ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

七种智能优化算法在23个基准测试函数上的MATLAB对比实现

七种智能优化算法在23个基准测试函数上的MATLAB对比实现 七种算法放在同一套MATLAB平台上跑23个基准测试函数听起来像流水线机械劳动但做过对比实验的人都知道这是智能优化算法研究里最扎实的“双盲测试”。这次我一次性实现了GRO、NOA、MA、PSO、GA、ZOA、SWO七个算法配套完整的基准测试函数集、统一评价指标和代码框架全部跑完并整理了结果表与参考文献列表。如果你正在准备算法对比实验或者想从工程角度客观评价一个优化器的真实水平这套方案可以直接拿来改也可以帮你把“这个算法到底行不行”这个问题彻底想清楚。1. 为什么是这23个函数基准测试的设计逻辑1.1 三类测试函数的“地形”差异23个经典基准测试函数不是随机凑出来的它们被设计成三组分别对应三种典型的搜索地形。第一组是单峰函数F1-F7整个定义域内只有一个全局最优点没有局部极小来干扰算法纯粹考验收敛速度和精度。第二组是多峰函数F8-F13地形里布满了大量局部极小值算法一不小心就会困在某个“假山谷”里主要考察全局勘探能力和跳出局部陷阱的能力。第三组是固定维度多峰函数F14-F23维度不高但地形更贴近真实工程问题很多函数带有平坦区域、窄长谷地或者欺骗性梯度用来评估低维条件下的精确搜索能力。你可以把这23个函数理解成体检套餐中的不同项目单峰函数查“心肺基础功能”多峰函数查“压力环境下的应激反应”固定维度函数则更像“真实生活场景模拟”。只跑一两个函数就下结论就像只查了血常规就判断全身健康说服力远远不够。1.2 23个函数速查表下面这张表是我在搭建测试平台时一直放在手边的速查表函数编号、名称、类型和考察点一目了然。编号函数名类型维度主要难点与考察点F1Sphere单峰30最简单的光滑凸函数检验基本收敛能力F2Schwefel 2.22单峰30含有绝对值与乘积项梯度变化剧烈F3Schwefel 1.2单峰30病态二次型变量之间强耦合F4Schwefel 2.21单峰30最大值型函数容易在边界处失效F5Rosenbrock单峰30经典香蕉谷窄长弯曲谷底极难收敛到最优点F6Step单峰30阶梯状平坦区域测试算法对离散地形的适应力F7Quartic with Noise单峰30带随机噪声考验鲁棒性F8Schwefel 2.26多峰30全局最优点远离次优点存在欺骗性F9Rastrigin多峰30大量规则的局部极小考验摆脱能力F10Ackley多峰30中间深坑四周密集局部极小经典陷阱F11Griewank多峰30乘积项引入周期性干扰全局和局部结构并存F12Penalized 1多峰30带惩罚项的多峰函数边界处理敏感F13Penalized 2多峰30同上地形更复杂F14Foxholes固定维度2大量均布局部极小点全局极小埋得深F15Kowalik固定维度4小规模参数辨识问题数值条件差F16Six-Hump Camel固定维度2多局部极小的双峰驼背函数F17Branin固定维度2三个全局极小点检验多模态处理能力F18Goldstein-Price固定维度2多项式复合地形局部极小密集F19Hartman 3-D固定维度3三维指数势函数适合中等复杂度测试F20Hartman 6-D固定维度6六维指数势函数维度扩展版F21Shekel 5固定维度4深坑型函数坑数可调F22Shekel 7固定维度4更多深坑局部极值干扰更强F23Shekel 10固定维度4坑最多全局极小隐蔽性最强我自己在写代码时最大的感受是千万别小看F7这种带噪声的函数。很多算法在干净函数上表现很好一遇到噪声就露馅因为它本质上考察的是算法在随机扰动下不停机的稳定能力。真实工程里的目标函数几乎都带噪声这比纯数学函数的收敛精度更有现实意义。1.3 测试平台的意义让算法说同一种语言我见过不少算法论文作者在自家函数集上测试效果“惊艳”但换个平台立刻打回原形。原因很简单每个算法论文都会选择有利于自己的测试场景这不算造假但横向可比性极差。23个经典函数作为公共基准价值恰恰在于大家都在同一套卷子上考试。这里有个容易踩的坑有些人不自觉“择菜”只选取自己算法表现最好的少数函数来报告。做工程选型时可以理解但如果是学术对比实验这样做容易翻车。我的习惯是23个函数全部跑完再按类型分组分析就算某个函数跑出来不理想也如实记录。那样反而让结论更可信也更容易发现算法的真实适用边界。2. 七种算法是什么从经典双雄到动物行为大军2.1 PSO和GA为什么永远是基准测试的对照组PSO和GA不需要过多介绍但在这个项目里它们是绝对的“地标”。PSO模拟鸟群觅食行为每个粒子同时被自身历史最优和全局最优牵引核心更新公式只有两行速度更新和位置更新。我习惯用的版本带惯性权重w线性递减这样前期勘探能力强、后期开发能力强效果比固定权重稳定得多。GA则模拟自然选择和遗传变异选择、交叉、变异三种算子缺一不可。实数编码版本里交叉多采用模拟二进制交叉变异采用多项式变异再搭配精英保留策略。这两个经典算法之所以必须作为对照组是因为它们覆盖了两种截然不同的搜索哲学PSO靠群体信息交互GA靠算子驱动的种群演化。新算法的表现好不好只有和这两种“体温计”放在一起量才知道是真创新还是换皮重排。2.2 五个新算法各自的“核心机制”GRO、NOA、MA、ZOA、SWO这五个算法绝大多数是近几年提出的“动物行为启发式算法”。第一次接触时可能会看得眼花缭乱但拆开看机制后会发现它们本质上都在回答同一个问题如何平衡勘探与开发。GRO模拟巨犰狳挖洞觅食的行为用“螺旋掘进-后退探查”这种动静结合的策略来动态切换搜索阶段是我认为这一批里比较注重动态切换节奏的算法。NOA模拟星鸦采集、储藏和取回松果的复杂行为把探索和开发拆成两个大阶段、四个子策略结构上更精细。MA这里指蜉蝣优化算法它把鸟群式的位置-速度模型和遗传交叉变异结合了起来雄雌蜉蝣分别更新、交配产生子代本质上是一套混合算法。ZOA则简单直接模拟斑马的觅食和防御行为觅食阶段围绕当前最优个体收缩搜索防御阶段根据随机概率选择逃跑或反击代码实现门槛很低特别适合新手读懂新算法的一般套路。SWO蜘蛛蜂优化器的设计更复杂雌性蜘蛛蜂捕猎蜘蛛、注入毒素、产卵、幼虫成长这些阶段都被建模成不同的搜索算子全局勘探和局部精细开发交织得很密。一个很重要的提醒不要因为一个算法名字新颖就高看一眼。这几年新出的动物行为算法很多有一部分就是把PSO的公式换个符号重写一遍。判断一个算法的含金量要看它的算子在哪些搜索阶段真正贡献了多样性而不是看它模拟的动物有多冷门。2.3 七种算法速览表算法全称核心思想主要算子/特点适合方向GROGiant Armadillo Optimization巨犰狳挖洞觅食螺旋挖掘、后退探查勘探开发动态切换复杂多峰连续优化NOANutcracker Optimizer Algorithm星鸦储食取回行为多阶段多策略交替结构精细高维函数优化MAMayfly Algorithm蜉蝣求偶交配飞行速度位置更新交配变异混合性强中等规模连续优化PSOParticle Swarm Optimization鸟群/鱼群觅食速度-位置更新群体信息共享连续光滑问题经典基线GAGenetic Algorithm自然选择与遗传选择、交叉、变异、精英保留离散/组合优化经典基线ZOAZebra Optimization Algorithm斑马觅食防御觅食跟随最优防御随机逃避/反击结构简单快速验证SWOSpider Wasp Optimizer蜘蛛蜂捕猎繁殖捕猎麻醉、产卵孵化两阶段多算子综合复杂度高的优化问题从这张表也能看出新算法的设计趋势是“把多个成熟机制缝合到一起”。比如MA是PSOGA的混合NOA则在交替执行不同策略SWO直接把多个阶段串成了一个逻辑链。理解了这一点再看代码就不会被一堆动物名词绕晕重点去看每个阶段里的更新公式用了什么经典算子。3. 实验设计保证公平的几个关键决策3.1 参数统一还是各调各的这个问题我在设计实验时纠结了很久。如果所有算法用完全相同的参数对某些算法来说不公平因为新算法通常要依赖额外算子种群规模和最大迭代次数的影响各不相同。如果每个算法都调到最优再比工作量巨大而且“最优参数”本身就可能被人为操纵。最终我选择了文献中最常用的折中方案能统一的全统一每个算法自带的特有参数用原始论文推荐值不额外精细调优。具体来说种群规模N固定为30最大迭代次数固定为500高维函数维度统一设为30固定维度函数使用各自定义维度。这个配置的好处是总评价次数固定为30×50015000次对比双方在计算资源上完全对等争议最小。3.2 独立运行次数与评价指标随机优化算法每次运行结果都可能不同只跑一次就下结论是最常见的错误。我每个函数独立跑30次所有随机种子在主脚本里统一生成保证每个函数每个算法拿到的初始随机序列是同一套。评价指标我记录五样最优值、最差值、均值、标准差和收敛曲线。这里我要多说一句均值不是万能的。某个算法可能在20次运行中达到全局最优但另外10次完全发散均值可能和另一个稳定收敛到次优解的算法差不多。真正能反映算法稳定性的指标是标准差能把异常大偏差暴露出来。我在看实验结果时习惯先看“均值”和“标准差”这一对“连体婴”如果均值和标准差都占优结论才是真的可靠。3.3 初始化与边界处理的一致性初始化方式对算法结果的影响经常被忽略。我用统一函数生成初始种群边界取每个基准函数定义域的标准范围。特别要注意的是初始种群必须是真正的随机均匀分布不要用固定矩阵反复复用否则后面没法分析随机性带来的差异。边界处理我也做了统一约定搜索过程中个体越界时采用“重新初始化到边界内”的方式而不是简单粗暴地截断到边界上。直接截断会让大量个体堆在边界上种群多样性瞬间消失这个问题非常隐蔽我在跑F4这种最大值型函数时吃过亏一开始收敛曲线看起来不错实际上全是边界截断的“假收敛”。3.4 实验参数速查表参数项取值说明种群规模N30中等规模避免算法差异被群体规模掩盖最大迭代次数500总评价次数15000次高维函数维度30F1-F13统一固定维度函数按定义F14-F23使用原始维度独立运行次数30每次用不同随机序列随机数管理主脚本统一rng设置算法内部不重设种子边界处理重新初始化避免种群堆叠在边界统计指标最优/最差/均值/标准差/收敛曲线可按需增加Wilcoxon检验这套参数表格建议直接贴在你的实验记录里。写论文的时候专家经常会追问实验配置有这张表就能应对大部分提问也不会出现“跑完了才发现忘了记录”的尴尬。4. MATLAB代码实现搭建一套可复用的测试框架4.1 顶层主脚本函数句柄数组驱动一切这套代码框架的核心思路是“统一接口 数据驱动”。所有算法对外只暴露同一个函数签名所有基准函数都通过函数句柄传递。这样主脚本可以写得很短新增算法时只需要往algorithms列表里加一行。% run_experiments.m clear; clc; % 统一实验配置 config.N 30; config.MaxIter 500; config.Dim 30; config.Runs 30; % 算法列表函数句柄 算法名 algorithms { (f,lb,ub,dim,N,iter) GRO(f,lb,ub,dim,N,iter), GRO; (f,lb,ub,dim,N,iter) NOA(f,lb,ub,dim,N,iter), NOA; (f,lb,ub,dim,N,iter) MA(f,lb,ub,dim,N,iter), MA; (f,lb,ub,dim,N,iter) PSO(f,lb,ub,dim,N,iter), PSO; (f,lb,ub,dim,N,iter) GA(f,lb,ub,dim,N,iter), GA; (f,lb,ub,dim,N,iter) ZOA(f,lb,ub,dim,N,iter), ZOA; (f,lb,ub,dim,N,iter) SWO(f,lb,ub,dim,N,iter), SWO; }; funcs loadBenchmarks(config.Dim); results cell(size(algorithms,1), numel(funcs)); for a 1:size(algorithms,1) for f 1:numel(funcs) fprintf(运行算法: %s 函数: %s\n, algorithms{a,2}, funcs(f).name); records zeros(1, config.Runs); curves []; for r 1:config.Runs initPop initPopulation(funcs(f).lb, funcs(f).ub, funcs(f).dim, config.N); [bestCost, history] algorithms{a,1}(... funcs(f).handle, funcs(f).lb, funcs(f).ub, ... funcs(f).dim, config.N, config.MaxIter, initPop); records(r) bestCost; curves [curves; history]; %#okAGROW end results{a, f} struct(best, min(records), worst, max(records), ... mean, mean(records), std, std(records), curves, curves); end end save(results_all.mat, results, algorithms, funcs, -v7.3);这套主脚本跑起来会输出进度信息避免长时间无用等待。我通常会加一个tic/toc统计每个算法在每个函数上的耗时如果某个函数明显比别的慢很多就要排查是不是代码里出现了死循环或者数据复制爆炸。4.2 基准函数集的封装基准函数集我用结构体数组封装每个函数固定包含名称、维度、边界、理论最优值和函数句柄。这样主脚本遍历起来非常干净。function funcs loadBenchmarks(D) funcs []; funcs(end1) struct(name,Sphere, type,单峰, ... dim,D, lb,-100, ub,100, optimal,0, ... handle,(x) sphere_func(x)); funcs(end1) struct(name,Rosenbrock, type,单峰, ... dim,D, lb,-30, ub,30, optimal,0, ... handle,(x) rosenbrock_func(x)); % ... 其余函数按同样方式补全 end这里有一个细节很容易被代码新手忽略Rosenbrock函数的全局最优点在(1,1,...,1)不在定义域中心零点。如果沿用大部分函数“对称边界”的惯性很可能会把搜索域设成[-1,1]那就把真正的最优点排除在外了。我建议每添加一个函数先手动在MATLAB里用理论最优值点算一次函数输出确认返回值和文献一致后再放进测试集。4.3 算法接口统一化所有算法的接口我统一写成[bestCost, history] algorithm(fobj, lb, ub, dim, N, MaxIter, initPop)。输入参数依次是目标函数句柄、下界、上界、维度、种群规模、最大迭代次数和初始种群。initPop可以传也可以传空让算法自己初始化但为了公平实验我在主脚本里统一生成后传给每个算法。function [bestCost, history] GRO(fobj, lb, ub, dim, N, MaxIter, initPop) % 统一接口实现示例 if isempty(initPop) initPop initPopulation(lb, ub, dim, N); end pop initPop; bestCost inf; history zeros(1, MaxIter); for t 1:MaxIter % 计算适应度 costs zeros(N, 1); for i 1:N costs(i) fobj(pop(i,:)); end % 更新最优解 [cmin, idx] min(costs); if cmin bestCost bestCost cmin; bestX pop(idx,:); end history(t) bestCost; % 算法核心更新算子不同类型算法在此替换 % pop ... end end我把最重要的经验放在这里写新算法实现时第一步不是“把论文公式翻译成代码”而是先把上面这个骨架跑通确保目标函数、边界、初始化和最优解记录都正常再往里面填论文里的搜索算子。否则你花两天写完整个算法最后发现是初始化把维度都传错了排查成本叠加好几倍。4.4 收敛曲线绘制与结果存档收敛曲线是整个实验最直观的展示方式。绘制时我有两个实操经验一是对多次运行取平均后再画单次运行的曲线噪声太大看不出趋势二是很多函数收敛到0或者极接近0画对数坐标时会出现log(0)导致曲线断掉。我处理的办法是记录历史值时统一加一个极小量1e-300绘图时只显示大于0的点。function plotConvergence(results, funcs, algNames, funcIdx) figure(Color,w, Position,[100 100 700 500]); hold on; for a 1:numel(results(:, funcIdx)) avgCurve mean(results{a, funcIdx}.curves, 1); iters 1:numel(avgCurve); mask avgCurve 0; semilogy(iters(mask), avgCurve(mask) 1e-300, LineWidth, 1.6); end hold off; grid on; legend(algNames, Interpreter,none); xlabel(迭代次数); ylabel(平均最优目标值对数坐标); title([函数 funcs(funcIdx).name]); saveas(gcf, [convergence_ funcs(funcIdx).name .png]); end另外结果存档不要只存一张图。我把results_all.mat里的均值标准差表额外导出成CSV写论文时可以直接粘贴到LaTeX表格里。导出方法很简单循环结果数组用writetable写CSV即可。5. 高频报错与排查记录5.1 索引和维度不一致这个错误在七种算法里我都遇到过多轮。典型场景是目标函数期望输入是1 x dim的单个个体算法内部却传进来N x dim的整个种群或者在初始化时把dim和N搞反生成出个30 x 30的矩阵然后在运算时又当作50维。排查方法很笨但有效在每个算法入口加一行disp(size(pop))用最简单的F1函数先跑一遍确认每个循环体内变量的size都符合预期。5.2 收敛曲线“躺平”不动如果你的收敛曲线前几代下降很快然后几十代完全水平大概率不是算法找不到更好解而是边界处理出了问题。个体越界后被直接截断到边界种群多样性急剧下降算法陷入“伪停滞”。另一种常见原因是适应度计算出现了NaNNaN参与比较时不会更新最优解而min函数遇到NaN经常静默返回。我建议在所有目标函数返回值出口统一检查~isfinite(cost)有异常就打印可疑个体。5.3 随机数种子管理不当实验开始复现性出问题九成是因为rng设置的位置不对。有些算法内部会调用rng(shuffle)或者randn这会悄悄改变全局随机数流导致后续算法拿到完全不同的初始种群。我做实验时的强制约定全局种子只在主脚本设置算法内部一律不调用任何重置随机数流的函数如果要保证每个函数都能用同一套初始种群就在主循环里先预先缓存所有函数的初始种群矩阵再传给算法。5.4 结果复现困难除了随机数复现问题还可能来自MATLAB版本差异和浮点计算顺序。我的做法是把运行环境信息一并保存到结果文件MATLAB版本、算法代码版本号、rng种子、每个函数的初始种群矩阵。这样即使三个月后再回看数据也能精确复现当时的实验。5.5 常见错误速查表错误现象可能原因解决方案维度报错或矩阵拼接失败dim和N混用算法入口打印size用F1函数最小化排查收敛曲线前段好、后段平坦边界截断导致多样性丢失改用边界内重新初始化减少直接截断结果跑几次都不一致算法内部重置随机种子全局种子统一在主脚本设置算法内禁用rng目标值出现NaN或Inf函数定义域外计算溢出越界重初始化并在目标函数返回值处检查有限性绘图时log曲线断掉收敛到0或负值记录时加1e-300绘图时过滤非正点某算法特别慢代码内向量化不彻底或死循环用tic/toc定位耗时环节检查是否有重复计算跑完这批实验我最想说的是新算法层出不穷23个函数就像一面照妖镜。比起总排名我建议你重点关注某个算法在哪些函数上失败再回看它的代码搞清楚失败原因到底是勘探不足还是开发过度这种分析带来的认知提升远大于看一张“七个算法排名表”。这套测试框架的扩展性很好想接真实工程问题也很方便把目标函数换成实际模型就行比如路径规划、参数辨识这类场景只需要改边界、维度和目标函数句柄其他部分完全不用动。我个人的体会是真正可靠的结论从来不是“哪个算法最强”而是“什么样的地形适合什么样的机制”这才是做基准测试实验最大的收获。
返回列表