
简介这份资源面向交通工程、智能交通与数据分析方向的学习者与研究人员聚焦短时交通流量预测这一典型场景提供一份可直接运行的MATLAB实现脚本帮助读者理解从历史流量数据到未来时段流量估算的完整建模思路。压缩包内共1个文件为m格式的MATLAB脚本整体体积约1KB轻量便于快速查阅与二次修改。目前已有340人学习关注说明其在短时交通预测入门与代码复现方面具有一定参考价值。脚本内容预计覆盖数据预处理、特征工程、模型构建、训练优化、预测评估与结果可视化等关键环节读者可据此掌握ARIMA、灰色模型、支持向量机或神经网络等方法的落地流程并借鉴误差指标评估与对比图绘制方式为交通调度、拥堵预警等实际应用提供建模参考。1. 短时交通流量预测从 yc.rar 这个压缩包名说起拿到yc.rar_交通_交通流量_交通流量预测_流量预测_短时交通预测这个标题时我第一反应不是去猜压缩包里有什么而是先判断它属于哪一类工程问题。短时交通流量预测通常指用过去 5 到 30 分钟粒度的流量数据预测未来 5 到 30 分钟的路段或路口流量。它和宏观交通规划不同不追求年度趋势而是服务于信号配时、智能导航、公交到站预估这些分钟级决策。如果你手上有卡口过车数据、线圈数据或浮动车轨迹想把它变成可用的预测模型这篇笔记就是按这个目标写的。我会按「数据长什么样 → 特征怎么构造 → 模型怎么选 → 怎么跑通 → 坑在哪」的顺序把一套可复现的方案讲清楚。适合有 Python 基础、做过一点时序任务、但还没把短时交通预测真正落地的工程师。2. 短时交通流量预测的数据底座从 yc.rar 里该期待什么2.1 交通流量数据的三种常见形态与字段含义短时交通预测的第一步不是选模型而是确认数据形态。常见的数据来源有三类线圈检测器、卡口过车记录、浮动车 GPS。线圈数据通常是固定时间粒度的流量和占有率字段少但稳定卡口数据是过车事件需要先聚合成时间窗浮动车数据是轨迹点需要先匹配到路段。yc.rar这类压缩包大概率装的是某城市某区域的历史流量表可能是 CSV 或 Excel字段一般包括时间戳、路段编号、流量值有的还会带车道号、车型、平均速度。我一般拿到数据先做三件事看时间粒度、看缺失比例、看路段数量。时间粒度决定预测步长5 分钟粒度适合信号控制15 分钟粒度适合路径诱导。缺失比例超过 10% 就要考虑插补策略不能直接 drop。路段数量决定你是做单路段模型还是多路段联合模型。如果压缩包里只有一张宽表每列是一个路段每行是一个时间点那它更适合做多变量时序预测如果是长表每行一条记录那要先 pivot 成时间 × 路段的矩阵。提示不要急着把数据喂给 LSTM。先画一张单路段 24 小时流量曲线看看早高峰、晚高峰、平峰的形状是否合理。如果曲线像随机噪声大概率是路段编号对错了或者时间戳时区有问题。2.2 把原始流量表转成监督学习样本的最小脚本假设yc.rar解压后得到一个flow.csv字段为timestamp, road_id, flow时间粒度 5 分钟。下面这段代码把它转成监督学习样本用过去 12 个时间步1 小时预测未来 1 个时间步5 分钟。这个窗口长度不是拍脑袋后面 2.3 会讲怎么定。import pandas as pd import numpy as np # 读取原始长表 df pd.read_csv(flow.csv, parse_dates[timestamp]) df df.sort_values([road_id, timestamp]) # 按路段分组构造滑动窗口 WINDOW_IN 12 # 输入窗口过去 12 个 5 分钟 1 小时 WINDOW_OUT 1 # 输出窗口未来 1 个 5 分钟 records [] for road_id, group in df.groupby(road_id): values group[flow].values times group[timestamp].values for i in range(len(values) - WINDOW_IN - WINDOW_OUT 1): x values[i:iWINDOW_IN] y values[iWINDOW_IN:iWINDOW_INWINDOW_OUT] records.append({ road_id: road_id, t_start: times[i], x: x.tolist(), y: y.tolist() }) samples pd.DataFrame(records) print(samples.shape) print(samples.head(2))这段代码的逻辑是对每个路段独立滑动保证窗口不跨路段。WINDOW_IN和WINDOW_OUT是核心参数前者决定模型能看到多长的历史后者决定预测步长。如果要做多步预测把WINDOW_OUT改成 3 或 6但要注意误差会累积。t_start保留下来是为了后面按时间划分训练集和测试集不能随机打乱否则会泄漏未来信息。2.3 窗口长度、粒度与预测步长的参数取舍窗口长度不是越长越好。短时交通流量有强日周期和周周期但 5 分钟粒度下过去 1 到 2 小时已经覆盖了当前拥堵的形成过程。窗口太长会引入噪声也会增加模型输入维度。我一般先用 12 个点1 小时做基线再试 6、18、24看验证集 MAE 是否下降。预测步长方面1 步预测5 分钟最容易做准3 步以上就要考虑用 seq2seq 或直接多输出回归。另一个关键参数是时间粒度。如果原始数据是 1 分钟可以聚合成 5 分钟降低噪声如果原始数据是 15 分钟就不要强行插值成 5 分钟那会制造虚假精度。粒度选择要和业务决策周期对齐信号配时通常 5 分钟更新一次导航诱导可能 1 分钟就够。yc.rar里的数据如果已经是 5 分钟或 15 分钟直接沿用不要重采样。3. 短时交通流量预测的模型选型从历史均值到时空图网络3.1 基线模型为什么必须先跑历史均值很多工程师一上来就上 LSTM 或 Transformer结果发现连历史均值都打不过。短时交通流量有很强的周期性同一路段周一早上 8 点的流量和上周一早上 8 点高度相关。所以第一个基线应该是「上周同一时刻流量」或「过去 4 周同一时刻均值」。这个基线不需要训练代码三行就能跑但它是你后面所有复杂模型的参照系。# 历史均值基线用过去 4 周同一星期几、同一时间片的均值 df[weekday] df[timestamp].dt.weekday df[time_slot] df[timestamp].dt.hour * 12 df[timestamp].dt.minute // 5 baseline df.groupby([road_id, weekday, time_slot])[flow].mean().reset_index() baseline.rename(columns{flow: pred}, inplaceTrue) # 合并回测试集 test df.merge(baseline, on[road_id, weekday, time_slot], howleft) mae np.mean(np.abs(test[flow] - test[pred])) print(Baseline MAE:, mae)这段代码的关键是weekday和time_slot两个分组键。它假设流量模式按周重复且每个 5 分钟片独立。如果 MAE 已经很低说明你的数据周期性极强复杂模型提升空间有限如果 MAE 很高再考虑引入空间关联和实时波动。3.2 LSTM 与 GRU 在单路段预测上的最小实现单路段预测是最简单的场景只用一个路段的历史流量预测它自己的未来。LSTM 和 GRU 是常见选择GRU 参数少、训练快适合数据量不大的情况。下面用 PyTorch 写一个最小可跑的 GRU 回归模型。import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset # 假设 X_train shape: (n_samples, 12, 1), y_train shape: (n_samples, 1) X_train_t torch.tensor(X_train, dtypetorch.float32) y_train_t torch.tensor(y_train, dtypetorch.float32) loader DataLoader(TensorDataset(X_train_t, y_train_t), batch_size64, shuffleTrue) class GRURegressor(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2): super().__init__() self.gru nn.GRU(input_size, hidden_size, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_size, 1) def forward(self, x): out, _ self.gru(x) # out: (batch, seq, hidden) out out[:, -1, :] # 取最后一个时间步 return self.fc(out) model GRURegressor() optimizer torch.optim.Adam(model.parameters(), lr1e-3) loss_fn nn.MSELoss() for epoch in range(50): for xb, yb in loader: pred model(xb) loss loss_fn(pred, yb) optimizer.zero_grad() loss.backward() optimizer.step() print(fEpoch {epoch}, Loss {loss.item():.4f})这里hidden_size64、num_layers2是起点不是最优。如果验证集 loss 震荡先把学习率降到 1e-4如果欠拟合把 hidden_size 加到 128。注意输入形状必须是(batch, seq, feature)单路段 feature1。训练集和测试集要按时间切分比如前 80% 时间训练后 20% 测试不能随机打乱。3.3 多路段联合建模把空间关系塞进模型单路段模型忽略了相邻路段的相互影响。早高峰时上游路口排队会直接影响下游流量。常见做法有两种一是把相邻路段的流量作为额外特征拼进输入二是用图神经网络显式建模路段拓扑。前者实现简单后者效果更好但需要邻接矩阵。如果你只有yc.rar里的流量表没有路网拓扑可以先构造基于相关性的邻接矩阵计算路段两两之间流量的皮尔逊相关系数取 top-k 作为邻居。然后把这个邻接矩阵喂给 GCN 或 Graph WaveNet。下面是一个简化的 GCN 层示例假设邻接矩阵已归一化。class GCNLayer(nn.Module): def __init__(self, in_dim, out_dim): super().__init__() self.linear nn.Linear(in_dim, out_dim) def forward(self, x, adj): # x: (batch, num_nodes, in_dim), adj: (num_nodes, num_nodes) support torch.matmul(adj, x) # 聚合邻居信息 return self.linear(support) # 使用示例先过 GCN 再过 GRU class STGCN(nn.Module): def __init__(self, num_nodes, in_dim1, hidden64): super().__init__() self.gcn GCNLayer(in_dim, hidden) self.gru nn.GRU(hidden, hidden, batch_firstTrue) self.fc nn.Linear(hidden, 1) def forward(self, x, adj): # x: (batch, seq, num_nodes, in_dim) batch, seq, nodes, _ x.shape x x.reshape(batch * seq, nodes, -1) x self.gcn(x, adj) x x.reshape(batch, seq, nodes, -1) x x.permute(0, 2, 1, 3).reshape(batch * nodes, seq, -1) out, _ self.gru(x) out out[:, -1, :] return self.fc(out).reshape(batch, nodes)这段代码把 GCN 和 GRU 串起来GCN 负责在空间维度聚合邻居流量GRU 负责在时间维度提取演化模式。adj需要提前归一化否则梯度会爆炸。如果路段数超过 500邻接矩阵会很大建议用稀疏矩阵或采样邻居。4. 短时交通流量预测的避坑与排查那些让我返工三次的细节4.1 时间泄漏随机切分训练集导致指标虚高现象验证集 MAE 低到 0.5上线后误差翻三倍。原因用train_test_split随机切分测试集里的时间点可能出现在训练集之后但模型已经见过相邻时间片的流量。解决严格按时间切分前 80% 时间训练后 20% 测试中间留一段 gap 防止窗口重叠。如果要做交叉验证用时间序列 CV不要用 KFold。4.2 缺失值插补前向填充把凌晨低流量带进早高峰现象早高峰预测值明显偏低排查发现原始数据有缺失用ffill把凌晨 3 点的低流量填到了 7 点。原因前向填充假设流量不变但交通流量在缺失期间可能已经发生变化。解决短缺失1 到 2 个时间片用线性插值长缺失用历史同时刻均值填充并加一个缺失指示特征让模型自己学。4.3 归一化方式全局归一化让晚高峰路段被低估现象不同路段流量量级差异大全局 min-max 归一化后低流量路段的波动被压缩。原因全局归一化把 0 到 2000 的流量映射到 0 到 1低流量路段的值集中在 0.1 附近模型难以区分。解决按路段分别归一化或者用对数变换后再标准化。预测时记得反变换回原始量级。4.4 过拟合周期特征模型只学会背上周数据现象训练集 loss 很低测试集在节假日或异常天气下完全失效。原因模型过度依赖weekday和time_slot特征把周期性当成了因果性。解决加入实时特征如当前速度、占有率、天气标志并在训练时随机 mask 部分周期特征迫使模型学习实时波动。另外保留一个「上周同时刻」作为特征而不是标签让模型自己决定信多少。4.5 评估指标选错MAE 好看但峰值全丢现象MAE 只有 20但早高峰峰值预测误差超过 200。原因MAE 对峰值不敏感平峰样本占多数模型倾向于预测均值。解决同时看 MAPE、RMSE 和峰值时段的 MAE。如果业务关心拥堵可以给峰值样本加权或者用分位数损失替代 MSE。5. 把短时交通流量预测跑成可复现的验证流程5.1 用时间序列交叉验证替代单次切分单次切分的结果波动大换一个测试周可能结论完全相反。我一般用滚动窗口验证每次用过去 N 周训练预测下一周然后窗口向前滑动。这样能看出模型在不同时间段的稳定性。下面是一个简化的滚动验证框架。def rolling_validate(df, model_fn, n_splits4): results [] timestamps sorted(df[timestamp].unique()) fold_size len(timestamps) // (n_splits 1) for i in range(n_splits): train_end fold_size * (i 1) test_end fold_size * (i 2) train_ts timestamps[:train_end] test_ts timestamps[train_end:test_end] train df[df[timestamp].isin(train_ts)] test df[df[timestamp].isin(test_ts)] model model_fn(train) pred model.predict(test) mae np.mean(np.abs(test[flow] - pred)) results.append(mae) return resultsn_splits4表示做 4 次滚动每次测试窗口占 1/5 数据。这个框架不依赖具体模型model_fn可以是历史均值、GRU 或 STGCN。关键是每次训练只用到测试时间之前的数据杜绝泄漏。5.2 特征重要性排查确认模型没有依赖错误信号训练完一个树模型或线性模型后看特征重要性。如果road_id的 one-hot 编码排在前列说明模型在背路段编号而不是学流量模式。这时候要检查路段编号是否和流量量级相关如果是考虑去掉road_id或改用路段嵌入。如果time_slot重要性过高而实时速度特征重要性接近零说明实时数据可能没对齐或归一化有问题。5.3 上线前的最后一道检查用最近一周做盲测在正式部署前留最近一周数据完全不参与训练和调参只做一次盲测。这一周最好包含一个工作日高峰和一个周末。盲测 MAE 如果比滚动验证差 30% 以上说明模型对近期模式变化不适应需要加入在线更新或增量训练。我习惯把盲测结果和基线模型并排打印如果复杂模型只比历史均值好 5%我会先上线基线再慢慢迭代。检查项合格标准不合格时的动作时间切分训练集时间全部早于测试集重新切分加 gap缺失比例单路段缺失 5%插补并加缺失指示归一化按路段分别归一化改逐路段 scaler峰值 MAE不超过平峰 MAE 的 3 倍加权损失或分位数损失盲测衰减比滚动验证差 30%加入增量训练这套流程跑下来短时交通流量预测就不再是一个黑匣子。我自己的习惯是每次换新数据先跑历史均值基线再跑 GRU最后才考虑图网络。如果基线已经满足业务要求就不要为了技术而技术。希望帮到你。本文还有配套的精品资源点击获取