ARTICLE DETAIL

资讯详情

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

深度强化学习DDPG实现交通信号灯控制:Python实战与调优

深度强化学习DDPG实现交通信号灯控制:Python实战与调优 简介一套基于深度确定性策略梯度算法DDPG的交通信号灯控制项目源码Traffic-Signal-Control-master使用Python实现主要面向智能交通方向的研究人员、深度强化学习爱好者以及希望尝试连续动作控制方案的中高级开发者。项目包含完整代码与配套论文可以从图像识别、环境仿真到策略调优各环节理解强化学习在真实场景中的落地方式。压缩包共23个文件大小仅103KB以9个Python源文件为核心覆盖训练、控制、网络构建与经验回放等模块同时提供4个XML参数配置、1份说明文档和2张损失函数变化图并附带工程环境配置便于直接对照学习和复现实验。目前已有1207人浏览学习具有较高的参考价值。通过运行示例和观察损失曲线能直观体会模型训练过程中的收敛情况进而学会调整神经网络参数、设计奖励函数和优化控制策略是一份小而完整的强化学习实战资料。1. 深度强化学习与交通信号灯识别从 DDPG 到可运行的 Python 控制闭环如果你搜到“Traffic-Signal-Control-master”这类项目名大概率是想找一个基于深度强化学习的交通信号灯控制实现而不是真的要去“识别”红绿灯颜色。这个标题里“交通信号灯识别”容易让人误解——在深度强化学习语境下它通常指智能体通过状态输入车辆排队长度、等待时间、相位信息来“识别”当前交通态势并决策下一个绿灯相位时长。DDPGDeep Deterministic Policy Gradient则是这套方案中处理连续动作空间的常用算法尤其适合把绿灯时长、相位切换时刻当作连续变量来优化。本文会从 DDPG 的动作空间设计出发给出一套能在 Python 环境中跑通的信号灯控制最小实现包括状态编码、奖励塑造、网络结构和参数调优并指出仿真环境与真实路口之间的关键差异。适合正在做课程设计、论文复现或智能交通项目初期的开发者也适合想了解 DDPG 在离散决策场景如何落地的强化学习工程师。2. 交通信号灯控制问题建模为什么 DDPG 能处理连续相位时长2.1 从 MDP 角度看信号灯控制信号灯控制本质上是一个序贯决策问题。每个时间步智能体观察路口状态决定是否切换相位或延长当前相位然后获得奖励进入下一个状态。用马尔可夫决策过程MDP可以形式化描述状态空间包含各个方向的车流量、排队长度、当前相位编号、已持续时间动作空间可以是离散的如切换/不切换也可以是连续的如绿灯剩余时长或下一相位持续时间奖励函数通常设计为负的车辆平均等待时间、排队长度或通行效率指标。许多经典强化学习库处理离散动作时直接用 DQN但真实信号灯控制往往需要输出“绿灯还要持续多少秒”这样的连续值。DDPG 的优势就在这里它采用 Actor-Critic 架构Actor 网络输出确定性动作Critic 网络评估状态-动作对的价值适合连续动作空间。不过需要注意DDPG 原始算法面向连续动作如果你只是想要“切换相位”这种离散决策也可以把连续输出量化成离散动作或者改用离散动作的变体。常见的做法是将动作定义为“当前相位的剩余绿灯时长”取值范围比如 [5, 60] 秒然后通过环境仿真推进这一时长。2.2 状态空间的编码方式在 Traffic-Signal-Control 类项目中状态向量设计决定了学习效果。最简单的状态可以包含当前相位编号one-hot 编码假设 4 个相位当前相位已持续时间每个进口方向的排队车辆数或平均等待时间最近一段时间内的车流量统计以下是一个用于迷你仿真环境的观测向量构造示例假设十字路口有 4 个进口方向import numpy as np def build_state(phase_id, phase_duration, queues, waits): queues: dict, 键为北东南西值为对应车道排队长度 waits: dict, 键为方向值为平均等待时间 # 相位 one-hot phase_one_hot np.zeros(4) phase_one_hot[phase_id] 1.0 # 排队长度归一化到 0~1 queue_vec np.array([queues[d] / 30.0 for d in [N, E, S, W]]) # 等待时间归一化 wait_vec np.array([min(waits[d] / 120.0, 1.0) for d in [N, E, S, W]]) # 相位持续时间归一化 duration_feat np.array([min(phase_duration / 60.0, 1.0)]) state np.concatenate([phase_one_hot, queue_vec, wait_vec, duration_feat]) return state.astype(np.float32)这段代码把状态向量拼成 13 维4 相位 one-hot、4 个排队长度、4 个等待时间、1 个持续时间。归一化的好处是让 DDPG 的神经网络输入尺度一致避免某些特征值过大导致训练不稳定。实际项目中如果使用 SUMOSimulation of Urban MObility或 CityFlow 仿真器状态维度可能扩展到几十甚至上百需要根据路口规模调整。2.3 动作空间与相位切换逻辑DDPG 输出的动作是一个连续值我们可以将其解释为“当前相位还需继续的秒数”。环境收到这个动作后保持当前相位不变直到计时结束然后强制切换到按固定轮转顺序的下一个相位。这种方案保持相位轮转的公平性又允许模型动态调节绿灯时长。另一种设计是输出两个动作头一个表示是否切换另一个表示若切换则新区绿灯时长。这种设计自由度更高但学习和调参难度也更大。对于初学者建议先采用“单一连续动作 固定相位顺序”的方案动作值范围设为 [-1, 1]DDPG 常用 tanh 输出然后映射到 [5, 50] 秒def interpret_action(raw_action, min_green5.0, max_green50.0): 将 DDPG 输出的原始连续动作映射为绿灯时长 raw_action: 形状为 (1,) 的数组值域约 [-1, 1] # 将 [-1, 1] 映射到 [0, 1] normalized (raw_action[0] 1.0) * 0.5 green_duration min_green normalized * (max_green - min_green) return float(np.clip(green_duration, min_green, max_green))这样设计的好处是即使 Actor 网络一开始输出随机值环境也能保证相位切换合法且不会出现负的绿灯时长。上限 50 秒是经验值过长会导致其他方向车辆等待过久过短则通行效率低下。你可以在训练中调节这个区间但建议不要低于 5 秒否则车辆来不及通过路口。3. 用 Python 搭建 DDPG 信号灯控制的最小可运行闭环3.1 环境模拟器设计要点在深度强化学习项目中环境模拟器决定训练速度和算法收敛难度。常见的开源选择是 SUMO但 SUMO 的安装和 Python API 集成比较繁琐。如果只是为了验证 DDPG 的信号灯控制流程可以先用自写迷你环境之后再切换到 SUMO。迷你环境需要实现以下核心逻辑车辆按泊松过程到达路口四个方向每个时间步更新车辆排队和等待时间根据动作设置当前相位剩余时间计算并通过率、等待时间等指标下面是一个简化版环境的关键类只展示核心回调结构class TrafficLightEnv: def __init__(self, phases4, max_steps360): self.phases phases self.current_phase 0 self.phase_remaining 10 self.max_steps max_steps self.time 0 def reset(self): self.current_phase 0 self.phase_remaining 10 self.time 0 return build_state(self.current_phase, 10, {N: 2, E: 3, S: 1, W: 2}, {N: 5, E: 8, S: 2, W: 3}) def step(self, raw_action): green interpret_action(raw_action) # 推进相位剩余时间 if self.phase_remaining 0: self.current_phase (self.current_phase 1) % self.phases self.phase_remaining green else: self.phase_remaining - 1 self.time 1 # ... 在这里更新车辆位置、计算奖励 reward self._compute_reward() done self.time self.max_steps next_state build_state(self.current_phase, self.phase_remaining, self._get_queues(), self._get_waits()) return next_state, reward, done, {}step方法的关键是处理相位切换逻辑判断phase_remaining是否小于等于 0若成立就切换相位并用 DDPG 输出的新绿灯时长重新计时。注意这里的_compute_reward是一个抽象占位你需要在具体实现中返回数值。3.2 DDPG 网络结构Actor 与 Critic 的实现DDPG 的 Actor 网络输入状态输出动作Critic 网络输入状态和动作输出 Q 值。在 Python 中可以使用 PyTorch 快速搭建这两个网络。下面是一个常见的全连接网络实现隐藏层大小为 256 和 128针对 13 维状态输入、1 维动作输出import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, state_dim, action_dim, hidden256): super().__init__() self.fc1 nn.Linear(state_dim, hidden) self.fc2 nn.Linear(hidden, hidden) self.fc3 nn.Linear(hidden, action_dim) def forward(self, x): x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) # 输出范围 通过 tanh 限制在 [-1, 1] return torch.tanh(self.fc3(x)) class Critic(nn.Module): def __init__(self, state_dim, action_dim, hidden256): super().__init__() self.fc1 nn.Linear(state_dim action_dim, hidden) self.fc2 nn.Linear(hidden, hidden) self.fc3 nn.Linear(hidden, 1) def forward(self, x, a): x torch.cat([x, a], dim1) x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) return self.fc3(x)Actor 最后一层用tanh激活将动作限制在 [-1, 1]与之前interpret_action的映射对应。Critic 的输入拼接状态和动作输出一个标量 Q 值。这种结构是 DDPG 的标准范式你也可以根据路口复杂度增加层数或改用 Attention但先跑通基线更重要。3.3 训练主循环与超参数DDPG 训练时最需要注意的是目标网络软更新、回放缓冲区Replay Buffer和噪声探索。下面给出训练循环的核心代码省略了回放存储和网络更新的完整细节但关键逻辑足够清晰def train_ddpg(env, actor, critic, target_actor, target_critic, buffer, episodes500, batch_size64, gamma0.99, tau0.005): actor_opt torch.optim.Adam(actor.parameters(), lr1e-4) critic_opt torch.optim.Adam(critic.parameters(), lr1e-3) for ep in range(episodes): state env.reset() ep_reward 0.0 while True: # 探索噪声使用 OU 噪声或高斯噪声 noise np.random.normal(0, 0.1, size1) action actor(torch.FloatTensor(state).unsqueeze(0)).detach().numpy()[0] noise next_state, reward, done, _ env.step(action) buffer.add((state, action, reward, next_state, done)) if len(buffer) batch_size: # 从回放缓冲区采样并按标准 DDPG 公式更新 update_ddpg(actor, critic, target_actor, target_critic, buffer.sample(batch_size), gamma, tau, actor_opt, critic_opt) state next_state ep_reward reward if done: break if ep % 20 0: print(fEpisode {ep}, Reward: {ep_reward:.2f})这段代码中tau是目标网络软更新的系数常取 0.005代表每次将目标网络参数向在线网络移动 0.5%。gamma使用 0.99 比较合适因为信号灯控制的场景是长时段决策未来奖励的重要性不应过低。噪声标准差 0.1 是初始值训练后期可以衰减到 0.01否则收敛后动作抖动太大会影响性能。4. 奖励函数设计避免“绿灯大放行”陷阱4.1 排队长度与等待时间的权衡奖励函数是信号灯控制中最容易踩坑的部分。如果只把奖励设为负排队长度总和模型会学到“永远保持绿灯让所有车通过”但这样会饿死其他方向。更合理的方案是将奖励拆成多分量并加入对切换频率的惩罚。常见的做法是每个时间步计算reward - (alpha * total_queue) - (beta * total_waiting_time) - (gamma * phase_change_penalty)其中alpha、beta、gamma是权重。phase_change_penalty只在相位切换时生效用于抑制频繁切换。这是因为信号灯切换时存在清空路口的时间损失频繁切换会降低通行效率。实现示例def _compute_reward(self, queues, waits, is_switch, prev_wait_sum): alpha, beta, gamma 0.3, 0.1, 0.5 total_queue sum(queues.values()) total_wait sum(waits.values()) switch_penalty gamma if is_switch else 0.0 reward - (alpha * total_queue beta * total_wait switch_penalty) return rewardprev_wait_sum可以用来计算等待时间的变化量如果当前等待时间比上一时刻增加则给更负的奖励以此鼓励模型优先处理长时间等待的车辆。压队时间的变化量往往是更灵敏的信号你可以将奖励改成reward - (alpha * delta_queue beta * delta_wait switch_penalty)这样模型会更关注“改善了多少”而不是绝对数值训练起来通常更容易稳定。4.2 奖励尺度归一化DDPG 对奖励的尺度比较敏感。如果奖励数值在几百到几千之间波动Critic 网络的学习率需要调得很小否则 Q 值估计容易发散。建议将每个时间步的奖励控制在 [-10, 0] 范围内使用排队车辆数除以一个基准值比如路口容量或者使用排队变化量的比例。一个简单的做法是对每一分量做归一化max_queue 20.0 max_wait 120.0 reward - (0.5 * total_queue / max_queue 0.5 * total_wait / max_wait)这样即使车流量很大奖励也不会超过 [-1, 0] 的范围训练更加稳定。实际项目中可以通过环境里观察到的最大排队长度动态调整归一化系数。5. 实战参数调优DDPG 训练不收敛的 7 个排查步骤5.1 从环境步长到学习率的检查清单运行 DDPG 训练后你会发现它比 DQN 更容易出问题常见表现是奖励不升反降或者动作输出一直贴着边界。下面是一份按优先级排列的排查步骤先验证环境本身固定随机种子里随机策略的平均奖励应该在一个合理区间。如果随机策略每轮奖励是 -5000而 DDPG 降到 -8000说明奖励设计或网络更新有 bug。检查状态是否归一化状态特征的量级如果差异超过 10 倍Actor 的梯度会被大数值特征主导。可以把每个维度拉到 0~1 之间查看状态向量每个维度的均值和方差。降低 Actor 学习率Actor 学习率通常应比 Critic 低一个数量级例如 Critic 用 1e-3Actor 用 1e-4。否则策略更新太快Critic 还没拟合准确Actor 就已经跳到错误方向。增大噪声但加衰减探索不足会让模型困在局部最优探索过剩会让训练永远不收敛。从 0.2 开始每 100 轮衰减 0.95 倍。检查目标网络软更新系数 tau如果 tau 太大如 0.1目标网络接近在线网络容易震荡如果太小0.0001目标网络更新太慢学习进度拖沓。0.005 是一个合理起点。观察 Q 值趋势如果 Critic 输出的 Q 值一直增大但实际奖励没有提升说明 Critic 过估计。建议在update_ddpg中加入target_critic的clamp或使用 Twin 网络TD3来缓解。检查是否有维度不匹配动作噪声的 shape 为 (1,)但网络输出可能是 (1,1)拼接时会报错。建议统一使用squeeze或view保证维度一致。5.2 学习率与回放缓冲区大小推荐下表是我在迷你环境上跑的推荐设置适用状态维度 13、动作维度 1 的场景。如果你的路口更复杂可做相应缩放超参数推荐值说明Actor 学习率1e-4太大会导致策略跳变Critic 学习率1e-3通常比 Actor 大折扣因子 gamma0.99长期决策不能太低回放缓冲区大小50000过小容易过拟合采样分布批量大小 batch_size6432 也可但 64 更稳软更新系数 tau0.005每步向在线网络移动 0.5%探索噪声起始标准差0.2线性衰减到 0.01每 episode 最大步数360模拟 1 小时5 秒一个决策周期5.3 从迷你环境迁移到 SUMO 的接口适配如果你已经在小环境上验证了 DDPG 的收敛性想迁移到更真实的仿真环境建议先跑通 SUMO 的 TraCI 接口。SUMO 的 traffic light 控制有三种模式静态开关、基于时间的切换、基于 TraCI 的动态控制。与 DDPG 对接时通常使用traci.trafficlight.setPhase和setPhaseDuration来控制当前相位和持续时间。import traci def apply_ddpg_action(tls_id, raw_action, current_phase, min_green5, max_green50): green_duration interpret_action(raw_action, min_green, max_green) traci.trafficlight.setPhase(tls_id, current_phase) traci.trafficlight.setPhaseDuration(tls_id, green_duration)注意 SUMO 中的信号灯相位索引可能包含黄灯和红灯全红状态这些过渡相位的时长不应由 DDPG 决定。常见做法是让 DDPG 只决定主要绿灯相位的时长黄灯和全红固定为 2~3 秒并在切换完成后再继续下一轮决策。这一步如果忽略模型输出的绿灯时长可能被黄灯干扰导致实际绿灯时间比预期少。6. 验证与进阶用 TraCI 实时绘制控制效果并比较基线6.1 动态评估 DDPG 策略的脚本框架训练完成后不能只看训练奖励还要验证策略在未见过的车流模式下的表现。一个有效的评估方法是固定 Actor 网络的权重关闭探索噪声让模型在多个随机种子的环境里运行统计平均等待时间和排队长度。以下是一个评估循环的核心片段def evaluate_policy(env, actor, episodes50): total_waits [] total_queues [] for _ in range(episodes): state env.reset() ep_wait 0.0 ep_queue 0.0 done False while not done: with torch.no_grad(): action actor(torch.FloatTensor(state).unsqueeze(0)).numpy()[0] next_state, reward, done, info env.step(action) ep_wait info.get(total_wait, 0) ep_queue info.get(total_queue, 0) state next_state total_waits.append(ep_wait) total_queues.append(ep_queue) print(f平均等待时间: {np.mean(total_waits):.2f}) print(f平均排队量: {np.mean(total_queues):.2f})这段代码里info字典需要环境在step返回值中携带排除了奖励的额外统计信息便于评估时不混淆指标。如果环境没有返回info你可以把奖励与真实指标分开存储。6.2 对比固定配时和 DDPG 的收益评估 DDPG 时一定要与固定配时比如每个相位固定 30 秒做对比。下面给出一个简单的对比表生成思路fixed_waits run_fixed_timing(env, cycle30) ddpg_waits run_ddpg_timing(env, actor) improvement (np.mean(fixed_waits) - np.mean(ddpg_waits)) / np.mean(fixed_waits) * 100常见结果中DDPG 在车流量波动较大的场景下比固定配时减少 10%~30% 的平均等待时间但在车流均匀的场景下差距不大。如果你得到的改善为负说明奖励函数或状态设计有问题优先回看第 4 章。6.3 进阶方向多路口协同与离散变体单个路口的 DDPG 控制只是起点。实际交通网络是多路口协同问题每个信号灯智能体需要感知邻近路口的拥堵状态形成多智能体强化学习问题。这里有一个值得尝试的落地方案每个路口用独立的 DDPG 网络但在状态向量中加入邻近路口的排队信息奖励函数不要只看本路口的等待时间而是加入相邻路口信号相位的耦合惩罚。训练时先独立预训练再联合微调这种方法在很多学术项目中是有效的。另外如果后续你发现 DDPG 在离散相位切换任务上效率不高可以改用 TD3 或 SAC。TD3 在 DDPG 基础上引入了双 Critic 和目标策略平滑有效缓解过估计问题代码迁移成本也不高只需修改update_ddpg函数增加一个 Critic 网络并取最小值估计 Q 值。新手建议先把 DDPG 调通再扩展因为 DDPG 的失败模式更直观调试工具栈也更成熟。本文还有配套的精品资源点击获取
返回列表