ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于LSTM的交通流量时间序列预测实战:从数据预处理到模型评估

基于LSTM的交通流量时间序列预测实战:从数据预处理到模型评估 简介这份资源是一套城市交通道路流量预测的代码与数据分享面向机器学习初学者、交通数据分析研究者以及智能交通系统开发人员旨在解决交通流预测从数据准备到模型训练与评估的完整流程问题。资源包内共80个文件主要包括41个csv数据文件涵盖工作日、节假日、周末等不同时段的流量记录、13个h5模型权重文件如LSTM、GRU等训练好的模型、6个Python脚本实现数据预处理、特征工程、模型训练与预测以及若干可视化图片和工程配置文件整个压缩包仅13.81MB轻量易用。已有3360人学习下载。配套数据划分细致包含训练集、测试集及去零处理版本便于对比实验模型文件覆盖LSTM、GRU、SAEs等常用深度网络可直接加载测试。通过阅读和运行这些代码能快速掌握交通流量预测的数据清洗、特征构造、交叉验证和误差评估等关键环节为城市交通规划与拥堵治理提供可落地的算法参考。1. 项目整体设计与思路拆解1.1 这项目到底解决什么问题城市交通道路流量预测说白了就是告诉你“接下来一段时间这条路大概会堵成什么样”。别小看这个需求它直接关系到信号灯配时优化、出行路线规划、网约车调度甚至是物流配送路径选择。我当初上手这个方向就是因为手里拿到了一批真实路口的流量监测数据想试试能不能用深度学习模型从历史数据中找出规律预测未来几个时间步的路口车流量。先说结论这套代码数据完整跑下来你不仅能拿到一个可复用的流量预测Pipeline还能理解时序预测任务从数据清洗、特征构造到模型训练、评估的全流程。适合刚入门时序预测的算法工程师、交通专业的研究生以及想在自己的数据集上迁移复用这套代码的开发者。很多人第一次接触流量预测容易被“交通”两个字吓住以为必须懂交通工程理论。实际上把路口流量当成一条时间序列用LSTM、GRU这类循环神经网络去拟合历史规律预测未来一段时间的变化这个思路完全可行而且在很多公开数据集上效果相当能打。1.2 方案选型为什么用深度时序模型流量预测本质上是一个多步时间序列预测问题。传统方法里ARIMA、SARIMA这种统计模型用了很多年但它对数据平稳性要求高非线性捕捉能力弱。遇到早晚高峰的突变、节假日效应、恶劣天气造成的异常波动表现就拉胯了。LSTM这类循环神经网络的强项恰恰在这里。它通过门控机制记住长时间跨度的依赖关系比如早上8点的早高峰规律同时能捕捉短期的突变特征比如前方事故导致的流量骤降。我在设计这个项目时把模型基线定为单层LSTM但代码里也预留了GRU、双向LSTM的切换接口方便对比试验。还有一个关键选择是滑动窗口的方式构造监督学习样本。流量预测不是直接拿整条序列去训练而是用过去lookback个时间步的流量数据去预测未来horizon个时间步的流量。这一步是整套代码的核心预处理逻辑后面我会详细展开讲。2. 数据准备与预处理全流程2.1 数据集构成与字段说明这套代码附带了一份路口的交通流量监测数据格式是CSV每一行代表某个时间点的路口流量记录。核心字段包括时间戳、路口编号、车道方向、车流量、平均车速、占有率。这里重点用的是车流量字段作为预测目标其他字段作为辅助特征。在实际项目中你拿到的原始数据几乎不可能干干净净。我先做了三步预处理时间字段解析成标准datetime格式确保排序正确缺失的流量值用前向填充线性插值补齐对车速、占有率这类量纲差异大的特征做MinMax归一化避免模型训练时数值震荡。注意归一化操作必须在划分训练集和测试集之前完成并且要用训练集的最小值、最大值去变换测试集防止未来信息泄露。这一点踩过坑的人应该都懂但新手特别容易忽略。2.2 滑动窗口把时间序列变成监督学习样本流量预测的核心预处理是构造滑动窗口样本。我选用lookback12意思是利用过去12个时间步的数据预测未来1个时间步。为什么用12因为这份数据的采样粒度是10分钟一次12个时间步就是过去2小时刚好能覆盖一个比较完整的交通变化片段。import numpy as np import pandas as pd def create_sequences(data, lookback12): X, y [], [] for i in range(len(data) - lookback): X.append(data[i:i lookback]) y.append(data[i lookback]) return np.array(X), np.array(y)这段代码的逻辑很简单遍历整条时间序列每次取出长度为12的窗口作为输入特征窗口紧邻的下一个时间点作为标签。循环往复滑动就能构造出成千上万条训练样本。窗口大小不是拍脑袋定的它直接影响模型效果。lookback太小模型看不到足够的历史信息预测等于盲猜lookback太大引入太多噪声反而干扰关键模式提取同时训练数据量减少内存开销变大。如果你要复现这套代码我建议先试12再对比6和24看验证集表现再定。2.3 数据泄露这个坑必须躲开数据处理环节最容易犯的错误是全局归一化。很多人直接对整列数据做MinMaxScaler这等于把未来信息泄露到过去训练时指标贼好看一上真实场景立刻现原形。正确的做法是把数据按时间顺序切成三段前70%做训练集中间15%做验证集最后15%做测试集。然后只在训练集上fit归一化器再用训练集的统计量去transform验证集和测试集。代码里我是这样处理的from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler() train_scaled scaler.fit_transform(train_data) val_scaled scaler.transform(val_data) test_scaled scaler.transform(test_data)另外还有一个细节容易忽略打乱训练样本时不要混入时间顺序信息。虽然LSTM对单个样本内部的时序顺序敏感但样本之间的顺序无所谓所以可以在每个epoch前打乱训练集增强泛化性。验证集和测试集则一定要保持时间顺序否则评估指标没有意义。3. 核心模型搭建与训练调参实战3.1 模型结构与参数量设计模型我选了单层LSTM隐藏单元数设为64。这个体量对单特征序列预测来说够用不会轻易过拟合在CPU上也能较快跑完一个训练周期。如果你想加特征比如把车速、占有率都塞进输入那隐藏单元数建议上调到128否则模型容量不够没法充分拟合多特征之间的关系。import torch import torch.nn as nn class TrafficLSTM(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers1, output_size1): super(TrafficLSTM, self).__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): out, _ self.lstm(x) out out[:, -1, :] out self.fc(out) return out这里有个容易困惑的点为什么全连接层输入取out[:, -1, :]因为LSTM每个时间步都会输出一个隐状态我们要的是最后一个时间步的隐状态它已经集中了整段窗口的上下文信息再接全连接层映射到预测值。如果你用双向LSTM拼接两个方向最后一个时间步的隐状态可以考虑把hidden_size翻倍。3.2 训练循环损失函数与优化器选择损失函数用MSE因为流量预测本质是回归任务MSE对大误差的惩罚更大会让模型更在意早晚高峰这种流量数值大、偏差容易大的时段。优化器我用的Adam学习率1e-3。Adam的自适应学习率机制在实际训练中收敛很稳不用像SGD那样手动调学习率衰减。optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.MSELoss() for epoch in range(100): model.train() epoch_loss 0.0 for X_batch, y_batch in train_loader: optimizer.zero_grad() output model(X_batch) loss criterion(output, y_batch) loss.backward() optimizer.step() epoch_loss loss.item() if (epoch 1) % 20 0: print(fEpoch {epoch1}, Loss: {epoch_loss/len(train_loader):.6f})batch_size我设的是64这是时序预测里比较常见的取值。太小了梯度震荡厉害训练不稳定太大了内存占用高而且收敛速度不一定更快。如果你的显存紧张调到32也是安全的。3.3 评估除了MSE还要看这些指标很多人训练完只看一个Loss值这远远不够。我习惯额外算MAE和MAPE。MAE直观反映预测值的平均偏差绝对值单位是辆方便和业务沟通MAPE是无量纲的百分比误差能横向对比不同路口的模型效果。def evaluate_model(model, test_loader): model.eval() preds, trues [], [] with torch.no_grad(): for X_batch, y_batch in test_loader: output model(X_batch) preds.extend(output.numpy().flatten()) trues.extend(y_batch.numpy().flatten()) preds np.array(preds) trues np.array(trues) mse np.mean((preds - trues) ** 2) mae np.mean(np.abs(preds - trues)) mape np.mean(np.abs((preds - trues) / (trues 1e-8))) * 100 return mse, mae, mape测试集上的MAPE在15%以内说明模型已经能比较可靠地预测流量了。如果MAPE超过25%建议先回头检查数据预处理环节看看是不是归一化泄露、窗口切分错位或者没有剔除明显异常值。4. 常见问题与排查技巧实录4.1 模型训练Loss不下降新手最容易碰到的就是Loss卡在某个值附近不动或者下降非常缓慢。第一步检查学习率。如果学习率太大Loss会在一个区间内震荡太小则收敛极慢。排除了学习率问题再检查输入数据。我在第一次跑通代码时就发现归一化后训练集里混进了一个极大的异常值导致Loss居高不下。把异常值剔除后Loss曲线瞬间就正常了。4.2 训练集效果好测试集效果差这个是过拟合的典型症状。先看训练数据量是否太少LSTM这类模型对数据量的要求不低几千条样本算是门槛。再看模型复杂度单层LSTM如果也过拟合可以把hidden_size降到32或者在LSTM后面加一个Dropout层率设0.2左右。self.dropout nn.Dropout(0.2) out self.dropout(out[:, -1, :])4.3 预测曲线整体滞后于真实值这个问题我也遇到过而且它几乎是无解但有优化空间的。流量预测本质是用历史窗口预测未来模型学到的往往是一段平滑后的趋势面对突变点反应不过来所以预测曲线相对真实值会有滞后。缓解方式有几个把lookback缩短一些让模型更关注近期的变化或者把目标从预测流量值改成预测流量变化量再反推流量。第二种方式我在后续版本中试过对早晚高峰的突变捕捉确实有改善但整体误差和第一种方式差距不大建议优先调整lookback。4.4 代码数据迁移到自己的数据集如果你手上也有流量数据但采样粒度、路口数量、字段名都和这份数据不同迁移使用代码只需改三个地方CSV读取路径、时间戳列名、目标列名。核心的滑动窗口构造、模型训练、评估函数完全不用动。这里分享一个经验新数据集先画图看整体趋势确认没有严重的缺失和异常后再套代码不然跑出来的结果你会怀疑是代码的问题实际上是数据的问题。注意不同路口的流量模式差异巨大同一个模型权重不能直接从一个路口搬到另一个路口。你需要在目标路口的数据上重新训练或者至少用目标路口的少量数据做微调。5. 扩展思路这项目还能怎么玩跑通这份代码只是第一步。我自己在后续迭代中验证过几个方向的扩展效果都还不错。把单路口扩展成多路口。把多个路口的流量数据拼接起来每个样本加一个路口ID的Embedding向量模型就能学到不同路口之间的空间关联。这对预测拥堵扩散超级有用比如某个路口堵了相邻几个路口也会陆续变慢单路口模型完全没有这个能力。引入天气和节假日特征。交通流量受天气影响很大雨雪天流量普遍下降但拥堵加剧。把天气编码和节假日标记作为额外特征拼到输入向量里预测精度在恶劣天气条件下提升明显。这个扩展做起来不难数据也容易获取值得一试。换成Transformer架构。LSTM的串行计算在长序列上比较慢Transformer可以并行处理整段窗口效率更高。而且它对超长依赖的建模能力更强如果你把lookback从12提升到48甚至更长Transformer会更有优势。我自己实测过把单层LSTM换成小规模的TransformerEncoderMAPE降低1到2个百分点但训练时间也翻了快一倍。还有一些工程化的玩法比如把模型导出成ONNX格式嵌入到实时计算平台里滚动预测未来30分钟流量这个在车路协同项目里是标配能力。不过这需要额外做模型压缩和推理优化属于另一个话题了。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进