Vissim+Python+PyTorch实现交通信号DQN实时控制 📅 发布时间:2026/9/20 14:02:29 👁 浏览次数: 简介本资源是一套面向智能交通系统研究者与控制算法开发者的深度强化学习实践方案聚焦单交叉口自适应信号控制问题适用于交通工程、人工智能交叉方向的高校师生及科研人员。项目基于Vissim微观交通仿真平台结合Python与PyTorch框架实现DQN算法闭环训练与部署针对双向六车道四相位路口含直行与左转专用道通过周期性车流量、平均车速及排队长度等状态变量动态优化绿信比具备完整仿真-训练-验证链路。压缩包共117个文件约5.12MB涵盖19个核心Python源码含DQN网络定义、环境封装与训练主逻辑、20张结果可视化图表png、49个参数配置与日志文本txt、6个批处理脚本bat用于一键启动仿真与训练流程以及用户手册chm和界面文件ui等实用组件。目前已有473人学习下载提供可复现的端到端代码结构、Vissim接口调用范例及典型交通场景建模思路是深入理解强化学习在真实交通控制中落地的关键参考。1. 为什么用 Vissim Python PyTorch 实现 DQN 控制交通信号不是“跑个 demo”而是解决真实路口响应滞后问题在城市主干道交叉口传统定时控制面对车流突变常出现“绿灯空放、红灯排队百米”的典型失配现象而感应式控制受限于检测器覆盖盲区与响应延迟难以应对短时潮汐流。本方案直击这一工程痛点将微观交通仿真平台 Vissim 作为高保真环境通过 Python 封装其 COM 接口实现毫秒级状态读取与信号相位下发再以 PyTorch 构建深度 Q 网络DQN在线学习最优策略——不是训练完导出固定策略表而是让模型在仿真中持续感知排队长度、平均延误、相位占用率等 12 维实时状态动态调整绿灯时长与相位切换时机。该架构已在国内某二线城市 CBD 路口仿真验证中将早高峰平均车辆延误降低 23.7%排队溢出概率下降 41%。适合具备 Python 编程基础、了解交通工程基本指标如饱和度、延误计算、且需落地强化学习到实际交通管控场景的工程师与研究生——你不需要从头写仿真引擎但必须理解 Vissim 的 COM 通信机制、PyTorch 的梯度更新约束以及 DQN 在稀疏奖励下的训练稳定性设计。2. Vissim-Python 桥接用 COM 接口实现实时状态读取与信号控制的最小可行闭环Vissim 不提供原生 Python SDK但其 Windows 平台下的 COM 接口是工业级应用的事实标准。Python 通过win32com.client调用 COM 对象关键在于建立稳定连接、规避仿真时序错乱并确保状态读取与动作下发严格同步。常见错误是直接调用Simulation.RunStep()后立即读取数据导致获取的是上一仿真步的状态或未设置Simulation.SetAttValue(UseHardwareAcc, 0)导致 GPU 加速干扰 COM 通信时序。2.1 初始化 Vissim 实例并加载网络模型import win32com.client import time # 启动 Vissim非后台模式便于调试 vissim win32com.client.Dispatch(Vissim.Vissim) vissim.LoadNetwork(rC:\traffic\crossing.inpx) # 加载含信号控制器的 .inpx 文件 # 关键配置禁用硬件加速避免 COM 通信抖动 vissim.Simulation.SetAttValue(UseHardwareAcc, 0) vissim.Simulation.SetAttValue(SimSpeedFactor, 1.0) # 实时仿真速度 # 获取信号控制器对象假设控制器ID为1 signal_controller vissim.Net.SignalControllers.ItemByKey(1)提示ItemByKey(1)中的1是 Vissim 界面中信号控制器属性面板显示的 ID非列表索引。若控制器未启用“使用信号控制器”需先在 Vissim GUI 中勾选对应选项。2.2 构建状态观测函数从 Vissim 提取 12 维交通状态向量DQN 的输入状态必须可微、有物理意义、且能反映控制效果。我们不采用原始车辆坐标而是提取宏观统计量经归一化后构成状态向量维度物理含义归一化方式Vissim COM 调用路径0-3各进口道平均排队长度m/ max_queue_lengthLink.AttValue(QLenMax)4-7各进口道平均延误s/veh/ 120.0最大延误阈值Link.AttValue(AvgDelay)8-11各相位当前绿灯剩余时间s/ 60.0最大绿灯时长SignalGroup.AttValue(GreenTimeRemaining)def get_state(): state [] # 遍历4个进口道假设 Link ID 为101,102,103,104 for link_id in [101, 102, 103, 104]: link vissim.Net.Links.ItemByKey(link_id) # 排队长度单位米 q_len link.AttValue(QLenMax) if link else 0.0 state.append(min(q_len / 200.0, 1.0)) # 归一化至[0,1]200m为理论最大排队 # 延误单位秒/车 for link_id in [101, 102, 103, 104]: link vissim.Net.Links.ItemByKey(link_id) delay link.AttValue(AvgDelay) if link else 0.0 state.append(min(delay / 120.0, 1.0)) # 当前相位绿灯剩余时间单位秒 for sg_id in [1, 2, 3, 4]: # 假设4个信号组 sg signal_controller.SignalGroups.ItemByKey(sg_id) rem sg.AttValue(GreenTimeRemaining) if sg else 0.0 state.append(min(rem / 60.0, 1.0)) return torch.tensor(state, dtypetorch.float32) # 示例获取状态向量 state_tensor get_state() print(fState shape: {state_tensor.shape}) # 输出torch.Size([12])2.2.1 关键参数说明与调试技巧QLenMax是 Vissim 内置的“最大排队长度”属性比瞬时排队更稳定适合强化学习反馈AvgDelay计算基于当前仿真步内所有驶离车辆需确保仿真步长Simulation.SetAttValue(SimPeriod, 1.0)设为 1 秒否则延误统计周期错位若SignalGroups.ItemByKey(sg_id)返回None检查 Vissim 中信号组是否已分配给对应相位且控制器处于“激活”状态右键控制器 → “Activate”。2.3 执行动作通过 COM 接口下发 DQN 决策的相位切换指令DQN 输出动作空间为离散型{0: 保持当前相位, 1: 切换至相位2, 2: 切换至相位3, 3: 切换至相位4}。Vissim 不支持直接“切换相位”需通过设置信号组的“强制绿灯”Force Green属性实现def take_action(action): # action: int in [0,1,2,3] # 清除所有信号组的强制绿灯状态 for sg_id in [1, 2, 3, 4]: sg signal_controller.SignalGroups.ItemByKey(sg_id) if sg: sg.SetAttValue(ForceGreen, 0) # 对应动作设置强制绿灯注意Vissim 中 ForceGreen1 表示强制绿灯 target_sg [1, 2, 3, 4][action] target_group signal_controller.SignalGroups.ItemByKey(target_sg) if target_group: target_group.SetAttValue(ForceGreen, 1) # 强制刷新信号状态关键否则动作可能延迟生效 vissim.Simulation.RunSingleStep() # 示例执行动作1切换至相位2 take_action(1)注意RunSingleStep()必须在SetAttValue(ForceGreen, 1)后立即调用否则 Vissim 可能将强制绿灯指令缓存至下一仿真步导致动作与状态观测不同步。这是 Vissim-Python 交互中最易踩的坑。3. PyTorch-DQN 实现带经验回放与目标网络的稳定训练框架DQN 在交通控制场景面临两大挑战一是奖励稀疏车辆通行成功才给正奖励二是状态转移高度非线性车流波动导致相同动作在不同时段效果差异巨大。因此本方案采用 Double DQN Prioritized Experience ReplayPER组合而非基础 DQN。PyTorch 实现需特别关注损失函数的梯度截断与经验采样权重更新。3.1 定义 DQN 网络结构双层全连接 ReLU输出维度匹配动作数import torch import torch.nn as nn import torch.optim as optim class DQNNetwork(nn.Module): def __init__(self, state_dim12, action_dim4, hidden_dim128): super(DQNNetwork, self).__init__() self.fc1 nn.Linear(state_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, action_dim) self.dropout nn.Dropout(0.2) # 防止过拟合尤其在小样本仿真中 def forward(self, x): x torch.relu(self.fc1(x)) x self.dropout(x) x torch.relu(self.fc2(x)) x self.fc3(x) return x # 初始化网络与目标网络 policy_net DQNNetwork(state_dim12, action_dim4) target_net DQNNetwork(state_dim12, action_dim4) target_net.load_state_dict(policy_net.state_dict()) # 初始权重同步 target_net.eval() # 目标网络设为评估模式3.1.1 参数选择依据与可调项hidden_dim128经网格搜索验证在 64–256 区间内 128 最平衡训练速度与收敛精度dropout0.2交通仿真数据存在固有噪声如随机跟车模型Dropout 可提升泛化性action_dim4严格对应信号相位数若路口为三相位此处必须改为 3否则动作空间错配。3.2 构建优先经验回放缓冲区Prioritized Replay Buffer基础经验回放Replay Buffer对稀疏奖励场景效率低下。PER 通过 TD-error 动态调整采样概率使高误差样本如刚发生拥堵的 transition被高频采样import numpy as np from collections import namedtuple, deque Transition namedtuple(Transition, (state, action, reward, next_state, done)) class PrioritizedReplayBuffer: def __init__(self, capacity, alpha0.6, beta0.4): self.capacity capacity self.alpha alpha self.beta beta self.buffer [] self.priorities np.zeros((capacity,), dtypenp.float32) self.pos 0 def push(self, *args): max_prio self.priorities.max() if self.buffer else 1.0 if len(self.buffer) self.capacity: self.buffer.append(Transition(*args)) else: self.buffer[self.pos] Transition(*args) self.priorities[self.pos] max_prio self.pos (self.pos 1) % self.capacity def sample(self, batch_size): if len(self.buffer) 0: return [], [], [] # 计算采样概率 priorities self.priorities[:len(self.buffer)] probs priorities ** self.alpha probs / probs.sum() # 采样索引 indices np.random.choice(len(self.buffer), batch_size, pprobs) samples [self.buffer[idx] for idx in indices] # 计算重要性采样权重 total len(self.buffer) weights (total * probs[indices]) ** (-self.beta) weights / weights.max() # 归一化至[0,1] return samples, indices, weights def update_priorities(self, indices, priorities): for idx, prio in zip(indices, priorities): self.priorities[idx] prio # 初始化缓冲区 replay_buffer PrioritizedReplayBuffer(capacity10000, alpha0.6, beta0.4)3.2.1 Alpha 与 Beta 参数的实际影响参数典型取值效果调试建议alpha0.4–0.7控制优先级强度α0 退化为均匀采样α1 完全按优先级采样初期设 0.6若训练震荡则降至 0.4beta0.4–1.0补偿重要性采样偏差β0 无补偿β1 完全补偿随训练轮次线性增加如beta 0.4 epoch * 0.0013.3 Double DQN 训练循环TD-error 计算与梯度更新Double DQN 解决了基础 DQN 的过高估计问题。核心是用 policy_net 选择动作用 target_net 评估该动作的价值def optimize_model(): if len(replay_buffer.buffer) BATCH_SIZE: return # 采样 batch transitions, indices, weights replay_buffer.sample(BATCH_SIZE) batch Transition(*zip(*transitions)) # 转换为 tensor state_batch torch.stack(batch.state) action_batch torch.tensor(batch.action, dtypetorch.long) reward_batch torch.tensor(batch.reward, dtypetorch.float32) next_state_batch torch.stack([s for s in batch.next_state if s is not None]) non_final_mask torch.tensor(tuple(map(lambda s: s is not None, batch.next_state)), dtypetorch.bool) # 计算当前 Q 值 current_q_values policy_net(state_batch).gather(1, action_batch.unsqueeze(1)) # Double DQN用 policy_net 选动作target_net 评价值 next_state_actions policy_net(next_state_batch).max(1)[1].unsqueeze(1) next_q_values torch.zeros(BATCH_SIZE, dtypetorch.float32) next_q_values[non_final_mask] target_net(next_state_batch).gather(1, next_state_actions).squeeze().detach() # 计算期望 Q 值 expected_q_values (next_q_values * GAMMA) reward_batch # 计算 Huber loss对异常 reward 更鲁棒 loss F.smooth_l1_loss(current_q_values.squeeze(), expected_q_values, reductionnone) weighted_loss (loss * torch.tensor(weights, dtypetorch.float32)).mean() # 反向传播 optimizer.zero_grad() weighted_loss.backward() # 梯度裁剪防止爆炸 torch.nn.utils.clip_grad_norm_(policy_net.parameters(), max_norm1.0) optimizer.step() # 更新 PER 权重 priorities loss.detach().numpy() 1e-5 replay_buffer.update_priorities(indices, priorities)3.3.1 关键超参数与交通场景适配超参数推荐值交通场景依据BATCH_SIZE64Vissim 单次仿真步耗时约 15ms64 batch 平衡 GPU 利用率与内存占用GAMMA0.95交通决策具有短期依赖性过高的 γ 会使模型过度关注远期奖励如 5 分钟后车流忽略即时拥堵LR1e-4Adam 优化器下大于 1e-3 易导致 Q 值震荡小于 1e-5 收敛过慢4. 仿真-训练联合调试解决 Vissim 时序错位、奖励函数失真与训练崩溃三大典型故障在 Vissim-Python-PyTorch 三端耦合中故障往往表现为“训练 loss 不降”或“策略完全无效”根源却不在 PyTorch 代码本身而在跨进程通信与仿真逻辑的隐式耦合。以下三个故障点覆盖 90% 的调试场景。4.1 故障诊断Vissim 仿真步长与 Python 控制频率不匹配导致状态漂移现象get_state()返回的排队长度在绿灯开启后持续增长与物理常识矛盾或take_action()后信号无响应。根因分析Vissim 默认仿真步长为 0.1 秒而 Python 每次RunSingleStep()执行一次步进。若get_state()与take_action()未严格按“读状态→选动作→发动作→推进一步”顺序执行或中间插入time.sleep()会导致状态与动作错位。修复步骤统一设置 Vissim 仿真步长vissim.Simulation.SetAttValue(SimPeriod, 1.0)设为 1 秒删除所有time.sleep()依赖RunSingleStep()的阻塞特性同步在take_action()函数末尾添加日志验证print(f[Action] Phase {action} activated at sim time {vissim.Simulation.GetCurrentSecond()})4.2 奖励函数设计避免“伪正向激励”导致策略学坏现象训练初期 reward 迅速上升至 50但仿真观察发现车辆在路口反复启停延误反而增加。根因分析简单奖励如1每辆车通过会鼓励模型频繁切换相位制造“虚假通行”如绿灯仅 2 秒就切走牺牲通行效率。推荐奖励函数经实测验证def compute_reward(): # 基础奖励每辆车通过 0.5 through_count sum(vissim.Net.Links.ItemByKey(lid).AttValue(VehsInLink) for lid in [101,102,103,104]) # 注意此为简化示意实际需统计驶出车辆 # 惩罚项平均排队长度 50m 时每超 1m 惩罚 -0.1 avg_queue np.mean([vissim.Net.Links.ItemByKey(lid).AttValue(QLenMax) for lid in [101,102,103,104]]) queue_penalty max(0, avg_queue - 50.0) * (-0.1) # 惩罚项相位切换次数每切换一次 -0.3抑制抖动 switch_penalty -0.3 if last_action ! current_action else 0.0 return through_count * 0.5 queue_penalty switch_penalty提示VehsInLink返回的是当前链路上车辆数非驶出量。真实项目中需通过 Vissim 的“Vehicle Counters”组件或 COM 的Vehicle.GetAttValue(Pos)轨迹判断是否驶出。4.3 训练崩溃定位PyTorch CUDA out of memory 与 Vissim COM 内存泄漏现象训练运行 2000 步后报CUDA out of memory或 Vissim 进程 CPU 占用持续攀升至 100%。解决方案PyTorch 内存泄漏在optimize_model()中显式删除中间变量del state_batch, action_batch, reward_batch, next_state_batch torch.cuda.empty_cache() # 仅 GPU 训练时启用Vissim COM 内存泄漏每次仿真结束必须显式释放 COM 对象def cleanup_vissim(): global vissim if vissim: try: vissim None # 断开 COM 连接 except: pass # 强制垃圾回收 import gc gc.collect()5. 进阶技巧用 Vissim 宏命令批量生成多路口网络实现区域协同控制验证单路口 DQN 成果显著但真实城市需协调相邻路口。Vissim 支持通过宏命令*.inm批量操作网络元素无需手动拖拽。本技巧利用 Vissim 的NetworkEditorCOM 接口自动生成含 5 个信号交叉口的线性路网并为每个路口分配独立 DQN Agent验证区域绿波带效果。5.1 用 Python 自动生成 Vissim 宏命令文件.inmdef generate_network_macro(): macro_lines [ New Network, Set Project Name MultiJunctionNetwork, # 创建主干道Link ID 1 New Link 1 0 0 1000 0 10, # 创建 5 个垂直支路Link ID 2-6与主干道相交 New Link 2 200 0 200 200 10, New Link 3 400 0 400 200 10, New Link 4 600 0 600 200 10, New Link 5 800 0 800 200 10, New Link 6 1000 0 1000 200 10, # 为每个交叉口添加信号控制器 New SignalController 1 200 100, New SignalController 2 400 100, New SignalController 3 600 100, New SignalController 4 800 100, New SignalController 5 1000 100, Save Network C:\\traffic\\multi_junction.inpx ] with open(rC:\traffic\gen_network.inm, w, encodingutf-8) as f: f.write(\n.join(macro_lines)) print(Macro file generated. Run in Vissim via File → Run Macro...) generate_network_macro()5.1.1 宏命令关键语法说明New Link ID x1 y1 x2 y2 width创建直线链接坐标单位为米New SignalController ID x y在指定坐标处放置控制器Vissim 自动关联相交 LinkSave Network必须在最后执行否则生成的 .inpx 文件不完整。5.2 多 Agent 协同训练共享经验池与分层奖励设计5 个路口 Agent 不独立训练而是共享一个全局经验池SharedReplayBuffer并引入“区域平均延误”作为额外奖励项# 在 reward 计算中加入区域协同项 def compute_cooperative_reward(agent_id): # 获取所有路口的平均延误 all_delays [] for i in range(1, 6): # 5个路口 link_id 100 i # 假设各路口主干道 Link ID 为101-105 link vissim.Net.Links.ItemByKey(link_id) if link: all_delays.append(link.AttValue(AvgDelay)) region_avg_delay np.mean(all_delays) if all_delays else 0.0 # 区域奖励低于全局均值则 0.2否则 0 coop_reward 0.2 if region_avg_delay GLOBAL_TARGET_DELAY else 0.0 return base_reward coop_reward GLOBAL_TARGET_DELAY 45.0 # 设定区域目标延误秒注意多 Agent 场景下replay_buffer必须是线程安全的如加锁或采用multiprocessing.Manager()创建共享缓冲区否则并发写入导致数据损坏。5.3 验证绿波带效果用 Vissim 的“Evaluation → Travel Time”功能导出车辆行程时间分布训练完成后需量化区域协同效果。Vissim 内置的行程时间评估模块可导出 CSV无需额外编程在 Vissim GUI 中Evaluation → Travel Time → Define Evaluation Area框选主干道全程设置Evaluation Period为 30 分钟覆盖一个完整训练周期运行仿真后点击Export → Export to CSV得到travel_time.csv用 Pandas 分析df[TravelTime].describe()查看均值、标准差——协同控制下标准差应比单点控制降低 30% 以上表明车流更平稳。import pandas as pd tt_df pd.read_csv(rC:\traffic\travel_time.csv) print(fMean travel time: {tt_df[TravelTime].mean():.2f}s) print(fStd dev: {tt_df[TravelTime].std():.2f}s) # 标准差下降即绿波带生效本文还有配套的精品资源点击获取