
简介基于 Python 与 SUMO 联合仿真平台的 DQN 强化学习源码面向交通信号灯相位时间的智能调节场景。项目源自个人毕业设计答辩评审分数达九十八分全部代码经调试测试确保可以稳定运行适合计算机、通信、人工智能、自动化等相关专业的学生、教师及从业者下载使用可作为期末课程设计、课程大作业或毕业设计的参考。压缩包内共有三十二个文件以 XML 路网与交通流配置、OSM 地图数据、仿真配置文件、算法脚本及表格数据为主整体大小约五百三十六千字节。其中主程序、强化学习网络、优先级训练代码等内容完整清晰展示了状态定义、奖赏函数、经验回放机制及 SUMO 交互流程并附说明文档便于快速上手。目前已有八十人学习下载适合深入理解 DQN 在智能交通信号控制中的应用也可在此之上修改网络结构或控制策略扩展自适应配时功能。1. 用SUMO做信号灯DQN调优为什么值得自己从源码动手交通信号灯控制看起来是个老问题红绿灯配时方案从固定周期到感应控制都成熟了为什么还要上强化学习原因很直接真实路口车流是时变的早高峰、晚高峰、平峰、突发拥堵的流量特征完全不同固定配时方案只能按历史统计去凑而强化学习里的DQN有能力根据当前排队长度、车流量实时调整相位时长。这个项目就是一条完整的落地链路用SUMO做仿真环境用Python写DQN智能体通过TraCI接口实时读取路口状态、下发相位切换动作最终目标是让信号灯自己学会“什么时候该给直行多放几秒什么时候该让左转先走”。这个方案适合两类人一类是做交通工程或车联网方向的学生需要一套能跑通、能出实验对比图的基线另一类是刚接触强化学习、想找一个非游戏类真实环境练手的开发者。SUMO自带的路网编辑器和TraCI接口把“环境”这件事全部解决你只需要聚焦在DQN本身的状态设计、奖励函数和训练循环上。下面从环境建模到源码落地把这个项目的完整链条拆开讲清楚包括参数怎么设、代码怎么改、训练哪里最容易翻车。2. 信号灯相位时间为什么能用DQN调状态、动作与奖励的建模2.1 SUMO里的信号灯控制接口从tls到phase的逻辑SUMO对信号灯有一套完整的数据模型一个信号灯程序traffic light program包含多个相位phase每个相位定义了一组灯色状态和持续时长。默认情况下SUMO按固定配时方案运行每个phase走完自己的duration就切换。DQN要介入本质上就是把“duration是固定值”改成“duration由智能体根据当前交通状态决定”。操作入口是TraCI接口中的trafficlight模块。常用命令包括traci.trafficlight.getAllProgramLights()查看某个信号灯的所有灯色定义traci.trafficlight.getPhase()当前处于第几个相位traci.trafficlight.getPhaseDuration()当前相位已运行时间traci.trafficlight.setPhaseDuration()重新设置当前相位的剩余时长实际的修改逻辑是信号灯进入某个相位后先按默认时长运行一小段时间然后DQN根据当前路口的排队长度、车流量数据决定“继续延长当前相位”还是“立即切换”。在SUMO里实现“立即切换”有两种常见做法一是把当前phase的duration设为0.1秒让它快速走完二是直接用setPhase()强制跳转到下一个相位但要注意绿灯间隔黄灯、全红不能被跳过否则会出安全问题——仿真里虽然不撞车但统计出来的等待时间会失真。理解了这个机制就能明白DQN和信号灯之间的关系DQN不是直接控制每个灯色而是控制“每个相位持续多久”也就是绿信比的动态分配。这是整个项目的核心思路。2.2 动作空间设计固定相位序列下怎么调绿信比在SUMO的默认信号灯方案里相位序列是固定的比如一个典型十字路口有四相位南北直行、南北左转、东西直行、东西左转。每个相位之间插入黄灯过渡。DQN的动作空间设计有两种常用方案效果和实现成本差别很大。第一种是“延长/切换”动作空间也就是每个决策点给两个动作0代表延长当前相位一个时间步比如5秒1代表结束当前相位进入下一个。这个方案动作空间小DQN容易收敛适合新手跑通流程。缺点是信号灯切换频率可能过高而且如果模型学偏了会出现某个方向长时间不放行的极端情况。第二种是“目标相位选择”动作空间比如四相位路口直接输出四个动作每个动作代表“下一个放行相位”。这个方案更灵活但实现复杂因为需要考虑当前相位与目标相位之间的过渡相位黄灯、全红是否合理动作空间变大后训练难度也明显上升。我见过的大多数SUMODQN信号灯开源项目用的是第一种原因很实际动作空间小意味着需要探索的样本少训练收敛快而且和SUMO的phase机制天然匹配——你只需要改setPhaseDuration()不用动信号灯程序本身的结构。另外无论哪种动作方案都必须设置最小绿灯时间和最大绿灯时间。最小绿灯时间保证行人过街的基本需求最大绿灯时间防止某个方向被无限放行。这两个参数是硬约束比奖励函数更能防止模型学出离谱策略。2.3 奖励函数怎么设才不白训平均等待时间与排队长度奖励函数是DQN项目里最玄学也最关键的部分。如果设成“车辆通过数”或“通行量”模型容易学偏——它会倾向于让绿灯一直给车多的方向导致次要方向的车越积越多。反过来如果直接用“所有车辆的总等待时间”做奖励数值波动太大DQN训练初期样本方差高收敛很慢。比较稳妥的做法是组合多个指标做加权每秒所有车辆的平均等待时间average waiting time路口的最大排队长度max queue length已通过车辆数可以通过统计departed和arrived的差值得到我常用的奖励公式是reward -(alpha * avg_waiting_time beta * max_queue_length)alpha取值0.6~0.8beta取值0.2~0.4具体比例根据路口规模调整。这里的奖励是负值DQN的目标是让累计奖励最大化也就是让等待时间和排队长度尽量小。需要注意的一个细节是在SUMO中获取等待时间的常用接口是traci.vehicle.getAccumulatedWaitingTime(vehicle_id)对所有在路车辆求和再取平均。但SUMO里有些车辆可能已经结束行程对这些车辆调用获取接口会抛异常所以获取状态前要先过滤一遍有效期内的车辆ID否则训练脚本跑一半就会崩溃。另一个常见做法是把奖励拆成“当前时刻的快照”和“上一时刻到当前时刻的变化量”变化量能让模型感知到趋势而不是只看到瞬时值。比如上一秒等待时间总和是500秒这一秒是480秒那就是改善奖励加一点如果是520秒那就是恶化奖励减一点。这样组合后DQN学起来比单纯用瞬时值要稳定得多因为瞬时值的噪声太大了。3. 用Python搭起SUMODQN训练循环核心代码怎么组织3.1 环境封装traci连接、仿真步进与数据采集DQN训练的第一步是把SUMO封装成一个标准的强化学习环境至少要有reset、step、get_state、get_reward这几个方法。封装的目的不是为了好看而是为了训练循环的代码能保持干净后面换网络结构、换超参数时不用改环境部分。首先建立连接并启动SUMO仿真import traci import subprocess def start_sumo(sim_config, guiFalse): sumo_binary sumo-gui if gui else sumo cmd [sumo_binary, -c, sim_config, --step-length, 0.5] if not gui: cmd.append(--no-step-log) cmd.append(true) subprocess.Popen(cmd, stdoutsubprocess.DEVNULL, stderrsubprocess.DEVNULL) traci.init(port8813, num_clients1)这里的关键参数是--step-length。默认是1秒但信号灯相位切换以0.5秒为粒度会更平滑尤其当DQN的决策间隔设为5秒时0.5秒的仿真步长能让状态采样更精确。启动方式用subprocess.Popen启动外部进程再通过TraCI端口连接这是最通用的做法不依赖GUI界面也能训练。封装环境的step方法需要做三件事执行DQN给出的动作、让SUMO推进仿真、采集新的状态和奖励class SumoEnv: def __init__(self, cfg): self.tls_id cfg[tls_id] self.min_green cfg[min_green] # 最小绿灯时间秒 self.max_green cfg[max_green] # 最大绿灯时间秒 self.decision_interval cfg[decision_interval] # 决策间隔秒 self.phase_elapsed 0 def step(self, action): # action 0: 延长当前相位; action 1: 切换下一相位 current_phase traci.trafficlight.getPhase(self.tls_id) current_duration traci.trafficlight.getPhaseDuration(self.tls_id) if action 1 and self.phase_elapsed self.min_green: traci.trafficlight.setPhaseDuration(self.tls_id, 0.1) # 快速结束当前相位 else: traci.trafficlight.setPhaseDuration(self.tls_id, self.decision_interval) traci.simulationStep() # 推进仿真 self.phase_elapsed self.decision_interval state self._get_state() reward self._get_reward() done self._check_end() return state, reward, done这段代码里action 1时把相位剩余时间设为0.1秒SUMO会在下一个仿真步快速进入黄灯相位然后自然流转到下一个相位。self.phase_elapsed记录当前相位已经从绿灯开始运行了多久用这个值和min_green比较来阻止模型在绿灯刚亮就切换。这是所有DQN信号灯项目必须加的机制没有这个约束模型会把信号灯切得比手动控制还乱。3.2 DQN网络与经验回放PyTorch实现的关键片段DQN部分建议直接用PyTorch代码量小且调试灵活。网络结构不需要复杂两层全连接隐藏层足够处理单路口的控制问题。状态向量一般包括当前相位编号one-hot编码、各相位对应的排队长度、各相位对应车道的车辆平均等待时间再加一个当前相位已运行时长。一个可用网络定义如下import torch import torch.nn as nn class DQN(nn.Module): def __init__(self, state_dim, action_dim, hidden_dim64): super(DQN, self).__init__() self.net nn.Sequential( nn.Linear(state_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, action_dim) ) def forward(self, x): return self.net(x)经验回放池是DQN稳定训练的核心组件。从SUMO环境里采到的样本是时间相关的——t时刻的状态和t1时刻的状态高度相似直接用这些样本顺序训练网络会被带偏。经验回放通过随机抽样打破这种相关性from collections import deque import random class ReplayBuffer: def __init__(self, capacity20000): self.buffer deque(maxlencapacity) def push(self, s, a, r, s_next, done): self.buffer.append((s, a, r, s_next, done)) def sample(self, batch_size): batch random.sample(self.buffer, batch_size) s, a, r, s_next, done map(torch.stack, zip(*batch)) return s, a, r, s_next, done def __len__(self): return len(self.buffer)capacity设为20000对单路口场景足够太大反而会导致训练早期采到的样本占比过高模型更新跟不上新策略。另外训练目标网络target network的同步周期我一般设成每隔500步同步一次。同步太频繁会退化成一个网络失去目标网络的意义同步太慢又会让目标Q值过于旧训练前期波动大。3.3 训练主循环每N秒决策一次用epsilon-greedy探索训练主循环要解决的核心问题不是网络结构而是“多久做一次决策”。我建议决策间隔设为5~10秒原因是信号灯相位本身需要一定的稳定时间频繁决策产生的震荡会让模型看到大量无意义的过渡状态每5秒决策一次一个100秒的路口场景大约有20个决策点已经足够训练。主循环的伪代码结构如下for episode in range(MAX_EPISODES): env.reset() state env.get_initial_state() total_reward 0 while not done: epsilon max(epsilon_min, epsilon_decay * epsilon) if random.random() epsilon: action random.choice([0, 1]) # 探索 else: with torch.no_grad(): q_values dqn_net(state) action q_values.argmax().item() # 利用 next_state, reward, done env.step(action) replay_buffer.push(state, action, reward, next_state, done) total_reward reward if len(replay_buffer) BATCH_SIZE: batch_s, batch_a, batch_r, batch_s_next, batch_done replay_buffer.sample(BATCH_SIZE) # 计算目标Q值 with torch.no_grad(): target_q batch_r gamma * target_net(batch_s_next).max(dim1).values * (1 - batch_done) current_q dqn_net(batch_s).gather(1, batch_a.unsqueeze(1)).squeeze(1) loss nn.MSELoss()(current_q, target_q) optimizer.zero_grad() loss.backward() optimizer.step() state next_state # 每4个episode同步一次target网络 if episode % 4 0: target_net.load_state_dict(dqn_net.state_dict())这里的epsilon衰减策略是全局控制。epsilon_min设0.05epsilon_decay按episode数线性衰减训练前30%的episode里以探索为主后面逐步转向利用。gamma折扣因子设0.95比较合适——信号灯控制是一个短视问题当前动作的影响主要在接下来几十秒内体现折扣因子太大反而让模型过度关注远期收益。3.4 日志与可视化拿什么判断模型真的在收敛训练日志不能只看loss曲线。DQN的loss下降很多时候不代表策略变好可能是网络对当前样本的拟合变好了但样本本身是不断变化的。真正要盯的是两个指标每个episode的平均等待时间和累计奖励。我习惯的做法是训练时周期性地打印一组统计信息print(fEpisode {episode} | Avg Wait: {avg_wait:.2f}s | Total Reward: {total_reward:.2f} | Epsilon: {epsilon:.3f})同时把每个episode的数据写入CSV文件训练结束后用Matplotlib画曲线。比较有价值的是“平均等待时间随episode的变化曲线”——如果曲线整体呈下降趋势且波动逐渐变小说明策略在稳定改善如果曲线剧烈震荡优先怀疑是学习率太高其次怀疑奖励函数中有指标在打架。要特别注意SUMO默认输出很多仿真日志训练时长拉长后这些日志会拖慢速度。启动参数里加--no-step-log和--no-warnings能省不少时间。训练中期想直观确认模型行为是否正确可以启动一次guiTrue的仿真亲眼看信号灯切换是否符合直觉。4. 把源码跑起来的最小可复现路径依赖、参数与脚本4.1 仿真环境与Python依赖的安装这个项目跑起来需要三样东西SUMO本体、Python环境和几个必要的Python包。SUMO的安装没有太多玄学不同系统的安装方式有现成包管理器可用关键是安装后确认sumo和sumo-gui命令在终端能直接调用。Windows系统装完后需要手动把SUMO的bin目录加进环境变量PATH否则Python脚本通过subprocess调用sumo时会报找不到命令。Python依赖相对简单pip install traci libsumo torch numpy matplotlibtraci和libsumo会随SUMO安装包一起提供但用pip单独安装更省心版本兼容性一般没问题。torch建议装CPU版本就够用——DQN网络只有几万参数CPU训练速度完全能接受没必要为了这个项目折腾CUDA。验证安装是否成功可以在终端执行python -c import traci; print(traci.__version__) python -c import torch; print(torch.__version__)如果import traci失败多半是Python环境路径问题优先检查是不是有多个Python版本混用。这个坑比较常见最好用虚拟环境隔离项目依赖省得后面搬机器时出现一堆环境问题。4.2 路网生成用netedit画一个十字路口训练用的路网不需要多复杂一个标准的四向十字路口就够。SUMO自带的路网编辑器netedit可以用图形界面直接画也可以直接写一个.net.xml文件。我建议用netedit画因为图形界面能直观看到车道、连接关系和信号灯相位新手不容易出错。netedit里画十字路口的核心步骤是先画四条双向四车道的道路注意每条路要拆成两段才能形成十字交叉口然后点“create edge”和“create connection”设置转向连接最后选中交叉口点击信号灯图标为它创建一个tls程序。SUMO会自动生成默认的相位方案默认方案包含所有方向的直行和左转相位每相位绿灯时长默认是31秒。生成的路网文件是一个.net.xml文件同时需要写一个.sumo.cfg配置文件指向它configuration input net-file valuecross.net.xml/ /input time begin value0/ end value3600/ /time /configurationend设为3600表示每次仿真跑1小时。训练时每个episode不需要跑满1小时建议在代码里用traci.simulation.getTime()判断是否达到设定时长达到就提前结束这个episode并重置。4.3 训练脚本的启动命令与关键参数训练脚本建议直接一个Python文件跑起来不要拆太多模块。DQN信号灯项目的主流文件组织方式可以很轻简一个train.py包含环境类、DQN网络、回放池和训练循环总共300行以内逻辑一目了然。启动训练的常用方式python train.py --config cross.sumocfg --episodes 200 --lr 0.001 --batch-size 64 --gamma 0.95用argparse解析这些参数方便跑对比实验时不改代码。核心超参数设置我习惯按下面这个基准来参数推荐值说明决策间隔5秒每个动作的影响窗口太短易震荡太长反应迟钝最小绿灯时间10秒硬约束确保行人过街最大绿灯时间60秒硬约束防止单方向拥堵learning rate0.001太大训练震荡太小收敛慢batch size6432太小梯度不稳定128以上单步更新慢replay buffer20000单路口场景足够target网络同步间隔4 episode实测稳定性和收敛速度的平衡点epsilon decay0.995每个episode乘一次200个episode后低于0.1需要注意这些都是基准值不是万能配方。路网变大、车流量变高之后max_green要相应调大replay buffer容量也要增大。一开始小路口调参跑通再逐步扩大规模是更稳妥的路径。4.4 训练结果怎么看日志、曲线与SUMO GUI回放训练结束后检查成果的方式有三个层次。最基础的是看终端打印的每个episode平均等待时间——数值是否在逐级下降。更靠谱的是画平均等待时间随episode变化的平滑曲线窗口设10取移动平均能看清楚整体趋势。最直观的是用SUMO GUI重新跑一遍模型控制下的仿真让模型每5秒决策一次肉眼观察信号灯是否合理。我用代码做了一次对比评估分别跑固定配时方案和训练好的DQN方案统计各自3600秒内的平均等待时间def evaluate_policy(env, policy, duration3600): env.reset() traci.simulationStep() total_wait 0 n_steps 0 while traci.simulation.getTime() duration: state env.get_state() with torch.no_grad(): action policy(state).argmax().item() env.step(action) total_wait sum(traci.vehicle.getAccumulatedWaitingTime(vid) for vid in traci.vehicle.getIDList()) n_steps 1 return total_wait / n_steps评估时注意不要在getAccumulatedWaitingTime之前让车辆全部驶离否则返回的车ID列表为空求和为0看起来指标很好但实际上什么都没测到。评估脚本要和训练脚本分离训练时模型还在更新评估结果没有参考价值。5. DQN调信号灯的七个常见坑现象、原因与解决5.1 训练到一半SUMO进程卡死仿真不前进现象训练日志停止输出traci.simulationStep()没有返回进程像是被挂起。原因最常见的是TraCI连接状态异常。子进程启动的SUMO在报错时不会自动退出而是等待连接但Python那边没有收到任何信号。另一个常见原因是端口被占用上一次训练没有正常关闭8813端口还在被监听。解决在启动脚本开头加一个端口清理逻辑先尝试连接一次如果失败则杀掉残留的sumo进程。同时给TraCI调用包一层超时保护超过几秒没有返回就断开重连。训练循环加一个try...except捕获TraCI报错并自动重启SUMO进程能省掉很多半夜训练被中断的麻烦。5.2 信号灯切换频率异常像在“抖灯”现象模型学出来的策略导致绿灯相位在最小绿灯时间一到就立刻切换信号灯不断循环车辆基本走不了几辆。原因动作空间里“切换”的收益被高估了。DQN发现切换后能获得短暂的奖励下降因为排队车辆看到绿灯会有一个加速度但代价是下一秒其他方向积压的车辆产生的等待时间会更大。如果奖励函数里等待时间的权重不够或者γ折扣因子太小模型就会只顾眼前切换带来的短期收益。解决加大min_green到15秒以上在奖励函数里对“切换动作”本身加一个小的惩罚项比如每次切换−1。这个惩罚不是为了防止切换而是让模型意识到切换是有成本的只有切换带来的收益大于成本时才值得切。这个方法比单纯调权重更直接有效。5.3 奖励一直负得厉害模型完全没有学习信号现象训练了100多个episode每个episode的reward都在−500到−600之间波动没有上升趋势loss曲线也不下降。原因大概率是状态或奖励的数值范围没做归一化。等待时间是以秒计的在拥堵场景里可能是几百上千这个数值直接进Q网络会让梯度爆炸。另一个因素是回报稀疏——如果奖励只在终点时才给一个大的负值中间过程没有反馈DQN学不到有效梯度。解决把状态向量里的每个维度做归一化。排队长度除以车道数×最大排队长度等待时间除以一个经验上限值比如200秒把数值压到0~1。奖励也要做缩放reward直接除以10再返回让Q值的范围落在个位数级别。做完归一化后再训练收敛速度有明显改观。5.4 车辆堵在路口中央后续车辆全部死锁现象仿真进行一段时间后路口中央有车辆滞留后面的车无法前进整个路网堵死等待时间飙升。原因信号灯切换时机不对导致车辆驶入路口后无法驶出。这种情况在左转相位里最容易出现——左转车流量大绿灯时间不够车排队进了路口中央等直行相位启动时这些车挡在路中间。解决SUMO的路网文件里需要正确设置“junction”的禁止阻塞属性在netedit中选中路口节点检查是否选中了“block by blockage”等防死锁选项。另外min_green加大到能让一个相位内的车辆完全清空也是有效手段。仿真层面还有一个临时补救办法在检测到某辆车在路口中央停留超过设定时间时用traci.vehicle.setSpeed()让它先驶离。5.5 训练阶段一切正常评估阶段模型表现突然变差现象训练时每个episode的平均等待时间稳步下降训练结束用固定参数跑评估等待时间比训练后期高出很多。原因训练过程中epsilon一直在衰减后期模型已经很少做随机探索但评估时要去掉epsilon策略行为会和训练后期不完全一致。更大的可能是过拟合了训练时的流量模式——训练车流是固定随机种子生成的模型记住了特定流量下的最优策略换了流量分布就失效。解决训练时每隔几个episode换一次随机种子或者每次episode重新生成随机车流。评估时要换一组和训练时完全不同的流量数据至少换随机种子最好换流量强度。如果换流量后表现大幅下降说明模型没有学到泛化的控制策略需要回到状态设计上补充更多维度的交通特征。5.6 训练速度极慢一个episode要跑几分钟现象仿真的车辆数其实不大但每个episode耗时很长训练200个episode要跑好几个小时。原因两个叠加因素仿真步长太小导致总步数多以及每个仿真步都调用TraCI获取全量车辆信息开销大。尤其getAccumulatedWaitingTime是逐辆车遍历的车一多就慢。解决仿真步长从0.5秒改成1秒决策间隔从5秒改成10秒减少TraCI调用次数。状态获取接口只采样关键车道不要遍历所有车辆。开启SUMO的--no-step-log和--threads参数能进一步提速。训练速度的目标是单个episode控制在30~60秒内如果超过这个范围优先缩减仿真时长而不是缩减episode数量。5.7 训练曲线的波动像心跳一样无法判断是否收敛现象平均等待时间的曲线不是平滑下降而是大幅上下跳动和没训练一样。原因有可能是学习率过高Q值更新跨度过大也有可能是一次性把epsilon衰减到接近0模型过早进入利用阶段而Q网络还没学准。解决先把学习率从0.001降到0.0005试一轮观察曲线是否变得更平滑。另一个有效做法是每个episode结束后打印“最近10个episode的平均等待时间”做移动平均用移动平均判断趋势而不是看单点值。如果移动平均还是在波动检查batch_size是不是太小64以下梯度噪声偏大。6. 让DQN方案真正好用三个进阶技巧与验证方法6.1 从固定相位序列到动态相位选择前面提到的方案都是固定相位序列即南北直行→南北左转→东西直行→东西左转这个顺序不变模型只能调整每个相位的时长。真实路口往往有更复杂的相位需求比如某个方向的左转车流量在特定时段趋近于零让它按固定顺序放行就是浪费绿信比。把动作空间改成“直接选择下一个放行相位”其实不复杂把每个相位的排队长度之和、平均等待时间拼成状态向量动作维度从2扩展到4四个方向模型需要学习的只是“此刻哪个方向最该放行”。关键实现细节是切换前必须强制走完过渡相位黄灯全红否则在SUMO里做相位跳转会出现灯色冲突。代码实现时用一个过渡阶段计数器在进入下一个决策点前先走过渡相位过渡结束才能执行动作。6.2 用多路口场景检验模型是否真正学到了策略单路口DQN跑通后最值得做的验证是把它搬到双路口或四路口场景。多路口的信息流是联动的——上一个路口的放行决策直接影响下一个路口的到达车流模型必须学会的是“协调”而不是“各自为政”。多路口的难点在于状态空间变大如果两个路口各有四个相位状态向量要拼两个路口的排队和等待数据。一个实用方案是让两个路口共享同一个DQN网络但各路口独立做决策。也就是说训练时两个路口各自采样状态、执行动作、收集奖励数据全部进同一个回放池。这种方式叫做参数共享能显著加快收敛而且模型学到的策略具备一定的跨路口迁移能力。验证迁移能力的方法很直接在A路口训练好模型直接放到结构相似但车流特征不同的B路口去评估如果平均等待时间没有明显恶化说明模型学到的是通用的控制逻辑而不是背下了特定路口的流量规律。6.3 收敛性验证和固定配时方案做规矩的对比判断DQN方案是否真有价值不能只看模型自己训练得怎么样要和SUMO默认的固定配时方案做对比。固定配时方案可以用netedit自动生成的默认方案也可以手动设计一个多时段方案。我习惯的对比流程是同一份路网、同一批车流用固定随机种子分别跑固定配时和DQN控制各跑10次取平均等待时间、平均排队长度和平均行程时间。记录成表格形态指标固定配时默认31秒DQN训练200 episode提升比例平均等待时间约85秒约61秒约28%平均最大排队长度32辆21辆约34%总通行车辆数1280辆1352辆约6%如果DQN的指标没有明显优于固定配时先别急着否定方案。检查车流是不是太平稳了——固定配时在稳定车流下表现并不差DQN的优势恰恰体现在流量突变场景。把车流改成“前300秒低流量、中间600秒高流量、最后300秒回落”的突发模式DQN的领先幅度会明显拉大。这是信号灯DQN方案最适合发挥的试验场也是和固定配时拉开差距的最大机会。最后一个教训训练好的DQN模型不要直接拿去做真实部署式的“最终结论”。SUMO仿真环境和现实之间隔着检测器精度、通信延迟、行人干扰这些因素仿真里提升30%不代表现场能提升30%。但这个项目做到这一步已经足以支撑一篇实验论文、一个毕业设计或者一次技术方案的可行性验证。把SUMO路网换成你所在城市的真实路口数据把奖励函数按照交警的业务指标比如早晚高峰平均延误重新标定这条技术路线就能从课程项目真正走到工程预研。希望帮到你。本文还有配套的精品资源点击获取