ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SUMO+DQN训练交通信号灯智能体:源码结构与调参实战

SUMO+DQN训练交通信号灯智能体:源码结构与调参实战 简介这是一套基于Python与SUMO仿真平台的交通信号灯相位时间调整项目源码核心采用DQN强化学习算法并给出优先级强化学习变体供对照。项目定位为计算机、通信、人工智能、自动化等专业的高分毕业设计/期末大作业作者答辩评审达98分代码经过调试测试、可直接运行便于学习DQN的训练与评估流程。资源包共32个文件压缩后约536KB主要包括xml与sumocfg路网仿真配置、osm真实地图数据、py算法脚本、xlsx数据记录表和md说明文档其中脚本涵盖DQN主程序、优先级强化学习变体及辅助工具配合多组路网和仿真场景还可结合README说明快速上手灵活替换或扩展信号控制策略。已有85人浏览学习整体结构清晰便于定位算法模块与仿真配置适合作为课程设计、毕业设计及强化学习交通控制方向的入门参考。1. 用 SUMO 训练 DQN 信号灯智能体这份源码究竟帮你解决了什么交通信号灯控制在仿真环境里跑强化学习最折磨人的不是 DQN 网络怎么写而是 SUMO 和 Python 之间的数据通路、动作定义和奖励反馈。这份基于 Python 实现的源码把 SUMO 作为仿真平台用 DQN 算法对红绿灯相位时间做实时调整源码把「仿真环境搭建、traci 接口交互、DQN 网络定义、训练主循环」这条链路完整拉通了。你要交毕业设计、期末大作业或者想验证深度强化学习在交通控制场景下的效果直接用这份源码起步比从零去读 SUMO 文档和 torch 教程省一半时间。它适合手里有 Python 基础、但对 SUMO 交互不熟的人你不需要懂交通仿真细节只需要会跑脚本、会看曲线就能看到 DQN 智能体在路网里把排队长度压下来。2. 仿真平台与算法选型为什么是 SUMO 而不是编写交通流模型2.1 SUMO 在交通强化学习里的角色交通信号灯控制是一个典型的时序决策问题每个时刻需要根据路口各方向的车流状态决定当前相位哪几个方向放行和相位持续时间。强化学习天然适合这个场景但训练需要一个能反复重置、可并行执行的仿真环境。SUMOSimulation of Urban MObility是目前最常用的开源微观交通仿真器它的 traci 接口允许 Python 在仿真运行过程中实时读取车辆位置、速度、排队长度并下发信号灯相位切换指令。我一般不建议自己用 Python 写一个排队模型代替 SUMO原因有两个一是自建模型很难刻画车辆加速、跟驰、换道行为训练出来的策略在真实路网里没法迁移二是 SUMO 自带路网编辑器 netedit可以画出任意拓扑的交叉口训练完还能把仿真过程可视化出来效果展示也是毕业设计加分项。这份源码里已经配好了路网文件和 SUMO 配置文件你要做的只是把 SUMO 安装好然后用 Python 启动它。2.2 为什么选 DQN 而不是 PPO 或 A2C交通信号灯动作空间是离散的选择相位或者延长当前相位时长DQN 天然适合离散动作控制而且训练稳定性在这类低维状态输入场景下表现不错。这份源码的状态输入不是图像而是路口的车辆排队数、等待时间这一类数值特征DQN 的全连接网络就能搞定。PPO 这类策略梯度算法在连续动作空间更有优势但信号灯决策本质上是在离散相位之间切换用 DQN 更容易收敛调参经验也更丰富。网络结构上源码采用三层全连接网络输入层接状态特征维度隐藏层用 ReLU 激活输出层维度等于动作空间大小。我补充说明一句如果你后续想要尝试更复杂的网络比如加入图注意力机制类似 CoLight 的做法这份源码的网络定义部分也方便替换。3. 源码结构与关键模块traci 交互、状态定义、DQN 网络3.1 目录结构和执行入口这套源码的目录结构不算复杂但每份文件的责任边界很清晰。你拿到手先看train.py和dqn_agent.py这两个文件前者是训练总控后者是 DQN 智能体定义env.py是 SUMO 环境的封装层sumo_config目录存放路网和配置文件。project_root/ ├── train.py # 训练主脚本 ├── dqn_agent.py # DQN 智能体网络、经验回放、更新 ├── env.py # SUMO 环境封装traci 交互 ├── sumo_config/ │ ├── intersection.net.xml # 路网文件netedit 导出 │ ├── intersection.sumocfg # SUMO 仿真配置 │ └── intersection.rou.xml # 车流随机生成配置 └── utils/ └── metrics.py # 训练指标记录排队长度、等待时间执行入口是train.py。它先实例化 env再初始化智能体然后进入 episode 循环。这里要提醒的是训练前必须先启动 SUMO 的 GUI 或命令行进程env.py里用traci.start()建立连接底层走的是 SUMO 的 traci 端口默认 8813多个仿真并发时这个端口要改掉。3.2 traci 接口交互的一个完整片段traci 接口是这份源码最重要的外部依赖。下面这段代码是从env.py里提炼出来的单步交互逻辑读取当前路口的各个车道排队长度并执行一次相位切换import traci def get_observation(self): 获取路口各方向的排队车辆数 obs [] for lane_id in self.lane_ids: # 统计该车道停止时间超过阈值的车辆数排队车辆 waiting traci.lane.getWaitingTime(lane_id) # 或者按停止速度判断排队 veh_list traci.lane.getLastStepVehicleIDs(lane_id) stopped 0 for veh in veh_list: speed traci.vehicle.getSpeed(veh) if speed 0.1: stopped 1 obs.append(stopped) obs.append(waiting) return obs def apply_action(self, action): action 是相位编号切换到对应相位 phase_index action traci.trafficlight.setPhase(gneJ00, phase_index) # 保持该相位持续一段时间 for _ in range(self.yellow_time): traci.simulationStep()get_observation()里我用了两种维度停止车辆数和车道等待时间。apply_action()里有个细节是yellow_time这是黄灯过渡时间切换相位不能瞬间跳变否则会出碰撞事故仿真也会报错。我一般会把黄灯设成 3 秒绿灯最小持续时间设成 5 秒这是 SUMO 交通仿真里默认的合理区间。3.3 DQN 网络定义与经验回放dqn_agent.py里定义了一个三层全连接网络输入维度是状态空间大小源码里默认是 8 个维度即 4 个方向各取排队数 等待时间隐藏层 64 和 32 个神经元输出层维度是相位动作数。这里我贴出网络定义部分import torch.nn as nn class DQN(nn.Module): def __init__(self, state_dim, action_dim): super(DQN, self).__init__() self.fc1 nn.Linear(state_dim, 64) self.fc2 nn.Linear(64, 32) self.fc3 nn.Linear(32, action_dim) self.relu nn.ReLU() def forward(self, x): x self.relu(self.fc1(x)) x self.relu(self.fc2(x)) return self.fc3(x)网络设计的关键点是最后一层不经过激活函数直接输出每个动作的 Q 值方便在训练时取最大值索引作为动作。经验回放缓冲区在源码里是用deque实现的默认容量 10000 条每次采样 64 条做批量更新。我建议你改这两个参数的时候注意缓冲区太小会造成数据相关性太高训练不稳定采样批次太大则会让 Q 值更新偏向近期样本影响收敛。4. 从零跑通训练流程参数配置、执行命令与收敛判断4.1 环境准备与 SUMO 版本匹配要跑这份源码第一步是装 SUMO 本身。我建议装 1.14 以上版本老版本对traci.trafficlight.setPhase()的支持有一些差异。安装完 SUMO 之后把安装目录下的bin路径加到系统环境变量确保命令行直接敲sumo-gui能启动界面。然后确认 Python 侧有traci这个库pip install traci注意SUMO 安装包自带 traci 的 Python 包放在sumo/tools目录下如果你用系统 Python 还要检查版本兼容。常见做法是直接pip install traci但要用和 SUMO 主程序同版本的 traci 包否则接口方法名或参数对不上。4.2 训练启动命令与核心参数讲解训练启动不需要额外参数所有配置集中在train.py头部。我摘出关键配置项你对照着看# train.py 关键配置 SUMOCFG sumo_config/intersection.sumocfg NUM_EPISODES 500 EPISODE_STEPS 360 # 每个 episode 仿真步数1 步 1 秒 LEARNING_RATE 0.001 GAMMA 0.95 # 折扣因子 EPSILON_START 1.0 EPSILON_END 0.05 EPSILON_DECAY 0.995 # 每 episode 衰减系数 BATCH_SIZE 64 MEMORY_SIZE 10000 TARGET_UPDATE 20 # 每隔多少步同步一次 target 网络执行训练python train.py训练启动后默认加载 SUMO 命令行模式不弹 GUI这样速度更快。但我在调试阶段建议你改一下 env 启动方式把sumo换成sumo-gui并且设置--delay 100让每一秒仿真步可视化这样能直观看到智能体的相位切换是否有明显不合理之处。训练结束后模型权重保存为dqn_weights.pth。4.3 奖励函数的设计逻辑这份源码的奖励定义在env.py的get_reward()方法里核心逻辑是惩罚排队车辆数和等待时间同时对通行车辆给予正向激励。我用伪代码还原这个逻辑def get_reward(self): total_waiting 0 total_queue 0 for lane_id in self.lane_ids: total_waiting traci.lane.getWaitingTime(lane_id) total_queue traci.lane.getLastStepHaltingNumber(lane_id) # 每通过一辆车给一点正奖励 passed_vehicles self.get_passed_vehicle_count() reward -0.5 * total_queue - 0.01 * total_waiting 2.0 * passed_vehicles return reward奖励设计的核心是量纲平衡。排队数乘以 0.5等待时间乘以 0.01通过车辆奖励是 2.0这样避免某一项占据绝对主导。如果你在训练时发现智能体选择「卡死一个方向、疯狂放行另一个方向」的偷懒策略问题就出在奖励函数对排队的惩罚力度不够把 0.5 提到 1.0 再试。4.4 训练指标观察怎么判断 DQN 有没有收敛源码的utils/metrics.py里记录每个 episode 的平均排队长度、平均等待时间和累计奖励。训练完看一条曲线就够平均排队长度是否整体下降并在某区间波动。DQN 这类算法的训练曲线不像监督学习那么平滑你需要看趋势而非单点值。我一般会在训练到 200 个 episode 时做一次人工检查启动 GUI然后用训练出的权重跑一次仿真看智能体的放行策略是否符合直觉——比如主路排队明显更长时它应该延长主路的绿灯时间。如果策略看起来完全随机多半是状态特征没传对先检查get_observation()返回的数据是不是每个 episode 都在变化。5. 训练与调试中常见的五个坑踩过才记得住5.1 坑一SUMO 端口冲突导致 traci 连接失败现象运行train.py时报traci.exceptions.FatalTraCIError: Cannot connect to TraCI server on port 8813。原因机器上已经有一个 SUMO 实例占用了 8813 端口或者上一次训练异常退出没有释放端口。解决在env.py里把traci.start()的 port 参数改成 8814 或者更大如果确认没有其他 SUMO 进程用netstat -ano | findstr 8813查出残留进程后直接杀掉。5.2 坑二相位切换没有黄灯过渡仿真直接报车辆冲突现象智能体频繁切换相位然后控制台出现车辆碰撞警告或者 SUMO 直接报错退出。原因apply_action()里没有等待黄灯时间信号灯从绿灯直接跳到另一方向绿灯这在微观仿真里不允许。解决每次切换相位前先设置黄灯相位并步进 3 秒然后再切到目标相位。可以写个小工具函数切换逻辑封装在里面。5.3 坑三奖励曲线一直不上升甚至越来越差现象跑了 200 个 episode累计奖励曲线没有明显上升趋势。原因最常见的是学习率太高或 epsilon 衰减太快。学习率 0.001 对 DQN 是安全区间如果换了优化器要重调epsilon 衰减太快会导致前期对环境的探索严重不足网络还没见过足够多的状态就进入利用阶段。我一般把EPSILON_DECAY调到 0.998让前 300 个 episode 都保持一定的探索概率。5.4 坑四状态特征量纲差异大网络输入失效现象排队数在 0~20 之间等待时间动辄上千两个特征拼接后网络训练极不稳定。原因等待时间单位是秒量级远大于车辆数全连接网络对输入量纲敏感。解决对状态特征做归一化把等待时间除以一个常数比如 100或者直接用sklearn.preprocessing.MinMaxScaler做归一化。这份源码里没做归一化你自己跑的时候强烈建议加上效果差异非常明显。5.5 坑五训练到后期过拟合到单一路网换路网就不行现象在训练路网上表现不错换个交叉口拓扑就完全失效。原因状态特征设计得太「本地化」比如采集的车道ID写死在代码里换路网后车道 ID 对不上。解决把车道采集逻辑改成动态获取——通过traci.edge.getIDList()和traci.lane.getIDList()动态遍历所有车道这样换任何路网文件都不用改代码。6. 训练速度与收敛质量的两点进阶优化训练速度在 SU MO DQN 这个组合里往往是最大的瓶颈。默认配置下每个 episode 360 秒仿真500 个 episode 就是 18 万步仿真每一步还要跑一次 Python 侧的网络前向推理跑完一次完整训练可能要两三个小时。第一个优化思路是不要每步都让智能体做决策而是让一个相位持续固定时长比如 10 秒只在这 10 秒结束时才做一次决策。这样仿真的交互频率下降一个数量级而决策逻辑没有本质变化——信号灯本来就是按秒级切换的不需要每 1 秒都改一次相位。第二个优化思路是减少 GUI 带来的渲染开销。训练阶段用命令行模式sumo不带-gui只在验证阶段用sumo-gui。同样的仿真步数GUI 模式下速度可能慢 3 倍以上。我做实验的习惯是跑两个配置一个quick_test模式只跑 50 个 episode每 episode 120 步验证代码通、奖励有正有负、网络参数在更新确认没问题之后再跑完整 500 个 episode。还有个容易被忽略的问题每次仿真开始时traci.start()会重新加载路网文件车流也是重新生成的。同一个 episode 如果车流量随机性太大DQN 学到的策略会有更多噪声。我建议你在intersection.rou.xml里把随机种子固定下来这样同一个 episode 的车流是确定性的算法对比时也更公平。具体做法是在.rou.xml文件的根节点上加routes seed42然后训练多次对比平均表现而不是单次结果。验证训练效果时我习惯把训练好的权重加载进来跑一组对比实验固定配时的信号灯比如每个相位固定 30 秒和 DQN 智能体各跑 200 个 episode统计平均通行时间。这个对比结果放在毕业论文里是很扎实的一页实验图表。如果 DQN 策略在这组对比里没有明显优势优先检查奖励函数里对排队的惩罚项是否太小其次是检查状态特征是否包含了足够信息——最少也要包含各方向的排队长度和当前相位编号。从那以后我每次跑这类强化学习仿真项目都强制把所有随机种子固定下来、把环境变量打印到日志头部避免实验结果不可复现。这份源码本身已经把 SUMO 和 DQN 的链路打通了你拿到手只需要沿着上面的参数和坑位改一改就能在半天内看到训练曲线下降希望帮到你。本文还有配套的精品资源点击获取
返回列表