简介:这份资源面向交通工程、智能交通与数据分析方向的学习者与研究人员,聚焦短时交通流量预测这一典型场景,提供一份可直接运行的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:i+WINDOW_IN] y = values[i+WINDOW_IN:i+WINDOW_IN+WINDOW_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'}, inplace=True) # 合并回测试集 test = df.merge(baseline, on=['road_id', 'weekday', 'time_slot'], how='left') 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, dtype=torch.float32) y_train_t = torch.tensor(y_train, dtype=torch.float32) loader = DataLoader(TensorDataset(X_train_t, y_train_t), batch_size=64, shuffle=True) class GRURegressor(nn.Module): def __init__(self, input_size=1, hidden_size=64, num_layers=2): super().__init__() self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True) 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(), lr=1e-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(f'Epoch {epoch}, Loss {loss.item():.4f}')这里hidden_size=64、num_layers=2是起点,不是最优。如果验证集 loss 震荡,先把学习率降到 1e-4;如果欠拟合,把 hidden_size 加到 128。注意输入形状必须是(batch, seq, feature),单路段 feature=1。训练集和测试集要按时间切分,比如前 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_dim=1, hidden=64): super().__init__() self.gcn = GCNLayer(in_dim, hidden) self.gru = nn.GRU(hidden, hidden, batch_first=True) 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_splits=4): 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_splits=4表示做 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,最后才考虑图网络。如果基线已经满足业务要求,就不要为了技术而技术。希望帮到你。
本文还有配套的精品资源,点击获取