ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

交通流预测三模型对比:SAEs、LSTM与GRU实战解析

交通流预测三模型对比:SAEs、LSTM与GRU实战解析

简介:这份资源是一套基于深度学习的交通流预测项目代码包,面向交通工程、智能优化及神经网络方向的本科与研究生,可用于课程设计、毕业设计或科研复现。压缩包共含17个文件,约3.2MB,主要文件类型包括Python脚本、模型权重文件、CSV数据文件、结果图像和说明文档;其中4个py文件覆盖数据预处理、训练与评估流程,3个h5文件为SAEs、LSTM、GRU三种模型权重,5个csv文件用于存储训练集、测试集与损失变化记录,4张png图像直观展示各模型预测效果。已有860人下载学习。资源适合具备一定Python基础但缺少完整实验框架的读者,可直接借助项目结构理解多模型对比方法,替换数据后进一步训练与调整参数,也可将评估与可视化流程迁移至其他时序预测任务,实用性与扩展性兼顾。

1. 交通流预测上手最快的资源:SAEs、LSTM、GRU 三模型对比包

做交通流预测的同行应该都有过这种经历:模型代码东拼西凑能跑,但训练曲线、误差图、权重文件三样东西拿不出手,写到论文里底气不足。这个基于深度学习(SAEs、LSTM、GRU)的交通流预测 Python 包,把「数据 → 训练 → 权重 → 评估图」整条链路一次性给齐了,main.py 一键跑通,train.csv / test.csv 自带,三个模型的 h5 权重和 loss.csv 都预先存好,images 目录里还放着四种评估图。适合本科毕设、硕士课程实验,也适合刚接触时序预测的从业者用来对比三种模型的收敛行为。拆包前先提醒一句:运行环境是 Python + TensorFlow/Keras,README 里如果提到 matlab 那是发布者的模板描述,别下错版本。

2. 从网络结构到数据形态:三模型预测的基本盘

2.1 为什么偏偏是这三个模型:SAEs / LSTM / GRU 的分工

这个包把三类模型放在一起对比,不是开发者随手拼的。交通流数据有两个显著特性:一是强周期性,早晚高峰的流量形态每天都在重复;二是强噪声,突发事件、信号灯配时变化、天气干扰都会让流量曲线产生毛刺。这三个模型恰好从三个角度去逼近这种数据。

SAEs(栈式自编码器)负责「特征重表达」。它先做无监督预训练,把原始流量序列压缩成低维表示,再用带标签数据微调。对噪声较多的交通流数据来说,SAEs 的作用类似于先滤波再回归,能去掉一部分和预测无关的高频抖动。作为一个 baseline,它训练速度快,但对长时依赖基本无能为力。

LSTM 解决的是「长时记忆」。交通流预测要用过去 15 到 30 分钟的流量变化去推断未来 5 到 15 分钟,这个时间窗里有明显的跨步依赖。LSTM 的细胞状态和三个门(输入门、遗忘门、输出门)让梯度能沿时间轴反向传播得足够深,所以它是这类任务里最稳的主模型。

GRU 则可以理解成「砍掉一半参数的 LSTM」。它只保留更新门和重置门,用更少的参数量逼近 LSTM 的效果。在训练资源有限、需要快速迭代的场景下,GRU 往往比 LSTM 更适合做第一版实验。

模型门控数量参数量训练速度适合场景
SAEs无门控最少快特征提取、baseline
LSTM3 个门多慢长时依赖主模型
GRU2 个门较 LSTM 少约 1/3较快快速迭代、资源受限

所以这套代码的设计逻辑很清晰:拿 SAEs 当轻量级参照,拿 LSTM 当精确主模型,拿 GRU 检验「参数减半后精度损失多少」。论文里的三模型对比表,正好用这三个角色填满。

2.2 从流量数据到监督学习:滑动窗口是绕不开的第一步

交通流原始数据是一维或多维时间序列,深度学习模型不能直接吃。常见做法是把它改造成监督学习格式:用前 n 个时刻的流量值预测后 m 个时刻的流量值。这个包里的 train.csv 大概长这样:每行是一个采样时刻,列是时间戳、车流量、平均速度、占有率这类字段。data.py 干的第一件事就是把序列切成长度为 look_back 的滑动窗口。

import numpy as np import pandas as pd def load_and_windowing(csv_path, look_back=10, predict_step=1): df = pd.read_csv(csv_path) values = df['flow'].values.reshape(-1, 1) # 只取流量列 X, y = [], [] for i in range(len(values) - look_back - predict_step + 1): X.append(values[i:i + look_back]) # 输入:过去 look_back 个时刻 y.append(values[i + look_back:i + look_back + predict_step]) # 输出:未来 predict_step 个时刻 return np.array(X), np.array(y)

这段代码是包内 data.py 逻辑的常用实现形态。look_back控制用多长的历史窗口,predict_step控制预测未来几个时刻。交通流短时预测的标准设定是 look_back=10(对应过去 10 个采样周期)加 predict_step=1(预测下一个周期)。如果你要预测未来 30 分钟,就把 predict_step 改成对应的高采样倍数。注意这里是按时间顺序构造样本的,切好之后必须保持原有顺序,不能 shuffle 整个数据集,否则验证集里会混进未来信息。

2.3 归一化:时序预测里最容易被低估的一步

README 里提到的 loss.csv 是训练过程的记录,但训练前要先把数据缩放到同一个量纲。交通流数据集里流量可能到几千,占有率只有 0 到 1,直接喂给神经网络会让优化器在占有率维度上寸步难行。包里的做法是 MinMaxScaler 或 StandardScaler,把每个特征独立缩放到 0 到 1 区间。

from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler(feature_range=(0, 1)) scaled_flow = scaler.fit_transform(values) # 训练完预测时,必须用同一个 scaler 做 inverse_transform pred_flow = model.predict(X_test_window) pred_flow_real = scaler.inverse_transform(pred_flow)

fit_transform只能用在训练集上,验证集和测试集要用transform,这是防止数据泄漏的基本底线。包里把 scaler 和模型权重一起存了下来,预测阶段直接恢复。很多人在这一步翻车:预测结果直接拿归一化数值和真实流量比 RMSE,误差小得离谱,全是假象。反归一化之后的误差才是能写进论文的数字。

3. 从 train.csv 到 loss.csv:完整跑通训练的每一步

3.1 包内文件结构与数据接口

拆开压缩包,实际负责干活的是这样几个文件:

文件/目录作用
main.py入口脚本,控制整个训练与评估流程
data.py数据加载、滑动窗口构造、归一化
model.py定义 SAEs / LSTM / GRU 三个模型结构
train.py训练循环,保存模型权重和 loss 记录
train.csv / test.csv训练与测试数据集
*.h5三个模型训练好的权重文件
loss.csv每个 epoch 的训练损失记录
images/三模型预测效果图及误差对比图

先打开 main.py,通常是一个顶层调度脚本。它会依次调用 data.py 加载数据、调用 model.py 构建模型、调用 train.py 完成训练,最后把预测图输出到 images 目录。如果你的目的是复现训练过程,而不是重新发明一套,直接按 main.py 的顺序走就可以。

数据接口的核心约定在 data.py 里。train.csv 必须包含flow列(流量),其余列如速度、占有率可作为附加特征。data.py 里有一个load_dataset()函数,我一般会确认它做三件事:读 CSV、按时间索引排序、把特征列标准化。如果 CSV 里的时间戳是字符串格式,记得先pd.to_datetime()解析,不然排序会按字典序来,早晨 9 点会排在下午 2 点后面。

3.2 model.py 里的三套结构:Keras 实现与参数要点

model.py 是包的核心模块,三个模型全部用 Keras 的 Sequential API 搭出来。SAEs 的结构一般是这样:输入层 → 若干个全连接自编码层 → 顶层的回归输出层。关键是自编码器部分要用无监督预训练,但包内为了简化,通常直接以有监督方式端到端训练,效果接近,省去预训练阶段。

LSTM 和 GRU 的标准结构则是LSTM(units, return_sequences=True)堆叠一层到两层,最后接 Dense 输出。units 的取值和交通流采样频率相关,常见是 64 或 128。这里给出 model.py 中核心的构建片段,实际打包代码就是这个套路:

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, GRU, Dense, RepeatVector, TimeDistributed def build_lstm(look_back=10, n_features=1, units=64): model = Sequential() model.add(LSTM(units=units, input_shape=(look_back, n_features), return_sequences=True)) model.add(LSTM(units=units)) model.add(Dense(units=1)) model.compile(loss='mse', optimizer='adam') return model def build_gru(look_back=10, n_features=1, units=64): model = Sequential() model.add(GRU(units=units, input_shape=(look_back, n_features), return_sequences=True)) model.add(GRU(units=units)) model.add(Dense(units=1)) model.compile(loss='mse', optimizer='adam') return model

return_sequences=True的含义是当前层输出每个时间步的完整序列,供下一层继续消化,堆叠 LSTM/GRU 时第一层必须开这个参数。input_shape=(look_back, n_features)里的 look_back 要和 data.py 里一致,否则模型构建时直接报维度错误。损失函数用mse而不是mae,是因为交通流预测对异常峰值的惩罚更敏感,MSE 能放大峰值误差,加快模型在高峰时段的收敛。

3.3 train.py 的训练循环:早停、Checkpoint 与 loss 记录

train.py 负责把模型和数据接起来。包内既然预设了 saes.h5、lstm.h5、gru.h5 三份权重,说明训练脚本里配了 ModelCheckpoint,每轮或每几轮把最优权重落盘。训练循环的核心逻辑如下:

from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, CSVLogger early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True) checkpoint = ModelCheckpoint('lstm.h5', monitor='val_loss', save_best_only=True) csv_logger = CSVLogger('lstm_loss.csv', append=True) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=200, batch_size=32, callbacks=[early_stop, checkpoint, csv_logger], verbose=1 )

几个参数值得按自己机器调整:patience=10意味着验证集 loss 连续 10 轮不下降就提前终止,这是训练这类小数据集的保命配置。交通流数据量通常不大,跑满 200 轮也不慢,但早停能省掉明显过拟合的后半段。batch_size=32是通用值,如果你的机器内存吃紧,改成 16;如果数据量很小(几千条),改成 8。CSVLogger 每轮把 loss 追加进文件,这就是包里那三份 loss.csv 的来源。

训练完成后,模型权重以 h5 格式存放,三份 loss.csv 记录了收敛过程,images 目录下同时生成 GRU.png、LSTM.png、SAEs.png 三个模型的预测对比图和 eva.png 的误差评估图。到这一步,一个标准的三模型交通流预测实验已经完整交付。

4. 常见问题排查:训练交通流模型的四个典型坑

4.1 坑一:loss 卡在同一个值附近不下降

现象:训练 30 个 epoch 后,loss 停在比如 0.02 左右几乎不动,预测曲线是一根斜直线或恒定值。

原因:最常见的是数据没归一化。流量值动辄几百上千,不缩放到 0~1 区间,激活函数的梯度很容易饱和。另一个原因是学习率太大,Adam 默认的 0.001 在大多数时候没问题,但如果损失曲线上下剧烈震荡,说明 lr 偏高。

解决:先确认 data.py 里做了 MinMaxScaler,且训练特征和标签都经过了缩放。再在 compile 时显式传optimizer=tf.keras.optimizers.Adam(learning_rate=0.0005),把学习率降一半重训。最后检查 loss.csv 里记录的 epochs 是否真的有波动,如果一行不变的死水,基本就是特征缩放或梯度饱和的问题。

4.2 坑二:预测曲线比真实曲线滞后一个时间段

现象:模型效果图里,预测曲线(通常是橙色或绿色)整体比真实流量曲线往右偏移,峰值出现的时刻延后一个采样周期。RMSE 看起来不算离谱,但图非常难看。

原因:这是时间序列一步预测的经典假象。模型学到的最优策略是「用上一个时刻的值近似下一个时刻」,因为交通流相邻时刻强相关,这样预测的误差最小。峰值来临的时刻,模型来不及反应,自然把上一时刻的低值搬了过来。

解决:这不算代码 bug,但写论文时不能回避。常用处理手法:一是把预测误差按峰值时段拆分统计,说明滞后主要发生在流量突变的转折区;二是给模型加入速度、占有率等辅助特征,让峰值变化有更丰富的信号可依;三是改成多步预测框架,迫使模型自己建立提前量。包里 images/eva.png 如果画的是滞后图,记得在论文里用「相关性分析」补充说明。

4.3 坑三:加载 h5 权重文件时报 shape 不匹配或 Unknown optimizer

现象:直接用load_model('lstm.h5')报错,有时是维度对不上,有时是提示优化器状态不存在。

原因:h5 文件里不仅存了权重,还存了训练时的优化器状态和模型结构。如果你换了 TensorFlow 版本(比如从 2.4 升到 2.10),Keras 对优化器的序列化格式可能变化;或者你改过 model.py 里某一层的 units 数量,导致权重 shape 对不上。

解决:加载时先只取网络结构,不取优化器状态:

from tensorflow.keras.models import load_model # 第一次尝试正常加载 try: model = load_model('lstm.h5') except Exception: # 退路:只反序列化网络,不要优化器状态 model = load_model('lstm.h5', compile=False) model.compile(loss='mse', optimizer='adam')

compile=False的作用是跳过优化器状态恢复,只看网络权重。这样即使 TensorFlow 版本有差异也能把模型结构拉起来,自己重新 compile。代价是加载后不能接着从上次轮次继续训练,但做预测和评估完全不受影响。我一般会先看 README 里标注的 TensorFlow 版本,尽量用同版本环境跑,从源头避开这个坑。

4.4 坑四:训练时内存溢出或 GPU 显存不足

现象:Windows 上跑 main.py 到 fit 阶段,提示 MemoryError,或者 CUDA out of memory。

原因:train.csv 如果包含多年逐分钟数据,样本量能达到几十万,滑动窗口切出来的 X 数组会撑爆内存。GPU 显存不足则是因为 batch_size 太大,模型一次塞进 GPU 的样本太多。

解决:把 batch_size 从 32 降到 8 或 4,显存开销成比例下降。内存溢出时改用生成器喂数据,或者切断冗余特征只保留流量列。这个资源包里的 train.csv 是精简过的,正常配置不会爆,但如果你替换成自己的海量数据集,这两个调整是必须做的。

5. 验证与进阶:三份 loss.csv 的正确读法和权重复用习惯

5.1 从 loss.csv 判断模型收敛,而不是看训练截图

包里自带三份 loss.csv,分别是 saes loss.csv、lstm loss.csv、gru loss.csv。很多人只看 images 里的训练曲线截图,其实 CSV 文件里藏着更多信息。用 pandas 读出来,每行是 epoch 序号和 loss 值。判断标准很简单:loss 在前 20 轮快速下降,后 20 轮缓慢收敛趋于平缓,说明训练正常;如果 loss 曲线呈 U 形,先降后升,那是过拟合信号,应直接把 patience 调小到 5 再重训。

5.2 复用训练好权重的完整套路:数据预处理对象要一起恢复

这套代码里最有复现价值的是三份 h5 权重。拿到新一段测试数据,正确做法是加载权重后用同一套预处理管道做推理:

import joblib from tensorflow.keras.models import load_model model = load_model('gru.h5') # 恢复模型 scaler = joblib.load('scaler.pkl') # 恢复归一化器,如果没有就用原训练代码里的 scaler raw_series = pd.read_csv('test.csv')['flow'].values.reshape(-1, 1) scaled = scaler.transform(raw_series) # 注意:这里用 transform,不是 fit_transform X_test = build_windows(scaled, look_back=10) # 复用 data.py 的函数 pred = model.predict(X_test) pred_real = scaler.inverse_transform(pred) # 反归一化回真实流量

scaler.pkl是你在训练前手动保存的归一化器,如果包里没带这个文件,只能从训练脚本里找到当时的 scaler 对象重新 fit 一遍训练数据。这提醒了一件事:以后自己训练模型时,把 scaler 和 h5 权重放在同一目录保存,别只留权重文件。反归一化这步漏掉的话,拿到的预测结果还是一堆 0 到 1 的数字,根本没法跟真实流量比较。

从那以后,我每次跑交通流数据集都会强制走一遍全链路:数据划分 → 归一化 → 训练 → 早停 → 反归一化 → 误差统计 → 画图对比,最后再写结论。这套包的目录结构正好逼着你按这个顺序走完,省掉不少回头路。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进