首页 / 资讯中心 / 文章详情

基本粒子群算法原理与MATLAB实现:从公式到避坑指南

基本粒子群算法原理与MATLAB实现:从公式到避坑指南 ★ FEATURED ARTICLE
简介面向算法初学者与优化问题研究者的粒子群优化算法原理讲解与MATLAB实现文档系统梳理了算法模拟鸟群捕食行为的核心思想说明速度与位置更新公式中惯性权重、认知系数、社会系数等参数的含义并对比了全局与局部两种PSO模式的区别。文档从随机初始化粒子群入手逐步讲解个体极值与全局极值的跟踪机制、迭代停止条件的设定以及参数调整对收敛速度和解质量的影响并附带基本流程说明。资源为单个doc文档压缩包约39KB已有50人次浏览学习文档可作为算法课程作业、毕业设计或科研入门阶段的参考也适合需要快速了解PSO原理并对照MATLAB代码理解参数作用的读者使用。1. 基本粒子群算法是什么一份word版资料背后的标准实现无论是赶课程作业还是做课题预研很多人手里都躺着一份《完整word版基本粒子群算法的原理和matlab程序(1).doc》。这份资料讲的基本粒子群算法Particle Swarm OptimizationPSO是Kennedy和Eberhart在1995年提出的群体智能算法思路非常直白把每个候选解看成一只在解空间里飞的鸟靠自身经验和群体经验不断修正方向最终逼近最优解。它能处理连续变量和离散变量两类优化问题PID参数整定、路径规划、神经网络超参数搜索都是它的常见战场最大优点是目标函数不需要可导黑匣子也能优化缺点是收敛精度依赖参数整定容易早熟。这篇文章按我跑通并改过多轮的经验把原理、MATLAB实现、参数整定和踩坑一次讲清楚新手能照抄代码熟手可以回来核对参数。2. 从速度-位置更新公式到收敛性三个分量决定粒子往哪儿飞先把原理立住后面调参数、改代码才有依据。网上这类word版资料最值钱的部分就是原理推导但往往写得太简略读者只知道“照着公式敲进MATLAB”不知道为什么要这么写。这里我把标准PSO的数学模型完整拆开讲清楚位置、速度、个体最优和全局最优之间的关系。2.1 标准PSO的数学模型位置、速度、pbest与gbest假设要优化的是一个n维变量x [x1, x2, ..., xn]每个变量都有上下界。PSO维护一组“粒子”每个粒子是解空间里的一个点同时携带一个速度向量。第i个粒子在t时刻的状态由两部分描述位置向量xi(t)和速度向量vi(t)。位置就是候选解速度决定了下一轮朝哪个方向走、走多远。目标函数f(x)的返回值称为适应度PSO默认按最小化处理最大化问题取负号或取倒数即可。每个粒子在飞行过程中记住自己见过的“最好位置”记为pbest_i整个种群共享一个“最好位置”记为gbest。pbest和gbest就是粒子群的两个核心记忆前者是个人经验后者是社会经验。初始化时pbest_i就是该粒子的初始位置gbest则是全体粒子中适应度最好的那个位置。这套模型没有复杂的梯度信息只需要反复算目标函数值。所以它天生适合那些“能算出结果、但求不出导数”的问题这也是为什么很多工程优化场景宁可忍受它慢也要用它。2.2 速度-位置更新公式拆解惯性项、认知项和社会项标准PSO的每一次迭代做两件事先更新速度再更新位置。速度更新公式是全部算法的核心v_i(t1) w·v_i(t) c1·r1·(pbest_i − x_i(t)) c2·r2·(gbest − x_i(t))然后位置更新x_i(t1) x_i(t) v_i(t1)其中w是惯性权重c1和c2是学习因子r1和r2是每次独立采样的[0,1]均匀随机数。速度公式可以拆成三个分量来理解。第一项w·v_i(t)叫惯性项意思是粒子保留一部分上一时刻的运动趋势。w越大粒子越“不愿意转弯”飞行路径更开阔有利于探索大范围区域w太小粒子很快被后续两项牵着走容易早熟。第二项c1·r1·(pbest_i − x_i(t))叫认知项把粒子朝自己历史上最好的位置拉。随机系数r1让这个拉力每轮都在变避免粒子太机械地奔向pbest。第三项c2·r2·(gbest − x_i(t))叫社会项把粒子朝当前群体最优位置拉。c1和c2的相对大小决定了粒子是更“相信自己”还是更“相信群体”。注意r1和r2每一轮都重新随机采样而不是取固定值。这样即使两个粒子位置完全一样下一轮速度方向也会不同保持了搜索的多样性。位置更新则是简单的“老位置加新速度”如果把速度看成步长位置更新就是沿速度方向走一步。实际代码中还要限制速度幅值不能超过vmax否则粒子可能一步跨出整个解空间。2.3 收敛性条件与参数约束w、c1、c2的关系为什么标准PSO建议w取0.9左右、c1和c2都取2.0这不是拍脑袋而是从收敛性推导出来的。把速度更新公式代入位置更新公式可以消去速度得到关于位置xi的二阶差分方程其收敛性取决于特征根是否落在单位圆内。经典结论是当w 1且满足 w (c1 c2) / 2 − 1 时粒子系统是稳定的不会发散。这个条件解释了三个常见现象。第一w必须小于1否则粒子速度只增不减种群必然爆炸。第二c1 c2 最好不要超过4超过后即使w取0.9稳定性条件也可能被破坏。第三c1和c2取2.0是1998年Shi和Eberhart论文里的经典配置配上w从0.9线性降到0.4就是所谓“基本粒子群算法”的标准形态这也是绝大多数word版资料的默认参数。后来Clerc引入了收缩因子概念用χ 2 / (φ − 2 sqrt(φ² − 4φ))φ通常取4.1算出χ约等于0.7298。等价到学习因子上就是c1 c2 1.49445。这组参数在数学上保证了收敛性很多MATLAB优化工具箱的实现用的就是收缩因子版本。实际用下来1.49445比2.0更稳但收敛可能略慢一点2.0则搜索更活跃在简单问题上收敛更快。参数经典取值作用说明w0.9 → 0.4线性递减控制探索与开发平衡前期大w跑得开后期小w收得细c12.0 或 1.49445个体认知拉力太大容易各飞各的c22.0 或 1.49445群体社会拉力太大容易集体早熟vmax边界跨度的10%~25%限制步长防止粒子一步飞出解空间搞清楚这些再看word里的程序就知道为什么作者要写w_update wmax - (wmax - wmin)*iter/maxiter为什么c1和c2默认填2.0。参数不是随便定的都对应着这条收敛性链条。3. 用MATLAB写一个最小可运行的PSOSpher测试函数与45行核心代码原理落在纸面上只是一页公式能不能跑通才是关键。我在处理这类word版资料时第一步永远是把程序缩减成一个“最小可运行版本”能跑、能画收敛曲线、能调参别的花哨功能一律不要。这样排查问题最快也方便后面逐个加改进策略。3.1 目标函数选型为什么先用Sphere测试函数PSO的第一道坎是目标函数怎么写。很多word程序把目标函数和主循环写在一个文件里导致换一个优化问题就要改一堆代码容易改错。更好的做法是把目标函数单独写成函数文件接口统一为“输入一个矩阵输出一个列向量”。测试函数我建议从Sphere开始f(x) sum(x_i²)。它有四个特点适合新手连续、单峰、全局唯一最小值在原点、最优值已知为0。如果PSO连Sphere都搜不到理想结果那肯定是更新逻辑有bug而不是算法本身的问题。等Sphere跑通了再换成Rastrigin这类多峰函数考验全局搜索能力。3.2 写第一个函数文件my_func.m% my_func.m % 目标函数Sphere 函数 % 输入 xnPop × nVar 矩阵每一行是一个粒子 % 输出 fnPop × 1 列向量每一行是该粒子的适应度 function f my_func(x) f sum(x .^ 2, 2); % 按行求和得到列向量 end这个文件只有一行核心代码但接口约定决定了后面主程序能不能写成矩阵化形式。sum(x.^2, 2)的第二个参数2表示按行求和输出是nPop×1的列向量。如果去掉2x是矩阵时sum(x.^2)会返回一个1×nVar的行向量后续和pbest_f做比较时维度对不上MATLAB直接报“Matrix dimensions must agree”。想换测试函数时只需要改动my_func.m这一处。比如Rastrigin函数的MATLAB写法是f sum(x.^2 - 10cos(2pi*x) 10, 2)函数文件的其他部分不用动。3.3 写主程序pso_demo.m% pso_demo.m % 基本粒子群算法标准PSO求解 Sphere 函数 % 变量范围[-100, 100] 内找 sum(x.^2) 的最小值 clear; clc; %% 参数区 nVar 10; % 变量维度 lb -100; % 变量下界 ub 100; % 变量上界 maxIter 200; % 最大迭代次数 nPop 40; % 种群规模 w_max 0.9; % 惯性权重上限 w_min 0.4; % 惯性权重下限 c1 2.0; % 个体学习因子 c2 2.0; % 群体学习因子 vmax 50; % 速度上限取边界跨度的1/4 %% 初始化种群 % 每一行是一个粒子位置 x、速度 v、历史最优位置 pbest_x x lb (ub - lb) * rand(nPop, nVar); % 均匀随机初始化位置 v -vmax 2 * vmax * rand(nPop, nVar); % 对称随机初始化速度 pbest_x x; % 初始个体最优就是自身 pbest_f my_func(x); % 初始个体最优适应度 %% 确定初始全局最优 [best_f, gbest_idx] min(pbest_f); gbest_x pbest_x(gbest_idx, :); history zeros(maxIter, 1); %% 主循环 for t 1:maxIter w_t w_max - (w_max - w_min) * (t / maxIter); % 惯性权重线性递减 % 更新速度惯性项 认知项 社会项 % repmat 把 gbest_x 复制成 nPop 行兼容 R2016a 之前的版本 v w_t * v ... c1 * rand(nPop, nVar) .* (pbest_x - x) ... c2 * rand(nPop, nVar) .* (repmat(gbest_x, nPop, 1) - x); % 速度限幅防止粒子一步跨出解空间 v max(min(v, vmax), -vmax); % 更新位置 x x v; % 越界粒子拉回边界吸收型边界 x max(min(x, ub), lb); % 重新计算所有粒子的适应度 f my_func(x); % 更新个体最优新位置比历史最优好就替换 better_mask f pbest_f; pbest_x(better_mask, :) x(better_mask, :); pbest_f(better_mask) f(better_mask); % 更新全局最优 [f_min, idx] min(pbest_f); if f_min best_f best_f f_min; gbest_x pbest_x(idx, :); end history(t) best_f; end %% 输出结果 fprintf(最优解 x* 的前5个分量\n); disp(gbest_x(1, 1:min(5, nVar))); fprintf(最优适应度 f* %.6e\n, best_f); % 画收敛曲线加eps防止对数坐标下出现log(0) figure; semilogy(history eps); xlabel(迭代次数); ylabel(全局最优适应度); grid on;代码的核心在更新速度的四行。w_t * v保留了上一轮速度乘以衰减后的惯性权重c1 * rand(nPop, nVar) .* (pbest_x - x)让每个粒子朝自己的历史最优靠拢随机数保证拉力方向每轮有抖动c2那项同理朝全局最优靠拢。repmat把gbest_x从1×nVar扩展成nPop×nVar这样矩阵减法是逐行运算R2016a之前的老版本MATLAB也能跑。better_mask这段是体现MATLAB风格的写法。f pbest_f得到一个逻辑向量true表示该粒子找到了更好的位置直接用它索引替换避免写for循环逐个判断。粒子多、维度高时这种向量化写法比循环快一个数量级以上这也是word里老式循环代码最常见的性能问题来源。跑完之后看两个东西一是终端打印的最优适应度Sphere问题上应该到10^-6以下才算正常二是收敛曲线应该平滑下降最后趋于水平。如果最优值停在几十甚至几百优先检查vmax是否太大、w_t是否衰减到0.4以下以及目标函数是否写错维度。4. 参数整定的三个必调项惯性权重、学习因子与种群规模很多读者拿到能跑的PSO程序后第一件事就是调参数。可调的东西看着很多实际上最值得动的只有三个惯性权重w、学习因子c1/c2、种群规模nPop。速度上限vmax和边界处理方式属于“设对一次就不用再动”的次要参数。这一章把每个参数的调节逻辑讲透顺带给出我常用的取值表。4.1 惯性权重w线性递减为什么是默认第一选择基本粒子群算法里w是最敏感的参数没有之一。Shi和Eberhart在1998年给的标准方案是w从0.9线性降到0.4这个方案沿用至今理由很朴素迭代前期粒子需要大范围探索w大一点速度的惯性成分高粒子不容易被某个局部最优立刻吸走迭代后期需要精细搜索w小一点粒子在最优解附近慢慢磨。线性衰减的实现就是主程序里那一行w_t w_max - (w_max - w_min) * (t / maxIter)。这个写法把最大权重、最小权重和当前进度绑定在一起改起来也方便。如果目标函数是简单的单峰函数可以把w固定成0.6省去衰减逻辑收敛速度反而更快如果是多峰函数强烈建议保留衰减否则后期粒子绕圈不收敛。调w时最直观的判断看收敛曲线形状。曲线下降很快但最后停在明显偏大的值说明w降得太快或w_min太高粒子没时间精细搜索。曲线一路震荡、尾端还在上下跳说明w_t下降不到位后期惯性仍偏大。还有一种情况是曲线开头就平那大概率不是w的问题而是vmax太小粒子飞不出去。4.2 学习因子c1、c2怎么判断粒子在“盲飞”c1和c2分别控制粒子对个人经验和群体经验的信任程度。c1过大每个粒子只盯着自己的pbest飞群体信息被忽略整体表现为“各飞各的”收敛慢c2过大所有粒子一股脑冲向当前gbest如果gbest是个局部极值整个种群就集体早熟。所以调这两个参数的实质是平衡探索与开发而不是把单值调得越大越好。word版资料最常见的配置是c1 c2 2.0这个组合搜索积极但在多峰问题上容易震荡。如果换成收缩因子版本c1 c2 1.49445配合w固定0.7298收敛会更稳。还有一种异步时变方案c1从2.5线性降到0.5c2从0.5线性升到2.5。前期让粒子更相信自己的探索后期让群体共识主导对Rastrigin这类陷阱多的函数比固定2.0有效。判断粒子是否在“盲飞”可以打印中间过程。如果迭代到一半所有粒子的位置差、速度差都很小但gbest还没有达到理想值说明c2的拉力太强粒子过早扎堆如果迭代结束粒子分布还很散说明c1过强或c2过弱。调参本质上是个玄学不同目标函数的最优参数组合不一样我的习惯是先固定w的衰减方案再以10%步长扫描c1和c2每次跑完记录收敛曲线而不是一次调三个参数。4.3 种群规模、速度上限与边界处理的取值表种群规模nPop决定每代计算量计算量等于nPop乘以nVar再乘以目标函数耗时。维度低于10的简单问题nPop取20~40足够维度到30以上建议取50~100。不要盲目加到几百因为PSO的信息共享是全局的种群到一定规模后再加人收敛速度提升有限计算成本却线性增加。速度上限vmax影响单步最大移动距离。我习惯取边界跨度的10%~25%比如变量范围[-100, 100]跨度为200vmax取20到50。vmax太小粒子被限速搜索效率低vmax太大粒子频繁撞边界速度限幅形同虚设。边界处理方式有三种word程序里最常见的是“拉回边界”即越界后直接赋值为边界值实现简单但粒子容易在边界堆积尤其是最优解靠近边界时。参数建议范围调试优先级我的默认配置nPop20 ~ 100高40维度30时取80w0.9→0.4线性衰减高按Shi方案c1, c21.0 ~ 2.5中2.0多峰时换1.49445vmax边界跨度的10%~25%低20%边界处理吸收/反射/周期低吸收型早熟时改反射型一套配置先用默认值跑通再单独调最敏感的那一两个参数。我见过最多的失败是同时改五个参数结果收敛曲线变差都不知道是哪个参数惹的祸。5. 避坑与排查这份word版程序最常见的5个翻车现场这类word版的基本粒子群算法程序大多是从教材或论文里抄下来的循环版本结构差不多问题也高度相似。我把调过的程序里最常见的五个坑按“现象→原因→解决”列出来照一条条对就能省下大半天血泪排查时间。5.1 报错“Matrix dimensions must agree”——目标函数写成标量现象把word代码粘进MATLAB第一次运行就报维度不匹配错误指向目标函数那一行。原因目标函数里写的是f sum(x.^2)而传入的x是nPop×nVar矩阵sum默认按列求和输出变成1×nVar行向量。主程序拿它和pbest_f比较时要么维度对不上要么广播出错。解决目标函数一律按“输入矩阵、输出列向量”的约定写核心改动就是把sum(x.^2)改成sum(x.^2, 2)。如果是从别人手里接的代码先在命令行执行size(f)确认目标函数返回的维度再动主程序。5.2 粒子全堆在边界结果停在很差的解上现象迭代曲线下降几步后就不动了打印gbest发现它的分量正好等于边界值很多粒子也贴在边界上。原因word程序里越界处理用的是x max(min(x, ub), lb)这叫吸收型边界。粒子撞墙后贴在墙上下一轮速度如果还指向墙外又被拉回来墙面成了隐形陷阱。解决换成反射型边界把越界的粒子折回界内速度反向% 反射型边界越上界折回越下界折回速度反向 over_ub x ub; over_lb x lb; x(over_ub) 2 * ub - x(over_ub); x(over_lb) 2 * lb - x(over_lb); v(over_ub | over_lb) -v(over_ub | over_lb);反射型适合最优解在边界附近的问题粒子不会堆积在墙上搜索空间利用得更充分。代价是多两行代码以及目标函数在边界附近不光滑时反射会引入轻微的振荡。这个改动属于收益远大于成本的类型。5.3 惯性权重不衰减迭代曲线像心电图现象收敛曲线下降一段时间后开始上下跳动尾部完全不平滑。原因程序把w固定成0.9从不衰减。后期粒子速度惯性太大每轮都从最优解身边冲过去被认知项和社会项拉回来再冲过去形成振荡。解决改成线性衰减w从0.9降到0.4。如果衰减后仍然震荡把w_min降到0.3或者把vmax缩小到边界跨度的10%。判断标准很简单收敛曲线尾部应该是一条接近水平的线而不是锯齿状。5.4 结果每次跑都不一样无法复现现象同一份程序、同一组参数连续跑三次最优值和收敛曲线都有差异有时候结果好得离谱有时候差得离谱。原因rand()依赖MATLAB的全局随机数流每次启动流状态不同。这不是bug是随机算法的固有特性但调参时就成了大坑——你分不清曲线变好是因为参数改对了还是单纯运气好。解决程序开头加一行rng(0)固定随机数种子。调参阶段务必固定种子组间对比才有意义确定最终参数后可以去掉种子多跑几次看稳定性。5.5 早熟停滞gbest不动了粒子扎堆现象gbest从第30代开始就再也不更新但已知理论最优值还没达到进一步打印x矩阵发现各行几乎完全相同粒子全部挤在一个点附近。原因所有粒子收敛到同一个位置后pbest趋同认知项和社会项都趋近于零剩下的只有惯性项。如果这个点是局部极值粒子就彻底失去逃逸能力。解决检测种群多样性或gbest停滞代数触发随机重置。下面这段代码插在主循环gbest更新之后即可逻辑是连续30代没有更新gbest就把30%的粒子重新撒回解空间% 停滞检测并重置部分粒子插在主循环gbest更新之后 if noChange 30 reset_idx randperm(nPop, ceil(0.3 * nPop)); x(reset_idx, :) lb (ub - lb) * rand(length(reset_idx), nVar); v(reset_idx, :) vmax * (2 * rand(length(reset_idx), nVar) - 1); noChange 0; end注意这段代码共用了主程序里的变量重置只改变位置和速度不动pbest和gbest。这样重生的粒子带着群体的记忆重新搜索一旦找到更好的位置会自然更新pbest不会破坏已收敛的部分成果。下一轮主循环会自动重新计算这些粒子的适应度所以不需要额外补算。一个更温和的做法是给惯性权重w_t加一个下限比如不低于0.5让粒子保留一定的搜索活力。但根治早熟还得靠多样性恢复这是第6章要展开的进阶技巧。6. 一个能直接抄的进阶技巧基于停滞检测的自适应变异PSO处理word版程序时我做的最多的改造就是给标准PSO加“后悔药”。标准PSO的致命弱点是没有多样性恢复机制一旦早熟就只能看着gbest卡住。前面5.5给了一段最简重置代码这一章把它扩展成一个可以嵌入几乎所有PSO程序的自适应变异模块并讲清楚怎么验证它真的有效。6.1 在原有主循环里插入停滞检测与重置思路是维护一个noChange计数器每一轮如果gbest没有改善计数器加1一旦改善清零。当计数器达到阈值说明算法已经停滞此时对一部分粒子重新初始化但保留它们的pbest让群体带着记忆重新探索。完整的嵌入逻辑如下可以直接替换第3章主循环尾部% 主循环尾部追加停滞检测与自适应重置 if best_f last_best last_best best_f; noChange 0; else noChange noChange 1; end if noChange 40 % 连续40代没有改善判定早熟 reset_idx randperm(nPop, ceil(0.25 * nPop)); x(reset_idx, :) lb (ub - lb) * rand(length(reset_idx), nVar); v(reset_idx, :) vmax * (2 * rand(length(reset_idx), nVar) - 1); noChange 0; % 重置完成后重新计时 end阈值40和重置比例25%是经验值。阈值太小粒子还没来得及在局部区域精细搜索就被重置浪费前期计算阈值太大早熟后要白跑很多代。重置比例太高会破坏已经收敛的结构太低又起不到恢复多样性的作用。我一般在多峰函数上用40代加25%单峰函数上把阈值放大到80代。6.2 判定改造是否有效的验证方法不要只看最后一行最优值就下结论。我的验证步骤是固定rng(0)分别在Rastrigin函数上跑标准PSO和加了自适应重置的PSO各三次记录每次的最优值和“首次达到该值的迭代次数”然后对比。标准PSO大概率会掉进某个局部极值而带重置的版本由于粒子被反复重新撒网最终值通常更接近理论最优。我自己早期用固定w的word程序跑Rastrigin调了三天参数始终陷在局部最优里最后发现自己犯的错就是“只在参数上使劲没在机制上动手”。给粒子留一条重新出发的路比把w、c1、c2调到某个玄学组合更管用。这个改造同样适用于深度学习MATLAB场景下的超参数搜索——把PSO当成基线优化器早熟问题不解决搜出来的学习率组合很容易骗过你的眼睛。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站