资讯动态

车流量预测实战指南:从数据清洗到LSTM与LightGBM模型选型

发布时间:2026/10/12 20:46:51 来源:尧图企业网站定制
简介车流量预测是智慧交通与城市管理中的典型时间序列任务资源基于 Python 与 Keras 实现了完整建模方案面向交通工程、数据科学初学者及课程设计群体。资源提供 LSTM、GRU、BiLSTM、SAEs 和岭回归等多种对比实现覆盖数据清洗、补缺、滑动窗口构造与模型训练评估可直观看出不同结构在车流预测任务上的表现差异。压缩包共三十八个文件大小约四点六七兆字节。其中十个 ipynb 笔记完整记录实验过程与调参思路八个 csv 文件存放原始流量与训练损失六个 h5 为训练好的模型权重五个 png 是损失曲线与预测效果图另含两个 py 脚本和一个 README目录分层清晰便于按步骤复现。目前已有两千六百八十五人学习适合作为课程设计参考或入门深度时序预测的练习材料。通过阅读代码与笔记可掌握 Keras 中 RNN 系列模型的搭建套路、训练参数调整方法以及回归评估思路为后续研究交通流、客流量等类似问题提供可迁移的实践基础。1. 车流量预测到底难在哪先看清数据集和业务口径做车流量预测的人一上来就想着上模型十有八九会翻车。我见过太多团队拿到几千万条路口卡口数据直接套 LSTM 或者 LightGBM训完一看 MAPE 高得离谱最后才发现是数据里时间戳错位、车道方向没拆分、节假日没单独处理。车流量预测的核心问题不是“用哪个模型”而是“你要预测哪条路的哪个方向、什么粒度、谁在看这个预测”。城市道路车流有强周期但这周期会被天气、事故、大型活动撕碎。城区主干道和高速匝道的车流形态完全不一样早高峰和晚高峰的波动也不是简单双峰能描述的。这篇文章会沿着数据清洗、特征构造、模型选型、评估口径这条线走一遍每一步都给可直接抄的 Python 脚本也把那些只会在真实数据集上冒出来的坑说透。适合想用 Python 做交通流预测的算法工程师、数据分析师也适合刚入门但不想只在 Kaggle 上跑通 Demo 的人。2. 从原始卡口数据到可用训练集时间对齐、缺失值、离群点处理是第一个分水岭2.1 先认清车流量数据的自然形态车道、方向、统计粒度决定模型上限车流量数据的源头一般是地磁线圈、视频卡口、微波雷达。这些设备上报的原始数据通常是“每条车道每 5 分钟或每 15 分钟过车数”附带平均车速、时间占用比Occupancy、车道编号。做预测前你必须先明确业务上要预测的粒度总流量和单车道流量的波动特征不同预测难度也不同。常见做法是把上游几百米的断面流量合并成“路段进入流量”但这会掩盖车道级潮汐现象。我一般会保留 lane_id 和 direction 字段做聚合至少按小时和按 15 分钟各出一份镜像数据测试时再決定用哪个粒度。数据时间戳是另一个容易出问题的地方。设备时钟漂移会导致同一时段的数据在边缘错位节假日和夏令时切换更麻烦。原始 CSV 里的 timestamp 可能是字符串、可能是 Unix 时间戳、也可能是带时区的 ISO 格式。第一道工序永远是把时间戳解析成 pandas.DatetimeIndex并强制落到本地时区接着做排序和去重。数据里出现同一时刻两条记录、或者同一时刻多个车道记录缺失都必须在建模前解决否则后面所有特征工程和滑窗构造都会错位。2.2 清洗流水线统一时间戳、按设备补齐时间轴、缺失值插补的最小代码先看一段可复用的清洗函数。它做的事情是读取原始 CSV把 timestamp 统一成YYYY-MM-DD HH:MM:SS按时间和车道分组去重再对缺失的统计时段做插补。import pandas as pd import numpy as np def load_traffic_data(csv_path, time_coltimestamp, value_cols[volume]): df pd.read_csv(csv_path) # 统一时间戳格式先转成 datetime再强制本地时区去除 UTC 偏移 df[time_col] pd.to_datetime(df[time_col], errorscoerce) df[time_col] df[time_col].dt.tz_localize(None) df df.sort_values([time_col]).drop_duplicates(subset[time_col, lane_id, direction]) # 生成完整时间轴按 lane_id direction 分组每个分组重新采样到 15 分钟 df df.set_index(time_col).groupby([lane_id, direction]).resample(15T).asfreq().reset_index() # 数值列前向填充 线性插值兜底 for col in value_cols: df[col] df[col].replace(0, np.nan).fillna(methodffill).interpolate(methodlinear, limit_directionboth) return df这里的逻辑重点在两个地方。第一drop_duplicates必须在排序之后做否则保留的是原始文件里随机顺序的那条记录可能导致同一时刻取了错误车道的数。第二resample(15T).asfreq()会把缺失的统计时段显式变成 NaN这样你才能在插补前区分“设备没上报”和“真的没车通过”。把 volume 为 0 视为缺失要谨慎夜间低谷流量为 0 是正常的但连续 6 个时段全是 0就要怀疑设备断线了。对这种连续全 0 的段我常按同星期、同时段的 7 日历史均值去填单纯线性插值会让午夜流量也形成一条直线。2.3 节假日、天气、特殊事件这些外生变量不入特征模型再深也白搭车流量预测模型如果只用历史 volume 序列做自回归节假日当天的预测必然失真。春节、国庆这类长假城市道路车流会显著下降但火车站、机场周边反而会暴涨。要做的是一个节假日特征表包含日期、节假日类型、是否调休上班。把节假日特征和天气特征温度、降水强度、能见度一起 join 到流量数据上再做滞后。天气数据按小时匹配即可不需要精确到分钟。特别注意“前一天是否放假”这个特征比“今天是否放假”更有用因为车流变化往往发生在放假前一天的晚高峰。外生变量的构造有个时间窗口问题。预测 T1 小时的车流时你只能用 T 时刻之前已知的天气和节假日信息。千万不要把未来天气直接当特征喂进去除非你的预测系统接了气象预报接口并且在线上推理时真的能拿到这些字段。否则训练时有未来数据、推理时没有模型离线指标虚高上线直接崩。常见做法是把“天气预报”和“实测天气”视为两个特征源训练时用实测推理时用预报并在评估时引入噪声来模拟预报误差。3. 车流量预测模型怎么选ARIMA 做基线、LightGBM 抓非线性、LSTM 处理长周期3.1 先把基线跑起来使用 statsmodels 的 SARIMA 模型进行季节性差分不要一上来就上深度学习。车流量序列有日内周期、周内周期SARIMA 模型能帮你快速摸清数据的季节长度和稳定性。道路车流基本是工作日和周末两种模式周季节性s7*96如果数据是 15 分钟粒度会让模型参数规模膨胀。更稳的办法是先把数据聚合到小时粒度用s24或s168做周季节。from statsmodels.tsa.statespace.sarimax import SARIMAX # 假设 data 是小时粒度单车道方向 volumes 序列 model SARIMAX( data[volume], order(1, 1, 1), seasonal_order(1, 1, 1, 24), enforce_stationarityFalse, enforce_invertibilityFalse ) fit model.fit(methodlbfgs, maxiter200) print(fit.summary())SARIMA 的三个参数组合大多靠 AIC 去选不要在网格搜索上花太多时间。这里order(1,1,1)表示自回归、差分、移动平均各一阶seasonal_order里的24是周期长度。如果你的数据粒度是 15 分钟周期就是 96但 96 阶的季节差分很容易过拟合预测步长超过半天后误差会迅速放大。SARIMA 适合做“未来 24 小时”的流量预测基线不适合做实时滚动多步预测因为每滚动一步都要重新拟合耗时不可控。3.2 LightGBM 路线滞后特征、滑动窗口统计特征、外部特征联合训练梯度提升树在车流量预测里非常有优势因为它对特征尺度不敏感可以同时吃数值特征和类别特征。用 LightGBM 做车流量预测的最小特征集通常包括历史同期流量前 1 小时、前 24 小时、前 168 小时、滑动平均、滑动标准差、时间编码小时、星期、是否周末、节假日编码。import lightgbm as lgb def build_features(df): df df.sort_values(timestamp).copy() # 滞后特征直接用 shift 构造 df[lag_1h] df[volume].shift(4) # 15分钟粒度4个时段前 df[lag_24h] df[volume].shift(96) # 一天前同一时段 df[lag_168h] df[volume].shift(672) # 一周前同一时段 # 滑动窗口统计过去1小时的均值与标准差 df[roll_mean_1h] df[volume].rolling(4).mean().shift(1) df[roll_std_1h] df[volume].rolling(4).std().shift(1) # 时间编码 df[hour] df[timestamp].dt.hour df[weekday] df[timestamp].dt.weekday df[is_weekend] (df[weekday] 5).astype(int) return df.dropna() train_data build_features(train_df) features [lag_1h, lag_24h, lag_168h, roll_mean_1h, roll_std_1h, hour, weekday, is_weekend] model lgb.LGBMRegressor( n_estimators500, learning_rate0.05, num_leaves64, subsample0.8, colsample_bytree0.8, random_state42 ) model.fit(train_data[features], train_data[volume])注意shift的次数必须大于预测步长。如果你要预测未来 15 分钟的流量shift(1)都不能用因为当前时刻的流量在预测时还没有发生。shift(1)作为特征会让模型在训练时作弊测试时天然不可用。这是时间序列预测最容易犯的错误。另一个点是滑动窗口特征也要 shift(1)保证窗口统计只使用历史信息。LightGBM 的num_leaves不宜过大车流量数据有噪声太大的树容易把某个时段的尖峰死记硬背下来。特征重要性出来后重点看 lag_168h 和 hour如果这两个特征排名不高说明数据清洗仍存在问题。3.3 LSTM 路线用滑窗把时间序列转成监督学习处理多步预测LSTM 适合做多步序列预测但车流量数据长度大、噪声强纯序列模型容易收敛慢。更稳定的做法是把 LSTM 当作特征提取器后面接一个全连接输出层并且只用它做短期预测未来 1 到 3 个时段。训练时要用 MinMaxScaler 做归一化LSTM 对输入尺度非常敏感。import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense from sklearn.preprocessing import MinMaxScaler def create_sequences(data, seq_len48, pred_len4): X, y [], [] for i in range(len(data) - seq_len - pred_len 1): X.append(data[i:iseq_len]) y.append(data[iseq_len:iseq_lenpred_len]) return np.array(X), np.array(y) # 假设 values 已经按时间排序且仅包含 volume 单一变量 scaler MinMaxScaler(feature_range(0, 1)) scaled scaler.fit_transform(values.reshape(-1, 1)) X, y create_sequences(scaled, seq_len48, pred_len4) # 划分训练集/测试集注意不要 shuffle split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] model Sequential() model.add(LSTM(64, return_sequencesTrue, input_shape(X.shape[1], X.shape[2]))) model.add(LSTM(32, return_sequencesFalse)) model.add(Dense(pred_len)) model.compile(optimizeradam, lossmse) model.fit(X_train, y_train, batch_size64, epochs30, validation_split0.1, verbose1)seq_len48表示用过去 48 个时段约半天预测未来 4 个时段如果你的数据粒度是 15 分钟这等价于预测未来 1 小时。LSTM 必须设置shuffleFalse否则验证集里会有未来信息。return_sequencesTrue表示第一层输出每个时间步的隐藏状态堆叠第二层时只取最后一步输出做全连接。实际使用中纯 LSTM 的误差通常略高于 LightGBM但 LSTM 的好处是可以直接输出多条未来轨迹的联合分布如果改成分位数回归这在信号灯配时需要多时段联合预测时很有价值。3.4 四个模型放在一起怎么选精度、训练耗时、推理延迟、可解释性对比模型精度表现训练耗时推理延迟可解释性适用场景SARIMA中等周末节假日偏差大秒级秒级很高短基线、快速验证LightGBM高特征到位可到最优分钟级毫秒级高中短期预测、生产主力LSTM中高训练充分可达最好小时级毫秒级低多步联合预测、科研对比Prophet中事件驱动效果一般分钟级毫秒级高快速原型、展示趋势实际生产里我见到的车流量预测系统大部分是 LightGBM 或者 XGBoost 扛主力LSTM 做兜底对比。原因是树模型对特征缺失的耐受度高支持分位数训练可以做不确定性输出。深度学习模型需要维护一套数据管道和 GPU 调度如果业务上只需要未来 15 分钟的流量预测这是个高射炮打蚊子的配置。4. 评估指标与排查清单MAE、RMSE、MAPE 各有脾气预测毛刺比平均误差更致命4.1 用 sklearn 计算 MAE、RMSE、MAPE并输出分时段误差表评估车流量预测模型不能只看全局 MAE。夜间流量低绝对误差小高峰流量高绝对误差大。统一看 RMSE 会被高峰主导看 MAPE 又会被夜间小流量值放大到失真。更合理的做法是按小时分桶统计误差输出一张分时段误差表。下面是评估脚本。from sklearn.metrics import mean_absolute_error, mean_squared_error def evaluate(y_true, y_pred, timestamps): y_true np.asarray(y_true) y_pred np.asarray(y_pred) mae mean_absolute_error(y_true, y_pred) rmse np.sqrt(mean_squared_error(y_true, y_pred)) # 只对非零真实值计算 MAPE防止除零 mask y_true ! 0 mape np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100 eval_df pd.DataFrame({true: y_true, pred: y_pred}) eval_df[timestamp] pd.to_datetime(timestamps) eval_df[hour] eval_df[timestamp].dt.hour hourly eval_df.groupby(hour).apply( lambda x: pd.Series({ mae: mean_absolute_error(x[true], x[pred]), mape: np.mean(np.abs((x[true] - x[pred]) / x[true].replace(0, np.nan))) * 100 }) ) return {mae: mae, rmse: rmse, mape: mape}, hourly这段代码里 MAPE 的 mask 是必须的。车流量数据必然有夜间低峰期 value0 的记录如果不对真实值做 maskMAPE 会算出一个天文数字没有任何参考价值。分时段误差表能告诉你模型是早高峰崩了还是夜间崩了。如果夜间 MAE 很低但 MAPE 高说明预测值和真实值之间只差几辆车对业务没有影响不必为此加大模型容量。4.2 避坑章节车流量预测必须躲开的 4 个实战坑坑一训练测试随机切分未来信息泄漏。现象是离线测试 MAPE 只有 8%上线后实际误差翻倍。原因是代码里用了train_test_split默认的随机切分导致训练集里混入了测试时段前后的数据。车流量序列的滞后特征会把未来值间接带进训练集。解决方法是严格按时间顺序切分测试集必须是时间上完全靠后的连续区间。血泪经验是砍掉所有随机种子相关的切分操作只保留df[time] cutoff作为切分条件。坑二节假日当作普通工作日训练。现象是模型在国庆、春节期间的预测误差是平日的 3 倍以上。原因是节假日车流模式完全变化早高峰消失景区周边流量异动。解决方法是把节假日样本单独分出或者对节假日样本增加权重。最稳的做法是训练两个模型工作日模型和节假日模型节假日模型用过去 3 到 5 年的同期数据训练。如果样本量不足就至少把节假日特征放进去并且用因果推理验证这个特征确实被树模型使用。坑三滚动预测时误差累积越预测越偏。现象是预测未来 1 小时误差 5%预测未来 6 小时误差 20%。原因是多步预测使用上一步的预测值作为下一步的输入误差指数级放大。解决方法是把多步预测改为直接多输出模型即一次性输出未来 6 个时段的预测值而不是滚动递归。LightGBM 可以直接用multi_output训练或者为每个预测步长单独训练模型。LSTM 侧则是pred_len设置为 6训练时直接监督多步输出。坑四设备故障导致的连续零值没处理模型把零值当规律。现象是某路段数据连续 10 天为 0模型预测结果也趋近于 0但实际该路段只是设备断网。解决方法是数据清洗阶段做连续性检测如果一个时段组的零值连续超过 N 个小时标记为设备故障整段置为缺失并用历史同期数据插补而不是保留零值让模型学习到“该路段没有车”的错误规律。4.3 用残差图定位异常时段快速判断模型是否拟合了流量尖峰训练完模型后把测试集残差真实值减预测值按时间排序画出来能直观看到误差是否集中在特定时段。残差图如果显示早晚高峰时段误差呈尖刺状说明特征里缺少高峰时段的信号比如信号灯配时变化、学校上下学时间。这时不要急着调模型先回看特征表把“是否处于高峰窗口”这个二值特征加进去。残差如果在周末全面上偏说明节假日特征的取值粒度太粗需要细分到“周末午后”这种组合。这些排查手段比盲目调参有效得多。5. 进阶落地技巧把模型接到实时数据管道用滚动重训对抗概念漂移车流量预测模型上线后最大的敌人不是精度而是概念漂移。道路施工、公交专用道启用、共享单车大规模投放、附近商场开业都会让车流模式发生永久性改变。传统做法是每天凌晨用前一天的全量数据重新训练一次模型但全量重训的成本高、影响面大。我常用的策略是增量训练LightGBM 支持init_model参数可以在原有模型基础上继续拟合新增数据。import lightgbm as lgb # 加载前一天保存的模型 model lgb.Booster(model_filetraffic_model.txt) # 构造今天的增量数据从当天零点到当前时刻的特征 new_X build_today_features() new_y get_today_volume() # 增量训练num_boost_round 控制新增树的数量注意不要设太大 model lgb.train( params, lgb.Dataset(new_X, labelnew_y), num_boost_round20, init_modelmodel ) model.save_model(traffic_model_v2.txt)增量训练的数字要把握好。num_boost_round20表示只增加 20 棵树如果当天有突发事故导致流量异常这 20 棵树会猛学异常模式之后几天正常数据反而被带偏。我的习惯是增量训练后做一次快速验证用最近 7 天的留出数据对比新老模型的误差如果增量后 MAE 上涨超过 2%回滚到前一天的模型并把当天数据标记为异常日。这个回滚操作就是时间序列模型特有的“后悔药”一定要在训练脚本里自动保留上一版模型文件别覆盖。特征监控是另一个容易忽略的点。生产环境要记录每天特征分布的变化特别是lag_168h这类周同期特征。如果连续几天同一时段的 lag_168h 值全部偏低可能需要检查上游数据接口是否延迟。这里可以用 pandas 的describe()输出每日特征统计并和近 30 天的均值做差。差值超过 3 倍标准差时触发告警不用每次都靠人眼看曲线。最后说一个习惯每次模型迭代我都会把测试集误差和“当前正在使用的模型误差”打成一个对比表附上时间段起止。这个习惯帮我避免了很多次“换了新模型但效果变差”的尴尬。车流量预测没有一劳永逸的方案只有不断校准数据、滚动更新模型、持续监控特征分布才能保持稳定。真心希望这篇文章能帮你少踩几个坑把预测模型稳稳落地到真实道路上。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑