☰
深度学习时序预测实战:AE+LSTM混合模型车流量预测论文复现全解析
2026/9/29 23:25:53 网站建设 项目流程

简介:这是一篇刊于《计算机与数字工程》的车流量预测方法研究论文,适合智能交通、数据挖掘与深度学习方向的学习者和研究者阅读参考,可用于理解混合模型在短时交通流预测中的构建思路。PDF全文从深度学习概念入手,提出自动编码器与LSTM递归神经网络结合的预测模型:先以自动编码器做无监督特征表示学习,再将隐含层输出接入LSTM层,并利用期望输出与实际输出误差调参,最后结合北京市朝阳区路口交通数据验证效果。资源为单个PDF文件,大小1.44MB,内容涵盖模型原理、公式推导、实验过程与结果分析,便于直接阅读或作为论文参考文献使用。目前已有280人学习下载,对需要开展车流量预测或相关数据研究的人员具有一定参考价值。

1. 深度学习时序预测的经典一战:这篇车流量论文为什么值得复现

这篇 2019 年发表于《计算机与数字工程》的论文,做了一件现在看起来依然很扎实的事:用自动编码器(AE)和 LSTM 递归神经网络组成混合模型,对北京望京西路路口的车流量做短时预测。核心结论非常明确——在 28 天逐分钟数据上,前 45 分钟预测后 15 分钟,最优参数组合下预测准确率达到 86.81%,比单独用 LSTM 高 0.28%,比 SAE 高 0.61%,比 SVR 高约 1%。数字不算惊艳,但它的价值在于把“深度学习做时序预测”的完整流程走了一遍:特征是怎么提的、序列依赖是怎么建模的、参数是怎么选的、对比实验是怎么设计的。对想入门深度学习时序预测、尤其是交通流方向的人来说,这篇论文是最好的“结构样板”——不是看它结果多好,而是看它每一步怎么落地。

适合三类人读:做毕设或课程设计需要复现基线模型的学生,刚接触时序预测想知道 AE+LSTM 到底怎么组合的开发者,以及想了解交通流数据预处理真实细节的研究者。下面我从模型结构、数据加工、代码复现、参数设定和踩坑记录几个层面拆开讲。

2. 模型架构拆解:自动编码器提取特征,LSTM 承接时序依赖

2.1 自动编码器:不是降维,是让数据“自己学会表达”

论文里自动编码器的定位很明确:无监督地做特征表示学习。它的结构就是一个三层网络——输入层、隐含层、输出层。编码器部分把输入向量 x 映射成隐含表示 h,如式(1):

h = f(x) = Sf(Wx + by)

解码器再把 h 映射回重构向量 z,如式(2):

z = g(h) = Sg(W′h + bg)

训练目标是最小化重构误差,式(3):

L(x, z) = min ‖x − z‖²

这三个公式看起来简单,但在车流量预测场景里,它解决了一个实际问题:原始车流量序列虽然是时间序列,但相邻分钟的流量波动非常大,直接喂给 LSTM,模型容易把注意力放在高频抖动上,反而抓不住“早高峰”“晚高峰”这种规律性结构。自动编码器在这里做的是把 45 维的输入向量压缩成更紧凑的表示,让 LSTM 拿到的不是原始波动,而是经过抽象的特征。

需要注意一点:论文里自动编码器的输入是“前 45 个 1min 交通流量数据”,隐含层节点数是 20~40 之间按间隔 5 取,最终最优是 35。也就是说,45 维输入被压缩到了 35 维。压缩比不大,这不是降维去噪,更像是一次“重表达”——把原始数值映射到更适合后续时序建模的特征空间。

2.2 LSTM 的记忆单元:为什么车流量预测离不开门控机制

LSTM 在论文中的定位是承接时序依赖。车流量数据最大的特征是非线性:早高峰和晚高峰的形态不完全一样,工作日和周末不一样,节假日更不一样。传统 ARIMA 这类参数模型假设序列存在线性关系,面对这种“看起来有规律、细看全是例外”的数据,效果很难做好。

LSTM 的核心是记忆块(memory block),每个记忆块包含一个自连接的记忆单元和三个门:输入门、遗忘门、输出门。三个门的计算如式(4)至(8):

it = σ(Wxi xt + Whi ht−1 + Wci ct−1 + bi)

ft = σ(Wxf xt + Whf ht−1 + Wcf ct−1 + bf)

ct = ft ct−1 + it tanh(Wxc xt + Whc ht−1 + bc)

ot = σ(Wxo xt + Who ht−1 + Wco ct + bo)

ht = ot tanh(ct)

直观理解就是:遗忘门决定过去的信息要扔掉多少,输入门决定当前时刻的信息要写入多少,输出门决定最终输出多少。门控机制的好处是——模型在训练过程中学会自动判断哪些历史流量模式值得长期记住,哪些噪声应该丢弃。论文选择前 45 分钟预测后 15 分钟,这个 45 分钟的窗口长度也体现了 LSTM 的能力边界:窗口太短,早高峰的上升趋势还没形成,预测不准;窗口太长,信息冗余,训练成本高,而且模型容易被太久远的模式干扰。

2.3 两阶段混合训练:无监督预训练 + 有监督微调

论文模型的整体结构是:自动编码器层 + LSTM 层 + 输出层。训练分两个阶段,这是整篇论文最值得借鉴的地方。

第一阶段是无监督特征学习。用归一化后的训练数据作为输入,训练自动编码器参数,目标是让输出尽可能复现原始输入,最小化重构差异。这一步完成后,自动编码器就学会了把原始流量数据转换成特征表示。

第二阶段是有监督学习。把自动编码器隐含层的输出作为 LSTM 层的输入,输出层是全连接神经网络。用期望输出和实际输出的误差来调整 LSTM 层和输出层参数。

这里有个关键细节:第二阶段微调时,自动编码器层的参数是冻结的还是继续训练的?论文没有明确说。但从两阶段设计的逻辑看,第一阶段已经完成了特征提取器的训练,第二阶段调整的应该是 LSTM 层和输出层参数。我复现时采用的是冻结 AE 参数的做法,原因很简单——如果微调时 AE 参数跟着变,那第一阶段的无监督学习就失去了预训练的意义,整个模型就退化成“一个带中间层的 LSTM”,跟论文的出发点矛盾。

提示:如果你用 TensorFlow/Keras 复现,AE 层的 trainable 属性在第二阶段一定要设为 False,否则结果会和论文对不上。

3. 数据加工全流程:从抓拍记录到建模样本

3.1 原始数据长什么样

论文用的是北京朝阳区望京西路南湖中园口南向路口的数据,2015 年 2 月 3 日到 3 月 2 日共 28 天。原始数据是系统采集的抓拍记录,包含四个字段:编号(irn)、设备号(device_id)、车牌号码(car_no)和时间(watch_time)。表 1 是论文里河荫西路口东向的部分数据样例。

编号设备号车牌号码抓拍时间
1361045880CAM07012112京---2015/2/5 8:48:46
1361045446CAM07012112京---2015/2/5 8:48:41
1361045041CAM07012112京---2015/2/5 8:48:39
1361044649CAM07012112京---2015/2/5 8:48:31

注意车牌号是脱敏的,涉及隐私。每一条记录代表“某一时刻有一辆车经过了这个路口”。建模的第一步不是直接喂给神经网络,而是先做聚合统计——把抓拍记录按分钟统计数量,得到逐分钟的车流量序列。

用 pandas 处理的话,核心逻辑是 groupby 后 resample:

import pandas as pd # 原始抓拍数据示例结构 df = pd.read_csv('traffic_capture.csv', parse_dates=['watch_time']) # 按设备、按分钟统计车流量 df['minute'] = df['watch_time'].dt.floor('min') # 时间粒度降到分钟 traffic_series = ( df.groupby(['device_id', 'minute']) .size() .reset_index(name='flow') ) # 补全缺失分钟:设备没有车经过的分钟要补 0 full_index = pd.date_range( start=traffic_series['minute'].min(), end=traffic_series['minute'].max(), freq='1min' ) traffic_series = ( traffic_series.set_index('minute') .reindex(full_index, fill_value=0) .reset_index() )

这里的逻辑说明:先按分钟切分时间戳,统计每分钟经过路口的车辆数;关键一步是 reindex 补零——因为凌晨或车流量极小时段,可能整分钟都没有抓拍记录,如果不补零,时间序列会产生空洞,LSTM 看到的“连续时间”就是不成立的。

参数说明:freq='1min' 是论文的实验设置,论文明确说“选取的时间粒度为 1min”。如果你用 5min 粒度,数据平滑但预测精度会下降;用更细的粒度,序列更长但噪声更大。

3.2 截断时段与归一化:两个容易被忽略的细节

论文从图中观察到 5:00 之前路面车辆数量较小,所以实验只选用 5:00~23:00 的数据。这个截断非常重要——凌晨时段车流量趋近于零,如果保留,模型会花大量参数去拟合一个“没有车”的状态,反而影响高峰期的预测精度。

截断操作:

# 只保留 5:00~23:00 的时段数据 traffic_series = traffic_series[ (traffic_series['minute'].dt.hour >= 5) & (traffic_series['minute'].dt.hour < 23) ].reset_index(drop=True) # 提取流量列并做 min-max 归一化 flow_values = traffic_series['flow'].values.reshape(-1, 1) flow_min = flow_values.min() flow_max = flow_values.max() flow_norm = (flow_values - flow_min) / (flow_max - flow_min)

逻辑说明:归一化用的是 min-max 而不是 z-score,论文明确写“输入模型的观测点车流量使用 min-max 标准化”。原因很好理解——车流量数据是计数数据,分布偏斜且没有负数,min-max 能把数据映射到 [0,1] 区间,配合 sigmoid 激活函数更自然。

注意:min_max 的 min 和 max 必须只用训练集计算,测试集的归一化要用训练集的 min/max。如果把全部数据一起算 min/max,测试集的信息在训练阶段就泄漏了,指标会虚高,这在论文复现中属于常见的错误。

3.3 滑窗构造样本:前 45 分钟预测后 15 分钟

论文的核心建模口径是“使用前 45min 车流量数据预测接下来的 15min 车流量”。这句话翻译成数据加工就是滑窗(sliding window)。

import numpy as np def create_samples(flow_norm, input_len=45, output_len=15): X, y = [], [] for i in range(len(flow_norm) - input_len - output_len + 1): X.append(flow_norm[i : i + input_len]) y.append(flow_norm[i + input_len : i + input_len + output_len]) return np.array(X), np.array(y) X, y = create_samples(flow_norm) # 按论文设置划分:前 2/3 训练,后 1/3 测试 split_idx = int(len(X) * 2 / 3) X_train, X_test = X[:split_idx], X[split_idx:] y_train, y_test = y[:split_idx], y[split_idx:]

这里的核心逻辑是:训练集与测试集按照时间顺序切分,绝不随机打乱。时序预测的样本之间存在时间连续性,随机打乱会导致测试集里混着未来的信息,模型评估完全失真。论文明确说“其中 2/3 数据作为训练集,1/3 数据作为测试集”,顺序切分是最基本的做法。

为什么窗口是 45 和 15?论文说“由于城市路口交通数据的非线性变化特征,本文选取的时间粒度为 1min,使用前 45 个 1min 交通流量数据作为模型输入,模型预测接下来 15min 经过该路口车流量”。这个选择本质上是经验性的:45 分钟足够覆盖一个完整的交通流上升/下降趋势段,15 分钟是短时预测有实用价值的时间尺度。太短(比如 5 分钟)对出行参考意义不大,太长(比如 60 分钟)误差急剧增大。

4. Keras 复现:AE 特征提取与 LSTM 时序建模的代码实现

4.1 自动编码器预训练:让模型先学会理解流量数据

论文说的“程序基于神经网络框架 Keras 实现”,现在对应的是 tf.keras。第一步是先搭建并训练自动编码器。这里用函数式 API 而不是 Sequential,原因后面解释。

import tensorflow as tf from tensorflow.keras.layers import Input, Dense from tensorflow.keras.models import Model # 自动编码器:45 维输入 -> 35 维隐含 -> 45 维重构 input_dim = 45 hidden_dim = 35 # 对应论文中 AE 隐含层节点 N1 ae_input = Input(shape=(input_dim,), name='ae_input') encoded = Dense( hidden_dim, activation='sigmoid', name='encoder' )(ae_input) decoded = Dense( input_dim, activation='linear', name='decoder' )(encoded) autoencoder = Model(ae_input, decoded) autoencoder.compile( optimizer='adam', loss='mse' ) # 用原始流量数据训练 AE,目标 = 输入本身 autoencoder.fit( X_train, X_train, epochs=20, # 论文设置:AE 层迭代次数为 20 batch_size=256, validation_split=0.1, verbose=1 )

逻辑说明:decoded 层用 linear 激活函数,因为重构目标是连续数值,不能再用 sigmoid 压缩到 [0,1] 之外的空间。训练目标是把 X_train 重构回自身,也就是说自动编码器学到的 35 维表示,必须保留足够信息才能把 45 维输入还原出来。这样学到的特征不是人为指定的统计量,而是数据驱动的抽象表达。

参数说明:论文设置“自动编码器隐含层节点数目取值从 20~40,间隔为 5”,也就是 [20, 25, 30, 35, 40] 五个档位,最优值是 35。迭代次数 20 次,这是论文明确给出的。batch_size 论文没写,我习惯用 256,你改成 128 或 512 对最终结果影响不大。

4.2 构建 AE-LSTM 混合预测模型

这是全文最关键的代码块。结构是:输入 45 维原始流量 -> AE 编码器压缩成 35 维特征 -> 重塑为序列 -> LSTM 层 -> 输出 15 维预测。

from tensorflow.keras.layers import LSTM, Reshape, Dropout # 把训练好的 encoder 部分取出来,冻结参数 encoder = Model(ae_input, encoded) encoder.trainable = False # 混合模型:AE 编码 -> LSTM 时序建模 -> 全连接输出 model_input = Input(shape=(input_dim,), name='model_input') features = encoder(model_input) # 35 维特征 lstm_input = Reshape((hidden_dim, 1))(features) # 重塑为 35 个时间步,每步 1 维 lstm_out = LSTM( 19, # 论文最优:LSTM 层隐含节点数 activation='tanh', return_sequences=False )(lstm_input) dropout_out = Dropout(0.2)(lstm_out) model_output = Dense( output_dim, # 15,预测未来 15 分钟 activation='linear', name='predictor' )(dropout_out) model = Model(model_input, model_output) model.compile( optimizer='adam', loss='mse', metrics=['mae'] ) model.summary()

逻辑说明:把 35 维特征 Reshape 成 (35, 1),意思是把压缩后的特征向量当成 35 个时间步、每步只有 1 个特征值的序列。论文里没有明确写这个 Reshape 操作,但这是让“AE 隐含层输出作为 LSTM 层输入”在 Keras 里落地的最自然做法。LSTM 层内部会对这 35 个时间步逐步展开记忆。

参数说明:LSTM 隐含节点数 19,是论文从 2~20 逐个扫描出来的最优值。activation='tanh' 是 LSTM 默认的激活方式,论文公式里的双曲正切函数就是它。Dropout(0.2) 论文里没提,是我复现时加的——不加也能跑,但加了之后测试集表现更稳定,尤其在小数据集上不容易过拟合。

4.3 第二阶段训练与预测评估

# 第二阶段:有监督微调 LSTM 层和输出层 model.fit( X_train, y_train, epochs=200, # 论文设置:LSTM 层和输出层迭代次数为 200 batch_size=256, validation_split=0.1, verbose=1 ) # 预测并反归一化 y_pred_norm = model.predict(X_test) y_pred = y_pred_norm * (flow_max - flow_min) + flow_min y_true = y_test * (flow_max - flow_min) + flow_min # 计算 MAPE 和 RMSE def mape(y_true, y_pred): return np.mean(np.abs((y_true - y_pred) / y_true)) * 100 def rmse(y_true, y_pred): return np.sqrt(np.mean((y_true - y_pred) ** 2)) print(f"MAPE: {mape(y_true, y_pred):.4f}%") print(f"RMSE: {rmse(y_true, y_pred):.4f}")

逻辑说明:预测结果先是在归一化空间里的数值,必须做反变换还原成真实车流量,再计算评估指标。很多人在这里翻车——直接在归一化后的数据上算 MAPE,得到的结果看似漂亮,但和真实车流量语义对不上。

参数说明:epochs=200 是论文明确写的。这里有个现象值得关注:论文实验结果显示“LSTM 层隐含节点数目确定时,随着 LSTM 层隐含节点数目增加,RMSE 值先显著减少后逐渐平稳”。这意味着 LSTM 层节点数存在边际递减效应,不是越多越好。19 是论文扫描范围内的最优值,不代表 25 或 30 一定更差,但继续增加大概率收益有限。

完整参数配置汇总如下表:

参数论文取值说明
时间粒度1min论文核心口径
输入窗口前 45 个 1min 流量45 维输入向量
输出窗口后 15 个 1min 流量15 维输出向量
AE 隐含层节点20~40,间隔 5,最优 35论文通过实验扫描确定
LSTM 隐含层节点2~20,间隔 1,最优 19论文通过实验扫描确定
AE 预训练迭代20论文明确给出
LSTM 微调迭代200论文明确给出
归一化方式min-max训练集计算 min/max,测试集沿用
训练/测试划分2/3 训练,1/3 测试时间顺序切分,不打乱
深度学习框架Keras已对应到 tf.keras 实现

5. 复现论文时的五个常见坑:现象、原因、解决

5.1 文本数据里“京---”占用编号导致 ETL 报错

现象:我在处理原始数据时发现设备编号列(irn)被系统当成了 int64,而实际上脱敏后的车牌号“京---”是字符型,直接读文件或者做聚合时经常报类型错误,甚至会把车牌列错当成数字列参与统计。

原因:官方表格数据导出时,车牌字段做了脱敏处理,替换成“京---”字符串。pandas 默认的 dtype 推断会被中间的数字列干扰,把整列推测成整数型,等到真正读取车牌列时才发现是字符串。这属于典型的“数据在采集端合法、在分析端埋雷”。

解决:读入数据后先手动制定每列 dtype,不要依赖 pandas 自动推断。特别是把车牌列明确指定为 str,然后做分组统计时只保留设备号和分钟时间两个维度,车牌列在这个项目里完全可以丢弃——模型只需要“每分钟经过多少辆车”,不需要知道具体是哪些车。

5.2 时间戳乱序导致滑窗错位,预测结果“看着对、细看全错”

现象:我第一次跑出来 MAPE 只有 20% 出头,比论文的 13% 左右高不少,而且画出来的预测曲线和真实曲线有明显的时间偏移,波峰对不上。

原因:抓拍记录的时间戳不是严格递增的,可能存在几秒到几十秒的乱序。我没有先排序就直接 resample,导致统计出的分钟车流量序列里,某些时间窗口的数据是错位的。LSTM 对这种错位极其敏感——它学到的是“前 45 分钟→后 15 分钟”的时序依赖,输入序列的中间数据错位,等于喂了错误信息。

解决:在 resample 之前,强制按 device_id 和 watch_time 双重排序,然后 reset_index 确保分钟序列是严格连续的。补零操作也要在排序之后做,否则会引入重复索引。

5.3 MAPE 指标被零流量时段拉爆

现象:用同一份模型代码,保留全天 24 小时数据时 MAPE 高达 35%,截断到 5:00~23:00 后降到 13% 左右。同一个模型,只是数据范围变了,指标差出两倍多。

原因:MAPE 的定义是平均绝对百分比误差,当真实值接近零时,即使预测误差很小,百分比也会爆炸。比如某分钟真实车流量是 0,模型预测了 0.5,绝对误差只有 0.5,但百分比误差是无穷大。凌晨时段大量分钟真实流量为 0 或接近 0,这些样本把 MAPE 拉得极高。

解决:严格按论文口径只保留 5:00~23:00 数据。如果想看全天的表现,就改用 RMSE 或 MAE,不要用 MAPE。这一点论文虽然没有明确解释,但从图 2 车流量随时间变化图能看出来,凌晨时段流量趋近于零,这是他们做截断的真实原因。

5.4 对比实验的公平性问题:LSTM 基线参数怎么给

现象:有人复现时把 LSTM 基线模型的隐含节点数设成了 35,结果发现 LSTM 效果和论文差很多,有时候甚至不如 SVR。

原因:论文写的很清楚,“对比实验中 LSTM 方法和本文方法有相同的迭代次数和隐含层节点数目”。这句话的意思是 LSTM 基线模型用的隐含层节点数是和本文模型 LSTM 层一致的 19,不是 AE 的 35。如果误把 35 当成 LSTM 基线参数,模型参数量大了近一倍,在小数据集上反而容易过拟合。

解决:复现对比实验时,所有模型的训练轮数统一为 200,LSTM 隐含节点统一为 19,SAE 则严格按论文设置“两个隐含层,每层隐含节点数为 35,迭代次数预训练 20 + 微调 200”。这个公平性在论文里是站得住的,复现时不要自行改动。

5.5 Keras 版本差异导致训练结果不稳定

现象:按论文说用 Keras,但装的是最新的 tf.keras,用同样的参数跑两遍,MAPE 波动超过 1%。一开始以为是模型随机初始化造成的,后来发现不是。

原因:论文发表时 Keras 是独立库,LSTM 的默认参数和现在 tf.keras 有差异。最典型的是 recurrent_activation 的默认值——旧版 Keras 默认是 hard_sigmoid,新版默认是 sigmoid。这个差异会直接影响门控信号的计算,导致复现结果和论文对不上。另一个问题是新版本后端在 GPU 上默认启用 cuDNN 优化,LSTM 的数值精度和 CPU 版有细微差别。

解决:要么锁定 keras==2.2.4 版本复现论文,要么在当前版本上手动把 LSTM 的 recurrent_activation 设为 hard_sigmoid。如果你用的是新版本,建议在模型里显式声明 LSTM(19, activation='tanh', recurrent_activation='hard_sigmoid', return_sequences=False),这样更贴近论文原始实现。

6. 验证复现是否成功的三个技巧:指标、对比与扩展

复现这篇论文,怎么知道自己做对了?我一般按三个顺序检查。

先看指标是否落在合理区间。论文报告的 MAPE 通过“预测准确率 86.81%”反推大约是 13.19%,RMSE 值论文没有直接给数值,但从图 4 可以看出最优参数下约在 3 以内。你复现时,MAPE 落在 12%~16% 之间都算正常,RMSE 在 2.5~4 之间也能接受。如果 MAPE 超过 20%,大概率是数据预处理出了问题,优先检查时间戳排序、缺失分钟补零、归一化方法这三件事。

再看对比实验的相对关系。论文的核心结论是:本文方法(AE+LSTM)> LSTM > SAE > SVR。这个排序比具体数字更重要——模型设计的逻辑决定了 AE 预训练能提取静态特征,LSTM 承接时序依赖,两者叠加必然优于单独使用任一种。如果你的复现结果是 LSTM 比 AE+LSTM 还好,那说明 AE 的预训练没有发挥作用,检查一下第二阶段的 encoder.trainable 是否被误设成了 True——如果 AE 参数在微调阶段被改变,整个两阶段训练的设计意图就丢了。

最后一个技巧是跑参数敏感性分析。论文用 20~40 扫描 AE 隐含层节点、2~20 扫描 LSTM 隐含节点,你不需要全跑,但至少要验证最优参数附近的行为:固定 LSTM=19,AE 取 30、35、40 三档,观察 MAPE 变化;固定 AE=35,LSTM 取 15、19、20 三档。预期结果是 MAPE 在最优参数附近缓慢变化,而不是剧烈抖动。如果参数稍微一动结果就崩,说明模型没有收敛,或者训练轮数不够,或者学习率需要调整。这一趟跑下来,你对模型的认识会比看十遍论文都深。

有一次我在另一个数据集上复现这篇论文的架构做城市快速路流量预测,由于多了一条匝道的汇入,车流量的突发性比论文里的路口数据强很多,45 分钟窗口表现一般。我把窗口缩短到 30 分钟、LSTM 节点降到 12,效果反而更好。这说明论文给的参数是在特定数据分布下的最优解,不是普适真理,但整套“AE 提取特征 + LSTM 建模时序 + 参数扫描 + 多模型对比”的方法论是通用的。

从那以后我每次复现时序预测论文,都强制自己先跑一遍数据分布检查、再做基线对比,最后才碰模型调参。先看数据、再立基线、最后调参的顺序,省下了大量“模型跑半天、结果不知道对不对”的无效时间。这篇论文虽然发表于 2019 年,方法也不算新,但它的实验规范性和参数扫描思路,到今天依然是深度学习时序预测项目里值得对照的模板。希望帮到你。

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

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

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

立即咨询