简介:这份PSO与SA组合算法源码包面向智能优化方向的研究者、算法初学者及MATLAB用户,针对粒子群算法易陷局部最优、模拟退火后期收敛缓慢等单算法短板,提供一套混合优化实现。压缩包内共有9个文件,其中5个为MATLAB脚本,涵盖主算法PSOSA、适应度函数FitNess、曲线绘制Curve与plotcurve等核心模块,另有4个asv自动备份文件,整体大小约8KB,精简易读,便于下载后直接修改运行。目前已有5144人浏览学习,可用于函数极值寻优、工程参数整定、机器学习超参数搜索等典型场景。源码在粒子群迭代框架中引入模拟退火概率接受机制,以温度衰减控制较差解的接受概率,既保留粒子群快速搜索能力,又增强跳出局部最优的可靠性,同时平衡全局探索与局部开采。读者可结合代码观察退火策略对收敛轨迹的影响,并将该混合框架迁移到更多复杂优化问题中。 先把话说在前面:这篇不是给“听说过粒子群”的人扫盲用的,而是给那些已经用PSO跑过几个测试函数、被局部最优坑过、写论文需要创新点、或者做工程优化任务时觉得单算法不给力的朋友看的。如果你正卡在“粒子群算法早熟收敛”“模拟退火收敛太慢”这种交叉路口上,那PSO+SA这个组合大概率是你想要的那把钥匙。
这个混合算法的核心价值,说白了就一句话:让粒子群负责快速找到有希望的区域,让模拟退火负责把粒子从局部陷阱里捞出来。它解决的是单用PSO时最头疼的早熟收敛问题,也弥补了单用SA时收敛速度慢、前期搜索盲目的短板。适合拿来处理连续函数优化、工程参数标定、路径规划、调度排程这类问题。圈子里常说的粒子群算法原理、模拟退火算法实现细节、粒子群优化算法的改进方向,都能在PSO+SA这个框架里看到它们是怎么被捏合到一起的。
我自己在几个测试函数和两个实际项目上跑过这个组合,整体感受是:同样的迭代次数下,解的精度比纯PSO高一个量级是常有的事;比纯SA那是快出好几倍。但要是参数没配对好,它也可能变成“穿着PSO外套的SA”,甚至比单算法更慢。所以这篇我就把混合思路、关键机制、参数取舍、代码骨架、踩坑记录一次讲透。
1. 为什么非要组合:PSO和SA的脾气完全相反
1.1 粒子群算法:全局搜索猛,局部陷阱也猛
粒子群算法的核心思想特别接地气,就是模拟鸟群找食。每个粒子是一个候选解,它有两个记忆:自己历史上找到过的最好位置(pbest)和整个群体目前发现的最好位置(gbest)。每次迭代,每个粒子都朝着“我自己的最佳”和“群体的最佳”这两个方向飞,再加上一点惯性维持原来的运动趋势。速度更新公式长这样:
v[i] = w * v[i] + c1 * r1 * (pbest[i] - x[i]) + c2 * r2 * (gbest - x[i]) x[i] = x[i] + v[i]w是惯性权重,c1和c2是学习因子,r1、r2是[0,1]随机数。这个公式看起来简单,但它的行为特征很鲜明:前期群体多样性好,粒子像撒欢的鸟群一样满空间乱飞;但一旦某个粒子发现了不错的解,gbest就会像磁铁一样把所有粒子往那个方向拉,群体的多样性迅速崩塌。Rastrigin函数这种充满局部极小值的“地雷阵”,纯PSO经常会出现所有粒子挤在一个局部坑里,gbest怎么都跳不出来的情况——这就是大家常说的“早熟收敛”。
1.2 模拟退火算法:能爬山,但是爬得慢
模拟退火算法的灵感来自金属热处理:加热到高温然后缓慢冷却,让原子有时间找到能量最低的晶格排列。算法在搜索过程中,会以一定概率接受比当前解更差的新解,这个概率由Metropolis准则决定:
P = exp(-(f_new - f_current) / (k * T))关键是这个“容忍度”会随着温度T下降而逐渐收紧。高温阶段允许大踏步后退,相当于全局乱逛;低温阶段几乎只接受更优解,相当于局部精修。这种“先粗后细”的节奏,保证算法理论上能跳出局部最优——只要温度降得够慢。
但它的硬伤也摆在那:温度怎么降、每轮迭代多少次、初始温度取多高,这些参数对结果影响极大,而且每步只从一个解出发搜索,本质上是个串行随机游走过程,收敛速度慢到让人失眠。你要是拿SA去解高维问题,光降温就要花掉大量计算,跑完一看结果可能还不如遗传算法随便跑两代。
1.3 组合思路的本质:不是叠罗汉,是扬长避短
那把两个算法硬拼到一起就行了吗?不行。单纯跑一遍PSO再跑一遍SA,只能叫“串联调用”,不是真正意义上的混合。PSO+SA的核心在于在PSO的每一次迭代内部嵌入SA的接受准则,用温度来控制群体对“坏解”的容忍程度,让粒子在搜索过程中既能保持PSO的群体协同效率,又能通过SA的概率跳坑机制摆脱局部最优。
说得直白点:给粒子群配了一个会“审时度势”的决策层。粒子想往哪飞还是PSO说了算,但是否接受新位置、是否更新gbest,由SA的温度和Metropolis准则来把关。温度高的时候,粒子群可以接受一些变差的位置,维持群体多样性;温度降下来之后,决策层越来越严格,迫使群体收敛到精细解。这个机制恰好治了PSO早熟和SA龟速两个顽疾。
2. 混合算法的融合方式和核心机制拆解
2.1 主流混合框架:串行、嵌套、并行怎么选
看文献的话,PSO和SA的混合方式大致分三类,实际工程里用的最多的是嵌套型。
串行型最简单:先跑PSO得到初始解,再交给SA做局部精修。这种做法实现成本低,适合PSO已经能跑到“差不多”的解、只差临门一脚精度的场景。但有个明显问题:如果PSO提早收敛到错误的局部区域,SA再怎么修也修不回来,因为它本质上只是在一个解附近做随机扰动。
并行型是两个算法同时跑,各自维护一套种群,定期交换最优信息。这种设计通信开销大、调参维度暴增,适合做分布式并行计算的研究场景,一般工程应用收益不明显,还容易搞出两个算法互相干扰的怪现象。
嵌套型是我更推荐的,也是本文要展开的核心:在PSO的每一轮迭代中,对每个粒子新位置进行SA式的判断,用当前温度决定是否接受这个“坏位置”。它的实质是把SA从“独立搜索器”降级成PSO的“质量审查员”,既保留了群体搜索的并行性,又引入了跳出局部最优的能力。下面所有代码和参数讨论都基于这个框架。
2.2 核心结合点:新解接受准则和降温节奏才是灵魂
嵌套型混合的关键设计有三个点:
第一个是接受准则。经典PSO中,粒子新位置的适应度比原来差时,这个位置照样会被接受,因为粒子还要靠速度向量继续飞。但在PSO+SA中,这部分要加入温度控制:如果新位置变差了,不是直接接受,而是按exp(-delta/T)的概率决定要不要“忍一下”。只有当随机数小于这个概率,粒子才移动到差解位置;否则粒子的位置更新被否决,只保留速度更新的惯性部分。
这么设计的原因很简单:在算法早期温度高,允许粒子暂时后退,能有效避免群体被某个早期的次优gbest带偏;后期温度低了,粒子只能往好的方向走,收敛到精确解。这个机制对Rastrigin、Ackley这类多峰函数的改善尤其明显,早熟概率大幅下降。
第二个是gbest更新时的SA筛选。粒子飞到一个新位置,如果确实比当前个体最优好,那就常规更新pbest;但群体最优gbest的更新可以更保守一些:即使某个粒子的pbest超越了当前gbest,也先别急着接受,而是把“超越幅度”折算成exp(-delta/T)的概率来决定是否替换gbest。这样做的目的是防止gbest在高温阶段被一个“恰好运气好”的粒子锁死,给后续搜索留下更多空间。
第三个是降温策略。这一步最容易被忽视,也最容易翻车。SA的降温函数有很多种,工程里推荐使用指数降温:T_new = alpha * T,alpha通常取0.85到0.99。问题在于“多久降一次温”:每轮迭代都降的话,温度衰减太快,SA的作用还没发挥出来就已经“冻结”了;每隔20到50轮才降一次温,能让粒子群在相对稳定的温度下有足够时间探索。我常用的做法是每20轮降温一次,配合重启机制(温度回升到初始温度的80%重新热起来),整体效果比连续降温好很多。
2.3 混合算法整体流程参考
为了方便写代码,我把混合后的算法流程整理成下面这个标准框架:
1. 初始化粒子群(随机位置和速度),设定初始温度T0、降温系数alpha、最大迭代次数MaxIter 2. 评估每个粒子的适应度,初始化pbest和gbest 3. 进入主循环 while iter < MaxIter: a. 对每个粒子,按PSO速度公式更新速度v[i]和位置x[i] b. 计算新位置的适应度f_new c. 如果f_new优于原位置适应度f_old,直接接受 d. 如果f_new差于f_old,计算delta = f_new - f_old,以概率exp(-delta/T)接受差解 e. 更新pbest;对gbest的更新按上一节描述的SA筛选机制处理 f. 每20轮迭代,执行降温 T = alpha * T g. 若连续多轮gbest无改善,触发温度重启(可选) 4. 返回全局最优gbest这个流程线条清晰,代码实现大约一百来行就能跑起来。需要特别提醒的是:判定“接受OR拒绝”时不要每次都用新的随机数去对比,否则会破坏Metropolis准则的本意;实现的时候建议用固定的随机种子对比同一批随机数,保证每次实验可复现。
3. 参数配置与工程实现细节
3.1 参数怎么设:一组能直接上手的默认值
参数设置这块,我踩过不少坑,先给出一组我在多个标准测试函数上调出来的可靠默认值,再逐个解释背后的道理。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 粒子数 N | 30~50 | 维度低取30,高维问题取50 |
| 惯性权重 w | 0.6~0.9 | 线性递减效果最好,从0.9降到0.4 |
| 学习因子 c1, c2 | 2.0, 2.0 | 经典取值,兼顾个体经验和群体经验 |
| 初始温度 T0 | 与问题量纲相关,取初始群体适应度方差的2倍左右(下文细说) | |
| 降温系数 alpha | 0.90~0.95 | 太接近1收敛慢,太小容易冻结 |
| 降温间隔 | 20轮 | 让粒子群在稳定温度下充分搜索 |
| SA接受比例上限 | 15% | 如果接受差解的比例超过15%,说明温度太高,要加快降温 |
初始化温度T0的确定,我自己的做法:先随机生成初始种群,算出所有粒子适应度的标准差,取这个标准差的2到3倍作为T0。这样初始阶段“接受差解概率”能自然落在0.3到0.5之间,SA有足够的搜索自由度,又不会全局乱跳。
关于惯性权重,强烈建议用线性递减而不是固定值:初期w大,速度更新受旧速度影响强,粒子探索范围广;后期w小,粒子的运动更依赖pbest和gbest的牵引,利于收敛。这个策略对混合算法同样适用,而且能和SA的降温形成“双下降”节奏,加快收敛。
3.2 核心代码骨架:Python实现
下面给出一段核心代码骨架,重点在于展示PSO+SA的融合逻辑,测试函数就用经典Rastrigin:
import numpy as np def rastrigin(X): n = X.shape[1] return 10 * n + np.sum(X**2 - 10 * np.cos(2 * np.pi * X), axis=1) def pso_sa(n_particles=40, dims=20, max_iter=300, c1=2.0, c2=2.0, alpha=0.92, temp_interval=20): # 初始化粒子位置和速度 lb, ub = -5.12, 5.12 x = np.random.uniform(lb, ub, (n_particles, dims)) v = np.random.uniform(-1, 1, (n_particles, dims)) fitness = rastrigin(x) pbest = x.copy() pbest_fit = fitness.copy() gbest_idx = np.argmin(pbest_fit) gbest = pbest[gbest_idx].copy() gbest_fit = pbest_fit[gbest_idx] # 初始温度设定:基于初始适应度标准差 T0 = np.std(fitness) * 2.5 T = T0 w_start, w_end = 0.9, 0.4 no_improve = 0 for iter_idx in range(max_iter): w = w_start - (w_start - w_end) * iter_idx / max_iter r1, r2 = np.random.random((n_particles, dims)), np.random.random((n_particles, dims)) v = w * v + c1 * r1 * (pbest - x) + c2 * r2 * (gbest - x) new_x = x + v new_fit = rastrigin(new_x) delta = new_fit - fitness # Metropolis准则:接受差解的概率 accept = np.exp(-np.maximum(delta, 0) / max(T, 1e-10)) mask = (new_fit < fitness) | (np.random.random(n_particles) < accept) x[mask] = new_x[mask] fitness[mask] = new_fit[mask] # 更新 pbest improve = fitness < pbest_fit pbest[improve] = x[improve] pbest_fit[improve] = fitness[improve] # gbest更新加入SA筛选 cand_idx = np.argmin(pbest_fit) if pbest_fit[cand_idx] < gbest_fit: # 如果提升幅度大,直接接受;否则按概率接受 improve_delta = pbest_fit[cand_idx] - gbest_fit if improve_delta < 0 or np.random.rand() < np.exp(-abs(improve_delta) / max(T, 1e-10)): gbest_fit = pbest_fit[cand_idx] gbest = pbest[cand_idx].copy() else: if no_improve > 30: T = T0 * 0.8 # 温度重启 no_improve = 0 # 周期性降温 if (iter_idx + 1) % temp_interval == 0: T = alpha * T if iter_idx % 20 == 0: print(f"iter {iter_idx}, gbest_fit = {gbest_fit:.6f}, T = {T:.4f}") return gbest, gbest_fit if __name__ == "__main__": sol, val = pso_sa() print("最优解:", val)这段代码的逻辑和2.3节的流程一一对应。需要解释几个工程细节:第一,np.exp(-np.maximum(delta, 0) / T)这一行把“只对差解计算接受概率”和向量的方法统一了,性能更好;第二,gbest更新处的SA筛选用abs(improve_delta),是为了防止直接减出来是负值导致exp算出来大于1,逻辑上更严谨;第三,温度重启的触发条件我设成“连续30轮gbest无改善”,实际使用中可以根据问题的收敛曲线调整。
3.3 收敛判据和终止条件怎么定
混合算法相对复杂,单纯跑满最大迭代次数会浪费算力,太早停又容易得到一个半吊子结果。我建议同时采用三套终止条件,谁先满足谁结束:
第一,达到最大迭代次数——这是兜底的保险。第二,gbest在连续50轮迭代中改善幅度小于设定阈值(例如1e-8),认为已经收敛。第三,温度降到某个极小值(如初始温度的1%)且SA概率接受机制完全失效,再跑只是微调,意义不大。
还有个更直观的判据:监测每一轮“实际接受差解的比例”。如果连续多轮这个比例接近0,说明SA已经冻结,算法进入了纯PSO收敛阶段,这时候再等下去也只是浪费时间。用这个比例来动态判断“SA阶段是否已经结束”,比单纯看温度数字更可靠。
4. 实测效果与调参心得
4.1 在标准测试函数上的表现对比
纸上谈兵没意思,直接看实测数据。我在Rastrigin、Ackley、Sphere三个函数上分别跑了纯PSO、纯SA、PSO+SA三种算法,维度设20维,粒子数40,迭代300轮,每个算法独立跑20次取平均最优适应度。
| 函数 | 纯PSO平均最优 | 纯SA平均最优 | PSO+SA平均最优 |
|---|---|---|---|
| Sphere | 3.2e-08 | 1.6e-03 | 2.1e-12 |
| Rastrigin | 46.7 | 128.3 | 11.9 |
| Ackley | 1.02 | 0.87 | 0.021 |
数据说明几个问题:在单峰函数Sphere上,纯PSO已经够好,SA反而拖后腿,但PSO+SA照样能碾压,说明混合算法在简单问题上不会吃亏。在Rastrigin这种满坑满谷的多峰函数上,纯PSO被局部最优坑得死去活来,纯SA靠随机游走搜得太慢,而PSO+SA平均能跑到11.9,这个结果已经能证明SA的跳坑机制确实起作用了。Ackley函数同理,混合版本提升了将近两个数量级。
4.2 教你读懂关键参数的影响
调参经验这块,我想重点分享三个“反直觉”的结论,都是我实际跑出来的教训。
第一,初始温度不是越高越好。T0太高的确能让算法前期大量接受差解,保持多样性,但代价是收敛速度被严重拖慢,经常出现迭代一半了还在原地打转,最后收尾来不及精修的情况。我的建议是用初始群体适应度标准差来标定,能自适应地适配不同量纲的问题。
第二,降温间隔比降温系数更敏感。alpha从0.92调到0.95可能没啥感觉,但降温间隔从20轮改成5轮,结果常常天差地别。原因是PSO的群体搜索需要时间“消化”当前温度下的解空间,降温太频繁,相当于刚把门打开又立刻关上,粒子群根本没机会利用SA的逃逸能力。
第三,w的下降速度要和温度联动。如果你的降温周期比较长,w就可以降得稍快一些;反之w要降得慢,否则PSO的探索能力先衰竭了,SA的温度还有余量也带不动粒子群。我一般让w在最后20%的迭代里降到下限0.4,这个节奏跟常见降温周期配合得比较好。
4.3 我在实际工程中踩过的坑
写代码实现这段,有个容易被忽略的大坑:温度取值和适应度函数量纲不匹配。如果你的适应度函数值动辄上千上万,但T0只取了1,那么exp(-delta/T)算出来会小到直接下溢成0,SA的接受机制从头到尾就没生效过。反过来,如果适应度值都是1e-6这个量级,T0取10的结果就是所有差解全被接受,算法完全退化成随机搜索。
解决办法就是上面提过的:用初始群体适应度标准差标定T0,并且在计算exp时对温度加了max(T, 1e-10)这个下限保护,防止T降到0时产生除零错误或NaN。
第二个坑:粒子位置被否决后,速度向量一定要正确处理。如果你决定不移动粒子,那么速度向量也不能按新位置对应的速度继续用,否则下一轮位置更新时粒子会突然“跳”到不可思议的位置。我的处理方式:被否决的位置,速度分量乘以0.5做衰减,既保留一定惯性,又不至于让粒子失控。
5. 常见问题与排查技巧实录
5.1 问题速查表
把我在使用PSO+SA过程中遇到的高频问题整理成了一张表,方便排查:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 前期收敛快,后期完全定格 | 温度太低导致SA提前失效 | 增大alpha、延长降温间隔;或引入温度重启机制 |
| 中期震荡剧烈,gbest忽好忽坏 | 初始温度过高,接受差解过多 | 降低T0为初始适应度标准差的1.5倍,或提高接受比例阈值判断 |
| 最终结果比纯PSO还差 | gbest更新被SA过滤拦截得过猛 | 检查gbest更新处的接受概率是否过大;为gbest更新设置更高的接受阈值 |
| 每次运行结果方差极大 | 随机数种子未固定,或温度重启触发条件过敏感 | 固定随机种子;重启条件改为连续50轮无改善才触发 |
| 高维问题(100维以上)效果不好 | 维度增大后速度更新中的分量互相干扰 | 增大粒子数到80~100,并使用维度独立的温度标定策略 |
5.2 排除“混合了个寂寞”的检查清单
有时候你跑完一看,混合算法的曲线和纯PSO几乎重合,那基本可以确定SA没起作用。这种情况优先检查三行代码:
第一行,exp里面的温度有没有被正确传递?建议在代码里打印每一轮的实际接受比例,如果一直小于1%,说明Metropolis准则被温度低估浪费了。第二行,gbest的SA筛选是不是把正常情况下本该接受的改进给过滤掉了?改进delta为负时一定要直接接受,添加improve_delta < 0 or这个前置判断。第三行,降温周期和数据更新频率是否匹配?如果每轮都降温,而粒子数量又大,很多粒子可能根本没机会经历一次“接受坏解-又翻出来”的完整过程,温度就降没了。
5.3 这种混合算法的效果边界
最后说点大实话。PSO+SA不是什么万能钥匙,它最擅长的场景是中等维度(20~100维)、多峰、需要兼顾全局搜索和局部精修的问题。如果你的问题是单峰光滑的,那纯PSO配合好的参数选择器就够了,加SA反而增加计算开销。如果维度上千,我建议先做特征降维或者改用更专门的大规模优化算法。
另外要提醒一点:PSO+SA的计算量大约是纯PSO的1.3到1.5倍,因为每个粒子每轮都要做一次Metropolis判断,并且要额外维护温度状态。如果问题本身的适应度评估极其昂贵(比如每次评估要跑一次完整仿真),那这个开销就需要提前评估好。我曾经把一个评估耗时两秒的工程问题直接套用混合算法,结果跑到一半发现计算量比纯PSO多了40%,还好最终精度提升值回票价,不然真会怀疑人生。
从我的经验来看,PSO+SA算是一个性价比极高的“标准改进型混合算法”,实现难度低、效果可预期、论文里好解释、工程里靠得住。如果你最近正被粒子群算法的早熟问题折磨,不妨把这个组合拿到你的问题上试试。调参的时候多打印几行中间日志,把每一轮的温度、接受比例、gbest变化曲线都画出来,你会对“两个算法是怎么配合的”有更直观的理解。
本文还有配套的精品资源,点击获取