)
做优化算法的人这两年应该没少被各种新型群智能算法刷屏。蜣螂优化算法Dung Beetle Optimizer简称DBO是其中辨识度比较高的一位它的灵感来自蜣螂滚粪球时依靠天体导航、下坡跳舞、繁殖育雏、觅食和偷窃等一整套行为在搜索阶段确实有很强的探索欲望。不过我把标准版本放到Matlab里跑了几圈问题很快就露出来了前期收敛很快后期容易在原地打转放到多峰函数和高维问题上表现尤其不稳。所以我做了一个探索型项目改进的蜣螂优化算法目标很简单——在Matlab里把它改得更稳定、更好用而不是堆砌一堆看似高深的技巧。这篇文章会从算法原理、改进策略、完整实现到调参避坑都过一遍给出关键代码和实验方法。适合已经在用DBO、觉得它收敛精度不够或者想独立设计算法改进方案的朋友也适合那些刚接触智能优化算法、想找一个能上手的工程案例的读者。下面直接进入正题。1. 整体设计与思路拆解1.1 标准DBO的仿生机制与核心方程DBO能引起关注很大程度是因为它不像粒子群、灰狼那样只有一套固定搜索模式而是把一个种群拆成四种角色负责滚球的蜣螂、进入繁殖球的繁殖蜣螂、寻找食物的觅食蜣螂以及跟着最优个体捡便宜的偷窃蜣螂。每种角色对应不同的位置更新逻辑相当于在全局搜索和局部开发之间做了一个角色分工。滚球阶段模拟蜣螂推动粪球前进位置更新可以简化为x(t1) x(t) alpha * k * x(t-1) b * delta_x其中alpha用来区分是否遇到障碍k是偏转系数b是常数系数delta_x和环境因素相关。当蜣螂遇到障碍需要重新定向时会通过跳舞机制来转方向利用正切函数模拟转向x(t1) x(t) tan(theta) * |x(t) - x(t-1)|繁殖阶段和觅食阶段会利用当前种群中较优的信息把部分个体向一个动态缩减的区间靠拢偷窃阶段的个体则会向整个种群的全局最优位置聚集。这四类行为组合起来让算法在早期拥有较强的全局探索能力在后期通过收缩搜索区间和向最优个体学习来提升收敛速度。1.2 标准DBO的短板在哪里我最初用标准DBO测试了Sphere、Rastrigin、Rosenbrock这些经典基准函数总结下来有三个让人头疼的问题。第一个问题是初始种群的分布太随机。Matlab默认的rand函数生成的是均匀随机数但均匀随机不代表均匀覆盖尤其在30维以上的搜索空间里随机初始化的个体很容易扎堆在某些区域导致初始阶段就漏掉大片潜在最优区域。第二个问题更明显后期收敛精度不够。标准DBO在单峰函数上表现还行但遇到Rastrigin这类多峰函数收敛曲线经常在迭代到一半时就变得平坦种群被某个局部最优吸引很难再跳出来。偷窃个体虽然名义上是向全局最优学习但当全局最优只是局部最优时这种机制反而加速了种群的同质化。第三个问题是对参数比较敏感。alpha、k、b这些参数对步长影响很大如果按默认值不动换一个目标函数可能结果就完全无法接受。参数敏感度高意味着在新问题上的概率要花更多时间调参这在工程上是很大的隐形开销。1.3 为什么按这条路线来改进做算法改进最容易犯的毛病就是听说哪个策略好用就全塞进去。混沌初始化、Levy飞行、差分进化、小生境、局部搜索一股脑混在一起最后谁也不知道改善是好策略的功劳还是坏策略的运气。所以我在设计改进方案时先把标准DBO的瓶颈定位清楚初始覆盖差、全局与局部失衡、种群多样性丢失。针对这三个瓶颈只选了三类低风险策略用Tent混沌映射替代随机初始化解决初始覆盖差的问题在滚球阶段引入Levy飞行和自适应权重解决探索与开发失衡的问题引入差分进化的变异交叉算子只作用于部分个体防止种群过早同质化。同时补上一个边界反弹处理避免个体堆积在搜索区域的边缘。整体思路是克制地改进每一步优化都有明确目标而不是在做算法竞赛拼盘。2. 改进方案与核心细节解析2.1 混沌映射初始化用更均匀的起点铺路标准DBO每个个体独立随机生成初始位置虽然理论上也能覆盖搜索空间但点数少的时候分布并不均匀。要做到在有限种群数下覆盖范围尽可能广混沌映射是代价很低的办法。我选的是Tent帐篷映射。它有两个优点均匀性和遍历性都比Logistic映射更容易控制且实现复杂度和rand差不多。Tent映射的迭代表达式如下x(n1) 2*x(n) 当 0 x(n) 0.5 x(n1) 2*(1 - x(n)) 当 0.5 x(n) 1在Matlab中生成混沌初始化种群的参考代码如下function X chaoticInit(N, dim, lb, ub) X zeros(N, dim); X(1, :) rand(1, dim); for j 1:dim for i 2:N if X(i-1, j) 0.5 X(i, j) 2 * X(i-1, j); else X(i, j) 2 * (1 - X(i-1, j)); end end end % 等比映射到搜索空间 for j 1:dim X(:, j) lb(j) X(:, j) * (ub(j) - lb(j)); end end这里有一个容易被忽略的细节一定要让每个维度使用不同的初始混沌值否则所有维度的序列会完全相同个体只会在一条对角线上分布。我推荐用rand(1, dim)给每个维度一个独立的混沌起点这样生成的N个个体才能真正铺开。2.2 Levy飞行与自适应权重让搜索步长会跳跃标准DBO的滚球步长主要由系数k控制虽然能保证一定程度的移动但遇到复杂多峰地形时普通小步长很难跨过沟壑。我在滚球阶段加了一个随机扰动源Levy飞行。Levy飞行是一种重尾分布随机游走它的特点是大部分时间走小步长但偶尔会产生一个长距离跳跃。这个偶尔跳一下的特性正好能把蜣螂从局部最优的坑里踢出来同时还保留局部精细搜索的能力。数学上Levy步长的生成可以用Mantegna方法实现function L levyStep(dim, beta) sigma (gamma(1beta) * sin(pi*beta/2) / ... (gamma((1beta)/2) * beta * 2^((beta-1)/2)))^(1/beta); u randn(dim, 1) * sigma; v randn(dim, 1); L u ./ (abs(v).^(1/beta)); endbeta通常取1.5它在Levy分布中控制步长分布的厚尾程度。beta越小长尾跳跃越容易出现随机性越强beta越大行为越接近高斯分布。直接在原始位置更新加入Levy飞行可能破坏收敛节奏所以我又配了一个自适应权重w。w随迭代次数从0.9线性衰减到0.1让算法前期敢大步探索后期逐步收敛到精细开发w 0.9 - (0.9 - 0.1) * (t / MaxIter); newX oldX w * levyStep(dim, beta) .* (globalBest - oldX);这样Levy飞行就不会从头到尾乱跳到接近最优解时它会变成近似小步长扰动不影响最终精度。2.3 差分进化思想的杂交算子为种群多样性兜底混沌初始化和Levy飞行主要负责提升探索能力但种群一旦被某个局部最优个体吸引更新公式很难让其他个体保持差异。这时候需要一个显式的多样性修复机制。我参考了差分进化的核心思想DE/rand/1在每轮主更新结束后只对种群中最差的一部分个体做变异和交叉。变异公式是V X_r1 F * (X_r2 - X_r3)其中r1、r2、r3是从当前种群中随机选择的三个不同个体F是变异缩放因子。交叉时对每个维度生成随机数rand_j如果rand_j小于交叉概率CR则新个体在该维度取V的值否则保留原个体。最终比较新个体和旧个体的适应度优者进入下一代。关键代码片段如下% 只对最差的20%个体执行 worstIdx population(1:round(N*0.2), :); for idx 1:size(worstIdx,1) r randperm(N, 3); V population(r(1),:) F * (population(r(2),:) - population(r(3),:)); mask rand(1, dim) CR; U worstIdx(idx,:); U(mask) V(mask); if objective(U) objective(worstInt(idx,:)) worstInt(idx,:) U; end end这里的关键是只对最差个体操作。如果让所有个体都参与差分进化就会打乱DBO原本的角色分工变成DBO和DE的混合体反而削弱蜣螂行为带来的结构优势。而只处理最差的群体相当于给种群兜底不影响主流搜索方向。2.4 边界处理与种群修复准则边界处理是个容易被忽视但影响很大的细节。标准做法是把越界的位置直接拉回上下界但这样做有一个隐藏问题大量个体在边界上堆积搜索空间边缘区域的局部最优会被反复强化。更稳妥的做法是反弹法if x ub x 2 * ub - x; elseif x lb x 2 * lb - x; end如果反弹后的位置仍然越界说明这个个体已经走得太远干脆用混沌序列生成一个随机位置替换它而不是硬拉回来。另外我还会在每一代结束后做一次种群修复统计有多少个体没有对全局最优做出任何改进。如果连续多代改进个体的比例低于阈值就从当前个体中抽出一部分用混沌初始化重新生成并替换掉种群中最差的个体。这种做法让种群始终有一部分新鲜血液避免所有个体围着同一个局部解打转。3. 实操过程与Matlab实现3.1 主框架如何组织改进版DBO在Matlab里最好封装成一个函数不要直接在命令行写脚本。函数签名我推荐写成这样function [bestX, bestF, history] improvedDBO(fitnessFun, dim, lb, ub, N, maxIter, params)fitnessFun是适应度函数句柄dim是维度lb和ub是下界和上界向量N是种群规模maxIter是最大迭代次数params是一个结构体用来存放beta、CR、F等改进参数。返回bestX为最优位置bestF为最优适应度history记录每次迭代的全局最优值用于后续画图。这样的结构有一个好处换目标函数时只需要换fitnessFun其他代码完全不用动。我在调试阶段会用结构体params托管所有改进参数这样在参数敏感性分析时可以批量循环改变某个字段非常方便。3.2 核心更新代码实录下面给出改进版DBO主循环的核心Matlab实现。为了便于阅读我把代码精简到只保留关键步骤但保证逻辑完整可运行。% 初始化 X chaoticInit(N, dim, lb, ub); fitness arrayfun(fitnessFun, X(:,1), X(:,2)); % 注意这里只是示意实际需要按dim循环 % 主循环 for t 1:maxIter [bestF, bestIdx] min(fitness); bestX X(bestIdx, :); % 自适应权重 w 0.9 - (0.9 - 0.1) * (t / maxIter); for i 1:N % 滚球阶段 if i round(0.2 * N) r rand(dim, 1); delta sum(X(i,:) - bestX); newX X(i,:) w * levyStep(dim, 1.5) .* (bestX - X(i,:)); newX boundaryFix(newX, lb, ub); if fitnessFun(newX) fitness(i) X(i,:) newX; fitness(i) fitnessFun(newX); end % 繁殖/觅食/偷窃阶段示意可按标准DBO比例分配 elseif i round(0.4 * N) newX X(i,:) 0.5 * (X(randi(N),:) - X(randi(N),:)); newX boundaryFix(newX, lb, ub); if fitnessFun(newX) fitness(i) X(i,:) newX; fitness(i) fitnessFun(newX); end else newX bestX w * levyStep(dim, 1.5); newX boundaryFix(newX, lb, ub); if fitnessFun(newX) fitness(i) X(i,:) newX; fitness(i) fitnessFun(newX); end end end % 差分进化修正最差个体 [sortedF, sortedIdx] sort(fitness); for i 1:round(N * 0.2) r randperm(N, 3); V X(sortedIdx(i),:) params.F * (X(r(1),:) - X(r(2),:)); mask rand(1, dim) params.CR; U X(sortedIdx(i),:); U(mask) V(mask); U boundaryFix(U, lb, ub); if fitnessFun(U) sortedF(i) X(sortedIdx(i),:) U; end end [bestF, bestIdx] min(fitness); history(t) bestF; end代码里的boundaryFix就是刚才提到的反弹边界函数。Levy生成函数在上一节已经给出。要注意的是上面为了演示把标准DBO的四个阶段简化成了三个分支实际复现时建议严格按照标准DBO的角色比例来分配因为繁殖和偷窃阶段的搜索行为各有特点简化版本只能作为快速原型验证。3.3 基准函数与实验配置基准函数是检验改进效果最直接的工具。我这次用了5个经典测试函数覆盖单峰、多峰、欺骗性地形等不同难度函数名表达式特点最优值地形特征Sphere各维平方和0单峰较容易Rosenbrock经典山谷香蕉函数0单峰但路径狭窄RastriginSphere 余弦扰动0多峰局部极多Griewank多峰且存在相互耦合0中等难度多峰Ackley指数叠加余弦0多峰且深谷实验参数我设置了维度30种群规模50最大迭代次数500独立运行30次。每次独立运行固定不同的随机种子保证可比性。最终统计30次的平均最优适应度、标准差和收敛趋势用收敛曲线和箱型图展示。3.4 结果记录与可视化在Matlab中记录收敛曲线的做法很简单在主循环里把每代的bestF存到history数组30次独立运行后会得到一个30 x maxIter的矩阵。为了方便对比我习惯取几何平均后画成半对数图因为智能优化算法前期的适应度变化往往跨越多个数量级线性坐标会丢失很多早期信息。semilogy(mean(history,1), LineWidth, 1.5); hold on;箱型图则直接使用boxplot绘制boxplot(finalResults);对比结果时不要只看最终的最优值还要看标准差。标准差大说明算法不稳定即使最终均值逼得很低实际工程中也不敢依赖它。4. 实验对比与参数调优4.1 改进效果对比表为了确认改进不是自我感觉良好我拿改进版DBO分别和标准DBO、粒子群PSO、灰狼GWO做了对比。这里以Rastrigin 30维函数为例列出一次典型的独立运行结果严格来说不同随机种子会波动但趋势稳定算法平均最优适应度标准差相同预算下收敛到1e-3所需迭代数PSO1.2e014.5e00未收敛到目标GWO6.8e002.3e00未收敛到目标标准DBO5.6e001.9e00未收敛到目标改进DBO4.3e-022.1e-02约240次从表里能看出来改进版在精度和稳定性上都明显优于标准DBO和其他两种对比算法。标准DBO在Rastrigin这样的多峰函数上大量个体的偷窃行为会把种群往错误的最优方向拉而改进版里的Levy飞行和DE修正恰好缓解了这个问题。单峰函数上的对比则没有这么夸张但也有效果。以Sphere函数为例标准DBO可以收敛到1e-20附近改进后能到1e-40以下不过两者在工程上已经没有本质区别因为都已经远远超过实际需求的精度。4.2 参数敏感性分析与推荐取值改进算法引入了几个新参数Levy指数beta、差分进化缩放因子F、交叉概率CR。这三个参数如果调不好反而会让改进算法比标准版更差。我做了简单参数扫描得到以下经验值可以直接作为初始参考beta 1.5。小于1.2时步长跳跃过于频繁收敛曲线像锯齿大于1.8时行为接近高斯分布失去了Levy的跳跃能力。F 0.5。F太小DE变异几乎不影响个体F太大变异个体经常越界在边界反弹上浪费大量计算。CR 0.8。CR太高会破坏原有蜣螂行为的大量信息CR太低则变异无法有效引入新维度。自适应权重w的上下限0.9到0.1。这个区间比较安全不建议把下限压到0否则后期完全没有位置更新来源之一。参数扫描最好做成循环脚本每次改一个参数、固定其他参数然后看中位数收敛曲线。不要看单次运行结果单次运行受随机性影响太大。4.3 时间成本与工程权衡改进算法不是没有代价。Levy步长的生成涉及伽马函数计算差分进化又引入了额外的适应度评估。我的测试环境是普通笔记本30维、种群50、迭代500次标准DBO单次大约运行0.3秒改进版大约0.4秒耗时增加了20%~30%。但这个时间成本在多数工程场景下完全可以接受。因为改进版在同样的迭代预算内达到了更高的精度反过来看要达到相同精度改进版需要的迭代次数远少于标准版总运行时间反而更短。不过如果你的目标函数单次评估就很耗时比如是你自己写的复杂仿真函数那么差分进化带来的额外评估可能比较明显建议把DE只作用于最差10%的个体而不是20%。5. 常见问题与排查技巧实录5.1 结果不收敛或适应度恒为NaN这是我被问得最多的问题也是我自己踩过无数次坑的区域。适应度变成NaN最常见的原因是Levy步长生成了Inf值。Levy步长公式里有除法和伽马函数当v的绝对值接近0时步长会瞬间爆炸。解决办法很简单在生成Levy步长后进行一次截断限制步长绝对值不超过搜索空间对角线的3倍。这样可以保证偶尔有长跳但不会直接飞出天际。另一种情况是初始种群里有越界的值而目标函数对输入范围很敏感越界后计算出现复数或者溢出。这种问题可以通过给chaoticInit加一层后置保护来解决生成的结果全部检查一遍只要越界就用边界值替代。排查时推荐在Matlab中打开dbstop if naninf这样一旦出现NaN或Inf调试器会直接停在出错的那一行比看日志高效得多。5.2 改进后反而不如标准的坑如果你按我上面的代码把三个策略全部打开测试发现比标准DBO还差先不要怀疑算法思路。我遇到过的情况是策略之间相互干扰。最典型的例子Levy飞行和DE同时以全概率执行会让整个种群彻底随机化尤其是后期原本接近最优的个体反复被DE的变异拉走最终无法精细收敛。这就是典型的改进过度。我的建议是给每个策略设一个触发概率开关而不是让它们每代固定执行。比如Levy飞行只以40%的概率叠加到滚球个体上DE只作用于最差的15%个体并且CR不要超过0.8。调试时先把所有创新策略的开关关掉确认标准复现没有变形再依次打开其中一个观察收敛曲线变化。这样定位到具体是哪个策略拖了后腿。5.3 种群多样性监控方法改进算法调试不能只看最终结果还要看过程。最能暴露问题的一个指标是种群多样性也就是种群中个体到平均位置的平均距离。多样性快速下降说明所有个体在向同一个区域聚集也就是早熟了多样性长时间不下降说明搜索太散浪费计算资源。监控代码很简单distances sqrt(sum((X - mean(X)).^2, 2)); diversity(t) mean(distances);画图时把diversity和收敛曲线叠在一起看。如果改进后多样性下降速度比标准版慢同时收敛精度提高说明种群确实在保持一定差异的同时往最优解靠近如果多样性下降很快但精度没有提高可能需要调低偷窃个体的比例。5.4 高效排查速查表排查问题的时候我习惯用一张速查表把现象和可能的对应关系列出来现象可能原因解决方案收敛曲线锯齿严重Levy步长过长当前解被反复弹开截断Levy步长或把触发概率降到40%以下前期陷入平坦区域混沌初始化序列被归一化到时差点覆盖盲区检查每维度独立初始值增加种群数后期震荡无法收敛w的衰减下限过高或DE的CR太高把w下限降到0.1CR降到0.6~0.820次运行结果差异极大随机种子混乱或种群过小固定种子增大种群到80以上计算时间慢得离谱适应度函数没有向量化边界反弹调用太频繁预分配数组向量化fitness评估这张表并不全面但覆盖了我在这个项目中遇到的大部分问题。遇到新问题时建议从随机性是否过强边界处理是否正确目标函数是否可导/连续三个方向去拆解基本都能定位。6. 改进算法的扩展应用6.1 从基准函数到工程优化改进后的DBO不只是一堆公式它可以直接拿去当通用优化器用。我在一个特征选择任务里把改进DBO嵌进分类流程用分类正确率作为适应度特征子集长度作为搜索变量效果比标准DBO稳定不少尤其是在特征数30到50的这个区间改进版没有出现灾难性的早熟。另一个值得尝试的场景是路径规划。把路径点的横纵坐标作为优化变量整条路径长度和障碍物距离加权作为适应度改进版DBO的优势是可以保持种群多样性因此不会每次都收敛到同一条局部最优路径。三维空间的无人机路径、仓库机器人路径都可以试试。还有神经网络权重初始化。这里我不推荐直接用改进DBO去训练整个大网络计算量太大但可以把它用于小规模网络或用于优化LSTM层的超参数组合比如学习率、隐层神经元数和dropout比例。对这些超参数搜索问题改进DBO的混沌初始化和Levy飞行能比随机网格搜索更早发现问题区域。6.2 后续可尝试的方向如果想把改进DBO做深有四个方向值得继续探索。第一是多目标化。目前改进版还是单目标搜索把非支配排序或MOEA/D框架加进来就能处理多目标权衡问题。第二是小生境技术通过拥挤距离控制让不同个体分布在不同峰值区域适合解决多峰问题的多解需求。第三是与局部搜索混合在改进版收敛后期每隔若干代用一次模式搜索或Nelder-Mead单纯形法做局部精炼可以进一步提高最终精度。第四是用Sobol序列替代Tent映射Sobol序列的均匀性更好但需要额外生成低差异序列实现上并不困难。每个方向都可以单独成为一个项目并且都能在现有Matlab代码基础上改造不需要重写整个框架。我在实际做这个项目时最深的体会是改进算法的价值不在于让某个基准函数分数好看而在于面对不确定的新问题时算法能不能稳定找到一个还算不错的解。改完以后别急着调参数先在固定随机种子条件下跑10次把Box图和种群多样性曲线画出来看清楚每一轮迭代都在干什么。如果只想立刻要一个能用的版本我建议先做两件事把初始化换成Tent混沌映射再给最差20%种群加一次差分进化替换。这两步实现成本低、效果又稳Levy飞行和动态角色分配等确实需要再加别一股脑全塞进主循环。算法的乐趣在于删减而不在于堆砌。