
你听说过一种没有大脑、却能在迷宫里找到食物最短路径的生物吗这不是科幻段子而是2000年发表在Nature上的真实实验。日本科学家把多头绒泡菌放在迷宫入口把食物放在出口没过多久这只单细胞黏菌就“想”出了连通两点的最短路线。我当年第一次看到这个实验心里冒出的想法是这不就是一个活在培养皿里的优化算法吗后来读到2020年提出的黏菌算法Slime Mould AlgorithmSMA真有“旧友重逢”的感觉。SMA是近年来元启发式优化里讨论度非常高的一种群体智能算法因为名字好记、结构简单、收敛快很多论文叫它SMA网上还因为它那种“锁定食物就一路猛冲”的劲头火过一阵“sma黏菌”的梗。这篇博文我就把SMA的原理、公式、手算推演、工程落地和踩坑经历一次性讲透小白能看懂有基础的老手也能从实测经验里捡点东西。1. 思路拆解从黏菌觅食到群体优化1.1 黏菌只是单细胞凭什么能做优化先说清楚我们讨论的黏菌是什么。它叫多头绒泡菌Physarum polycephalum本质是一团单细胞生物但细胞里有很多细胞核身体可以延展成一张巨大的网状结构。它没有中枢神经系统却能在觅食时表现出类似“智能”的行为整个菌体不断扩散、伸出伪足感知营养物质的浓度信号然后通过收缩和膨胀有节奏地把细胞质泵向更有“价值”的区域同时放弃那些没有食物的分支。这个行为放在优化问题里非常妙。搜索空间可以看成培养皿食物浓度可以看成适应度函数的数值黏菌群体的每一次扩张和收缩就相当于算法在做“探索”和“开发”先大面积铺开找可能的好区域再用收缩机制集中力量逼近最优解。我后来做优化算法对比的时候经常跟同事说SMA的底层逻辑不是“猜答案”而是“模仿一种生物在未知环境里把资源调到最值得去的地方”这也是它能火起来的原因——模型来源直观不需要多少数学基础就能讲清楚它想干嘛。1.2 SMA在元启发式算法家族里的位置如果你接触过粒子群算法PSO、遗传算法GA、灰狼优化GWO再看SMA会有种“熟人”感因为SMA也是基于群体的随机优化算法。它同样先随机撒一批解然后通过迭代更新位置最终收敛到目标函数的最优解区域。区别在于SMA把“群体迭代”这套框架套进了黏菌觅食行为里核心更新由三个机制驱动接近食物、包裹食物、抓取食物。从我实际跑实验的感受来说SMA有两个特点让它在工程里更容易上手。第一需要人工设置的参数非常少核心就是种群规模、最大迭代次数、边界和一个随机初始化概率z相比差分进化里的CR/F要省心很多。第二它在中等维度十几维到几十维的单峰和多峰问题上收敛速度确实快经常在相同迭代次数里拿到比PSO和GA更好的结果。这也使得SMA在2020年提出后迅速出现了大量改进版本比如混合GWO、混合樽海鞘群甚至出现了面向离散问题的二进制SMA。当然它也不是万能的高维问题上容易早熟处理强约束问题时不够稳这些我会在第4章专门展开。先记住一句话SMA是一把趁手但需要知道“适用边界”的螺丝刀不是万能电钻。2. 数学模型全拆解每个公式到底在干什么2.1 SMA的三段式行为模型SMA把黏菌觅食过程抽象成三个阶段的循环对应到算法里就是三种不同的位置更新模式。第一个阶段是“接近食物”。黏菌随着体液流动感知到食物浓度后会主动朝最优区域移动。在算法里这一阶段通过一个带权重的差分向量来完成以当前全局最优位置为基准点再叠加两个随机个体的差值让每个黏菌向“可能更好的方向”搜索。第二个阶段是“包裹食物”。当黏菌的网络已经找到营养丰富的区域时它会收缩细胞质、集中资源去包围食物。算法里对应的操作是“当前解乘以一个随时间衰减的系数”让群体在后期做更精细的局部开发缩小振动幅度。第三个阶段是“抓取食物”但这里面暗藏一个巧妙设计。为了不让算法过早困在局部最优SMA引入了一个极低概率的随机重置机制当随机数小于z论文里常取0.03时某个体会被直接扔到搜索空间的全新位置相当于黏菌在已经铺开的管网之外又“长”出一根新探针。这个设计很便宜却对后期跳出局部极值很有用。这三段式行为不是按时间顺序切换而是在每一次迭代里对所有个体统一判断每个个体根据自己的适应度情况、随机数的运气决定走“靠近最优”的路径还是走“原地收缩”的路径偶尔有人被随机重生成一个新“种子”。2.2 核心公式里的变量与直觉下面把SMA的关键公式拆开讲。如果你找原论文会看到这几个公式初始化概率z一般取0.03。选择概率p tanh(|S(i) - DF|)其中S(i)是第i只黏菌的适应度DF是当前全局最优适应度。权重前一半优势个体用 W 1 r · log((bF - S(i))/(bF - wF) 1)后一半劣势个体用 W 1 - r · log((bF - S(i))/(bF - wF) 1)。振动参数vb的范围是[-a, a]a arctanh(1 - t/T)vc是从1近似线性衰减到0的系数。不要被这一堆字母吓到我把它们的物理直觉说清楚。先看p。假设当前个体适应度和全局最优差距很大|S(i) - DF|就大tanh接近1那么这个个体“走靠近最优分支”的概率就高。反过来如果这个个体本身就是最优p几乎等于0它大概率走“原地收缩”分支稳稳地做局部开发。这个设计很像黏菌的行为离食物远的触手猛长已经吃到食物的部分收缩咀嚼。再看W。它要做的事情是给不同质量的个体分配不同“进攻力度”。前一半更优的个体W略大于1相当于让它们在差分项里贡献更多“前进动力”后一半更差的个体W被压缩到1以下避免它们在随机差分时飞出太远。log的作用是把适应度差值缩放到一个温和的范围免得个体之间的数量级差距把步长带崩。值得注意的是W计算里分母bF - wF如果接近0会出现除零问题工程实现里必须给它加一个极小量保护或者直接跳过。最后看vb和vc。vb在[-a, a]之间随机振动a随迭代次数从大变小模拟黏菌前期大幅度探索、后期小步收敛。vc则负责“收缩”从1逐渐降到0保证收敛过程不是一步到位而是有节奏地压紧。2.3 一次迭代的完整流程和伪代码完整的一次迭代可以拆成下面几步计算当前所有个体的适应度找出全局最优bF和全局最差wF记录最优位置best_pos。对适应度排序按排名计算每个个体的权重W。计算当前迭代的vb、vc和p。对每个个体先判断随机数是否小于z小于就直接生成一个随机位置否则逐维判断随机数是否小于p小于就走“接近食物”的差分公式否则走“乘以vc收缩”的公式。做边界检查把越界的维度拉回边界内。重新计算适应度更新全局最优。用伪代码表示就是下面这个样子你可以直接照着搭骨架import numpy as np def sma(fobj, N30, T500, dim2, lb-10, ub10, z0.03): lb np.full(dim, lb) ub np.full(dim, ub) X np.random.uniform(lb, ub, (N, dim)) fit np.array([fobj(x) for x in X]) best_idx np.argmin(fit) best_pos X[best_idx].copy() best_fit fit[best_idx] for t in range(1, T 1): # 排序与最值 sorted_idx np.argsort(fit) bF fit[sorted_idx[0]] wF fit[sorted_idx[-1]] denom max(wF - bF, 1e-10) # 权重W W np.ones(N) r np.random.random(N) for rank, idx in enumerate(sorted_idx): log_term np.log((bF - fit[idx]) / denom 1) if rank N / 2: W[idx] 1 r[rank] * log_term else: W[idx] 1 - r[rank] * log_term # 振动参数 a np.arctanh(1 - t / T) vb np.random.uniform(-a, a, (N, dim)) vc 1 - t / T for i in range(N): if np.random.rand() z: # 随机重置分支抓取新区域 X[i] lb np.random.rand(dim) * (ub - lb) else: p np.tanh(abs(fit[i] - bF)) for d in range(dim): if np.random.rand() p: # 接近食物分支 sel np.random.choice([j for j in range(N) if j ! i], 2, replaceFalse) A, B sel[0], sel[1] X[i, d] best_pos[d] vb[i, d] * (W[i] * X[A, d] - X[B, d]) else: # 包裹食物分支 X[i, d] vc * X[i, d] X[i] np.clip(X[i], lb, ub) fit np.array([fobj(x) for x in X]) cur_best np.argmin(fit) if fit[cur_best] best_fit: best_fit fit[cur_best] best_pos X[cur_best].copy() return best_pos, best_fit这个版本的细节可能和论文原码略有出入但整体结构是忠于原论文的。不同论文里差分项可能是“减号”也可能是“加号”这属于随机搜索的等价变体不影响算法本质你自己复现的时候保持一致就行。3. 手把手推演一次迭代含数字实例3.1 初始化让黏菌随机撒到搜索空间看公式容易头晕不如直接拿数字走一遍。假设我们要最小化一个简单的二维函数f(x, y) x² y²最优解显然在原点(0,0)最优值是0但别着急算法不知道这个答案。我们设种群规模N5维度dim2边界lb-5、ub5最大迭代T100z0.03。假设迭代进行到t10时的群体如下个体当前位置适应度1(2.5, -1.8)9.492(-0.7, 3.2)10.733(4.1, 0.6)17.174(-3.3, -2.9)19.305(1.8, 2.4)9.00当前全局最优DF也就是bF9.0对应个体5位置(1.8,2.4)全局最差wF19.3对应个体4。记住这两个数后面所有公式都要用到它们。3.2 适应度排序与W权重计算先按适应度升序排序得到排名排名个体适应度分组159.00前半优势组219.49前半优势组3210.73后半劣势组4317.17后半劣势组5419.30后半劣势组权重公式里前面一半个体用“1 r·log(...)”后面一半用“1 - r·log(...)”。为了推演方便我们假设随机数r统一取0.5实际实现中每个个体是独立随机数。计算过程如下个体5最优log((9 - 9)/(9 - 19.3) 1) log(1) 0W5 1 0.5·0 1。个体1分子9 - 9.49 -0.49分母9 - 19.3 -10.3比值0.0476加1后取log约0.0465W1 1 0.5·0.0465 1.0232。个体2分子9 - 10.73 -1.73比值0.1680log约0.1552W2 1 - 0.5·0.1552 0.9224。个体3分子9 - 17.17 -8.17比值0.7932log约0.5843W3 1 - 0.5·0.5843 0.7079。个体4分子9 - 19.3 -10.3比值1log约0.6931W4 1 - 0.5·0.6931 0.6535。你看优势个体的权重接近1甚至略大于1劣势个体的权重被压到0.65~0.92。这就是SMA的自适应调节越差的个体在下一轮差分移动时越“不敢乱跑”把探索空间让给优势个体。3.3 位置更新三个分支怎么触发接下来计算p值。p tanh(|S(i) - DF|)个体|fit - bF|p10.490.45421.730.93938.170.9999410.30.9999500离最优越远的个体p越大越容易触发“接近食物”的差分分支最优个体p0几乎必然走“包裹收缩”分支。t10时a arctanh(1 - 10/100) arctanh(0.9) ≈ 1.472vb在[-1.472, 1.472]之间随机振动vc 1 - 10/100 0.9。逐个看更新情况。个体5是当前最优p0。假设随机数r20.6因为r2不小于z0.03也大于p所以走包裹分支X5_new vc · X5 0.9 · (1.8, 2.4) (1.62, 2.16)。最优个体没有原地躺平而是缓慢向原点收缩。个体1的p0.454假设随机数r20.3小于p触发接近食物分支。随机选两个其他个体作为A和B假设A个体2B个体3。差分项 W1·X_A - X_B 1.0232·(-0.7, 3.2) - (4.1, 0.6) (-4.8163, 2.6743)。 再假设该维的vb取0.8则更新量(-3.8530, 2.1394)新位置best_pos 更新量(1.8 - 3.853, 2.4 2.1394)(-2.053, 4.5394)。这只黏菌虽然会飞得有点远但还在[-5,5]边界内属于搜索过程中允许的跃迁。个体2的p0.939假设随机数r20.95大于p走包裹分支X2_new 0.9 · (-0.7, 3.2) (-0.63, 2.88)。个体3的p接近1假设随机数r20.5小于p走接近食物分支。假设A个体5B个体1差分项 W3·X_A - X_B 0.7079·(1.8, 2.4) - (2.5, -1.8) (-1.2257, 3.4990)。 假设vb0.4更新量(-0.4903, 1.3996)新位置(1.3097, 3.7996)。个体4同理p接近1随机数小就触发差分随机数大就收缩。这个流程对每个个体、每一维独立执行代码里的循环就是干这件事的。3.4 收敛观察点把这一轮更新的位置画出来你会发现一部分个体在向best_pos靠拢一部分个体在围绕best_pos做大范围跳跃另一些个体在原地缩小步长。三种行为叠加在一起就形成了SMA特别的搜索节奏。我在实际跑算法时喜欢在每次迭代后记录“群体平均适应度”和“最优适应度”。如果在迭代后期群体平均适应度还在大幅波动说明探索行为过强收敛会偏慢如果最优适应度连续几十代纹丝不动那就要小心早熟。这个判断技巧可以拿来快速验证自己的SMA实现是否正确。4. SMA的优劣势与适配场景4.1 优点和“爽点”为什么SMA经常跑赢我拿SMA和PSO、GA在同台测试函数上做过对比包括Sphere、Rastrigin、Griewank这些经典测试函数。SMA在中等维度下的表现确实能打尤其是在Rastrigin这类多峰函数上它比PSO更不容易陷入最靠近初始位置的那个局部谷底。原因就在于那个z0.03的随机重置机制相当于随时有3%的个体被空投到新区域这种“低成本多样性保持”策略非常有效。另外SMA的参数少到几乎不需要“调参大师”。你只需要把种群N设在30到50最大迭代T设在300到500边界写对z用默认0.03基本就能跑出和论文同量级的结果。对于工程人员来说这可太重要了很多传统算法光是把超参数调稳就得花掉大半天。4.2 短板与误用场景但它不是没有脾气。第一个短板是高维问题。维度一旦超过100SMA的收敛速度会明显下降而且容易早熟。我做过一次对比在200维的Sphere函数上SMA表现和PSO差不多但明显打不过差分进化原因在于差分进化的变异机制在高维空间里保持搜索多样性的能力更强。第二个短板是强约束问题。SMA原生没有处理约束的手段如果你的搜索空间是高度非凸、可行域极其狭窄的SMA很容易把大量个体浪费在不可行区域。这种场景建议使用罚函数法配合SMA或者直接改用eSMA、ISMA这类带约束改进的变体。第三个短板是第一次迭代的数值稳定性。前面推演时也提到a arctanh(1 - t/T)如果t从0开始arctanh(1)会趋向无穷大导致vb范围爆掉。工程实现里要么让迭代从t1开始要么给a加个保护上限否则第一代黏菌会满天飞。4.3 四个最值得尝试的应用方向SMA最舒服的战场是中等维度的连续优化问题尤其是那些没有梯度、没有解析表达、只能用仿真器评估的“黑箱问题”。第一个方向是机器学习超参数优化。比如训练BP神经网络时用SMA搜学习率、隐藏层节点数、正则化系数替代网格搜索和随机搜索能在同样的评估次数下拿到更低的验证集误差。第二个方向是PID控制器参数整定。把PID的三个增益和积分/微分系数编码成一个解用SMA去最小化超调量和调节时间效果很稳。第三个方向是路径规划。在二维栅格地图里把路径点坐标拼成一个向量作为解SMA能较快找到一条从起点到终点的较优路径。第四个方向是图像分割阈值搜索。最大类间方差法Otsu在单阈值时能暴力搜但多阈值时组合爆炸用SMA搜一组阈值又快又方便。我甚至见过有人把SMA用在做投资组合的风险优化上虽然算法本身不含金融知识但只要你把目标函数写成夏普比率的负数SMA就能帮你搜出一组资产权重组合。这类“抽象成目标函数”的问题SMA都能接。5. 工程落地代码框架与常见坑5.1 最小可运行的SMA代码骨架下面这份是我实际项目里精简出来的SMA实现目标函数以fobj传入可以直接替换成你自己的代价函数。伪代码在第2章已经给出这里说一下决定代码正确性的几个关键细节。第一排序与W的对应关系要对上。最好用argsort得到排名索引再根据排名分配W然后把W填回原来的个体序号。如果直接给排序后的数组算W后面更新时容易串位置几行代码的错误会直接影响收敛方向。第二边界检查一定要写在每次更新之后。差分搜索很容易产生越界值我一般用np.clip把解直接拉回边界。另一种更好的做法是“边界吸收”即越界后把该维度值置为边界值实测收敛更稳。第三全局最优不要被当前代的差解覆盖。很多新手会在循环里直接把best_pos更新成当前代最小适应度对应的位置却忘了这一步只能和“历史最优”比较否则收敛曲线会来回跳。这是一个非常隐蔽的Bug排查时要格外注意。# 核心更新循环伪代码见2.3 # 注意第3章推演的公式对应这里的前半组/后半组权重计算。5.2 常见问题排查表我把SMA落地中常见的坑整理成一张表方便你对照排查现象可能原因解决建议第一代位置剧烈飞散arctanh(1)导致a无穷大迭代从t1开始或给a加上限如max(a, 3)收敛过快结果很差种群太小或最大迭代太少N增大到40~50T增大到500以上W全部等于1或波动极小bF-wF接近0除以了一个极小量denom加保护值或者当范围过小时跳过权重计算多次运行结果差异大随机种子影响大边界处理不当固定种子测试调大种群检查边界clip逻辑高维问题效果变差维度升高后探索效率骤降改用混合策略或分层SMA限制应用维度在30维以内适应度出现NaNlog里出现负数或除零检查bF/wF逻辑给log输入加max(eps)保护这些坑我基本都踩过一轮尤其是第一个“a发散”的问题当时第一次跑SMA前几百代结果离谱到以为公式抄错了后来查源码才发现是迭代起点的问题。这类数值稳定性问题在别的算法里很少遇到属于SMA自己的性格。5.3 参数设置的实测经验关于参数网上很多文章会直接抄论文默认值但工程里不能这么死板。我自己总结了一套经验种群N在30左右足够应付90%的二维到三十维问题再小就容易被随机性主导最大迭代T设在300到500之间比较划算再多也不会显著提升精度反而浪费算力。z取0.03基本是安全的如果你发现算法总在局部最优停留可以把z提高到0.05甚至0.08但代价是收敛会变慢。还有一个经验是如果你手头的问题对精度要求很高可以把vb的衰减从arctanh改成线性或余弦衰减。原版SMA的arctanh衰减在前期探索太猛、后期衰减太快改成a 1 - (t/T)会更平滑一点我实测在一些测试函数上能把最优值从10的负4次方量级推到10的负7次方量级。这个改动不改变算法主体属于“换了个更顺手的油门”。6. 变体与扩展不止是“SMA原版”很多算法火起来之后会迅速出现一大批魔改版本SMA也不例外。如果原版SMA满足不了你的问题可以按下面几个方向找改进版本。第一个方向是混合策略。把SMA和GWO、PSO或差分进化混在一起用常见做法是每轮迭代里一部分个体用SMA更新另一部分用对方算法更新再汇总比较。混合算法在CEC测试函数上的得分通常比原版高因为它同时引入了两种不同的搜索步态。代价是实现复杂度提高调参难度也翻倍。第二个方向是二进制和离散化改造。经典的SMA输出连续解但特征选择、组合优化这类问题需要0/1编码。常见做法是把连续位置经过Sigmoid函数映射到[0,1]再按概率转成0或1。我记得有一篇论文就用二进制的SMA做故障特征选择在UCI几个数据集上把分类精度提升了几个百分点。第三个方向是多目标化。用非支配排序的思路把SMA扩展成MOSMA这类多目标算法可以同时优化两个或三个冲突目标。这个方向的论文也不算少但说实话多目标优化里更强的算法很多SMA的这个变体更多是“能用”而不是“最佳”。我个人的建议是如果你只是短期求一个稳妥的解原版SMA就够了如果你要做学术对比或工程挑战再考虑混合方向。没必要一上来就追最新变体先吃透原版的脾气最重要。7. 最后分享一点个人体会SMA带给我的最大启发不只是它跑分高而是它再次说明了好的优化算法往往来源于对生物行为的深刻观察。黏菌没有大脑却能通过局部的群体互动解决全局最优路径问题这本身就是“涌现智能”的绝佳例子。就像网友玩梗说的“sma黏菌”某种角度也是描述一个人锁定目标后一路向前冲的状态算法里确实有这种气质。我做SMA实验这段时间最深的一个体会是复现一篇算法论文最快的方法不是直接啃公式而是先把它对应到具体的生物过程理解每一行代码在模仿什么行为。黏菌会感知、会收缩、会放弃SMA的公式里每一个变量都对应一种“生存策略”。只要把它当做一个生物系统去调试很多参数设置的直觉会自然涌现出来。希望这篇内容能让你对SMA建立起同样清晰的画面感下次在项目里遇到合适的优化场景大胆拿它去跑一跑。