粒子群优化算法在红绿灯配时中的应用与MATLAB实现 简介面向交通工程、智能算法学习者这份资源以MATLAB为运行环境将粒子群优化算法PSO应用于红绿灯路口配时方案求解以降低路口车辆整体延误为目标适合想在课程设计、毕业设计或科研课题中快速上手粒子群优化实践的读者。压缩包共9个文件包含7个m脚本、1个mat数据文件和1张效果示意图大小仅74KB。核心程序与多个功能函数分工明确主流程负责算法启动与迭代控制辅助脚本分别承担粒子位置更新、速度更新、适应度计算等任务data.mat中保存了各象限交通流量与初始信号周期等参数便于替换数据直接复现实验。整个优化过程覆盖粒子初始化、适应度评价、个体最优与全局最优更新等关键环节相比传统配时方法能用较少迭代搜索到更优的信号切换策略。目前已有263人学习下载资源虽小但逻辑完整既能帮助理解PSO的社会性与认知性学习机制也可作为红绿灯优化研究的可扩展代码基础。1. 粒子群优化算法为什么适合红绿灯配时——先说清问题再谈算法十字路口的延误并不只是「等红灯」那么简单。某个方向排了 40 辆车另一个方向却在一轮绿灯里放了 10 辆车过去剩下 30 秒空放——这就是固定配时的典型浪费。延误函数在通行能力临界点附近是非线性跳变的相位顺序一变排队长度曲线就完全不一样传统的数学规划很难求导穷举又有组合爆炸问题。粒子群优化算法PSO恰好适合这种场景它不要求目标函数可导也不要求凸性用一群粒子在解空间里互相参考、追逐个体历史最优和群体历史最优几十次迭代就能逼近一组把路口延迟时间压低的配时方案。代码结构也不复杂MATLAB 里十几行主循环就能跑通一套 PSO作为交通工程或智能算法的入门级完整案例非常合适。这篇博文会把延误模型怎么建模、粒子怎么编码、main.m、funv.m、funx.m这些文件怎么配合讲透最后给出收敛性验证和结果输出的实操方法。2. 红绿灯路口延误模型与 PSO 编码方式——目标函数、自变量与粒子维度设计2.1 路径口延误的数学描述从车辆到达率到总延迟时间粒子群算法需要一个确定性的「评分函数」来评价每个配时方案的好坏。在红绿灯路口问题里这个评分函数就是路口总延误时间。常见做法是分四个象限东、西、南、北统计各方向的车辆到达率再按相位拆开计算每个相位内红灯期间到达的车辆要停下绿灯启动后这些车以饱和流率通过阻塞队列逐步消散。一个工程上可用的延误模型可以用改进的 Webster 公式来表达D_i (T * (1 - g/C)^2) / (2 * (1 - x)) 排队修正项其中D_i是第 i 个相位的平均延误g是该相位有效绿灯时间C是周期时长x是该相位的饱和度到达流量与通行能力之比。这个公式在低饱和度场景下精度不错在接近饱和时误差会放大但作为 PSO 的适应度函数足够了——优化算法只要保证目标函数对不同解有合理区分度不必要求它精确到秒级。把所有相位的D_i乘以各相位流量再求和就得到路口总延误时间total_delay。这就是整个优化问题要最小化的目标函数对应项目里的fun3.m或者fun1.m。fun3.m更接近「评价函数」fun1.m、fun2.m则可能分别承担单相位延误计算和综合加权计算。数据文件data.mat里存放的就是各象限到达率、初始周期、相位设置这些fun3.m运行所需的参数。2.1.1 自变量的编码方式粒子位置就是一组配时方案PSO 的粒子位置在这个问题里就是一个多维向量x [C, g1, g2, g3, g4]C是周期时长单位秒g1到g4是四个相位的绿灯时长。所有相位的绿灯时间之和加上黄灯/全红时间应该等于周期长度这是硬约束。编码时通常不直接用原始秒数而是用各相位绿灯时间占周期的比例这样可以天然满足求和约束x [C, r1, r2, r3, r4] sum(r1...r4) 1 - (全红时间 黄灯时间) / C粒子群优化算法在这种连续向量空间里的搜索效率很高。需要注意不是所有路口都支持任意相位时间实际落地时后续还要把比例映射回整数的秒数。2.2 MATLAB 函数文件的职责划分fun1 / fun2 / fun3 怎么配合项目包里fun1.m、fun2.m、fun3.m三个函数文件职责最可能的划分如下文件名职责推断输入输出fun1.m单相位延误计算到达率、绿灯时长、周期该相位平均延误fun2.m相位权重与流量加权各相位延误、各方向流量加权后的延误分量fun3.m总延误计算主适应度函数粒子位置 x、data.mat 中的参数总延误时间主程序每次评估粒子时调用fun3.mfun3.m内部再调用fun1.m和fun2.m。这种分层方式的好处是后续想更换延误模型比如换成 HCM 2000 查表模型只需要改fun1.m不需要动 PSO 主循环。下面给出一个符合这种结构的fun3.m参考实现function delay fun3(x, data) % x: [周期, 绿信比1, 绿信比2, 绿信比3, 绿信比4] % data: 包含四个方向到达率 q 的路口基础数据 C x(1); % 周期时长秒 r x(2:5); % 各相位绿信比和为 1 q data.q; % [东, 西, 南, 北] 的车辆到达率单位 veh/s Q data.s; % 各方向饱和流率单位 veh/s delay 0; for i 1:4 g r(i) * C; % 该相位实际绿灯时间 D (C * (1 - g/C)^2) / ... (2 * (1 - min(q(i)/Q(i), 0.95))); % 单相位延误 delay delay q(i) * D; % 按流量加权 end delay delay * 3600; % 换算成 veh·h/h end这段代码的关键点是min(q(i)/Q(i), 0.95)这个保护性截断避免饱和度等于 1 时延误公式出现分母为零的情况。实际项目中fun1.m可能单独封装这个公式fun2.m负责加权求和好处是方便在fun1.m里增加排队长度修正项而不影响其他文件。参数的物理含义是饱和流率Q一般取 1800 veh/h 或按车道数和折减系数标定到达率q由data.mat提供是早高峰、晚高峰还是平峰的数据决定了优化结果偏好的周期长短。2.3 data.mat 里到底有什么流量矩阵与配时约束的存放格式data.mat是 MATLAB 的二进制数据文件用load直接加载进工作区。运行项目之前建议先用以下命令查看它包含哪些变量load(data.mat); whos;whos会列出变量名、尺寸和类型。这个项目里data.mat通常需要包含变量名含义示例尺寸q四个象限的交通流量veh/h4×1s饱和流率veh/h4×1intergreen黄灯加全红时间秒标量min_green各相位最小绿灯时长秒1×4max_cycle周期上限秒标量如果whos显示变量名和这个表不完全一样需要打开main.m看它实际引用了哪些字段。现实中我遇到过data.mat里存储的流量单位是「辆/小时/车道」和算法内部计算的veh/s不一致跑出来的最优周期永远贴在上边界——这就是单位没换算导致的。接手代码第一步永远是把data.mat的变量导出成.csv或者直接在 MATLAB 里显示一遍确认量纲。3. main.m 的 PSO 主循环——粒子初始化、适应度评估与全局最优更新3.1 标准 PSO 主循环与 main.m 结构main.m是启动入口。整个流程分三步加载数据并初始化粒子群、进入迭代循环、输出最优解。下面是符合项目结构的主循环参考代码%% main.m - 粒子群优化算法求解红绿灯配时 clear; clc; load(data.mat); % 加载路口流量与配时参数 % 参数设置 nParticle 30; % 粒子数量 maxIter 100; % 最大迭代次数 w 0.7; % 惯性权重 c1 1.5; % 个体学习因子 c2 1.5; % 群体学习因子 dim 5; % [周期, 4个绿信比] % 1. 初始化粒子位置与速度按边界约束 lb [40, 0.10*ones(1,4)]; % 周期最小40s各相位绿信比不小于0.1 ub [180, 0.60*ones(1,4)]; % 周期最大180s各相位绿信比不大于0.6 x rand(nParticle, dim) .* (ub - lb) lb; v randn(nParticle, dim) .* 0.1; % 保证绿信比之和为 1 - intergreen/C for i 1:nParticle x(i,2:5) x(i,2:5) / sum(x(i,2:5)) * ... (1 - data.intergreen / x(i,1)); end % 2. 初始适应度评估 pbest x; % 个体历史最优 pbest_fit zeros(nParticle, 1); for i 1:nParticle pbest_fit(i) fun3(x(i,:), data); end [gbest_fit, idx] min(pbest_fit); gbest pbest(idx, :); % 3. 迭代搜索 for iter 1:maxIter for i 1:nParticle v(i,:) w * v(i,:) ... c1 * rand * (pbest(i,:) - x(i,:)) ... c2 * rand * (gbest - x(i,:)); x(i,:) x(i,:) v(i,:); % 边界约束修正反射法 for d 1:dim if x(i,d) lb(d), x(i,d) lb(d); end if x(i,d) ub(d), x(i,d) ub(d); end end % 重新归一化绿信比 x(i,2:5) x(i,2:5) / sum(x(i,2:5)) * ... (1 - data.intergreen / x(i,1)); % 适应度与本轮评估 fit fun3(x(i,:), data); if fit pbest_fit(i) pbest_fit(i) fit; pbest(i,:) x(i,:); end end [current_best, idx] min(pbest_fit); if current_best gbest_fit gbest_fit current_best; gbest pbest(idx, :); end end % 输出结果 fprintf(最优总延误: %.2f veh-h\n, gbest_fit); fprintf(最优周期: %.0f 秒\n, gbest(1)); fprintf(绿信比: %.3f %.3f %.3f %.3f\n, gbest(2:5));这段代码的层次是这样的初始化阶段先随机撒粒再通过归一化强制绿信比之和满足约束——这一步非常关键PSO 的速度更新公式只关心数值变化趋势不会自己产生满足约束的方案如果不做归一化大量搜索时间都浪费在非法解上。迭代循环里三个核心步骤分别是速度更新、位置更新和边界处理。速度更新的三部分分别代表对上一次速度的惯性继承、向个体历史最优靠拢的认知行为、向全局最优靠拢的社会行为。关于v的初始化randn * 0.1会在负值和正值之间波动幅度约为位置范围的 10%太小会让粒子探索不到远处的区域太大会频繁撞边界。实际项目中main0.m很可能是早期版本区别往往在边界处理上——main0.m可能用的是直接截断法粒子撞边界后停在边界上后续更新容易反复被拉回来导致多样性下降而上面代码用的反射修正虽然简单但比直接截断能保留更多的搜索方向。3.2 速度更新函数 funv.m 与位置更新函数 funx.m 的拆分逻辑项目包里funv.m和funx.m把速度、位置更新单独抽出来。好处是主循环更干净也方便把标准 PSO 替换成带收敛因子或者惯性权重的变体。function v_new funv(v, pbest, gbest, x, w, c1, c2) % 速度更新函数 r1 rand(size(v)); r2 rand(size(v)); v_new w * v ... c1 * r1 .* (pbest - x) ... c2 * r2 .* (gbest - x); endfunction x_new funx(x, v, lb, ub) % 位置更新函数执行边界反射 x_new x v; for d 1:length(lb) if x_new(d) lb(d) x_new(d) 2 * lb(d) - x_new(d); % 反射回可行域 elseif x_new(d) ub(d) x_new(d) 2 * ub(d) - x_new(d); end end endfunv.m的参数w、c1、c2、r1、r2有一个容易被忽视的细节r1、r2每次调用都重新用rand生成这保证了随机性但如果使用固定随机种子的方式做对比实验就应该在main.m里统一设置rng(42)否则两次实验结果没有可比性。funx.m的反射处理在粒子严重越界时会遇到一个问题如果x v跑得离边界太远反射回来可能还在边界外需要加max和min做二次保护。这个函数里没有二次保护日常调试可以接受如果追求鲁棒性要加上x_new(d) max(min(x_new(d), ub(d)), lb(d))的最后一道保险。无论主循环写在一起还是拆成函数颗粒度的选择取决于你的使用场景如果只是跑通一次优化写在主循环里更快如果你想对比标准 PSO、惯性权重的线性递减 PSO 和带收缩因子的 CPSO 三种变体就必须把funv.m独立出来。4. 参数整定与约束处理——惯性权重、学习因子和绿信比归一化边界4.1 惯性权重与学习因子的经验整定粒子群优化算法对参数不敏感但这个「不敏感」是有条件的在红绿灯配时问题里维度低5 维、目标函数多峰但连续只要参数落在合理区间内都能收敛到接近全局最优解。合理的参数区间可以参考下表参数建议范围推荐初始值作用调参方向w0.4 ~ 1.00.7粒子继承上一时刻速度的程度增大增强全局搜索减小增强局部收敛c10.8 ~ 2.01.5个体认知向自身历史最优学习偏大会搜索过度收敛变慢c20.8 ~ 2.01.5社会学习向群体最优靠拢偏大容易早熟陷入局部最优粒子数20 ~ 6030解的采样密度超过 60 对 5 维问题无显著提升最大速度位置范围的 10% ~ 20%0.1*(ub-lb)防止粒子飞越目标区域过大导致震荡过小导致搜索不充分项目文件中funv.m的w、c1、c2参数从哪传入main.m的 4.1 节代码里把它们定义为常量了。一个性价比很高的改进是让w随迭代次数线性递减w_max 0.9; w_min 0.4; for iter 1:maxIter w w_max - (w_max - w_min) * iter / maxIter; % 之后的循环体不变 end这个写的逻辑是迭代初期用较大的惯性权重让粒子在解空间大范围探索避免一上来就围着某个局部最优转迭代后期减小w让粒子收缩到优质区域精细搜索。红绿灯配时问题里典型表现是前 20 轮收敛速度极快后面 40 轮震荡变小、曲线平缓从收敛曲线就能判断w衰减速度是否合适。注意别把w这条语句放在内层for i 1:nParticle循环里否则每个粒子都重新计算一次浪费不算大但逻辑上是不必要的。4.2 绿信比归一化与最小绿灯约束的实现顺序把红绿灯方案编码为[C, r1, r2, r3, r4]后任何粒子在数值上都无法保证sum(r1..r4) 1 - intergreen/C。因此每次位置更新之后都要做归一化。这个修正应该在「边界反射」之后做还是之前做很多人会踩坑。我推荐的核心逻辑是位置更新funx.m内部完成反射对x(2:5)做边界限定每个绿信比不小于min_green/C不大于上限归一化到合法的总和用归一化后的值计算适应度对应代码如下% 每个相位最小绿信比 最小绿灯时间 / 周期 min_ratio data.min_green / x(i,1); for d 2:5 if x(i,d) min_ratio(d-1) x(i,d) min_ratio(d-1); end end % 归一化到合法总和 legal_sum 1 - data.intergreen / x(i,1); x(i,2:5) x(i,2:5) / sum(x(i,2:5)) * legal_sum;这里有个隐蔽的问题归一化之后某些相位的绿信比可能再次低于min_ratio。因为归一化是统一缩放如果初始向量总和比legal_sum大缩放后会整体减小但如果某个相位恰好非常小缩小后就会跌破下限。严格处理需要迭代做「裁剪→归一化→再裁剪」直到所有分量都满足约束且总和等于legal_sum。5 维问题通常两轮就收敛。如果fun3.m里已经有针对非法输入的惩罚项那这个约束可以从简但惩罚系数设置不当会引入新的局部最优不如约束修正来得干净。参数整定实验里还需要固定随机种子。PSO 的随机性来自funv.m中的rand调用两次运行同一组参数得到的结果会有细微差异。做方案对比之前在main.m开头写上rng(42)确保每次从同一个初始解空间出发这样迭代曲线的差异就只来自参数本身而不是随机波动的运气成分。5. 收敛性验证与绿灯时间输出——跑完 main.m 后你必须做的三件事5.1 记录收敛曲线并判断是否陷入局部最优main.m只输出最终结果不保留收敛轨迹这给验证带来很大不便。改动方法是在主循环里加一行gbest_history(iter) gbest_fit;所有迭代结束后绘制曲线figure; plot(1:maxIter, gbest_history, b-o, LineWidth, 1.2); xlabel(迭代次数); ylabel(全局最优总延误 / veh·h); title(PSO 收敛曲线); grid on;判断标准是曲线是否在迭代中期进入平台期。如果 100 次迭代的结果曲线大约在第 30 次就完全水平说明粒子群在重复围绕同一个解做扰动此时增大w或者把c1调大一些让粒子有更强意愿跳出去探索其他区域。如果曲线在第 80 次仍在明显下降说明迭代次数不够应该把maxIter调大有经验的做法是设定两次迭代间gbest_fit的相对变化小于 1e-4 时提前终止循环节省无谓计算。5.2 多次运行取最优解规避单次随机性红绿灯配时问题的目标函数存在多个相近的局部最优尤其是各方向流量相差悬殊时比如东西向流量远大于南北向算法很容易收敛到「牺牲小方向、保住大方向」的次优解。单次运行结果不可靠我的做法是外层再套 10 次运行best_overall inf; for trial 1:10 rng(trial * 100); % 不同随机种子 run(main.m); % 每次运行得到 gbest_fit 与 gbest if gbest_fit best_overall best_overall gbest_fit; best_solution gbest; end end10 次运行在 5 维问题上耗时大约是小几十秒到几分钟完全可接受。取出的最优解需要人工检查有没有不合理的取值比如绿灯时间出现小数、个别相位绿灯只有 12 秒都要酌情修正。5.3 把最优解还原成可执行的红绿灯配时方案PSO 得到的是连续数值需要还原成红绿灯控制器能用的整数秒配时表C_opt round(best_solution(1)); green_seconds round(best_solution(2:5) .* C_opt); % 黄灯时长取自 data.intergreen假设均匀分布在各相位 fprintf(周期: %d s\n, C_opt); fprintf(相位1绿灯: %d s, 相位2绿灯: %d s, 相位3绿灯: %d s, 相位4绿灯: %d s\n, ... green_seconds);round之后可能出现绿灯总和与周期差一两秒需要手动把差额加在流量最大的相位上这是工程落地与纯算法之间的典型间隙。还原后的方案可以直接输入到 VISSIM 或 SUMO 仿真器里做二次验证对比固定配时方案和 PSO 优化方案的排队长度与平均停车次数——这一步能有效确认fun3.m里延误模型的偏差是否影响最终结论。项目本身到这里就完成了从算法到结果的闭环。本文还有配套的精品资源点击获取