多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

LSTM+SUMO交通信号配时优化:从流量预测到闭环控制实战

LSTM+SUMO交通信号配时优化:从流量预测到闭环控制实战 简介这份PDF资源面向交通工程、智能交通与机器学习方向的学习者和研究者聚焦城市交叉口信号配时不合理导致的拥堵与绿时浪费问题提出一种将长短期记忆人工神经网络LSTM与SUMO微观交通仿真相结合的配时优化方案。资源包共1个PDF文件约1.1MB内容为完整的学术论文涵盖SUMO十字路口车流模拟、通行能力与平均等待时间等特征函数设计、15000组训练集生成、LSTM网络建模及与模糊控制算法的对比实验并附有相位配时图与特征函数曲线等图表。目前已有706人学习下载。读者可从中获取从仿真环境搭建、数据集构造到神经网络训练与效果验证的完整研究思路理解LSTM如何学习交通流长期依赖关系并输出更优配时方案适合作为深度学习与交通控制交叉领域的课题参考或论文写作范本。1. 信号配时这件事为什么值得用LSTM加SUMO认真做一遍城市路口最让人头疼的不是车多而是绿灯空放、红灯排长队。传统配时方案要么按历史流量写死周期要么靠感应线圈做单点触发遇到潮汐流或突发拥堵就明显跟不上。把长短期记忆人工神经网络LSTM和SUMO仿真放在一起做交通信号灯配时优化本质是让模型学会“接下来几十秒各方向会来多少车”再把这个预测结果喂给配时策略在仿真里反复试错最终得到一套比固定配时更贴合实际流量的相位时长。这套方案适合做交通工程、智能交通或强化学习方向的研究生和一线算法工程师也适合手里有路口流量数据、想验证预测驱动控制是否划算的团队。它不要求你真有路口控制权一台能跑Python的机器加SUMO就够了。2. 从流量数据到LSTM预测输入到底怎么组织2.1 为什么选LSTM而不是普通全连接或ARIMA交通流量序列有两个绕不开的特性周期性和突发性。早高峰的上升沿、晚高峰的回落、平峰期的随机波动前后时刻强相关但又不是严格周期函数。普通全连接网络把过去若干个时间片拉平成一个向量丢掉了时间顺序ARIMA对线性趋势和固定周期还行遇到事故或天气导致的非平稳突变就吃力。LSTM的遗忘门和输入门能在长序列里保留“昨天同时段流量偏高”这类远距离信息又能在突增时快速更新状态所以做短时流量预测更稳。我一般把预测目标定为未来1到4个控制周期每个周期90到120秒的各进口道流量。输入用过去12个时间片每个时间片包含各方向流量、占有率、平均车速。如果只有流量数据只用流量也能跑但加占有率和车速后模型对拥堵的区分度会明显好一些。2.2 数据清洗与滑动窗口构造的代码实现原始数据常见问题是缺失和异常。缺失用前向填充加线性插值异常用3倍标准差截断。下面这段代码把一张带时间戳的流量表转成LSTM需要的三维张量形状是样本数时间步特征数。import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler # 假设df的列timestamp, flow_n, flow_s, flow_e, flow_w, occ_n, occ_s, occ_e, occ_w def build_sequences(df, time_steps12, pred_steps4): # 缺失值处理前向填充后线性插值 df df.ffill().interpolate(methodlinear) # 异常值截断3倍标准差 for col in df.columns: if col timestamp: continue mean, std df[col].mean(), df[col].std() df[col] df[col].clip(mean - 3 * std, mean 3 * std) # 归一化到0-1LSTM对尺度敏感 scaler MinMaxScaler() feature_cols [c for c in df.columns if c ! timestamp] scaled scaler.fit_transform(df[feature_cols]) X, y [], [] for i in range(len(scaled) - time_steps - pred_steps 1): X.append(scaled[i:i time_steps]) # 预测未来pred_steps个时间片的流量列前4列 y.append(scaled[i time_steps:i time_steps pred_steps, :4]) return np.array(X), np.array(y), scaler # 调用示例 # X, y, scaler build_sequences(df, time_steps12, pred_steps4) # print(X.shape) # (样本数, 12, 特征数)逻辑说明先做缺失和异常处理避免脏数据把LSTM的细胞状态带偏。归一化用MinMaxScaler而不是StandardScaler因为流量和占有率都不是高斯分布压到0到1更稳。滑动窗口的time_steps12对应过去12个时间片如果数据粒度是5分钟就是过去1小时pred_steps4对应未来20分钟。这两个参数要根据你的控制周期调整控制周期90秒数据粒度5分钟那预测4步其实覆盖了20分钟对配时来说偏长可以改成pred_steps2。2.3 LSTM网络结构与训练参数怎么定网络不用堆太深。两层LSTM每层64个隐藏单元后面接一个全连接输出层输出维度是pred_steps乘以4个方向。损失函数用MSE优化器用Adam学习率从1e-3开始如果验证集损失震荡就降到5e-4。批次大小取64训练轮数看早停一般50到80轮收敛。import tensorflow as tf from tensorflow.keras import layers, models def build_lstm_model(time_steps, n_features, pred_steps, n_directions4): model models.Sequential([ layers.LSTM(64, return_sequencesTrue, input_shape(time_steps, n_features)), layers.LSTM(64, return_sequencesFalse), layers.Dropout(0.2), # 防过拟合流量数据噪声大 layers.Dense(pred_steps * n_directions), layers.Reshape((pred_steps, n_directions)) ]) model.compile(optimizertf.keras.optimizers.Adam(1e-3), lossmse) return model # model build_lstm_model(12, 8, 4) # model.summary()参数说明第一层LSTM返回完整序列第二层只返回最后时刻的状态这样全连接层拿到的是整个输入窗口的浓缩表示。Dropout设0.2再高容易欠拟合。输出层用Reshape把一维向量还原成pred_steps, 4方便后面按方向取用。如果四个方向流量差异大可以在损失里给每个方向加权比如主路方向权重1.2支路0.8。3. 把LSTM预测接进SUMO配时优化的闭环怎么搭3.1 SUMO路网与信号配时的最小配置SUMO里信号灯由additional文件里的tlLogic定义。每个相位有duration和statestate字符串里G表示绿灯y表示黄灯r表示红灯字母顺序对应路网里连接connection的顺序。下面是一个四相位十字路口的tlLogic片段周期固定为120秒但每个相位的duration后面会被Python脚本动态改写。additional tlLogic idJ1 typestatic programID0 offset0 phase duration30 stateGGrrGGrr/ phase duration3 stateyyrryyrr/ phase duration25 staterrGGrrGG/ phase duration3 staterryyrryy/ phase duration30 stateGGrrGGrr/ phase duration3 stateyyrryyrr/ phase duration25 staterrGGrrGG/ phase duration3 staterryyrryy/ /tlLogic /additional注意state字符串的长度必须和该路口所有连接数一致写错一个字母就会报错。常见做法是先用netedit打开路网看每个相位的state自动生成结果再复制到additional文件里改duration。3.2 用TraCI在仿真循环里动态改配时SUMO的TraCI接口允许每一步仿真前修改信号相位时长。核心逻辑是每过N秒用LSTM预测未来流量按流量比例分配各相位绿灯时长再通过traci.trafficlight.setPhaseDuration写入。下面是一个最小闭环脚本。import traci import numpy as np # 假设model和scaler已经加载history是最近12个时间片的特征 def predict_green_split(model, scaler, history, min_green15, max_green45): # history形状 (12, n_features) x scaler.transform(history) # 注意scaler要用训练时的 x x.reshape(1, 12, -1) pred model.predict(x, verbose0)[0] # (pred_steps, 4) # 取未来pred_steps的平均流量作为需求估计 demand pred.mean(axis0) # 四个方向的平均需求 total demand.sum() if total 0: return [30, 30] # 兜底 # 简化为两个主要相位东西向和南北向 ew demand[0] demand[2] ns demand[1] demand[3] green_ew min_green (max_green - min_green) * ew / (ew ns) green_ns min_green (max_green - min_green) * ns / (ew ns) return [int(green_ew), int(green_ns)] def run_simulation(sumo_cmd, model, scaler, history_buffer): traci.start(sumo_cmd) step 0 while traci.simulation.getMinExpectedNumber() 0: traci.simulationStep() step 1 # 每90秒更新一次配时 if step % 90 0: greens predict_green_split(model, scaler, history_buffer) # 相位0和相位4是东西向绿灯相位2和相位6是南北向 traci.trafficlight.setPhaseDuration(J1, greens[0]) # 实际中需要按相位索引逐个设置这里只示意 # 收集当前流量进history_buffer # ... traci.close()逻辑说明predict_green_split把预测流量按方向汇总成两个主要相位需求再用线性映射把绿灯时长限制在min_green和max_green之间。min_green不能低于15秒否则行人过街时间不够max_green不超过45秒避免一个方向饿死其他方向。setPhaseDuration只影响当前相位下一周期需要重新设置。history_buffer要按仿真步滚动更新保持最近12个时间片。参数说明更新间隔90秒对应一个控制周期如果SUMO的step长度是1秒就是每90步更新一次。预测用的history_buffer特征顺序必须和训练时完全一致否则scaler会给出错误结果。这是最容易翻车的地方。3.3 评价指标不要只看平均延误配时优化常用平均延误、平均排队长度、平均车速。但只看平均延误会被主路的大流量掩盖支路的恶化。我一般同时看四个指标全路网平均延误、各进口道最大排队长度、平均停车次数、以及95分位延误。95分位延误能暴露少数车辆被严重耽误的情况比平均值更敏感。在SUMO里可以用tripinfo输出每辆车的等待时间再用Python聚合。下面这段代码从tripinfo.xml里算95分位延误。import xml.etree.ElementTree as ET import numpy as np def parse_tripinfo(path): tree ET.parse(path) waits [] for trip in tree.getroot().findall(tripinfo): waits.append(float(trip.get(waitingTime))) waits np.array(waits) return { mean_wait: waits.mean(), p95_wait: np.percentile(waits, 95), max_wait: waits.max() } # stats parse_tripinfo(tripinfo.xml) # print(stats)逻辑说明waitingTime是车辆在信号灯前的累计等待时间单位秒。p95_wait超过60秒就说明有相当一部分车等了一个周期以上需要检查配时是否对某些方向太苛刻。4. 避坑与排查LSTM加SUMO配时最容易翻车的五个地方4.1 预测很准但配时没改善现象LSTM的验证集MSE很低但接进SUMO后平均延误和固定配时差不多。原因通常是预测目标和控制目标不一致。模型预测的是各方向流量但配时用的是流量比例如果流量预测的绝对误差在低流量时段被放大绿灯分配就会抖动。解决在预测后加一层平滑比如用指数移动平均或者直接把预测流量离散成低、中、高三档按档位给绿灯减少抖动。4.2 SUMO里setPhaseDuration不生效现象脚本调了setPhaseDuration但仿真里相位时长没变。原因有两个一是tlLogic的type设成了static某些SUMO版本对static程序的动态修改支持不好改成actuated或把programID换一个再设置二是设置时机不对必须在当前相位结束前设置下一个相位的duration。解决在仿真步里判断当前相位索引提前一个步长设置下一相位时长。4.3 训练集和仿真数据分布不一致现象模型在真实数据上表现好在SUMO里预测偏差大。原因是SUMO的车辆到达模型比如flow定义和真实数据分布不同。解决先用SUMO跑固定配时收集仿真产生的流量数据混入训练集做微调。或者直接在SUMO里用真实流量文件route文件作为输入保证分布一致。4.4 归一化scaler没有保存现象重新加载模型做推理时预测结果完全不对。原因是训练时用了MinMaxScaler推理时忘了用同一个scaler或者重新fit了。解决训练完把scaler用joblib.dump保存推理时load。特征顺序也要用列表固定下来不能靠列名隐式匹配。4.5 仿真步长和控制周期不匹配现象配时更新太频繁绿灯时长来回跳。原因是仿真步长1秒但控制周期设成了30秒LSTM预测的又是5分钟粒度时间尺度对不上。解决统一时间尺度。如果数据是5分钟粒度控制周期至少设成5分钟或者把预测结果插值到更细的粒度再用。我一般把控制周期设成90到120秒数据粒度也重采样到这个级别。5. 进阶技巧用滚动预测和相位约束把配时再压一压前面搭的是最小闭环能跑通但离好用还有距离。真正让LSTM加SUMO配时比固定方案明显好的是两个细节滚动预测和相位约束。滚动预测的意思是不要每90秒重新预测未来4步就完事而是每30秒用最新数据更新一次预测但只取预测结果的第一个时间片来调整当前相位剩余时长。这样模型能对突发流量更快响应。实现上就是把history_buffer的更新频率从90秒改成30秒预测函数每次只取pred[0]来算绿灯微调量。代价是计算量增加但LSTM推理一次只要几毫秒完全扛得住。相位约束是另一个容易被忽略的点。纯按流量比例分绿灯会出现某个方向绿灯很短、频繁切换的情况增加停车次数。我一般加三条硬约束最小绿灯15秒最大绿灯45秒相邻周期同一相位绿灯变化不超过10秒。第三条约束用代码实现就是记录上一周期的绿灯时长新的绿灯时长和它做差超过10秒就截断。class GreenAllocator: def __init__(self, min_green15, max_green45, max_delta10): self.min_green min_green self.max_green max_green self.max_delta max_delta self.last_green {ew: 30, ns: 30} def allocate(self, demand_ew, demand_ns): total demand_ew demand_ns if total 0: return self.last_green[ew], self.last_green[ns] green_ew self.min_green (self.max_green - self.min_green) * demand_ew / total green_ns self.min_green (self.max_green - self.min_green) * demand_ns / total # 变化率约束 green_ew np.clip(green_ew, self.last_green[ew] - self.max_delta, self.last_green[ew] self.max_delta) green_ns np.clip(green_ns, self.last_green[ns] - self.max_delta, self.last_green[ns] self.max_delta) # 再夹到全局范围 green_ew int(np.clip(green_ew, self.min_green, self.max_green)) green_ns int(np.clip(green_ns, self.min_green, self.max_green)) self.last_green[ew] green_ew self.last_green[ns] green_ns return green_ew, green_ns这段代码的关键是max_delta10它让绿灯时长平滑变化避免仿真里车辆因为相位突变而急刹。实测中加了这个约束后平均停车次数能降8%到12%代价是平均延误可能略升1%到2%看你怎么权衡。验证方法上我习惯跑三组对比固定配时、感应控制SUMO自带的actuated、LSTM预测配时。每组跑10个随机种子取95分位延误和平均停车次数的均值。如果LSTM组在95分位延误上比固定配时低15%以上就说明预测确实带来了收益如果只低5%以内那可能是流量太规律固定配时已经够用不值得上模型。最后说个血泪经验别一上来就调LSTM的层数和学习率先把SUMO的流量输入和信号相位对齐。我见过太多人模型训得漂漂亮亮结果SUMO里连接顺序写错绿灯放给了错误方向仿真直接堵死。先让固定配时在SUMO里跑出合理结果再换LSTM每一步都对比才能知道问题出在预测还是出在控制。希望帮到你。本文还有配套的精品资源点击获取
返回列表