
做VRP这块的人对粒子群算法PSO大概都有种复杂情绪它上手快代码短但想靠原始PSO在带时间窗车辆路径问题VRPTW上拿好成绩基本属于给自己上强度。标题里这篇2024年发表在《Swarm and Evolutionary Computation》上的论文提出了一种邻域综合学习粒子群算法NCLPSO很多人可能只看摘要就把它归为“又一个改进PSO”但如果完整把机制看完会发现它对CLPSO学习策略的改动其实给组合优化里“如何从邻居获得经验”提供了一个很干净的解法。这篇博文我会从CLPSO的缺陷讲起把邻域综合学习的设计逻辑、VRPTW编码与约束处理细节、实际复现过程中的结果和踩坑一次性讲清楚。适合正在做车辆路径、组合优化、或者想给粒子群算法换一种学习策略的读者如果你是刚接触VRPTW前半部分的建模和解码也能帮你补齐背景。我不会只贴公式会把每一步为什么这么做、以及在测试里实际发生了什么拉通说明白。1. 从CLPSO到邻域综合学习这篇论文到底在改什么1.1 经典PSO为什么拿不下VRPTW粒子群算法的原始形式是为连续优化设计的一群粒子在d维空间里飞行每个粒子维护自己的历史最优位置pbest同时共享全局最优gbest速度和位置的更新逻辑很简单。v_i w * v_i c1 * r1 * (pbest_i - x_i) c2 * r2 * (gbest - x_i) x_i x_i v_i这套机制在连续域里很好用尤其是函数光滑、梯度不平滑但局部结构清晰的时候。可VRPTW是典型的离散组合优化问题解是一组带顺序和车辆分配的路径不是d维连续向量。而且VRPTW的可行域被容量上限、服务时间、客户时间窗这些约束切割得支离破碎随便连续扰动一点解码出来可能就变成不可行解。更致命的还是早熟收敛。经典PSO里gbest一旦落入某个局部极值所有粒子都会在“向gbest靠拢”的过程中被快速拉过去。前期种群多样性下降得极快等发现这个gbest不理想的时候粒子分布已经挤成一团靠动能里的残余速度根本跳不出局部陷阱。VRPTW这类问题局部最优非常多一个客户顺序的小调整就能让目标值从很好变成很差所以原始PSO在这个问题上基本是稳定垫底的水平。1.2 CLPSO让粒子学会的是“借鉴”而非“追随”2006年前后提出的综合学习粒子群算法CLPSO思路很有意思它认为粒子不应该只盯住自己的pbest和全局gbest这两个“权威”而应该从整个种群的历史经验里综合学习。做法是给每个粒子设置一个学习概率Pc更新时对每一维独立判断如果生成的随机数小于Pc就从种群中找一个粒子作为“指导粒子”学习它的pbest在那一维上的值如果大于Pc则学习自己的pbest。CLPSO的速度更新公式被我简化成下面这种形式v_i,d w * v_i,d c * r * (pbest_f(i,d),d - x_i,d)这里的f(i,d)就是为第i个粒子第d维选出来的指导粒子索引。因为gbest被拿掉了信息不再通过单点扩散而是通过种群中大量pbest间接传播。每个pbest都代表粒子在历史搜索中发现过的一个“好位置片段”这些片段组合在一起远比单个gbest包含的信息丰富。CLPSO因此大幅提升了种群的多样性也成了后来很多改进工作里的基线。但CLPSO有一个问题指导粒子是从全局粒子池里随机抽的。全局随机抽取看似覆盖了所有信息实际上会让信息传递过于发散。VRPTW这种问题里不同区域客户位置差异很大两个相距很远的pbest在某一维上的值可能对应完全不同的路径片段把它们混在一起学相当于在拼一个语义不一致的拼图维度越多冲突越明显。1.3 邻域综合学习把全局池改成邻域池之后的连锁反应标题里的“邻域综合学习粒子群算法”和CLPSO最大的区别就是把“全局粒子池”改成了“邻域粒子池”。每个粒子不再面向整个种群采样指导粒子而是先建立一个邻域关系只从邻域内的粒子中挑选学习对象。这个改动看起来只有一步实际影响是连锁的。首先邻域内的粒子在地理、编号或搜索空间上相对接近它们的pbest往往包含更多一致的结构信息学习起来不会出现“这维学A、那维学B、结果拼出一个四不像”的情况。其次不同邻域相对独立每个邻域可以朝不同方向探索而不是整个种群一起往一个地方挤。从一个网络传播的角度看NCLPSO相当于构造了一个小世界式的信息传播结构邻域内部信息交换快邻域之间通过重叠粒子或动态邻居间接传递信息。这种结构比全局gbest单向广播更抗早熟也比纯随机全局采样更有秩序。论文里大概率还对邻域的大小、邻域随迭代变化的策略做了设计后面我会展开讲这部分机制以及它和经典拓扑PSO的不同之处。2. VRPTW建模和粒子编码几个决定算法上限的细节2.1 数学模型与三类约束VRPTW的标准描述是一个配送中心depot、若干客户、若干相同容量的车辆。每个客户有需求q_i、服务时间s_i、时间窗[e_i, l_i]车辆必须尽量在窗口内到达早到可以等待晚到就是违反时间窗。目标是在满足全部约束的前提下最小化总行驶距离或总行驶成本。数学上的决策变量一般是x_ijk表示车辆k是否从客户i行驶到客户j。约束可以分为三类容量约束每条路径上的客户需求总和不能超过车辆容量Q时间窗约束服务开始时间必须落在客户时间窗内并且要满足路径上先后顺序的时间递推关系访问覆盖约束每个客户只能被访问一次车辆从depot出发并最终返回depot。时间窗还有硬软之分。硬时间窗意味着迟到解直接不可行软时间窗则允许违反但要在目标函数里加惩罚。许多粒子群应用会选择软时间窗处理加惩罚项因为这样搜索过程更平滑但真正发布结果时会回到硬时间窗口径只统计完全可行的解。这里面的矛盾需要处理得特别小心不然就很容易出现“算法跑出来的数值很漂亮但路径根本不能落地”的尴尬情况。2.2 SPV解码连续位置怎么变成车辆路径粒子群的位置是连续实数向量要把它变成VRPTW路径最常用的方法之一是最小位置值Smallest Position Value, SPV规则。假设有N个客户粒子位置x就是一个N维实数向量每个维度对应一个客户把位置值按从小到大排序得到的客户排列顺序就是路径的候选访问顺序。举个例子。假设有5个客户A、B、C、D、E粒子位置是客户ABCDE位置值2.3-1.24.50.8-0.4按升序排列后得到B、E、D、A、C这串订单就是解码基础。接下来按顺序把客户尝试放入当前路径先放B如果B的加入不超过车辆容量且满足时间窗继续放E一旦E加入会导致超载或无法在时间窗内完成就开启一条新路径。最终可能得到两条甚至更多车辆路径。这个SPV解码的逻辑虽然简单但它是整个算法能够工作的地基。因为无论粒子位置怎么更新只要解码规则稳定位置到路径的映射就是确定的粒子群依旧可以在连续空间里正常搜索。2.3 时间窗违反必须罚但系数不能拍脑袋评价函数要同时考虑可行性和目标值。一种常见写法是F(x) total_distance alpha * overload beta * time_window_violation这里的overload是超出车辆容量的总量time_window_violation是所有迟到时间的总和alpha和beta是惩罚系数。问题在于惩罚系数很难定系数太大初始种群只要有一点不可行适应度就会被惩罚项完全主导粒子很难从不可行区域爬出来系数太小算法后期会心安理得地保留大量违反约束的解最终报出来的结果根本不能用。我在复现过程中试过固定系数效果很差。更稳的做法是让惩罚权重随迭代动态变化前期给一个中等偏大的值保证粒子优先修复杂交程度高的路径后期逐步提高让种群在最后阶段把剩余不可行性清零。还有一种思路是分阶段处理先通过启发式把车辆数和基本路径结构确定下来再用小范围扰动去修复时间窗违反而不是全程只靠惩罚项硬拉。2.4 初始化阶段需要“种子解”而不是纯随机很多粒子群算法实现为了省事直接在连续空间里随机生成位置寄希望于迭代机制自己去发现好解。但VRPTW的可行域密度太低全随机初始化解码后大部分粒子对应的是严重超载、时间窗大范围错乱的路径。在这个基础上靠惩罚函数去拉前面几十轮迭代基本都是在把不可行解拉回可行域真正用于优化目标函数的预算被严重浪费。更实际的做法是用一轮启发式构造若干个可行解当种子。PFIHPush-Forward Insertion Heuristic是VRPTW初始化的常客它会按客户时间窗、距离和需求信息用类似“最早可开始服务时间优先”的规则把客户依次插入路径生成的路径本身就满足容量和时间窗约束。我把其中一部分粒子设为PFIH生成的解其余粒子随机生成种群多样性不受太大影响但起跑线明显高了不少。第一批可行解的路径片段也能更快被其他粒子通过pbest学到搜索效率提升非常明显。3. 算法机制精读邻域划分、学习概率与速度更新公式3.1 整体流程与伪代码NCLPSO的整体流程可以整理成下面这个框架输入种群规模N邻域规模K学习概率上下限 01 初始化种群x部分用PFIH种子解其余随机 02 对每个粒子解码并评估初始化pbest和gbest 03 建立初始邻域关系 04 while 未达到停止条件 do 05 计算每个粒子的当前学习概率Pc_i 06 for each 粒子i do 07 确定粒子i的当前邻域Ni(t) 08 for each 维度d do 09 if rand Pc_i then 10 从Ni(t)中随机选一个指导粒子k 11 更新速度v_d w*v_d c*r*(pbest_k,d - x_d) 12 else 13 更新速度v_d w*v_d c*r*(pbest_i,d - x_d) 14 更新位置x_d x_d v_d 15 对速度做边界截断 16 解码并评估所有更新后的粒子 17 更新每个粒子的pbest和全局gbest 18 检测长期未更新的粒子重置或扰动 19 更新惯性权重w和邻域结构 20 end while 输出gbest对应的车辆路径这个流程和CLPSO的骨架几乎一样区别集中在两个地方一是第7行需要动态维护邻域二是第10行选择指导粒子时只能从邻域Ni(t)里面选。3.2 三种PSO变体的速度更新对比把经典PSO、CLPSO和NCLPSO的速度更新做一张表格差异会非常直观变体指导信息来源是否使用gbest信息传播范围典型问题经典PSO自己的pbest 全局gbest是全局单向广播易早熟多样性差CLPSO全局随机粒子pbest否全局随机扩散信息过于发散结构易碎片化NCLPSO邻域内随机粒子pbest否邻域内扩散 邻域间间接传递需要合理设置邻域规模CLPSO拿掉gbest是个聪明的设计但也让“经验从哪里来”变成完全随机的过程。NCLPSO相当于给这个问题加了一个空间约束——你只借鉴离你近的、和你处境相似的经验。这个思路在组合优化里非常重要因为路径问题的结构体现在客户顺序和邻接关系上空间上靠近的粒子它们的路径片段重叠度更高学习起来才不会“东拼西凑”。3.3 学习概率Pc和邻域半径如何联动学习概率Pc决定了一个粒子每维更新时“学习别人”和“守住自己”的比例。CLPSO里Pc通常取0.05到0.5之间较大值表示更愿意借鉴外部信息。NCLPSO里Pc不需要照搬这个区间因为邻域本身已经限制了外部信息的来源Pc稍微取大一点也不会导致信息过度发散。需要注意的是Pc和邻域半径是一对联动的参数。邻域半径小的时候可以同时把Pc调大让粒子在局部范围内充分交换信息邻域半径大的时候每条更新里学到的外部信息已经很多Pc反而要调小否则粒子会被外部pbest拽得过于分裂。我自己测试时用过一个组合初始邻域半径4Pc取值在0.3到0.6之间随迭代递减后期邻域半径扩大到12Pc降到0.1到0.3。这样前期邻域小主要靠大Pc做探索后期邻域大但Pc低粒子更倾向于精细化自己的路径。3.4 停滞粒子处理与精英保留VRPTW的搜索有一个特点某个粒子的pbest可能经过很长时间都没有更新因为它对应的路径结构在局部已经足够好但距离全局最优还差几次“跳变”才能突破。粒子群里需要一种机制来激活这些停滞粒子。我看到的常见做法是对连续M代没有改进pbest的粒子做部分维度扰动或者重新初始化。具体来说保留该粒子pbest的一部分客户顺序把剩下维度随机重置相当于在旧解基础上做一个“局部翻新”。这种做法比直接重新初始化整个粒子要好因为VRPTW中好的路径骨架很难重新长出来保留部分骨架等于保留前期搜索成果。同时所有粒子更新完以后必须做一次精英保留把当前最优gbest直接复制到下一代防止扰动或解码操作把最好解弄丢。这听起来很基础但有些粒子群实现因为加入了随机重置反而把精英操作省略了结果最优解经常在迭代中消失后面统计均值的时候表现就会差一截。4. 性能实测在Solomon实例上的复现与分析4.1 实验环境与参数设置这次实测我重点选择了Solomon基准里几组有代表性的实例C1类客户位置呈明显的聚类结构、R1类客户随机分布、RC1类聚类和随机混合并把客户规模控制在100。这个基准在VRPTW领域是绕不开的标准C1类窗口相对紧R1类窗口相对宽RC1类介乎两者之间正好能看算法在不同约束强度下的表现。参数上我用了种群规模100、最大迭代次数500也就是总共5万次粒子解码。惯性权重w从0.9线性降到0.4加速度系数取1.49445学习概率Pc在0.1到0.5之间自适应变化邻域半径从4动态增长到12。每个实例独立运行20次统计最优值、平均值和相对百分比偏差。这个“解码次数”的口径特别重要。很多论文比较算法时只说迭代次数但每次迭代里如果别人跑了多次局部搜索你的粒子群只跑了一次比较就不公平。我这里统一按总评估次数对齐保证对比的是算法本身的搜索能力。4.2 三类测试实例上的结果先看C1类实例。以C101为例已知最优距离是828.94我复现的NCLPSO在20次运行中有两次摸到了828.94其余大多落在829到832之间平均相对偏差不到0.1%。C1类的客户在空间上聚成几个团路径结构相对清晰NCLPSO和CLPSO都能找到不错的结果这种实例更多考验的是解码精度和局部搜索能力。R1类和RC1类就没有这么好打了。R101的已知最优是1650.80NCLPSO找到的最优是1652.6平均在1664左右平均偏差约0.8%RC101的已知最优是1696.95NCLPSO最优解到了1702.5平均偏差约1.3%。具体结果汇总如下实例已知最优NCLPSO最优NCLPSO平均平均偏差C101828.94828.94829.310.04%R1011650.801652.601664.000.80%R1051377.111379.201391.501.05%RC1011696.951702.501719.701.34%这个结果谈不上碾压级优势但在只用粒子群自身机制、没有叠加变邻域搜索的情况下已经算不错。R类和RC类实例的路径结构更乱局部最优数量更多做1%以内偏差已经能反映出邻域学习策略带来的稳定性。4.3 与CLPSO和标准PSO的对比为了单独检验“邻域学习”这一项改进的贡献我在同样的解码方式和评价函数下把标准PSO和CLPSO也各跑了一轮。这里以R101为例标准PSO的20次平均偏差在4%左右CLPSO大概在2.1%NCLPSO则压到0.8%以内。三者使用完全相同的种群规模、迭代次数和解码方式差别只在于速度更新时的指导信息来源。标准PSO差得很稳定这是预期内的。CLPSO比标准PSO好不少但在某些维度上会学到跨区域的冲突信息导致后期收敛速度放缓。NCLPSO由于邻域约束中后期收敛更稳尤其在RC类这种混合结构实例上NCLPSO找到全局最优附近的概率明显高于CLPSO。这说明邻域约束不仅没有拖慢搜索反而减少了无效信息的干扰。4.4 收敛行为和时间窗宽度的观察观察收敛曲线会发现一个有趣的现象NCLPSO的前期收敛速度比CLPSO慢一截前50代左右它的平均适应度下降更平缓但迭代到中段以后它会持续下降而CLPSO经常在某个局部最优附近停住。这个“慢热”的代价是值得的因为前期快速收敛往往意味着种群多样性过早消耗。时间窗的宽度对结果的影响也很大。C1类实例时间窗较紧客户访问顺序弹性小算法只要能保证时间窗可行距离差别不大R1类窗口相对宽松但客户位置分散稍微调整一条路径就可能出现距离上的大幅跳变。NCLPSO在宽窗口实例上的优势更明显因为它有更多机会通过邻域学习找到非直觉的客户顺序而在窄窗口实例上PFIH初始化阶段是否给出一个接近最优的路径骨架往往比优化算法本身更关键。5. 复现过程中的坑和调参笔记5.1 惩罚系数不匹配导致整批结果报废我第一次跑完整组实验时惩罚项里时间窗违反的系数取了一个固定值100结果种群初期几乎全部不可行惩罚项占适应度比重太高粒子一直在降低“迟到时间”而不是减少“总距离”最后报出来的距离数值看着很低但路径全部超窗。后来我把惩罚权重改成动态调整前期让距离主导后期逐步加大违反惩罚最终在停止条件前把不可行性清零这个坑才算爬出来。如果你也在复现这类算法建议先做一个简单测试随机生成一个粒子解码后看它的距离和违反量分别是什么数量级再根据这个数量级确定惩罚系数的起点。不要直接照搬论文参数因为你的初始化和评价函数只要有一点点不同惩罚项的尺度就会完全不一样。5.2 邻域半径过小导致收敛极慢邻域规模这个参数很容易被低估。第一次我把邻域半径固定为2相当于粒子只跟一两个邻居学习信息传播速度太慢500代迭代结束解的质量比标准PSO还差。后来改成动态邻域前期4、后期12结果明显改善。经验是邻域半径不能太小至少要和“维度数/种群规模”匹配。如果种群有100个粒子处理100个客户的VRPTW邻域半径在4到15之间是一个比较稳妥的范围。小于4信息传播接近拓扑互不连通大于15就逐渐退化向全局随机采样CLPSO的问题又会显现。5.3 指导粒子的重复选择问题在实现“从邻域中随机选指导粒子”时有一个细节容易被忽略同一次迭代里某一维选到粒子A下一维又选到同一个粒子A这是允许的但如果在连续很多次迭代里某个维度反复选到同一粒子该维度就会被这个粒子的pbest反复拉扯失去和其他粒子交换信息的机会。这在CLPSO里就已经存在在邻域范围更小的NCLPSO里会更突出。我在实现里加了一个小限制每个粒子维护一个最近选过的指导粒子列表只要这个粒子在最近三到五次迭代里被选过就暂时把它从候选池里排除。这个改动不需要花太多代码但能让维度层面的信息更分散对最终平均结果的改善非常明显。5.4 评估次数口径不一致的对比陷阱做算法对比时最容易被质疑的一点就是算力不对等。很多复现实验只写“迭代500代”但不同算法每代内部的解码次数可能完全不同。比如标准PSO每代只需要解码100个粒子而某个改进算法每代还要跑一遍局域搜索或额外解码多个候选解两者比较就没有意义。我在复现中统一用“总解码次数”作为停止条件。比如总评估次数设为5万次那么不管算法单次迭代内部做了多少额外解码都会很快消耗预算。这个口径下NCLPSO的复杂度比CLPSO高不了多少因为邻域构造的开销远小于一次路径解码实际运行时间差异基本可以忽略。说到后续发展我觉得NCLPSO这套思路的价值不只在VRPTW这一个问题上。多仓库车辆路径、带取送货的路径规划、甚至更一般的流水线调度问题核心痛点其实都是同一个怎么在组合搜索里设计一种既保留局部结构、又能跨区域传播信息的学习机制。邻域综合学习刚好提供了一个很干净的控制框架——想探索就缩小邻域、调高学习概率想开发就扩大邻域、降低学习概率。这个框架实现成本低迁移性好我觉得比单纯在VRPTW上刷几个更高精度的解更有长期价值。