提到“蜣螂优化算法”这个名字,总是有人先笑两声——屎壳郎也能做优化?但它确实是正经发表在学术期刊上的群体智能优化算法,英文全称Dung Beetle Optimizer,简称DBO,2022年底提出之后很快被应用到路径规划、参数整定、资源调度一类工程问题里。IDBO是它的改进版本,全称Improved DBO,针对的就是DBO在实战里暴露出来的收敛慢、容易早熟、高维精度不足这些毛病。这篇文章不摆姿态,直接拆开讲:DBO本身是怎么工作的,它到底弱在哪儿,IDBO通常从哪几个维度下手改进,每个改进背后的计算逻辑是什么,以及我实际复现时踩过的坑。
如果你正准备拿DBO或者IDBO做实验,或者被导师、同事安排去“改进一个群体智能算法”,这篇内容应该能帮你省掉不少试错时间。我不打算复述论文里的原文公式,而是站在“怎么把指标跑上去”的角度,把思路和做法讲清楚。
1. 先看懂DBO的底子:它做什么、卡在哪
1.1 五种“蜣螂行为”其实是五类搜索动作
DBO的设计灵感来自蜣螂的日常,但落到算法层面,它就是把生物行为翻译成了搜索策略。滚球行为负责大范围探索,蜣螂遇到障碍物会停下来“跳舞”,相当于重新规划方向,对应的是算法跳出局部区域的能力。繁殖行为是在已有粪球附近产卵,幼虫在局部孵化,这对应的是在较优位置附近做精细挖掘。觅食行为是围绕当前食物源小范围搜索,相当于局部开发。偷窃行为模拟部分蜣螂去抢别人的粪球,对应的是种群内部的信息共享和快速向优势个体靠拢。
你可以把这五种行为理解成一个分工明确的小团队:有人跑远看地图,有人留在原地深耕,有人专门盯着别人的成果去蹭。团队配合得好,算法就能在“探索”和“开发”之间维持平衡;配合不好,就会掉进局部最优或者迟迟不收敛。
DBO把五种行为通过一堆概率参数和阈值组织起来,每次迭代时根据每个个体的角色执行不同更新策略。听起来很完整,但真正跑起来之后,问题恰恰出在这些参数和行为的配合上。
1.2 DBO的四个明显短板,就是IDBO要改的靶点
我最早复现DBO是在一组CEC基准函数上,测试结果能看出来它比传统PSO、差分进化有竞争力,但稳定性一般。用了一段时间之后,我发现它的短板基本集中在四个方面。
第一是参数敏感。DBO中的行为切换参数、偷窃概率、缩放系数都需要人工设置,换一个测试函数或者换一个工程问题,最优参数区间往往就变了。参数敏感意味着可复现性差,同一套参数在这边表现很好,在那边可能效果崩掉。
第二是多样性维持能力弱。默认的随机初始化容易让种群扎堆在搜索空间的局部区域,遇到多峰函数时,前期多样性不足会直接导致早熟。高维问题上这个问题更明显,维度上去之后,随机分布的点在超立方体里非常稀疏,但彼此之间的距离又容易集中在同一量级,探索效率很差。
第三是后期收敛精度不足。DBO的开发能力不算差,但如果局部开发算子的步长不随迭代缩小,后期会出现最优解附近反复震荡。有些测试函数上,前100代下降很快,后400代基本不动,精度卡在10^-3级别上不去。
第四是边界处理粗糙。很多DBO实现里,越界个体直接取边界值或者随机弹回,这种做法会破坏已经搜索到的有效信息。特别是在约束优化里,边界附近往往是可行域边缘,处理不当会让算法在边界上来回折腾。
这四个短板并不是DBO独有的,几乎所有群体智能算法都存在类似问题。IDBO做的事情就是针对这些短板,逐个叠加改进策略。接下来我把常用改进手段逐个拆开看。
2. IDBO改进的多维度路径:从初始化到参数自适应
2.1 种群初始化:混沌映射与反向学习怎么组合
先聊初始化,因为这一块对最终结果的影响比很多人想的大。随机初始化是群体智能算法的默认选项,但它有个毛病:随机数发生器产生的序列并不保证在搜索空间里均匀分布。维度越高,种群越容易在局部堆积,前期多样性差,后面怎么救都费劲。
比较常用的替代方案是混沌映射初始化。混沌序列有着“看似随机、实际遍历”的特性,可以在一段区间内比较均匀地分布。常见的形式是Tent映射:x(t+1) = 1 - |2x(t) - 1|。把生成序列从区间[0,1]映射到搜索空间[lb, ub],就得到一批分散度更好的初始个体。需要注意Tent映射初值最好不要选0.5,因为会落入不动点,序列不再变化,实际效果和随机初始化没区别。我一般取0.1到0.9之间的一个值,或者用当前时间戳作为随机种子再归一化一次。
光有混沌还不够,另一个很常见的增强手段是准反向学习,代码实现里常见缩写QOBL。它的逻辑很简单:对每个混沌初始解X,构造一个反向解X' = lb + ub - X,把原始种群和反向种群合在一起,按适应度从中选出最好的N个作为初始种群。这样做的价值在于,个体和它的反向个体中至少有一个大概率离全局最优更近,选优之后相当于给算法一个更好的起跑线。代价是初始化阶段多做一次适应度评估,但换来的多样性提升非常划算。我自己做改进实验时,混沌加反向学习基本是固定组合。
2.2 全局探索:Levy飞行和透镜反向扰动的取舍
初始化解决的是起点问题,迭代过程中的全局探索能力则是另一个关键点。DBO的探索主要依赖滚球和偷窃行为,但这两者的随机性有限,遇到复杂多峰函数时还是容易早熟。于是IDBO里常见的一类改进是引入额外的扰动机制,比较典型的有Levy飞行和透镜成像反向学习。
Levy飞行是一种重尾随机游走,简单说就是大多数时候小步移动,偶尔来一次大步长跳跃。它模仿的是许多动物在觅食时“局部搜索+偶尔远距离搬家”的模式。把这个机制接入DBO,可以让个体在陷入局部区域时有概率跳出来,等于给探索行为加了一个“长距离侦察兵”。但Levy飞行的步长尺度需要控制,扰动系数设太大会导致算法退化成一通乱跳,搜索变成随机游走;设太小则起不到跳出的作用。我通常把扰动系数设置在搜索空间范围的1%到5%之间,再按迭代次数逐渐缩小。
透镜成像反向学习是另一种思路。它借鉴光学透镜成像原理,对当前最优个体做一次“反向”扰动,生成一个与原解关于当前区域中心对称的候选解。这个解和原最优解存在明显空间差异,但又不是完全随机,所以能同时提供开发性和探索性。实际操作中,我会每隔10代或者20代对全局最优解执行一次透镜反向扰动,生成候选点后与原来最优解比优,只保留好的那一个。触发间隔非常关键——如果每一代都做,最优解不断被改变方向,收敛曲线会持续震荡;如果间隔太长,又起不到探索作用。
这两个机制怎么选?我的习惯是:Levy飞行更适合加在普通个体身上,用来增加种群整体多样性;透镜成像反向学习更适合作用在当前最优解上,用来改善解的精度。两者可以共存,但要控制频率,否则计算量翻倍、收益却不明显。
2.3 局部开发:自适应权重与动态边界处理
探索增强之后,局部开发也得跟上。DBO的局部开发行为主要围绕最优个体周围做小范围搜索,但这种搜索的步长通常是固定的。固定步长的问题在于,迭代前期收入不足,后期又显得太大导致精度上不去。解决办法是给更新公式加一个随迭代递减的自适应权重w。
常见的做法是:w从0.9左右开始,随迭代进程非线性下降到0.2左右。公式可以写成w(t) = w_min + (w_max - w_min) * (1 - t/T)^2。把这个权重乘到位置更新的缩放项上,起到的效果是前期步长大、全局搜索充分,后期步长小、局部精修细致。生活化类比就是:先大步流星跑遍整个街区,确认哪块最有可能后,再用小碎步在附近细细找。权重下降曲线越陡,算法越早进入局部搜索;曲线越平,算法更愿意保持探索。我一般取平方衰减曲线,因为它前期衰减不会太快,后期又能压到比较低。
边界处理也是容易被忽视的细节。早期我实现DBO时用的是“触碰边界就固定”的黏滞策略,结果大量个体挤在边界上,Z字形收敛。后来换成反射策略,也就是越界个体像乒乓球撞墙一样反弹回搜索空间内,情况才明显改善。反射策略的好处是个体在边界附近的信息不会被丢弃,还能沿着反弹方向继续搜索。另一种方案是动态边界收缩,即后期允许算法在可行域的一个缩小子区域内精搜,但这种策略只适合确信全局最优不在边界附近的问题,否则会引入误差。
2.4 参数自适应:让偷窃概率和权重自己调节
DBO里的关键参数基本是固定值,比如偷窃行为触发的概率P、滚球行为跳舞的阈值。固定参数最大的问题是没办法适配不同阶段的搜索需求。迭代前期,我们希望种群多探索;迭代后期,我们希望种群多开发。一个固定概率不可能同时满足这两个要求。
IDBO里常见的做法是让参数随迭代或随种群状态动态变化。最朴素的是线性或非线性递减:偷窃概率P从0.6线性降到0.1,保证前期有充足探索、后期减少盲目偷抢。更聪明一点的做法是引入种群分散度反馈。种群分散度可以粗略计算为所有个体到种群中心点的平均欧氏距离:距离大说明种群分散,还在探索期;距离小说明种群聚集,可能收敛或早熟。把这个分散度归一化到0到1之间,再去调P:分散度高时提高偷窃概率,让更多个体向优势区域靠拢;分散度低时降低概率,防止种群过度聚集。
权重w也可以这样自适应调整。比如用“成功率”作为反馈信号:如果最近几次迭代中全局最优被更新的次数比较多,说明当前收缩方向是对的,可以适当加大开发权重;如果多次更新失败,说明可能陷入局部,反过来增加扰动强度。这类反馈机制实现不复杂,核心是额外计算量很小,但能明显改善算法在不同阶段的行为切换。实际实验里,自适应参数比手工调参要稳得多,尤其是跨不同测试函数时。
2.5 阶段化组合:不同改进不是一锅炖
前面说的这些改进策略,如果在同一份代码里全部加上去,结果不一定会更好。我见过很多改进论文的做法是“混沌初始化+Levy+反向学习+自适应权重+动态边界”全堆上去,性能确实比原始DBO好,但没人说得清到底是哪个模块在起作用,而且运行时间翻了不止一倍。
我更倾向于按迭代阶段来组合。前20%的迭代,重点靠混沌初始化和Levy飞行增加探索多样性,这个阶段不要频繁做最优解扰动,否则还没找到靠谱的区域就一直在跳。中间40%到70%,探索和开发并行,自适应权重开始生效,可以用透镜反向扰动来精修已有的最优解。最后20%到30%,基本进入开发期,这时候应当减少大步长扰动,把重心放在局部精搜上,可以把自适应权重降到最低并配合反射式边界处理。
阶段化组合的核心理念是“不同阶段解决不同问题”。前期保证找到正确的盆地,中期快速逼近盆底,后期拿到足够高的精度。如果所有机制都均匀地作用于整个迭代过程,那么前期的探索可能被过度开发压制,后期又可能被随机扰动干扰,反而两头不讨好。这也是我在实践中最重要的一条经验:改进策略要讲究节奏,不是把所有好东西堆在一起就万事大吉。
3. 一个可复现的IDBO变体:配置清单、伪代码与参数整定
3.1 改进模块清单与设计动机
为了让前面这些思路落到实处,我先给出一个具体的IDBO变体配置。这个配置并不是标准答案,而是一套经过我多轮测试、效果比较稳定的基线设置,你可以照着复现,再在这个基础上改动。
| 改进模块 | 作用 | 对应DBO短板 |
|---|---|---|
| Tent混沌映射初始化 | 提升初始种群均匀性和多样性 | 初始化随机,多样性弱 |
| 准反向学习(QOBL)选优 | 用反向个体和原始个体竞争,提高起跑质量 | 初始化随机,多样性弱 |
| Levy飞行扰动 | 增加全局探索能力,跳出局部区域 | 探索能力不足,易早熟 |
| 自适应权重w | 控制位置更新步长,前后期行为平滑切换 | 后期收敛精度不足 |
| 透镜成像反向学习 | 对全局最优解做周期性精修 | 后期收敛精度不足 |
| 动态偷窃概率P | 按迭代进度调整探索与开发比例 | 参数固定,无法自适应 |
| 反射式边界处理 | 保留越界个体信息,避免边界堆积 | 边界处理粗糙 |
这套配置的总体思路是:每个短板至少对应一个修复手段,但修复手段之间不是均匀发力,而是按照迭代阶段动态调整强度。实际运行中,Levy扰动只在前期和中期开启,透镜反向学习从中期开始周期性触发,后期则主要依赖自适应权重做精细搜索。整体计算代价比原始DBO大约多20%到30%,但收敛速度和最终精度有明显改善。
3.2 核心流程伪代码
下边这段伪代码是我在复现时用的核心流程,去掉了细节公式,保留关键逻辑。真正实现时,需要把滚球、繁殖、觅食、偷窃这四个动作的具体位置更新公式按原始DBO实现补齐。
# 参数设置 N = 30 # 种群规模 T = 500 # 最大迭代次数 dim = 30 # 问题维度 lb, ub = [-100, 100] # 搜索边界 # 1. 初始化:Tent混沌映射 + 准反向学习选优 pop = tent_chaos_init(N, dim, lb, ub) pop_rev = reverse_pop(pop, lb, ub) pop = select_best(concat(pop, pop_rev), N) # 2. 主循环 for t in range(T): # 动态参数 w = 0.2 + (0.9 - 0.2) * (1 - t / T) ** 2 P = 0.6 - (0.6 - 0.1) * (t / T) for i in range(N): if role[i] == ROLLER: # 滚球行为,更新时乘上自适应权重w new_pos[i] = dbo_roller_update(pop[i], w, lb, ub) elif role[i] == BREED: # 繁殖行为,在局部区域产生新个体 new_pos[i] = dbo_breed_update(pop[i], pop_best) elif role[i] == FORAGE: # 觅食行为,在最优附近精细搜索 new_pos[i] = dbo_forage_update(pop[i], pop_best, w) elif role[i] == STEAL: # 偷窃行为,以概率P向最优个体靠拢 if random() < P: new_pos[i] = pop_best + levy_flight(dim) * 0.05 # 反射式边界处理,替代默认的黏滞边界 new_pos = reflect_boundary(new_pos, lb, ub) # 透镜成像反向学习,每10代触发一次,只对全局最优做精修 if t % 10 == 0: cand = lens_opposition(pop_best, lb, ub) if fitness(cand) < fitness(pop_best): pop_best = cand # 更新种群、角色分配、全局最优 pop = update_pop(pop, new_pos) reassign_roles(pop) pop_best = update_best(pop)这个伪代码里,Levy飞行的触发是放在偷窃行为里的,通过概率P控制。注意P是动态减小的,所以前期的偷窃行为更活跃、更利于探索,后期更多个体转入局部开发。
3.3 初始参数怎么定:我的默认值
初始参数的设定对复现结果影响很大,我这里给出一个相对通用的默认值组合,并解释为什么这么取。
种群规模N,一般取30到50。N翻倍意味着每轮迭代评估次数翻倍,但收敛精度并不会成倍提升。在30维问题上,N=30已经足够;100维以上可以适当增加到50或更多。最大迭代T,基准测试常用500到1000,工程问题里如果评估一次代价高,可以把T降到100到200,配合更少的种群数,但效果通常要降一个档次。
自适应权重w的范围,我常用[0.2, 0.9]。w_min不能太低,否则后期步长过小,个体几乎停止移动,容易卡在局部点;w_max也不宜超过1,否则前期更新幅度太大,容易来回震荡。偷窃概率P的范围,我常用[0.1, 0.6]。P_max越高,前期探索越强,但过高会让大量个体一直往最优解靠拢,反而丢失多样性。透镜反向学习的触发间隔,我推荐10到20代。间隔太小时,最优解反复被扰动,收敛曲线会出现锯齿;间隔太大,精修作用就不明显。
这些默认值不是拍脑袋来的,我是用一组CEC函数做网格搜索对比后确定的,逻辑是找一个在多数测试函数上都过得去的折中点。做工程应用时,不必迷信这些值,但可以用它们作为起点,再根据具体问题的规模和目标函数形态微调。
4. 从基准测试到工程落地:怎么验证、怎么用
4.1 基准测试的正确打开方式
很多新手改进算法之后,直接跑一遍,画一条收敛曲线,就说“你看我改进的算法更好”。但这种验证方式说服力非常低。群体智能算法是随机算法,单次运行结果有很强的偶然性,必须用统计方法验证。
我的标准流程是这样的:挑选CEC2017或者CEC2022基准函数集,包含单峰、多峰、混合、组合等不同类型;每个算法在每个函数上独立运行30次,记录每次的最终适应度;汇总后计算均值、标准差、最优值、中位数。光看均值不够,因为一次极端的好结果会把均值拉高。标准差的含义是稳定性:标准差小,说明算法在不同随机种子下表现一致;标准差大,说明结果波动剧烈、不可靠。
接着做显著性检验。优化算法性能数据通常不是正态分布,所以不要直接用t检验,更稳妥的是Wilcoxon符号秩检验。把所有算法两两比较,计算p值,看差异是否显著。这样做比单纯比均值说服力强得多。我自己习惯把结果整理成下面这种表格格式:
| 算法 | F1均值 | F1标准差 | F5均值 | F5标准差 | Wilcoxon p值(与DBO比) |
|---|---|---|---|---|---|
| DBO | 1.23e-5 | 2.10e-6 | 2.31e+2 | 1.34e+1 | - |
| IDBO变体 | 8.47e-7 | 3.05e-8 | 1.72e+2 | 8.92e+0 | 0.003 |
注意表格里的数字只是示意格式,不要当作普遍结论。但其中有两个关键点是通用的:第一,改完算法之后一定要跑消融实验,分别测“只加混沌初始化”“只加自适应权重”“全部叠加”这几种配置,才知道哪个模块在起作用。第二,对比收敛曲线时取30次运行的中位数曲线,而不是均值曲线,避免少数极端运行把曲线拉变形。
4.2 三个典型工程场景的落地方案
基准测试通过之后,算法最终要落到工程问题里。我挑三个最常见的应用场景讲一下怎么套用IDBO。
第一个是无线传感器网络覆盖优化。传感器节点部署通常要把节点坐标作为决策变量,目标函数是最大化覆盖面积和连通性。决策变量维度是节点数的两倍,比如50个节点就是100维,属于中高维优化。这种问题容易陷入局部覆盖空洞,用混沌初始化加Levy飞行的IDBO变体效果比较明显。关键是目标函数里要加连通性惩罚项,否则算法可能在物理上把节点堆在一起换高覆盖率,实际上不连通。
第二个是PID参数整定。这其实是低维问题,只有Kp、Ki、Kd三个决策变量。DBO本身就能处理,但加上约束后问题来了:PID参数必须保证系统稳定,否则目标函数值会爆炸。我在实际使用时会把IDBO的搜索空间限制在经验参数区间内,同时把不稳定情况的目标函数设为一个极大的惩罚值。透镜反向学习和自适应权重在低维问题上优势不明显,这时候不要过度设计,用简单的DBO核心配合动态概率就足够。
第三个是特征选择。特征选择是一个高维离散优化问题,决策变量对应每个特征选或不选。IDBO是连续优化算法,直接用它解决离散问题需要做一个转换,常见做法是用S型传递函数把连续值映射成0或1。目标函数通常是分类器错误率加上特征数量的权重惩罚。这类问题中高维特性明显,改进算法的多样性增强对最终效果帮助很大,但要注意评估目标函数时分类器的计算成本,如果特征维度几千甚至上百万,每次评估都跑一次分类器代价极高,需要预先做特征筛选或者用代理模型。
4.3 复盘:我踩过的几个坑
我自己踩过几个比较典型的坑,写出来希望你能绕开。
第一个坑是盲目堆算子。有一版IDBO我同时加了混沌初始化、Levy飞行、透镜反向学习、自适应权重、动态概率、边界收缩六个模块,结果是运行时间比DBO多了近一倍,精度却没提升多少。后来做消融实验发现,边界收缩在这个问题上反而是负优化。做改进算法,每加一个模块都必须能说清楚它对应哪个短板,并且用消融实验证明它确实有效。
第二个坑是透镜反向学习的触发频率。一开始我以为越频繁越好,每代都对最优解做反向扰动,结果收敛曲线像锯齿一样。后来改成每10代一次,效果立刻稳定了。原因是反向扰动本质上是把当前最优解推向另一个可能更优的位置,频繁执行等于不断打断局部收敛。
第三个坑是混沌映射的瞬态效应。直接用随机初值跑Tent映射,生成的序列前几十个点可能并不理想。后来我先生成500个混沌点,丢掉前200个,再用后面的点做初始化,效果才有明显提升。这个细节很多文章里根本不会提。
第四个坑是边界处理方式的选择。我之前用“越界就设回边界值”的做法,导致大量个体堆在搜索边界上,算法搜索效率大幅下降。换成反射式边界处理后,越界个体重新回到搜索空间内部继续搜索,问题解决。
第五个坑是复核原始DBO的实现细节。网上流传的DBO源码版本很多,有些对原始论文理解有偏差。我踩过一次角色分配逻辑写颠倒的坑,导致改进前后的对比结果完全失真。所以拿到一段DBO代码后,先对照原始论文逐行核对自己的实现,再谈改进。
5. 常见问题速查与下一步还能做什么
5.1 高频问题与排查建议速查表
在前面这些实验和工程实践中,我整理了一份高频问题速查表,每次调试算法时都会对照着排查。
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 前期迅速陷入局部最优 | 初始化多样性不足 | 检查混沌初始化初值,加入准反向学习选优 |
| 后期收敛精度卡住,曲线平坦 | 开发步长过大或自适应权重失效 | 把w_min调低,检查权重是否随迭代正常衰减 |
| 改进后运行时间明显增加 | 额外算子触发太频繁 | 增加触发间隔,比如透镜反向学习从5代改成10代以上 |
| 高维问题表现反而更差 | 维度增加导致距离度量和边界处理失效 | 改用反射边界,增加Levy飞行扰动强度,适当提升种群数 |
| 多次运行结果差异悬殊 | 随机性太强或者评估次数太少 | 固定随机种子做调试,最终验证时跑30次取统计结果 |
| 种群大量聚集在边界 | 边界处理方式不当 | 黏滞边界改为反射边界,检查是否有越界后强制赋值的代码 |
| 改进效果只在某些函数上明显 | 算子风格与问题特征不匹配 | 不要追求全函数提升,记录在哪些函数上有效,分析原因 |
这张表其实也说明了改进算法的一个现实规律:没有一套配置能在所有测试函数上都赢。真实的工作场景里,更重要的是快速定位算法在哪个环节出了问题,而不是纠结于某个函数上差了几个数量级。
5.2 后续可以继续尝试的方向
这套IDBO方案离“完美”还有距离,我自己也在持续探索几个方向。
第一个是混合局部搜索。DBO和IDBO这类群体智能算法,在迭代后期收敛速度会放缓,即使加了自适应权重,局部的精细搜索能力还是不如传统的模式搜索或者Nelder-Mead方法。可以考虑在迭代后20%的阶段,每隔一定代数对当前最优解执行一次局部搜索算子,把群体搜索和局部精修结合起来。这种混合方式在工程问题里效果提升通常很直接,但要注意局部搜索次数控制,以免评估成本过高。
第二个是多策略自适应选择。目前阶段划分是人为设定的,但不同问题的最优节奏可能不同。可以做一个成功率反馈机制,每个改进算子记录自己最近若干次“有效更新”的比例,比例高的算子获得更多使用机会,比例低的被逐渐淘汰。这样算法能在运行过程中自动调整策略组合,但实现复杂度会上升,如果不小心容易出现算子竞争导致的性能振荡。
第三个是针对约束问题的修复算子。很多真实工程问题带有强约束,简单用惩罚函数处理可行域边界效果差。可以考虑设计专门的约束修复机制,比如对越界个体做投影,把不可行解修正到最近的可行区域,再参与评估。这类修复算子一旦建立好,IDBO在带约束优化中的实用性会有质变。
关于IDBO,我最后说一点私人心得。这几年反复调各种群体智能算法,最大的体会是:改进算法更像做实验,而不是写诗。每加一个算子,你要能说清楚它对应哪个短板,并且跑一组消融实验验证它的贡献,否则就是在给收敛曲线叠buff。IDBO的价值并不在于某一个算子有多惊艳,而是把这些模块按照正确的节奏组合起来,让DBO原本的短板逐步被补上。
如果你准备动手复现,一个小建议是:把原始DBO的代码单独留一份,永远不要删。每次改进完,都用同一组测试函数、同一批随机种子跑一遍对比,你才能知道改动到底是在帮忙,还是在拖后腿。算法实验最怕的不是失败,而是搞不清楚哪个改动起了作用,最后只能面对一堆“看起来还行但是说不清为什么”的结果。