简介:面向交通数据分析人员、智能交通领域学习者及MATLAB建模爱好者,这份源码包聚焦短时交通流量预测任务,利用历史车流数据对后续数分钟到数小时的路段流量进行估算,可为信号灯优化、拥堵预警和出行引导等场景提供辅助决策。压缩包采用rar格式,整体仅含1个m格式的MATLAB脚本,大小约1KB,代码精炼却系统覆盖了数据清洗与归一化、交通流特征构造(如滞后值、节假日与通勤高峰),以及ARIMA、支持向量机或神经网络等模型的构建与参数优化;同时包含基于历史数据训练和交叉验证的环节,并以均方误差、平均绝对误差等指标评估效果,还能绘制预测值与真实流量对比图,便于直观检验。已有339人学习该资源,作为一份可运行的完整示例,读者既能借此快速上手短时交通预测完整链路,也可在MATLAB中复现基线模型,或在此基础上替换数据、调整参数,进一步设计更优算法。
1. 短时交通流量预测:在确定性假设消失之后
短时交通流量预测解决的问题并不复杂——给定过去一段时间内某个断面或某条路段的历史流量、速度和占有率数据,预测未来 5 到 30 分钟内的交通流状态。它的难点不在“预测”这个词,而在于交通系统本身的高度非平稳性:早高峰的一个小事故、雨天的临时管制、甚至一个红绿灯配时方案的调整,都会让历史数据中的统计规律迅速失效。实际工程里最常见的一个坑是:模型在训练集上 MAPE 做到 8%,上线后第一个暴雨天就跳到 25% 以上——这不是模型崩塌,而是输入分布和训练分布发生了漂移,靠调参数救不回来。因此,做短时交通预测的第一步不是选模型,而是理解数据的采样粒度、空间关联性和异常事件的注入路径,然后才能谈建立基线、迭代模型和上线评估。
这篇文章面向的是交通大数据平台、智慧城市项目中的算法工程师和数据开发,目标是给出一个可以照着落地的技术路线:从数据处理、特征工程到模型选型与调参,再到验证指标与在线校准。文章里涉及的命令和代码都是常见开源方案的最小可复现版本,不会依赖不存在的库或接口。对于新手来说,按顺序跟下来能跑通一个端到端的流量预测管线;对于有经验的从业者,中间关于预测步长、标签移位和多步预测策略的讨论,往往是线上效果差、但排查手册里不写的隐藏因素。
2. 输入数据与特征工程:短时交通预测的地基
2.1 断面流量数据的时间对齐:先解决用哪一分钟的流量
短时交通流量预测最基础的数据来源是地磁线圈、微波雷达和视频检测器输出的断面流量,一般以 5 分钟或 15 分钟为聚合周期。数据表结构通常是四列:路段编号、时间戳、流量(辆/周期)、平均速度(km/h)和占有率(%)。理想情况下,每条记录应该严格对齐到周期边界,比如 08:00、08:05、08:10。但实际从路口采集器拿到的原始表经常存在时间戳偏差——有的设备按整点对时,有的按设备启动时间计时,导致同一个 5 分钟周期内的数据被标记在不同时间槽里。这个问题如果不处理,后续的滞后特征就全部错位。
常见做法是按下述规则做重采样和对齐:将每台设备的上报时间归一化到最近的周期边界,再对同一路段、同一时间槽内的多条记录做流量求和、速度和占有率取均值。归一化使用floor向下取整,保证数据永远属于周期开始时所在的槽位,而不是中间点。这样处理后,每个路段在每个时间周期只有一条记录。
import pandas as pd # df 包含列: road_id, ts(设备上报时间), volume, speed, occ # 对齐到5分钟周期边界 df['ts'] = pd.to_datetime(df['ts']) df['slot'] = df['ts'].dt.floor('5min') # 同路段同时段聚合,流量求和,速度和占有率按流量加权 def weighted_avg(x, w): return (x * w).sum() / w.sum() agg_df = df.groupby(['road_id', 'slot']).agg( volume=('volume', 'sum'), speed=('speed', lambda x: weighted_avg(x, df.loc[x.index, 'volume'])), occ=('occ', lambda x: weighted_avg(x, df.loc[x.index, 'volume'])) ).reset_index()聚合逻辑的说明:流量是周期内累计值,必须求和;速度和占有率是断面状态的瞬时采样,当同一周期内有多条记录时应按流量加权平均,而非简单平均——高流量时段的检测数据更可信,应占更高权重。floor对齐的作用是保证训练时构造的历史窗口和预测标签落在同一时间语义下,避免线上推理时出现数据位移。
2.2 异常流量值的判定:异常值和真实拥堵的区分
交通流量数据里有两类”异常”需要区分对待。第一类是设备故障,比如流量为零但速度正常、速度突然变为 0 但前后周期流量正常、流量超过车道数乘以单车道最大通行能力等。这类数据可以直接剔除或置空。第二类是真实交通事件导致的突变,比如事故后流量瞬间跌到接近零而占有率升高,这种数据恰恰是短时预测模型最需要学习的信号,不能用清洗规则抹掉。
实用的清洗策略是用滚动中位数窗口检测”孤立异常”而非“持续突变”。取当前时刻前后各 12 个周期(1 小时)的流量中位数,如果当前流量与中位数的绝对差超过中位数的 300%,则判定为设备异常,用中位数填充;如果只是连续 2 到 3 个周期下跌,这属于事件演进过程,保留不动。
| 异常模式 | 示例 | 处理方式 |
|---|---|---|
| 流量为零且速度为 0 | 设备断电 | 置空,后续插值 |
| 流量为零但速度 60 km/h | 检测器漏拍 | 用前后周期均值填充 |
| 流量突然翻 5 倍又回落 | 信号灯故障或数据合并错误 | 中位数替换 |
| 连续 3 个周期流量阶梯下跌 | 前方事故 | 保留,作为特征输入 |
2.3 时序特征构造:滞后、周期编码和外部变量
特征工程是短时交通预测里投入产出比最高的环节。一个标准的特征集合由三部分构成:当前时刻的历史序列特征、时间周期编码特征、外部条件特征。历史序列特征用滞后操作构造,核心是选择滞后阶数——5 分钟粒度下,通常取过去 6 到 12 个周期(30 到 60 分钟)的流量、速度、占有率作为原始滞后特征,再加一阶差分和滚动统计量。
代码实现滞后特征和滚动特征:
# 对每个路段单独构造特征,避免跨路段混淆 def build_features(df, road_id, lag_steps=12, roll_windows=[6, 12]): sub = df[df['road_id'] == road_id].sort_values('slot').copy() for lag in range(1, lag_steps + 1): sub[f'vol_lag_{lag}'] = sub['volume'].shift(lag) sub[f'spd_lag_{lag}'] = sub['speed'].shift(lag) # 滚动均值与标准差 for w in roll_windows: sub[f'vol_roll_mean_{w}'] = sub['volume'].rolling(w).mean() sub[f'vol_roll_std_{w}'] = sub['volume'].rolling(w).std() # 一阶差分捕捉突变趋势 sub['vol_diff_1'] = sub['volume'].diff(1) sub['vol_diff_3'] = sub['volume'].diff(3) # 周期编码:一周内的周期序号,捕捉早晚高峰的周期重复 sub['day_of_week'] = sub['slot'].dt.dayofweek sub['slot_of_day'] = sub['slot'].dt.hour * 12 + sub['slot'].dt.minute // 5 # 周期正余弦编码,避免 0 和 11 之间的距离被当成 1 sub['sin_slot'] = np.sin(2 * np.pi * sub['slot_of_day'] / 288) sub['cos_slot'] = np.cos(2 * np.pi * sub['slot_of_day'] / 288) return sub滞后特征让模型能看到流量变化的短期趋势,滚动标准差则刻画了波动幅度——这在大流量高波动时段(如晚高峰)对预测精度的提升比单纯加滞后阶数更明显。周期正余弦编码解决了周期特征的边界问题:23:55 和 00:00 在时间上只差 5 分钟,但在原始整数编码里差 143 个槽位,正余弦编码把这种距离映射到连续空间上。外部条件特征方面,最常见接入的是降雨量、温度、节假日标识和相邻路段的流量——是否引入取决于数据可得性,原则是一个离散取值不超过 5 类的分类特征,直接做 label encoding 即可;超过 5 类的,用 target encoding 或 embedding。
3. 从统计模型到深度模型:流量预测的模型选型路径
3.1 统计基线:ARIMA 和它的适用边界
建立基线是所有时间序列预测项目的第一步。短时交通流量预测的经典统计基线是 ARIMA 和季节性 ARIMA(SARIMA),其中 SARIMA 通过(p, d, q) × (P, D, Q, S)的形式显式建模周期性。在进入复杂模型之前,先用 statsmodels 跑一个 ARIMA 基线,能快速判断数据中是否存在强趋势和周周期性,也能给后续模型的精度提升提供对比锚点。
用 statsmodels 实现单路段 ARIMA 基线:
import statsmodels.api as sm from statsmodels.tsa.stattools import adfuller def arima_baseline(series, order=(2,1,2)): # ADF检验确认差分阶数,d=1时通常已经平稳 result = adfuller(series.dropna()) print(f'ADF p-value: {result[1]}') model = sm.tsa.ARIMA(series, order=order) fitted = model.fit() # 预测未来6个周期(30分钟) pred = fitted.forecast(steps=6) return fitted, pred这段代码里的series是单路段连续时间排序后的流量序列。order=(2,1,2)表示自回归阶数 2、差分阶数 1、移动平均阶数 2。短时流量序列的 ADF 检验通常 p 值远小于 0.05,说明一阶差分后已经平稳,这也是 ARIMA 在这个场景中能作为基线的原因。但 ARIMA 的实际应用有瓶颈:它处理不了外部变量(天气、事件)的输入,多步预测误差会迅速累积,而且对非平稳的长周期漂移(如一个路口的配时方案调整导致流量整体抬升)响应很慢。
3.2 树模型:把特征交互交给梯度提升
实际的短时交通预测工程中,LightGBM 或 XGBoost 往往是上线模型的默认选项,而不是 LSTM。原因在于:流量预测的特征结构以表格为主,滞后特征和周期特征天然适合树模型的分裂逻辑;梯度提升对特征尺度不敏感,不需要做归一化;训练速度快,支持在线增量更新。LightGBM 在几十万行数据上的表现通常优于深度学习模型。
一个可跑通的最小训练流程如下:
import lightgbm as lgb from sklearn.model_selection import TimeSeriesSplit # features来自上一节的build_features输出,去掉NaN行 train_df = build_features(df, road_id='RD001').dropna() feature_cols = [c for c in train_df.columns if c not in ['road_id', 'slot', 'volume']] X = train_df[feature_cols] y = train_df['volume'] # 时序交叉验证:按时间顺序划分,不打乱 tscv = TimeSeriesSplit(n_splits=5) lgb_params = { 'objective': 'regression', 'metric': 'mape', 'learning_rate': 0.05, 'num_leaves': 63, 'min_data_in_leaf': 50, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 1, 'verbosity': -1, } for train_idx, val_idx in tscv.split(X): X_train, X_val = X.iloc[train_idx], X.iloc[val_idx] y_train, y_val = y.iloc[train_idx], y.iloc[val_idx] d_train = lgb.Dataset(X_train, label=y_train) d_val = lgb.Dataset(X_val, label=y_val) model = lgb.train(lgb_params, d_train, num_boost_round=1000, valid_sets=[d_val], callbacks=[lgb.early_stopping(100)])用 TimeSeriesSplit 的原因很直接:交通数据的时间相关性意味着随机 K 折会把未来数据泄漏进训练集,导致验证分数虚高。num_leaves=63配合min_data_in_leaf=50控制模型复杂度,防止在流量低谷期过拟合噪声。metric='mape'的选择在交通场景下要小心——夜间低流量时段流量基数小,MAPE 会被极小值拉爆,所以实际项目中普遍同时监控mae和mape。
3.3 深度模型的用武之地:LSTM 与时空图网络
树模型能处理特征交互,但它看不到跨路段的时空依赖。对于单点断面预测,树模型已经足够了;但如果要做路网级的联动预测,比如预测一条主干道上下游 10 个断面的流量,或者某一路口排队长度对相邻路口的影响,这时就需要引入序列模型或图网络。
LSTM 是深度方案里最容易落地的选择。它的输入格式为[batch_size, time_steps, num_features]——time_steps是历史时间窗口长度,num_features是每个时刻的特征数量。和树模型不同,LSTM 需要把训练数据重构成三维张量,且对数值尺度敏感,训练前需要做 z-score 标准化。
这里不展开 LSTM 的全部实现细节,因为这个方案在公开代码库里很成熟。要注意的是:LSTM 在短时交通预测中未必优于 LightGBM,除非你有明确的空间结构需要建模,或者用户数据本身就是轨迹序列。如果考虑更前沿的方案,GCN + LSTM 或 Graph Transformer 可以处理路网拓扑,但代价是工程复杂度显著上升,且需要维护道路邻接矩阵,这个矩阵在现实道路中会随施工封路频繁变化,维护成本很高。
4. 短时交通预测的调参与评估:从训练到上线的关键参数
4.1 预测步长与标签构造:五分钟或十五分钟怎么选
预测步长决定了整个建模方案。5 分钟预测和 15 分钟预测在模型选型和标签构造上有本质区别:5 分钟预测的自相关性很强,滞后 1 阶特征就能解释大部分方差,简单模型和复杂模型差距不大;15 分钟预测的挑战要大得多,因为流量序列的相关性在 15 分钟尺度上已经明显减弱,需要依赖更多外部特征和更宽的滞后窗口。
标签构造是高频踩坑点。假设数据是 5 分钟粒度,要预测未来 15 分钟的流量,工程上有两种常见做法:
# 方案A:直接预测15分钟后的单点值(future value prediction) df['y_future_15min'] = df['volume'].shift(-3) # 方案B:预测未来3个周期的累计值(cumulative aggregation) df['y_cum_15min'] = df['volume'].rolling(3).sum().shift(-3)方案 A 的缺点是:它预测的是未来第 15 分钟那个时间点的瞬时流量,而不是未来 15 分钟的总体状态,在信号控制场景中参考意义有限。方案 B 的累计口径与信号配时和交通诱导的决策粒度更匹配,但预测难度更高——因为累计值的方差比单点值更大。实践中建议以方案 B 为主指标,同时辅助输出方案 A 的单点值,供不同下游系统使用。无论如何,使用shift(-k)时都要确保最后 k 行的标签为NaN,在训练前用dropna()剔除,否则模型会在无标签样本上计算损失,导致训练过程出现奇怪的震荡。
4.2 损失函数和评估指标:别让 MAPE 骗了你
短时交通流量预测的评估指标通常选 MAPE、RMSE 和 MAE 三个一起看,但不能一视同仁。MAPE 在流量低谷时会被无限放大——比如某路段夜间流量只有 20 辆/5 分钟,模型预测 25 辆,误差 5 辆,MAPE 是 25%,而同样 5 辆误差在早高峰流量 200 辆/5 分钟时只有 2.5%。同一模型的 MAPE 在夜间和白天可能差 10 倍,这种不均匀的误差放大让 MAPE 不适合作为全部时段统一优化的目标函数。
实际项目中的做法是按流量区间分层评估。将一天分成三个场景:平峰、高峰和夜间低峰,分别计算指标,并要求模型在高峰时段的 MAPE 优先达标。LightGBM 训练时如果metric='mape',它会按全部样本均匀计算,这在数据分布偏斜时会导致模型对夜间低流量时段拟合充分,高峰时段留下较大预测偏差。
| 评估时段 | 样本占比 | 主要指标 | 达标线参考 |
|---|---|---|---|
| 早高峰 7:00-9:00 | 15% | MAPE、拥堵断面误差 | MAPE < 10% |
| 平峰 9:00-17:00 | 35% | MAPE、RMSE | MAPE < 12% |
| 夜间 22:00-6:00 | 35% | 绝对误差 | MAE < 10 辆/周期 |
| 节假日 | 15% | MAE、方向一致率 | 特殊处理 |
如果你的项目不需要统一优化,可以考虑自定义损失函数对高峰时段样本加权,例如在 LightGBM 中设置sample_weight列,对 7:00-9:00 和 17:00-19:00 的样本给予 2 倍权重,损失函数加权的效果通常优于调大num_leaves。
4.3 多步预测的策略:直接式、递归式和直接递归混合式
短时交通预测的最终输出往往不是单步值,而是未来 30 分钟内每 5 分钟一个点,共 6 步的预测序列。多步预测的实现方式直接影响误差累积速度和推理延迟。三种主流策略如下。
递归式(recursive)的做法是:用第一个预测值作为下一个时刻的输入特征,继续预测后续值。它的工程实现最简单,但误差逐级累积——第 1 步误差进入第 2 步的输入,第 2 步误差进入第 3 步,通常第 4 步之后预测值会趋向于序列均值或过期值。直接式(direct)的做法是:为每个预测步长单独训练一个模型,第 k 步模型预测 y_{t+k},这样每一步的输入都是真实历史值,没有误差传播,模型数量却要乘以步长数,训练和推理成本高。
工程上推荐的是直接递归混合式:第 1 步用直接式模型,保证短期的精度;第 2 到第 6 步用递归式,但输入特征中的滞后值全部替换为前一步的预测值。这样兼顾了精度和模型数量,在实践中能避免第 4 步之后预测曲线变平的问题。
4.4 模型更新频率:短时预测必须在线更新
交通流的模式会随季节、道路施工、信号配时方案调整而漂移。一个在 3 月训练好的模型,到 5 月可能因为白天变长、晚高峰推迟而失去精度。常见的更新策略是滚动重训:每天凌晨用前 30 天的数据重训一次模型,并保存验证指标。但如果发生突发性变化(如连续封路一周),每日重训跟不上变化速度,需要引入在线更新机制。
LightGBM 支持增量训练,每月一次全量训练,期间每日用当天新数据微调更新:
import lightgbm as lgb # model为已保存的历史模型 update_data = lgb.Dataset(X_new, label=y_new) updated_model = lgb.train( lgb_params, update_data, num_boost_round=200, init_model=model, # 在已有模型基础上继续训练 )上述代码中的init_model参数让新数据只影响后续提升树的拟合方向,而不改变既有树的结构。实际操作中有两点需要注意:一是增量训练的学习率应设置得比全量训练更小(比如 0.02),避免新数据的梯度变化覆盖掉模型的长期记忆;二是每次更新后需要在冻结的验证集上对比新旧模型的 MAPE,如果增量训练后的模型指标恶化,保留旧模型继续服务。
5. 预测结果的校准与滚动验证:把输出变成可用的信号
短时交通预测模型上线后,输出的流量预测值并不会直接被业务使用——它要变成信号灯配时参数、交通诱导屏的发布文案或路况分钟级的 ETA(预计到达时间)。从模型输出到业务可用,中间隔着一层校准。最常见的是残差偏置校准:模型在高峰时段存在系统性的低估或高估,这通常是训练数据分布与线上分布不一致所致,但通过在线统计残差可以持续修正。
import numpy as np # recent_errors: 最近N个周期的 (真实值 - 预测值) recent_errors = np.array([2.3, -1.5, 0.8, 4.2, -3.1, 2.9]) # 用最近30个周期的残差中位数作为偏置修正量 bias = np.median(recent_errors) # 修正后的预测值 calibrated_pred = raw_pred + bias用中位数而不是均值的原因是:交通事件导致的极端残差会让均值产生较大偏移,而中位数对异常值更稳健。上面这段代码是每 5 分钟执行一次实时校准的简化逻辑,实际系统中 bias 的更新窗口一般取最近 24 小时的残差序列,并按照工作日和周末分别统计,因为两者偏置模式不同。
滚动验证是短时交通预测上线后必须持续运行的机制。每 5 分钟产生一个预测集,等到真实数据到达后计算误差并入库。当误差超过阈值触发告警:连续 6 个周期高峰时段 MAPE 超过 15%,自动重训;如果重训后仍不达标,则回退到上一版本模型并通知运维检查输入数据源。具体的告警逻辑用一段定时任务描述清楚:
# 伪代码:定时评估任务,每5分钟触发一次 def evaluate_prediction(): y_true = fetch_actual() y_pred = fetch_prediction() current_mape = mape(y_true, y_pred) if current_mape > 15.0: consecutive_fail += 1 else: consecutive_fail = 0 if consecutive_fail >= 6: trigger_retrain() # 触发自动重训 save_fallback_model() # 保存当前模型作为回退版本这段逻辑的关键在于把“触发重训”和“保存回退版本”放在同一个分支里,保证模型切换的过程是可逆的,避免新版模型在数据漂移时上线失败后无模型可用。这层机制是短时交通预测系统稳定在线运行的底线保障。
最后提一个容易被忽略的细节:预测结果的时间戳必须和下游系统的决策时戳对齐。诱导屏发布路况时,使用的是预测时段开始时点的值;信号灯配时方案使用未来 5 分钟内的累计流量。输出时建议同时返回预测值、预测时刻和置信度,虽然大多数信号控制系统现在还不用置信度,但预测方案演进到区间预测是明确的方向。
本文还有配套的精品资源,点击获取