ARTICLE DETAIL

资讯详情

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

1D CNN+LSTM的高速公路短时交通流量预测实战解析

1D CNN+LSTM的高速公路短时交通流量预测实战解析 简介面向高速公路短时交通流预测场景这份Python资源提供了基于1D CNNLSTM组合结构的LCTFP模型完整实现适合具备一定深度学习基础、正在做交通流预测或时序建模的开发者参考。模型利用1D CNN提取交通流空间特征LSTM捕捉时间动态属于经典时空特征融合方案脚本覆盖数据读取、预处理、归一化、时间排序、模型训练、超参数搜索和预测对比。压缩包共21个文件以7个Python脚本、6个H5权重、4个JSON配置为主另含2张PNG模型结构图/结果图和1个README说明整体约31MB。已预处理好的站点交通流数据与README帮助快速复现训练好的h5文件可直接加载使用cnn_lstm_param.py借助hyperas搜索超参数便于进一步调优。资源已有5334人学习浏览配套数据样本、模型权重和可视化结果适合作为课程设计或项目基线在此基础上做改进和扩展。1. LCTFP 是套什么资源用 1D CNN 加 LSTM 拆解高速公路短时流量预测LCTFP 这套基于 Python 实现的高速公路交通流量预测资源核心是一整条 1D CNN LSTM 的混合网络。先用 1D CNN 卷积层从短时流量窗口里抓空间关联再把卷积输出交给 LSTM 处理时间上的先后依赖最后用全连接层输出下一时段的流量值。你能拿到的是完整工程PeMS 原始数据、预处理脚本 data_preprocess.py、监督样本脚本 input_data.py、训练主脚本 cnn_lstm_final.py、超参数搜索脚本 cnn_lstm_param.py以及 Model 目录里一排训练好的 json/h5 权重文件。适合三类人想复现 CNNLSTM 流量预测的毕设党、要做多模型对比的研究生、正在做断面短时流量预测却缺一条可跑通基线的从业者。我的建议是先把 cnn_lstm_final.py 完整跑通再动超参数。下面按数据入口、网络结构、调参训练、避坑、验证的顺序拆给你看。2. 数据入口与预处理从 PeMS 原始 txt 到可训练的监督序列模型结构放后面先看数据。LCTFP 的训练数据来自 PeMS 数据集的断面流量记录这套包里 PeMS 目录下的原始数据是 txt 存储每一行是一个时间戳加对应的流量值。data_preprocess.py 负责把所有 txt 读进来完成缺失处理、时间排序和归一化input_data.py 再把归一化后的序列切成带标签的监督样本。交通流量预测的第一个坑通常不在模型选型而在数据准备阶段就把时间顺序弄乱、把量程混掉。所以这一章把读入、归一化、切窗三件事分别讲透。2.1 先理清 PeMS 数据里有什么txt 读取与按时间排序从 PeMS 拿到的原始断面数据一般是一个站点一个 txt字段至少包含时间戳和流量值。读入的逻辑必须按时间戳排序不能依赖文件在磁盘上的物理顺序。资源里的 data_preprocess.py 干的就是这件事常见的读取逻辑是这样import os import numpy as np def load_flow_txt(path): ts_list, flow_list [], [] with open(path, r, encodingutf-8) as f: for line in f: line line.strip() if not line or line.startswith(#): continue parts line.split(,) ts_list.append(parts[0].strip()) flow_list.append(float(parts[1].strip())) # 关键按时间戳排序后续窗口切分必须是严格时序 order np.argsort(ts_list) flow_arr np.array(flow_list)[order] return flow_arr逻辑说明先把所有行读到内存再按时间戳排序。真实 txt 文件因为采集端网络抖动写入顺序和实际时间顺序经常不一致直接切窗会导致训练样本错位模型学到的是乱序关系。排序后得到的 flow_arr 是一维流量序列后面所有切窗、归一化都基于这个有序数组。参数说明如果原始数据是 5 分钟一条记录取 step12 表示用过去 1 小时的流量预测下一条取 step18 表示用 1.5 小时。LCTFP 的场景是短时交通流预测窗口不宜拉太长太长会把早高峰和低谷期混进同一个上下文模型反而分不清当前处于哪个流量阶段。2.2 归一化别一把梭MinMax 的 fit 与 transform 要分开读进来的流量数值范围跨度很大PeMS 里早高峰断面流量可能是低谷期的几倍如果不归一化LSTM 的梯度很容易被大数值把持。常见做法是用 MinMaxScaler 把流量压到 0~1。但这里有一个血泪经验对整个数据集一起 fit测试段的最小值和最大值就提前泄进了训练过程验证集指标会虚高真实预测能力被高估。from sklearn.preprocessing import MinMaxScaler def normalize_flow(flow_train, flow_val, flow_test): scaler MinMaxScaler(feature_range(0, 1)) # 只用训练段 fit验证和测试段只做 transform scaler.fit(flow_train.reshape(-1, 1)) train_scaled scaler.transform(flow_train.reshape(-1, 1)).flatten() val_scaled scaler.transform(flow_val.reshape(-1, 1)).flatten() test_scaled scaler.transform(flow_test.reshape(-1, 1)).flatten() return train_scaled, val_scaled, test_scaled逻辑说明fit 只发生在训练段验证段和测试段只调用 transform。这样测试段的数值范围不会以任何形式参与训练验证指标才可信。参数说明如果同时处理多个站点不要把所有站点的流量拼成一个长序列再统一 fit。不同断面的车道数、限速值、上下游位置不同流量量纲差异大混在一起会让模型把站点差异当成噪声去拟合。我一般按站点分别建 scaler每个站点一套缩放参数。归一化方式做法风险全局 MinMax对整个序列一次 fit transform测试段信息泄漏验证指标虚高训练段 MinMax训练段 fit验证/测试段 transform无泄漏推荐提示PeMS 数据读进来之后先画一张流量曲线看看有没有大段 0 值区间。凌晨流量为 0 是正常的如果白天出现连续 0 值多半是采集点故障这类区间要剔除后再切窗否则会拉低模型的峰值预测能力。2.3 把序列切成监督样本input_data.py 的职责归一化后的序列还是线性一维数组不能直接塞进 Conv1D 和 LSTM。需要按窗口滑动生成 X 和 yX 是过去 look_back 个时刻的流量y 是下一时刻流量。资源里的 input_data.py 干的就是切窗工作。切窗时必须按时间顺序切训练集、验证集、测试集不能做随机 shuffle一旦 shuffle 未来信息就混进了训练上下文这种泄漏在时序任务里比归一化泄漏更难察觉。def create_supervised(flow_scaled, look_back12, train_ratio0.7, val_ratio0.15): X, y [], [] for i in range(len(flow_scaled) - look_back): X.append(flow_scaled[i:i look_back]) y.append(flow_scaled[i look_back]) X np.array(X).reshape(-1, look_back, 1) y np.array(y).reshape(-1, 1) n len(X) train_end int(n * train_ratio) val_end int(n * (train_ratio val_ratio)) return (X[:train_end], y[:train_end], X[train_end:val_end], y[train_end:val_end], X[val_end:], y[val_end:])逻辑说明切窗方式决定了 LSTM 能看到多长的历史。窗口滑动步长默认是 1也就是说连续两个训练样本只差一个时刻数据高度重叠这会让训练样本之间的相关性变高loss 曲线偏平滑这是正常的不要误以为过拟合。参数说明train_ratio0.7、val_ratio0.15 是常见划分我也习惯按月份切前两个月训练、第三个月验证、第四个月测试更贴近流量预测的落地场景。切窗后 X 的形状是 (样本数, look_back, 1)第三维 1 表示单通道流量后面如果加站点编码或天气特征这个维度要相应扩展input_data.py 里要同步改。3. 模型结构拆解1D CNN 抓空间特征、LSTM 抓时间特征网络结构是这套资源的灵魂。LCTFP 用的是先卷积后 LSTM 的组合打开 cnn_lstm_final.py 会看到主干是 Conv1D → MaxPooling1D → LSTM → Dense。为什么这样排原因在于交通流特征本身是双重的同一断面相邻时隙存在局部关联而更长时间尺度上存在持续性和周期性。模型既要捕捉「前 10 分钟和现在的关系」也要捕捉「昨天同时段和现在的关系」两种模式用不同结构分开学比混合在一个单元里更稳定。3.1 为什么是 1D CNN不是 2D卷积在短时流量上的作用深度学习 CNN 卷积神经网络在流量序列上做卷积等价于在每个历史时刻附近开一个小窗口把窗口内相邻时隙的加权和作为局部特征。它的优势是权重共享同一个卷积核扫描不同时间段需要学习的参数少不容易过拟合。流量预测不用 2D CNN 的原因也直接2D 卷积假定输入是图像那样的二维结构在平面上有平移不变性而流量序列只有时间一个轴。强行把流量转成站点×时刻的二维矩阵对齐成本高训练数据量通常也撑不起 2D 卷积的参数规模。方案空间特征处理时间特征处理参数规模适用场景纯 LSTM空间关系压进门控逻辑LSTM 门控处理顺序依赖中单断面流量1D CNN LSTMConv1D 显式提取局部特征LSTM 继续处理卷积后序列较小多断面/短时流量2D CNN LSTM需构造二维矩阵结构复杂大数据量很大的场景实际跑实验你会发现加了前置 1D CNN 之后LSTM 的收敛明显变快。因为卷积层先把相邻时隙的冗余信息压缩了一遍LSTM 再学时间依赖时面对的输入更干净门控结构不用分心去处理「5 分钟前和 10 分钟前信息太像」的问题。3.2 cnn_lstm_final.py 的网络结构Conv1D 到 LSTM 再到全连接核心构建代码按 Keras 的 Sequential 写法是这样from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense, Dropout look_back 12 # 历史窗口长度我一般取 12~18 model Sequential() # 第一层1D 卷积 ReLU每次扫 kernel_size 个相邻时隙 model.add(Conv1D(filters64, kernel_size3, activationrelu, input_shape(look_back, 1))) # 池化把卷积后的序列长度减半降低 LSTM 计算量 model.add(MaxPooling1D(pool_size2)) # LSTM读取卷积后的序列输出最后一个隐藏状态 model.add(LSTM(units64, return_sequencesFalse)) # 防过拟合 model.add(Dropout(0.2)) # 全连接输出单个流量预测值 model.add(Dense(1))逻辑说明Conv1D 在时间轴上移动卷积核每次覆盖 kernel_size 个历史时隙filters64 表示有 64 个不同权重的卷积核并行提取局部模式MaxPooling1D 把序列长度减半LSTM 处理步数变少训练更快LSTM 的 return_sequencesFalse 表示只输出最后一个时间步的隐藏向量这个向量携带了整段历史信息的压缩表达最后 Dense(1) 把它压成单个流量值。参数说明filters 越大能学到的局部模式越多但训练时间线性增长128 以上在单站点数据上容易过拟合。kernel_size 取 3 或 5取太大会把远期信息提前混合进局部特征LSTM 反而没东西可学。units 是 LSTM 隐藏单元数对突变点的捕捉能力直接和它挂钩但也不是越大越好超过 128 之后收益递减明显。3.3 Model 目录下的文件关系json 存结构、h5 存权重训练完再看 Model 目录你会发现这套资源把结构和权重分开存了cnn_lstm_final.json 是 CNNLSTM 的结构定义cnn_lstm_best.h5 是早停保留的最佳权重同目录下还有 lstm.json、lstm.h5、sae.json、sae.h5、cnn.h5对应纯 LSTM、SAE 自编码、纯 CNN 三个对照模型的存档。这些对照模型的存在是为了让 compare.py 能在同一份测试数据上做公平比较。文件内容加载方式cnn_lstm_final.jsonCNNLSTM 模型结构model_from_json(json)cnn_lstm_best.h5 / cnn_lstm.h5最佳权重 / 训练过程权重副本model.load_weights(h5)lstm.json lstm.h5纯 LSTM 对照模型同上sae.json sae.h5SAE 自编码对照模型同上cnn.h5纯 CNN 对照模型权重同上加载模型的典型写法from tensorflow.keras.models import model_from_json with open(Model/cnn_lstm_final.json, r, encodingutf-8) as f: model model_from_json(f.read()) model.load_weights(Model/cnn_lstm_best.h5)逻辑说明json 只描述网络层结构和每层的参数设置h5 存实际权值。加载时必须先通过 model_from_json 重建结构再 load_weights 灌权重顺序反了会报结构不一致的错误。这一点在你后续用自己的数据微调时同样适用改了结构就必须要重新保存 json。4. 超参数搜索与训练hyperas 调参与模型落盘模型结构定下来之后剩下的工作量大部分在超参数。LCTFP 专门带了一个 cnn_lstm_param.py 做超参数搜索功能是用 hyperas 在卷积核数量、LSTM 单元数、dropout、batch_size 这些维度上自动找一组使验证 loss 最小的组合。用它之前要先在你的 python 环境里装好 hyperas不然脚本第一行 import 就会报错。很多人以为超参数搜索是玄学其实搜索空间设得合不合理直接决定搜索结果是可用模型还是垃圾配置。4.1 hyperas 搜参数cnn_lstm_param.py 在做什么hyperas 的思路是把 Keras 模型定义里的关键数字换成分布表达式再用 hyperopt 后端采样验证。搜索空间每个参数的取值范围要和流量预测的常识对齐窗口 look_back 固定把 filters、units、dropout 留给搜索器。比如from hyperopt import Trials, STATUS_OK, tpe from hyperas import optim from hyperas.distributions import choice, uniform def data(): # 这里加载 input_data.py 切好的 X_train, y_train, X_val, y_val return X_train, y_train, X_val, y_val def model(X_train, y_train, X_val, y_val): filters {{choice([32, 64, 128])}} units {{choice([32, 64, 128])}} dropout {{uniform(0.1, 0.4)}} # 中间省略网络构建和 cnn_lstm_final.py 主干一致 model.fit(X_train, y_train, batch_size{{choice([32, 64])}}, epochs20, validation_data(X_val, y_val), verbose0) val_loss model.evaluate(X_val, y_val, verbose0) return {loss: val_loss, status: STATUS_OK, model: model} best_run, best_model optim.minimize( modelmodel, datadata, algotpe.suggest, max_evals30, trialsTrials())逻辑说明{{choice([...])}}和{{uniform(...)}}不是普通 Keras 代码它们会被 hyperas 解析成搜索维度。max_evals30表示最多尝试 30 组参数组合每跑完一组用验证集 loss 作为目标函数值由 tpe 算法决定下一组采样方向。参数说明choice 是离散候选uniform 是连续区间。流量预测场景下 dropout 别搜到 0.5 以上短时流量序列本身样本间相关性强dropout 过大会让网络学不到相邻时刻的峰值延续关系验证 loss 不降反升。提示cnn_lstm_param.py 的搜索结果不能直接当最终模型用。它找到的是「验证集上 val_loss 最小的组合」落地时还要在测试集上重验一遍确认高峰时刻的预测没有整体滞后这一步经常被跳过。4.2 训练闭环early stopping、best h5 和 final h5 差在哪训练主脚本里一般会挂两个回调EarlyStopping 监听验证集 loss连续多轮不降就停止ModelCheckpoint 只在验证 loss 创新低时保存一次快照。这就是 Model 目录里 cnn_lstm_best.h5 的来源它保存的是整个训练过程中验证集最优的那一版而不是最后一次 epoch 的权重副本。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint checkpoint ModelCheckpoint(Model/cnn_lstm_best.h5, monitorval_loss, save_best_onlyTrue) early EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue) history model.fit(X_train, y_train, batch_size64, epochs50, validation_data(X_val, y_val), callbacks[checkpoint, early])逻辑说明验证集 loss 在 epoch 20 左右最低、到 epoch 30 开始回升时EarlyStopping 会在 epoch 35 左右截断训练restore_best_weightsTrue 把权重回滚到 epoch 20 的状态checkpoint 同时在磁盘上留了一份 cnn_lstm_best.h5。这样即使训练中途崩了也有一份可用的最优权重兜底。参数说明patience 是连续几轮验证 loss 不降就停止一般取 5~10。太小容易在 loss 波动期提前停止太大则失去早停意义。batch_size64 在单站点流量数据上是个稳定的起点数据量小就降到 32数据量大可以试 128。超参数建议范围对预测的影响filters32~128太小学不到局部模式太大过拟合kernel_size3~5决定局部感受野覆盖几个时隙LSTM units32~128决定时间依赖的记忆容量dropout0.1~0.3控制过拟合短时流量不宜太大batch_size32~64太小训练震荡太大收敛慢patience5~10早停等待轮数5. 避坑交通流量预测最容易翻车的四个点跑这套 LCTFP 代码时真正让人头疼的不是模型结构而是数据处理和训练细节里的玄学问题。下面四条是我拆这个资源时踩过、也看别人反复踩的坑每条都按现象、原因、解决来讲。前两条是数据泄漏问题后两条是环境和多站点问题顺序基本对应你实际跑代码时会遇到的先后次序。5.1 第一雷区全局 MinMax 归一化把测试信息泄进来现象训练时的验证集 RMSE 非常漂亮把模型放到真实流量曲线上预测高峰时段几乎全变成平线完全看不出早晚高峰的起伏。原因归一化时对整个数据集做了一次 fit测试段的最大值早在训练前就被模型看到了。流量预测里测试段往往是高峰日或节假日数值范围跟训练段差异大一旦全局 fit测试段的量程信息就进了缩放器验证指标虚高真实预测能力被高估。解决预处理脚本里把归一化严格拆成两步训练段单独 fit验证和测试段只调 transform。上面 2.2 的 normalize_flow 函数就是按这个逻辑写的。跑完记得再打印一次测试段真实最大值确认它不在训练段的统计范围内。5.2 第二雷区LSTM 输出去学「复制粘贴」现象预测曲线和真实曲线形状相似但整体右移一个时间步RMSE 不算高可高峰到达时刻永远比真实晚一个时隙做拥堵预警时完全不可用。原因流量序列在相邻时间步上自相关很强LSTM 最省力的拟合方式是直接复制上一时刻的值到输出。这种模型 loss 低但没有真正学到增长趋势只是把输入滞后了一份。解决先对流量序列做一阶差分训练差分序列预测完再把差分量加回原值import numpy as np diff np.diff(flow, n1) # 一阶差分消除相邻强自相关 # 训练完成后还原预测值 flow_pred np.cumsum(np.concatenate(([flow[0]], diff_pred)))逻辑说明差分把原始流量的趋势项去掉模型学的是「下一时段比当前时段多多少」这个增量而不是「下一时段等于当前时段」的复制。还原时用 cumsum 累加回去。参数说明n1 表示一阶差分流量数据通常一阶就够。如果数据采样密度更高、自相关更强可以试二阶差分但还原时要连续做两次 cumsum链式累积误差也会变大需要额外观察还原后的数值漂移。5.3 第三雷区cnn_lstm_param.py 一跑就报错现象python 环境下运行 cnn_lstm_param.py报错信息指向 hyperas 里的 import 或 theano 相关模块有时还会提示找不到某个 keras 内部函数。原因hyperas 对 tensorflow/keras 的版本很敏感较新的 keras 版本里部分 API 已调整hyperas 内部还在用旧写法直接 import 会触发兼容性错误。报错信息往往不直接说版本问题而是甩一个「AttributeError: module X has no attribute Y」容易误导排查方向。解决先确认 python 环境里装的是 tensorflow 2.x keras 2.x 的配套版本然后安装 hyperas再单独跑一条最小调用验证pip install hyperas python -c from hyperas import optim; print(hyperas ok)逻辑说明这条最小命令把 hyperas 的导入问题提前暴露出来。如果 import 报错优先检查 python 版本和 keras 版本hyperas 在 python 3.10 上偶尔有兼容问题如果这行能过再回跑 cnn_lstm_param.py问题基本出在数据加载函数上和 hyperas 本身无关。5.4 第四雷区多站点混训导致 loss 震荡现象把 PeMS 多个站点数据直接拼成一个长序列喂给模型训练 loss 忽高忽低验证集指标也反复抽风上一轮还好好的下一轮就崩了。原因不同断面的车道数、限速、上下游位置不同流量量纲和峰值形态差异很大。模型把「站点差异」误当成要学习的特征在站点切换处产生大的预测偏差梯度被拉来拉去训练稳定性自然差。解决两个方向。简单的是按站点分别训练各存一套权重推理时按站点加载对应模型这套资源本身也按站点组织 PeMS 数据操作起来并不麻烦如果要混训就给每条样本加一个站点编码特征输入从 (look_back, 1) 变成 (look_back, 1 n_sites)用 one-hot 告诉模型当前样本来自哪个断面网络就能把站点差异当作条件而不是噪声。6. 拿到模型先别急着上线用 compare.py 横向对比和方向命中率做检查资源里的 compare.py 是专门干对比的它加载 lstm.json、sae.json、cnn_lstm_final.json 三套结构分别配上对应的 h5 权重对同一段测试集做预测再把三条曲线和真实曲线画在一起输出 threemodels.png。看这张图时我建议先盯两个地方早高峰的尖峰时刻是不是对齐晚高峰的回落段是不是滞后。预测曲线一滞后做信号联动或拥堵预警就失去意义这时候 RMSE 再低也不值得高兴。除了看图我每次还会补一个数字指标——方向命中率。它衡量预测值和真实值在相邻时刻之间的变化方向是否一致比 RMSE 更能反映峰值跟随能力import numpy as np def direction_accuracy(y_true, y_pred): diff_true np.diff(y_true.flatten()) diff_pred np.diff(y_pred.flatten()) return np.mean((diff_true * diff_pred) 0)逻辑说明真实流量上升而预测也上升方向一致真实流量下降而预测还在上升方向不一致。direction_accuracy 低于 0.5 说明模型基本是在贴均值对峰值拐点不敏感。RMSE 看数值误差大小方向命中率看趋势是否跟对两个指标配合起来才是完整的评估闭环。把 LCTFP 代码包拿回去之后我的习惯是只改数据路径先跑通 cnn_lstm_final.py再单独跑 compare.py 生成三模型曲线然后把测试集预测结果喂进上面这个函数。从那以后我每次做流量预测都强制走一遍「差分-训练-还原-方向命中率」的闭环避免陷在 RMSE 低、滞后严重的假象里反复浪费版本迭代时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表