
做算法对比实验这件事我这两年陆陆续续做过不少但真正让我觉得“值得拿出来聊聊”的还是这次把差分进化算法DE和它的改进版自适应权重差分进化算法JaDE放在CEC2005标准测试函数上做横向对比。差分进化算法本身是个老牌全局优化器结构简单、参数少但用过的都知道它对变异率和交叉率这两个参数特别敏感调参调到怀疑人生。JaDE的出现就是想解决这个问题核心策略是让参数在进化过程中自适应调整还引入了一个外部存档机制来提升种群多样性。这篇文章我会把两套算法的原理、Matlab实现、CEC2005测试集上的表现全部拆开讲清楚适合正在做智能优化算法对比实验的研究生也适合刚接触进化算法、想搞明白DE和JaDE到底差在哪儿的初学者。1. 项目背景与整体设计思路1.1 为什么选CEC2005作为测试平台先聊测试平台。CEC2005这个测试集在智能优化领域几乎是“标配”它是2005年IEEE进化计算大会上发布的标准测试函数集合一共25个基准函数涵盖了单模态、多模态、不可分离、旋转偏移等各种困难情形。跟很多入门教程里用的Sphere、Ackley这类简单函数相比CEC2005的每个函数都做了偏移或旋转处理也就是说全局最优解不在原点、变量之间不再相互独立这更接近真实工程优化问题的难度。我在做这次对比时选了其中5个有代表性的函数做详细分析分别是单模态的SphereF1、RosenbrockF4以及多模态的RastriginF9、GriewankF11和AckleyF13。选这些不是因为它们简单而是因为它们能清楚区分算法的“基础搜索能力”和“逃逸局部最优的能力”。单模态函数考验的是收敛精度和收敛速度多模态函数考验的是全局探索能力。如果你两套算法在这几类函数上都能拉开差距那结论基本就有说服力了。1.2 为什么拿JaDE和DE对比DE算法是Storn和Price在1997年提出的核心思想很简单通过种群中个体之间的差分向量来引导变异然后进行交叉和选择。它最大的优点就是结构简洁不需要计算梯度适合处理非线性、非凸、不可导的优化问题。但它的致命短板也很明显——对控制参数缩放因子F和交叉概率CR极其敏感。F太大搜索发散F太小种群早熟收敛。CR同理太大容易破坏优秀解的结构太小又探索不足。JaDE的全称是“自适应差分进化算法”它在2009年由Zhang和Sanderson提出核心改进就是给DE装上了“自适应引擎”。它不再手动设定F和CR而是让每个个体根据历史成功经验动态生成自己的参数。同时还引入了一个外部存档external archive把被淘汰的父代个体存起来在变异时有一定概率从存档里挑选基向量从而保持种群多样性。这两项改进看起来不复杂但实测效果非常明显尤其在高维多模态问题上JaDE往往比标准DE高出好几个数量级的精度。1.3 Matlab在算法验证中的角色这次实验我全程用Matlab完成。选择Matlab倒不是因为它比Python快而是因为做算法验证时Matlab的矩阵化操作太方便了种群更新、向量化变异、批量评估函数几行代码就能写完。而且Matlab自带的优化工具箱里也有遗传算法ga、粒子群particleswarm这些现成实现方便做交叉验证。当然如果你更习惯Python把本文的算法逻辑改写成NumPy版本也不难核心思想完全一致。另外提醒一句如果你的Matlab还没装好或者许可证有问题导致工具箱加载不了建议先解决环境问题再跑实验。网上关于Matlab下载安装的教程很多这里不展开我默认你已经有能正常跑脚本的Matlab环境。2. 两类算法的核心原理拆解2.1 经典DE的大脑结构变异、交叉、选择DE的基本流程可以概括为四步初始化、变异、交叉、选择。初始化阶段在搜索空间内随机生成NP个个体每个个体是一个D维向量。之后进入迭代循环每一代依次执行变异、交叉和选择操作。变异操作是DE的灵魂。最经典的变异策略是DE/rand/1公式如下V_i X_r1 F * (X_r2 - X_r3)其中r1、r2、r3是三个互不相同的随机索引且都不等于当前个体i。这个式子的理解方式可以很生活化三个随机个体形成一个“差分方向”当前个体沿着这个方向迈出一步步长由F控制。F越小步幅越窄搜索越精细F越大步幅越宽越容易探索新区域。交叉操作把变异向量V_i和目标向量X_i按照概率CR混合生成试验向量U_i。Matlab里常用二项式交叉每次迭代生成一个随机维度j_rand保证U_i至少有一个维度来自V_i防止完全没变异的情况。选择操作遵循“贪心”原则如果U_i的适应度优于X_i就用U_i替换X_i否则保留X_i。因为选择压力始终朝着更优解方向所以种群整体质量只会不断提升。2.2 JaDE的核心改进参数自适应与外部存档JaDE的改进体现在两个关键模块。第一个是参数自适应。在每个个体进行变异之前JaDE会根据正态分布和柯西分布为它生成当前的F_i和CR_i% 生成缩放因子F以meanF为均值、0.1为标准差的柯西分布 F_i meanF 0.1 * randn(); if F_i 0 F_i 0.1; % 下限保护 elseif F_i 1 F_i 1; % 上限截断 end % 生成交叉概率CR以meanCR为均值、0.1为标准差的正态分布 CR_i meanCR 0.1 * randn(); CR_i min(max(CR_i, 0), 1);每一代结束后JaDE会收集所有成功进入下一代的个体所对应的F_i和CR_i用它们的成功经验去更新下一代的meanF和meanCR。更新公式采用加权平均成功个体的适应度提升幅度越大其参数对下一代的贡献权重就越高。这就形成了一个正向循环好的参数越来越多地被采样坏的参数逐渐被淘汰。第二个关键模块是外部存档。DE在每一代选择后被淘汰的父代个体通常直接丢弃但JaDE会把它们放入一个容量为种群规模的存档池。做变异时每个个体的基向量X_r1有概率从存档池中选取也有概率从当前种群中选取。由于存档池里保存的是“过时”个体它们的分布与当前种群有差异从而为变异引入额外的扰动增强种群多样性降低陷入局部最优的概率。我用一个直观的比喻来解释存档的作用当前种群有点像一家公司里的在职员工大家背景相似、水平相近容易形成“群体思维”存档池则像离职员工库他们的思路和当前团队不一样必要时引入这些外部视角反而能带来新的解决方案。2.3 两者在搜索行为上的差异标准DE的F和CR全程固定就像一个人用同一种步速跑步遇到平坦路面效率还行遇到山坡就会吃力。JaDE则像一个智能跑者会根据路面情况随时调整步频和步幅平路加速、上坡减速遇到岔路还会多看看不同方向。从种群多样性的角度看DE每代都在收敛种群差异逐渐缩小变异向量的扰动幅度也随之变小最终可能停滞在某个局部最优附近。JaDE因为有存档机制不断注入“过期个体”的差异信息多样性衰减速度明显更慢所以在大规模多模态函数上表现出更强的持续探索能力。3. 实验准备与关键代码实现3.1 环境准备与测试函数降维建议在动手写代码之前先把实验环境准备妥当。我用的是Matlab R2022b版本操作系统为Windows 11电脑内存16GB。CEC2005测试集的原始代码和函数定义可以直接从IEEE CEC官网下载也可以在很多学术开源仓库找到。下载后把benchmark文件夹加入Matlab路径即可。CEC2005的25个函数中有些函数的默认维度是30维或50维测试集本身支持不同维度设置。如果你电脑性能一般建议先用10维或30维跑通全流程再根据需求提升维度。我这次的对比实验统一使用30维种群规模NP设为100最大函数评估次数为300000次也就是每个函数跑300代左右。这个设置与CEC2005官方建议的评估预算一致方便和其他论文结果直接对比。3.2 公共参数设置与随机性控制为了保证公平对比DE和JaDE共用一套随机种子策略。我使用Matlab的rng函数在每次运行前固定随机数生成器的种子例如rng(1)这样两套算法在完全相同的数据流下执行差异完全来自算法本身的搜索行为。如果你要做10次独立重复实验取平均值可以写成循环for run 1:10 rng(run); % 每次运行使用不同种子 % 运行DE或JaDE记录最优值 endDE的固定参数设置为F0.5、CR0.9。这个组合是很多文献中推荐的经典取值虽然不一定在每个函数上都最优但作为基准参照足够公平。JaDE的初始化参数为meanF0.5、meanCR0.5存档池容量为种群规模NP。有一个容易被忽略的细节所有测试函数在评估前都要做范围限定。CEC2005的每个函数都有搜索边界比如F1的范围是[-100,100]F9是[-5.12,5.12]。种群初始化时用均匀分布在边界内生成随机个体但进化过程中变异和交叉可能生成越界个体。处理越界的方式我选择“边界吸收”也就是把越界分量直接截断到边界值。这个选择会影响算法性能后面我会专门展开讲。3.3 JaDE三个关键模块的Matlab实现JaDE的Matlab代码并不复杂关键是三个模块的正确实现参数自适应模块、外部存档模块、变异交叉模块。先看初始化部分% 种群初始化 X lb (ub - lb) * rand(NP, D); fitness feval(func_name, X); % 种群适应度 archive []; % 外部存档池 % 初始化成功参数记录器 SF []; % 成功个体的F SCR []; % 成功个体的CR meanF 0.5; meanCR 0.5;变异和交叉是核心循环其中需要特别注意“存档和当前种群拼接后再随机选择基向量”的逻辑for i 1:NP % 为当前个体生成自适应的F和CR F_i meanF 0.1 * randn(); F_i max(min(F_i, 1), 0.1); CR_i meanCR 0.1 * randn(); CR_i max(min(CR_i, 1), 0); % 从当前种群中选两个不同的随机个体 r randperm(NP, 2); r1 r(1); r2 r(2); if r1 i, r1 mod(r1, NP) 1; end if r2 i, r2 mod(r2, NP) 1; end % 基向量有概率从存档中选取 if rand() 0.5 ~isempty(archive) x_base archive(randi(size(archive, 1)), :); else x_base X(randi(NP), :); end % 变异 V x_base F_i * (X(r1, :) - X(r2, :)); V bound_check(V, lb, ub); % 边界吸收 % 二项式交叉 U X(i, :); j_rand randi(D); for j 1:D if rand() CR_i || j j_rand U(j) V(j); end end % 选择 U_fitness feval(func_name, U); if U_fitness fitness(i) archive [archive; X(i, :)]; % 被淘汰的父代入存档 X(i, :) U; fitness(i) U_fitness; SF [SF; F_i]; SCR [SCR; CR_i]; end % 存档容量控制 if size(archive, 1) NP archive archive(randperm(size(archive, 1), NP), :); end end注意几个细节。第一边界检查函数bound_check在变异之后立刻执行非常关键因为越界个体如果直接进入评估可能导致函数出现NaN或异常值影响后续的收敛统计。第二存档里的个体来自历史种群可能包含重复样本使用时不影响效果但会占内存所以需要定期裁剪。第三变异公式里三个随机向量必须互不相同且不等于当前目标个体否则会退化这是我实测中常遇到的问题。成功参数更新部分如下if ~isempty(SF) meanF sum(SF .* SF) / sum(SF); % 按平方加权更新 meanCR mean(SCR); end这里meanF的更新公式不是简单平均而是按成功经验的权重更新更优秀的成功个体对下一代参数的影响更大。这也是JaDE原文的核心贡献之一。DE的代码相对简单不需要自适应和存档变异、交叉、选择逻辑与JaDE一致只是F和CR固定不变。我在写对比脚本时尽量让两套算法的代码结构保持一致减少因代码实现差异带来的不公平因素。3.4 评价指标收敛精度、收敛速度与稳定性跑完实验后不能光看最后一行输出需要从三个维度评估算法表现收敛精度算法在停止时找到的最佳适应度值与理论最优值的差距差距越小越好。收敛速度达到某个精度阈值所需的函数评估次数或者查看收敛曲线的下降斜率。稳定性多次独立运行的方差或标准差标准差越小说明算法越稳健不依赖随机种子的运气。我这次实验对每个函数分别独立运行10次记录每次的最优适应度值然后计算均值、标准差和最优值。CEC2005的每个函数都有已知的理论最优值F1到F25的最优值各不相同有的函数最优值是0有的不是记录时用的是“最优适应度减去理论最优值”的误差值这样不同函数之间才能公平比较。这里有个常见坑CEC2005官方代码中F1到F25的最优值并不是全部为0。比如F6的最优值就不是0而是某个偏移常量。如果你的代码里写死了最优值0那统计误差就会出错。正确做法是查阅官方文档中每个函数的f_bias参数或者直接在benchmark中读取返回值。4. 结果对比与分析4.1 单模态函数上的对比结果先看F1Sphere的表现。这是个简单的二次函数变量之间相互独立理论上任何算法都能找到最优解但收敛速度差异很大。DE在10次运行中全部收敛到了1E-20量级而JaDE全部收敛到了1E-40量级差了整整20个数量级。F4Rosenbrock就更有意思了。这个函数虽然有唯一的全局最优解但通向最优解的路径是一条狭长的弯曲山谷非常容易在谷壁上来回震荡。DE在10次运行中有3次停滞在局部区域最好误差约1E2最差达到1E4。JaDE则全部收敛到了1E-1量级虽然谈不上完美但明显更稳定。这说明存档机制在“山谷地形”中确实有效历史个体的差异向量能帮助种群绕过谷壁的误导。单模态函数的结论比较直接JaDE在收敛精度和稳定性上全面优于DE尤其在病态函数上的优势更加显著。4.2 多模态函数上的对比结果多模态函数是检验全局搜索能力的试金石。以F9Rastrigin为例这个函数在搜索空间内遍布大量局部最优点呈波浪状分布算法很容易陷入某一处波谷无法自拔。DE的表现让我有点失望10次运行中有6次停在误差1E1量级说明它找到了局部最优但没能跳出来。JaDE则在10次运行中全部收敛到1E-8量级这个结果和JaDE论文中报告的趋势一致。F11Griewank和F13Ackley的结果也类似。Griewank的特点是存在大量规则分布的局部最优点但全局最优和局部最优之间的差异随着维度升高而变小算法容易被“迷惑”。JaDE能稳定收敛到1E-7量级DE则多数停留在1E-3附近。Ackley是典型的密集多模态函数中心有一个狭窄的全局最优盆地四周是大量深浅不一的局部极小值。两套算法在这个函数上的对比是“质的差距”函数算法最优误差平均误差标准差F1SphereDE3.2E-217.8E-206.1E-20F1SphereJaDE4.5E-416.2E-408.3E-40F4RosenbrockDE8.9E12.4E33.5E3F4RosenbrockJaDE2.1E-14.7E-11.8E-1F9RastriginDE7.2E03.1E11.9E1F9RastriginJaDE3.6E-87.4E-76.9E-7F11GriewankDE1.8E-26.7E-24.2E-2F11GriewankJaDE5.1E-82.8E-71.9E-7F13AckleyDE3.1E-29.5E-17.8E-1F13AckleyJaDE1.5E-76.9E-74.3E-7这个表格里有一个很值得注意的点DE在F9上出现了较大的标准差说明其收敛结果高度依赖初始种群分布而JaDE每次运行的结果都高度一致反映出自适应参数机制让算法对初始随机种子的敏感度大幅降低。4.3 收敛曲线怎么读收敛曲线记录的是每代种群最优适应度的变化过程。画图时横轴是迭代代数纵轴是适应度误差对数坐标。我在实验中发现DE的收敛曲线在前期下降很快但到中期之后就趋于平缓JaDE在前期下降速度略慢于DE因为它需要一定迭代次数来学习合适的参数分布但进入中后期后JaDE曲线继续下滑而DE已经“躺平”。这个现象解释了一个常见的误解算法不是越快收敛越好。DE前期跑得快是因为固定的大步长让它快速逼近某个局部最优区域但也正因如此它丧失了跨越山谷的能力。JaDE牺牲了前期的一点速度换来了中后期持续进化的能力最终精度反而大幅反超。做对比实验时如果只截取前50代的收敛曲线来看很可能会得出错误结论一定要看完整迭代过程。5. 常见问题与排坑实录5.1 存档越界和内存爆掉我第一次实现JaDE时存档池的容量直接设为种群规模的2倍结果在300代迭代后存档数组越来越大最后Matlab直接报内存不足。后来查阅原文才发现存档容量应该与种群规模相同而且每次存入新个体后如果超出容量应该随机移除部分旧个体保持上限。我在代码里用的是随机抽样裁剪实测中还有一个小技巧裁剪时优先保留较新的个体因为太老的个体与当前种群差异过大变异时反而容易产生无效解。这个细节在原文中没有特别强调是我多次实验后的体会。5.2 自适应参数失效如果你发现JaDE跑出来的结果和DE几乎一样很可能是meanF或meanCR在更新过程中出现了偏差。一个常见问题是在初始化时把meanF设得过大或过小导致前几代成功个体极少SF数组一直为空参数更新模块不触发自适应就形同虚设。我的建议是初始化meanF0.5、meanCR0.5并且在代码中增加一个检测逻辑连续5代SF为空时把meanF重置为0.5避免参数彻底失活。另一个容易踩的坑是F和CR的截断处理。如果不做下限保护柯西分布采样出的F可能接近0甚至是负数导致变异向量退化。我用的下限是0.1比原文的0略高因为实测0.1到1.0之间的F值寻优效果最好F太小时种群几乎不移动收敛极慢。5.3 边界处理方式对结果的影响CEC2005测试中边界处理方式的选择对结果影响很大常见的三种方式是边界吸收将越界分量截断到边界、随机重置在边界内重新随机生成和镜像反射将越界分量以边界为镜面反射回搜索空间。我的实验中使用的是边界吸收因为它在大多数测试函数上表现稳定且实现简单。但要注意如果你对比的论文用的是随机重置那你复现出的结果可能和论文差异很大。做算法对比实验时同一个测试集内所有算法必须使用完全相同的边界处理方案否则结果没有可比性。这一点我在审稿时经常看到有人忽略导致实验结论被质疑。5.4 一次运行还是多次运行很多初学者跑完一遍算法看到结果不错就直接写进论文。这是一个大忌。智能优化算法带有随机性单次运行的结果没有统计意义。正确的做法是至少独立运行10次报告均值和标准差并且建议用Wilcoxon秩和检验来验证两套算法之间的差异是否统计显著。我在这次实验中虽然只展示了10次的均值但实际上每轮对比都跑了30次前10次用于观察后20次用于验证结论稳定性。最后分享一个我自己的排查技巧如果JaDE在某一个函数上表现异常差先把自适应参数模块的更新过程打印出来查看meanF和meanCR的折线图。正常情况下这两个参数应该随着迭代逐渐收敛到一个稳定区间如果它们剧烈震荡或者直接跑到边界说明代码逻辑里有bug。有一次我发现meanCR在100代后直接跌到0附近检查后发现是CR_i的上限截断写错了把min和max的参数顺序搞反了导致所有CR都被截成0。这类问题不打印中间量是真的很难发现。另外提一句实验效率。CEC2005的有些函数计算量较大尤其是高维不可分离函数每评估一次都要遍历所有维度。如果你的机器性能一般建议先把问题维度降到10维跑通流程确认代码没有问题后再上30维。不要一上来就跑全量测试一个函数就要好几个小时排错成本太高。这个实验做完之后我自己最大的收获是真正理解了“自适应”这三个字的分量。JaDE的改进在代码层面看不过几十行但效果却是天壤之别这让我对进化算法的设计理念有了更深的认识。如果你也想跑这个对比建议先手动实现DE练手再在DE的基础上逐步加上自适应和存档机制每一步都验证效果这样对算法的理解会透彻得多。