
简介基于 SUMO 交通仿真环境的 Deep Q-Learning 智能体框架面向深度强化学习与智能交通控制方向的初学者及研究者用于在交叉路口自动选择最优信号灯相位以提升通行效率。代码提炼自作者硕士论文简化后保留核心训练与测试逻辑适合作为 SUMODRL 的入门实验基础。压缩包共 19 个文件、约 39KB包含 9 个 Python 脚本模型构建、训练/测试仿真、生成器、可视化、记忆回放与工具函数、2 个 XML 路网文件、2 个 ini 运行参数、1 个 Jupyter Notebook支持 Colab 训练及 1 个 sumocfg 仿真配置目录划分清晰便于基于现有结构二次开发。当前已有 611 人学习下载。读者可据此快速搭建 TensorFlow-GPU 强化学习环境理解交通信号相位选择、奖励设计、经验回放与 SUMO 接口调用等关键实现并在此基础上扩展自己的研究。 接手这个项目之前我对红绿灯配时的理解还停留在“多等一个周期不如多放几秒直行”的工程直觉上。真正做完这个 Deep Q-Learning Agent for Traffic Signal Control 框架我才发现让强化学习代理在交叉路口自己选择信号灯相位本质上不是“调参”而是把一个连续决策问题重新建模成一套可以持续试错并优化的系统。这个项目我前后跑了大概三周核心代码量不大但踩的坑比写代码的时间多得多。这篇就把整个设计思路、实现细节和训练过程里遇到的那些“错误奖励”和训练震荡问题一次性讲清楚。1. 项目定位为什么非要把强化学习搬进信号灯控制1.1 传统配时方案搞不定的动态性传统交叉口信号控制里最有名的是韦伯斯特配时法按各相位流量比来分配绿灯时间计算简单、工程上特别稳。问题是它默认车流模式相对固定一旦遇到早高峰、晚高峰、节假日或者附近商场散场这种流量突变固定配时方案就会明显滞后。感应控制稍微聪明一点通过检测器延长或者截断绿灯但本质上还是在预设相位框架里做局部调整谈不上全局最优。我一开始也在犹豫是不是直接用感应控制加人工调参就够了。后来我拿了两个相邻交叉口的历史车流数据做仿真对比发现车流随机波动大的情况下固定配时的平均排队长度能比最优方案高出 30% 以上。这个差距不是靠多设几个时段方案能抹平的。强化学习 agent 的优势在于它不依赖显式的车流模型而是通过不断和环境交互自己发现“什么时候该放行哪个方向”的策略。这也是我最终决定上 DQN 的核心原因。1.2 让 agent 自己“悟”出信号策略把信号灯控制问题丢给强化学习本质上就是把红灯绿灯的切换决策变成一个序列决策问题。在每个时间步agent 观察交叉口的状态排队长度、车流速度、当前相位持续时长等选择一个动作保持当前相位、切换到下一个相位、或者强制切换环境生成新的状态同时返回一个奖励信号。agent 的优化目标就是让累积奖励最大。听起来很简单但这里有个工程上的关键点agent 学到的策略不是“规定第几秒该变灯”而是一个状态到动作的映射函数。也就是说数据流量变大了agent 会自己延长绿灯而对向车道空着agent 可能会提前结束当前相位。这种灵活性是固定配时方案给不了的也是这个项目最有价值的部分。2. 把交通问题翻译成强化学习的语言2.1 状态空间设计怎么让 agent “看”懂路口状态设计是整个项目里最考验经验的地方。我最初的尝试是把整个交叉口的车辆位置坐标全部塞进状态向量结果网络参数量爆炸训练效率极低而且坐标数据本身包含大量冗余信息。后来我换成了更工程化的做法选定了四个关键状态分量——各进口车道的排队车辆数、进口车道的平均车速、当前相位已经持续的时间、当前信号相位编码。排队车辆数和平均车速直接反映拥堵程度相位持续时长用于让 agent 知道这个绿灯已经放了多久避免长时间不放行某一方向。这里有个小提醒车道级信息一定要离散化后再进网络比如把排队数量分成 0-5、6-10、10 这样的档位比直接用连续数值更稳因为车检器的数据本身有噪声连续值反而容易让网络学到虚假的细节。2.2 动作空间和相位定义交叉口的相位方案五花八门最简单的两相位南北直行、东西直行复杂一点的有八相位甚至十六相位。这个项目里我选了一个标准的四相位方案南北直行、南北左转、东西直行、东西左转。每个相位内部允许车辆通行相位之间按固定黄灯间隔切换。动作空间我定义成离散的 4 个动作但这里有个容易困惑的地方agent 输出的是“下一个相位”还是“保持/切换”我在实践中发现直接输出“保持”或者“切换到下一个相位”这种二值动作配合一个最小绿灯时长约束训练起来更稳定。如果让 agent 直接从 4 个相位里选一个它很容易在相位之间疯狂跳变既不安全也没有效率。最后我的动作空间是二值继续当前相位或者黄灯后切换到下一个相位。2.3 奖励函数千万别一开始就写复杂奖励函数是强化学习里最接近“人性”的部分。我第一次做的时候直接用了“所有车辆的平均等待时间”作为奖励结果训练时 loss 一直震荡agent 行为也很奇怪。后来我仔细分析才发现平均等待时间是一个延迟且非平稳的信号同一时刻排队长度一样但不同车流的平均等待时间差异非常大agent 很难从这种噪声里学到一致的趋势。我后来采用的方案是组合奖励每步奖励 通过的车辆数 × 1.0 - 排队车辆数 × 0.2 - 相位切换惩罚 × 0.5。通过车辆数给一个正向激励让 agent 知道“放车走是好的”排队车辆数做负向惩罚防止它只顾放行某一方向切换惩罚则是为了减少不必要的频繁换相。实践经验是奖励函数的每一项都要小、要可解释并且要有物理意义。不要试图用一个巨大的公式把各种指标揉在一起那样只会让梯度信号变成一团乱麻。3. 核心实现网络结构、训练机制和仿真环境3.1 网络结构三层 MLP 完全够用我见过不少项目一上来就上 LSTM、注意力机制但在交通信号控制这个场景里单步状态并不是强序列依赖问题至少在做栅格化状态表达的情况下一个三层 MLP 就能取得很好的效果。我的网络结构非常朴素输入层接状态向量长度 20 左右中间两个隐藏层各 128 个神经元激活函数用 ReLU输出层是 2 个动作的 Q 值。这里有一个容易踩的坑不要用太宽的网络。交通状态的特征维度有限网络太宽不仅训练慢还容易在训练早期过拟合到某几个路口状态上。我用过 512 个神经元的隐藏层效果反而不如 128 个。后来分析是因为 512 层把很多不相关的状态细节也记住了泛化性能反而差了。Batch size 我试过 32、64、128最后稳定在 64学习率 1e-3 配合 RMSProp 优化器训练过程最平滑。3.2 Experience Replay 和 Target Network 一个都不能省DQN 的两个核心技巧——经验回放和 target network——在这个项目里是必须的缺一个就训练不稳定。经验回放的作用是打破样本之间的时间相关性。交通数据是高度时序的某一个时刻的状态和下一个时刻的状态强相关如果不做回放网络会在连续几个样本上反复学习同一个趋势导致灾难性遗忘。我的 replay buffer 大小设定为 50000 条经验每次训练从中随机采样 64 条。Target network 则是为了稳定 Q 值更新。直接用一个网络同时计算目标 Q 值和当前 Q 值会导致更新目标随着网络变化而不断移动训练过程很容易发散。我每 500 步把训练网络参数复制到 target network这个间隔我试过 200、500、1000500 步时训练稳定性和收敛速度平衡得最好。3.3 仿真环境对接SUMO 是首选交通信号控制的强化学习实验几乎离不开仿真环境。项目里我用的 SUMOSimulation of Urban Mobility配合 TraCI 接口。SUMO 的好处是支持细粒度的车辆跟驰模型和信号配时控制而且通过 TraCI 可以实时读取车辆位置、速度、排队长度还能下发信号灯状态。环境封装这一层我建议按 Gym 的接口来写做到 reset、step、render 三个方法。这样后续换环境、加算法都不用大改代码。Step 的逻辑是收到相位动作后把信号灯状态写进 SUMO跑一个仿真步长我设的是 5 秒然后读取新的状态、计算奖励、判断 episode 是否结束。一个 episode 我跑 3600 步相当于仿真 5 个小时的交通流覆盖早高峰到平峰的流量变化。4. 训练过程中的坑错误奖励、震荡和过拟合4.1 奖励信号错位导致的“卡黄灯”行为项目进行到大约第 2000 个 episode 时我注意到一个非常诡异的现象:agent 学会了让信号灯一直卡在黄灯闪烁状态。原因是我的奖励函数里车辆通过数给的正奖励占了大头agent 发现“切换相位”这个动作能频繁让车辆缓慢通过路口从而获得更多累计奖励但实际交通效率并没有提升反而因为黄灯时间过长造成安全隐患。这就是典型的奖励 hacking——agent 找到了一个比设计者预期更“省力”的方式去最大化奖励。这个问题暴露出的本质是奖励函数中某些指标容易被钻空子。我的解决办法是给相位切换惩罚加大权重同时对黄灯状态做了硬性约束黄灯持续时间固定为 3 秒动作空间里不允许连续两次切换相位。这两个约束一上去卡黄灯的行为立刻消失了。看 log 的时候挺感慨的很多强化学习项目最后不是在调网络而是在调奖励和约束。4.2 Loss 震荡和训练不收敛怎么排查训练到中期loss 曲线经常会突然飙升然后又缓慢降回来看起来非常吓人。我排查的经验是先看是不是 reward scaling 出了问题。如果奖励数值在 1e-3 量级网络梯度更新会非常慢loss 看起来像一条水平线如果奖励数值在 1e2 量级梯度又会爆炸loss 直接变成 NaN。这个项目里我最后把奖励整体缩放到了 1 左右的范围训练明显平稳。另一个常见原因是 epsilon 衰减太快。我最初从 1.0 开始3000 步就衰减到 0.05agent 在探索不充分的情况下就被迫进入利用阶段策略还没成型就开始固化loss 自然反复震荡。我把衰减步数拉长到 10000 步并且设置 epsilon 下限为 0.05探索期足够长之后loss 震荡幅度显著下降。4.3 过拟合到固定车流模式还有一个特别容易忽略的问题agent 在单一车流场景下训练久了会对该模式过拟合。我一开始只用了早高峰的数据训练结果模型放到平峰和晚高峰场景里表现非常差甚至比固定配时还差。这是因为 agent 已经把“长绿灯”和“短切换”这种策略绑定到了早上车流的统计特征上一旦流量分布变了策略就失效。解决思路有两个一是训练时同时混入多个时段的流量数据二是做 domain randomization——每次 reset 时随机生成车流强度和方向比例。我后来用了后者效果立竿见影模型的泛化能力上了一个台阶。现在做这类的项目我基本都会默认上 domain randomization成本低收益高。5. 结果怎么评价以及下一步还能怎么玩5.1 几组常用的评价指标评价一个信号控制策略好不好不能只看单一步的通过量。我用三组指标来对比 DQN agent 和传统固定配时平均排队长度、平均旅行时间、路口总吞吐量。在这三组指标里DQN agent 在高峰场景下比固定配时提升了大约 15% 的吞吐量平均排队长度下降了 20% 左右但平均旅行时间的改善没那么明显约 8%。这个差距说明一个问题agent 学会了更聪明地分配绿灯时间但旅行时间受路段限速、下游路口影响更大单纯优化单个交叉口能带来的收益是有限的。如果你想进一步压低旅行时间要么做多路口联合控制要么把信号配时和路径诱导结合起来。这也是我下一步准备尝试的方向。5.2 值得尝试的改进方向第一个改进方向是做多交叉口协同。单路口 DQN agent 只关心自己的局部状态容易导致“我放行了结果车堵在下一个路口”的情况。把相邻路口的排队状态都放进状态空间或者把多个路口的动作空间合并成一个联合动作空间理论上能实现更好的全局协调但状态和动作的维度也会暴涨。实际操作里我会从两个相邻路口开始试不要一上来搞一个路网。第二个方向是改成决斗 DQNDueling DQN或者带优先经验回放的 DQN。交通场景中很多状态下的不同动作价值差异不大决斗网络把状态价值和动作优势分开建模能减少这种“动作价值近似的状态”带来的学习干扰。优先经验回放则能让网络更多关注那些“高 TD 误差”的样本也就是更有信息量的样本训练效率会更高。第三个方向是把奖励函数改成多目标加权把排放、能耗也加进去。现实中交通信号控制不只是要效率还要考虑环保和驾驶员体验多目标奖励是一个更贴近真实需求的玩法。最后再分享一个我在反复调试中体会到的小技巧一定要把每次实验的随机种子、超参数和奖励权重完整记录下来。这个项目里我至少碰见三次“改了一个奖励权重训练曲线完全变了一个样”的情况如果没有实验记录回归起来会非常痛苦。做强化学习项目实验管理比模型结构更决定你的迭代效率。这个框架本身还有很多可以深挖的地方但如果你也想试试用强化学习做交通控制我强烈建议先把这一套基础版跑通把 MDP 建模、奖励设计和训练稳定性的手感练出来再去追更复杂的算法和更大的路网。本文还有配套的精品资源点击获取