ARTICLE DETAIL

资讯详情

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

DDPG交通信号灯控制实战:源码解析、训练调参与避坑指南

DDPG交通信号灯控制实战:源码解析、训练调参与避坑指南 简介面向交通信号灯控制的深度强化学习项目源码包以DDPG算法为核心结合Python实现图像解析与连续信号灯控制附有论文说明与训练可视化结果适合系统学习强化学习并落地智能交通场景的开发者。压缩包共23个文件体积仅103KB以9个Python源码文件为主体涵盖DQN变体、策略梯度、经验回放、网络定义等核心模块另有4个XML配置、2张损失函数曲线图及README工程说明目录分为Agent、images等模块便于按需查阅。项目通过Actor-Critic架构训练连续动作策略并提供损失变化图像可直接观察模型收敛过程与调参效果对理解DDPG原理和优化方法很有帮助。目前已有1207人学习下载体量轻但结构完整是一份从入门到进阶的实践参考。1. 从交通信号灯控制到 DDPG这份源码包究竟能学到什么交通信号灯的配时固定周期方案在早高峰和突发拥堵面前几乎毫无还手之力车流分布一变整个路口的通行效率就崩。深度强化学习Deep Reinforcement Learning就是冲着这类动态决策场景来的DDPGDeep Deterministic Policy Gradient作为连续动作空间的代表算法正好能用来决定每个信号相位该放行多久、什么时候切换。这份 Traffic-Signal-Control-master 源码包把 DDPG 在交通信号灯控制上的完整流程摊开了Agent 目录里同时放着 DQN 两代实现和 Policy Gradient 代码根目录的 train_vis.py 负责拉起训练vis_env.py 提供可视化仿真环境dqn.inp 集中管理超参数images 目录下的 T_Mix Q.png、Q_Mix Q.png 和 loss 曲线让你训练完直接看图判断收敛质量。它适合三类人想搞懂 DDPG 怎么从论文落到代码的新手做交通仿真急需对照实验的研究生以及想把强化学习接进自己 Python 项目的一线工程师。2. 拆解源码包的运行逻辑谁在决策、谁在评估、谁在记录经验2.1 目录结构Agent 目录与根目录的分工拿到压缩包先别急着跑把文件职责理清楚后面调参才不抓瞎。根目录和 Agent 目录分工很明确根目录管环境和训练组装Agent 目录管算法实现。下表是我拆包时整理的关键文件职责照着这个清单看代码半小时能摸清整个项目的骨架。文件路径职责关键点Agent/dqn_nips.pyDQN 初版实现NIPS 2013 版本无目标网络适合理解最基础的 Q-learning 扩展Agent/dqn_nature.py带目标网络的 DQNNature 2015 版本加入 target network 与经验回放Agent/pg.py策略梯度算法Policy Gradient直接对策略做梯度上升Agent/networks.py网络结构定义Actor/Critic 的 placeholder 与全连接层构建Agent/replay_memory.py经验回放缓冲存储五元组训练时随机采样Agent/tf_utils.pyTF 工具函数变量初始化、summary 写入train_vis.py训练主入口解析 dqn.inp、初始化环境、执行训练循环vis_env.py可视化仿真环境路口建模、车流产生、状态与奖励计算pg_control.pyPG 控制脚本独立于 DQN 的 PG 控制入口dqn.inp参数配置文件学习率、batch_size、epsilon 等images/训练结果图T_Mix Q.png、Q_Mix Q.png 等这里有个很多人第一次拆包会困惑的点标题写的是 DDPG但代码里没有单独一个 ddpg.py 文件。原因是 DDPG 本质上是 DQN 加 PG 的组合思路——Actor 网络用策略梯度的方式更新Critic 网络用 DQN 的 TD 误差方式更新。所以项目里 dqn_nature.py 提供了 Critic 的更新范式pg.py 提供了 Actor 的更新范式两者拼起来就是 DDPG 的完整骨架。把多个算法放进同一套环境里做横向对比结论也更扎实。2.2 Actor 与 CriticDDPG 在信号灯决策里的完整链路在信号灯场景里状态空间的设计决定了智能体能看到什么。最朴素的做法是把十字路口四个方向的排队长度和车流密度拼成向量维度大约八到十六。如果走视觉识别路线把路口摄像头图像直接作为状态输入那需要在 networks.py 里把全连接层换成卷积层做特征提取。这份包两种输入都兼容我第一次跑用的是向量输入方便调试。动作空间的思考更有意思。固定配时方案把相位时长写死成十五秒、三十秒而 DDPG 输出的动作是当前相位继续放行的时长一个任意实数。这正是 DDPG 相对 DQN 的核心优势DQN 只能把动作离散成有限集合去枚举DDPG 直接输出连续值信号灯控制这种问题天生适合连续决策。# 依据项目 Agent/networks.py 的常见写法还原的 Actor 网络 import tensorflow as tf def build_actor(state_dim, action_dim): # state_dim排队长度、车流密度拼接的向量维数 # action_dim信号灯相位数通常为 2 或 4 state_input tf.placeholder(tf.float32, shape[None, state_dim]) # 中间层用全连接激活函数 ReLU hidden_1 tf.layers.dense(state_input, 256, activationtf.nn.relu) hidden_2 tf.layers.dense(hidden_1, 256, activationtf.nn.relu) # 输出层用 tanh动作值落在 [-1, 1]再映射到实际相位时长 raw_action tf.layers.dense(hidden_2, action_dim, activationtf.tanh) return state_input, raw_action输出层选 tanh 而不是 ReLU是有讲究的。ReLU 输出无上界梯度一大直接爆掉tanh 把动作压到 [-1, 1] 区间再线性映射到实际相位时长范围比如五秒到六十秒。隐藏层节点数设 256 是这类中小规模路口够用的选择路口复杂到十几个方向就得加到 512 甚至更高。上面只写了 Actor 网络Critic 网络的输入比这复杂要把状态和动作拼在一起输入输出单个 Q 值。动作拼进输入是为了让 Q 值对动作可导这样 Actor 的梯度才能通过 Critic 流回去。2.3 经验回放与目标网络训练稳定性的两个根基信号灯场景的状态转移高度时序相关前一个时刻的拥堵直接影响下一个时刻的排队长度。如果按时间顺序把样本一条条喂给网络连续几十条样本都高度相似网络会被带偏。经验回放的核心就是把四要素存进缓冲池训练时随机采样打破这种时序相关性。项目里 replay_memory.py 干的就是这件事。# 依据 Agent/replay_memory.py 的实现思路还原 import random import collections Transition collections.namedtuple( Transition, [state, action, reward, next_state, done] ) class ReplayMemory: def __init__(self, capacity20000): self.capacity capacity self.storage [] def push(self, state, action, reward, next_state, done): # 环形覆盖池子满了丢最老的经验 if len(self.storage) self.capacity: self.storage.pop(0) self.storage.append(Transition(state, action, reward, next_state, done)) def sample(self, batch_size): # 随机采样打散状态之间的时序相关性 return random.sample(self.storage, batch_size)这里顺带提醒一句这个简单实现用 pop(0) 删最早经验是 O(n) 操作经验池一大训练速度会肉眼可见地变慢。我一般会换成 collections.deque 来做环形覆盖代码改动量极小收益却很明显。batch_size 常见取三十二或六十四太小梯度噪声大太大会让更新变得迟钝。目标网络的更新方式也是 DDPG 训练稳定性的关键。Nature 版本 DQN 里目标网络定期硬拷贝DDPG 改成软更新每步把目标网络往在线网络的参数方向拖一点拖动的比例就是 tau。tau 通常取 0.001 到 0.01这个值直接决定了 Q 值曲线是平滑下降还是剧烈震荡。包里的 dqn_nature.py 能看到这类目标网络的实现逻辑理解了这个机制再去看 train_vis.py 里每一轮循环怎么组织状态、动作、奖励、下一状态整个训练闭环就通了。3. 把训练跑起来Python、TensorFlow 版本与第一次可视化3.1 锁定环境版本这是一份 TF1 时代的老项目拿到包第一件事不是运行而是配环境这一步能劝退一半人。项目里所有 TensorFlow 调用都是 tf_utils.py 的典型风格——placeholder、tf.layers.dense、tf.Session这是 TensorFlow 1.x 的鲜明特征跟现在主流的 TF2/Keras 写法完全不同。Python 3.8 及以上装 TF1.15 大概率直接报错或者编译失败这是血泪经验别在这上面浪费时间。最保险的组合是 Python 3.7 加 tensorflow 1.15numpy 版本也要锁到 1.18.5不然高版本 numpy 在 TF1 下面会触发一堆兼容警告甚至断言错误。直接用 conda 建独立虚拟环境是最稳的做法也能避免污染系统里其他项目的 Python 环境。# 强烈建议用 conda 建独立环境 conda create -n traffic_drl python3.7 conda activate traffic_drl # TensorFlow 1.15 在 Python 3.7 下最稳定 pip install tensorflow1.15.0 pip install numpy1.18.5 matplotlib如果你用 vscode 跑这个项目记得把 Python 解释器切换成 traffic_drl 这个 conda 环境不然终端里 import tensorflow 直接 ModuleNotFoundError。这个操作在 vscode 里是 CtrlShiftP 打开命令面板选 Python: Select Interpreter再挑对应环境。很多 Python 环境配置翻车案例最后查下来都是解释器选错而不是包没装好。3.2 读配置、跑训练dqn.inp 与 train_vis.py 的配合dqn.inp 是这份包的超参数集中地训练之前先把配置读一遍脑子里有个数。以下是我按该项目的常见约定还原的示例配置字段名和取值范围基本符合这类 inp 文件的写法具体以压缩包内的 dqn.inp 为准# dqn.inp 常见字段示例 learning_rate 0.001 gamma 0.95 batch_size 32 memory_size 20000 epsilon_start 0.9 epsilon_end 0.05 epsilon_decay_steps 10000 tau 0.005 max_episodes 500熟悉之后就可以启动训练了。train_vis.py 是主入口通常这样运行# 在项目根目录下执行 python train_vis.py --config dqn.inp如果配置文件默认放在根目录直接 python train_vis.py 也能跑。训练循环的逻辑是这样的每个回合开始时重置 vis_env.py 里的路口环境智能体根据当前策略选一个动作相位延长时长环境执行这个动作后返回下一个状态和瞬时奖励每隔若干步程序会打印当前的平均奖励和 loss。训练过程中的 Q 值估计会逐步接近真实回报而 loss 是 TD 误差的体现。3.3 第一次看结果T_Mix Q.png 与 loss 曲线的读法训练结束后images 目录里的两张图是判断收敛质量的关键素材。T_Mix Q.png 画的是目标网络在训练过程中对状态动作对的 Q 值估计Q_Mix Q.png 是在线网络的 Q 值。两张图的曲线差越小说明 Critic 网络越稳定如果两条曲线越拉越远多半是 tau 设置太大目标网络跟着在线网络剧烈波动失去了稳定锚点的作用。loss 曲线的横轴是训练步数或回合数纵轴是 TD 误差。摘要里特意点出 loss 损失函数图像说明这份包把训练诊断信息做成了现成图片省去了你自己写日志解析的麻烦。我第一次跑完看到 loss 曲线先升后降还以为是 bug后来才明白前期 Q 值估计误差大、loss 偏高是正常现象关键是看中后期能不能压下去。4. 调参与损失函数分析让 loss 曲线变成训练诊断工具4.1 超参数表哪些参数决定了训练成败调参是强化学习项目里最玄学的部分但玄学背后有规律可循。dqn.inp 里那十几个参数真正决定训练成败的就几项整理成表更直观参数作用常见范围learning_rate网络更新步长Actor 1e-4Critic 1e-3gamma折扣因子未来奖励的衰减程度0.9 到 0.99batch_size每次采样的经验条数32 或 64tau目标网络软更新系数0.001 到 0.01epsilon探索率随机动作的概率0.9 衰减到 0.05memory_size经验池容量20000 起步reward_scale奖励缩放系数0.1 到 1.0这里最容易被忽略的是 reward_scale。很多人直接把环境返回的等待时间差值当奖励差值的绝对值可能上千Q 值的回归目标被撑得巨大loss 曲线直接发散。我一般会把奖励除以一个经验最大值或者直接乘 0.01把数值尺度压到接近单位量级训练立刻稳定很多。learning_rate 也要区分对待Critic 的收敛速度通常比 Actor 快所以 Critic 学习率设高一些Actor 设低一些这个不对称设计在 DDPG 里是惯例。4.2 三条典型 loss 曲线收敛、锯齿、发散读 loss 曲线是强化学习工程师的基本功三种典型形态对应三种完全不同的处理方向。第一种是正常收敛前几百步 loss 居高不下随着经验积累逐步波动下降最后稳定在一个低位。这种曲线的 Q 值图里在线网络和目标网络的差值也在逐步缩小说明策略在持续改进。第二种是锯齿振荡loss 周期性反弹降一段又跳回去。最常见的原因是 epsilon 衰减过快探索不足智能体过早陷入局部策略每隔一段时间遇到新状态分布就触发一轮大的 TD 误差。处理方法是把 epsilon_decay_steps 拉长让探索期覆盖更多状态空间。第三种是发散loss 一路冲高不带回头甚至出现 NaN。先检查奖励尺度是不是爆了再检查学习率是不是太大最后看状态输入里有没有 NaN 值渗透进网络。这三个排查点覆盖了绝大多数发散案例。4.3 信号灯场景的奖励函数设计别只给稀疏奖励固定配时方案里常用平均等待时间作为评价指标直接拿来做奖励却是个陷阱。如果只在每回合结束给出一个平均等待时间的数字这就是稀疏奖励智能体很难把最终成绩归因到具体哪一步动作上学习效率极低。常见做法是把奖励拆到每个决策步车辆通过数给正奖励排队长度增量给负奖励两者加权求和作为即时反馈。这样每个动作都有明确的奖惩信号梯度信息更密集。# 写在 vis_env.py 的 step 函数里的奖励拆法 reward passed_vehicles * 1.0 - queue_increment * 0.5权重需要实测调整。如果发现智能体倾向于刷通过数而忽视排队长度就把排队惩罚系数调大反过来如果训练前期排队一直压不下来先把正奖励系数压低促使智能体优先清空排队车辆。这类设计细节在论文里通常只有一句话自己实现才知道要试多少轮。5. 避坑指南跑这个项目我踩过的五个坑5.1 Python 3.8 装不上 TF1.15现象pip install tensorflow1.15.0 报错或者装完之后 import tensorflow 直接崩溃提示找不到某个动态库。原因TensorFlow 1.15 官方支持止步于 Python 3.73.8 以上的 ABI 不兼容强行安装要么编译失败要么运行期崩溃。解决conda create -n traffic_drl python3.7 建新环境然后 pip install tensorflow1.15.0 配合清华镜像源加速下载。别试图在现有环境里降级 Python那才是真正的大麻烦。5.2 GPU 显存默认占满现象跑起来发现显存占用直接达到显卡总容量其他程序都卡了甚至直接 OOM。原因TensorFlow 1.x 默认一次性申请全部显存不是按需分配。解决训练脚本开头加上显存动态增长配置config tf.ConfigProto() config.gpu_options.allow_growth True sess tf.Session(configconfig)5.3 训练不收敛先查奖励尺度现象loss 曲线一路冲高数值溢出成 NaN。原因奖励绝对值太大Q 值回归目标爆炸梯度更新步长相对变得过大参数直接飞掉。解决把奖励缩放 0.01 倍或者除以一个经验上限值让 reward 量级落在零点几到几之间。我遇到 NaN 的第一反应永远是看 reward而不是调学习率。5.4 经验池采样越界现象训练刚开始没几步就报 IndexError 或随机采样返回空列表。原因训练循环一开始就从经验池采样 batch但池子里还没有足够多的经验或者 warmup 阶段没执行。解决在正式训练之前先跑一段随机策略往经验池里填充一定数量的样本比如攒满两三千条再开始学习。这样可以写死一个预热循环等 len(storage) 超过 batch_size 再进入训练阶段。5.5 可视化窗口闪退现象vis_env.py 的窗口一闪而过或者弹出来就是空白。原因matplotlib 的后端跟当前的 GUI 环境不匹配有些 Linux 服务器上默认的 Agg 后端不支持弹窗有些 Windows 环境则是 TkAgg 版本冲突。解决在脚本开头强制指定后端import matplotlib matplotlib.use(TkAgg) # 根据系统环境选 TkAgg 或 Agg如果服务器上跑干脆把可视化关掉只保留日志输出。6. 进阶模型保存、加载验证与多路口扩展训练收敛只是第一步真正让模型产生价值的是验证和部署。# 训练结束时保存 saver tf.train.Saver() saver.save(sess, ./checkpoints/traffic_ddpg.ckpt) # 验证阶段加载 saver.restore(sess, ./checkpoints/traffic_ddpg.ckpt)保存和加载模型的代码就这几行但要注意 checkpoint 和训练脚本必须在同一套网络结构定义下使用稍有改动就恢复失败。验证方案可以设计成固定配时策略跑两百个仿真周期记录每回合的平均等待时间再用 DDPG 加载好的模型跑同样的两百个周期最后对比两者的分布箱线图比均值更能看出差异。多路口扩展是常见的进阶方向。把状态从单路口改成多个路口的组合排队长度、车流密度按路口拼接动作维度相应翻倍奖励函数除了考虑单路口内车的通过量还可以把相邻路口的排队差纳入促使智能体学会协调控制而不是各自为政。我后来每次拿到新的强化学习开源项目都会先花十分钟检查环境版本、显存设置、网络结构这三个基础项再动手调参数这个习惯帮我省掉了大量训到一半翻车的返工时间。希望这份 Traffic-Signal-Control-master 的拆解能帮你在 DDPG 落地的路上少走几个弯路把省下来的时间花在真正有价值的调参和实验上希望帮到你。本文还有配套的精品资源点击获取
返回列表