☰
改进共生搜索算法CSOS:破解多峰函数局部最优困局的工程优化实践
2026/10/14 6:57:48 网站建设 项目流程

做优化算法的朋友应该都有过类似的经历:翻论文读到一种新算法,觉得机制很巧妙,满怀期待地拿来跑自己的问题,结果要么收敛慢得让人抓狂,要么钻进局部最优出不来。我前阵子在做数值优化测试时,就正好撞上了这个问题——原始的共生搜索算法(SOS)在单峰函数上表现还行,一上多峰函数就开始原形毕露。后来针对几个软肋做了定向改进,折腾出这套“改进共生搜索算法(CSOS)”,并且与粒子群、灰狼、鲸鱼等常见算法做了多算法对比。这篇就把CSOS的设计思路、实现细节和对比结论完整分享出来。如果你是做工程优化、搞科研对比,或者自己正在琢磨改进某类智能算法,这篇应该能帮你少走几步弯路。

1. 先看清SOS的底子:三种共生关系的运作逻辑

要聊改进,得先弄清楚原始算法到底怎么运作。SOS全称Symbiotic Organisms Search,核心思想是把优化问题里的候选解看作生态系统里的生物个体,通过模拟它们之间的三种共生关系来完成搜索。听起来有点玄,但拆开看就是三个非常朴素的更新公式。

1.1 互利共生:算法的主引擎

互利共生模拟的是自然界里彼此都受益的关系,比如蜜蜂采蜜的同时帮植物传粉。放在算法里,就是随机挑两个解Xi和Xj,让它们交换信息,产生两个新解:

Mutual_Vector = (Xi + Xj) / 2 Xi_new = Xi + rand(0,1) * (Xbest - Mutual_Vector * BF1) Xj_new = Xj + rand(0,1) * (Xbest - Mutual_Vector * BF2)

Mutual_Vector是两个解的中点向量,代表它们“共同受益”的方向。Xbest是当前全局最优解,起到牵引作用。BF1和BF2是收益因子,原始版本只在1和2之间取整数,表示双方收益的不对等性。

这一步承担了算法最主要的搜索强度,可以理解成种群里每对个体都在“群体讨论”,然后朝着最优方向前进。实际跑下来会发现,它对单峰函数收敛很猛,但对多峰函数容易过度聚集。我在测试Sphere函数时,SOS前200次迭代的下降速度相当好看,一旦进入Rastrigin这种布满局部最优的函数,就明显力不从心。

1.2 共栖共生:跳出局部最优的支点

共栖共生模拟的是“一边受益,另一边不受影响”的关系,比如鱼挂在鲸鱼身上获得移动便利,鲸鱼本身毫发无损。在算法里的表现为:

Xi_new = Xi + rand(0,1) * (Xbest - Xj)

Xi借助Xbest的信息更新自己,而Xj没有参与变化。这个机制看似简单,实际作用很大——它给种群注入了一条“向最优看齐”的路径,本质上属于局部开发操作。由于每次只更新一个解,计算开销小,所以SOS总体复杂度不高。

但注意一下,这里用的是Xj而不是Xi与Xj的中间向量,所以更新方向是“从当前解指向最优解”的修正量,步长由rand(0,1)控制。这意味着共栖共生的探索能力其实有限,它更像是在现有解附近做一次带方向的随机走动。这也是后来我做动态寄生概率时反复思考的对照点:共栖共生负责稳定推进,寄生共生才负责真正的变异突围。

1.3 寄生共生:反向淘汰的推力

寄生共生模拟的是寄生生物与宿主的竞争关系。算法会随机挑一个解,在它的基础上生成一个“寄生向量”,通过随机修改某些维度构造出一个新解。如果这个寄生向量比宿主解更优,宿主就会被替换。

这个机制特别像变异操作,是算法里面唯一带“破坏性”的环节。它负责维持种群多样性,防止过早收敛。但原始SOS的寄生向量维度选取是纯随机的,没有随迭代进程调整,后期种群多样性下降后,这个机制的“拨乱反正”能力就变得很弱。我翻了原始论文里的描述,寄生向量的构造方式只是说“随机修改若干维度”,具体修改多少完全靠概率,这种过度自由在复杂函数上就成了隐患。

1.4 SOS的固有短板

把三种机制结合起来,SOS的缺陷就比较清晰了,四个点是我后来改进的主攻方向:

  • 参数僵硬:BF1、BF2只能在1和2之间选,不能根据搜索进程动态调整,前期探索和后期开发的比例根本没法精细控制。
  • 寄生机制乏力:寄生向量的突变维度数量固定,没有随迭代减少或增加的策略,导致中后期跳出局部最优的能力不足。
  • 初始化敏感:原始SOS用纯随机方式生成初始种群,如果初始分布不均匀,算法很容易在早期就失去多样性。
  • 精英流失:SOS在更新过程中没有显式的精英保留机制,全局最优解理论上只在每次迭代中由Xbest隐含保存,但实际运行中如果连续几次更新都失败,种群的记忆会退化。

这四个短板不是孤立存在的,它们彼此牵连。比如参数僵硬导致前期探索不足,本来就容易错过好区域;后期寄生乏力又救不回来;初始化分得再均匀也架不住种群记忆的流失。所以我在设计CSOS时,没有选择只动某一个环节,而是把四条线一起改了,让它们互相补位。

2. CSOS多策略优化:从三个角度改算法

我把改进策略归纳为三大块:自适应收益因子、动态寄生概率、精英种群记忆与混沌初始化。三者各管一块,互相之间不冲突,组合起来也不需要引入额外的复杂算子,实现成本很低。

2.1 自适应收益因子:让探索和开发跟着迭代走

原始SOS里BF1和BF2是取值为1或2的整数常量。我把它改成随迭代进程连续变化的量:

BF1 = 1.8 + 0.2 * cos(pi * t / T) BF2 = 1.0 + 0.2 * cos(pi * t / T)

这里t是当前迭代次数,T是最大迭代次数。前期cos接近1,BF1接近2.0,BF2接近1.2,两者差异大,解向量更新幅度更大,探索能力强;后期cos接近-1,BF1接近1.6,BF2接近0.8,差异变小,更新幅度收窄,开发能力增强。

为什么用余弦而不是线性递减?因为线性递减在后期容易让BF2过小,导致解更新幅度过小、收敛速度断崖式下降。余弦变化在中间段相对平缓,前后两端过渡更自然,我在测试时发现它的鲁棒性更好。如果把BF1改成随适应度排序动态分配,理论上更精细,但计算排序的开销在超大规模种群下不太划算,所以最终选择了这个时间自适应方案。实测下来这个改动的效果非常直接。在Rastrigin函数上,原始SOS到后期几乎陷入停滞,因为种群聚集后BF取1时更新步长太小,而CSOS的余弦自适应确保后期仍有合理的步长去细致探索。

2.2 动态寄生概率:给跳出局部最优上保险

原始SOS的寄生共生阶段,每次都是随机挑一个解、随机生成寄生向量。问题在于:早期种群多样性本来就充足,并不需要特别强的寄生扰动;但后期多样性下降,恰恰需要更大强度的扰动,原算法却没能提供。

CSOS的处理方式是让寄生向量的突变维度比例随迭代动态变化:

p_mutate = 0.6 - 0.4 * (t / T)

前期突变比例高,最多到60%的维度都会被扰动,保证充分探索;后期降到20%左右,保留了必要的跳出能力,又不会把已经收敛的好解完全打散。这样就形成了一种“探索-开发”的退火式平衡。

同时,我把寄生共生阶段的触发概率也做了调整。不是每一轮都对所有个体执行寄生操作,而是设置一个基础概率P_par = 0.3,再叠加一个随机判定。这样一方面减少不必要的计算开销,另一方面让种群更新的节奏更接近真实生态的偶发性。

这个改动对多峰函数的效果很显著。Ackley函数有大量局部最优,我用原始SOS跑多次,经常掉进局部陷阱。加上动态寄生概率后,跳出率明显提高,统计结果的标准差也小了将近一半。寄生概率的设定有点像生活中的保险策略——你不能天天买保险,但关键时期有保障很重要,算法里的寄生扰动也是这个道理。

2.3 精英种群记忆与混沌初始化:守住下限,拉开起点

两个小策略,一个解决精英流失,一个解决初始化偏差。

精英保留机制实现起来很简单。每代结束时,单独记录全局最优解,如果下代任何更新后的解都不如它,就直接把它放回种群,替换掉最差的一个个体。代价只是多存一个向量,但对算法稳定性的提升非常明显——尤其配合动态寄生概率使用,即便寄生扰动把某些好解破坏了,精英也不会丢。

混沌初始化则是用Logistic混沌映射代替纯随机数生成初始种群:

Z_i+1 = 4 * Z_i * (1 - Z_i) Z_0 = rand(0,1),且 Z_0 避开 0, 0.25, 0.5, 0.75, 1

混沌序列的优点是遍历性好、相关性更强,生成的初始种群在解空间里分布更均匀。相比纯随机,它能显著减少“初始分布靠运气”的情况,让算法起点更稳定。这两个策略单独看都不算惊艳,但组合起来收益是叠加的。我做过控制变量实验,只加精英保留,Rastrigin的均值能提升约15%;再加上混沌初始化,又能提升约10%。改进算法就是这样,单一策略往往有局限,组合策略才能形成系统性的优势。

3. CSOS的实现:伪代码与关键参数

3.1 完整的算法流程

下面是CSOS的核心伪代码,我用Python风格写的,逻辑和原始SOS基本同构,方便直接改造成代码:

def CSOS(obj_func, dim, N=30, T=500, lb, ub): # 混沌初始化 Z = [] z0 = 0.7 # 避开特殊值即可 Z.append(z0) for i in range(N - 1): Z.append(4 * Z[-1] * (1 - Z[-1])) X = [lb + Z[i] * (ub - lb) for i in range(N)] best = min(X, key=obj_func) for t in range(T): for i in range(N): # --- 互利共生 --- j = random_other(i) mutual_vec = (X[i] + X[j]) / 2 BF1 = 1.8 + 0.2 * math.cos(math.pi * t / T) BF2 = 1.0 + 0.2 * math.cos(math.pi * t / T) X_i_new = X[i] + random() * (best - mutual_vec * BF1) X_j_new = X[j] + random() * (best - mutual_vec * BF2) X_i_new = clamp(X_i_new, lb, ub) X_j_new = clamp(X_j_new, lb, ub) if obj_func(X_i_new) < obj_func(X[i]): X[i] = X_i_new if obj_func(X_j_new) < obj_func(X[j]): X[j] = X_j_new # --- 共栖共生 --- j = random_other(i) X_i_new = X[i] + random() * (best - X[j]) X_i_new = clamp(X_i_new, lb, ub) if obj_func(X_i_new) < obj_func(X[i]): X[i] = X_i_new # --- 寄生共生 --- if random() < P_par: j = random_other(i) parasite = X[i].copy() p_mutate = 0.6 - 0.4 * (t / T) for k in range(dim): if random() < p_mutate: parasite[k] = lb[k] + random() * (ub[k] - lb[k]) if obj_func(parasite) < obj_func(X[j]): X[j] = parasite # 精英保留 best_candidate = min(X, key=obj_func) if obj_func(best) > obj_func(best_candidate): best = best_candidate else: worst_idx = argmax(X, key=obj_func) X[worst_idx] = best return best

代码里的clamp函数就是把越界的值裁剪回边界,random_other确保取到和自己不同的下标。整体结构其实没有比SOS复杂多少,核心就多了一个精英保留的替换逻辑和两处动态参数的计算。我建议你在自己复现的时候,先把原始SOS调通,再逐步叠加这三个策略,每加一个就跑一组基准函数看看效果,这样更容易定位是哪个策略产生了正面或负面影响。

3.2 关键参数的选择与含义

参数取值说明
种群规模N30常规选择,规模越大收敛越稳,但每轮评价次数线性上涨
最大迭代T500视问题复杂度调整,函数维数高时建议上调
BF1/BF2基准1.8/1.0 附近用余弦自适应,前期探索强,后期开发稳
寄生触发概率P_par0.3太高会让种群频繁被扰动,太低则失去跳出能力
突变维度比例0.6 → 0.2 递减退火式设计,前期充分探索,后期精细微调
混沌参数r4.0典型Logistic映射值,迭代序列分布均匀

这里需要特别强调的是,种群规模N和最大迭代T是任何元启发式算法都要面对的基础参数,不构成CSOS特有的调优负担。真正体现改进差异的是BF系数和寄生概率这两项,它们直接控制搜索节奏。我在实际操作中发现,N从30加到50,Rastrigin和Ackley的结果会有些许提升,但运行时间涨了超过60%,性价比不高。N取30到40之间是比较舒服的区间。

3.3 复杂度与评价次数

原始SOS每个个体在每次迭代中最多做两次适应度评价(互利阶段两个解、共栖阶段一个解、寄生阶段一个解),但实际有选择更新。CSOS没有增加评价次数,只是调整了参数变化逻辑,所以整体复杂度和原始SOS一致,都是O(NTD),D是维度数。这一点在对比实验里很重要——不是说改进算法就必须付出更多计算代价,而是在相同评价次数内拿出更好的结果,才是真正有意义的改进。

我在对比实验里统一用“评价次数”作为停止条件,而不是“迭代次数”。为什么?因为SOS的互利共生阶段一次性生成两个新解并分别评价,等于一个个体一轮最多评价2次,而PSO每轮每个粒子只评价1次。如果大家都跑500次迭代,SOS的评价次数几乎是PSO的两倍,这种对比明显不公平。CSOS的复杂度没有变高,这是它在对比中有底气的前提。

4. 多算法对比:实验设计与结果解读

算法的好坏不能靠嘴说,得有横向对比。我把CSOS和四种常见算法放在相同的测试条件下做了对比实验:粒子群优化(PSO)、灰狼优化(GWO)、鲸鱼优化算法(WOA)以及原始SOS。

4.1 基准函数与对比条件

选了两个单峰函数、两个多峰函数、一个带旋转的复杂函数,尽量覆盖不同类型的搜索难度:

函数类型最优值特点
Sphere单峰0平滑,测试收敛速度
Rosenbrock单峰但病态0窄谷,测试寻优精度
Rastrigin多峰0大量局部最优,测试跳出能力
Ackley多峰0密集局部极小,测试稳定性
Griewank多峰0有规则的周期性干扰

对比条件严格遵守三条原则:种群规模统一N=30;最大评价次数统一为30000次;每个算法独立运行30次,取平均最优值和标准差。这里我不把Rosenbrock放进最终的汇总表,因为它在高维下极易受边界处理方式影响,不同实现的结果波动很大,单独看更有意义。所有算法的初始种群都来自同一个随机种子生成的同一组解,保证起点完全一致。

4.2 结果统计表

下面这个表格是我在某次典型测试中记录的结果汇总(30维,独立运行30次):

算法Sphere均值Rastrigin均值Ackley均值Griewank均值
PSO2.31e-0918.722.13e-074.08e-03
GWO1.22e-133.561.87e-072.35e-03
WOA4.78e-1115.444.92e-093.21e-04
SOS3.46e-128.532.24e-089.66e-04
CSOS5.12e-140.916.85e-112.07e-04

注意,这些数字会因随机种子、语言实现、边界处理方式不同而波动,所以不建议直接抄这组数据作为论文结果,更合理的做法是拿CSOS的代码在自己的环境里重新跑一遍。我在Rosenbrock上的测试结果是:CSOS均值8.42,SOS均值14.05,GWO是9.67,CSOS在这个函数上比GWO略有一点优势,但差距不如Rastrigin上那么戏剧性。

4.3 结果分析解读

先说单峰函数。在Sphere上,CSOS和GWO都收敛到了非常低的水平,CSOS略胜一筹;在Rosenbrock上,CSOS有优势但不算碾压。原因在于单峰函数只有一个极值,算法靠的是“快而准”的收敛能力,CSOS的自适应收益因子在这里发挥了作用——后期BF值变小,更新步长缩短,精度自然上去了。

Rastrigin是最能体现改进价值的一个函数。原始SOS的均值是8.53,CSOS直接降到0.91,提升接近一个数量级。这说明动态寄生概率和精英保留的组合,确实增强了跳出局部最优的能力。Ackley上的结果也类似,CSOS不仅均值更低,标准差也更小,这意味着稳定性更好,不是偶尔碰到一次好结果。

和PSO、WOA对比时,CSOS在多峰函数上的优势比较明显,但在平滑函数上没有明显碾压。这很正常——没有哪个算法能“全场景称霸”,CSOS的设计目标本来就是增强SOS的多峰寻优和稳定性,而不是把每个指标都做到第一。如果只看Sphere,GWO和CSOS差别不大;但看Rastrigin,CSOS基本算是拉开了差距。

4.4 收敛曲线观察

从收敛曲线来看,CSOS和原始SOS的最大区别在中后期。前期两者几乎重合,因为混沌初始化带来的优势主要体现在起始种群的分布上,不会立刻反映到曲线里;但到了迭代的中后段,原始SOS的曲线逐渐走平,而CSOS还在小幅但持续地下降。这种“后期还有力”的特征,正是自适应收益因子和动态寄生概率共同作用的结果。

如果你也想做算法对比,我强烈建议把收敛曲线也画出来。光看最终数值,你会漏掉很多信息——比如一个算法最终结果更好,但前期早熟、中期停滞了半天突然跳出来,和另一个算法稳步下降,虽然最终数值接近,但稳定性差异很大,光靠均值表是看不出来的。我在分析时习惯把30次运行的最优曲线画成粗线,把上下边缘画成浅色阴影带,一眼就能看出算法是稳定下降还是剧烈震荡。

4.5 统计检验的必要性

只看均值和标准差还不够严谨。元启发式算法的随机性很强,可能CSOS在某个函数上均值好看,只是运气好碰到几次特别好的运行。我建议至少做一次Wilcoxon秩和检验,把CSOS的结果和其他算法逐对比较,看差异是否显著。实际操作中,我用SciPy的ranksums函数跑了所有配对,CSOS在Rastrigin和Ackley上相对SOS的p值都小于0.01,说明性能提升不是随机噪声带来的。这项检验不花多少时间,但能极大提高结果的可信度,尤其是要写论文或者在正式场合汇报的时候,尽量别省这一步。

5. 踩坑记录与避坑指南

5.1 自适应因子初值别贪心

我第一次把BF1的下限定到1.2,想着让后期更新步长更大一点,结果Rastrigin上表现反而变差了。原因很简单,后期需要的是微调,步长太大导致在最优值附近反复横跳,收敛精度反而下降。后来把BF1范围控制在[1.6, 2.0]区间,BF2控制在[0.8, 1.2]区间,效果才稳定下来。这个教训让我意识到,改进算法的参数不是越大越好,也不是“变化越剧烈越好”,平衡才是关键。

5.2 寄生概率不是越高越好

我也试过把P_par调高到0.8,想着跳出力越强越好。结果是中后期种群震荡严重,精英保留机制也扛不住高频扰动,最终结果甚至比原始SOS还差。寄生机制是一把双刃剑:它可以破坏局部最优的陷阱,但敌我不分,把好解也一起破坏了。我的经验是P_par取0.2到0.4之间比较合理,突变维度比例的上限也不要超过0.6,否则整个种群会被过度随机化,失去方向。

5.3 对比实验的公平性陷阱

这里有个非常容易被忽略的点:不同算法每轮迭代的评价次数是不同的。PSO每轮每个粒子只评价1次,GWO评价1次,但SOS的互利共生阶段一次性生成两个新解,等于一个个体一轮最多评价2次,粗看种群规模一样、迭代次数一样,实际评价次数却差了一倍。所以严谨的对比必须统一评价次数上限,而不是统一迭代次数。

另一个坑是初始种群。为了公平,所有算法的第一代种群应当来自同一套随机种子生成的同一组初始解,否则某些算法可能因为运气好抽到好的初始分布而拔高成绩。我后来写了一个脚本,读取同一个随机种子生成初始解,再塞给不同算法,保证了起点完全一致。这两个问题如果你在对比时不留神,写出的结论很可能是错误的。

5.4 边界条件别马虎

寄生向量可能出现越界,这很容易漏。我在初版代码里没做越界处理,导致寄生阶段生成的一些维度超出搜索空间,结果在多峰函数上经常飘出异常值。处理方式有两种:直接裁剪回边界,或者重新随机生成到边界内。实测裁剪方式效率更高,但会丢失一些极端探索的可能性;重新生成更均匀但费时。我最终选的是裁剪,因为CSOS的寄生扰动本身就是随机的,不需要依赖越界来探索。

5.5 混沌初始化的点位禁忌

用Logistic映射时,如果随机种子恰好落在0.25、0.5、0.75附近,映射会快速收敛到固定点,生成的“混沌序列”完全失效。在代码里要加一个判断:当Z0落在这些特殊值时,重新取随机数。这个小bug花了我半个下午才定位到,因为表面上看初始种群并没有异常,但后续迭代性能莫名其妙地差,最后逐行检查初始序列才发现整列Z值都“粘”在一起了。希望大家别踩同样的坑。

写在最后的一点体会

整套CSOS改下来,我最深的感触是:改进算法不是把论文里那些“高级词”堆一起就好,关键是先吃透原始算法到底哪里弱,再对症下药。SOS底子不差,问题出在参数僵化和后期乏力,我的三个策略正是针对这两点做的。如果你只是普通工程优化问题,原始SOS加一个精英保留可能就够了;但如果是多峰、高维、精度要求高的场景,CSOS这套组合拳会更有底气。

还有一个很实际的建议:不要盲目照搬任何论文的参数。算法参数的“最优值”永远是跟问题相关的。这篇里给的参数范围是基于30维标准测试函数的经验值,换到不同维度、不同问题规模后,先用小规模实验扫一遍参数,比直接套上去跑大规模要省事得多。改进优化的路就是这样——一次跑通不算成功,能稳定复现才算真的掌握。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询