ARTICLE DETAIL

资讯详情

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

SUMO+DQN实现交通信号灯自适应配时:从环境搭建到训练避坑

SUMO+DQN实现交通信号灯自适应配时:从环境搭建到训练避坑 简介基于Python的Sumo交通仿真与DQN强化学习源码围绕交通信号灯相位时间动态调整问题设计适合计算机、人工智能、自动化等专业学生完成课程设计、期末大作业或毕业设计。项目以Sumo为仿真环境利用DQN算法优化信号配时代码经调试可正常运行答辩评分98分具备较高参考价值。压缩包共32个文件包含16个XML配置文件仿真路网、流量、检测器、6个OSM地图文件、5个Python脚本DQN主程序、强化学习逻辑、辅助工具以及xlsx数据表和说明文档包体约536KB已有80人浏览学习。项目整体结构清晰从地图导入、路网生成到模型训练均有对应脚本便于理解Sumo与强化学习结合的全流程可在现有框架上修改奖励函数或网络结构扩展自适应控制实验附有README说明与原始数据适合作为期末大作业或毕设起点。1. 从固定配时到自适应配时这个SUMODQN信号灯项目到底在解决什么每天早晚高峰固定配时信号灯把南进口排成长队、北进口放成空路的画面几乎是城市交通里最典型的资源错配。基于Python开发、用SUMO作为仿真平台、采用强化学习DQN调整交通信号灯相位时间的源码项目就是为这类问题而做的信号灯不再背着一张死时间表而是根据实时排队信息自己决定“当前相位放多久、下一相位切给谁”。这个方向的落地链路很完整从交通仿真到强化学习训练再到策略评估每一步都有明确产出。适合三类人刚入门强化学习、不想只跑CartPole这类玩具环境的新手需要在课程设计或毕设里完整交付“环境算法训练分析”流程的学生以及想量化验证“自适应配时到底比固定配时值不值”的交通算法工程师。先说结论这个项目真正有价值的地方不在DQN本身而在环境封装和奖励函数设计。模型结构三五十行就能写完但如果你没把SUMO的相位切换逻辑、状态观测和奖励对齐训练出来的策略很可能连最普通的固定配时都打不过。后面几章就按“环境→算法→训练→排错→评估”的顺序把这套链路拆开讲。2. 搭建SUMO仿真环境最小交叉口从路网建模到TraCI打通2.1 为什么是SUMO交通仿真平台选型的一个理由做交通信号灯强化学习选型时基本是VISSIM、SUMO、MATLAB/SimEvent三选一。VISSIM微观仿真能力很强但商业授权和封闭API让它在批量训练和外部控制上都很不友好学生项目和算法验证基本不会选它MATLAB的SimEvents更适合排队论层面的抽象车辆轨迹、跟驰模型、车道拓扑的精度都不够做细粒度信号控制研究。SUMO是开源的微观交通仿真器每一辆车都有独立的加速度、跟驰模型和行驶轨迹路网用XML描述最关键是自带TraCITraffic Control Interface协议外部Python程序可以通过TCP端口实时读取交通状态、下发信号灯相位指令。也就是说强化学习智能体需要的“环境”和“交互通道”SUMO原生就提供了不需要自己写车辆动力学或排队模型。这也是标题里选SUMO而不是其他平台的根本原因它能让你把精力集中在DQN算法和环境设计上而不是从零造一个交通仿真器。SUMO从1.5到1.20的核心API都保持着比较稳定的兼容性按新版安装即可不用纠结旧版本。2.2 安装和工具链SUMO本体、Python调用接口最常用的是apt安装Ubuntu/Debian系一条命令搞定sudo apt install sumo sumo-tools装完先验证两个东西是否可用sumo --version python3 -c import traci; print(traci.__file__)这里有一个新手必踩的坑SUMO的Python接口不是pip安装的而是随SUMO本体装好后由tools目录暴露给Python。很多人在import traci时报ModuleNotFoundError其实是环境变量没配好。常见做法是把SUMO的tools路径加进PYTHONPATHexport SUMO_HOME/usr/share/sumo export PYTHONPATH$SUMO_HOME/tools:$PYTHONPATHWindows上装了官方安装包的话路径通常在C:\Program Files (x86)\Eclipse SUMO\下面同样把tools目录加进PYTHONPATH。Python版本建议用3.9到3.11之间3.12以上偶尔会遇到TraCI依赖的兼容问题退回3.10最省事。提示如果import traci失败先不要急着折腾pip优先检查SUMO_HOME和PYTHONPATH这两个环境变量九成问题出在这。2.3 用netgenerate生成最小交叉口路网别手写XML新手最容易犯的错是一上来手写.net.xml。手写节点和边确实可行但SUMO路网XML里还有车道连接、交叉口内部路径、信号灯相位定义漏一个字段路网就加载失败或者车辆不走。最稳的办法是用netgenerate命令生成。要做一个十字交叉口先跑这句netgenerate --cross --junctiontraffic_light -L2 --output-fileintersection.net.xml参数说明--cross让netgenerate生成一个十字路口--junctiontraffic_light把交叉口强制标记为信号灯控制-L2表示每个方向两条车道一组直行加一组左转--output-file指定输出路网文件。生成后建议用netedit打开看一眼拓扑和边ID因为后面写车流文件时要用到真实边名。交通需求文件是另一个必须配齐的部分我一般单独写一个rou.xml!-- intersection.rou.xml -- routes vType idcar accel2.6 decel4.5 sigma0.6 length5.0 maxSpeed13.89 color0.9,0.2,0.2/ route idWE edgesleft_to_center center_to_right/ route idEW edgesright_to_center center_to_left/ route idNS edgesbottom_to_center center_to_top/ route idSN edgestop_to_center center_to_bottom/ flow idf_WE routeWE typecar begin0 end1800 period3.2/ flow idf_NS routeNS typecar begin0 end1800 period5.0/ /routes注意netgenerate生成的路网里边的ID一般会形如left_to_center、center_to_right这样但不同SUMO版本的命名规则可能有差异。最保险的做法是先用netedit打开intersection.net.xml在边属性面板里确认实际ID再回填到rou.xml。这一步看起来繁琐实际上比手写XML快得多。把路网和交通流组织进一个sumocfg配置configuration input net-file valueintersection.net.xml/ route-files valueintersection.rou.xml/ /input time begin value0/ end value3600/ step-length value0.1/ /time /configuration这三个文件齐了SUMO仿真就能跑起来。之后所有TraCI控制代码只需要把intersection.sumocfg的路径传给Python启动命令。2.4 通过TraCI把SUMO接进Python最小可运行探测脚本TraCI的交互模型是“Python作为客户端连上SUMO进程每调一次traci.simulationStep()推进一个仿真步然后在这个间隙里读写仿真状态”。先跑一个最小连接脚本验证环境是通的import traci SUMO_CMD [sumo, -c, intersection.sumocfg, --no-warnings] traci.start(SUMO_CMD) # 启动SUMO进程并建立TraCI连接 for step in range(60): # 步长0.1s所以这一步一共跑6秒 traci.simulationStep() if step % 10 0: total_wait sum( traci.edge.getWaitingTime(e) for e in [left_to_center, right_to_center, top_to_center, bottom_to_center] ) print(fsim_step{step}, total_waiting_time{total_wait:.2f}) traci.close()这里有个关键点traci.start()的参数不是配置文件路径而是一条完整命令——[SUMO二进制, -c, 配置路径, 其他参数]。很多人第一反应是traci.start(intersection.sumocfg)结果SUMO报错找不到文件。另外启动时那句--no-warnings一定要加否则SUMO每步都可能往stderr刷警告训练日志会被淹没。这个脚本能跑通说明SUMO、TraCI、路网和车流文件全部就绪可以开始封装强化学习环境了。3. 拆解DQN三件套状态、动作、奖励怎么映射到交通信号灯3.1 为什么是DQN从Q表到深度Q网络的关键跃迁把信号灯控制写成强化学习问题很自然智能体是信号灯环境是交叉口和车流动作是选择相位奖励是通行效率。如果状态是离散且很小的经典Q-Learning查表就能解决但交通场景的状态是连续车流信息——左转排队长度、直行车道占有率、当前相位运行时间组合起来状态数爆炸Q表根本存不下。DQN的思路是用神经网络近似Q函数Q(s,a)输入状态s输出所有动作的Q值。它有两个论文里反复强调的组件经验回放和目标网络。经验回放把历史转移样本存进缓冲区训练时随机采样打断样本之间的时间相关性同时提高数据利用效率目标网络单独维护一份参数让Q_target的计算不随当前网络每一步波动而是每固定步同步一次缓解自举造成的发散。核心的贝尔曼目标就是Q(s,a) r γ · max(a) Q_target(s,a)这个公式会在后面所有代码里反复出现。理解它之后再看代码就能明白每一行在算什么。3.2 状态空间把路口能看见的车流信息变成向量状态怎么设计决定信号灯“能看到什么”。单路口控制里性价比最高的观测项是排队长度其次是平均速度和车道占有率。下表是一组我在源码里常看到的观测映射状态分量TraCI读取接口归一化方式东进口排队长度traci.edge.getWaitingTime(left_to_center)/60秒南进口排队长度traci.edge.getWaitingTime(bottom_to_center)/60秒西进口排队长度traci.edge.getWaitingTime(right_to_center)/60秒北进口排队长度traci.edge.getWaitingTime(top_to_center)/60秒当前相位编号traci.trafficlight.getPhase(0)/相位总数相位已运行时间traci.trafficlight.getPhaseDuration(0)/最大绿灯时长归一化是DQN训练里非常容易被忽略的一环。神经网络对输入尺度敏感把等待时间这种0到几百的值直接喂进去会让网络前几层的梯度被大数主导。除以一个经验阈值压到0~1区间训练稳定性会明显改善。我习惯把阈值取成略大于“单方向最大可容忍等待”这样正常拥堵区间基本落在0~1内。第一版建议只用4条进道口的等待时间加当前相位编号组成6维状态就够了。平均速度和占有率可以等基础版本跑通后再加状态维度越高探索难度越大收敛越慢。3.3 动作空间相位选择与相位时长两种建模的区别动作是信号灯能做的最原始决策。第一类做法把动作定义为“当前相位持续时间档位”比如持续5秒、10秒、15秒三档第二类做法把动作定义为“选择下一个要运行的相位”。后者更好用因为交通信号灯的控制逻辑本来就以相位为单位且动作空间小。四相位标准路口动作空间就是4。有一个常见设计是把动作退化成“保持当前相位或切换下一个相位”的二值动作智能体每5秒选一次。这个设计动作空间最小但收敛慢——切换动作太稀疏模型要试很多次才能学到“什么时候该换”而且容易陷入频繁切换或一直不动的振荡。我更推荐直接选相位加上最小绿灯约束。实现上相位一旦切换至少维持decision_interval秒决策时间粒度也等于这个最小维持时长。动作空间从2扩到4但每个动作都保证有效绿灯时间学习效率反而高。3.4 奖励函数差的奖励让DQN学到“看起来正确”的坏策略奖励是整个工程里最玄学也最决定成败的地方。如果只给reward -全局等待时间模型确实会尝试降低等待但它一个更简单的解法是——把某几个方向长期放行另几个方向堵到天荒地老。因为惩罚按总等待算死锁一个方向对总值影响有限模型不会去消除堵车只会“把车赶到一个不碍眼的角落”。实际验证下来最好用的是差分等待奖励r(t) -(W(t) - W(t-1))其中W(t)是当前时刻所有进道口等待时间之和。这样奖励只反映“本轮相比上一轮改善了多少”模型必须让所有方向排队持续下降才能拿到正奖单纯切换相位的操作没有收益。再叠加一个通过车辆数的正激励效果更好r(t) α × passed_vehicles - β × (W(t) - W(t-1))α取0.5~1β取1~2。γ折扣因子也在这里定信号灯控制是长期决策问题当前放行一个方向会同时影响下一个相位周期的排队状态γ0.95是个好起点再小模型会变短视再大训练方差上升。4. 源码架构与训练流程从环境封装到DQN智能体的4个关键文件4.1 文件结构与职责边界先看整个项目怎么组织。一个能跑通的版本文件不必多但职责要分清楚文件/目录职责sumo_files/intersection.net.xmlSUMO路网文件netgenerate生成sumo_files/intersection.rou.xml交通流需求文件sumo_files/intersection.sumocfgSUMO启动配置env/sumo_env.py把SUMO封装成Gym风格环境负责reset/step/rewardagent/networks.pyQ网络与目标网络定义agent/dqn.pyDQNAgent类含ReplayBuffer和执行梯度更新train.py主训练入口跑episode循环分层原则很简单环境代码不写神经网络的逻辑智能体代码也不直接调TraCI。环境只负责回答“我现在是什么状态、执行这个动作后世界变成什么样、给多少奖励”智能体只负责回答“面对这个状态该选哪个动作、这条经验怎么更新参数”。边界画清楚后面换奖励函数、换Double DQN都不需要动另一侧代码。4.2 环境封装把SUMO包装成Gym风格的step/reset接口沿用Gymnasium的接口约定reset()拉启一个全新的SUMO仿真进程step()执行一次动作并推进n个仿真步长。代码里我把观测简化为6维保持模型规模可控# env/sumo_env.py import traci import numpy as np import gymnasium as gym PHASE_NUM 4 INLET_EDGES [left_to_center, bottom_to_center, right_to_center, top_to_center] class SumoTLEnv(gym.Env): def __init__(self, sumo_cfg, decision_interval5.0, max_decisions360): super().__init__() self.sumo_cfg sumo_cfg self.decision_interval decision_interval # 同时是最小绿灯时长 self.max_decisions max_decisions # 一个episode最多决策次数 self.action_space gym.spaces.Discrete(PHASE_NUM) self.observation_space gym.spaces.Box(low0, high1, shape(6,)) self.decisions 0 self.prev_wait 0.0 self.total_wait 0.0 def reset(self): traci.start([sumo, -c, self.sumo_cfg, --no-warnings, --quit-on-end]) self.decisions 0 self.prev_wait 0.0 self.total_wait 0.0 traci.trafficlight.setPhase(0, 0) return self._get_obs() def step(self, action): traci.trafficlight.setPhase(0, int(action)) # 直接切换相位 for _ in range(int(self.decision_interval / 0.1)): traci.simulationStep() # 维持该相位一段时间 obs self._get_obs() reward self._compute_reward() self.decisions 1 done (self.decisions self.max_decisions) return obs, reward, done, {} def _get_obs(self): wait [] for edge in INLET_EDGES: wait.append(traci.edge.getWaitingTime(edge) / 60.0) current_phase traci.trafficlight.getPhase(0) / float(PHASE_NUM) return np.array(wait [current_phase], dtypenp.float32) def _compute_reward(self): now_wait sum(traci.edge.getWaitingTime(e) for e in INLET_EDGES) diff_wait now_wait - self.prev_wait self.prev_wait now_wait self.total_wait now_wait # 等待时间增加越多惩罚越重排队减少则给正奖 return -diff_wait / 100.0逻辑说明_get_obs返回6维状态4条进道口等待时间加当前相位编号等待时间除以60做归一化。step里先调用TraCI的setPhase切换相位再在decision_interval内循环推进仿真步这个循环同时实现了最小绿灯约束。_compute_reward按差分等待计算除以100控制数值量级。max_decisions360对应真实世界30分钟360×5秒足够一个episode学到路口控制的基本节奏。4.3 DQN智能体网络结构、经验回放、目标网络网络用PyTorch写一个三层MLP。状态6维、动作4维网络结构就是6→128→128→4。这个规模对单路口完全够用不要一上来就堆几百宽度的巨型网络交通信号灯问题没有这么高的状态复杂度模型大了反而过拟合仿真路网# agent/networks.py import torch import torch.nn as nn class QNetwork(nn.Module): def __init__(self, state_dim6, action_dim4, hidden128): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, action_dim), ) def forward(self, x): return self.net(x)经验回放和更新逻辑# agent/dqn.py from collections import deque import random import numpy as np import torch import torch.nn.functional as F from agent.networks import QNetwork class ReplayBuffer: def __init__(self, capacity50000): self.buffer deque(maxlencapacity) def push(self, s, a, r, s2, done): self.buffer.append((s, a, r, s2, done)) def sample(self, batch_size): batch random.sample(self.buffer, batch_size) s, a, r, s2, done map(np.array, zip(*batch)) return s, a, r, s2, done class DQNAgent: def __init__(self, state_dim6, action_dim4, lr1e-4, gamma0.95, target_update_steps500): self.q QNetwork(state_dim, action_dim) self.q_target QNetwork(state_dim, action_dim) self.q_target.load_state_dict(self.q.state_dict()) self.optimizer torch.optim.Adam(self.q.parameters(), lrlr) self.gamma gamma self.buffer ReplayBuffer() self.learn_steps 0 self.target_update_steps target_update_steps def act(self, obs, epsilon): if random.random() epsilon: return random.randint(0, 3) obs_t torch.FloatTensor(obs).unsqueeze(0) with torch.no_grad(): q_vals self.q(obs_t) return int(q_vals.argmax(dim1).item()) def update(self, batch_size128): if len(self.buffer.buffer) batch_size: return None s, a, r, s2, done self.buffer.sample(batch_size) s torch.FloatTensor(s) a torch.LongTensor(a).unsqueeze(1) r torch.FloatTensor(r).unsqueeze(1) s2 torch.FloatTensor(s2) done torch.FloatTensor(done).unsqueeze(1) q_sa self.q(s).gather(1, a) with torch.no_grad(): q_next self.q_target(s2).max(dim1, keepdimTrue).values target r self.gamma * q_next * (1.0 - done) loss F.mse_loss(q_sa, target) self.optimizer.zero_grad() loss.backward() self.optimizer.step() self.learn_steps 1 if self.learn_steps % self.target_update_steps 0: self.q_target.load_state_dict(self.q.state_dict()) return float(loss.item())逻辑说明act在epsilon概率下随机探索否则走前向网络拿argmax动作。update里最关键的一行是target r gamma * q_next * (1.0 - done)——done为1的终止状态会把未来收益清零防止Q值高估。如果漏掉(1.0 - done)训练末期Q值会严重虚高表现就是loss不降、策略振荡。注意done数组在目标值计算里必须参与否则Q值高估是必然的只是时间早晚问题。target_update_steps如果设太小比如50目标网络跟着当前网络频繁波动训练容易发散设太大比如5000前期目标基本不动学习慢。500是一个经过多个项目验证的折中值。4.4 主训练循环一个episode的完整生命周期训练循环把环境和智能体接起来。每轮episode重置环境、按ε-greedy选动作、把转移样本推入缓冲、做一次网络更新# train.py from env.sumo_env import SumoTLEnv from agent.dqn import DQNAgent EPISODES 200 BATCH_SIZE 128 EPS_START, EPS_END, EPS_DECAY 1.0, 0.05, 0.995 def train(): env SumoTLEnv(sumo_files/intersection.sumocfg) agent DQNAgent(state_dim6, action_dim4, lr1e-4, gamma0.95, target_update_steps500) epsilon EPS_START for episode in range(EPISODES): obs env.reset() total_reward, decisions 0.0, 0 done False while not done: action agent.act(obs, epsilon) obs_next, reward, done, _ env.step(action) agent.buffer.push(obs, action, reward, obs_next, done) agent.update(BATCH_SIZE) obs obs_next total_reward reward decisions 1 epsilon max(EPS_END, epsilon * EPS_DECAY) avg_wait env.total_wait / max(decisions, 1) print(fep {episode:3d} | steps {decisions:3d} | freward {total_reward:8.2f} | avg_wait {avg_wait:7.2f} | feps {epsilon:.3f}) if __name__ __main__: train()逻辑说明agent.update(BATCH_SIZE)在缓冲区不足时直接跳过这是常见做法避免训练早期强行动梯度。total_reward整体为负是正常的因为差分等待的平均值天然为负观察它的变化趋势比看绝对值更有意义。运行200个episode后reward在后期应比前期更接近0avg_wait逐步下降说明策略在变好。4.5 训练监控不要只看打印要看曲线只打印一行数字很难判断训练质量。建议把每个episode的total_reward、avg_wait和loss追加到csv有条件就接TensorBoard的SummaryWriter。判断收敛看三个波形特征reward曲线整体上升后在某个区间震荡avg_wait从几百秒降到几十秒后趋平loss在前1万个学习步内先降之后进入窄幅波动平台。如果loss降了但avg_wait没降优先怀疑奖励函数设计其次怀疑环境代码里setPhase和decision_interval是否真的生效——这两个问题在下一章用具体的翻车案例展开。5. SUMODQN训练避坑与排查五个容易让项目返工的高频问题训练排错是最耗时间的部分。下面五条都是我在实际跑SUMODQN时翻过车、又逐个定位到原因的问题每一条按现象、原因、解决三个步骤写可以直接对照你自己的日志来判断。5.1 现象loss先降后涨训练越跑越崩前几万个学习步loss下降很漂亮之后突然大涨甚至出现NaN。原因基本指向目标网络更新太快或学习率过高。目标网络要给出稳定的target如果每50步就同步一次target跟着当前网络一起漂模型等于在追一个会跑的靶子。学习率1e-4到3e-4对DQN是安全区间超过1e-3基本会炸。解决把target_update_steps调到500~1000学习率降到1e-4batch_size提到128后重训。如果还想更稳把硬同步改成软更新每次学习都让目标网络参数朝当前网络挪一点q_target tau * q (1 - tau) * q_targettau取0.005~0.01。改动不大却能把曲线压得平很多。5.2 现象所有车辆越堵越死信号灯却没有救训练几百个episode后路网上出现死锁某个方向的绿灯一直放行其他方向排队到溢出甚至车流完全静止。第一嫌疑是环境没加最小绿灯约束。如果智能体每0.1秒仿真步就能切换一次相位它会学到“频繁切换能立刻改变状态、引流某个方向的压力”这种短期策略但车辆刚起步就被切换相位通过率极低全局等待时间快速上升。解决在环境里强制最小绿灯时长。上文SumoTLEnv的decision_interval5.0就是这个约束——相位一旦选定至少保持5秒仿真步才往下走。常见取值在5到10秒之间太短2秒起不到通过车队的作用太长20秒会让动作分辨率太粗模型来不及响应突发车流。按城市路口饱和流率粗算5秒大约能让2~3辆车通过是一个经验上有效的下限。5.3 现象一个episode跑几分钟训练慢到没法迭代用sumo-gui跑训练一个3600秒仿真、步长0.1秒意味着36000步仿真加上画面渲染一个episode能磨四五分钟200个episode就是十几个小时起步这还没算神经网络梯度计算。排查顺序先看SUMO是不是开着图形界面再看是不是每个0.1秒仿真步都在做决策。解决训练统一用不带gui的sumo命令只在回放验证时用sumo-gui。把决策粒度从每个仿真步改成每5秒一次本质上一个决策步少跑49个仿真步时间开销直接降到约1/50。再把--no-warnings和--quit-on-end参数加上避免终端IO等待和警告刷屏。如果还慢把max_decisions缩短到360真实30分钟这通常足够学到路口控制的基本节奏。5.4 现象训练收敛了评估时却打不过固定配时有一种坑是测试时才发现DQN策略的avg_wait比固定配时高出20%以上。查奖励设计大概率是只用了全局总等待作为直接惩罚没考虑方向均衡。模型学到的是“把车堵在一个方向其他方向放空”全局总等待依然很小。在差分等待基础之上把四路等待的方差加进惩罚项训练难度会略高但能避免单方向饿死。另一个常见原因是决策间隔太大比如20秒一次模型无法响应短时车流波动。解决评估先和固定配时跑同一个随机种子控制变量。奖励函数至少改成r -diff_wait / 100输出里同时跟踪每个方向的排队长度不只看总平均。如果模型已经训到后期改奖励需要整个重训这也是为什么前期试奖励函数时要把episode数设小、快速跑几十轮看方向。5.5 现象在训练路口有效换个路口就“废”模型过拟合是强化学习项目里最隐蔽的问题。单一路口、单一流量模式下训练出的策略把车道数、路口尺寸、流量比稍改一版评估结果就掉下来。原因是状态特征用的是绝对值——排队秒数、车辆数没有相对路口容量做归一化模型学到的是“等待秒数超过X就切换”而不是“排队占比超过容量阈值就切换”。解决状态里所有量都除以相应车道的最大容量或限速。排队长度除以车道可容纳车辆数速度除以限速把绝对值转成0~1的占用率。训练时不要只用一种流量模式至少混合“东进口高流量”“南北高流量”“双向均衡”三种需求每个episode随机选一种。如果目标是多路口迁移就得换架构——用图注意力网络把路口拓扑编码进状态像CoLight这类以图网络为骨干的多路口控制方法在泛化上明显比单点MLP强但工程量是另一个量级了。6. 验证与进阶评估指标、基准对比和值得投入的后续方向训练完别急着报数字先定一套可重复的评估流程固定随机种子、固定交通需求文件跑10次取均值。先把固定配时跑出来作为baseline再把DQN策略加载回去跑同样的需求。核心指标看四类指标计算方式合格参考平均等待时间所有车辆等待时间均值比固定配时低15%以上平均排队长度四个进道口排队长度均值下降20%~40%吞吐量统计时间内通过路口车辆总数不低于baseline95分位等待时间等待时间排在第95百分位的值不应出现单方向饿死对照表格跑下来DQN至少要赢得平均等待这一项才值得继续投入。如果只赢一条路、其他指标全输多半是奖励函数偏科了。进阶方向按性价比排序我建议先做三件事把DQN换成Double DQN目标Q值计算从max改成“当前网络选动作、目标网络评价值”能缓解过估计通常等待时间还能再降几个百分点第二是优先经验回放把误差大的样本更频繁地采出来训练后期提速明显第三才是多交叉口联合控制——信号灯联动要考虑上下游协调单点DQN天然做不到这时图注意力网络和离线强化学习比如IQL离线学习历史配时数据是更值得投入的方向但工程复杂度会从单环境封装上升到多路口环境编排。我自己的习惯是先跑固定配时拿基线再跑DQN没有10%以上的收益就不换模型。第一次做这个项目时我把决策间隔设成2秒、还在奖励里漏了方向均衡项结果策略越训越差查了三天日志才定位到环境层。条件允许的话把每一步的相位、等待时间、奖励都记录下来回放时用sumo-gui开着看一遍车流基本能看出模型是在学控制还是钻奖励空子。希望帮到你。本文还有配套的精品资源点击获取
返回列表