ARTICLE DETAIL

资讯详情

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

交通流预测实战:SAE特征提取与LSTM、GRU时序建模完整代码解析

交通流预测实战:SAE特征提取与LSTM、GRU时序建模完整代码解析 简介这套基于深度学习的交通流预测实现覆盖SAE、LSTM、GRU三种典型模型面向交通运输、数据挖掘等方向的研究生和工程师适用于交通流量短时预测研究、课程设计、毕业设计以及算法横向对比实验。压缩包共23个文件整体大小3.32MB包含Python训练与预测脚本py、训练后的模型权重h5、实验数据csv、评估与结果图表png以及README说明文档文件类型分布直观目录结构便于按模型模块定位。目前已有29人学习下载。资源在数据读取、模型搭建、训练评估和可视化环节均有代码支撑三种模型分别保留权重文件与loss曲线可直接运行复现方便对比SAE、LSTM、GRU在相同交通流数据上的预测效果附带说明文档和测试数据有助于理解数据组织方式与模型调用流程降低复现门槛适合作为深度学习交通流预测的起步参考。1. 交通流预测为什么值得做从SAE到LSTM、GRU的代码方案城市交通路口的流量数据每天都在堆量但大多数人只拿它画曲线看“今天堵不堵”真正做短时预测的少。深度学习这一块SAE负责从高维交通特征里压出有用的表示LSTM和GRU负责把时间依赖吃进去三个模型放在一起基本覆盖了从特征提取到时序建模的完整链路。我这套拆过的代码包里最值钱的不是某个模型本身而是把原始断面流量数据变成监督学习样本、再训练、再评估的完整流程。适合正在做智能交通课设、论文实验或者刚入门时序预测的开发者你不需要自己从零去拼数据处理管道照着跑就能看到预测曲线和误差指标。2. 数据侧把原始流量变成监督学习样本的完整做法2.1 先从字段和时间粒度说起交通流数据最常见的来源是线圈检测器和卡口过车记录落库之后一般长这样一个路口编号、一个时间戳、一个方向编号再加两个核心数值——流量单位时间通过车辆数和占有率检测器被车辆占用的时间比例。有些数据集里还有平均车速但那不是标配。拿到手先别急着上模型第一件事是确认时间粒度常见的是5分钟聚合、15分钟聚合和1小时聚合。5分钟粒度最贴近短时预测场景但噪声也大经常出现连续几个窗口流量为0等红灯排队的车一起放行后又突然冲到顶。15分钟粒度平滑很多训练出来的曲线也好看。我做实验默认用5分钟粒度因为LSTM这类模型在细粒度数据上才能体现出学习时序模式的价值如果你换成1小时粒度GRU和LSTM的差距会变得非常小甚至不如简单的ARIMA。代码包里第一步是统一时间索引把原始过车记录按时间窗口聚合成标准表格。常见的做法是拿pandas的resample去重采样但这里有个坑原始数据的时间戳可能是字符串也可能是带时区的必须先转成datetime再设置为索引。import pandas as pd import numpy as np def aggregate_traffic(raw_df, freq5min): # raw_df 必须包含 time, lane_id, volume 三列 # lane_id车道或方向编号volume该时间戳内过车数 df raw_df.copy() df[time] pd.to_datetime(df[time]) df.set_index(time, inplaceTrue) # 按5分钟窗口求和缺失窗口先用NaN占位再插值 agg df.groupby([lane_id]).resample(freq)[volume].sum().unstack(lane_id) agg agg.resample(freq).asfreq() return agg这段代码先按车道分组再用resample把时间切到5分钟网格groupby之后unstack是把每个车道变成一列方便后续做多变量输入。asfreq的作用是补出那些没有任何过车记录的空白窗口这些窗口在原始数据里根本不存在但模型训练时缺一个时刻序列就对不齐。你要是直接跳过这一步后面构造滑动窗口时会出现标签时间错位预测结果看着收敛实际上学的全是错位信息。2.2 缺失值和异常值的处理原则交通流数据的缺失不是随机缺失很多是设备故障导致的持续掉线可能连续几个小时都是NaN。对这种连续缺失线性插值会插出一段平滑到不真实的曲线模型很容易把这段假数据当规律学进去。我一般按缺失长度分两种策略连续缺失不超过3个窗口用线性插值超过3个窗口直接用前后同时间段的历史均值填充比如周一早上8点到9点的数据丢了就取过去四周周一同一时段的均值。异常值更要小心。流量不可能为负也不可能在5分钟内突然从10跳到500。这里不能单纯按标准差过滤因为交通流的波动本身就是重尾的峰值时段的标准差天然大。代码包里实现的是分位数截断法。def clean_traffic(series, lower_quantile0.001, upper_quantile0.999): # 超过99.9%分位数的一律视为设备误报 low series.quantile(lower_quantile) high series.quantile(upper_quantile) cleaned series.clip(lowerlow, upperhigh) # 连续0值超过12个窗口1小时认为是设备离线做标记 zero_streak (cleaned 0).astype(int) streak zero_streak.groupby((zero_streak ! zero_streak.shift()).cumsum()).cumsum() cleaned[streak 12] np.nan return cleaned.interpolate(methodlinear)clip不是万能的上限设太大会把真实峰值削掉设太小又滤不掉误报。0值连续超过12个窗口这一条是经验值5分钟粒度下等于持续1小时一个车都没有这在城市主干路基本不可能宁可当成缺失去插值也不能拿一堆0去骗模型。真实项目中我还会加一道基于上下游断面一致性的校验比如A断面和B断面相邻同一时段流量差超过某阈值就两边都标记为可疑但这属于进阶玩法基础代码包里没放。2.3 窗口构造时间步长和标签错位监督学习样本的构造是整个项目最容易被忽视、但最决定上限的一步。交通流预测常用的是滑动窗口方式用过去window_size个时刻的流量预测未来horizon个时刻的流量。窗口大小不是拍脑袋它和数据的自相关强相关。5分钟粒度的数据一般取6到12个窗口也就是看过去30分钟到60分钟预测未来15到30分钟。窗口太小模型看不到完整的拥堵形成过程窗口太大输入维度膨胀LSTM的训练时间成倍增加而且序列过长时梯度回传路径变长GRU的优势也会被稀释。代码里我留了create_sequences函数一次性把多车道的流量矩阵切成样本。def create_sequences(data, window_size12, horizon3, step1): X, y [], [] for i in range(0, len(data) - window_size - horizon 1, step): X.append(data[i : i window_size]) y.append(data[i window_size : i window_size horizon]) return np.array(X), np.array(y) # 假设 data 形状为 (样本数, 车道数) # X 输出形状: (样本数, window_size, 车道数) # y 输出形状: (样本数, horizon, 车道数)这里有一个新手极其容易犯的错预测目标是未来horizon个时刻的流量但很多人画样本时把i时刻到iwindow_size时刻作为输入标签却用了iwindow_size时刻的当前值。这等于让模型用已知信息预测已知信息训练误差低得离谱一旦部署就崩。我每次构造完样本都要打印一次X和y的最后几个时刻做人工核对确认标签确实在输入时间窗口的后面这个习惯帮我挡掉过好几次数据泄漏事故。step参数控制滑动步长默认1表示样本高度重叠训练数据量很大但很多样本几乎一样容易被模型背下来。如果数据量充足我建议设成horizon让样本之间不重叠泛化能力明显更好。数据量少的时候再退回step1用重叠样本做数据增强。2.4 归一化要在切分之前还是之后这个问题我被人问过很多次答案是必须在切分之前。先把全部数据做MinMax归一化再切训练集和测试集这样测试集的分布和训练集保持一致。如果先切再归一化测试集的归一化参数只反映测试集自己的范围模型在训练时见过的输入分布和测试时完全对不上预测曲线会整体偏移。from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) scaled_data scaler.fit_transform(data) # 按时间顺序切分不能随机打乱 train_ratio 0.8 train_size int(len(scaled_data) * train_ratio) train_data scaled_data[:train_size] test_data scaled_data[train_size:]时间序列数据切分不能用train_test_split的随机模式原因很简单模型必须见过“昨天之前”的数据才能预测“今天”的数据随机打乱等于时间穿越。代码包里已经固定用前80%做训练、后20%做测试如果你数据里有明显的早晚高峰区别建议按整周切分比如取前六周训练、最后一周测试避免训练集里全是工作日而测试集全是周末。3. 模型侧SAE特征提取与LSTM、GRU的PyTorch实现3.1 为什么中间夹一个SAE如果你直接用LSTM接原始多车道流量矩阵也能收敛但训练会比较吃力。原因在于交通流数据是高相关的相邻车道的流量走势几乎一致占有率也和流量强相关这些冗余特征会让LSTM的输入门和遗忘门花很多额外参数去学习“哪些特征该忽略”。SAE栈式自编码器的价值就是先把原始输入压缩成低维稠密表示把冗余去掉之后再喂给时序模型。这个思想放在深度学习里叫特征预训练放在传统交通工程里其实就是主成分分析的升级版。SAE的隐层可以设成两层第一层把特征从原始维度压到一半第二层再压到四分之一。代码包里的实现是逐层预训练而不是直接堆一个多层自编码器一次性训练因为一次性训练深层自编码器容易陷入局部最优。import torch import torch.nn as nn import torch.optim as optim class SAE(nn.Module): def __init__(self, in_dim, hidden_dims): super(SAE, self).__init__() layers [] prev in_dim for h in hidden_dims: layers.append(nn.Linear(prev, h)) layers.append(nn.ReLU()) prev h self.encoder nn.Sequential(*layers) # 解码器是编码器的对称结构输出还原到 in_dim decode_layers [] dims [in_dim] hidden_dims for i in range(len(dims) - 1, 0, -1): decode_layers.append(nn.Linear(dims[i], dims[i - 1])) if i 1: decode_layers.append(nn.ReLU()) self.decoder nn.Sequential(*decode_layers) def forward(self, x): encoded self.encoder(x) decoded self.decoder(encoded) return decoded, encodedself.encoder输出作为后续LSTM的输入self.decoder只在预训练阶段参与损失计算预训练结束后解码器整个扔掉。之前有读者把解码器也留在训练流程里导致模型同时优化两个目标效果反而不如不用SAE。预训练时每个样本是单个时刻的多车道流量向量不需要窗口信息所以SAE不感知时间顺序。训练轮数不用多20到30个epoch就够早期轮数就能看到重构误差快速下降超过50轮容易把噪声也编码进来特征反而变差。3.2 LSTM的参数配置和PyTorch实现LSTM在这个项目里是主力预测模型。标准LSTM单元内部有输入门、遗忘门和输出门对交通流这种有明显周期性的序列非常合适。但它的参数量比GRU多三分之一训练更慢对小数据集容易过拟合。代码包里的LSTMPredictor接收SAE输出的低维特征然后过一个单层LSTM最后接全连接层输出未来horizon个时刻的流量。我不推荐用两层LSTM因为交通流预测单序列的复杂度没到需要堆叠才能表达的程度一层LSTM配合足够大的隐层维度在大多数路口数据上效果已经够好。class LSTMPredictor(nn.Module): def __init__(self, input_size, hidden_size64, num_layers1, horizon3): super(LSTMPredictor, self).__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue ) self.fc nn.Linear(hidden_size, horizon) def forward(self, x): # x 形状: (batch, window_size, input_size) out, (hidden, cell) self.lstm(x) # 取最后一个时间步的隐藏状态再映射到 horizon 个输出 last_hidden hidden[-1] return self.fc(last_hidden)hidden_size取64是我在多数路口数据上试出来的甜点区间。取32模型拟合不足早高峰的峰值会被系统性低估取128训练时间翻倍但误差下降不足5%。num_layers不加深的原因是LSTM每一层都自带遗忘机制层数加深后训练稳定性快速下降需要同步调高学习率调度器的耐心对新手很不友好。batch_firstTrue这个参数一定记得加。PyTorch默认输入维度是(seq_len, batch, feature)但绝大多数人习惯把batch放第一位不加这个参数后期处理输出维度时特别容易弄混。3.3 GRU参数更少速度更快收敛更稳GRU是LSTM的简化版把遗忘门和输入门合并成更新门还引入了一个重置门。从交通流预测的实际效果看GRU在多数场景下预测精度和LSTM相当但训练速度快20%到30%对小数据集更稳。如果样本量不到几千条我建议直接用GRULSTM容易过拟合却不会带来精度优势。实现上一个很巧的点是只要把LSTMPredictor里的nn.LSTM换成nn.GRU输出结构多了一个维度差异。LSTM的forward返回(output, (hidden, cell))GRU返回(output, hidden)取隐藏状态时要注意。代码包里单独写了GRUPredictor避免在训练循环里做条件分支。class GRUPredictor(nn.Module): def __init__(self, input_size, hidden_size64, num_layers1, horizon3): super(GRUPredictor, self).__init__() self.gru nn.GRU( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue ) self.fc nn.Linear(hidden_size, horizon) def forward(self, x): out, hidden self.gru(x) last_hidden hidden[-1] return self.fc(last_hidden)GRU的隐藏状态初始化默认是全零张量用在全零填充的输入上会有问题交通流夜间低峰期可能出现连续几个窗口全是0GRU会把全零输入和全零隐藏状态搞混输出接近零向量然后平缓期预测就一直偏低。这种情况LSTM因为有独立的遗忘门和候选记忆反而能自动调整偏置。解决这个问题的常用做法是给输入序列加一个时间位置编码或者归一化时把0值映射成小正数我习惯选后者改动最小。3.4 SAE和时序模型怎么拼接训练流程分两阶段第一阶段单独训练SAE第二阶段冻结encoder参数把归一化后的原始数据过一遍encoder得到压缩特征再训练LSTM或GRU。第二种做法是把SAE的encoder接到LSTM前面端到端微调。代码包里给了两种模式默认是第一种因为它快、稳定、且结果可复现。端到端微调理论上能让特征表示更适配预测任务但实际操作中会遇到一个问题LSTM训练时的梯度要回传到SAE的每一层深层梯度消失导致encoder参数几乎不动却白白增加了计算量。我在代码里加了freeze_sae开关你想实验端到端模式就把开关设成False但建议只在数据量超过5万条样本时尝试。def build_model(model_type, input_size, hidden_size, horizon): if model_type sae_lstm: sae SAE(in_diminput_size, hidden_dims[32, 16]) predictor LSTMPredictor(input_size16, hidden_sizehidden_size, horizonhorizon) return sae, predictor elif model_type sae_gru: sae SAE(in_diminput_size, hidden_dims[32, 16]) predictor GRUPredictor(input_size16, hidden_sizehidden_size, horizonhorizon) return sae, predictorhidden_dims[32, 16]意味着输入假如是8条车道第一层压到32又升维不对这里in_dim是原始特征维度hidden_dims[32,16]表示中间层维度分别为32和16。如果in_dim小于32升维再降维的意义不大。代码包里默认in_dim是车道数通常4到8条所以hidden_dims我改成[16, 8]更合理。拆项目时你要根据实际车道数量调整这个参数不是越大越好。4. 训练与评估指标选型、超参数搜索和三种模型的横向对比4.1 评估指标MSE、MAE和MAPE各管什么训练损失用的是MSE因为它对大误差的惩罚强模型会优先去拟合晚高峰的尖峰。但MSE的数值受流量绝对大小影响不同路口之间没法横向比较。评估阶段我建议同时输出MAE和MAPEMAPE是百分比误差最能反映模型在业务上的表现但要注意流量接近0时MAPE会暴躁地放大。交通流预测里常见的一个指标陷阱MAPE在夜间低峰期没有意义因为真实值很小预测值稍偏一点百分比就飙到几百。所以代码包里对MAPE做了保护只计算真实流量大于某阈值的样本比如只算流量大于50辆/小时的时刻。def evaluate_model(y_true, y_pred, min_volume50): y_true np.asarray(y_true) y_pred np.asarray(y_pred) mse np.mean((y_true - y_pred) ** 2) mae np.mean(np.abs(y_true - y_pred)) mask y_true min_volume if mask.sum() 0: mape np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100 else: mape float(inf) return mse, mae, mape这个min_volume参数在代码包里明确留出来了。做课设时很多人复制了一份评估函数却发现评估结果不理想原因就是把夜间零点到凌晨五点的样本全算进去了。时间维度的另一个细节预测未来三个窗口15分钟时通常只需要评估第三个窗口的误差前两个窗口误差天然更小它们只能作为参考不能拿第一个窗口的误差去验证15分钟预测能力。4.2 超参数怎么定时间步、学习率和早停我拆这个代码包时最深的感受是模型结构对结果的影响远小于数据预处理和训练参数。时间步window_size对结果的影响最直接我在一个两车道路口样本上做过简单网格搜索window_size从6提到12MSE下降约12%从12提到24MSE反而回升。原因不是模型学不到更长依赖而是5分钟粒度的交通流自相关在60分钟之后衰减到很低喂太多无关信息反而干扰了关键近邻信息。学习率用Adam优化器时初始值1e-3最稳妥loss震荡不下降时降到1e-4。代码里还加了ReduceLROnPlateau它在验证集指标连续5个epoch不降时把学习率除以5。optimizer optim.Adam(model.parameters(), lr1e-3) scheduler optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5, verboseFalse ) for epoch in range(max_epochs): model.train() for x_batch, y_batch in train_loader: optimizer.zero_grad() pred model(x_batch) loss nn.MSELoss()(pred, y_batch) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0) optimizer.step() val_loss validate(model, val_loader) scheduler.step(val_loss) if early_stopping.should_stop(val_loss): breakclip_grad_norm_是这里最重要的防翻车手段很多读者直接套用图像分类的训练代码没有做梯度裁剪。交通流序列数据里偶尔会出现一个明显偏差的样本它的梯度可能比正常样本大几个数量级一次更新就把模型参数推出正常区域之后loss变成NaN。max_norm5.0是我常用的值你可以理解为“每个参数的梯度向量长度上限”和batch size没有直接关系。训练轮数不要硬定死。代码包里的EarlyStopping耐心是15个epoch也就是说验证集连续15轮不下降才停给模型足够时间去跳出局部极小。我先跑30个epoch看基础收敛情况再决定是否加大模型容量。模型容量加大后必须同步加大耐心否则模型还没收敛就早停了。4.3 batch size和denormalization的配合batch size在时序预测里的作用和图像任务不同它影响的是每个batch里时间窗口的覆盖范围。batch size取32时一个batch包含32个不同起始时刻的窗口分布在一天的不同时段batch size取256时极端情况下一个batch可能全部来自同一个时段比如全是夜间数据参数更新方向就偏向夜间模式。这就是为什么交通流预测里batch size不要盲目往大调。代码包里默认32跑大数据集时可以调到64。如果显存紧张不要先砍batch size优先砍window_size因为砍batch size导致训练波动太大要用更多epoch才能弥补。预测结果反归一化是最容易出现细微错误的一环。很多人在训练前用MinMaxScaler做了归一化预测后忘了用同一个scaler.inverse_transform把结果变回真实流量数值。这里有一个隐藏问题scaler是在完整训练集上fit的测试集预测结果反归一化时用的仍是训练集的缩放参数这是正确的。如果你在测试集上重新fit了一个scaler再去反归一化得到的数值整体偏差而且偏差量随测试集分布漂移变化。pred_scaled model(x_test_tensor).detach().cpu().numpy() pred_real scaler.inverse_transform(pred_scaled.reshape(-1, num_lanes)).reshape(-1, horizon, num_lanes) y_test_real scaler.inverse_transform(y_test.reshape(-1, num_lanes)).reshape(-1, horizon, num_lanes)reshape的维度顺序在代码包里特别容易错模型的输出形状是(batch, horizon, num_lanes)inverse_transform要求二维(样本数, 特征数)必须先合并batch和horizon两个维度反归一化后再拆回来。顺序搞反了会得到一组形状正确但数值错位的矩阵plot出来的曲线看着像模像样其实每个时间点的数据都对不上。4.4 三种模型的横向对比别只盯测试集均值用同一份预处理数据分别训练SAELSTM和SAEGRU后代码包会输出一个对比表。典型的结论是LSTM在流量突变时段表现略好GRU在平稳时段误差更低总体差异不超过8%。这个差异程度在交通流预测领域是常态不要指望GRU一定不如LSTM。真正显著的差异发生在训练时间上GRU在CPU上训练一个epoch大约比LSTM快接近30%在路口数量多、模型反复调参的场景里这个时间优势很实用。做横向对比时有个评估陷阱只看整体平均误差。交通工程业务更关心的是早高峰和晚高峰两个时段的表现。我通常把一天分成四个时段——早高峰(7-9点)、晚高峰(17-19点)、平峰(10-16点)、夜间(0-6点)分别计算MAE。这样能看出模型是不是只在夜间数据上表现好白天高峰时段预测差得离谱。代码包里没有直接做这个分段评估但我建议你自己加上一个pd.cut或者np.where就能实现。对比结果还受到随机种子影响。PyTorch默认初始化权重的随机性很大同一个模型两次训练之间MSE可能漂移5%以上。如果报告里只写了某一次实验的数值说服力不足我通常在对比前固定三个种子各跑三遍取中位数。代码包里在训练脚本开头就设了随机种子但只固定了PyTorchPython内置random和NumPy的种子也需要固定三者不一致依然无法完全复现。import random import numpy as np import torch random.seed(42) np.random.seed(42) torch.manual_seed(42) torch.cuda.manual_seed_all(42)如果你在GPU上训练还必须在训练前设置torch.backends.cudnn.deterministic True和torch.backends.cudnn.benchmark False否则cuDNN的算法选择具有随机性同一份代码重复跑两次loss曲线也可能不同。代价是训练速度变慢一些但换来可复现性写论文时这笔交易非常划算。5. 避坑指南交通流时序预测中常见的四个翻车现场5.1 训练集和测试集之间时间泄漏现象验证集误差低到不可思议MAPE不到2%但把模型部署到新数据上预测误差立刻翻几倍。原因构造滑动窗口时没有按时间顺序切分或者切分前做了随机打乱。模型在训练时已经见过测试集的“未来”数据预测变成了记忆。另一个常见原因是测试集本身包含训练集时间范围内窗口的一部分尤其当窗口大小大于切分边界时最后一个训练样本的窗口延伸到了测试集区间。解决先构造全部样本再按索引切分。永远不要让任何一个样本的输入时间窗口和标签时间窗口跨越训练/测试边界。做法是以时间点为基准做切分而不是以样本索引为基准比如train_size int(len(scaled_data) * 0.8)之后确保窗口构造的循环只在range(0, train_size - window_size - horizon 1)里运行。5.2 归一化参数和逆变换的错位现象训练曲线很漂亮但反归一化出来的流量预测值整体偏低或者高峰期被明显压缩。原因训练集fit了scaler测试时又用测试集的min/max反归一化两套缩放基准不一致。或者是反归一化前reshape维度错位把时间维度和特征维度搞混。解决训练前只对训练集scaler.fit()然后用同一个scaler对训练集和测试集做transform。预测完成后也必须用同一个不是重新fit的scaler做inverse_transform。在代码里把scaler保存为joblib.dump(scaler, scaler.bin)部署阶段重新加载不要重新计算。5.3 LSTM训练到一半loss变成NaN现象训练前几个epoch正常某轮loss突然变成NaN然后永远回不来。原因最常见是梯度爆炸。交通流数据里突发拥堵时的流量值会猛然升高这个样本产生的梯度极大把一个batch的参数更新推爆。其次可能是输入数据里混入无穷值归一化前原始数据中存在极大异常值没有被过滤干净。解决梯度裁剪是第一道防线clip_grad_norm_(model.parameters(), max_norm5.0)写在每次backward之后这是我排查时第一个加的保护。第二道防线是把学习率从1e-3降到1e-4。如果还是NaN检查输入数据里有没有inf或NaN残留np.isnan(data).sum()在构造数据集后必须打印一次确认。如果数据没问题验证模型最后一层输出是否有指数运算有的话考虑换成线性输出。5.4 同样的代码换一批数据效果差很多现象在示例数据上测试集的MAPE稳定在12%左右换到自己的路口数据后MAPE变成28%而且怎么调参都降不下来。原因数据分布特性不同。示例数据可能是高速路段流量波动平稳你的数据是城市交叉口受信号灯周期和排队影响5分钟流量序列的突变程度完全不同。另一个潜在问题是传感器质量部分路口的检测器在高峰期丢包严重数据本身就不可靠。解决先做数据诊断打印出连续7天的流量曲线肉眼确认有没有周期性、有没有大量的块状缺失、晚高峰峰值是否稳定出现。如果原始数据质量不理想把聚合粒度从5分钟改成15分钟通常能大幅消除噪声。高峰期预测偏低是普遍现象不要强求模型把尖峰完美拟合交通流预测在突变时刻的精度上限是由数据本身决定的模型只能尽可能接近这个上限。提示遇到效果变差时先把预处理代码单独跑一遍输出各时段统计量再谈调模型。超过一半的情况问题出在数据管道不是模型结构。5.5 早停一轮就把权重恢复到最佳状态现象训练结束后保存的模型不是早停时权重而是最后一个epoch的权重导致验证集指标和最终测试指标不一致。原因我最初踩过这个坑early_stopping.should_stop(val_loss)返回True时确实触发了停止但模型参数停留在最后一个epoch的状态上而这个epoch的验证loss可能已经反弹了许多。解决早停逻辑里维护一个best_val_loss每次验证后如果当前loss更低就深拷贝一份model.state_dict()。训练结束后先加载最佳权重再跑测试集。代码包里特意把load_best_weights接口单独暴露出来复现实验前先确认这一点否则你对比的LSTM和GRU可能都不是各自的最佳状态。6. 扩展用法把模型部署到更大路网并用MATLAB做交叉验证6.1 从单路口到多路口的迁移玩法这套代码默认处理单路口多车道数据但路网级交通流预测并不是把每个路口单独训练一个模型那么简单。我试过的可行做法是先做路网拓扑编码把相邻路口的流量作为额外特征拼进输入然后用一个全局SAE压缩所有路口的特征再用GRU做时序预测。这样单个模型就能服务一片区域而不是每个路口各养一套模型。代码包里没有实现完整的图神经网络但特征拼接的接口已经留好create_sequences接收的data矩阵可以自由扩展列数。我在把5个连续路口的数据拼到一起做实验时模型输入维度从原来的单向4车道变成20维SAE的hidden_dims需要改成[40, 16]其余训练逻辑完全不用动。这种做法在数据量足够时比单路口模型更稳因为它让模型学到了上下游之间的传播关系拥堵从一个路口蔓延到下一个路口的规律被显式捕捉。多路口实验时要注意标签对齐不同路口的峰值时间可能错开。下游路口的早高峰峰值通常比上游晚10到15分钟如果直接用同一时刻的流量当预测目标模型会混乱。我把特征矩阵里每一列都做了独立归一化不共享scaler参数部分缓解了这个错位问题。6.2 导出预测结果和MATLAB交叉验证深度学习的训练和预测在Python里完成没问题但很多交通工程项目的数据清洗、基础统计和最终报告图表用MATLAB做。这套代码包里在预测结束后会把结果导出为CSV方便两边共享数据格式。import pandas as pd result_df pd.DataFrame({ time: test_timestamps, y_true: y_test_real[:, -1].flatten(), y_pred_lstm: lstm_pred[:, -1].flatten(), y_pred_gru: gru_pred[:, -1].flatten(), }) result_df.to_csv(prediction_results.csv, indexFalse, float_format%.2f)在MATLAB里加载这个CSV后我一般写一个简单脚本做两件事一是画时间序列对比图把y_true和两个模型的预测曲线叠在一起按工作日和周末分面二是计算滑动平均误差曲线看误差是不是集中在特定时段。MATLAB在画图定制上比Matplotlib方便论文出图我用它比较多。交叉验证的意义不是证明Python模型的正确性而是确认预测曲线和工程上常用的基线方法差异在可接受范围内。我习惯在MATLAB里跑一个简单的历史均值模型作为下界如果LSTM预测误差比历史均值还差那一定是数据或预处理环节出了问题而不是模型不够强。6.3 多步预测的两种输出策略代码包默认一次输出未来三个时刻的预测这是多步预测里的“直接多步”策略实现简单但三个预测窗口共享同一个隐藏状态误差会随时间步累积。第二种策略是“递归多步”把第一步预测结果当作输入依次预测下一步在代码里改动很小但误差累积更严重我不推荐用于交通流。更稳妥的做法是训练三个独立模型分别预测未来5分钟、10分钟、15分钟。代价是训练时间变三倍但每个模型只专注一个目标精度普遍比单模型多输出更好。我在正式实验里都是这么做的把horizon3的任务拆成三个horizon1的模型。代码包为了演示方便保留了多输出模式你换数据时建议比较一下这两种做法的差异。6.4 部署时最容易忽视的时间戳同步把模型接到实时数据流时最后一个隐患是时间戳对齐。离线训练时数据是完整的历史序列实时部署时你需要维护一个长度为window_size的滑窗每来一个新数据踢掉最旧的数据再调用模型预测。这个滑窗里的时间戳必须是严格递增的任何一个时刻数据延迟到达或重复推送预测结果就会错位。我见过一个生产事故数据采集程序把同一个5分钟窗口的数据推送了两次滑窗里出现两个相同的时刻模型预测出的流量值比真实值高了将近一倍。解决办法是在数据入口加一个去重逻辑按时间戳精确去重重复数据直接丢弃。这段逻辑代码量不大但价值极高我每次部署前都会把这个脚本单独测试一遍确保实时数据流的每个时刻只被消费一次。从那以后我每次拿到新的交通流数据都会强制走一遍完整流程先检查时间戳连续性再做缺失值填充构造窗口时打印首尾样本核对标签最后才训练模型。这套习惯帮我挡掉了非常多莫名其妙的预测问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表