ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

智慧交通流量预测实战:从数据构造到模型避坑

智慧交通流量预测实战:从数据构造到模型避坑 简介针对天池大数据竞赛中的智慧交通流量预测任务这套实现方案以贵州高速路网旅行时间预测为实战场景面向备战大数据竞赛或希望提升时空序列建模能力的开发者。资源包共56个文件以35个Python脚本为核心覆盖数据预处理、特征工程、模型训练与集成预测等完整链路。压缩包同时包含9个工程备份文件与4个XML配置文件便于还原PyCharm项目环境JSON配置与Markdown说明可辅助快速理解运行逻辑另有Git配置与模块描述文件保留工程原始结构整体体积仅68KB结构紧凑。已有32人学习。借助其中的FullCNN、RNN、ARIMA、XGBoost等模型实现以及fillDataToTensor、FeatureEngineering、EnsembleFile等脚本可快速掌握交通流数据张量化、缺失值填充、多模型融合与MAPE自验证等关键方法适合用于复现赛题方案并迁移到类似预测任务如路段通行时间估计、流量异常检测等。1. 智慧交通流量预测天池赛题背后要解决的三个现实问题天池大数据竞赛里的智慧交通流量预测拿到的是一堆路网流量和速度的历史数据要预测的是未来某条路段的车流量或平均车速。很多第一次接触这类赛题的人以为它是纯算法比拼刷一个 LSTM 就能上分实际上做得好的方案都在解决三个现实问题怎么把离散的历史流量组织成模型能学的监督样本、怎么把时间和路网结构压缩成有效特征、怎么躲开隐藏的数据穿越。这三个问题不解决模型再先进也拿不到稳定分数。这篇笔记面向打算用这套方案打比赛、做毕设或做路侧流量预测验证的工程师按从数据构造到模型上线的顺序讲解其中最容易丢分的时间切分和特征泄漏问题会单独放到避坑章。2. 赛题理解与数据认知先搞清楚你要预测的到底是什么2.1 流量预测在竞赛中通常被定义成什么任务天池这类智慧交通赛题最常见的是单步或多步回归任务。数据里给出若干路段的流量和平均速度序列时间粒度可能是 5 分钟、15 分钟或者 1 小时你要预测未来一个或多个时间点的流量值。少数赛题会带拥堵指数或按高/中/低分档本质还是回归。拿到题目第一步别急着建模先看三件事预测对象是流量还是速度预测的时间跨度是多久训练集和测试集的时间范围是否连续。这三条决定了后续构造样本的方式。如果预测的是未来 6 个时间点的流量那就是多输出回归如果只预测下一个点那是单步回归两种任务的误差评估方式完全不一样。我一般会先写一段最简单的脚本把数据按路段和时间排序后用 shift 生成滞后特征丢给 LightGBM 跑一个 baseline。这个过程通常一个小时内就能完成得到的分数可以作为后续所有改进的对照线。很多选手一上来就做复杂图神经网络反而连 baseline 都没建立最后连自己模型到底有没有学到时序模式都判断不了。2.2 拿到数据后的第一件事按路段时间做粒度检查数据文件里常见的坑是时间字段不统一、路段编号有缺失、同一时间戳下出现多条重复记录。在构造任何特征之前先对每条 link 检查时间序列是否连续、间隔是否均匀。这里说的连续不是指物理上每秒都有数据而是业务上每个统计周期都应有一条记录比如 5 分钟粒度就应每小时有 12 条。import pandas as pd df pd.read_csv(traffic_flow.csv, parse_dates[time]) # 按路段和时间排序避免后续 shift 取出乱序的滞后值 df df.sort_values([link_id, time]).reset_index(dropTrue) # 检查每个路段的时间间隔是否都是固定值 df[gap] df.groupby(link_id)[time].diff().dt.total_seconds() gap_summary df.groupby(link_id)[gap].describe() print(gap_summary.head()) # 把间隔不在正常范围的路段单独挑出来 bad_links gap_summary[gap_summary[mean] 3600].index.tolist() print(异常路段数, len(bad_links))这段代码的价值在于把数据质量问题显性化。groupby(link_id)[time].diff()是 pandas 求相邻时间差的标准写法返回每个路段内部相邻两条记录的时间间隔。dt.total_seconds()把时间差统一转成秒方便按业务周期判断。如果出现大量间隔为 7200 秒的路段说明上游采集有丢失这类路段要么补齐缺失值要么直接在训练中降权。补齐缺失值的常用做法是线性插值或前向填充但要注意如果缺失发生在节假日前后线性插值会抹掉真实的流量脉冲。我的习惯是先用前向填充保住趋势把插值标记成单独一列特征让模型自己去学缺失时刻是否特殊。2.3 最小训练集构建用 Pandas 把流量序列转成监督学习样本构造监督样本是流量预测里最核心的一步。目标是让每一行样本同时包含三部分历史时间窗口的流量值、当前时间的时间特征、未来目标值。pandas 的shift函数是完成这个转换最直接的工具。def build_supervised(df, link_id_col, time_col, target_col, lookback6): frames [] for link, group in df.groupby(link_id_col): group group.sort_values(time_col) for lag in range(1, lookback 1): group[ftraffic_lag_{lag}] group[target_col].shift(lag) group[hour] group[time_col].dt.hour group[weekday] group[time_col].dt.weekday # 目标值下一时刻的真实流量 group[target] group[target_col].shift(-1) frames.append(group) result pd.concat(frames, axis0) result result.dropna(subset[ftraffic_lag_{lookback}, target]) return result sample build_supervised(df, link_id, time, volume, lookback6) print(sample.shape) print(sample.head())这段代码有几个关键细节。一是用循环显式构造多列滞后值而不是一次性shift多列方便后续按 lag 数调试。二是dropna只检查最远滞后项和目标项这样近几天的样本不会因为早期的滞后缺失而被全部丢弃。三是目标列用shift(-1)表示下一时刻流量如果你要预测未来第 3 个点就要用shift(-3)并且预测第 3 点时要把中间两个点的滞后特征错开避免用未来信息。这里会引出流量预测最常见的翻车点训练集里如果混入了测试集时间段的数据或者用shift(-3)构造目标时把中间时刻的特征带进了样本评估分数会虚高很多真实出场效果却很差。针对这个问题我习惯在构造样本之后单独打印一条样本的完整时间戳和特征来源人工抽查三五条确认特征全部来自过去目标全部来自未来。3. 特征工程把时间、空间和天气压进一张表3.1 时间特征分钟、小时、星期、节假日哪种最有效流量预测里最稳定的强特征是周期性时间特征。工作日早高峰和晚高峰的形态完全不同周五晚高峰和周日返程高峰也各有特点。常见做法是直接从时间戳里拆出小时、星期、是否周末、是否节假日四个维度其中小时和星期的交叉组合对模型提升最明显。sample[hour] sample[time].dt.hour sample[weekday] sample[time].dt.weekday sample[is_weekend] (sample[weekday] 5).astype(int) # 小时与星期的交叉特征本质是把一周的每个小时当成独立类别 sample[hour_weekday] sample[hour] * 7 sample[weekday]hour_weekday这个交叉特征把一周 168 个小时全部编码成独立类别LightGBM 可以非常高效地在这个类别上切分。相比直接把 hour 和 weekday 两个数值特征丢进模型交叉特征更能捕获“工作日早 8 点”和“周末早 8 点”的差异。缺点是类别数多如果训练数据只有两周部分类别样本极少需要配合最低样本数约束。节假日特征在网络公开赛题中经常是隐藏变量。如果你用天池的公开数据做实验节假日信息通常不会直接给出需要根据赛题时间范围自己标注。我的做法是维护一个节假日日期列表用isin生成布尔特征再进一步构造“节假日前后三天”的缓冲特征因为假期前一天下午和假期最后一天晚上的流量形态最特殊。3.2 空间特征相邻路段的流量滞后值怎么加进去智慧交通的“智慧”很大程度体现在空间相关性上。一条路的流量往往受上游路段影响上游堵了下游的流量会在一段时间后下降。如果赛题提供了路网拓扑关系比如邻接表就可以把相邻路段的滞后值作为特征加入当前路段样本。import json # 假设邻接表格式: {link_1: [link_2, link_3]} with open(adjacency.json) as f: adj json.load(f) neighbor_feature_list [] for link, group in sample.groupby(link_id): neighbors adj.get(link, []) sub group.copy() if neighbors: # 取邻居路段同时间戳的流量前向填充缺失 neighbor_df df[df[link_id].isin(neighbors)][[time, link_id, volume]] pivot neighbor_df.pivot_table(indextime, columnslink_id, valuesvolume) # 对所有邻居做均值后取滞后表示周边整体流量水平 sub[neighbor_avg_volume] pivot.mean(axis1).reindex(sub[time]).values sub[neighbor_avg_volume] sub[neighbor_avg_volume].shift(1) neighbor_feature_list.append(sub) sample_spatial pd.concat(neighbor_feature_list, axis0)这段实现里有两个容易忽略的细节。一是pivot_table会把邻居路段的缺失时间变成 NaN均值计算默认跳过 NaN这等于隐式做了邻居流量“只有有数据才参与平均”的处理比强行填充 0 要合理。二是reindex(sub[time]).values这一步是把邻居均值对齐到当前路段的时间轴上再用shift(1)防止当前时刻的邻居流量被当作特征泄漏给模型。如果赛题没给路网拓扑也可以退一步用“全路网同时刻平均流量”作为特征。这个全局特征虽然不能体现路段差异但在节假日和大规模拥堵时可以捕捉整个路网的系统性变化实现成本极低。很多方案里它的重要性排在小时特征之后属于性价比很高的低成本空间特征。3.3 滑动窗口统计特征与目标编码除了直接滞后值滑动窗口的均值、标准差、最大值和最小值也是流量预测里常用的特征。它们反映的是最近一段时间路段的稳定程度和波动水平对于判断是否处于突发拥堵很有帮助。sample[volume_roll_mean_3] sample.groupby(link_id)[volume].transform( lambda x: x.shift(1).rolling(3).mean() ) sample[volume_roll_std_3] sample.groupby(link_id)[volume].transform( lambda x: x.shift(1).rolling(3).std() )注意这里用的是shift(1).rolling(3)而不是直接rolling(3)原因是计算当前时刻的窗口特征时当前时刻的真实流量还没有“发生”如果直接用包含当前时刻的窗口统计特征就相当于让模型偷看了答案。流量预测里这种细微的泄漏极其常见尤其在用rolling做滑动平均时漏掉shift会导致线上分数比线下高百分之十以上。目标编码的做法是按时间段或路段分组用历史目标均值替代类别特征。比如把“路段 ID”编码成“该路段历史平均流量”可以显著减少类别特征在高基数下的稀疏问题。但目标编码最大的风险是过拟合必须用交叉验证在训练集内部计算编码值并做平滑处理。平滑公式通常是(group_sum global_mean * alpha) / (group_count alpha)alpha 取 20 到 100 之间效果比直接用groupby().transform(mean)稳定。4. 模型选型与调参LightGBM 还是时序模型4.1 表格特征模型LightGBM 训练与 Early Stopping流量预测的特征工程做完之后表格特征模型往往是投入产出比最高的选择。LightGBM 对缺失值、数值量纲、特征交叉都有很强的鲁棒性训练速度快调参空间也相对宽容。在大多数天池流量赛题中一个调好的 LightGBM 就能达到前 20% 的水平不需要一上来就上深度学习。import lightgbm as lgb feature_cols [c for c in sample_spatial.columns if c not in [link_id, time, target]] X sample_spatial[feature_cols] y sample_spatial[target] # 按时间顺序切分前 70% 训练后 30% 验证 split_idx int(len(X) * 0.7) X_train, X_valid X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_valid y.iloc[:split_idx], y.iloc[split_idx:] model lgb.LGBMRegressor( n_estimators2000, learning_rate0.05, num_leaves63, colsample_bytree0.8, subsample0.8, subsample_freq1, random_state42, verbose-1, ) model.fit( X_train, y_train, eval_set[(X_valid, y_valid)], callbacks[lgb.early_stopping(100), lgb.log_evaluation(200)], )early_stopping(100)表示验证集分数连续 100 轮不再提升就停止能有效防止过拟合。这里的验证集切分方式是有意为之直接用时间顺序切分而不是随机切分。随机切分会把未来时间段的数据混进训练集导致模型“看过未来”验证分数虚高。如果你手头数据覆盖了好几个月可以考虑用最后一周做验证集训练集用更早的时间段这样更贴近真实上场时的分布。4.2 序列模型用 LSTM 做多步预测的输入输出组织如果赛题的时间跨度长、路段多或者你发现滞后特征无法捕捉长时间依赖可以考虑序列模型。LSTM 在交通流量预测里是经典选择但它的效果强依赖数据构造方式。组织序列样本时输入是过去 N 个时间点的流量序列输出是未来 M 个点或者先只预测一个点再滚动生成多步。import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Input def build_sequence_dataset(series, input_len12, output_len3): X, y [], [] for i in range(len(series) - input_len - output_len): X.append(series[i:i input_len]) y.append(series[i input_len:i input_len output_len]) return np.array(X), np.array(y) # 用单个路段的流量序列示例 one_link_series df[df[link_id] link_1][volume].values X_seq, y_seq build_sequence_dataset(one_link_series, input_len12, output_len3) model_lstm Sequential([ Input(shape(X_seq.shape[1], 1)), LSTM(64, return_sequencesTrue), LSTM(32, return_sequencesFalse), Dense(16, activationrelu), Dense(y_seq.shape[1]), ]) model_lstm.compile(optimizeradam, lossmse) model_lstm.fit(X_seq, y_seq, epochs30, batch_size64, validation_split0.2, verbose0)build_sequence_dataset这个函数的作用是把一维流量序列切成固定长度的输入输出对。input_len12表示用过去 12 个时间点预测未来 3 个点这种“未来输出多个点”的方式比单点预测然后滚动拼接的好处是训练时每个样本的 loss 是未来整段的平均误差多步预测的误差累积能被直接优化。LSTM 的return_sequencesTrue表示第一层 LSTM 输出每个时间步的隐状态第二层return_sequencesFalse只输出最后一个时间步的状态再经过全连接层映射到未来 3 个预测值。数据量较小的情况下两层 LSTM 加一层全连接已经够了把 LSTM 单元数加到 128 或 256 并不会稳定提升精度。4.3 参数怎么设LightGBM 和 LSTM 的必调参数LightGBM 里优先调的是num_leaves、learning_rate和min_data_in_leaf。num_leaves控制在 31 到 127 之间太大会轻微过拟合learning_rate从 0.05 开始配合较高的n_estimators才能发挥 Early Stopping 的作用min_data_in_leaf设成 20 到 50防止叶子节点样本太少导致预测值极端。LSTM 里最影响结果的是input_len、batch_size和learning_rate。input_len太小只看到短时波动太大则引入大量噪声我之前在 5 分钟粒度的数据上用 12 到 24 个时间点作为输入窗口效果最好。batch_size设 32 或 64 即可过大容易收敛到平坦的局部最优。学习率 0.001 是通用起点如果 loss 震荡明显就降到 0.0005。还有一个小技巧序列模型输入前一定要做归一化把流量值缩放到 0 到 1 或做标准化。LSTM 内部的 tanh 和 sigmoid 激活函数对输入量纲敏感原始流量数值从几十到几千波动不归一化时模型很难收敛。归一化之后预测结果要记得反变换回真实流量尺度再计算误差。5. 交通流量预测的避坑指南5 个让分数翻车的细节5.1 数据穿越用到了“未来”的滞后特征现象线下验证集分数极高但提交线上后分数暴跌或训练过程 loss 降得异常快。原因构造滞后特征时把目标时刻之后的数据也shift进了特征矩阵。常见于取“未来第 3 个点”为目标时错误地用中间时刻的真实值填充了缺失特征。解决构造每个样本前先按时间排序再逐路段确认所有特征列的时间戳都早于目标时间戳。检查方法很简单打印一条样本的事件时间线和特征来源肉眼扫一遍即可。更可靠的方案是用sklearn的TimeSeriesSplit做时序交叉验证它能保证每次训练都只用更早的数据。5.2 随机切分训练集和验证集现象LightGBM 验证集 loss 非常低但实际预测误差高。原因验证集是随机抽取的意味着验证集里包含了与训练集同一时间段的数据。流量具有强自相关性模型在时间上“见过了”验证集附近的样本。解决严格按时间切分。我用 70% 训练、15% 验证、15% 测试且验证集时间必须晚于训练集测试集时间必须晚于验证集。不要用train_test_split默认的随机切分。如果你想追求稳妥可以用TimeSeriesSplit做多折交叉验证取均值作为最终评估。5.3 节假日和大型活动日被当作普通工作日现象在工作日预测效果不错但逢节假日误差突然增大。原因模型没有节假日特征把假期的流量模式强按工作日模式预测。节假日流量形态完全不同于工作日且不同路段的响应方向可能相反景区周边上涨、办公区下跌。解决在特征表里增加节假日标签和节假日前后一天标签。如果赛题没有给就自己根据时间段推断或者把“目标日期是否在节假日”“距离最近节假日天数”作为两个新特征。周末效应用is_weekend捕获但法定节假日的调休规则比较复杂需要单独处理。5.4 多步预测误差累积导致远点预测失真现象预测未来第 1 个点误差还行第 2 个点误差变大第 3 个点几乎不可用。原因多步预测模型在训练时直接优化多步 loss但推理时如果采用单步预测滚动拼接的思路前一步的预测误差会作为输入影响下一步误差逐点放大的问题就会暴露出来。解决训练和推理保持一致。训练时直接输出未来多个时间点推理时也一次输出多步不滚动拼接。如果模型只能单步输出就在预测时用真实历史数据做输入而不是用上一步的预测值回填如果没有后续真实数据则退而求其次用预测值但要意识到远点误差会偏高产出预测区间而不是单点数值可能更有业务意义。5.5 缺失路段直接丢弃导致预测结构不完整现象模型在部分路段上完全没有预测结果提交时报“link_id 缺失”。原因对不同路段分别训练模型时没有把所有路段都纳入训练集或者预处理阶段把有缺失的路段直接删掉了。流量数据缺失很常见但竞赛和业务系统都要求每个路段都要有预测结果。解决先对缺失流量做前向填充单独加一列“该时刻是否有缺失”的标记特征。保留所有在测试集中出现的路段即使训练数据不完整也要推一个值。如果某个路段几乎全程缺失就用全路网同路段类型的均值兜底保证输出结构完整。6. 从竞赛到落地把预测结果接到实际业务系统的关键一步竞赛做完后要落地最关键的改造是预测接口和评估方式。竞赛只需要离线评估一次分数真实系统则需要每天定时预测并输出未来若干时间点的流量。常见做法是把训练好的模型参数固化下来用当前时间窗口的实时数据作为特征输入预测未来 3 到 6 个时间点结果写入下游数据库供信号灯调度或 App 路况模块消费。我处理这类落地时会做一个轻量级滚动预测模块。它接收实时流量表内部维护每个路段最近 12 个时间点的流量序列先构造滞后和滚动窗口特征再调用训练好的 LightGBM 或 LSTM 模型预测下一个时间点并把预测值拼接进实时表作为下一轮预测的历史输入。这个模块最需要注意的是特征列名必须与训练时完全一致一个列名或顺序的差异就会让模型输出完全无法解释的预测值。评估指标上竞赛常用的 MAE 和 RMSE 可以直接复用但真实业务里建议额外关注高峰时段的误差因为早高峰晚高峰的流量绝对值高同样的相对误差在高峰期造成的拥堵预警偏差更大。我的习惯是分别统计工作日高峰时段和非高峰时段的 MAE高峰时段误差高就考虑在训练样本中加权高峰时段数据。真实系统还会遇到数据中断的情况所以落地时一定要为模型准备降级策略。当实时数据延迟超过 30 分钟时我见过的可靠做法是切换成按星期和时间段的平均历史值作为预测输出并在结果上标记置信度低等数据恢复后再切回模型预测。这套方案做下来最深刻的教训是流量预测的问题从来不在模型的复杂程度而在数据和时间关系的一致性处理上。你精心设计的 LSTM 可能还不如一个干净的 LightGBM因为后者对时间泄漏和缺失值的处理更宽容。每次开始新的流量预测任务之前我建议你先花半天时间把数据按时间顺序画成折线图肉眼看清楚流量形态是日周期主导还是周周期主导这个习惯能帮你避开至少三分之一无效的建模方向。希望这篇文章对你在天池或真实交通场景里做流量预测有所帮助。本文还有配套的精品资源点击获取
返回列表