尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
基于SUMO与DQN的信号灯相位时长优化实战指南
简介这套基于SUMO与DQN的交通信号灯相位时间调整项目面向交通仿真、强化学习算法研究与智能交通开发人员提供从路网构建、环境定义到DQN训练评估的完整实现。压缩包共32个文件含16个XML路网与交通流配置、6个OSM地图源数据、5个Python算法脚本覆盖标准DQN及优先级回放DQN、XLSX指标表格、SUMO仿真配置与说明文档整体仅532KB便于快速解析与复用。通过这些文件可掌握SUMO路网建模、检测器配置、状态特征选择、动作空间设计、经验回放机制等关键环节并对照多组不同车流量的仿真场景和输出数据评估平均旅行时间等指标。项目已有497人学习下载适合具备Python基础、希望用强化学习解决交通信号控制问题的进阶读者。1. 用SUMO训练DQN调信号灯为什么先跑通闭环比调网络结构更重要一个十字路口早晚高峰两个方向车流明显不对等固定配时经常出现一边绿灯空放、一边红灯压了十几辆车。你当然可以靠手工配时但路况一变又要重来。用SUMO做仿真平台训练一个DQN智能体去决定每个相位该延长还是缩短就是这类问题最主流的自动解法。这个具体方向通常被打包成一个.zip工程里面包含路网、仿真配置和训练脚本。不过很多人拿到手第一件事就去找神经网络结构结果连SUMO的仿真步进都没跑通就翻车了更不用说DQN。这篇笔记从一个可复现的角度梳理怎么把相位时间调整建模成强化学习问题如何搭起SUMO与DQN的联调环境参数怎么调、坑在哪以及单路口验证之后怎么迁移到多路口。2. 信号灯相位时间调整的DQN建模状态、动作、奖励怎么定在最开始动手写代码之前先把强化学习三件套定义清楚。状态、动作、奖励这三者决定了模型能感知什么、能做什么、以及怎么算好。很多入门项目失败不是DQN算法难而是这三件套没配对后面的训练全是在错误的路子上打转。所以这一章先把建模讲清楚再用代码落下来。2.1 状态空间用SUMO的车辆数据拼出能感知拥堵的向量DQN的输入状态要能反映路口拥堵状况。在SUMO环境里最直接的来源是TraCI接口它每步仿真都会更新车辆的实时数据。常见的状态维度包括每个进口方向的排队长度、当前相位的剩余时间、当前相位ID以及最近一段时间内的车辆平均等待时间。注意不是所有数据都有用比如瞬时速度波动太大会让状态很不稳定我一般会换成排队长度和累积等待时间这两个更平滑的指标。排队长度怎么算可以设置一个速度阈值来判定车辆是否在排队。比如速度低于0.1m/s就认为这辆车在排队等待。下面这个函数用来拼接状态向量import traci import numpy as np def get_state(approach_edges, phase_id, phase_remaining, max_phase4, max_queue50): # approach_edges 是各进口方向的主车道ID列表 state [] for edge in approach_edges: # 统计该车道上速度低于阈值的车辆数 veh_ids traci.edge.getLastStepVehicleIDs(edge) queue sum(1 for v in veh_ids if traci.vehicle.getSpeed(v) 0.1) state.append(queue / max_queue) # 排队长度归一化到[0,1] # 平均等待时间先取该车道当前所有车累积等待时间的均值 waiting traci.edge.getWaitingTime(edge) state.append(min(waiting / 60.0, 1.0)) # 超过60秒就截断 # 归一化后的相位和时间 state.append(phase_id / max_phase) state.append(min(phase_remaining / 60.0, 1.0)) return np.array(state, dtypenp.float32)代码逻辑不复杂先遍历每个进口方向把排队数量和等待时间压到0到1之间最后把当前相位编号和剩余相位时间也拼进去。这样做的好处是所有特征量级一致神经网络不需要额外去适配不同量纲。参数说明max_queue和等待时间上限都是归一化阈值如果实际路口车流很大需要把阈值调大否则大量排队长会一直被截断成1状态区分度就没了。这里有个常见错误把所有车道都加入状态导致状态维度蹭蹭涨到几十甚至上百。单路口场景下网络输入维度太大反而会拖慢收敛而且很多车道是冗余的。我通常会挑每个方向的一条主干道和一条直行车道够用就行。多路口场景另说第6章会谈到。还有一种进阶做法是把连续几个时间步的状态叠起来比如把过去5秒的排队长度一起输入让网络感知到趋势但这样做状态维度会翻五倍单路口时可以试试不一定都需要。2.2 动作空间离散化相位时长比连续控制更适合DQNDQN本身是处理离散动作的算法它输出的是每个动作的Q值。在信号灯控制里相位时长天然就是秒级别的离散量所以离散化不仅省事还很自然。常见的做法是定义三个动作缩短3秒、保持当前相位时长不变、延长3秒。如果希望调整粒度更细也可以用五档减少6、减少3、不变、增加3、增加6。动作空间不需要太大五档已经能覆盖大部分场景。动作的执行逻辑看起来是这样# 动作索引到相位时长的增量 ACTION_DELTA [-6, -3, 0, 3, 6] def apply_action(tl_id, phase_remaining, action_idx): # 先计算新的剩余相位时长并限制在[5, 40]秒之间 delta ACTION_DELTA[action_idx] new_duration min(max(phase_remaining delta, 5), 40) traci.trafficlight.setPhaseDuration(tl_id, new_duration) return new_duration这里要注意setPhaseDuration设置的是当前相位的剩余时长不是下一相位的时长。如果你在相位已经运行了几秒后才调用它相当于把当前这个阶段的时间拉长或缩短。参数说明min_green5是为了保证最短绿灯时间不至于刚变绿灯又被掐掉max_green40是防止某个方向被无限延长。这两个值要根据路口实际信号周期调整小路口用[5,30]更合适。还有一种动作设计是直接选择相位即让DQN决定下一个放行哪个方向而不是调整当前相位的剩余时间。这种做法更接近传统的信号控制机逻辑但缺点是动作空间随相位数量线性增长而且相位切换顺序容易被打乱。在大多数开源项目里尤其以单路口起步的项目调整相位时长的方案更容易写、也更容易见到效果。我推荐先从这个方案开始等你看到效果了再尝试连续动作控制比如用DDPG去输出具体的相位时长但那已经超出DQN的范畴了。2.3 奖励函数别只选排队长度平均等待时间更贴近真实奖励函数是DQN训练里最主观、也最容易翻车的部分。如果只选排队长度作为奖励模型会想办法让当前方向的车赶紧放过去但可能让另一个方向的排队更长。更好的选择是奖励当前的平均等待时间变化量如果这一步之后所有车辆的累积等待时间比上一步少了就给正奖励否则给负奖励。它反映的是整个路口的即时通行效率。伪代码和实际实现差别不大下面是个常用的版本def compute_reward(tl_id, prev_total_wait, approach_edges): # 求所有进口车道当前累积等待时间之和 total_wait sum(traci.edge.getWaitingTime(e) for e in approach_edges) # 等待时间减少奖励为正增加奖励为负 reward (prev_total_wait - total_wait) / 100.0 return reward, total_wait除以100是把奖励尺度压到[-1,1]附近。为什么这么做因为DQN的Q值更新是对奖励的累计期望奖励量级太大网络权重更新会非常剧烈表现为loss震荡、收敛缓慢。奖励量级太小又容易被噪声淹没。在实际项目里我会先跑几组静态车流测试出总等待时间的波动范围再确定缩放系数。另一个细节是相位切换惩罚。如果DQN每步都在切换相位车流就永远处于启动-停止-再启动的状态平均等待时间反而更差。所以很多实现会给一次性setPhaseDuration加入-0.1到-0.5的惩罚让智能体尽量保持当前相位直到确实需要调整。这个惩罚值不用太大否则模型会死守一个相位不动。到这里状态、动作、奖励这三块定义清楚了后面的训练环境搭建才有意义。3. 从零搭起SUMO和DQN的联合训练环境安装、路网、最小闭环模型建好之后接下来是把SUMO和Python拉通。这个环节至少有一半的初学项目卡在环境上不是路网文件缺东少西就是TraCI连不上。我的经验是把这个任务拆成三个独立验证的小步先让SUMO自己跑起来再让Python读到数据最后才让Python去修改信号灯。每一步验证通过再往下走速度反而最快。3.1 SUMO的安装与路网生成先解决仿真跑起来这个拦路虎SUMO是一个开源交通仿真平台项目标题里的仿真角色就是SUMO。安装方式非常常规Linux下可以直接用包管理器安装Windows下用官方安装包conda用户也可以从社区频道装。这些过程不复杂真正麻烦的是路网文件。如果你还没有路网最简单的办法是用netedit图形化编辑器手动画一个十字路口保存为.net.xml。如果项目里已经带路网直接跳过这一步。对于想用脚本生成路网的人常见做法是先写好两个节点和边的XML文件再用netconvert转换。下面是一个最小可用的节点文件!-- nodes.nod.xml -- nodes node idA x-200 y0 typetraffic_light/ node idB x200 y0 typetraffic_light/ node idC x0 y-200 typetraffic_light/ node idD x0 y200 typetraffic_light/ node idcenter x0 y0 typetraffic_light/ /nodes这里创建了四个进口节点和一个中心交叉口节点中心节点会被视为信号灯控制点。对应的边文件需要把四个方向连接到中心比如从A到center、center到B以此类推。写好之后用netconvert把两个文件合并netconvert --node-filesnodes.nod.xml --edge-filesedges.edg.xml --output-filecross.net.xml这样我们就有了一个十字交叉口路网。如果你只是做测试也可以直接用SUMO的随机路网生成工具但随机路网可能包含不规则路口对信号灯控制不友好所以新手更推荐手画。生成好的路网只是一个静态拓扑还需要车辆流。车辆流可以用flow文件定义也就是每几秒生成一辆车、从哪条边进从哪条边出。为了训练稳定我一般会设置多组车流高峰期一组车流密集平峰期一组稀疏这样DQN能看到不同的状态分布。3.2 让SUMO跑起来并提供TraCI端口SUMOCFG与启动命令有了路网和车流还需要一个SUMO配置文件把两者串起来。一个最小的cross.sumocfg长下面这样configuration input net-file valuecross.net.xml/ route-files valueroutes.rou.xml/ /input time begin value0/ end value3600/ /time /configuration这个配置文件指定了路网文件和车流文件仿真从0秒跑到3600秒。下一步就是启动带有TraCI服务端口的SUMO。TraCI是SUMO提供的交通控制接口Python通过它才能读取路网状态和修改信号灯参数。# 启动SUMO图形界面并开启TraCI端口12345 sumo-gui -c cross.sumocfg --remote-port 12345 --start--remote-port参数把SUMO变成网络服务端--start表示启动后立即运行仿真而不是等人来点运行。如果你在服务器上跑训练可以不用sumo-gui换成sumo命令行版本把图形界面省掉资源占用更小。区别只是你能不能肉眼看仿真过程。Python端连接就简单了import traci traci.init(12345) # 连接到已经启动的SUMO进程 for step in range(3600): traci.simulationStep() # 让仿真前进一步一步通常对应1秒 # 到这里我们就能读取车辆数据、执行DQN动作了 traci.close()注意traci.init调用时机一定在SUMO启动之后而且端口要一致。有一种翻车情况是SUMO版本和Python的TraCI版本不匹配导致init成功后第一次simulationStep直接崩掉。后面避坑章会专门讲。这里还有个容易被忽略的点sumo-gui启动后会弹出图形窗口如果你是在远程服务器上跑没有显示环境会启动失败必须用命令行版sumo或者加一个虚拟显示参数。3.3 用TraCI修改信号灯相位时间setPhaseDuration怎么用信号灯在SUMO里是一个junction对象TraCI把它包装成trafficlight模块。先要拿到信号灯IDtl_id traci.trafficlight.getIDList()[0]拿到之后可以通过getPhase读取当前相位编号通过getPhaseDuration读取当前相位的剩余时间。修改相位时长用setPhaseDuration这个函数会在当前相位剩余时间上生效不会立刻跳到下一相位# 获取当前相位剩余时间 remaining traci.trafficlight.getPhaseDuration(tl_id) # 根据DQN的动作调整 new_remaining max(5, min(40, remaining ACTION_DELTA[action])) traci.trafficlight.setPhaseDuration(tl_id, new_remaining)这里的逻辑是每次simulationStep之后根据DQN决策去修改剩余时长然后在后续仿真步里SUMO会按照新的倒计时走。注意这个接口修改的是剩余时间不是相位的总时间所以如果相位已经运行了20秒你改成5那它还会再运行25秒才切相位。理解这一点很有用因为如果你每步都调用setPhaseDuration相当于不断把倒计时往前推绿灯永远切不过去这就是很多人的训练卡住不动的原因。一个更稳妥的写法是判断当前相位是否刚切换只有刚切换的那一步才允许DQN调整时长后续步只读取不修改。这样既保留了相位时长的灵活性又不会因为重复设置导致倒计时被反复重置。具体判断方式可以通过traci.trafficlight.getPhase(tl_id)是否发生变化来知道不过要记住上一次的相位编号。到这里SUMO环境已经能跑起来Python也能控制信号灯了。下一步就是把DQN放进这个闭环里。这一章代码较多但每一步都可以独立验证先跑通SUMO再跑通TraCI连接最后才接DQN。千万不要一次性写完整个训练脚本再调试那样问题定位会非常痛苦。4. DQN训练参数详解经验回放、目标网络、探索策略的落地调优环境通了真正的强化学习训练才算开始。DQN本质上是个带目标网络和回放缓存的Q学习代码写起来很固定但参数一旦没调好训练曲线会让你怀疑算法是不是坏的。这一章把网络结构、两个让收敛变得更稳的机制、以及探索策略逐个拆开讲最后给一个可抄的调参顺序。4.1 DQN网络结构小MLP比大网络更适合单路口状态很多从图像识别转过来的同学一上来就搭一个CNN或者很深的MLP其实在单路口场景下完全没必要。状态的输入维度也就是十来个连续值一个小规模MLP就足够拟合Q函数。我用过64-64结构也试过128-64差异不大。更深的网络在样本量大时才体现优势小样本下只会增加过拟合和调参成本。下面是用PyTorch定义一个基础DQN网络的方式import torch import torch.nn as nn class DQN(nn.Module): def __init__(self, n_state, n_action): super(DQN, self).__init__() self.net nn.Sequential( nn.Linear(n_state, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, n_action) ) def forward(self, x): return self.net(x)n_state是状态向量的维度n_action是离散动作数量。中间隐藏层64个神经元两层ReLU输出不接softmax因为DQN输出的是Q值。训练时用的优化器一般是Adam学习率从1e-3起步如果发现loss震荡明显可以降到5e-4。这段代码里的关键点是输出尺寸等于动作数不是等于1这样每个动作的Q值才能独立。有人会问要不要像分类网络一样在输出层加激活不需要。Q值本身可以是负数动作选择是argmax加sigmoid会限制输出范围反而影响表示能力。这类问题没有标准答案但你多看几个开源项目会发现输出层通常都是线性层。如果你状态里包含图像信息那是另一回事单路口特别是只有向量状态时线性输出就够了。4.2 经验回放与目标网络让DQN收敛的两根拐杖DQN相比普通Q学习最核心的改进就是经验回放和目标网络。经验回放是把每一步的(状态, 动作, 奖励, 下一个状态)放进一个固定大小的缓冲区训练时随机采样一小批。这样可以打破相邻样本之间的相关性让梯度更新更稳定。目标网络则是把计算TD目标值的网络参数固定住每隔一段时间再同步降低自举带来的震荡。这两个机制的落地代码可以很简洁from collections import deque import random replay_buffer deque(maxlen20000) # 每一步存入一条经验 replay_buffer.append((state, action, reward, next_state, done)) # 随机采样一小批 batch_size 64 batch random.sample(replay_buffer, batch_size) # 目标网络每C步同步一次 C 200 target_net DQN(n_state, n_action) target_net.load_state_dict(net.state_dict()) if step % C 0: target_net.load_state_dict(net.state_dict())目标网络同步步数C是一个关键参数。C太小目标更新太频繁相当于没有目标网络C太大目标值长期不变可能跟不上实际策略的变化。200是比较常见的经验值如果你的仿真步长是1秒等价于每200秒同步一次。注意经验回放缓冲区大小20000对应的是步数不是回合数这一点很多人会搞混。在训练时从回放缓冲区采样后计算目标Q值用target_net而当前Q值用net两个网络分开计算然后做均方误差损失反向传播。这一步是DQN的标准流程如果对TD公式不熟可以先跑通一个简单的CartPole练手再迁移到SUMO场景。CartPole虽然和信号灯完全不同但它能帮你验证DQN框架本身有没有写错省下不少时间。4.3 探索策略与参数顺序epsilon衰减到底怎么设DQN训练离不开探索和利用的平衡。常见做法是epsilon-greedy以一定概率随机选择动作其他时候选择当前Q值最大的动作。epsilon的初始值通常设为1.0代表完全随机探索然后随步数线性衰减到0.1左右。但衰减速度影响很大。我常用的设法是先计算总步数上限比如训练10000秒仿真步数就是10000。如果要让epsilon在第6000步降到0.1那么衰减速率就是(1.0-0.1) / 6000。下面这段代码展示了衰减过程eps_start 1.0 eps_end 0.1 total_steps 6000 eps_decay (eps_start - eps_end) / total_steps def choose_action(state): global eps if random.random() eps: action random.randint(0, n_action - 1) else: with torch.no_grad(): q_values net(torch.FloatTensor(state).unsqueeze(0)) action q_values.argmax().item() eps max(eps_end, eps - eps_decay) return action这个代码里每次调用choose_action都会让epsilon下降一点。参数说明eps_end设为0.1是因为完全设为0会导致后期没有探索一旦陷入次优策略就再难跳出来。保持0.1的随机率能让模型偶尔尝试新动作。如果你发现训练初期reward一直在低位震荡可以先看看epsilon是不是降得太快了导致模型过早收敛到一个固定的差策略。但反过来如果训练结束时epsilon还很高说明探索还没收敛要适当加快衰减。这类问题很像调PID三个参数互相牵制没有银弹。你可以把batch_size、gamma、C、epsilon衰减速度这四项画成四张对比曲线一次只改一个参数。这个流程就是调参的实用路径比盲搜网格要高效得多。5. 避坑/常见问题排查SUMO与DQN联调的五个高频问题这一章写给那些跑不起来或者训练不收敛的读者。以下五类问题基本覆盖了我在几个模拟项目里踩过的所有大坑。每一条按现象、原因、解决的顺序展开你可以直接对照自己的报错信息来找。5.1 TraCI连接被关闭版本和启动顺序是头号凶手现象Python端执行traci.init(12345)后第一次simulationStep就抛FatalTraCIError说connection closed。原因最常见的是SUMO进程没有真正启动或者启动后因为配置错误立刻退出了。其次是TraCI版本和Python的traci包版本不匹配SUMO更新到新版本后接口协议有变化旧版的traci包连接后会被拒绝。解决先手动启动sumo-gui确认它能打开路网并正常跑起来再确认sumo-gui进程还在运行最后检查用同一个Python环境安装的traci版本。如果你是用系统包管理器装的SUMO最好用同一个包管理器提供的Python绑定不要混装。还有一个容易被忽略的细节--remote-port端口号必须大于1024因为很多系统不允许普通用户占用低位端口。如果你是在win环境下用命令行启动sumo还要注意环境变量是否把sumo的bin目录加进了PATH否则traci包会找不到SUMO的执行文件。5.2 绿灯永远切不走setPhaseDuration被反复调用现象仿真运行时信号灯看起来很懒一个方向一直绿灯长时间不切换reward也一直不变。原因我在3.3节里提到过每一步仿真都调用setPhaseDuration会让相位剩余时间不断被重置倒计时永远到不了0于是相位切换事件永远不会触发。解决给信号灯控制加一个当前相位是否已完成的判断。只有检测到相位刚切换时才允许调用setPhaseDuration。实现方式可以用traci.trafficlight.getPhase(tl_id)记录上一次相位编号如果发生变化说明已经切到了下一相位这时才应用DQN的新时长否则跳过。另外也可以把动作频率降低比如每隔5秒才做一次动作而不是每1秒步都调整。我见过一种更粗暴的做法只在仿真前300步随机跑之后每10步才让DQN决策一次这样能明显减少这种问题。5.3 奖励不降反升归一化和奖励尺度在拖后腿现象训练一万步reward一直在-2到-1之间乱跳看不到上升趋势loss曲线却正常下降。原因奖励值量级太大。如果总等待时间一分钟有几百秒直接作为奖励会让网络梯度爆炸。还有一种情况是状态没有归一化排队长度几十、等待时间几百两者量级差了一百倍神经网络很难把这两个特征学均匀。解决把所有状态除以一个经验最大值奖励除以一个缩放因子。我习惯把奖励控制在[-1,1]之间状态控制在[0,1]之间。具体做法可以先去随机策略下跑2000步记录平均等待时间的波动范围再用这个范围的倒数作为奖励缩放系数。不要小看这个步骤很多项目调了一个星期才发现是量纲问题。拿到基线数据后你也可以用标准化而不是归一化但标准化需要维护均值和方差的运行统计在仿真场景里容易引入额外状态依赖我建议先归一化。5.4 模型学会了摆烂总是选不变或缩短动作现象训练后期DQN输出的动作基本固定为不变或者缩短但路口的平均排队时间并没有明显改善。原因奖励函数只对当前等待时间变化做反馈缩短当前相位有时确实会让当前方向的车快速通过但换个方向看另一条路可能被堵死。模型发现只要不动或者缩短至少不会承担更多惩罚于是进入了一个次优的局部最优。解决奖励函数要加入全局视角。常见做法是统计每分钟通过的车辆数把通过率也放进奖励。如果等待时间减少但通过率也下降了说明模型可能在压车需要通过率来兜底。同样可以给相位切换增加一个小惩罚避免频繁切换带来的启动延误。如果这些都试过还是摆烂建议把gamma调大从0.9调到0.95或0.99让模型更看重远期收益。还要检查动作间隔如果每个仿真步都调一次相位模型很难学到保持一段时间再调整的概念。5.5 路网一旦复杂训练速度骤降把仿真图形界面关掉现象从单个十字路口换到三四个连续交叉口训练速度从每秒几十步掉到每秒几步甚至卡死。原因sumo-gui的图形渲染本身会占用大量CPU而且TraCI每次读取车辆信息都是同步调用车一多网络传输就成了瓶颈。此外如果仿真Step频繁读取所有车道的状态也会拖慢循环。解决训练时不使用sumo-gui改用纯命令行版sumo只保留TraCI端口。同时可以关闭某些不必要的数据读取例如不读取车辆轨迹只读取队列和等待时间。如果需要可视化可以预先把每步的agent决策记录下来训练结束后再回放仿真。这个做法在强化学习项目里非常普遍等于把训练和可视化完全解耦。另外SUMO支持把单步仿真推进多个时间步比如traci.simulationStep(5)一次推进5秒如果你的信号灯策略不需要每秒都跑可以大大加速训练。这五个问题基本覆盖了从环境联调到策略收敛的常见卡点。如果你在网上搜类似项目会发现大家踩的最多的其实就是这五类。实际项目里还会有一些细节问题比如路网坐标不一致、信号灯相位ID对不上但那些问题通常看报错就能改反而是上面这几个问题外表看起来像算法不好其实是环境或参数写错了。6. 进阶从单路口到多路口DQN的验证与迁移技巧单路口的DQN跑通之后你一定会想验证这个策略到底有没有用。最直接的验证方式是把同一个路网和车流丢给固定配时逻辑跑一遍再让DQN跑一遍对比平均排队长度和平均等待时间。注意固定配时的周期要和DQN动作的周期一致否则比较不公平。评估时要把epsilon设为0关闭探索用同一个随机种子生成车辆流保证两组实验条件完全一致。这个方法不仅能验证效果还能让你提前发现DQN是否在过度拟合某个特定车流。如果换一组流量分布后效果明显下降说明策略泛化性不足。这时候可以增加训练数据的多样性例如在SUMO配置里随机设置不同车流生成率让模型见到更多车流模式。从单路口迁移到多路口时我踩过一个大坑直接把单路口模型搬过去用结果两个路口协同一点都没学到排队长度反而比固定配时更高。后来我采用了一个比较稳妥的做法每个路口独立使用一套DQN参数但状态里额外加入邻接路口最近一段时间的排队长度。这样每个agent能看到下游是否堵车天然形成一定的协调机制。训练时不需要一起更新所有参数可以按顺序逐个路口微调先固定第一个路口权重只训练第二个路口几轮后再放开所有参数联合训练几天。这个迁移思路的落地代码如下# 多路口场景每个agent有自己的网络但共享经验回放 agents {tl_id: DQN(n_state, n_action) for tl_id in traffic_light_ids} # 状态构建时把邻接路口的平均等待时间拼进来 state np.concatenate([local_state, neighbor_waiting])这里的关键是n_state要重新设置因为加入了邻居信息。经验回放共享还是不共享都可以共享能加速训练但需要保证每个agent的输入维度一致。我最开始做这个方向的时候总盯着DQN的loss曲线看总觉得loss掉下去就能好。后来发现自己完全被骗了loss不降的时候平均等待时间反而可能已经不错了。强化学习不像分类loss并不是策略质量的直接指标。从那以后我每个训练阶段都会缓存一段仿真回放用真实路网里的排队状态下判断策略是否合理。这也是我想告诉你的一个习惯看指标不要只看loss要看路口的实际通行情况。希望这个SUMO加DQN的方向能帮到你少走一点我走过的弯路。本文还有配套的精品资源点击获取
RELATED

相关推荐

外卖系统数据库设计:22张表拆解订单链路与SQL避坑指南

外卖系统数据库设计:22张表拆解订单链路与SQL避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/12 1:12:27
佳能EDSDK C#开发实战:相机自动化控制从初始化到成片全流程

佳能EDSDK C#开发实战:相机自动化控制从初始化到成片全流程

简介:佳能EDSDK的C#完整开发示例,面向需要在.NET平台联机控制佳能相机的C#开发者,覆盖设备管理、实时预览、远程拍摄、图像下载与事件处理等核心场景。资源共18个文件,以8个C#源代码文件为主,包含主窗体实现、相机操作…

📅 2026/10/12 1:12:27
e2e不是缩写,而是工程协作的语境信号灯

e2e不是缩写,而是工程协作的语境信号灯

1. “e2e”不是缩写谜题,而是工程实践中最常被误读的信号灯最近在多个技术协作群里,频繁看到开发者发问:“这个需求里写的 e2e 是指什么?是端到端测试?还是端到端加密?抑或是边缘到边缘部署?”—…

📅 2026/10/12 1:12:27
MORE NEWS

更多资讯

📰

AnyPS5:低延迟本地串流方案,让任何设备秒变PS5延伸屏

1. 项目缘起与核心定位AnyPS5 这个名字第一次出现在我视野里的时候,我正蹲在一堆旧硬件中间翻找能用的零件。当时手头有一台闲置的迷你主机,配置不算差,但总觉得少了点什么——直到我看见这个标题。AnyPS5,拆开来看就是“Any”加“…

📰

中国土壤数据集从解压到栅格化的完整处理指南

简介:这是一份面向水文模型研究、农业规划、环境评估与城乡规划等场景的土壤专题数据包,旨在为科研与实践者提供全国尺度的土壤本底资料。内容覆盖土壤类型图、质地比例、容重、渗透率、含水量、pH、有机质及氮磷钾养分等关键参数,可用于径流…

📰

CH592蓝牙MCU选型与实战:低功耗无线外设开发指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Python数据预处理实战:从数据清洗到特征工程的全流程指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Xilem 内置 Emoji 名称数据集(emoji_names)解析:CSV 格式、数据溯源与 emoji_picker 示例实战

前端桌面应用 【免费下载链接】xilem An experimental Rust native UI framework 项目地址: https://gitcode.com/gh_mirrors/xil/xilem 点击查看 免费下载 本指南围绕 Xilem 仓库内 emoji_names 数据资源目录 展开,详细说明其 emoji.csv 数据的文件结构…

📰

WinForm中用ScottPlot绘制可拖拽贝塞尔曲线:坐标换算与实时刷新

简介:针对.NET平台的开源绘图库ScottPlot,提供了一份完整的WinForms图形展示演示包。该库以极为简洁的API实现折线图、柱状图、饼图、散点图以及贝塞尔曲线等大数据集交互式可视化,适合C#桌面应用开发者快速集成图表功能,也适用于…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬