首页 / 资讯中心 / 文章详情

SUMO交通信号自适应控制实战:Python与Shell实现强化学习对比

SUMO交通信号自适应控制实战:Python与Shell实现强化学习对比 ★ FEATURED ARTICLE
简介一套基于SUMO仿真平台的交通信号自适应控制项目涵盖深度Q网络DQN、深度确定性策略梯度DDPG、韦氏算法、最大压力与自组织交通灯五种控制策略。资源面向智能交通研究者、算法工程师以及强化学习初学者帮助理解不同策略在交叉口信号优化中的差异与适用场景无论学术实验还是工程验证都能快速上手。压缩包共含六十五个文件以三十七个Python脚本为主干配合Shell训练脚本、SUMO路网配置、实验截图及说明文档整体仅二点九四兆字节目录划分清晰代码模块包含智能体、信号控制器、仿真进程等部分便于二次开发。目前已有四十六人学习使用。通过运行项目可复现完整训练与调参流程对比各算法在车辆平均等待时间、通行效率等指标上的表现并提供超参数优化与结果绘图脚本适合作为交通仿真与深度强化学习交叉领域的实践基座。1. 先讲清楚SUMO 交通信号自适应控制这套工具箱到底在解决什么问题SUMO 交通信号自适应控制指的是在一个微观交通仿真器里把韦氏配时、最大压力、自组织交通灯这类经典策略和 DQN、DDPG 这类强化学习算法放在同一张路网上做对比和调优。标题里的 Python 与 Shell 已经说明这类下载包的大致形态Python 承担策略逻辑和训练流程Shell 脚本负责批量跑仿真、切参数、收集日志。对做信号控制研究的工程师来说真路口做 A/B 测试成本极高策略之间的平均延误差出 30% 以上而 SUMO 里一个四相位交叉口几十秒就能复现一轮。这篇笔记不打算复述某个现成项目的说明书而是按一套可复现的落地路径来讲环境怎么搭、策略怎么接、参数怎么设、五个典型坑在哪以及最后怎么出让人信服的对比结论。2. 先建可控的仿真环境SUMO 路网、流量配置与 Shell 批量管线拿到任何带“Python_Shell_下载”字样的交通控制代码包第一件事都不是看算法而是先把仿真环境跑通。SUMO 的强项在于它是微观仿真每辆车有跟驰模型和换道模型信号灯策略的任何改动都会真实反馈到排队长度、车速和延误上。但代价是它的输入文件和运行方式比较多版本不齐、路径不对、参数策略没对齐都会让后续的算法对比变得没有意义。2.1 SUMO 安装与路网文件的三种来源版本不齐就别想输出稳定无论你是从 SUMO 官网下载安装包还是用包管理器装好 sumo第一步永远是确认你的 sumo 二进制版本和 Python 端 traci 模块来自同一发行版。这个细节常常被忽略不少人是在某个教程里 pip 装了 traci然后发现和系统里的 sumo 版本对不上轻则打印警告重则运行到一半收到不支持的 TraCI 命令。我一般会在虚拟环境里固定一个版本Windows 下直接用官网安装包自带的 tools 目录Linux 下包管理器装什么版本Python 端就跟着用什么版本绝不让两者跨大版本相处。一个最小可跑的 SUMO 实验需要三类文件路网文件.net.xml、车辆需求文件.rou.xml、仿真配置.sumocfg。路网可以用 netedit 手动画也可以写节点和边的 XML 后用 netconvert 生成常见的做法是后者因为可以版本化、批量改。下面是最小转换命令netconvert \ --node-filesnodes.nod.xml \ --edge-filesedges.edg.xml \ --output-filecross.net.xml逻辑说明这个命令把两个文本格式的路网描述合并成 SUMO 真正运行时要读的二进制化路网文件。节点文件里定义交叉口的位置和类型边文件里定义路段的车道数、限速和连通关系。参数说明--node-files和--edge-files分别指定输入--output-file指定输出如果路网有道路坡度或公交站点还要追加--osm-files之类参数但普通单路口实验用不到。命令跑完若没有报错再用 netedit 打开生成的 net.xml 检查一遍车道连接特别是转向车道有没有接对这一步比起手调 XML 靠谱得多。rou.xml流量文件可以手写也可以用 Python 脚本生成。常见的做法是用randomTrips工具起步再按需求修正流量和发车分布最后统一由.sumocfg引用这两个文件。启动仿真的最小命令是sumo -c cross.sumocfg --start --quit-on-end run.log 21逻辑说明--start让仿真启动后立即运行而不等待 GUI 上的开始按钮--quit-on-end让仿真在所有车辆到达或仿真时段结束后自动退出。输出重定向到 run.log 是为了后续排查。参数说明如果要在后台批量跑大量场景可以不加-S之外的任何可视化参数直接用无界面模式这一步常见误用是开着sumo-gui跑实验每画一帧都要同步界面速度会慢一个数量级。2.2 用 Shell 脚本批量跑五套策略for 循环、参数注入与产物整理单次仿真跑通只是开始做自适应控制对比必然要跑很多组不同流量、不同策略、不同随机种子。这时候 Shell 脚本的价值就出来了。一个 Python 脚本负责单次实验Shell 脚本负责把所有实验串起来失败时也能从日志快速定位是哪一组出了问题。下面是一个我常用的批量脚本骨架#!/usr/bin/env bash set -euo pipefail ROOT_DIR$(cd $(dirname $0) pwd) for demand in 600 900 1200; do for phase in webster maxpressure selforg dqn ddpg; do echo [RUN] demand${demand} phase${phase} python3 ${ROOT_DIR}/run_controller.py \ --cfg ${ROOT_DIR}/nets/cross.sumocfg \ --phase ${phase} \ --demand ${demand} \ --seed 42 \ --output-dir ${ROOT_DIR}/output/${phase} done done逻辑说明外层循环遍历三个流量等级内层循环遍历五套控制策略每次调用都指定独立输出目录避免后续数据互相覆盖。set -euo pipefail是 shell 脚本入门就该养成的好习惯-e让脚本在任一条命令失败时直接退出-u防止未初始化变量-o pipefail保证管道命令的返回码不被最后一个命令吞掉。参数说明--seed 42固定随机种子这一步做对比实验时非常关键--demand控制车流量密度--output-dir把每套策略的结果分开保存后续统计均值才方便。这里有一个取舍为什么不用一个 Python 循环做完全部事因为交通仿真批量实验很容易因为某个路网文件缺失、某个策略中途崩溃而中断Shell 脚本负责隔离开每轮实验Python 只负责这一轮实验内的计算。这个分工在实际跑数据时能省很多时间。2.3 四个改动最频繁的配置参数step-length、黄灯、最大绿灯与随机种子SUMO 仿真的可调参数很多但交通信号控制实验里真正影响结论稳定性的就四个。仿真步长step-length默认是 1 秒强化学习场景里常常要改成 0.1 秒让车辆在信号切换附近有更真实的加减速行为代价是仿真时间变长action-period指控制器每隔多少秒做一次决策固定配时不需要DQN 和 DDPG 必须要否则每个仿真步都变更信号灯不仅学习不稳定还严重偏离真实信号机的行为最大绿灯时长控制单相位一次最长放行时间是自组织和强化学习都要遵守的约束随机种子影响车辆插入的随机性同一个策略不同种子跑出来的平均延误可能差 10% 以上对比实验至少五个种子起步。参数常见取值影响典型误用step-length1.0 / 0.1 s仿真精度与耗时调小后不做时长归一导致训练变慢action-period5 ~ 15 s决策频率强化学习收敛难度每个 step 都换相位max-green30 ~ 50 s绿灯上限防止单方向饿死设太小压力大的方向放不完random-seed0 ~ 65535流量随机性只跑一个种子就下结论这四个参数之间不是独立的。比如 action-period 变大后max-green 也要相应放宽否则一个相位还没放满 8 秒就被强制切换。把决策周期和信号灯本身的约束拆开是后面接算法前最值得花时间做的一件事。3. 三套经典策略的基准价值韦氏配时、最大压力与自组织信号灯原理与实现强化学习不是唯一的主角。在 SUMO 交通信号自适应控制这个标题下韦氏配时是固定控制的理论基座最大压力和自组织交通灯是两个思路完全不同的轻量自适应方法三者合在一起构成了强化学习的对照组。没有对照组你根本说不清 DQN 的收益是来自“学会了策略”还是仅仅因为“信号灯终于会动了”。3.1 韦氏配时用饱和流量比算出固定周期也是后续一切比较的零点韦氏配时Webster 配时是最经典的单点固定配时方法核心是用各相位的流量比估算最佳周期时长再把周期切给各相位。公式里最关键的两个输入量总损失时间 L 和流量比之和 Y。下面的 Python 函数实现了最基础版本def webster_timing(flows_by_phase, lost_time4.0, saturation1800): # flows_by_phase 是每个相位的车流到达率单位 veh/h y_sum sum(max(f / saturation, 1e-6) for f in flows_by_phase) if y_sum 1.0: raise ValueError(流量比之和必须小于 1当前路口的负荷已超过通行能力) cycle (1.5 * lost_time 5.0) / (1.0 - y_sum) green_by_phase [ max((cycle - lost_time) * (f / saturation) / y_sum, 3.0) for f in flows_by_phase ] return cycle, green_by_phase逻辑说明先算出总流量比y_sum它代表路口整体饱和度。韦氏经验公式认为最佳周期时长和损失时间成正比和剩余通行能力成反比。绿灯分配则按每个相位的流量比占总流量比的比例切分剩余时间。参数说明lost_time一般取 4 秒等于黄灯加启动损失saturation即饱和流率理想情况下一条直行车道约 1800 veh/h但受 SUMO 跟车模型影响实际值会偏低flows_by_phase来自仿真日志里各方向上一轮统计的车流量。要提醒的是韦氏配时对流量输入非常敏感真实交叉口的车流在一天内波动很大固定周期不可能全时段最优。所以在整套对比里韦氏配时只作为“不变化的基准线”用来回答“如果什么都不调延误是多少”。3.2 最大压力控制用排出与进入的排队差决定放行谁最大压力Max Pressure控制是自适应交通信号领域被研究比较多的一类方法思路是给每个候选相位算一个压力值谁的排队压力大谁就获得绿灯。压力定义有很多变体最常见也最容易在 SUMO 中实现的是“该相位放行的车道排队数之和减去下游接收车道排队数之和”import traci def lane_queue(lane_id): # 统计该车道当前停车等待的车辆数而不是所有车辆数 return traci.lane.getLastStepHaltingNumber(lane_id) def phase_pressure(entry_lanes, exit_lanes): q_in sum(lane_queue(lane) for lane in entry_lanes) q_out sum(lane_queue(lane) for lane in exit_lanes) return q_in - q_out def select_max_pressure(candidate_phases): # candidate_phases: {phase_id: {entry: [...], exit: [...]}} scored [] for phase_id, lanes in candidate_phases.items(): pressure phase_pressure(lanes[entry], lanes[exit]) scored.append((pressure, phase_id)) return max(scored, keylambda x: x[0])[1]逻辑说明每个决策周期都重新计算所有候选相位的压力然后选压力最高的相位放行。压力为正是入方向排队占优说明该方向更需要绿灯压力为负则表示下游已经堵死放了也会快速溢出。参数说明getLastStepHaltingNumber查的是步长结束时处于停车状态的车数这是 SUMO 里对“排队”最直接的近似不要用getLastStepVehicleNumber它统计的是整条车道所有车包括正在以 60 km/h 行驶的车会严重高估压力。实现时还有一个容易被忽略的点最大压力控制和固定时序一样也要遵守最小绿灯时长否则可能出现相位频繁切换绿灯刚亮 2 秒就被抢走行人过街时间完全不够。常见做法是把最小绿灯设成 8 ~ 10 秒决策周期和它绑定。3.3 自组织信号灯局部检测器触发的绿灯延长与相位跳转自组织交通灯这个说法没有统一公式市面上能看到的多是基于检测器占用率或到达状态的触发式规则当前相位绿灯时间先走一个最小基础值如果检测器显示车辆还在持续到达就延长数个固定时长如果检测器已经空了就提前结束当前相位跳到有需求的相位。下面是一段非常简化的实现def selforg_controller(current_green_time, lane_occupancy, min_green8, max_green40): # lane_occupancy 取关键车道检测器的占有率0~1 if current_green_time min_green: return current_green_time # 最小绿灯未走完继续当前相位 if lane_occupancy 0.7 and current_green_time max_green: return current_green_time 3 # 车流密集延长 3 秒 return current_green_time # 车道空了交给相位调度逻辑切换逻辑说明这是一个单车道版的最简规则真实代码里要同时看多个关键车道的实时占有率并和相位状态机配合。current_green_time是当前相位已累计的绿灯秒数lane_occupancy来自 SUMO 的检测器或者车道的占用率估计。参数说明占用率阈值 0.7 意味着约七成时间有车占据检测区域这个数值对单车道进口道比较合适多车道会让占有率整体偏高需要相应上调到 0.8 左右延长步长 3 秒在真实信号机里常见太短的延长会造成每 2 秒评估一次策略闪烁严重。自组织和最大压力的最大区别在于自组织不追求全局最优它只响应局部的“有车没车、多车少车”切换逻辑是预定义的规则。而最大压力要为每个相位计算压力和竞争关系属于带协调的局部决策。把两套都跑一遍你会直观看到参数对行为的影响也能为后面解读 DQN 学到的策略提供参照。4. 把决策交给神经网络DQN 与 DDPG 如何接进 SUMO到了强化学习这一层工作重心不再是信号配时理论而是如何让一个无模型算法在一个可重复交互的环境里学出策略。SUMO 的价值在于它可以按需求暂停、推进和重置这让每个 episode 都能拿到一致的初始条件和流量分布。常见做法是自己用 traci 包一层类似 Gym 的环境核心要回答三个问题状态给你看什么、动作允许你做什么、奖励按什么逻辑发。4.1 先定状态、动作和奖励这三个定义错了后面全是白做状态设计直接影响收敛速度。一个最小可用的状态向量至少包括当前相位编号、当前绿灯已经持续的时间、关键方向的排队差、本周期累计上车数。下面是一个示例import traci import numpy as np def build_state(tl_id, queueing_diff_lanes): phase_id traci.trafficlight.getPhase(tl_id) phase_left traci.trafficlight.getPhaseDuration(tl_id) q_diff 0 for entry, exit_ in queueing_diff_lanes: q_entry traci.lane.getLastStepHaltingNumber(entry) q_exit traci.lane.getLastStepHaltingNumber(exit_) q_diff q_entry - q_exit total_waiting sum( traci.lane.getLastStepHaltingNumber(lane) for lane in traci.lane.getIDList() if lane.startswith(in_) # 只关心路口上游车道 ) return np.array([phase_id, phase_left, q_diff, total_waiting], dtypenp.float32)逻辑说明相位编号和剩余时长让智能体知道当前信号机处于哪个状态排队差是压力的连续近似等价于告诉它哪个方向的“积压”更严重总等待数是全局惩罚项用来衡量整体路口健康度。参数说明getPhaseDuration返回当前相位剩余秒数不同 SUMO 版本下字段语义略有差异接入时打印一遍确认queueing_diff_lanes需要在环境构建时把相位和车道映射关系传进来。动作空间在 DQN 场景下是离散的相位 0 到 3分别对应南北直行、南北左转、东西直行、东西左转。奖励设计普遍采用压迫式的负向奖励每一步把延误增量或排队增量转化为负数反馈。更细一点的做法是把“是否切换相位”也纳入奖励切相位的瞬间扣 1 分以抑制抖动否则智能体学会每帧换相位指标反而更差。4.2 DQN 最小训练循环经验回放、目标网络与动作掩码DQN 不需要模拟信号灯的动态方程它只需要在每次决策时维护一个 Q 表让状态映射到各相位动作的期望收益。交通信号控制里状态空间通常不大上千维的场景少见因此隐藏层 64 到 128 个神经元就够用。必要的训练组件是经验回放缓冲区和目标网络下面给出一个可控的最小骨架from collections import deque import random class ReplayBuffer: def __init__(self, capacity20000, batch_size128): self.buffer deque(maxlencapacity) self.batch_size batch_size def add(self, transition): self.buffer.append(transition) def sample(self): batch random.sample(self.buffer, self.batch_size) states, actions, rewards, next_states, dones, masks zip(*batch) return (np.array(states), np.array(actions), np.array(rewards), np.array(next_states), np.array(dones), np.array(masks)) def __len__(self): return len(self.buffer)逻辑说明经验回放把一条条五元组(state, action, reward, next_state, done)存进缓冲每次梯度更新随机抽一小批打断样本之间的相关性。masks在这里表示动作掩码比如当前相位正处于最小绿灯阶段禁止切相位就把对应动作的 Q 值设成负无穷避免网络选择非法动作。参数说明容量 20000 是针对单路口 10 秒决策周期、一个 episode 约 300 步设计的如果决策周期缩短到 3 秒容量要相应放大否则早先的好经验会被快速挤出缓冲。训练循环里还要设一个目标网络每固定步数把在线网络的权重同步过去。目标网络不参与梯度更新只负责计算r gamma * max_a Q_target(next_state, a)避免“用自己更新自己的同一个参数”造成 Q 值发散。同步步数常见取 200 到 500 步交通仿真步长 0.1 秒时500 步等于 50 秒仿真时间并不会拖慢训练。还有一个容易影响成败的选项是动作决策周期。我刚才提到的所有实现里action-period都应当和信号机相位最小绿灯绑定通常取 10 秒。也就是说智能体每 10 秒做一个新的放行决策而不是每个仿真步都做。这个约束让 DQN 的问题规模小了一个量级也更接近现实信号机的工作方式。4.3 DDPG 处理连续输出连续绿灯延长而不是连续相位DDPG 在交通信号控制里的角色有点微妙。相位本身是离散的强行让 DDPG 输出一个连续值代表相位选择再映射到离散集合性价比并不高而且削弱了这个算法本来擅长连续控制的优势。我见过做通的方案大多让 DDPG 输出绿灯延长量智能体观察当前状态输出一个实数代表“在当前最大绿灯限制下再延续多少秒”。这个动作天然是连续域DDPG 的连续 Actor 刚好合适。def apply_ddpg_action(raw_action, current_green_time, min_green8, max_green45): # raw_action 由 Actor 网络输出tanh 激活后范围在 [-1, 1] delta float(raw_action[0]) * 10.0 target current_green_time delta return max(min(target, max_green), min_green)逻辑说明网络输出先被 tanh 压缩到 -1 到 1乘上 10 秒后作为延长量。当前绿灯若已持续 30 秒网络输出 0.5那么绿灯再延长 5 秒到 35 秒若输出 -0.8则当前绿灯缩短 8 秒。min_green和max_green是硬约束防止网络因为探索噪声把绿灯压到 0 或无限长。参数说明10.0是延长量的缩放因子可以根据车道长度和仿真步长调整车流量大时调大到 15让动作具有更大的调控范围。DDPG 训练的常见手感是比 DQN 更难稳定。Actor 和 Critic 两个网络交替更新很容易出现 Critic 估计偏低进而带偏 Actor 的情况。一个务实的做法是给整个训练循环加一个强力的 Critic 学习率衰减并每隔几个 episode 做一次“参数敏感性自检”拿固定场景重放训练中保存的多个 checkpoint指标最好的是中间那几次而不是最后那次这时就要把学习率调小或扩大噪声衰减周期。# 训练时常用的 epsilon/噪声退火参数DDPG 也适用 class OUNoise: def __init__(self, mu0.0, theta0.15, sigma0.2): self.mu, self.theta, self.sigma, self.state mu, theta, sigma, 0.0 def sample(self) - float: self.state self.theta * (self.mu - self.state) self.sigma * np.random.randn() return self.state逻辑说明OU 噪声让探索幅度随训练推进逐渐收敛前 100 个 episode 的随机动作多后面逐渐稳定到确定性策略。参数说明theta控制噪声回到均值的速度sigma控制探索强度交通信号控制场景下theta0.15, sigma0.2的搭配比较稳妥噪声太大容易让信号灯策略完全随机化影响学习曲线。5. 仿真训练里的五个常见坑从 SUMO 版本到奖励失真这一章的标题里我直接写了“常见坑”因为整个流程最容易翻车的点几乎都集中在这五个环节。每一条我都按现象、原因、解决的顺序整理读者可以在自己复现时直接对照查。5.1 SUMO 与 traci 版本不一致现象仿真能启动但策略脚本跑一段时间后报 TraCI 命令错误比如查询某个字段返回空值或直接抛TraCIException或者前一天还能用升级一次 SUMO 后同样的脚本拿到的状态结构变了相位号从 int 变成带属性对象。原因绝大多数情况下是 SUMO 二进制和 Python 端 traci 库分别来自不匹配的版本。SUMO 的 TraCI 协议在不同大版本之间存在命令兼容性差异小版本也偶有字段行为变化这类似前端里后向兼容的缺憾只能靠对齐版本解决。解决固定一个 SUMO 版本号并全程使用它自带或同版本发布的 tools 下的 traci。在虚拟环境里不要混着装多个交通仿真库每次升级前先做一轮旧实验结果能不能复现的回归验证。我现在的习惯是把 SUMO 安装路径写进环境变量并在训练脚本启动时打印traci.getVersion()和sumo --version两行版本确认后再跑正式实验。5.2 仿真越跑越慢单步耗时从 2 毫秒涨到 20 毫秒现象仿真前期流畅几十万步之后每步耗时急剧上升或者明明只修了一个信号灯整个路口所有车都像被“冻住”一样日志里显示 simulation speed 掉到个位数。原因最常见的是在每个仿真步里做全局 lane 遍历。前面那个build_state示例里用了traci.lane.getIDList()去取所有车道这在车辆多、路网大时会拉垮性能。另一个常见原因是开着 sumo-gui 跑批量实验GUI 渲染本身要占大量 CPU。解决批量实验一律用无界面sumo命令只在调试时开 GUITraCI 调用不要每个 step 全量查可以把查询分成低频和高频两批比如排队长度每 1 秒仿真时间查一次相位状态每 100 毫秒查一次。用脚本批量跑时还要定期检查日志里是否出现了step duration too high这类警告出现了就在配置里把step-length适当调大。5.3 强化学习不收敛Q 值乱跳、奖励曲线不降现象训练了 500 个 episode平均奖励曲线依然像心电图前一秒下行后一秒反弹同一组超参数换一个随机种子结果完全两样。原因奖励尺度不归一是最大的问题。交通延误数值动辄上百秒直接作为奖励会让损失值跨数量级跳动。其次是动作决策间隔太短信号灯每两秒就被动一次状态和动作之间的因果链被噪声切断智能体根本学不到“这个动作导致了那个结果”。解决先把奖励做归一化比如扣分项限定在 0 到 1 之间把排队车辆数除以观察范围内最大可能排队数。把 action-period 放到 10 秒以上以 15 秒为丹田再次调整时改动一次。第三经验的网络有了基础样本之后才开始训练前面 50 个 episode 只收集数据不更新梯度也是一个有效的稳定手段。5.4 黄灯和相位切换吞掉了绿灯时间现象智能体明明获得了更短的排队长度平均延误反而比固定配时高出去一截或者策略学到了频繁切相位但每个相位实际放行时间不足最小绿灯。原因相位切换过程有黄灯和全红过渡这期间车辆不允许通过。代码里如果直接把绿灯结束后的下一状态设置为新的绿灯相位没有扣除过渡时间智能体感知到的“绿灯”和车辆真正通过的绿灯相差了 3 到 4 秒策略就会被误导。解决把相位编号分成可决策相位和过渡相位两类过渡相位不进入 DQN 的动作空间也不会作为状态里的当前相位。奖励计算时凡是刚切换完的相位第一个决策周期内不计入排队改善因为这部分时间本来就在损失时间里。这样奖励信号才真正对应车辆可以通行的有效绿灯时长。5.5 Shell 批量运行时路径和参数转义问题现象脚本单独跑某一条命令是正常的放进 for 循环后就出现找不到.sumocfg文件或者$phase的值带上了意外的空格或换行日志里显示的命令参数看起来对但 Python 侧解析到的路径是错的。原因Shell 脚本里用了相对路径当前工作目录一变就找不到文件或者参数值里含有空格、中文未加引号导致被 Shell 拆成多个 token。Windows 上同一条脚本里混用了反斜杠路径也会触发转义问题。解决脚本第一行用ROOT_DIR$(cd $(dirname $0) pwd)把脚本所在目录转成绝对路径后面所有文件引用都拼在ROOT_DIR上。循环变量和传给 Python 的路径参数统一加双引号养成习惯后这类坑会少很多。用 shell 脚本重命名实验产物时也要注意同样的规则for 循环里变量不加引号几户是最大事故源。6. 让五套方案的对比结论可信指标、随机种子与参数敏感性检查很多人在 SUMO 里跑了一堆数据最后汇报时只说“强化学习平均延误降低了 15%”却拿不出另一套流量下的复现结果。这个章节教你如何把实验设计得能让结论站得住脚。6.1 需要盯住的四个指标平均延误是最直观的指标但一定要写明统计口径是所有车辆的平均还是只统计通过路口这一小段路的延误排队长度对应的是停车等待的车数能反映路口是否溢流停车次数表达驾驶体验一个相位频繁切换会让它异常升高吞吐量则是单位时间内通过路口的车辆数适合判断策略是否在“牺牲某方向保全整体”。下表是建议的统计方式指标定义数据来源使用要点平均延误实际行程时间与自由流行程时间之差traci 导出的 tripinfo区分路段还是路口域排队长度各上游车道停车等待车辆数lane.getLastStepHaltingNumber取时间平均不是瞬时值停车次数单车速度低于阈值后重新加速的次数tripinfo 的 stopNumber评价乘客舒适度吞吐量单位时间离开路口的车辆数edge 输出统计判断是否牺牲某一方向6.2 跑出可信对比的最小流程先跑韦氏配时和最大压力得到两个基准值再逐个加自组织和强化学习。每个策略至少五个随机种子for seed in 1 2 3 4 5; do for phase in webster maxpressure selforg dqn ddpg; do python3 run_experiment.py \ --phase $phase \ --seed $seed \ --demand 900 \ --outdir output/${phase}/seed_${seed} done done逻辑说明五套策略在同一流量、同一路网、同一批随机种子下重复五次每组结果独立存储。最后统计平均延误的均值和标准差写成mean ± std。参数说明--demand 900只是一个起点还应该把 600、1200 两档也跑一遍看策略在低饱和和高饱和下的表现趋势是否一致。参数敏感性检查是容易被跳过的一步。把最大绿灯在 30、40、50 秒三档之间滑动重新跑最大压力、自组织和 DQN你会发现有些策略的指标曲线很平有些则剧烈波动。前者说明策略参数鲁棒后者说明它学到的规则高度依赖配置。我现在的习惯是不管接真实路口还是写论文第一版永远先把韦氏配时和最大压力跑通把基准线钉死再上强化学习。这样至少能还原出“这 15% 的改善是策略带来的还是随机种子带来的”。一个连基准策略在不同随机种子下都漂 10% 的实验再好的算法结果都不值得相信。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站