
做交通流预测最容易翻车的地方不是模型而是数据。这两年被各种实验折腾下来我越来越觉得“数据决定上限模型只是逼近上限”这句话在交通时序任务里特别真实。所以我打算把交通流预测的爬坑经历整理成一个系列第一篇先聊最基础、也最容易被忽视的部分交通流数据集和原始数据。这篇写给两类人看一是准备入门交通流预测、正在纠结用什么数据跑实验的研究生或工程师二是已经有模型跑通、但总觉得结果不稳定想回头检查数据是否存在隐患的同学。文章里不会有花哨的模型推导只讲我在METR-LA、PEMS-BAY、PEMS04这些常用数据集上实际摸爬滚打的经验包括原始字段含义、时区陷阱、缺失值处理、矩阵构建和训练集划分。如果你刚开始接触这类任务看完这篇至少能少熬三个通宵。1. 交通流预测的起点先把原始数据搞明白1.1 为什么先写数据集而不是模型我之前见过不少同学一上来就复现STGCN、Graph WaveNet这些模型代码跑通之后就急着调参结果无论怎么调测试集指标都停在某个水平上不去。后来我把他们用的数据要过来查了一遍发现有的数据已经缺失了三分之一有的传感器通道顺序和邻接矩阵对不上有的甚至训练集里混进了未来时刻的统计量。模型再优秀喂进去的是脏数据输出自然就是垃圾。交通流预测跟图像分类不太一样。图像分类的样本独立性很强一张图坏了顶多丢一个样本交通流数据是高度时空相关的一个传感器坏掉影响的不仅是一个点的缺失还会通过邻接矩阵污染整片路网的学习过程。更麻烦的是交通流数据的时间依赖特性决定了你必须严格按照时间顺序处理数据任何随机打乱或者全局归一化都可能把未来的信息泄漏进训练阶段导致验证集指标虚高上线后直接崩溃。所以我想把这个系列的第一篇放在数据上先把原始数据这个地基打牢。1.2 交通流预测的输入输出到底是什么做实验之前先把任务定义说清楚。交通流预测通常指的是短时预测利用过去一段时间窗口比如过去1小时的路网观测数据预测未来一段时间比如未来15分钟、30分钟、1小时的交通状态。这里的交通状态可以是流量、速度、占有率也可以是通行时间。如果用数学语言描述模型输入通常是一个三维张量形状是N, T_in, C其中N是传感器数量T_in是历史时间步数C是特征维度。输出一般有两种一种是单步预测输出N, T_out, 1预测未来若干个时间步的速度或流量另一种是序列生成像自回归一样把预测结果再喂回模型。不管哪种方式数据准备的最终目标都是把这个三维样本组织好同时保证时间上不乱序、空间上不错位。很多人以为拿到公开数据集后就能直接训练实际远没有这么简单。我下面会详细拆解主流数据集的特点和原始数据的常见问题。2. 主流交通流数据集选型2.1 METR-LA 和 PEMS-BAY避不开的两个基准交通流预测方向如果只选两个数据集那一定是METR-LA和PEMS-BAY。这两个数据集在DCRNN、Graph WaveNet、STGCN等经典论文里反复出现已经成为公认的基准。METR-LA来自洛杉矶县高速公路的传感器采集系统包含207个传感器数据时间范围主要集中在2012年3月1日到6月30日采样间隔5分钟。PEMS-BAY来自加州湾区包含325个传感器数据时间范围是2017年1月1日到5月31日同样是5分钟间隔。这两个数据集的原始文件格式非常相似常见的公开版本包含一个传感器列表文件包含传感器编号、经纬度和一个数据矩阵文件每一行是一个时间戳每一列是一个传感器值为该时刻的速度或流量。很多论文用的是速度值预测目标也是速度。我在实际实验中发现METR-LA的数据缺失率比PEMS-BAY高而且部分传感器存在连续多天无数据的现象处理起来要更小心。下面是两个数据集的对比项目METR-LAPEMS-BAY区域洛杉矶县加州湾区传感器数量207325时间范围2012.03-2012.062017.01-2017.05采样间隔5分钟5分钟数据来源Loop检测器检测器常用预测目标速度速度缺失情况相对较高相对较低2.2 国内自采数据和 PeMS 系列怎么选METR-LA和PEMS-BAY虽然是基准但很多做实际项目的人更关心国内路网的数据表现。这时候一般有两条路一是用国内的公开数据集比如某些高校或企业开放的出租车轨迹数据、卡口数据二是自建采集系统通过地磁、视频检测或者浮动车GPS回传数据。另外加州PeMS官网还提供了PEMS03、PEMS04、PEMS07、PEMS08等一系列数据集这些也是5分钟采样包含流量、速度、占有率三个字段传感器数量从几百到上千不等。PEMS08有170个传感器PEMS04有307个PEMS03有358个。这类数据集的优点是字段更全、时间跨度更长缺点是原始数据文件很大而且官网的下载方式需要一点耐心去摸索。如果你需要做多传感器大范围预测PEMS04或PEMS08会是不错的选择。选数据集的逻辑其实很简单如果目的是发论文、做横向对比老老实实用METR-LA和PEMS-BAY如果目的是做实际项目尽量用本地真实路网数据哪怕数据质量没那么好至少能暴露真实工程里会遇到的问题。2.3 拿到数据集后的第一件事不管从哪个渠道拿到数据我建议第一件事不是写模型而是先写一个数据体检脚本把原始文件的大小、行数、列数、时间范围、缺失率、传感器数量全部打印出来。这个习惯帮我避免过很多次“实验到一半发现数据加载错误”的尴尬。比如我初次接触METR-LA时用pandas读数据矩阵后发现行数并不是完整四个月逐5分钟的数量原因是原始数据已经做了去重和部分清洗。如果不先看基础统计后面构造时间序列时就会出现索引错位模型训练出来的结果完全不可信。提示数据体检的结论最好单独存一个文本文件标注数据版本、来源、处理日期、统计结果方便以后写论文或复盘时查找。3. 原始数据长什么样字段、时区和缺失值3.1 数据字段详解以PeMS系列原始数据为例一条检测器记录通常包含以下字段时间戳Timestamp、检测器编号Station ID、流量Flow、速度Speed、占有率Occupancy。流量表示单位时间内通过检测器的车辆数单位通常是辆/小时速度是检测器覆盖区间的平均速度单位通常是英里/小时占有率表示检测器被车辆占用的时间比例可以在一定程度上反映拥堵程度。METR-LA和PEMS-BAY的公开预处理版本简化了这些字段一般只保留速度矩阵和传感器经纬度。但如果你自己处理PeMS原始CSV会发现原始数据里有大量重复值和时间戳跳变。比如一个检测器可能同时上报多条车道的数据你需要按检测器聚合或者决定是否要跨车道求平均。字段含义不清楚的话很容易在数据清洗时做出错误判断。比如把流量值当成速度去做归一化或者把占有率不为零的时刻判断成“有车通过”而忽略速度为零的异常情况。建议先画几个传感器的时序曲线看看数值范围是否合理。3.2 时间戳和时区最容易踩的第一个坑时区问题是我见过最多的坑没有之一。美国交通数据的原始时间戳很多是本地时间比如PeMS使用的是太平洋时区METR-LA的数据如果直接读字符串而不转换就可能与UTC时间混淆。你要是用这些时间戳去跟别的数据源做时间对齐比如匹配天气数据或者事故数据偏差两个小时可能就让相关性分析完全失真。我在处理PEMS-BAY时遇到过一种情况数据文件里的时间戳是字符串形式如“2017-01-01 00:00:00”如果pandas直接按字符串排序在跨月、跨年时可能没问题但只要有些行被写成“2017-1-1 0:00”这种非标准格式字符串排序就会出错。所以正确做法是统一用pd.to_datetime解析并且指定时区。import pandas as pd df pd.read_csv(pems_bay_raw.csv) df[timestamp] pd.to_datetime(df[timestamp], utcTrue) df[timestamp_local] df[timestamp].dt.tz_convert(America/Los_Angeles)当你把时间统一成UTC之后再做任何跨数据源的融合都会省心很多。我的建议是内部处理一律用UTC时间戳展示和画图时才转本地时间。3.3 先做一次全面的数据体检体检的核心指标包括缺失率、传感器有效时间比例、时间戳的连续性、所有传感器的时间对齐程度。下面这段代码可以快速计算每个传感器的缺失率import pandas as pd import numpy as np # 假设df形状为 (T, N)索引是DatetimeIndex列是传感器ID missing_ratio df.isna().mean(axis0) print(总缺失率: {:.4f}.format(df.isna().mean().mean())) print(缺失率最高的10个传感器:) print(missing_ratio.sort_values(ascendingFalse).head(10))当时我在METR-LA上跑这个脚本发现确实有传感器缺失率超过50%。对这种传感器直接删除可能比插值更稳妥。因为如果一个传感器大部分时间都在失效剩下的少量有效数据很难代表真实交通状态反而会给模型带来噪声。还有一点需要留意有些数据文件里缺失值是用-1或者0填充的而不是NaN。如果不做特殊处理这些无效值会被当成真实观测导致模型学习出“速度为0”这种错误模式。体检脚本里一定要检查数值范围把所有异常值统一替换成NaN再进行后续处理。4. 预处理从原始记录到预测样本4.1 缺失值处理怎么选缺失值处理是交通流预测里最纠结的环节。常用的方法有线性插值、前向填充、后向填充、KNN插值、矩阵补全等。我从实际效果出发给出的建议是如果缺失率低于5%且缺失片段很短线性插值足够。如果缺失率在5%到20%之间推荐用前向填充加线性插值的组合先看交通流不会瞬间突变的特点。如果缺失率超过20%建议直接删除该传感器或者采用更复杂的时空插值方法。交通流数据有一个特点短期内有较强的时间连续性相邻两个5分钟的速度值差别通常不大。这让我在大多数场景下倾向于用线性插值而不是简单的0填充。插值前要先用时间索引排序否则fillna的结果会乱掉。df df.sort_index() # 对每一列做线性插值限制最大连续缺失数 df_interp df.interpolate(methodlinear, limit12, limit_directionboth)limit12是因为5分钟粒度下12个点正好是1小时。超过1小时连续缺失我就认为是检测器故障插值意义不大。如果你处理的是流量数据还要注意流量可能出现零值这跟缺失是完全不同的概念不能用同样的策略。4.2 时间对齐与重采样公开数据集虽然标称是5分钟间隔但实际采集过程中会存在时间戳抖动、重复、漏采等问题。原始数据里可能有些传感器在00:00:02上报另一个在00:01:30上报直接组装成矩阵会导致某一时刻只有部分传感器有值。正确做法是先创建一个完整的时间索引比如从开始时间到结束时间每5分钟一个点然后把原始数据重采样对齐到这个索引上。full_index pd.date_range(startdf.index.min(), enddf.index.max(), freq5min) df df.reindex(full_index)如果原始时间戳并不统一可以先将原始表单按时间戳排序设置成索引再reindex到完整时间轴上缺失的地方自然变成NaN。之后再做插值。这一套流程下来矩阵的行数应该等于完整时间步数列数等于传感器数。重采样时还要注意频率换算。如果你想把5分钟数据变成15分钟数据可以用resample取均值df_15min df.resample(15min).mean()但要注意官方很多基准模型是直接使用5分钟粒度的15分钟数据虽然噪声更小却会让预测问题变得偏简单不便于跟论文指标对比。所以我通常保留5分钟粒度做实验只在探索性分析时看15分钟或1小时的聚合曲线。4.3 构建流量矩阵和邻接关系数据经过清洗和重采样之后最常见的组织方式是二维矩阵行是时间戳列是传感器。这个矩阵就是模型的原始特征。但交通流预测还需要空间信息也就是传感器之间的邻接关系。构建邻接矩阵最经典的方法是利用传感器经纬度计算两两距离然后通过高斯核函数把距离转换成权重。这个步骤在原始数据阶段就要准备好否则后面做图神经网络时会卡壳。代码示例from scipy.spatial.distance import cdist import numpy as np coords sensor_df[[lat, lon]].values dist cdist(coords, coords, metriceuclidean) sigma dist.std() adj np.exp(-dist ** 2 / sigma ** 2) adj[adj 0.1] 0这个邻接矩阵的构建方式在很多论文里是标配。我使用的经验是阈值不要设得太极端不然邻接矩阵会过于稀疏图卷积根本聚不到邻居信息。后续有空我可以专门写一篇关于空间图构造的完整分析这篇先把原始数据部分讲透。5. 划分训练集和归一化别让信息偷偷泄漏5.1 按时间顺序划分而不是随机划分交通流预测跟普通机器学习任务最大的区别在于样本之间有时间依赖关系不能随机打乱划分。随机划分虽然会让训练集和验证集的分布更接近但会导致验证集里的某些时间步的上下文信息出现在训练集里数据泄漏非常严重。正确做法是直接按时间先后切成三段比如前70%训练中间15%验证后15%测试。train_ratio, val_ratio 0.7, 0.15 n df.shape[0] train_end int(n * train_ratio) val_end int(n * (train_ratio val_ratio)) df_train df.iloc[:train_end] df_val df.iloc[train_end:val_end] df_test df.iloc[val_end:]为什么验证集也要按时间顺序放在训练集后面因为模型的超参数要依据验证集调优如果验证集数据在时间上早于训练集就相当于用“过去”的数据去调参来预测“未来”这在实际部署中做不到。5.2 归一化只统计训练集数据归一化是另一个容易泄漏的地方。很多教程直接对整个数据集做z-score归一化然后再划分训练集和测试集。这样做的问题是测试集的均值和标准差已经参与了训练过程测试集不再“干净”。正确的做法是只从训练集上计算均值和标准差然后把同样的参数应用到验证集和测试集上mean df_train.mean() std df_train.std() df_train_norm (df_train - mean) / std df_val_norm (df_val - mean) / std df_test_norm (df_test - mean) / std如果数据里有缺失值建议先插值再计算均值和标准差否则NaN会被忽略导致统计量偏小。还有一点验证集和测试集的分布如果和训练集有较大差异最好单独画一画均值曲线避免模型在测试阶段遇到数据漂移。5.3 滑动窗口生成预测样本有了归一化后的一维矩阵下一步是用滑动窗口生成序列样本。交通流预测常用的输入窗口是12个时间步也就是过去1小时输出窗口可以是3、6、12个时间步对应15分钟、30分钟、1小时。生成样本时要注意窗口滑动步长默认为1不要跳步否则会损失大量样本。只使用时间索引完整的窗口末尾不足一个窗口的丢弃。样本生成过程不要打乱顺序等划分完数据集之后再考虑是否需要打乱训练集内部顺序。def create_samples(df, input_len12, output_len12): X, Y [], [] values df.values for i in range(len(df) - input_len - output_len 1): X.append(values[i : i input_len]) Y.append(values[i input_len : i input_len output_len]) return np.array(X), np.array(Y)这样生成的X形状是样本数12NY形状是样本数12N。如果你的模型要求输入是样本数N12只需要一行transpose调整维度。我自己的习惯是先保留样本数时间步节点数这种格式因为很多图模型的标准接口就是这样。注意生成训练集样本时只能用从训练集矩阵里滑动窗口生成验证集和测试集样本时同样只从各自矩阵里取绝对不能跨段取窗口否则边界处会泄漏。6. 常见问题与加班实录6.1 传感器通道对不齐我在一次PEMS04实验里遇到过一件奇怪的事模型训练指标正常测试指标也正常但画出来的预测结果地图上传感器位置和预测值完全对不上。排查之后发现数据矩阵的列顺序是按传感器ID排序后的但邻接矩阵是按另一种顺序生成的两边没有按同一个ID列表对齐。解决方案很简单在构建邻接矩阵之前固定一个传感器顺序列表并且保证数据矩阵的列顺序和邻接矩阵的行列顺序完全一致。最好在保存中间文件时把传感器ID列表一起存成npy或者csv不要指望下次加载时顺序不变。6.2 训练集突然出现NaN模型训练到一半loss突然变成NaN很多人会去调学习率但我会先去检查数据里有没有NaN。有一段时间我用PyTorch训练数据加载时设置了num_workers0结果某些worker处理到了没有插值的片段NaN被送进了模型。从那以后我在数据加载器里加了一步断言assert not np.isnan(batch_x).any(), 输入数据包含NaN如果某个传感器实在补不齐数据在训练时给对应位置做一个mask可能比强行插值更合理但这个操作会改变模型结构如果不是特别必要建议还是先把插值做到位。6.3 预测值永远是一条直线先查数据模型如果预测输出几乎是一条水平线最常见的解释不是模型坏了而是目标变量在训练集里方差极小。比如你用某几个传感器在凌晨时段的数据做训练流量基本接近于零模型自然学会输出常数。这时候应该先检查训练数据里目标变量的标准差而不是急着换模型结构。另一个常见原因是归一化参数用了全局统计量模型输入输出都被压到很小的范围导致输出层很难产生变化。这种情况下可以尝试只对输入做归一化输出使用原始值或者改用一个可学习的归一化层。下面整理一份快速排查表现象可能原因排查方向测试集指标虚高随机划分或全局归一化检查划分代码归一化是否只基于训练集训练loss为NaN原始数据有NaN或极大值检查数据加载器绘制特征分布预测曲线输出常数目标变量方差过小检查目标在训练集上的标准差不同传感器预测结果互换传感器ID顺序不一致统一列顺序与邻接矩阵顺序时间轴错位时区未统一或重采样出错核对完整时间索引与原始时间戳插值后效果变差缺失率过高或插值策略不当提高删除阈值尝试时空插值6.4 原始数据的备份与版本管理最后这一点是我自己吃过亏之后才养成的习惯原始数据绝不直接修改所有清洗步骤都写成脚本输出到新的文件。比如我保留raw/目录存原始下载文件processed/目录存清洗后的矩阵experiment/目录存每次实验用的数据版本。这样做的好处是论文评审或同事问起“这个结果是用哪个版本数据跑的”你能快速定位到具体的脚本和参数。交通流预测对数据版本非常敏感不同时间的清洗策略会导致指标出现几个百分点的波动没有版本管理的话你根本不知道哪个结果是可信的。我个人的做法是每个清洗脚本开头写清楚输入输出路径和数据版本号运行完自动生成一个hash文件记录数据变更记录。虽然多花几分钟但后续能省下大量重复排查时间。如果你正准备开始自己的交通流预测项目我建议你从这一刻起就把数据这关盯紧。数据字段看明白、时区统一好、缺失率统计出来、划分方式写清楚然后再碰模型。把这一步做扎实后面遇到模型效果不好时你能更有底气地说“问题不在数据”从而把精力集中在真正的算法优化上。我在实际项目中最大的体会是数据问题带来的返工成本远高于模型调参的成本。很多看起来玄乎的预测误差追到根上都是原始数据处理时埋下的隐患。下一篇我会继续聊聊在构建时空图连接关系时遇到的那些坑包括距离阈值怎么定、邻接矩阵归一化怎么做、异构传感器如何融合这些内容同样和数据集紧密相关希望能帮你把交通流预测的地基打得再牢一点。