尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

深度学习交通流量预测实战:RNN+CNN双分支模型源码解析与调参避坑指南

发布时间:2026/9/30 20:58:58

资讯中心
01
ARTICLE

深度学习交通流量预测实战:RNN+CNN双分支模型源码解析与调参避坑指南

深度学习交通流量预测实战:RNN+CNN双分支模型源码解析与调参避坑指南
简介这份深度学习交通流量预测实战项目源码面向刚接触时序预测与神经网络的新手以及希望快速跑通完整流程的开发者。项目围绕城市交通流量数据展开用循环神经网络与卷积神经网络搭建预测模型帮助读者理解从原始数据到预测结果的全链路实现。压缩包共15个文件约1.18MB包含8个Python源码文件分别负责数据加载、模型定义与训练主流程另有2个npz数据文件、3张训练指标图、1份数据说明文档和1个日志文本便于对照代码与实验结果。目前已有6069人学习下载。资源覆盖LSTM、GRU、CNN以及CNN-LSTM、CNN-GRU等组合模型的实现并配有数据预处理、模型训练、性能测评与指标可视化模块代码结构清晰、易于阅读读者可据此掌握不同网络在交通流量预测任务上的差异并在此基础上替换数据或调整超参数开展自己的实验。1. 从一段拥堵路况说起这套交通流量预测源码到底能跑出什么早高峰的环路前十分钟还畅通二十分钟后突然堵成停车场。交管部门想知道未来 15 分钟每条路段的流量导航软件想提前把车流引开可传统的时间序列模型一到早晚高峰切换点就发懵——它记不住昨天同一时刻的拥堵模式也读不懂上下游路口之间的空间关联。这套深度学习交通流量预测新手入门实战项目源码解决的正是这个问题用循环神经网络抓时间依赖用卷积神经网络抓空间依赖把路网流量预测做成一个能直接跑起来、能改参数、能换数据集的完整工程。它适合三类人刚学完深度学习课程、想找一个真实场景练手的学生做智慧交通、需要快速搭出预测基线的工程师以及手里有卡口数据、想验证深度学习到底比 ARIMA 强多少的从业者。源码把数据预处理、模型定义、训练循环、评估指标都拆成了独立模块你不需要从零搭框架改几个路径和超参就能看到 loss 往下掉。下面我按自己拆包复现的顺序把这份资源从结构到跑通再到调参的路径讲清楚。2. 拆开源码包目录结构、数据流与模型选型的底层逻辑拿到一份深度学习项目源码最怕的是上来就python train.py报错一串再回头翻文件。我一般先花十分钟把目录结构和数据流摸清楚这一步做扎实后面调参和排错能省一半时间。这套交通流量预测项目属于典型的「数据驱动 双分支模型」结构理解它的组织方式比记住某个 API 怎么调更重要。2.1 目录分层与各模块职责源码包通常按职责分成四层我拆过的同类项目基本都逃不出这个骨架目录/文件职责复现时要关注的点data/原始流量数据与预处理脚本数据格式、时间粒度、缺失值处理方式models/网络结构定义RNN 与 CNN 分支如何拼接、输出维度train.py训练主循环损失函数、优化器、学习率策略evaluate.py评估与可视化指标计算、预测曲线绘制config.py超参与路径配置窗口长度、批次大小、训练轮数这个分层的价值在于你想换数据集只动data/想换模型结构只动models/想调训练策略只动train.py和config.py。耦合度低改一处不会牵一发动全身。新手常犯的错是把所有逻辑塞进一个文件改个窗口长度要翻三百行代码这套源码没这个问题。2.2 交通流量数据的时空特性决定了模型选型为什么交通流量预测要用「RNN CNN」而不是单纯 LSTM 或单纯 CNN这是选型的核心问题想清楚了才不会盲目换模型。交通流量数据本质是一个时空矩阵行是路段空间维度列是时间步时间维度。循环神经网络擅长处理时间序列它通过隐藏状态把历史信息传递下去能捕捉「早高峰流量逐步爬升、晚高峰骤降」这种时间模式。但纯 RNN 有个短板——它逐时间步计算对同一时刻不同路段之间的空间关联不敏感。而卷积神经网络在空间维度上做卷积能提取「相邻路段流量互相影响」的特征比如上游路口堵了下游路口几分钟后流量会变化。所以常见做法是双分支并行一路用 RNNLSTM 或 GRU沿时间轴提取时序特征一路用 CNN 沿空间轴提取路段间关联再把两路特征拼接后送入全连接层输出预测值。这套源码就是按这个思路组织的。理解了这个结构你就知道调参时该动哪里预测滞后就调 RNN 的窗口长度空间关联抓不住就调 CNN 的卷积核大小。2.3 数据预处理把原始流量转成模型能吃的张量模型再强数据格式不对也白搭。交通流量原始数据通常是「时间戳 路段 ID 流量值」的长表需要转成(样本数, 时间步, 路段数, 特征数)的四维张量。下面是我按源码逻辑整理的预处理脚本可以直接抄import numpy as np import pandas as pd def build_dataset(df, time_steps12, predict_steps1): df: 长表含 timestamp, road_id, flow 三列 time_steps: 用过去多少个时间步预测未来 predict_steps: 预测未来第几个时间步 # 透视成 时间 x 路段 的矩阵 pivot df.pivot_table(indextimestamp, columnsroad_id, valuesflow) pivot pivot.interpolate(methodlinear) # 线性插值补缺失 values pivot.values.astype(np.float32) # 按路段做归一化避免不同路段量级差异干扰训练 mean values.mean(axis0, keepdimsTrue) std values.std(axis0, keepdimsTrue) 1e-6 values (values - mean) / std X, y [], [] for i in range(len(values) - time_steps - predict_steps 1): X.append(values[i:i time_steps]) # 历史窗口 y.append(values[i time_steps predict_steps - 1]) # 预测目标 X np.array(X)[..., np.newaxis] # 补一个特征维度 y np.array(y) return X, y, mean, std逻辑说明pivot_table把长表转成时间×路段的宽表这是时空模型的标准输入形态。interpolate处理传感器掉线导致的缺失值比直接填零更合理因为流量是连续变化的。归一化按路段独立做是因为不同路段流量基数差好几倍统一归一化会让小流量路段的信息被淹没。最后滑窗切分time_steps12表示用过去 12 个时间步预测下一步如果数据是 5 分钟粒度就是拿过去 1 小时预测未来 5 分钟。参数怎么改time_steps调大能捕捉更长历史但显存和训练时间上升predict_steps调大做多步预测难度会明显增加新手建议先跑通单步再改多步。3. 把模型跑起来RNN 与 CNN 双分支的搭建、训练与评估数据准备好了接下来是模型定义和训练。这一章是整套源码的核心我会把网络结构、训练循环、评估指标三块拆开讲每块都给出可运行的代码和参数解释。跑通这一章你就能看到第一条预测曲线。3.1 双分支网络结构LSTM 抓时间CNN 抓空间先看模型定义。这套源码用的是 LSTM 一维卷积的组合我按同样思路写一个清晰版本import torch import torch.nn as nn class TrafficPredictor(nn.Module): def __init__(self, num_roads, time_steps, hidden_dim64): super().__init__() # 时间分支LSTM 沿时间轴提取时序特征 self.lstm nn.LSTM(input_sizenum_roads, hidden_sizehidden_dim, num_layers2, batch_firstTrue, dropout0.2) # 空间分支一维卷积沿路段轴提取空间关联 self.conv nn.Sequential( nn.Conv1d(in_channelstime_steps, out_channels32, kernel_size3, padding1), nn.ReLU(), nn.Conv1d(32, 64, kernel_size3, padding1), nn.ReLU() ) # 融合层 self.fc nn.Sequential( nn.Linear(hidden_dim 64, 128), nn.ReLU(), nn.Linear(128, num_roads) ) def forward(self, x): # x: (batch, time_steps, num_roads, 1) x x.squeeze(-1) # (batch, time_steps, num_roads) lstm_out, _ self.lstm(x) # (batch, time_steps, hidden_dim) lstm_feat lstm_out[:, -1, :] # 取最后时间步 conv_in x.permute(0, 1, 2) # (batch, time_steps, num_roads) conv_feat self.conv(conv_in) # (batch, 64, num_roads) conv_feat conv_feat.mean(dim-1) # 全局平均池化 (batch, 64) fused torch.cat([lstm_feat, conv_feat], dim1) return self.fc(fused)逻辑说明LSTM 分支的input_sizenum_roads因为每个时间步的输入是所有路段的流量向量batch_firstTrue让输入维度是(batch, seq, feature)符合直觉。取lstm_out[:, -1, :]是拿最后一个时间步的隐藏状态作为时序特征汇总。CNN 分支把时间步当作通道、路段当作长度做一维卷积kernel_size3表示每次看相邻 3 个路段捕捉局部空间关联。两路特征拼接后过全连接输出每个路段的预测值。参数怎么改hidden_dim控制 LSTM 记忆容量数据量大可以调到 128num_layers2是两层堆叠层数多了容易过拟合新手先用 1 到 2 层CNN 的kernel_size根据路网密度调路段密集就用 3 或 5。3.2 训练循环损失函数、优化器与学习率模型搭好训练循环决定它能不能收敛。下面是标准训练脚本from torch.utils.data import DataLoader, TensorDataset import torch.optim as optim def train_model(X, y, num_roads, time_steps, epochs50, batch_size64, lr1e-3): device torch.device(cuda if torch.cuda.is_available() else cpu) dataset TensorDataset(torch.tensor(X), torch.tensor(y)) loader DataLoader(dataset, batch_sizebatch_size, shuffleTrue) model TrafficPredictor(num_roads, time_steps).to(device) criterion nn.MSELoss() # 回归任务用均方误差 optimizer optim.Adam(model.parameters(), lrlr) scheduler optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience5) for epoch in range(epochs): model.train() total_loss 0 for batch_x, batch_y in loader: batch_x, batch_y batch_x.to(device), batch_y.to(device) optimizer.zero_grad() pred model(batch_x) loss criterion(pred, batch_y) loss.backward() optimizer.step() total_loss loss.item() avg_loss total_loss / len(loader) scheduler.step(avg_loss) if (epoch 1) % 10 0: print(fEpoch {epoch1}, Loss: {avg_loss:.4f}, LR: {optimizer.param_groups[0][lr]:.6f}) return model逻辑说明MSELoss是回归任务的标准损失预测流量和真实流量的平方误差。Adam优化器对学习率不敏感适合新手起步。ReduceLROnPlateau是学习率衰减策略验证损失连续 5 轮不降就把学习率调小避免在最优解附近震荡。每 10 轮打印一次损失和学习率方便观察收敛情况。参数怎么改batch_size显存够就调大训练更稳lr初始 1e-3 是安全值损失不降可以试 1e-4epochs看损失曲线平稳了就可以停不必跑满。3.3 评估指标别只看 MSEMAE 和 MAPE 更贴近业务训练完不能只看 loss要换算成业务能理解的指标。交通流量预测常用三个import numpy as np def evaluate(y_true, y_pred, mean, std): # 反归一化回真实流量 y_true y_true * std mean y_pred y_pred * std mean mae np.mean(np.abs(y_true - y_pred)) rmse np.sqrt(np.mean((y_true - y_pred) ** 2)) # MAPE 要避开零流量路段 mask y_true 1e-3 mape np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100 print(fMAE: {mae:.2f}, RMSE: {rmse:.2f}, MAPE: {mape:.2f}%) return mae, rmse, mape逻辑说明反归一化是必须的否则指标没有物理意义。MAE 是平均绝对误差单位是流量直观RMSE 对大误差更敏感能暴露模型在高峰时段的崩盘MAPE 是百分比误差业务方最爱看但零流量路段会导致除零所以用 mask 过滤掉。提示如果 MAPE 异常高但 MAE 正常多半是低流量路段预测偏差被放大了检查一下归一化是否按路段独立做的。4. 避坑与排查复现这套源码时最容易翻车的五个地方源码能跑通不代表结果可信我在复现同类项目时踩过的坑基本都集中在这五处。每条按「现象 → 原因 → 解决」写照着排查能省不少时间。4.1 损失降到很低但预测曲线是一条直线现象训练 loss 一路降到 0.01 以下但画出来的预测曲线几乎不动跟真实流量的波动完全对不上。原因归一化做错了。如果对所有路段用同一个均值和标准差大流量路段主导了数值范围小流量路段被压成接近零的常数模型学到的就是「输出平均值」。解决按路段独立归一化也就是axis0而不是全局。上面预处理脚本里的mean values.mean(axis0, keepdimsTrue)就是干这个的。改完重新训练曲线立刻有波动。4.2 训练集表现好测试集一塌糊涂现象训练集 MAE 很低测试集 MAE 翻两三倍典型的过拟合。原因模型容量相对数据量太大或者训练轮数过多。交通流量数据如果只有几周LSTM 两层加 128 隐藏单元很容易记住训练集。解决先降hidden_dim到 32 或 64加 dropout源码里 LSTM 的dropout0.2就是为此再用早停——验证损失连续 10 轮不降就停。数据量实在小考虑数据增强比如加轻微噪声。4.3 显存爆了或者训练慢得离谱现象跑几个 batch 就CUDA out of memory或者一个 epoch 要半小时。原因time_steps或batch_size设太大四维张量展开后占用惊人。time_steps12、num_roads200、batch_size64时一个 batch 的输入就是 64×12×200再乘 LSTM 的隐藏维度显存消耗很快。解决先降batch_size到 16 或 32再考虑降time_steps。如果必须用大窗口用梯度累积模拟大 batch。另外确认数据加载没有把整个数据集一次性搬到 GPU。4.4 MAPE 算出 inf 或 nan现象评估时 MAPE 打印出inf或nan。原因真实流量里有零值或接近零的值除法炸了。夜间某些路段流量确实可能为零。解决评估前加 mask 过滤只对真实流量大于阈值的样本算 MAPE上面评估脚本里的mask y_true 1e-3就是干这个的。或者改用 SMAPE分母是两者绝对值之和天然避开除零。4.5 换了数据集后维度对不上现象用自己的数据替换后报shape mismatch或input_size不匹配。原因模型初始化时的num_roads和time_steps是写死的换数据后路段数和窗口长度变了但没同步改。解决把num_roads和time_steps从数据里动态读取别硬编码。在config.py里根据数据集自动计算模型初始化时传入。这是工程习惯问题养成「维度从数据来」的意识换数据集就不会翻车。5. 进阶技巧多步预测、注意力加权与结果验证的完整闭环单步预测跑通只是起点真实业务要的是未来 15 分钟、30 分钟甚至 1 小时的流量。这一章讲两个进阶方向和一个验证习惯都是我在实际项目里反复用到的。5.1 从单步到多步直接多输出与滚动预测的取舍多步预测有两条路。第一条是直接多输出把全连接层输出维度从num_roads改成num_roads * predict_steps一次性预测未来多个时间步。改动很小# 输出层改为 self.fc nn.Sequential( nn.Linear(hidden_dim 64, 128), nn.ReLU(), nn.Linear(128, num_roads * predict_steps) ) # forward 里 reshape out self.fc(fused) return out.view(-1, predict_steps, num_roads)第二条是滚动预测每次预测一步把预测值拼回输入窗口再预测下一步。直接多输出的优点是误差不累积缺点是各步之间独立预测可能不够平滑滚动预测的优点是能利用最新预测值缺点是误差会逐步放大预测 6 步以上基本不可用。我的经验是预测 3 步以内用直接多输出超过 3 步考虑加一个时序解码器或者干脆用 Seq2Seq 结构。新手先把直接多输出跑通理解多步预测的难度在哪。5.2 给时间步加注意力让模型自己挑重要的历史时刻LSTM 取最后一个时间步的隐藏状态等于默认「最近的历史最重要」。但交通流量有个特点早高峰的预测可能昨天同一时刻的模式比前一小时更有参考价值。注意力机制能让模型自己学权重class Attention(nn.Module): def __init__(self, hidden_dim): super().__init__() self.attn nn.Linear(hidden_dim, 1) def forward(self, lstm_out): # lstm_out: (batch, time_steps, hidden_dim) weights torch.softmax(self.attn(lstm_out), dim1) # (batch, time_steps, 1) context (lstm_out * weights).sum(dim1) # 加权求和 return context把原来lstm_out[:, -1, :]换成这个注意力输出模型就能对不同时间步分配不同权重。参数上没增加多少但预测高峰切换点时通常有提升。注意softmax的dim1是对时间步做归一化别写错维度。5.3 验证闭环用残差分析和分时段指标确认模型真的可用指标好看不代表模型可用。我习惯做两件事验证。第一件是画残差图把预测误差按时间画出来如果误差在高峰时段明显放大说明模型对突变场景学得不够需要加高峰样本权重。第二件是分时段算指标把一天切成早高峰、平峰、晚高峰、夜间四段分别算 MAE。如果夜间 MAE 很低但早高峰很高整体指标会被夜间拉好看实际业务最需要的高峰时段反而不可靠。def segment_evaluate(df, y_true, y_pred): df[hour] pd.to_datetime(df[timestamp]).dt.hour segments {早高峰: (7, 9), 平峰: (10, 16), 晚高峰: (17, 19), 夜间: (22, 6)} for name, (start, end) in segments.items(): if start end: mask (df[hour] start) (df[hour] end) else: mask (df[hour] start) | (df[hour] end) mae np.mean(np.abs(y_true[mask] - y_pred[mask])) print(f{name} MAE: {mae:.2f})这个习惯救过我一次整体 MAPE 只有 8%看起来很美分时段一拆早高峰 MAPE 到了 23%根本没法用。从那以后我每次评估交通流量模型都强制走一遍分时段指标和残差图整体指标只当参考。希望这套源码和这些排查思路能帮你少走几个我踩过的弯路。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。