news 2026/9/7 14:11:55

SUMO仿真环境下五种自适应交通信号控制算法对比与实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SUMO仿真环境下五种自适应交通信号控制算法对比与实践

简介:本资源是一个面向交通工程研究者、智能交通系统开发者及强化学习实践者的SUMO仿真项目,聚焦多策略自适应交通信号控制算法的实现与对比验证。项目集成DQN与DDPG两种深度强化学习方法,并融合韦氏模型、最大压力算法及自组织交通灯控制等经典策略,支持在微观交通仿真环境中评估不同控制逻辑对通行效率、延误与积压的影响。压缩包共54个文件,含37个Python核心模块(涵盖仿真接口、RL智能体、网络数据处理与结果可视化)、5个Shell脚本(用于训练、评估与环境清理)、4张性能分析图及2个SUMO配置文件,整体仅1.35MB,轻量易部署。已有1321人学习下载,提供开箱即用的完整实验框架:从SUMO场景构建、算法训练到指标生成与图表绘制,所有模块解耦清晰、命名规范,便于二次开发与算法替换。 自适应交通信号控制这几年被提到得越来越多,但大部分谈这个方向的文章都停留在概念层面:要么贴一张配时公式,要么放一段DQN的网络结构图。真正动手把几种算法放到同一个仿真环境里跑一遍、逐项对比各项指标、再把训练脚本和批量实验串起来,做的人不算多。这个项目做的正是这件事:在SUMO仿真平台上,把韦伯斯特(Webster)定时配时、最大压力控制(Max Pressure)、自组织交通灯(SOTL)、DQN和DDPG五种信号控制算法全部实现,统一在同一个路网上做对比,整套代码用Python调TraCI接口、Shell脚本做批量训练和评测评测。如果你正在研究信号控制,或者想找一个同时覆盖传统方法、强化学习方法和无模型自适应方法的技术参考项目,这篇文章应该能帮你省下很多自己摸索的时间。

1. 五套算法放进同一个项目:核心目标与对比思路

1.1 这个项目解决的真实问题是什么

信号灯控制本质上是一个在线决策问题:每个时刻,面对不断变化的车流,决定放行哪个方向、放行多久。传统做法是固定周期固定绿信比,用一天里某几个时段的平均流量去标定信号配时方案;一旦真实流量偏离标定值,整个控制效果会迅速恶化。自适应控制的目标就是让信号灯能根据实时排队、到达流量、下游占用情况去动态调整相位和绿信比。

项目把五类做法放在一起,核心不是证明某一个算法“天下无敌”,而是搞清楚三个问题:

  • 传统理论方法(Webster 定时配时)在流量波动下的性能上限在哪里;
  • 不需要学习的自适应策略(Max Pressure、SOTL)能不能追平甚至超过强化学习算法;
  • 强化学习(DQN、DDPG)在状态设计、动作空间、奖励函数都比较合理的前提下,到底能比无学习方法提升多少,代价又是什么。

1.2 为什么用SUMO当试验场

SUMO是德国宇航中心开源的微观交通仿真软件,支持连续路网建模、随机车流生成、排放模型、TraCI接口。选择它有几个很实际的考虑:

  • 完全免费,没有授权限制,跑实验不需要向公司申请license;
  • TraCI允许外部Python程序在每次仿真步内读取车辆状态、改变信号灯相位,正好适合做闭环控制算法验证;
  • 支持命令行无界面运行,可以很轻松地用Shell批量启动多个仿真实例,训练几十个episode也不用一直开着GUI;
  • 社区活跃,常见问题基本都能搜到现成的解法。

对比商业软件Vissim,SUMO在微观行为的精细度上稍有差距,但对于验证信号控制算法来讲完全够用,甚至因为Python生态更完善,做强化学习接入反而更顺手。

1.3 实验场景怎么搭

我在复现过程中用的是单交叉口四相位经典场景:

  • 东西南北四个进口道,每个方向包含直行和左转车道,右转不单独控制;
  • 信号相位按照南北直行、南北左转、东西直行、东西左转四相位轮转;
  • 车流按泊松分布随机生成,每个方向的平均到达率在600~800 veh/h之间波动;
  • 仿真步长设为1秒,单次仿真时长3600秒(模拟一小时的高峰流)。

把路网规模控制在这个级别,是为了保证DQN和DDPG的训练在普通PC上几个小时内能跑完。多交叉口的场景不是不能做,但状态空间、动作空间和奖励设计都会复杂几个量级,作为项目起步阶段,单交叉口是最合理的验证场。后续要扩展也是在这个基础上往多路口、干线协调方向走。

2. 两个经典基线:Webster配时与Max Pressure的落地细节

2.1 Webster公式:从流量比到固定相位时长

Webster配时是信号控制里最经典的定时方法,它假设车流平稳、交叉口不饱和,用各相位关键车道的流量比来算出最优周期和绿信比。

核心公式如下:

# webster_calculation.py def webster_timing(flows, saturation_flow=1800, lost_time=3.0): """ flows: 各相位关键车道到达流率 (veh/h) saturation_flow: 饱和流率 (veh/h/ln) lost_time: 每相位损失时间 (s) """ num_phases = len(flows) y = [f / saturation_flow for f in flows] Y = sum(y) L = num_phases * lost_time C_opt = (1.5 * L + 5) / (1 - Y) green_times = [(C_opt - L) * yi / Y for yi in y] return C_opt, green_times

参数取值举个例子:四个关键方向流量分别是 650、520、700、580 veh/h,饱和流率取1800 veh/h,每相位损失时间3秒,那么:

  • 流量比Y = 0.361 + 0.289 + 0.389 + 0.322 = 1.361,这个值已经逼近1,说明交叉口接近饱和;
  • 最优周期 C_opt = (1.5×12 + 5) / (1 - 1.361) 已经变成负数,说明Webster公式在这个场景下失真了。实际中流量比Y不能太接近1,一般要求Y < 0.9。

所以我在项目里把平均到达率整体下调到500~700 veh/h,得到Y约0.9,周期约80秒,再按绿信比分配到四个相位。在SUMO的tlLogic里就是一组固定phase表,运行时完全不做调整。

Webster的问题很明显:它不是闭环控制,没有实时反馈,流量波动一旦超过标定区间,红灯方向可能空放,绿灯方向却排队溢出。把它当基线,是为了给其它算法一个“最朴素做法”的参照物。

2.2 最大压力控制:用状态差决定放行谁

Max Pressure控制来自背压路由思想,名字里“最大压力”指的是:每个候选相位对应一组待放行车道的排队压力和下游可用空间之差,选择压力最大的相位放行。它天然会避开下游已经堵死的方向,这是它比固定配时强很多的地方。

在SUMO里的简化实现:

# max_pressure.py import traci def get_lane_queue_vehicles(lane_id): vehicles = traci.lane.getLastStepVehicleIDs(lane_id) queue = 0 for veh_id in vehicles: speed = traci.vehicle.getSpeed(veh_id) if speed < 0.1: queue += 1 return queue def compute_phase_pressure(upstream_lanes, downstream_lanes): up_pressure = sum(get_lane_queue_vehicles(l) for l in upstream_lanes) down_pressure = sum(get_lane_queue_vehicles(l) for l in downstream_lanes) return up_pressure - down_pressure def choose_next_phase(phase_candidates): pressures = [] for cand in phase_candidates: p = compute_phase_pressure(cand["upstream"], cand["downstream"]) pressures.append(p) return int(argmax(pressures))

关键细节是下游压力。很多人第一次实现Max Pressure只算上游排队,忽略了“下游是否还有空档”,结果会出现一个方向绿灯一直放行、下游路段被顶死的情况。加了 down_pressure 之后,算法会倾向于把绿灯给“上游有车、下游有空”的相位,整个交叉口的吞吐量反而上去了。

实际运行时还必须加两个约束:最小绿灯时间最大绿灯时间。最小绿灯时间保证行人和已经启动的车辆安全通过,最大绿灯时间防止某一个相位被连续选中导致其它方向饿死。我在项目里设的是 min_green=10秒,max_green=60秒。

2.3 无学习基线的共同局限

Max Pressure和SOTL虽然自适应,但都没有“记忆”,不会根据历史流量变化去调整参数。它们对瞬时状态反应灵敏,却谈不上最优。Max Pressure对排队检测的准确性要求很高,SUMO里的getLastStepVehicleIDs拿到的是车道上的车辆集合,需要自己过滤静止车辆;如果检测频率太低,切换会滞后,排队会长起来。这两个方法的价值是给强化学习做一个性能下限的参照:如果RL学了半天还不如Max Pressure,那问题多半出在状态或奖励设计上,而不是神经网络结构。

3. DQN在离散相位控制里的完整训练链路

3.1 状态空间、动作空间与奖励函数设计

DQN处理的是离散动作,所以它天然适合“选相位”这个场景。动作空间直接设置为四相位切换,但为了减少频繁切换,我在动作空间里增加了“保持当前相位”这一项,一共有5个动作。信号控制中动作空间的设计非常影响训练效果:如果动作只能让相位强制跳到下一个,模型没有机会维持当前绿灯,就会在仿真里出现绿灯刚亮就切走的抖动现象。

状态空间方面,项目里选择了以下特征:

  • 四个相位分别对应的上游车道排队车辆数;
  • 当前相位编号;
  • 当前相位已经持续的时间;
  • 最近60秒各方向的平均到达流量。

为什么选这些?排队数反映当前的拥堵程度,相位持续时间决定是否需要切换,到达流量反映趋势性的需求变化。这四类信息已经能覆盖单交叉口信号控制的主要决策依据,再加高维信息反而会让DQN更难收敛。

奖励函数是DQN信号控制项目的灵魂。我的设计是:

def compute_reward(queue_vehicles, waiting_times, switch_flag): total_wait = sum(waiting_times) total_queue = sum(queue_vehicles) switch_penalty = 2.0 if switch_flag else 0.0 reward = -0.4 * total_wait - 0.3 * total_queue - switch_penalty return reward

加 switch_penalty 是因为如果不惩罚频繁切换,DQN会倾向于每几秒钟就换相位,造成车辆刚加速就遇红灯,整体通行效率大幅下降。

3.2 网络结构与训练流程

网络结构本身不复杂,三层MLP加ReLU激活,隐藏层128个神经元,输出5个动作的Q值。这个规模在单交叉口场景下完全足够,不需要上CNN,也不需要Attention。

训练流程里最重要的几个超参:

buffer_size = 20000 batch_size = 64 gamma = 0.95 learning_rate = 1e-3 target_update_freq = 500 epsilon_start = 1.0 epsilon_min = 0.05 epsilon_decay = 0.995

gamma=0.95这个值值得说一下。信号控制是典型的长时决策问题,但决策影响的时间尺度其实不长,一个相位周期也就60到90秒,所以折扣因子不需要设得非常大。设成0.99甚至更高时,累计回报波动会明显变大,训练更难稳定。

训练循环和TraCI的交互顺序是这样:

# train_dqn.py 核心循环(伪代码) for episode in range(EPISODES): traci.load(["-n", net_file, "-r", route_file]) # 重载仿真 state = get_state() while step < EPISODE_STEPS: action = epsilon_greedy(state, epsilon) execute_action(action) # traci.trafficlight.setPhase / setPhaseDuration traci.simulationStep() # 推进1秒 next_state = get_state() reward = compute_reward(...) replay_buffer.push((state, action, reward, next_state, done)) if len(replay_buffer) > batch_size: update_q_network() state = next_state

重载仿真这一步特别容易被新手忽略。如果不重置SUMO,下一个episode会接着上一个的运行状态继续,状态空间直接被污染,训练曲线会乱成一团。

3.3 换几个随机种子再下结论

DQN在信号控制场景里最大的问题是训练方差大。同一套代码,同一个路网,随机种子不同,最好的情况平均等待时间能降到20秒以内,最差的情况可能比Webster还差。

为了避免被随机性误导,我的做法是每个配置跑三个不同随机种子,取平均值。最终收敛好的DQN agent表现大约是:平均等待时间比Webster下降25%到30%,最大排队长度下降20%左右。但注意,这是对训练流量分布有效。如果测试时流量大幅偏离训练分布,DQN的表现会明显下滑,这是强化学习样本外泛化问题的直接体现,也是后面要提到的多场景训练方案的出发点。

4. DDPG的连续控制思路:相位时长决策实战

4.1 为什么在信号控制里要用连续动作

DQN的动作是选相位,但真实交叉口控制的另一个维度是“相位持续时长”,这是个连续变量。比如当前南北直行绿灯已经亮了20秒,是继续放3秒还是继续放12秒?DQN没法直接输出这个值,只能通过离散动作间接实现。

DDPG的优势就在这里:它可以直接输出连续动作,比如“当前相位延长时间的比例”,然后映射成具体的秒数。这让控制变得更平滑——绿灯不是按整秒或整相位跳变,而是可以根据实时排队情况微调时长。

4.2 动作映射、噪声音与网络结构

DDPG在信号控制里的实现,首先要解决动作映射问题。我采用的做法是:

  • Actor网络输出动作 a ∈ [0,1];
  • 映射为相位延长时间 delta = a * 15(秒);
  • 当前相位绿灯时间达到 min_green 后,持续延长 delta 秒;
  • 当延长时间累计超过 max_green,强制切换到下一个相位。

注意DDPG不负责“切换方向”,它只决定当前相位是否继续延长。切换方向仍然由一个底层规则来处理,比如当另一相位的压力差超过阈值时。这套“上层优化时长、底层规则选相位”的分层架构,在实践中比让DDPG直接输出四相位切换要稳定很多。

网络结构用的PyTorch大概长这样:

class Actor(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.fc1 = nn.Linear(state_dim, 128) self.fc2 = nn.Linear(128, 128) self.fc3 = nn.Linear(128, action_dim) def forward(self, state): x = torch.relu(self.fc1(state)) x = torch.relu(self.fc2(x)) return torch.tanh(self.fc3(x)) * 0.5 + 0.5 # 映射到[0,1]

DDPG探索用的是Ornstein-Uhlenbeck噪声,而不是DQN的epsilon-greedy。这是因为动作本身是有连续性的,这一秒延长5秒,下一秒大概率是延长4.5秒到5.5秒之间,OU噪声能保证探索轨迹在时域上的相关性,不会像高斯噪声那样每步独立跳变,导致控制动作剧烈抖动。

4.3 实测下来DDPG的坑比想象中多

必须说实话:在单交叉口这个场景里,DDPG并没有显著优于DQN。我复现出来的结果是两者平均等待时间差不多,DDPG偶尔在流量波动大的配置里更平缓,但训练时间几乎是DQN的两倍,因为每一步都要更新两套网络(Actor和Critic),还要额外做目标网络软更新。

还有几个容易踩的问题:

  • 奖励尺度极其敏感。同一个奖励函数,DQN学得动,DDPG可能完全学不动。需要把reward压缩到-1到1的区间附近,否则Critic的Q值估计会非常不稳定。
  • 相位延长动作太容易导致饿死。如果某方向持续有车,DDPG会倾向于一直延长这个方向的绿灯,其它方向排队甚至溢出。必须把 max_green 这个约束写死,并且在奖励函数里增加对最大排队长度的惩罚项。
  • 目标网络软更新参数tau。经验上 tau 取0.005比0.001更合适,更新太慢会导致策略和评价脱节。

5. 不靠神经网络的智能:自组织交通灯的阈值机制

5.1 局部占用驱动的相位切换规则

自组织交通灯(SOTL)的思路跟强化学习完全相反:它不学全局最优策略,只根据局部车流占用状态做反应。核心规则很简单:

  • 当前相位绿灯时间大于最小绿灯时间后,系统开始检测其它方向的排队情况;
  • 如果某个非当前相位方向的车道排队车辆数超过阈值,并且当前相位方向的车已经基本放空,就切换相位;
  • 如果所有方向都没有达到阈值,就一直保持当前绿灯,直到最大绿灯时间强制切换。

用代码表达更清楚:

# sotl.py def sotl_should_switch(phase, green_time, queues, other_queues): MIN_GREEN = 10 MAX_GREEN = 60 SWITCH_Q_THRESHOLD = 5 if green_time < MIN_GREEN: return False if green_time >= MAX_GREEN: return True if max(other_queues) > SWITCH_Q_THRESHOLD: return True return False

这个机制的价值在于它是自适应的,但完全没有神经网络、没有训练过程。它自己会形成一种“谁的车多就放谁”的行为,实际效果在车流波动场景下经常能干掉定时配时。

5.2 阈值参数整定的经验

SOTL的阈值看起来只有两三个,实际调起来需要一点耐心。我最初用的阈值是排队车辆数大于3就切换,结果低频流量下车流刚聚集就被放行,绿灯频繁切换,反而增加了停车次数。后来把阈值提到5,同时把最小绿灯时间设成10秒,效果才稳定下来。

一个比较可靠的标定思路是:先用Webster配时跑一段基准仿真,统计每个方向排队车辆的平均数和标准差,然后取“平均值 + 1倍标准差”作为SOTL的切换阈值。这样至少保证阈值跟路网的规模是匹配的,而不是拍脑袋定的数字。

5.3 SOTL和Max Pressure的本质差异

很多人会把SOTL和Max Pressure混为一谈,它们都属于无模型自适应方法,但决策依据完全不同:

  • Max Pressure比较的是“上游排队与下游空间的差值”,是压力梯度驱动;
  • SOTL比较的是“其它方向的排队是否超过阈值”,是局部触发驱动。

Max Pressure避免了向堵塞方向放行,SOTL则简单得多,只关心“有没有车在等”。在高流量场景下,Max Pressure的吞吐量通常优于SOTL,因为压力梯度能感知下游瓶颈;在中低流量场景,SOTL反应快、计算量接近零,两者差距很小。

这个对比也解释了为什么项目要把SOTL放进算法集:如果某个RL算法的表现连SOTL都打不过,那说明它根本不是在学习什么有效策略,只是把随机动作和流量变化拟合在一起。

6. 同台实测:指标怎么定、数据怎么比、结论怎么读

6.1 评价指标的选取

评价信号控制算法不能只盯一个指标。单一指标很容易掩盖问题,比如只优化平均等待时间,可能导致个别方向长时间等待的“公平性问题”。项目里我同时统计了四个指标:

  • 平均等待时间:所有车辆在交叉口停止等待的累计时间平均值,是最直观的用户感知指标;
  • 平均行程时间:从车辆进入路网到离开路网的穿越时间,包含行驶时间和停车时间;
  • 平均停车次数:每辆车在交叉口前从高速减速到低速再重新加速的次数,反映驾驶体验和油耗;
  • 最大排队长度:仿真过程中任意时刻任意方向的排队最大值,反映交叉口是否接近溢出风险。

SUMO里通过TraCI可以很方便地统计这些数据:

# eval_metrics.py travel_times = [traci.vehicle.getTravelTime(v) for v in traci.vehicle.getIDList()] waiting_times = [traci.vehicle.getWaitingTime(v) for v in traci.vehicle.getIDList()] stops = [traci.vehicle.getStopState(v) for v in traci.vehicle.getIDList()]

注意getWaitingTime返回的是最近一次运动状态之前的累积等待时间,不是从进入路网开始的总等待,所以要在每个仿真步采样后自行累加。

6.2 五套算法的实测数据对比

下面是单交叉口场景下的复现数据,方向车流均值为600 veh/h,随机种子固定,测试集与训练集流量分布保持一致(示例数据,实际会有随机波动):

算法平均等待时间(s)平均行程时间(s)平均停车次数最大排队长度(veh)
Webster定时配时34.258.71.812
Max Pressure27.652.31.49
SOTL25.149.81.58
DQN22.447.61.28
DDPG23.848.51.39

这个表只能代表某一种车流分布下的情况,换个随机种子、换个流量强度,排名会变。但如果只看整体趋势,有几点非常明确:

6.3 结果背后的机理分析

Websters垫底是必然的,它完全不响应流量变化,属于“睁眼瞎”控制;Max Pressure和SOTL作为无学习自适应方法,已经能明显改善通行效率,而且不需要任何训练时间;DQN和DDPG在测试集和训练集分布一致时能取得最优成绩,但优势没有想象中大——和SOTL比,平均等待时间也就下降了10%左右。

更值得关注的是泛化能力。我把测试车流从600 veh/h提高到900 veh/h,结果让 DQN 和 DDPG 的平均等待时间反弹幅度明显高于SOTL和Max Pressure。这说明强化学习方法的性能高度依赖训练分布,而这在真实场景里恰恰是个棘手问题:现实中早高峰、晚高峰、平峰流量变化极大,一个只在600 veh/h下训练过的模型很难直接部署到全天场景。解决思路是在多个流量档位上交替训练,让经验回放池同时覆盖低中高三种状态分布。

7. 从0复现整套工程:目录结构、TraCI联动与Shell批量实验

7.1 解压后的工程结构

这个项目打包成zip后,目录结构大概是这样的:

SUMO-Adaptive-TSC/ ├── nets/ │ ├── intersection.net.xml # SUMO路网文件 │ ├── intersection.sumocfg # SUMO仿真配置文件 │ └── routes.rou.xml # 车流生成文件 ├── src/ │ ├── base_controller.py # 控制器公共基类 │ ├── webster.py # Webster定时配时实现 │ ├── max_pressure.py # Max Pressure实现 │ ├── sotl.py # 自组织交通灯实现 │ ├── train_dqn.py # DQN训练脚本 │ ├── train_ddpg.py # DDPG训练脚本 │ └── eval_all.py # 统一评测脚本 ├── scripts/ │ ├── run_all.sh # 一键跑完整套对比 │ └── eval_all.sh # 批量评测脚本 └── logs/ # 训练日志与指标输出

在动手写代码之前,先把路网文件和车流文件准备好。SUMO路网文件可以用 netedit 图形化画,也可以直接用 XML 手写一个最小交叉口。新手建议先用 netedit 画一遍,保存后对照 XML 理解每个<connection><tlLogic>的含义,后面调试TraCI时能少踩很多坑。

7.2 TraCI连接和动态配时的核心操作

无论哪种算法,跟SUMO交互的方式都一样:先启动SUMO服务,再用Python的traci库连接。启动命令:

sumo-gui -n nets/intersection.net.xml -r nets/routes.rou.xml --remote-port 8813

对应Python端:

import traci traci.init(port=8813)

运行时最常用的几个TraCI接口:

  • traci.trafficlight.getPhase("tl1"):获取当前信号灯相位编号;
  • traci.trafficlight.setPhase("tl1", next_phase):直接切换相位;
  • traci.trafficlight.setPhaseDuration("tl1", duration):延长当前相位的剩余时间;
  • traci.lane.getLastStepVehicleIDs(lane_id):获取车道上当前车辆列表,用于计算排队长度;
  • traci.vehicle.getWaitingTime(veh_id):获取车辆累计等待时间。

这几个接口基本覆盖了信号控制算法的所有交互需求。DQN和DDPG在每次仿真步里拿到这些数据,计算状态、奖励、动作,然后通过setPhasesetPhaseDuration把决策写回路网。

7.3 Shell脚本批量跑实验的正确姿势

训练强化学习算法必须跑多个随机种子,否则偶然性太大。手工一个个运行不现实,用Shell脚本批量处理是标准做法:

#!/bin/bash # scripts/run_all.sh seed_list=(1 2 3 4 5) for seed in "${seed_list[@]}" do echo "Running DQN with seed $seed" python src/train_dqn.py --seed $seed --episodes 2000 \ --log_dir logs/dqn_seed${seed} > logs/dqn_seed${seed}.log 2>&1 & done for seed in "${seed_list[@]}" do echo "Running DDPG with seed $seed" python src/train_ddpg.py --seed $seed --episodes 2000 \ --log_dir logs/ddpg_seed${seed} > logs/ddpg_seed${seed}.log 2>&1 & done wait echo "All training processes finished."

一个很容易被忽略的坑:如果多个训练进程同时使用同一个输出目录,日志文件和数据文件会被互相覆盖,跑完发现自己辛辛苦苦训了几小时的模型丢了。一定要在启动参数里按seed分隔子目录。

另外默认情况下SUMO仿真可能同时启动多个GUI窗口,占用大量内存。在批处理脚本里应该用sumo而不是sumo-gui启动,并加上--no-warnings--quit-on-end参数,让进程跑完自动退出。

8. 实测踩坑记录:版本兼容、训练不稳定和仿真细节

8.1 SUMO版本差异导致的API不兼容

不同版本的SUMO对TraCI接口的支持差异不小。项目最早在SUMO 1.12上开发,部分接口在迁移到1.20版本后行为有了变化。最典型的是traci.lane.getWaitingTime在旧版本里返回单位是秒,新版本里某些情况下返回的是毫秒,导致奖励函数数量级完全错乱,训练直接发散。

建议开发阶段固定SUMO版本,并且用虚拟环境管理,不要把系统全局的环境搞混。跨版本迁移时,先用一个小demo脚本测试常用接口的输出格式,确认无误再跑全套训练。

8.2 DQN训练不稳定的三个根因

训练曲线上下乱跳是DQN在信号控制项目里最常见的现象。我排查下来,有三个根因:

  • 奖励函数数量级不对。等待时间累加动辄几十上百,网络输出层如果不做归一化或裁剪,损失函数数值会非常大,梯度更新步长过大导致震荡。解决方法是先统计一个episode的reward范围,然后做标准化。
  • epsilon衰减太快。信号控制的状态空间不算复杂,但是reward信号延迟较长,如果epsilon衰减太快,网络还在探索阶段就已经进入利用为主的阶段,后面很难找到更优策略。我用的是每episode衰减0.995,500个episode后epsilon才降到0.08左右。
  • 目标网络更新频率太低。更新频率500步算一个合理起点,如果发现前期训练正常、后期震荡,可以尝试降低到200步或300步。

8.3 仿真细节上最容易忽略的问题

信号控制算法对仿真本身的设置非常敏感,我踩过的坑包括:

  • --step-length设置。步长设为1秒是通用选择,但如果你做DDPG相位时长决策,0.5秒的步长会让控制更细腻,训练时间也翻倍。建议先把算法逻辑跑通,再考虑步长精度。
  • 随机种子固定。SUMO的路网文件、路由文件、仿真参数里都可以指定随机种子。不固定的话,每次实验的车流都不同,算法对比就失去了控制变量意义。训练时固定,测试时再换新种子来测泛化。
  • 信号灯相位与路网连接的一致性。netedit画路网时生成的tlLogic相位顺序,有时候跟TraCI里读到的相位编号不一致。实现算法前先打印每个相位的关联车道,确认相位索引和车道方向一一对应,否则状态设计全错。

这套工程做下来,我最深的体会是“算法胜负”远没有“实验流程”重要。DQN和DDPG调参三个月得出的结论,可能只是“在这个路网、这个随机种子、这个奖励函数下”的结论;而把Webster、Max Pressure、SOTL这些基线实现好,把评测流程脚本化,得到的是一个可以反复用的实验平台。后续想扩展多交叉口协调控制、加入黄灯和全红清空期、甚至换成多智能体强化学习框架,都只需要在这个工程上增量开发。做自适应信号控制,先别急着追新算法,把基线和评测工具做扎实,后面每一步都省力。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 1:54:55

网易视频算法工程师笔试备考攻略:四大模块与目标跟踪考点精讲

每年到校招季&#xff0c;我后台收到最多的私信就是“网易视频算法工程师笔试到底考什么”“提前批是不是比正式批简单”“我算法题刷得还行&#xff0c;但视频知识很虚怎么办”。作为当年参加过网易提前批、后来又帮学弟学妹复盘过好几轮视频算法岗笔试的人&#xff0c;我想认…

作者头像 李华
网站建设 2026/9/4 14:37:50

具身智能万台交付的卡点:系统一致性、数据闭环与运维工程

具身智能行业最近在反复讨论同一个问题&#xff1a;万台交付的卡点到底在哪里。听了几位做整机、做软件、做供应链、做运维的一线从业者聊完一圈之后&#xff0c;我的判断很直接——卡点不在某个模型能不能跑&#xff0c;而在于整个系统是否具备批量复制的能力。这个问题对正在…

作者头像 李华
网站建设 2026/9/6 2:18:32

STM32桌面写字机实战:G-code解析、LVGL界面与SD卡脱机打印方案

简介&#xff1a;本资源是一套基于STM32微控制器的G-code解释器完整工程&#xff0c;面向嵌入式课程设计、物联网实践及智能机电系统开发学习者&#xff0c;解决写字机类设备的脱机运动控制与人机交互核心问题。项目集成LVGL图形界面、SD卡文件系统&#xff08;FatFS&#xff0…

作者头像 李华
网站建设 2026/9/5 9:04:42

基于51单片机的锂电池电量检测与充放电保护系统设计详解

简介&#xff1a;本资源面向电子类专业学生、嵌入式初学者及电池管理系统&#xff08;BMS&#xff09;实践开发者&#xff0c;提供一套覆盖锂电池检测、电量估算、充放电保护与均衡管理的完整51/52单片机工程方案。四套设计分别聚焦电压电流容量检测仪表、BMS均衡测试仪、仿真级…

作者头像 李华
网站建设 2026/9/2 22:56:14

yazi 剪贴板方案解析:三步搞定终端与 SSH 远程的复制粘贴

yazi 剪贴板方案解析&#xff1a;三步搞定终端与 SSH 远程的复制粘贴 【免费下载链接】yazi &#x1f4a5; Blazing fast terminal file manager written in Rust, based on async I/O. 项目地址: https://gitcode.com/GitHub_Trending/ya/yazi yazi 是用 Rust 编写的终…

作者头像 李华