时间序列交通流量预测:特征构建与模型训练实战指南
简介以python实现的交通拥堵预测毕设项目资源面向计算机相关专业的在校学生、教师及开发者解决道路时间段内车辆流量预测问题也可用于课程设计、毕业设计和项目初期立项演示。项目基于Gary-Chicago-MilwaukieGCM走廊真实交通数据涵盖八百余个传感器每五分钟采集的拥堵状态支持通畅、轻微、中度、重度四类拥堵等级预测。资源内提供项目说明文档、模板代码、数据预处理脚本以及划分好的训练集与测试集可完成未来三十分钟拥堵状态预测的完整实验流程代码已经过测试运行成功。全部文件共十九个包括六个压缩包、六个脚本文件、五个说明文档和两个表格数据整体仅32KB体量轻巧、便于直接部署。已有超过一千一百人学习下载适合希望快速上手交通预测实践的读者在调试通过的代码基础上继续拓展其他场景的流量预测。1. 交通拥堵预测毕设到底在做什么按时段统计车流量这套路值得复制把压缩包解压之后真正值得动手的就一句话拿到某个路口或某条路段的历史车流量记录按时间段聚合训练模型预测未来一个时段的车流量。题目里“拥堵预测”听起来很高大上但落到数据层面多数毕设做的其实是“车辆流量预测”而不是对拥堵等级的精细判定。很多同学一上来就盯着深度学习框架反而忽略了这类项目的胜负手在数据处理和按时间段切分的细节上。车流量有明显的周期规律——早高峰、晚高峰、周末、节假日——周期性抓得住简单模型也能出好看的结果周期特征没构建对再复杂的网络也是白搭。这套方案适合谁适合需要快速跑通一个可解释、可演示、能写进论文的预测系统的同学。我把常见做法拆开讲数据怎么盘、时间段怎么聚合、训练集和测试集怎么切、模型怎么选最后落到你一定会踩的坑上。2. 先把数据盘明白时间段聚合与训练集测试集的切分规则2.1 数据盘点第一步确认时间戳粒度和覆盖范围交通流量数据常见的来源是地感线圈、摄像头识别或第三方开放平台字段基本离不开记录时间、地点编号、流量值三类。拿到 CSV 之后不要急着建模先看一下时间戳是不是已经被正确解析成 datetime覆盖范围有多长中间有没有明显断档。这一步看起来基础但很多翻车现场都是从“时间格式没转换成功”开始的。import pandas as pd # 假设原始文件叫 traffic_raw.csv字段名可能是中文也可能是英文 df pd.read_csv(traffic_raw.csv, encodingutf-8) df[时间] pd.to_datetime(df[时间], errorscoerce) df df.dropna(subset[时间]) print(df.shape) print(df[时间].min(), df[时间].max()) print(df.dtypes)逻辑说明errorscoerce会把无法解析的时间转成 NaN随后用dropna把脏行清掉。这一步的作用是保证后面的重采样和特征构建不会因为混入空时间戳而对不齐时间轴。打印最小和最大时间是为了判断数据覆盖了多久——如果只有两三周的记录那树模型还能凑合如果只有几天后面做滞后 24 小时、滞后 168 小时的特征就完全没意义。参数说明这一步不用调什么敏感参数关键是确认“时间粒度”。常见上报频率是 5 分钟一条或 15 分钟一条也有直接按小时汇总好的。原始粒度决定了后续重采样窗口怎么选也决定了模型预测的最小时间单位。如果原始数据已是小时级就可以直接进入特征构建如果是分钟级需要先做第 2.2 节的聚合。2.2 按时间段聚合把零散记录变成等距时间序列很多原始数据是“每 5 分钟一条过车记录”而预测目标是某一小时内的总流量那就必须重采样。时间序列预测要求样本之间的时间间隔严格相等间隔不统一后面的滞后特征和滑动窗口全都会错位。常见的做法是先按路口分组再对每条时间序列做小时级重采样。# 先把时间列设为索引再按路口分组做小时求和 df df.set_index(时间) hourly ( df.groupby(路口)[车流量] .resample(h) .sum() .reset_index() ) # 如果关心的是“某个路段整体流量”可以只对单个序列重采样 # 可以顺手看下重采样后是否出现 NaN print(hourly.isna().sum()) print(hourly.head(10))逻辑说明groupby resample的组合能同时处理多个地点每条序列都会得到连续的按小时索引。流量计数只能做sum不能用mean如果你手里的字段是平均速度或占有率那才用mean。这个区别直接影响预测目标的物理含义。重采样之后出现的空档会变成 NaN需要用后面讲到的填充方式处理不能直接扔给模型。参数说明预测目标是“某时段内的车辆数”所以求和是对的如果改成 15 分钟粒度把resample(h)换成resample(15min)即可。粒度越细趋势越碎预测难度越大。毕设里建议先从小时级入手稳定之后再尝试 15 分钟级作为对比实验写进论文。2.3 训练集、验证集、测试集怎么切时间序列绝不能随机切分这一点值得单独用一整节强调。很多同学习惯直接调用train_test_split随机打乱这在普通分类问题里没问题但在交通流量预测里是致命的。交通数据的前后时刻高度相关随机打乱等于把未来信息混进训练集测试时的预测曲线会“贴着”真实值走漂亮得不像话答辩时被问两句就露馅。时间序列必须按时间先后切分。train_end 2024-06-30 23:00:00 val_end 2024-08-31 23:00:00 train hourly[hourly[时间] train_end] val hourly[(hourly[时间] train_end) (hourly[时间] val_end)] test hourly[hourly[时间] val_end] print(训练集:, len(train), 验证集:, len(val), 测试集:, len(test))逻辑说明先训练、再验证、最后测试模拟的是“用过去的数据预测未来”的真实用法。你可以用验证集调参但测试集只能用来做最终评估最好只在最后跑一次。如果压缩包里的说明文档明确给出了训练集和测试集文件注意不要直接用文件里的顺序先按时间戳排序再确认分界点按照和这里相同的逻辑重新做切分。参数说明比例不用死守 7:2:1但必须保证测试集是时间上最后的一段。我一般留最后 10%20% 做测试再往前切 15% 左右做验证。另一个常见做法是不单独切验证集直接拿最后一段做早停但毕设里还是建议单独留验证集方便你画三条损失曲线给老师看。3. 特征工程滞后特征与时间周期特征才是胜负手3.1 把时间拆成特征小时、星期几、是否节假日车辆流量有明确的 24 小时周期和 7 天周期。如果只把“车流量”这一列数值直接丢给模型模型很难自己同时学会这两种周期。最常见的做法是从时间戳里拆出小时、星期几、是否工作日、是否节假日等字段。树模型可以直接吃类别特征也可以用独热编码。这里有一个细节星期几是周期变量如果直接编码成 17模型会误以为 7 和 1 距离很远所以可以加一个“是否周末”的二值特征作为补充。hourly[小时] hourly[时间].dt.hour hourly[星期几] hourly[时间].dt.dayofweek # 0周一 hourly[是否周末] (hourly[星期几] 5).astype(int) # 节假日字段自己维护一张表merge 进来这里只给出开关 hourly[是否节假日] hourly[时间].dt.date.isin(holiday_dates).astype(int)逻辑说明小时特征让模型可以区分早高峰和凌晨星期几特征让模型知道周五晚高峰和周一早高峰不是一回事。节假日单独作为二值特征是因为节假日车流模式和普通工作日完全不同样本又少如果不显式告诉模型模型会把节假日当成普通周末来预测。参数说明“是否工作日”和“是否节假日”可以并存因为节假日也可能落在周末两者互不冲突。如果预测目标是分时段的流量还可以增加“时段类型”特征比如把一天切成长通勤时段、早高峰、平峰、晚高峰、夜间五档这比直接用小时数值更好解释。3.2 滞后特征与滚动窗口预测就是用好过去几个时刻这是整个特征工程里最核心的部分。所谓滞后特征就是把过去第 1 小时、第 2 小时、昨天同一时间、上周同一时间的流量值作为当前时刻的输入。滚动窗口特征则是用过去一段时间内的均值、最大值、最小值来反映近期的流量水平。构建时有一个必须遵守的纪律所有特征只能使用“当前时刻之前”的数据任何包含当前真实值的特征都会造成数据泄露。import numpy as np def build_lag_features(s: pd.Series): out pd.DataFrame(indexs.index) for lag in [1, 2, 3, 24, 168]: out[flag_{lag}] s.shift(lag) # 滚动均值与滚动最大值注意 shift(1) 对齐 for win in [6, 24]: out[froll_mean_{win}] s.rolling(win).mean().shift(1) out[froll_max_{win}] s.rolling(win).max().shift(1) return out feat build_lag_features(hourly[车流量]) hourly pd.concat([hourly, feat], axis1) hourly hourly.dropna() print(hourly.head())逻辑说明shift(lag)会把当前行对应的流量值挪到 lag 行之后这样第 t 行的lag_1就是第 t-1 小时的真实流量。滞后 24 的特征捕捉的是“昨天同一时间”的流量水平滞后 168 捕捉的是“上周同一时间”。滚动窗口的shift(1)是为了让窗口统计值不包含当前小时这个细节最容易漏一旦漏掉测试集指标会虚高。参数说明滞后项不是越多越好。交通流量最有用的是 1、2、3 小时因为流量变化在短时间内有惯性其次是 24 小时和 168 小时对应日周期和周期。滚动窗口我一般选 6 小时和 24 小时既能反映短期波动也能反映一天的整体水平。如果数据量足够还可以加滞后 48 小时和滚动标准差。3.3 时段交互特征让模型知道现在是周五晚高峰还是节假日早高峰单个特征分开给模型模型能学会“周五”和“17 点”各自的影响但“周五 17 点”的组合影响未必能自动表达。这里有条件做个交叉特征小时乘星期几或者把周末和平日分开构建两套滞后特征。常见做法是新增一列“时段编号”例如把 79 点记为早高峰、1719 点记为晚高峰、其余时段按平峰和夜间处理。def map_period(h, is_weekend): if is_weekend: if 10 h 18: return weekend_daytime return weekend_night if 7 h 9: return morning_peak if 17 h 19: return evening_peak return off_peak hourly[时段类型] [ map_period(h, w) for h, w in zip(hourly[小时], hourly[是否周末]) ]逻辑说明这段代码把一天切成几类区间作为类别特征送进树模型。它比单纯的“小时”数值更容易解释也能让模型对不同时段分别建模。做这段的意义在于早高峰的流量值往往很高但波动相对稳定夜间流量低但受突发事件影响的比例更高分开建模或加时段特征都能有效降低误差。参数说明时段划分没有统一标准关键要和预测目标一致。如果你的数据集显示当地晚高峰是 1720 点就按数据调整。这类交叉特征对 LightGBM 这类树模型收益明显对线性模型影响更大。生成后再次检查是否出现 NaN确保所有行都有完整的时段标签。4. 模型选型与训练先跑通 LightGBM再上 LSTM 做对比4.1 为什么要从 LightGBM 和 LSTM 的组合方案入手这里没必要一上来就选最难的方法。交通流量预测在论文里常见三套路子时间序列模型、树模型、神经网络。时间序列模型对周期性强但输入特征少的情况好用树模型对表格特征友好、不容易因 NaN 报错、训练速度快LSTM 能直接对序列建模但数据量小的时候容易过拟合且可解释性差。对毕设来说最稳妥的方案是以 LightGBM 作为主模型跑通完整流程再用 LSTM 做横向对比。很多同学在环境配置上卡住先在命令行执行pip install numpy pandas lightgbm再用pip install scikit-learn补上评估工具。这里如果安装失败基本是 Python 版本和依赖包版本不匹配建议新建一个 Python 3.9 或 3.10 的环境不要直接装在系统默认环境里。4.2 LightGBM 训练完整流程特征已经构建好后训练逻辑很简单但有几个细节直接决定结果是否可信。一是训练集和验证集必须按第 2 章切好的时间边界来取二是要明确指定特征列别把时间列和流量真值列误塞进训练矩阵三是不要随机打乱验证集。import lightgbm as lgb from sklearn.metrics import mean_absolute_error feature_cols [c for c in hourly.columns if c.startswith((lag_, roll_)) or c in (小时, 星期几, 是否周末, 是否节假日, 时段类型)] train_mask hourly[时间] train_end val_mask (hourly[时间] train_end) (hourly[时间] val_end) X_train hourly.loc[train_mask, feature_cols] y_train hourly.loc[train_mask, 车流量] X_val hourly.loc[val_mask, feature_cols] y_val hourly.loc[val_mask, 车流量] model lgb.LGBMRegressor( n_estimators500, learning_rate0.05, num_leaves31, random_state42, ) model.fit( X_train, y_train, eval_set[(X_val, y_val)], callbacks[lgb.early_stopping(50, verboseTrue)], ) val_pred model.predict(X_val) print(验证集 MAE:, mean_absolute_error(y_val, val_pred))逻辑说明这里只做了“训练到验证”的第一步测试集要留到最后再跑。早停回调会在验证集指标连续 50 轮不提升时停止训练防止过拟合。feature_cols用前缀筛选是为了把前面构造的所有滞后和滚动特征一次性纳入同时避免把时间列带进模型。参数说明n_estimators500配合早停其实足够learning_rate0.05是稳妥的默认值。如果验证集 MAE 明显偏高优先检查特征是否齐全、是否做了滞后对齐再考虑调学习率和树的复杂度。LightGBM 对缺失值有内置处理但如果特征里有大量 NaN模型训练会出现隐藏偏差所以前面dropna()是必要的。4.3 LSTM 模型的数据序列构造与训练LSTM 不吃表格它吃的是形状为(样本数, 时间步, 特征数)的三维数组。这里要处理两件事一是流量值是否标准化二是如何把一小时一条的数据切成长度为 48 的滑动窗口。常见做法是在切窗口前用 MinMaxScaler 把流量压缩到 01 之间防止数值差异过大影响收敛。from sklearn.preprocessing import MinMaxScaler import numpy as np # 用训练集部分拟合 scaler避免把测试信息提前带进来 train_series hourly.loc[train_mask, 车流量].values.reshape(-1, 1) scaler MinMaxScaler((0, 1)) scaler.fit(train_series) scaled_all scaler.transform(hourly[车流量].values.reshape(-1, 1)) def build_sequences(data, window48, horizon1): X, y [], [] for i in range(window, len(data) - horizon 1): X.append(data[i - window : i, 0]) y.append(data[i horizon - 1, 0]) return np.array(X), np.array(y) X_seq, y_seq build_sequences(scaled_all, window48, horizon1) print(LSTM 输入形状:, X_seq.shape, 目标形状:, y_seq.shape)逻辑说明scaler.fit(train_series)只用了训练段的数据拟合最小最大值这是防止数据泄露的关键一步。窗口长度 48 代表用过去 48 小时预测下一小时。i horizon - 1在 horizon1 时就是 i 本身也就是窗口结束后的那一小时。如果你想预测未来 24 小时有两种做法一种是把 horizon 设为 24另一种是保留 horizon1 的模型预测下一小时然后把预测值拼回序列递归地往后推 24 步。后者虽然实现简单但误差会累积毕设里建议至少做 24 小时和 3 小时的对比实验把误差累积现象写进论文这也是一个不错的分析点。参数说明LSTM 训练还需要把 X_seq 重塑成(样本数, window, 1)因为这里只使用了单变量流量序列如果要把多个特征也塞进去就得把特征同步切片组成三维矩阵。训练轮次建议设 50 左右配合批次大小 64观察验证集 loss 是否持续下降。如果验证集 loss 震荡严重优先降低学习率或减小窗口长度。4.4 评估指标MAE、RMSE 与分时段 MAPE模型跑完之后只看一个 MAE 是不够的。预测结果的误差在早高峰、平峰、夜间的分布差异巨大。白天流量基数大绝对误差偏大很正常夜间流量接近零一个绝对误差很小的预测可能相对误差极高。所以建议同时计算三个指标MAE 看绝对偏差RMSE 放大突出大误差MAPE 看相对偏差。但 MAPE 在真实值接近零时会出现极大值所以通常只计算流量高时段的 MAPE或者对真实值做下限截断。from sklearn.metrics import mean_squared_error def mape(y_true, y_pred, floor10): y_true np.array(y_true) y_pred np.array(y_pred) mask y_true floor return np.mean(np.abs(y_true[mask] - y_pred[mask]) / y_true[mask]) * 100 test_mae mean_absolute_error(y_test, test_pred) test_rmse np.sqrt(mean_squared_error(y_test, test_pred)) test_mape mape(y_test, test_pred) print(fMAE: {test_mae:.2f}, RMSE: {test_rmse:.2f}, MAPE: {test_mape:.2f}%)逻辑说明floor10表示只统计真实流量大于 10 的样本避免夜间小流量把 MAPE 拉爆。这样得到的指标更有代表性也是答辩时能讲清楚的关键细节。RMSE 比 MAE 对离群点更敏感如果 RMSE 明显高于 MAE说明少量时段的预测误差特别大值得单独排查。5. 交通流量预测的 5 个常见坑现象、原因与排查5.1 随机切分导致未来信息泄露现象训练集和测试集随机混合切分时模型预测结果好到离谱画出来的预测曲线几乎贴着真实值。原因随机切分把时间上相邻的样本同时分进了训练集和测试集模型等于见过测试时刻前后的小片段的未来值这种信息泄露在时间序列任务里是致命的。解决严格按时间顺序切分训练集、验证集、测试集验证一下预测曲线是否滞后于真实值一个时间段如果滞后明显说明模型本质还是在复制上一个时刻的值那就要检查特征里是否漏掉了关键滞后项或者模型是否过于依赖最近邻时刻。5.2 夜间零流量和缺失值混在一起现象数据里有连续几个小时流量为 0既有真实夜里没车的情况也有设备故障或累计数据断档的情况。模型在夜间预测得“很准”但在白天稍有问题时预测曲线会出现很诡异的阶梯状跳变。原因缺失值被当成真实零值填充模型学到“半夜就是零”一旦断档发生在白天模型会误判。解决先按时间连续性检查数据连续零值的时段如果和凌晨时段不吻合多半是缺失用前一天同一小时的数据或前后同一天的均值来补不要简单用 0 填充。补完之后画一条时间序列图用肉眼确认白天没有突兀的空洞。5.3 滞后特征没做 shift把当前值泄露给模型现象训练集和验证集上 MAE 都很低但实际预测时“预测值比真实值晚了一个时段”画图能看到明显的错位。原因构造滞后特征时直接用了当前时刻的流量模型只需要记住这个值就能在验证集上做出精确预测但真正做未来预测时当前时刻的真实值根本取不到。解决所有基于流量序列构造的特征包括滞后项和滚动统计量都必须用shift(1)或shift(n)对齐到过去自查方法很简单打印特征矩阵的最后一行确认lag_1是不是上一小时的值而不是当天的值。这个坑几乎所有做时间序列的人都踩过排查时别慌。5.4 节假日预测系统性失真现象平时各时段误差都正常但一到假期真实车流量比预测值低了三分之一或者某些景区路段完全相反。原因节假日样本在数据集中占的比例太小模型识别不出规律往往会按普通周末或工作日预测。解决把节假日作为显式特征加入输入还可以对节假日样本加权训练或在训练集中重复采样节假日数据。但要注意这样做可能降低测试集整体指标这是数据分布的真实反映在论文里如实讨论比硬调指标更有价值。解决方式上我一般先查节假日表——注意春运、黄金周等大假往往需要把节前一天和节后一天也标记为特殊时段。5.5 只报告全天 MAE不看分时段误差现象整份实验数据只有一行 MAE看着不错但答辩时被问到“早高峰预测误差多少”就答不上来。原因全天 MAE 被夜间低基数时段拉低了掩盖了高峰时段的高误差。解决把预测结果按小时的预测区间分组计算每小时的平均绝对误差画出分时段误差条形图早高峰时段出现全天最高误差是非常正常的现象关键是你能不能解释清楚比如数据量少、突发事件多、网格条件变化。排查方法是在测试集预测结果上增加预测目标时段的小时特征然后groupby(小时)[误差].mean()生成表格这比单独报一个 MAE 有用得多也更容易写进论文。6. 用滚动预测做最终验证一种能让答辩更有说服力的做法模型的最终验证不能只跑一次测试集就结束更稳妥的办法是做“滚动预测”。常见做法是拿最后 7 天数据让模型每天只用当天 0 点之前的数据作为历史依次预测之后 24 小时的流量然后把这 7 天的预测结果拼成一条连续曲线和真实曲线画在一张图里。def rolling_predict(model, df, feature_cols, start_date, steps24): work df.copy() preds [] for offset in range(0, steps): cut start_date pd.Timedelta(hoursoffset) row work.loc[work[时间] cut, feature_cols] pred_val model.predict(row)[0] preds.append((cut, pred_val)) return pd.DataFrame(preds, columns[时间, 预测流量])这段代码的思路很简单每次只用一条特征行做单步预测把预测结果存起来然后继续下个小时模拟真实使用中“到了整点才能知道前一小时真实值”的场景。和一次性预测 24 小时相比滚动预测不会因为输入被早期预测值污染而产生误差累积同时更贴近实际业务方式。用这个函数生成结果后配合 matplotlib 把真实值和预测值画在一起横轴用日期时间纵轴用流量数老师一眼就能看出白天高峰和夜间低谷的拟合情况。另外还可以加一个对比实验用“上一周同一天同时段真实值”当作朴素预测基线计算滚动预测相对基线的误差下降百分比这个数字比单个 MSE 更有说服力。做这类毕设项目时我吃过最大的亏就是在最后阶段才发现滞后特征少做了一次 shift导致验证集指标漂亮得发虚。等到重做特征、重新训练之后才发现真实误差比之前看到的翻了一倍。所以现在我养成了一个习惯每次构造完特征一定先打印最后几行肉眼确认当前行的历史特征都来自过去每次跑完模型一定画预测曲线看有没有滞后错位。数据切分和特征对齐这两个点能把控好这套基于 Python 的车辆流量预测系统就算立住了。希望这篇梳理能帮你少踩几个坑顺利把毕设跑通。本文还有配套的精品资源点击获取