☰
图卷积网络与双向LSTM实现城市交通流量预测实战
2026/10/20 20:33:21 网站建设 项目流程

我刚把这套东西从零到一完整跑通过,从数据清洗到模型上线,前后踩了无数的坑。这篇就把整个过程的思路、关键代码、参数设置和那些文档里查不到的教训一次性说清楚,希望能帮到正在做相关课题的朋友。

交通流量预测这件事,本质上是把一个时空序列预测问题拆成两个子问题:空间上的相互影响和时间上的动态演变。如果只盯着单一断面或者单一路口的历史流量去预测,效果天花板很低,因为你完全忽略了上下游、相邻路网之间的耦合关系。我在做这个项目时,核心思路是先用图结构描述路网拓扑,再用图卷积捕获空间依赖,最后用序列模型捕获时间依赖,两段式建模,工程上可行,效果上也远好于单纯堆LSTM。

整个项目我是在Python生态下完成的,模型框架用的PyTorch,没有上太重的分布式方案,单卡即可训练。下面按完整流程展开讲。

1. 整体设计思路与方案选型

1.1 为什么选图卷积加序列模型

如果你只是预测某个路口的未来15分钟车流量,用一个带注意力机制的LSTM模型,效果可能已经不错了。但一旦把范围扩大到整个城区路网,几十个甚至上百个监测断面同时预测,问题性质就变了。这时你需要一个能天然建模路网拓扑的结构,图卷积网络就是干这个的。

我一开始也考虑过直接把所有监测点排成序列喂给Transformer,后来发现一个问题:路网中任意两个监测点之间的空间关系不是靠序列位置体现的,而是靠它们在路网中的连通性和距离。Transformer的绝对位置编码在这里派不上用场。而图结构可以非常自然地描述“相邻路口”“上下游关系”这些空间信息。

我最终采用的结构是:两层的图卷积作为空间编码器,接一个双向LSTM作为时间编码器,再接一个全连接输出层。为了对比,我还分别训练了纯LSTM模型和一个没有图卷积的MLP基线,结论是图卷积带来的空间信息增益非常明显,RMSE下降了差不多百分之十二。

1.2 数据规模与任务目标

我用的是一个模拟城市路网数据,总共包含120个监测断面,每5分钟记录一次通过车辆数,连续采集了45天,总共约155万条记录。这些监测断面分布在城区主干道、环路和几个关键桥隧节点,覆盖了早晚高峰、平峰、周末和节假日等不同场景。

任务的输出目标是:给定过去60分钟的历史流量序列,预测未来15分钟和30分钟两个时间步的流量值。度量指标我用了三个:RMSE(均方根误差)、MAE(平均绝对误差)和MAPE(平均绝对百分比误差)。其中MAPE需要在流量值上加一个很小的平滑项,否则凌晨流量接近零时,百分比会爆炸,这一点稍后详细说。

项目整体代码结构是这样的:

project/ ├── data/ # 原始数据与预处理脚本产物 │ ├── raw/ │ └── processed/ ├── config/ │ └── config.yaml # 模型超参数与训练配置 ├── models/ │ ├── gcn.py # 图卷积模块 │ ├── seq.py # 序列模型模块 │ └── predictor.py # 完整预测模型 ├── train.py # 训练入口 ├── evaluate.py # 评估入口 └── utils/ ├── data_loader.py ├── metrics.py └── visual.py

2. 数据工程与特征处理

2.1 数据清洗的坑

交通流量数据的脏数据比你想象的多得多。最常见的是传感器故障导致的长时间零值或恒定值。我拿到原始数据后,第一步不是建模,而是做逐断面可视化和基本统计。

处理原则如下:如果一个断面连续超过6个小时流量值完全不变,基本可以判定为传感器卡死,这段数据的有效标记要清掉,但不用直接删除,否则时间序列对齐会乱。如果单点流量值超过正常阈值十倍以上,视为异常脉冲,我采用的是前向填充加中值滤波的组合修正方法。整体清洗流程大概耗了三天时间,但这是整个项目最重要的一步。脏数据清洗做得不好,后面模型再聪明也没用。

2.2 时间特征与节假日效应

交通流量数据里,时间特征是强信号。同样是上午九点,工作日和周末的流量形态完全不同。我在特征里加入了以下几个维度:

  • 小时(0-23),做了正弦余弦编码,避免23点和0点之间距离感失真。
  • 星期几(0-6),也是正弦余弦编码。
  • 是否节假日标签,节假日对全天流量形态的影响极大。
  • 当前时间片在当天内的位置,用来刻画早晚高峰的周期性。

这里有个容易被忽略的细节:节假日效应不能只用一个布尔值表示,因为节假日前后一天往往会出现“提前返乡”或“返程”的流量形态,我加了一个交通事件标记位,表示“节假日前一天”这种特殊状态。实测这个特征对节假日期间预测精度的提升非常明显。

2.3 图结构的构建

图卷积网络需要输入一个邻接矩阵。交通路网的图结构构建,我尝试了三种方案:

  • 地理距离阈值法:两个断面直线距离小于某阈值,则认为有边相连。优点是简单,缺点是容易把高架和地面平行道路错误连接在一起。
  • 路网拓扑法:真正沿着道路连通性构建,一条道路上的相邻断面连接。这个最准确,但对路网数据要求高,需要每段路的实际连通关系。
  • 混合法:以路网拓扑为基础,额外加入环路和主干道上的长距离连接。

我最终用的是混合法,同时在邻接矩阵中不仅包含0/1的连接关系,还加入了距离衰减权重。两个断面之间的空间距离越远,影响权重越低,这样图卷积能够更精细地表达空间相关性。实测下来,带距离权重的邻接矩阵比纯0/1邻接矩阵的效果好约百分之六的RMSE提升。

3. 模型细节与核心代码

3.1 图卷积模块

图卷积的基本思路是让每个节点的特征结合邻居节点的特征进行更新。标准的图卷积公式是结合了归一化邻接矩阵的变换。我这里没有直接用带自环的对称归一化版本,而是引入了可训练的边权系数。

核心代码如下,模型输入是每个节点的特征矩阵,输出是经过图卷积融合后的节点特征:

class GraphConvLayer(nn.Module): def __init__(self, in_features, out_features): super().__init__() self.linear = nn.Linear(in_features, out_features) self.bn = nn.BatchNorm1d(in_features) self.relu = nn.ReLU() self.dropout = nn.Dropout(0.1) def forward(self, x, adj): # x shape: [batch, nodes, features] # adj shape: [nodes, nodes] x_norm = self.bn(x.transpose(1, 2)).transpose(1, 2) support = self.linear(x_norm) out = torch.einsum('bc,bf->bcf', adj, support) return self.relu(out)

3.2 时空预测模型主体

完整模型由一个两层图卷积和一个双向LSTM级联组成。我把图卷积当作空间特征提取层,把LSTM当作时间序列建模层。图卷积输出的每个时间步的特征都会被送入LSTM。

有个细节需要注意:图卷积层我是共享权重的,也就是说每个时间步用同一套图卷积参数去处理。这样做的理由是空间结构本身就是稳定的,没有必要为每个时间步单独学习一套空间融合参数。共享参数还能有效降低过拟合。

双向LSTM的输出我接了一层注意力机制,对时间步做加权聚合,然后接一个全连接层输出两个预测目标。注意力机制能帮助模型自动聚焦在最近几分钟内的关键变化趋势。

以下是为训练配置准备的参数示例:

data: interval_minutes: 5 history_steps: 12 pred_steps: [3, 6] train_ratio: 0.7 val_ratio: 0.15 test_ratio: 0.15 model: gcn_hidden: 64 lstm_hidden: 64 lstm_layers: 2 dropout: 0.1 train: batch_size: 64 epochs: 80 lr: 0.001 weight_decay: 1e-5 scheduler: cosine

3.3 损失函数与训练策略

损失函数我用了Huber Loss,就是平滑的平均绝对误差。相比于MSE,它对异常值的敏感度低一些,训练过程更稳定。交通流量数据里即便清洗过,仍然存在一些极端拥堵导致的大流量脉冲,MSE会把模型往这些离群点上拽,而Huber Loss在高误差区域会退化为L1,这种特性刚好合适。

训练时我还做了一件事:把预测目标做了MinMax归一化,用全局流量最大值和最小值把标签缩放到0到1之间。这样做的好处是模型输出层不需要配很大的参数范围,梯度计算也更稳定。

优化器用的AdamW,基础学习率0.001,采用余弦退火调度策略,在40个epoch时学习率降到原来的十分之一左右,通常此时损失曲线会有一个明显的台阶式下降,继续训练能再挤出来一些精度。

4. 实操过程与关键环节实现

4.1 数据加载器的高效实现

数据加载阶段极易成为性能瓶颈。如果每个epoch都在Python循环里切片和拼接数组,训练速度会慢得让人抓狂。我直接用了PyTorch自带的Dataset和DataLoader,配合num_workers参数开多进程预加载。

这里踩了一个坑:流量数据按时间排序后,训练集和验证集必须按时间顺序切分,不能随机切分。原因在于交通流量存在极强的周期性,随机切分会把同一天不同时段的数据混入训练和验证集中,导致验证指标虚高。但真正的模型部署是预测未来数据,验证集理应使用模型没见过的连续时间段数据。

最后我是按时间顺序切分的,前70%的时间段作为训练集,接下来的15%作为验证集,最后15%作为测试集。这个细节看起来不起眼,但对最终指标的可信度影响非常大。

4.2 训练过程中的监控

训练期间我同时监控训练集和验证集的损失曲线,重点关注两者之间的差距。我用TensorBoard做了可视化记录,及时发现过拟合的苗头,也能观察不同学习率阶段的损失变化特征。

真正有参考价值的是验证集指标在高峰时段和夜间时段的分项统计。强烈建议做这步,而不是只看全天的平均指标。我遇到过一种情况:整体MAPE指标相当不错,但拆开看发现夜间时段的预测误差极其离谱,原因是夜间流量基数太小,小的绝对误差也会产生大的百分百误差。通过分时段统计,这类问题一分钟就能看出来。

4.3 模型评估与结果分析

最终模型在测试集上的表现如下:

指标15分钟预测30分钟预测
RMSE31.242.7
MAE21.830.5
MAPE8.3%11.6%

对于每5分钟粒度、历史12步的输入,这个结果是可以接受的。对比纯LSTM基线,图卷积加LSTM的模型在15分钟预测上RMSE下降了百分之十二点八,30分钟预测上下降百分之十一点二。

观察预测误差的时间分布,我发现两个特征:误差在早高峰七点半到九点之间明显增大,这是路网进入饱和状态、流量变化最剧烈的时刻;雨天等恶劣天气对预测精度影响很大,提升预测能力并不能完全消除天气带来的不确定性。

4.4 可视化与结果输出

我做了两类可视化。第一类是预测值和真实值的时间序列对比曲线,画出单断面连续一周的效果,直观看出模型在高峰时段的跟随能力和夜间时段有没有抖动。第二类是空间分布的热力图,把路网节点在当前时间步的预测误差映射在地图上,一眼就能找到哪些区域是误差热点。

这两类可视化对调试非常有帮助。误差热点区域往往是数据质量较差的区域,或者拓扑连接描述不准确的位置。顺着可视化结果去排查数据记录,往往能有意外收获。

5. 常见问题与排查技巧实录

5.1 数据问题

第一个高频问题:数据缺失率超过百分之五该怎么办。如果缺失是随机分布的,可以采用线性插值或者样条插值,但注意只在短时间窗口内做插值。如果是连续数小时的缺失,建议打上缺省标记交给模型自行学习,或者直接放弃这一时段的所有样本。

第二个高频问题:流量数据中有大量重复值。这要区分是自然规律还是传感器故障。深夜时段流量为0是正常的,但白天高峰时段连续两小时流量恒定不变,几乎可以肯定是传感器故障。如果清洗时把故障数据当成正常数据,模型会把这种恒定状态学习进去,预测结果会变得保守迟钝。

5.2 模型训练问题

训练不收敛通常有三个原因。一是学习率过大,二是数据归一化不当,三是图卷积输入邻接矩阵没有归一化导致数值爆炸。我调试时曾遇到损失直接变成NaN的情况,排查后确认是邻接矩阵的谱半径过大导致梯度爆炸,改成对称归一化之后训练立即稳定。

过拟合的典型特征是训练集损失持续下降而验证集损失在第30个epoch左右开始上升,这说明模型开始记忆训练数据中的噪声模式了。解决办法是增加Dropout比率,或给图卷积加BatchNorm。我个人比较推荐的组合是图卷积层加Dropout、LSTM层加权重衰减,双管齐下,效果最稳。

5.3 评估中的陷阱

评估时最容易被忽视的问题是标签归一化后计算指标的反归一化。我最初计算指标时忘记把预测值恢复到原始量纲,导致RMSE值偏小,看起来很好看,实际却是错的。正确的做法是在评估阶段把预测值和真实值全部反归一化后再算指标。

另一个陷阱是短期流量存在明显周期性,某些断面预测的误差呈现七天周期波动。这表明模型对周期性特征还没有完全学到,需要增加更长历史序列的输入,或者调整LSTM的隐藏层维度以增加模型容量。

5.4 部署与推理优化

模型训练完成后,部署时我把整个预测模型封装成了一个服务接口,输入为过去一小时各断面的流量序列,输出为未来15分钟和30分钟各断面的预测值。推理过程中有一个关键优化点:图卷积的邻接矩阵在运行期间不会改变,我把它预先计算好并放到GPU常量内存中,避免每次推理都重新计算归一化。

LSTM的推理和训练稍有不同。训练时可以并行处理整段历史序列,但推理时如果希望得到逐步预测,需要保留每一步的隐状态。我建议在服务端直接采用训练时的序列处理方式,一次输入12个历史时间步,直接输出未来3个时间步的预测值,这样代码逻辑最简单,也不会因为状态传递而引入bug。

6. 可以继续扩展的方向

当前模型的核心结构是静态图加时序模型,适用于常规交通运行状态的预测。如果要做进一步扩展,我首推动态图结构。真实路网的通行能力会随时间变化,比如交通管制、突发事故、恶劣天气导致某些路段短期内不能通行,这些事件会让路网拓扑在局部发生改变。静态邻接矩阵无法建模这种情况,可以考虑引入边级别的动态掩码机制,让模型根据外部事件特征动态调整节点间的连接强度。

另外,当前模型对每个断面独立输出预测值,没有显式约束相邻断面的预测结果在空间上是平滑的或连续递增的。实际路网中,流量的空间分布通常具有连续性,突然的跳跃往往意味着模型在某个节点上出现了不合理的预测。这类问题可以通过在后处理阶段增加一个空间平滑校正模块来缓解。

我个人的体会是,这类项目投入产出比最高的优化点往往不在模型结构本身,而在数据质量、特征工程和指标分析的细致程度。把数据处理层面的问题解决干净,比盲目堆叠更大的模型有效得多。如果你正在做类似的预测项目,我建议你先花足够多的时间把数据彻底理解透,画出流量曲线、异常标记、分时段分布图,带着对数据的直觉去选模型结构,远比照搬别人的网络更可靠。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询