简介:以城市交通通行预测为实战场景,面向国科大等高校数据挖掘课程大作业完成者与交通预测初学者,解决从原始数据到最终预测结果落地难的问题。资源完整呈现了原始路况数据清洗、特征提取到XGBoost建模与结果生成的全流程方案。包体共14个文件,核心包括4个ipynb交互式笔记(交通数据预处理、建模预测两大部分及checkpoint版本)、1个py工具脚本用于数据读取与辅助处理、1个pkl已训练模型可快速加载推断,另有6个txt预测结果和2个png可视化图,压缩包整体仅2.09MB,结构清晰、便于离线复现。目前已有1558人学习下载。通过该资源可掌握竞赛级交通数据的特征工程思路、回归模型调参与结果优化技巧,明确预测结果提交格式,并能利用模型对象与脚本快速验证,有效避开数据清洗与特征构造中的常见踩坑点,适合完成课程设计,也适合初学者快速上手交通流预测建模项目。
1. 交通通行预测大作业的第一关:把rar包解压并认清数据
交通通行预测是数据挖掘课程里常见的大作业选题,国科大这类作业通常以rar压缩包形式下发,里面是一份时间序列交通流量数据、一份报告模板,以及一段“预测未来交通通行状态”的宽泛要求。拿到包后第一件事不是急着上模型,而是解压、确认时间字段的粒度、覆盖范围和缺失情况。解压只是起点,压缩包里的数据形态才是决定整个作业工作量的部分。交通数据天然带时间戳,作业本质上是在做时间序列回归,数据对齐做不干净,后面的特征工程和模型评估都会被带偏。这套流程以一份典型rar包为例,从解压、清洗、构造特征到模型训练和验证完整走一遍,课程作业可以直接照着改,第一次接触交通流预测的工程师也能当参考路线。
2. 交通通行预测的数据清洗:粒度统一、缺失值插补与异常过滤
2.1 从rar解压到确认时间戳粒度与覆盖范围
rar解压是机械操作,但解压之后的文件检查决定了整个数据挖掘大作业的走向。Windows下用图形界面即可,Linux或macOS下命令行更顺手:
# Linux/macOS 下解压 rar 包 unrar x traffic_data.rar # 若已经解压过,用 l 参数列出包内文件,确认是否有数据字典 unrar l traffic_data.rarunrar x解压到当前目录,unrar l只列出包内文件名而不解压,适合先确认压缩包里是单个数据文件还是附带说明文档。当环境里没有 unrar 时,常见做法是用unar替代,装一下对应的包管理器组件就能用。
解压完成后,先写一小段 pandas 代码看数据轮廓,而不是直接用 Excel 打开。课程数据量通常在几万到几十万行,Excel 打开会卡,而且对列类型和时间格式的判断不如代码直观:
import pandas as pd df = pd.read_csv('traffic_data.csv', parse_dates=['time']) df = df.set_index('time') print(df.shape) print(df.head()) print('时间范围:', df.index.min(), '->', df.index.max()) print('时间戳是否对齐15分钟:', df.index.floor('15min').equals(df.index))parse_dates=['time']把时间列直接转成datetime64,省去后续手工转换;set_index('time')之后所有时间操作都走索引。floor('15min')用于校验原始数据是否已经对齐到整刻钟,返回True说明时间轴干净,返回False说明后面必须重采样。时间覆盖范围决定了滞后窗口能开多大,如果数据只有一周,48 阶的滞后特征会把样本首尾掏空,这个矛盾要在构造特征前就意识到。
2.2 缺失值插补:线性插值、周期填充与不引入未来信息
交通流数据缺失通常来自检测器故障或网络传输丢包,缺失模式分单点和连续段两种,处理方式完全不同。
| 缺失模式 | 常用方法 | 注意点 |
|---|---|---|
| 单点缺失 | 线性插值 | 相邻值差异大时插值偏平缓 |
| 连续短段缺失 | 前向填充 | 会产生平台期,夜间流量低时影响小 |
| 连续长段缺失 | 同期均值填充 | 按星期几+小时分组,保留周期特征 |
选择哪种方案要结合预测目标。交通通行预测关注的是早晚高峰的突变,如果缺失段刚好落在高峰时段,前向填充会把峰值直接抹平,模型在这个时段学到的是错误的平稳形态。周期均值填充用“上周同一天同时段”的均值补进去,虽然会平滑掉个别突发拥堵,但至少不会把整个高峰填成平路。
# 先线性插值处理少量缺失 df['flow'] = df['flow'].interpolate(method='linear', limit_direction='both') # 长段缺失用周同期均值兜底 period_mean = df.groupby([df.index.dayofweek, df.index.hour])['flow'].transform('mean') df['flow'] = df['flow'].fillna(period_mean)interpolate的limit_direction='both'允许数据开头和结尾也参与插补,避免首尾 NaN 堆积。groupby(...).transform('mean')返回与原索引对齐的均值序列,直接作为 fillna 的填充值。顺序是先线性插值、后周期均值,不能反过来,否则单点缺失也会被周期均值平滑掉,丢失短期波动信息。
2.3 重采样对齐时间轴并过滤异常流量
如果 2.1 的时间校验返回False,就需要重采样。检测器上报延迟会让部分时间戳落在 07:01、07:02 这类非整点位置,统一到 15 分钟或 1 小时粒度,后续滞后特征的语义才会干净。
# 统一重采样到 15 分钟粒度,取均值作为该时段流量 df = df.resample('15min').mean() # 剔除完全没有数据的空时段 df = df.dropna(subset=['flow'])resample('15min')把时间轴对齐到整刻钟,mean()把同一时段内的多个上报点聚合成一个值。重采样后不要用pad(),那等价于把缺失时段全部填成上一时刻的值,会制造出大量平台期,看起来数据连续了,实际上引入的是伪规律。
异常流量过滤用中位数绝对偏差(MAD)比 3 倍标准差更稳妥。交通流量在不同时段方差差异很大,凌晨可能只有几十辆,晚高峰能到几千,全局标准差会把凌晨的正常波动误判成异常。用滚动窗口计算局部中位数和 MAD,再过滤局部离群点:
rolling_median = df['flow'].rolling(window=48, center=True).median() mad = (df['flow'] - rolling_median).abs().rolling(window=48, center=True).median() df['flow'] = df['flow'].mask((df['flow'] - rolling_median).abs() > 5 * mad) df['flow'] = df['flow'].fillna(rolling_median)window=48在 15 分钟粒度下是 12 小时,覆盖半个日周期,能贴合交通流的局部变化;center=True让窗口以当前点为中心,避免只依赖过去信息造成偏移。超过 5 倍局部 MAD 的流量被替换为窗口中位数,这样不会因为个别异常上报把模型整体拉偏。
3. 构造时间特征与滞后特征:把交通通行预测变成监督学习
3.1 为什么不能直接拿原始流量列跑模型
不少大作业的第一版代码是拿“昨天同一时刻”的流量当预测值。这是一个很强的 baseline,但它本身不是数据挖掘模型。直接把流量列和时间戳丢给线性回归,模型只能学到“当前值约等于上一时刻值”的朴素规律,早晚高峰的爬升速度、工作日和周末的形态差异都体现不出来。
把时间序列预测改造成监督学习,核心是构造“X 是历史信息,y 是未来值”的训练样本。对交通通行预测任务,X 至少要包含三类信息:时间周期位置、近期流量形态、长期流量背景。这三类信息分别对应下面三节的特征构造方式。
3.2 时间周期特征:小时循环编码加上工作日标记
小时列如果直接当数值特征用,会有个明显问题:模型会认为 23 和 0 在数值上很接近,这个没问题;但它也会认为 7 和 17 都是“中午附近”的值,实际上一个是早高峰一个是晚高峰。离散数值编码无法表达时间的循环属性。
import numpy as np df['hour'] = df.index.hour df['weekday'] = df.index.weekday df['is_weekend'] = (df.index.weekday >= 5).astype(int) df['hour_sin'] = np.sin(2 * np.pi * df.index.hour / 24) df['hour_cos'] = np.cos(2 * np.pi * df.index.hour / 24)hour_sin和hour_cos把 24 小时映射到单位圆上,23 点和 0 点在三角函数空间里是相邻坐标,模型能感知“深夜到凌晨”的连续性。is_weekend是离散标记,树模型可以直接用它切分出工作日和周末两套规律。只保留hour数字列不够,数字列描述的是“第几小时”,循环编码描述的是“时间在周期中的位置”。
3.3 滞后特征与滑动窗口:窗口长度怎么定
滞后特征是把过去若干时间点的流量作为当前预测的输入。窗口长度的选择直接决定特征矩阵的信息量。15 分钟粒度的数据,过去 3 小时是 12 个点,过去一天是 96 个点;窗口太长会塞进无关历史,窗口太短看不到日周期。
| 特征列 | 含义 | 15分钟粒度下的构造方式 |
|---|---|---|
| lag_1 | 15分钟前流量 | shift(1) |
| lag_3 | 45分钟前流量 | shift(3) |
| lag_6 | 当前时刻的上一个半小时 | shift(6) |
| lag_96 | 昨天同一时刻流量 | shift(96) |
| rolling_mean_3h | 过去3小时平均流量 | shift(1).rolling(12).mean() |
# 短时滞后:过去1到6个点 for lag in range(1, 7): df[f'lag_{lag}'] = df['flow'].shift(lag) # 日周期滞后 df['lag_96'] = df['flow'].shift(96) # 过去3小时均值与标准差,先shift去掉当前值 df['rolling_mean_3h'] = df['flow'].shift(1).rolling(12).mean() df['rolling_std_3h'] = df['flow'].shift(1).rolling(12).std()shift(lag)把序列整体下移 lag 个位置,第 t 行的lag_3就是 t-3 时刻的真实流量。rolling(12).mean()默认包含当前点,必须先用shift(1)把当前值移除,否则预测目标 y 的信息被混进特征,这是时间序列特征工程最常见的泄漏点。
生成完特征后,数据开头会有约 96 行 NaN,最早样本没有足够历史。直接删掉这些行,不要用 0 填充;0 填充会让模型学到“流量从 0 开始”的错误先验。
3.4 训练集与验证集按时间切分,不能用随机打乱
数据挖掘通用流程里的train_test_split(X, y, test_size=0.2, random_state=42)会随机打乱数据,放到交通通行预测里就是灾难。随机打乱之后,训练集里混入了验证集时刻之后的样本,模型等于提前看到了未来,验证集分数虚高,换个时间段就崩。
feature_cols = ['hour', 'weekday', 'is_weekend', 'hour_sin', 'hour_cos', 'lag_1', 'lag_2', 'lag_3', 'lag_4', 'lag_5', 'lag_6', 'lag_96', 'rolling_mean_3h', 'rolling_std_3h'] valid_size = int(len(df) * 0.2) X_train = df[feature_cols].iloc[:-valid_size] X_valid = df[feature_cols].iloc[-valid_size:] y_train = df['flow'].iloc[:-valid_size] y_valid = df['flow'].iloc[-valid_size:]iloc按行位置切分,前 80% 训练、后 20% 验证,验证集的时间全部晚于训练集。feature_cols固定列顺序,LightGBM 预测时依赖同样的列集合。切分结果可以打印一次X_train.index.min()和X_valid.index.min()确认时间顺序,作业报告里放这个输出,能直接证明没有数据泄漏。
注意:如果数据量再大一些,可以按 7:3 切分或做滚动起源交叉验证,但作业周期内按时间切分一次就够,重点放在特征构造和参数调整上。
4. 用 LightGBM 训练交通通行预测模型:参数设定与调优
4.1 模型选型:树模型在课程作业场景下的优势
交通通行预测的模型选项不少,传统的有 SARIMA 和 SVR,深度方向有 LSTM。课程作业周期内,LightGBM 这类梯度提升树是最稳妥的选择。三个理由:特征工程阶段构造的是表格结构,树模型天然擅长;树模型不要求归一化,凌晨几百、晚高峰几千的动态范围对树模型没有尺度压力;训练快,几万条数据几十秒内出结果,留出时间迭代特征。
SARIMA 作为 baseline 可以放进报告对比,但它对缺失值敏感、需要显式处理周期阶数。LSTM 要把数据重排成三维序列,调参成本高,作业周期内要在验证集上稳定超过调好的 LightGBM 并不容易。如果报告里需要“多模型对比”,加入一个只做滞后特征的线性回归就够了,对比表格里能体现树模型的优势。
4.2 基线模型训练与评估:MAE、RMSE 和 MAPE 的取舍
import lightgbm as lgb from sklearn.metrics import mean_absolute_error, mean_squared_error model = lgb.LGBMRegressor( objective='regression', learning_rate=0.05, n_estimators=3000, num_leaves=31, min_child_samples=20, subsample=0.8, subsample_freq=1, colsample_bytree=0.8, random_state=42, n_jobs=-1 ) model.fit( X_train, y_train, eval_set=[(X_valid, y_valid)], eval_metric='mae', callbacks=[lgb.early_stopping(100), lgb.log_evaluation(200)] ) y_pred = model.predict(X_valid) mae = mean_absolute_error(y_valid, y_pred) rmse = mean_squared_error(y_valid, y_pred, squared=False) print(f'MAE: {mae:.2f}, RMSE: {rmse:.2f}')learning_rate=0.05配合n_estimators=3000,用小步长、多轮数逼近解,比一次性加大树数量更不容易过拟合。num_leaves=31控制每棵树的叶子数。subsample=0.8和colsample_bytree=0.8分别做行采样和特征采样,缓解过拟合。early_stopping(100)表示验证集 MAE 连续 100 轮不下降就提前终止,log_evaluation(200)每 200 轮打印一次日志。新版本 sklearn 里 RMSE 可以直接用root_mean_squared_error,效果与squared=False相同。
评估指标上 MAE 和 RMSE 够用。MAPE 在交通数据上有坑:夜间流量接近零时,真实值 50、预测偏差 30,MAPE 高达 60%,但 30 辆车的绝对误差在夜间场景完全可以接受。MAPE 只能分时段算,不能全局算。
注意:如果作业硬性要求 MAPE,先过滤掉
truth低于某个阈值的夜间样本,再计算并注明过滤规则,否则结果会被极小值主导。
4.3 三个直接生效的调参方向
4.3.1 学习率与迭代轮数配合早停使用
固定n_estimators然后反复试learning_rate是常见误区。正确做法是设置一个很大的n_estimators,让early_stopping决定实际轮数。学习率从 0.1 降到 0.05 通常能带来 1% 到 3% 的 MAE 改善,训练时间翻倍在作业数据量下可接受;再降到 0.03 提升有限,除非时间充裕否则不必。
4.3.2 num_leaves 与 min_child_samples 的取值边界
num_leaves是 LightGBM 里最容易过拟合的参数。几万条数据时,num_leaves=127可以在训练集上接近零误差,但验证集反而更差。先 31、再 64 和 15,观察验证集 MAE 的 U 型曲线,取最低点对应的值。min_child_samples设 20 到 50,防止叶子节点只覆盖个别极端时段。这两个参数配合调整,不要只动其中一个。
4.3.3 特征重要性排序与冗余特征剔除
importance_df = pd.DataFrame({ 'feature': feature_cols, 'importance': model.feature_importances_ }).sort_values('importance', ascending=False) print(importance_df)输出里通常lag_1排第一,hour_sin、hour_cos、rolling_mean_3h紧随其后。如果lag_5、lag_6重要性很低,删掉重新训练看验证集 MAE 有没有反弹。每次只删一两个低重要性特征,不要一次删完,树模型对冗余特征组合的依赖比线性模型更强。
| 参数 | 作用 | 作业场景建议值 |
|---|---|---|
| learning_rate | 每轮步长,越小越稳 | 0.05 |
| n_estimators | 最大训练轮数,配合早停 | 3000 |
| num_leaves | 树复杂度,越大越容易过拟合 | 31 |
| min_child_samples | 叶子节点最少样本数 | 20 |
| subsample | 行采样比例 | 0.8 |
| colsample_bytree | 特征采样比例 | 0.8 |
表格里的建议值是基线配置,最终以自己验证集上的 MAE 为准,不要照搬。
5. 用滚动预测验证交通通行预测模型:分时段误差与答辩可视化
5.1 滚动预测循环里的关键改动
前面的验证流程每一步都使用真实滞后特征,叫一步预测。实际部署时,t 时刻的预测值会被当作 t+1 时刻的输入,误差逐轮累积。滚动预测通过逐步回填预测值来复现这个过程,能看出模型在误差累积下是否仍能跟上早晚高峰的形态。循环里有两个关键点:lag_1到lag_6按从大到小的顺序整体平移,最后把新预测值填入lag_1;lag_96、滑动统计和时间特征在预测时属于已知信息,直接从验证集取对应时刻的值。
rec = X_valid.iloc[0:1].copy() preds_roll = [] for i in range(len(X_valid)): pred = model.predict(rec[feature_cols])[0] preds_roll.append(pred) for lag in [6, 5, 4, 3, 2]: rec[f'lag_{lag}'] = rec[f'lag_{lag - 1}'] rec['lag_1'] = pred if i + 1 < len(X_valid): for col in ['lag_96', 'rolling_mean_3h', 'rolling_std_3h', 'hour', 'weekday', 'is_weekend', 'hour_sin', 'hour_cos']: rec[col] = X_valid[col].iloc[i + 1]滞后更新的顺序必须是 6 到 2 而不是 1 到 6,反过来会提前覆盖掉lag_1到lag_5的原值。长滞后特征从验证集按位置取值,不参与滚动,这样误差只在短时窗口内传播,得到的结果反映模型在短期递归预测下的表现。
5.2 分时段计算 MAE 找到模型薄弱时段
把预测结果对齐到时间索引,按小时切出早高峰、晚高峰、平峰和夜间四段分别计算 MAE。早晚高峰 MAE 大是因为流量基数大,夜间 MAPE 大是因为基数小,这两组现象都是正常的。真正需要关注的是平峰时段的 MAPE 是否突然高于高峰时段,如果是,说明模型在平稳流量段存在系统性偏差。
eval_df = pd.DataFrame( {'truth': y_valid.iloc[:len(preds_roll)], 'pred': preds_roll}, index=X_valid.index[:len(preds_roll)] ) eval_df['hour'] = eval_df.index.hour for name, (s, e) in {'早高峰': (7, 10), '晚高峰': (17, 20), '平峰': (10, 17), '夜间': (0, 6)}.items(): sub = eval_df[(eval_df['hour'] >= s) & (eval_df['hour'] < e)] print(f'{name}: MAE={mean_absolute_error(sub["truth"], sub["pred"]):.2f}')分组评估能直接定位模型的短板。作业报告里把这张分时段误差表放进去,比只贴一个全局 MAE 更有说服力,也方便答辩时被问到“模型哪里不好”时有话可说。
5.3 答辩展示:四张图对应四个环节
数据挖掘大作业答辩时,评委不会从头读完代码。可视化按“数据—特征—模型—验证”四个环节各准备一张。第一张是连续两周的原始流量时间序列曲线,让人一眼看到日周期和周末低谷。第二张是滞后特征相关性热力图,展示lag_1到lag_6与当刻流量的相关系数,佐证特征工程方向没有跑偏。第三张是训练集与验证集的预测-真实散点图,点越贴近对角线效果越好。第四张是滚动预测最后一个星期的对比折线图,真实流量和预测流量画在同一坐标,早晚高峰的跟随效果一目了然。报告里把这四张图按顺序放好,对应章节分别给出关键参数表和分时段误差表,评委一般会先看这几页再决定要不要追问细节。
本文还有配套的精品资源,点击获取