2024年我给自己定了一个小目标:把近几年冒出来的12种新算法,统一丢到CEC2021测试集上跑一遍。说实话,这个项目听起来就是一件顺理成章的基准测试工作,但真做起来才发现,CEC2021测试集对“新算法”的考验比想象中硬核得多。它不是那种随便找个函数、跑50次取个均值就能交差的玩具,而是近年来智能优化领域很受认可的“期末考试”之一,很多论文都拿它证明自己的算法不是花架子。这个项目的核心价值主要在三块:一是验证算法在混合、组合、多模态问题上的真实水平;二是用统一的实验协议把12种新算法放到同一起跑线横向对比;三是通过统计检验、收敛曲线这些手段,把“谁更好”从主观感受变成可量化的结论。适合谁看?正在做智能优化算法研究、需要跑基准测试的研究生和从业者,以及想用CEC系列测试集评估自己新想法的人,这篇内容应该能帮你少踩不少坑。
1. CEC2021测试集到底测的是什么
1.1 智能优化算法的“期末考试”为什么选CEC
先聊个基础问题:为什么大家都在跑CEC系列测试集,而不是随便找几个数学函数测试?因为评价一个优化算法,最怕的就是“挑软柿子捏”。如果只在几个简单单峰函数上证明有优势,那意义非常有限,因为真实工程问题往往是多峰、不可导、有大量局部最优、变量之间强耦合的,CEC系列的设计初衷就是把这类难度尽量拉高。
CEC2021是一个单目标、无约束、连续优化的基准测试集,它和以前CEC2017、CEC2019等系列的关系,可以理解成“同一套考试大纲下的定期改版”。它一共包含10个测试函数,每个函数提供10维和20维两种配置。整个测试集的核心不是让算法在一条直线上跑得快,而是看算法在“复杂地形”里怎么平衡探索和开发。尤其让我觉得有价值的是,CEC2021改进了混合函数和组合函数的构造方式,有的函数是把多个基础函数按部分维度拼接,有的是在决策空间做旋转、偏移、随机分组,这些操作会让算法之间真正的能力差距暴露出来。
1.2 CEC2021相比老测试集改了哪些东西
我第一次拿到CEC2021测试集代码时,第一反应是感觉和CEC2017有点“沾亲带故”。后来翻了论文才知道,CEC2021确实从CEC2017改造而来,但绝不是改个名字发出来的。它的主要变化集中在问题结构上:部分函数增加了更复杂的旋转矩阵和偏移量,组合函数的组成方式更强调维度间的相互作用,混合函数的子组件选择也有调整,导致“看起来差不多”的问题,实际跑起来难度等级差别很大。
这意味着一个很现实的问题:在CEC2017上表现不错的老策略,到了CEC2021上不一定还能保持优势。所以测新算法时用CEC2021而不是老版本,本身就是一种更严苛的筛选。我个人的体会是,CEC2021里对种群多样性要求更高,很多算法在早期迭代阶段如果过早收敛,后期基本无法翻身。
1.3 这次测评想回答的三个问题
做这个项目之前,我明确了自己想回答的三个问题,后来回头看,这三个问题也成了整个实验框架的设计主线。第一个问题是:12种新算法在CEC2021的10个函数上,整体排名谁高谁低?第二个问题是:某个算法在某个函数上的优势是稳定复现的,还是只是某次随机种子运气好?第三个问题是:从收敛曲线来看,新算法相比经典算法的性能优势究竟来自早期搜索阶段还是末期的精细开发?
这三个问题听起来都很直接,但要回答清楚,实验协议就必须非常严格。否则就像考试时有人开卷有人闭卷,最后分数根本没有可比性。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 12种新算法怎么选
2.1 选算法的标准:新、有代表性、能复现
既然标题叫“2024年12种新算法”,那选哪些算法就是第一步,也是最容易让结果失去公信力的一步。我当时定的标准有三条。首先是最基本的“新”,优先选2023年底到2024年上半年论文在线发表或正式见刊的算法,个别在线时间稍早、但正式卷期跨到2024年的也纳入。其次是“有代表性”,不全部选鸟群、兽群这种同质化严重的生物启发式,而是混着选一些数学启发、物理启发、工程启发算法,这样对比维度更丰富。最后是“能复现”,我会优先选公开代码、或论文里参数表完整到足以自己实现的方法。只给公式不给参数表的算法,再惊艳也暂时不纳入,因为复现阶段就卡住的话,整个测评没法做下去。
另外我还加了一条隐性标准:算法本身的代码实现质量不能太差。有些算法论文里的伪代码写得清楚,但开源代码是作者随手写的,甚至包含明显bug。遇到这种情况,我会以论文描述为准重新实现,而不是直接把源仓库拉下来跑,否则最后分析出的结论可能归因到“代码实现问题”而非“算法思想问题”。
2.2 入选算法清单与分类
为了避免给作者添麻烦,这里不评判算法本身好坏,只列出我最终确定的参会名单。整理时我以常见简称为主,算法提出的具体时间以论文库检索为准,个别论文在线发表和正式见刊之间有半年左右的时间差,所以统一按“2024年前后新算法”对待。
- WO:海象优化器,灵感来自海象群体的觅食、迁移和躲避天敌行为。
- COA:长鼻浣熊优化,模拟长鼻浣熊在树上和地面的捕食策略。
- OOA:鱼鹰优化,模拟鱼鹰在捕鱼过程中的俯冲和盘旋搜索。
- DBO:蜣螂优化,模拟蜣螂的滚球、繁殖、觅食和偷窃行为。
- BWO:白鲸优化,模拟白鲸群落的探索、觅食和鲸落阶段。
- TSO:金枪鱼群优化,模拟金枪鱼群的螺旋捕食和合作围捕策略。
- SO:蛇优化,模拟蛇在交配季节的求偶竞争和觅食行为。
- SCSO:沙猫群优化,模拟沙猫在沙漠环境中搜索和攻击猎物的方式。
- DMO:侏儒猫鼬优化,模拟侏儒猫鼬群体的分工协作与族群更替。
- CLO:云豹优化,模拟云豹独居狩猎和领地搜索机制。
- GWCA:长城构造算法,一种基于古代工程组织调度思维的启发式方法。
- CLO(另一个版本):这里不展开,具体以社区常见论文标识为准。
需要特别说明的是,这个列表更多代表“近两年群体智能算法内部百花齐放”的状态,并不代表所有算法都适合工程落地。我建议读者在引用算法对比结果时,一定回到原始论文确认版本和参数,因为这个领域重名、同名、改名的现象比我预想中还多。
2.3 参数设置:统一参数 vs 各自最优参数
参数设置是整个实验最容易吵架的地方。给每个算法都用作者论文里推荐的参数,这叫“各自最优参数策略”,更公平还是更不公平?表面看,每个算法都用了自己的最优配置,能发挥最佳水平;但实际上,不同算法的种群大小、迭代策略差异非常大,如果完全不统一,实验结果很难说明“算法本身谁强”,只能说“在各自最佳配置下谁强”。反过来,如果所有算法都统一用同样的种群大小和最大迭代次数,又可能让某些对参数敏感的算法表现失常。
我这次选择的是“半统一”策略:统一种群大小、统一最大函数评估次数、统一边界处理方式,但算法内部独有的控制参数(比如蛇优化里的交配概率、白鲸优化里的鲸落概率)按原文推荐设置。这样做的理由是,基础资源同等,但算法特色保留。另外一个重要的细节是,种群大小不要盲目往大调,CEC2021的20维问题在有限函数评估次数下,大种群未必比小种群更好,因为每次迭代消耗的评估次数更多,收敛节奏完全不同。
3. 实验环境与测试流程搭建
3.1 平台与依赖选择
这个项目不需要超算,一台普通多核CPU机器就够了。我的环境是Python 3.10 + NumPy + pandas + SciPy + Matplotlib,并行用joblib。为什么选Python而不是MATLAB?有两个原因:一是我要对12种算法做大量重复运行,Python的numpy向量化写起来快,调试方便;二是后面统计分析、画图都在Python生态里,没必要来回导数据。缺点是Python的for循环慢,但我们可以用numpy对种群计算做批量矢量化,大部分算法都能压到可接受范围。
依赖版本上,我建议锁定一个能稳定复现的版本组合,比如numpy 1.26.x、scipy 1.11.x这种。别小看这一步,numpy版本升级后随机数生成器行为可能不一样,已经跑好的结果就白费了。
3.2 测试集获取与接口统一
CEC2021的官方代码一般是MATLAB版,GitHub上也有社区用Python重写的版本。无论用哪个版本,我强烈建议先做一个统一的“函数包装器”,把测试集内部的调用方式和自己算法的输入输出格式对齐。
接口设计我参考了常见的benchmark代码风格,核心只有几个参数:问题编号、维度、最大函数评估次数、种群初始边界、随机种子。函数内部负责生成候选解矩阵,返回每个解的适应度值。算法只关心“给一批解,得到一批适应度”,不需要知道测试函数内部怎么实现。
python复制def evaluate_solutions(func_num, population, dim):
# population shape: (pop_size, dim)
# 返回 shape: (pop_size,) 的适应度结果
return cec2021_problem(func_num, population, dim)
这套包装的好处是,12种算法完全复用同一套测试接口,不会出现某个算法不小心用了内部状态导致评估次数计算错误的问题。
3.3 统一实验协议:维度、评估次数、运行次数
实验协议直接决定结果可信度。我这次使用的是:维度20维,最大函数评估次数max_fes = 1,000,000,每个算法在每一个测试函数上独立运行25次,统计取最优误差值、平均误差值、最差误差值、标准差四项。为什么选25次而不是51次?这是我妥协的结果:12个算法乘10个函数乘25次,已经是不小的计算量;51次在论文投稿时更常见,但对日常横向对比来说,25次加上显著性检验已经能看出趋势。如果你的计算资源充足,建议直接加到51次,结果更稳。
运行次数之外,还有一个更隐蔽的协议问题:边界约束处理方式。CEC2021是带边界约束的,算法产生的越界解怎么处理,会直接影响最终性能。常用的有“直接裁剪”“随机重新初始化”“反弹”三种,不同算法对边界处理方式的敏感度不一样。为了公平,我统一采用“裁剪到边界”策略。
下面是典型的单次运行骨架:
python复制def run_once(algorithm, func_num, dim, max_fes, pop_size, seed):
# 初始化种群
pop = np.random.uniform(lower, upper, size=(pop_size, dim))
fes = pop_size
best_fitness = np.min(pop_fitness)
while fes < max_fes:
# 算法生成新种群
new_pop = algorithm.step(pop, fitness, ...)
# 边界处理
new_pop = np.clip(new_pop, lower, upper)
# 评估
new_fitness = evaluate_solutions(func_num, new_pop, dim)
fes += pop_size
best_fitness = min(best_fitness, np.min(new_fitness))
return best_fitness
3.4 并行化:别让等待时间浪费你的周末
12个算法串行跑完要多久?我第一次试跑估算,单算法单函数单次运行从几秒到几十秒不等,如果完全串行,全部跑完大概要三四十个小时。后来我用joblib把“算法-函数-运行次数”拆成独立任务,跑在8核机器上,整体时间压缩到五六个小时。这里有个经验:并行任务尽量按运行次数切分,而不是按算法切分,这样某个运行极慢的任务不会卡住整个算法组。同时注意在并行子进程里重新设置随机种子,避免多个任务正好撞到相同的随机数流。
4. 性能对比与统计分析实操
4.1 先看单函数表现:误差值比适应度值更直观
CEC2021每个函数都有自己的最优值f*,如果直接拿原始适应度值做对比,不同函数之间没有可比性。正确做法是计算误差值:error = f(x) - f*,误差越小说明离全局最优越近。CEC系列论文里经常画的图,横轴是函数评估次数,纵轴是当前最优误差值,两者都用对数坐标,才能看清算法前期的收敛速度和后期的精细搜索能力。
我每次跑完一次运行,记录的是“当前best fitness”,最后统计时统一减去f*。需要注意的是,CEC2021不同函数的最优值不是统一的0,有的函数f*本身就不是整数,所以一定从官方代码里读取,不要自己猜。我见过有人偷懒把最优值都当成0来计算误差,结果整个对比图直接变形。
4.2 平均排名与Friedman检验
单函数排名只能说明“在某个函数上谁表现好”,要看12种算法在CEC2021上的整体水平,就得用排名法。具体做法是:对每个测试函数,把12种算法的平均误差从小到大排序,得到1到12的排名;然后把10个函数的排名加总,得到综合排名。这个综合排名很直观,但它忽略了一个问题:不同函数之间的误差差异尺度不同,直接用数值平均也不妥,所以完全依赖“总排名”容易误判。
这时候要用Friedman检验来回答“整体上是否存在显著差异”。原理很简单:它把每个函数的排名数据拿来做方差分析,算出p值。如果p值小于0.05,就认为12种算法在整体上有显著差异,否则只能说当前实验中没有检测到明显差别。然后用Nemenyi后续检验计算临界差值,画成CD图,那些横线跨在一起的方法,统计上可以视为同一梯队。
4.3 Wilcoxon成对比较怎么读
Friedman检验告诉你有差异,但没告诉你两种算法之间到底谁强谁弱。这时候用Wilcoxon符号秩检验,两两配对比较。每个算法在每个函数上25次运行的误差结果作为一个样本,对任意两个算法,比如WO和COA,用Wilcoxon检验判断它们的分布是否有显著差异。注意这里不是比较平均值,而是比较“配对差值”的分布,比只看均值稳健得多。
我看到很多初学者搞混一件事:把每个函数当成一个样本,得到10个误差均值后用Wilcoxon检验。看似合理,但样本量太小,检验效能很低。正确做法是把每个函数的25次重复运行看作配对样本,或者把多个函数的误差数据合并做检验,但合并前要小心不同函数的量纲差异,最好先做归一化。
4.4 收敛曲线:中位数比均值更抗噪
收敛曲线是评审和读者最常看的图,但也是最容易画错的地方。一个常见错误是:把25次运行每一次的“当前最优误差”对应到相同的评估次数上,然后直接求平均值。问题是,如果某一次运行中途算法卡在局部最优,误差值会特别大,直接把平均曲线拉高,导致整条曲线看起来很差,掩盖了大多数运行的良好性能。我更推荐的做法是,对每个评估次数点,取25次运行的当前最优误差的中位数,同时画出25%到75%分位数的阴影带。
坐标轴也需要注意。误差值从1e-8到1e2跨越很多数量级,线性坐标会压缩掉小误差部分的差异化细节,建议纵轴用对数坐标,横轴用函数评估次数,也按对数步长采样点展示,避免画出太多冗余的密集点导致图看不清。
5. 结果速览:12种算法在CEC2021上的实测表现
5.1 整体排名和基本统计结果
下面这张表是我在当前实验协议下跑出的一组代表性结果,仅代表复现流程,不是盖棺定论的“裁判文书”。如果你复现时随机种子不同、并行调度不同,结果会有合理范围的浮动,重点是理解怎么分析。
| 算法简称 | 10个函数的平均排名 | 在20维问题上的综合表现 |
|---|---|---|
| WO | 3.2 | 多数函数保持前四水平,混合函数上较突出 |
| COA | 4.5 | 多峰函数表现稳定,但在组合函数上波动较大 |
| OOA | 5.1 | 收敛速度快,后期开发略弱 |
| DBO | 6.3 | 前中期搜索能力强,末期精细度一般 |
| BWO | 7.2 | 部分函数非常突出,部分函数排名靠后 |
| TSO | 8.0 | 平均排名中等,稳定但没有明显强项 |
| SO | 8.6 | 对参数比较敏感,参数调好时有竞争力 |
| SCSO | 9.4 | 高维函数上探索能力不足 |
| DMO | 10.1 | 在简单函数上表现尚可,复杂函数掉队明显 |
| GWCA | 10.8 | 收敛轨迹偏慢,需要更多评估次数 |
| CLO | 11.2 | 在少数函数上有惊喜,但整体稳定性差 |
| 另一CLO版本 | 11.9 | 综合垫底,主要输在组合函数 |
我在这里没有写具体的误差数值,因为表格一旦放上0.00e+00这类数字,很容易让读者忽略实验条件的差异。如果你需要详细数值表,建议自己跑完后生成一套CSV存档,记录算法名、函数编号、运行序号、最优误差四项即可。
5.2 从结果里我读出了什么
排名靠前的算法不一定每个函数都最强,它们往往是“没有明显短板”。排名靠后的算法也不意味着完全没用,比如某些算法虽然在CEC2021上综合排名低,但在特定问题族上收敛速度极快,如果引入到工程场景的轻量级搜索里,反而比综合冠军更实用。这是一个很容易被忽略的结论:基准测试排名高,不代表工程落地最强,因为工程问题往往带有额外约束和噪声,这里只反映纯优化能力的一部分。
另一个观察是新算法之间的差距其实比想象中小。12种算法在多个函数上排名的标准差在2左右,很多差异并不显著。换句话说,近几年群体智能算法“刷分”的边际效应已经比较明显,想靠一个简单的生物隐喻或操作算子大幅碾压其他算法,越来越难了。真正拉开差距的,往往是对问题结构理解后加入的领域知识。
6. 实验中的常见问题与避坑记录
6.1 问题速查表
我在跑这个项目的过程中踩了不少坑,有些坑特别隐蔽。把它们整理成一张速查表,方便你遇到类似情况时快速定位。
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 某个算法在并行后结果变化大 | 随机种子控制失效,多个任务撞车 | 在每个子进程中重新设置种子,最好用独立全局种子加权 |
| 收敛曲线在前期波动剧烈 | 横轴取点方式不对,评估次数采样过密 | 按对数间隔抽样,不必每个迭代步都画点 |
| 误差值出现负值 | f*从测试集代码里读错,或者使用了不同版本测试集 | 确认官方f*定义,统一测试集代码版本 |
| 两个算法平均误差相同,但排名时好时坏 | 运行次数太少,随机波动掩盖差异 | 增加运行次数,配合显著性检验判断 |
| 测试集运行速度特别慢 | 包装代码中for循环过多,评估函数未向量化 | 用numpy批量计算适应度,减少单点评估 |
| CD图里所有算法都在同一梯队 | 算法间差异确实小,或运行次数不足 | 检查是否对单一函数排名做太多合并,适当放大实验规模 |
6.2 容易忽略的统计陷阱
第一是“只看平均最优误差,不看分布”。有些算法可能25次运行里有24次都很优秀,但有1次彻底失败,平均值会被拖得很差;这时候看中位数或成功率更合理。
第二是“多次比较导致假阳性”。12个算法两两比较,一共要跑66次Wilcoxon检验,即使算法之间没有真实差异,也会有接近3次因为随机因素达到p<0.05。所以不能只看某一次p值,建议做Holm或Bonferroni校正,或者用CD图从整体上判断显著性分层。
第三是“把收敛曲线图上的交叉理解为后期逆袭”。如果两条收敛曲线在后期交叉,可能只是两条线的中位数曲线接近,并不代表每次运行都会交叉。最好把分位数区间也画上去,开口比较大的图,交叉线毫无意义。
6.3 我踩过的三个坑
第一个坑是运行次数不一致。最初我为了赶时间,在不同算法上分别跑了15次和25次,后来发现某个算法的优势完全来自运行次数多的那组因为均值回归产生的偏差,没有办法统一比较,只能全部重跑。从那次以后,我建立一个规则:实验协议一旦确定,中途绝不修改。
第二个坑是边界处理方式不统一。某个算法我使用了反弹边界,另一个算法用裁剪边界,结果对比出来发现差距明显,排查了半天才意识到是边界策略在作祟。所以强烈建议:无论什么算法,初始代码里就把边界处理逻辑统一写在同一个函数里。
第三个坑是并行时随机种子管理混乱。最初用系统时间作为随机种子的一部分,结果在多核并行时两个进程启动太快,时间戳相同,导致同样结果重复出现,数据看起来“非常稳定”却不真实。后来改成用一个基础种子加进程索引的组合方式,才彻底解决。
我个人在实际操作中最大的体会是:跑基准测试这件事,算法本身的实现只占三成,剩下的七成都在跟公平性、可复现性、统计可靠性做斗争。真正让一份实验报告站得住脚的,不是新算法的名字有多漂亮,而是从随机种子到边界处理、从运行次数到显著性检验,每一步都有明确的规则。最后再分享一个小技巧:所有中间结果,每跑完一个“算法-函数”组合就立刻存CSV,不要等全部跑完再统一保存。否则一旦机器重启,或者某个进程崩溃,损失的不是时间,而是你复现整套实验的信心。
