
简介基于深度学习的交通流预测Python实现包面向智能交通、交通数据分析及时间序列预测方向的本硕学生与科研入门者围绕SAEs、LSTM、GRU三种代表性网络提供从数据加载、模型训练到效果评估的完整代码流程。压缩包共17个文件类型搭配较为完整5个CSV文件存放训练集与测试集4个Python脚本分别负责数据预处理、模型定义、训练与评估等环节3个H5文件为已训练好的模型权重4张PNG图展示loss下降曲线与预测效果另有1份README说明文件整体约3.2MB便于快速下载与按模块复现。压缩包内已包含运行结果可直接查看SAEs、LSTM、GRU在交通流预测上的对比表现适合作为课程设计、论文实验或深度学习时序入门练习的参照。目前已有860人学习下载代码结构清晰可在该基础上进一步调整网络参数或替换数据。1. 交通流预测为什么绕不开 SAEs、LSTM 和 GRU先弄清楚模型再动手做交通流预测的工程师十有八九上来就是把 5 分钟粒度的流量序列丢给 LSTM跑完看一眼 RMSE 就收工。这个流程本身没错但它漏掉了两个关键环节特征维度怎么处理、LSTM 和 GRU 到底该选哪个。基于深度学习SAEs、LSTM、GRU的交通流预测核心思路是把堆叠自编码器当作特征提取前端把 LSTM 或 GRU 当作时序建模主体再用同数据、同指标的对比实验解决“谁更适合当前路段”的问题。这篇文章不铺概念直接把数据预处理、模型定义、训练评估和常见翻车点完整走一遍给你一份能直接改成自己数据的 Python 方案。适合正在做时序预测的工程师也适合刚想把 LSTM 用在 Python 项目里的开发者把它当作“先跑通、再对比、后调优”的工作笔记读就行。2. 数据预处理与滑窗建样本顺序错了后面全白做交通流预测里模型决定上限数据决定能不能接近上限。我见过不少项目在模型上反复堆注意力、堆层数结果数据预处理阶段就埋了雷缺失值乱填、归一化泄漏、样本顺序被打乱。这些问题在训练阶段不炸在对比实验和上线时一起爆。动手前先把 Python 3.8 和 PyTorch 环境装好命令行里执行python -c import torch; print(torch.__version__)能输出版本号再继续。2.1 原始数据与缺失值处理常见的交通流采集数据来自路口线圈、地磁或卡口按固定时间间隔上报。最常见的时间粒度是 5 分钟或 15 分钟字段一般包括时间戳、流量、速度和占有率。流量单位可能是 veh/h 也可能是 veh/5min切换时间粒度做模型时要先统一。数据样例通常长这样字段含义常见单位timestamp采集时间5 分钟间隔volume断面流量veh/h 或 veh/5minspeed平均车速km/hoccupancy时间占有率%缺失值在这一类数据里非常常见检测器离线、通信抖动都会造成一段缺口。最简单的处理方式是线性插值pandas 直接支持import pandas as pd df pd.read_csv(traffic_flow.csv, parse_dates[timestamp]) df df.sort_values(timestamp) df[volume] df[volume].interpolate(methodlinear, limit_directionboth) df df.dropna(subset[volume])这里有几个细节值得说。interpolate默认按索引等距插值前提是你的时间序列是连续等间隔的如果有重复时间戳先用drop_duplicates(timestamp)去重。limit_directionboth是给序列首尾的缺失值也做插值否则开头一段会变成 NaN。不建议把缺失行直接删掉因为后续滑窗建样本时删除会让相邻样本之间出现“时间空洞”模型会以为前后是紧挨着的两个时刻等于人为制造了断裂的上下文。另一个常见误用是拿“前一天同一时刻”填充。这个方法不是不行但高峰期和非高峰期的波动形态差很多填出来的值在夜间低谷段容易明显偏离真实状态。我一般先用线性插值兜底再检查缺失比例超过 20% 就直接淘汰这个检测器断面不值得为它补一段假数据。2.2 滑窗函数把流量序列变成监督样本LSTM 和 GRU 都是监督学习模型需要(X, y)配对。交通流预测里最常用的建样本方式就是滑窗用过去seq_len个时刻的流量预测未来horizon个时刻的流量。这个步骤单独提出来写是因为它是整个实验里最容易出 bug 的位置。import numpy as np def sliding_window(data, seq_len12, horizon1): X, y [], [] for i in range(len(data) - seq_len - horizon 1): X.append(data[i:i seq_len]) y.append(data[i seq_len:i seq_len horizon]) return np.array(X), np.array(y) # data 是一维流量序列 X, y sliding_window(train_volume, seq_len12, horizon1) print(X.shape, y.shape)data[i:iseq_len]取的是待预测时刻之前的连续窗口y取的是窗口之后horizon步的真实流量二者在时间上严格不重叠。这里最容易翻车的参数是seq_len5 分钟粒度下12 步等于 1 小时24 步等于 2 小时。交通流有明显的早晚高峰周期性窗口太短模型只看得到分钟级抖动太长则会引入无关的历史波动。我一般起步用 12数据量充足、要做高峰预测时再用 24 对比。还有一个常被忽略的参数是滑窗步长。上面这段代码每次移动 1 步生成样本之间高度重叠训练集很大但信息增益有限。如果数据有上百万行建议改成stride5或stride10降低样本重合度只有几万行的小数据步长 1 反而更合适。这个取舍在最终指标上的影响比换激活函数大得多。2.3 归一化与时间序列切分三个关键点归一化对 LSTM/GRU 几乎是必须的。流量数据白天能冲到几千午夜接近 0不归一化直接喂进去初始 loss 会很大Adam 也容易在峰值处反复震荡。常见的做法是用MinMaxScaler把序列压到 [0,1]流量数据没有明显的长尾异常时这个选择够用。from sklearn.preprocessing import MinMaxScaler # 按时间顺序切分前 70% 训练中间 10% 验证最后 20% 测试 n len(volume) train_df volume[:int(n * 0.7)] val_df volume[int(n * 0.7):int(n * 0.8)] test_df volume[int(n * 0.8):] scaler MinMaxScaler() scaler.fit(train_df.values.reshape(-1, 1)) train_scaled scaler.transform(train_df.values.reshape(-1, 1)).ravel() val_scaled scaler.transform(val_df.values.reshape(-1, 1)).ravel() test_scaled scaler.transform(test_df.values.reshape(-1, 1)).ravel()这套代码里藏着三个时间序列预测的关键约束。第一scaler.fit只作用在训练集上验证集和测试集只能用transform。如果对整个序列先归一化再切分测试集的均值、最大值就已经参与训练了这是最常见的数据泄漏。第二切分必须按时间顺序不能用sklearn的train_test_split随机打乱。序列一旦打乱训练样本里就可能出现“利用未来预测过去”的上下文验证集指标漂亮上线后一塌糊涂。第三验证集不能从训练集中间挖一段要取时间上训练集之后的数据这样 early stopping 判断的才是模型对未来真的有没有泛化能力。注意先归一化再做滑窗还是先滑窗再归一化我通常先对整段序列归一化再按顺序切分、再滑窗。原因很简单MinMaxScaler对每个点位做变换不依赖样本滑窗结构先滑窗再归一化容易在写代码时不小心让scaler.fit接触到验证区。上面代码处理的是单变量流量。如果你手上的数据同时有流量、速度、占有率或者要联合预测多个断面的流量滑窗函数可以原样复用只是传入data的形状从(T,)变成(T, F)。此时每一行样本的形态是(seq_len, F)滑窗结束后X.shape (N, seq_len, F)对应到模型里就是input_sizeF。很多项目把 SAE 放在 LSTM 前面目的就是对 F 这个维度做压缩下一章细说。3. SAEs 特征提取与 LSTM/GRU 建模跑通最小可复现基线模型结构上这套方案可以被拆成两段SAE 管特征压缩LSTM/GRU 管时间依赖。不要把它们当成竞争关系它们是串联关系。这一章先讲清楚 SAE 为什么放在 LSTM 前面然后给出两个最基础的序列模型定义和一套训练循环保证你能在半小时内跑出第一组 loss 曲线。3.1 自编码器与堆叠自编码器在交通流预测里的定位先明确一点自编码器的结构很简单——一个 encoder 把输入压成低维隐向量一个 decoder 把它还原训练目标是最小化重建误差。堆叠自编码器就是把多个这样的结构逐层串起来前一个 encoder 的输出作为后一个的输入逐层预训练之后再把整个 encoder 部分拿出来当特征提取器。交通流预测里 SAE 最常见的落位有两个。一是多断面场景把相邻路段的流量拼成一个向量用自编码器降到更低维消除断面之间的冗余关联再送给 LSTM。二是多字段场景流量、速度、占有率同时作为输入自编码器负责把高维原始特征压成隐含表示。单断面单变量的场景SAE 的价值不大直接 LSTM 往往更省事。这一点很多文章不提导致新手在单变量数据上硬套自编码器结果反而掉点。用 PyTorch 定义一个最基本的自编码器不需要多少代码import torch.nn as nn class AutoEncoder(nn.Module): def __init__(self, input_dim, hidden_dim): super().__init__() self.encoder nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU() ) self.decoder nn.Sequential( nn.Linear(hidden_dim, input_dim), nn.Sigmoid() ) def forward(self, x): code self.encoder(x) return self.decoder(code), code注意decoder末尾用了Sigmoid前提是输入已经归一化到 [0,1]。如果你用标准化而不是 MinMaxSigmoid 就不合适需要换成恒等输出。hidden_dim不要压得太狠输入 8 维压到 4 维起步信息损失太大时后面 LSTM 也救不回来。所谓堆叠就是把这个类实例化成两层第一层把 8 维压到 4 维训练好后把 encoder 输出喂给第二层第二层从 4 维压到 2 维最后把两层 encoder 拼接起来再接 LSTM。实际工程里很多项目为了省事不逐层预训练直接把多层 encoder 和 LSTM 拼起来端到端训练预训练当作可选的预热身。这里有个容易绕晕的地方SAE 压缩的是特征维不是时间维。假设你有 8 个断面的流量滑窗后每个样本的形状是(seq_len, 8)。要让自编码器处理就要把它 reshape 成(batch * seq_len, 8)对每个时间步单独做降维输出再 reshape 回(batch, seq_len, reduced_dim)最后接 LSTM。如果只有单断面单变量特征维是 1SAE 没有压缩空间直接用 LSTM 就好。3.2 用 PyTorch 定义 LSTM 模型无论前面接不接 SAELSTM 本身的定义是同一套。我习惯从最小模型开始写先保证结构能跑通再慢慢加复杂度。下面是完整可用的 LSTM 预测模型import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers1): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue ) self.fc nn.Linear(hidden_size, 1) def forward(self, x): # x: (batch, seq_len, input_size) out, _ self.lstm(x) # out: (batch, seq_len, hidden_size) last out[:, -1, :] # 取最后一个时间步的隐状态 return self.fc(last) # (batch, 1)这里值得展开的有三个参数。input_size对应每个时间步的特征数单变量流量就是 1多断面或多字段就是特征数 F。hidden_size是 LSTM 隐状态维度64 是比较稳的起点数据量大、序列长可以加到 128小于 32 表达力不足高峰期的突变细节很难学到。num_layers在交通流数据上不要一上来就设 2 或 3单层的训练速度和稳定性都更好等确认单层欠拟合再加深。再一个是batch_firstTrue。PyTorch 的 LSTM 默认输入形状是(seq_len, batch, input_size)设成batch_first后输入变成(batch, seq_len, input_size)和DataLoader出来的张量形状一致省得写transpose。很多人抄网络代码时没注意这个参数结果数据形状对不上报错后玄学式地加permute代码越改越乱。另外out[:, -1, :]取的是序列最后一个时间步的隐状态因为任务是用历史窗口预测未来只需要最后的汇总状态就够了。关于 dropoutPyTorch 的nn.LSTM(dropout0.2)只在num_layers 1时才起作用单层 LSTM 加了这个参数不会报错但也不会生效。网上很多模型定义里单层还挂 dropout属于看着热闹实际没参与计算。等模型加深到两层以上再加 dropout 并放在层间才是有效做法。3.3 用 PyTorch 定义 GRU 模型GRU 的定义几乎和 LSTM 一样把nn.LSTM换成nn.GRU就能跑class GRUPredictor(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers1): super().__init__() self.gru nn.GRU( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue ) self.fc nn.Linear(hidden_size, 1) def forward(self, x): out, _ self.gru(x) last out[:, -1, :] return self.fc(last)这两个模型放到一起最大的差别是参数量。LSTM 有三个门和一个 cell stateGRU 只有重置门和更新门没有 cell state。同样hidden_size64时GRU 的参数量大概比 LSTM 少三分之一训练更快占用显存也更低。在数据量不大、序列长度 12 到 24 的交通流预测任务里GRU 经常能取得和 LSTM 接近的精度这也是标题同时带上这两者的原因——不是让你二选一而是让你在对比实验里做选择。3.4 训练循环与最小评估函数模型定义好之后训练循环是固定的套路。下面这段代码覆盖了数据集加载、训练、梯度裁剪和验证评估的最小闭环from torch.utils.data import TensorDataset, DataLoader def make_loader(X, y, batch_size64, shuffleFalse): dataset TensorDataset( torch.tensor(X, dtypetorch.float32), torch.tensor(y, dtypetorch.float32) ) return DataLoader(dataset, batch_sizebatch_size, shuffleshuffle) model LSTMPredictor(input_size1, hidden_size64, num_layers1) optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.MSELoss() train_loader make_loader(X_train, y_train, shuffleTrue) val_loader make_loader(X_val, y_val, shuffleFalse) for epoch in range(100): model.train() for xb, yb in train_loader: optimizer.zero_grad() pred model(xb).squeeze(-1) loss criterion(pred, yb) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() if epoch % 10 0: model.eval() with torch.no_grad(): val_pred model(torch.tensor(X_val, dtypetorch.float32)).squeeze(-1) val_loss criterion(val_pred, torch.tensor(y_val, dtypetorch.float32)) print(fepoch {epoch}, train loss {loss.item():.4f}, val loss {val_loss.item():.4f})clip_grad_norm_(max_norm1.0)是序列模型训练的保命项。LSTM/GRU 在反向传播时梯度会沿着时间步累积稍不注意就梯度爆炸loss 直接跳到 NaN。加了裁剪之后梯度向量的范数会被限制在 1.0 以内训练过程稳定很多。学习率从1e-3起步如果发现 loss 震荡不降优先把学习率降到3e-4而不是去换模型结构。评估时有个细节容易忽略model.eval()只关掉 Dropout 和 BatchNorm 的训练行为不会自动关梯度计算所以验证阶段一定要包在torch.no_grad()里。对单变量预测任务MSE 作为 loss 是够用的但看指标时RMSE 和 MAE 才有更直观的物理含义下一章展开。4. LSTM 与 GRU 对比实验同一份数据上选出更优模型模型都能跑通之后剩下的问题就是选型。很多项目代码里同时写了 LSTM 和 GRU最后直接用测试集上的数字决定去留。这个想法没问题但对比实验做不严谨结论就是自欺欺人。这一章讲清楚比什么、怎么比、结果怎么看。4.1 门控结构差异与选型直觉先看两者结构上的核心差异这直接决定了选型方向的直觉维度LSTMGRU门控数量遗忘门、输入门、输出门重置门、更新门Cell State有无参数量同 hidden_size约多 1/3较少训练速度较慢较快长序列建模更强够用小数据量过拟合风险更高较低交通流预测的特点决定了 GRU 很少吃亏序列长度通常不超过几十步不存在超长依赖数据量往往只有几千到几万个样本复杂模型的优势发挥不出来反而更容易过拟合训练过程中需要反复调参、跑多组对比训练速度直接决定你一天能跑几次实验。所以我面对一个新的交通流数据集时默认先跑 GRU 拿基线LSTM 作为对照而不是反过来。4.2 固定随机种子与统一评价指标对比实验的第一条铁律是固定随机种子。LSTM/GRU 的初始化和训练过程都带随机性不固定种子两次训练同样的模型都能差出一截这时候任何指标对比都是玄学。import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False set_seed(42)torch.backends.cudnn.deterministic True这行容易被忽略。PyTorch 在 GPU 上跑 LSTM/GRU 时会调 cuDNN 的算法选择不关掉 benchmark哪怕固定了随机种子每次跑出来的结果也可能有细微差异。做单模型调参时无所谓做对比实验时必须关掉。评价指标上交通流预测对比实验至少要同时看 RMSE 和 MAE。RMSE 对峰值误差敏感能反映模型在早晚高峰的表现MAE 反映整体误差水平。MAPE 在流量预测里经常失真因为午夜流量接近 0分母趋近 0MAPE 会被极小值拉爆。如果一定要报 MAPE建议加上最低流量阈值只统计流量大于某个值的时间点。def eval_metrics(y_true, y_pred): mae np.mean(np.abs(y_true - y_pred)) rmse np.sqrt(np.mean((y_true - y_pred) ** 2)) return mae, rmse这里算指标时一定要用反归一化后的真实流量单位而不是在 [0,1] 空间里算。在归一化空间里RMSE 0.05 看起来很小还原成真实流量可能等于 150 veh/h 的误差汇报时容易误导人。反归一化用训练好的scaler.inverse_transform把预测值和真实值一起还原再算。4.3 实验结果怎么读一组典型对比的拆解假设你跑完四组实验整理出下面格式的记录趋势通常长这样模型seq_lenhidden_size验证集 RMSE (veh/h)单 epoch 耗时GRU126452.30.8sLSTM126451.81.1sGRU246449.61.5sLSTM246449.42.0s这种场景很典型LSTM 在每个配置下都比 GRU 低一点点但差距不到 1%而 GRU 的训练时间明显更短。判断时不能只看绝对值还要考虑应用场景。如果预测系统每天要重训一次GRU 省下的时间就是实打实的运维成本如果精度差 1% 直接决定是否满足业务指标那就选 LSTM。更值得注意的是 seq_len 从 12 变成 24 时 RMSE 从 52 降到 49 左右的趋势。这说明这个断面的流量规律需要更长的历史窗口才能捕捉。很多人会把时间花在换模型结构上其实把 seq_len 调大、把输入特征补全收益往往来得更快。反过来如果 seq_len 翻倍后验证集指标没有明显变化说明问题不在窗口长度再加大只会增加训练量。实验记录时我建议固定一个模板模型、seq_len、hidden_size、学习率、训练轮数、验证集 RMSE、测试集 RMSE、单 epoch 耗时。每一次实验跑完就把这八项填进表格对比时一目了然。不要让训练记录散落在终端输出里回头想复现某个结果时到处翻这是最耗时间的隐形坑。5. 交通流预测避坑指南五个常见翻车点与排查手段前面几章把从数据到模型再到对比实验的链路串起来了下面这些坑是我反复在不同项目里见到的问题每一个都值得在动手前先排查一遍。它们不一定让你代码报错但会让你的模型“看起来不错用不起来”。5.1 归一化泄漏测试指标虚高的头号原因现象训练和验证 loss 都很正常测试集上 RMSE 低到惊人但把模型接到实时数据流上预测误差直接翻倍。原因代码里先对整个序列执行了scaler.fit_transform再切分训练、验证、测试集。测试集的均值和最大值在 fit 阶段就进入了模型要学的统计量相当于开卷考试指标自然好看。解决把fit严格限定在训练集上。检查代码里有没有出现scaler.fit(whole_data)这类写法以及有没有在滑窗之前就对全量数据做了标准化。正确的顺序是先按时间切分再 fit 训练集最后 transform 三个集合。5.2 预测曲线滞后真实值一期现象把预测值画出来和真实值叠加曲线形状几乎完全吻合但整体向右平移了一个时间步。RMSE 不离谱图上却一眼假。原因交通流序列相邻时刻相关性极强模型学到的最省事策略就是“复制上一个时刻的值”。尤其当序列没有做差分、数据本身又很平滑时LSTM/GRU 很容易走这条路。如果训练时还把数据集 shuffle 过模型更可能学到未来模式而不是因果关系。解决先检查数据加载器验证集和测试集必须shuffleFalse训练集可以 shuffle 但不能跨越验证区。然后做差分变换用volume[t] - volume[t-1]作为输入序列预测差值再还原。这一步通常能明显削弱滞后。5.3 loss 变成 NaN 或者怎么训都不降现象训练到某个 epoch 后 loss 突然变成nan或者前几个 epoch loss 停留在同一个数值附近不动像被卡住一样。原因nan大多是梯度爆炸或学习率过大。序列模型的反向传播沿时间步累积梯度流量数据又存在早晚高峰的突变点一次大步长更新就可能让权重直接发散。loss 不降则要检查归一化是否做到位、初始学习率是否过高以及标签有没有奇怪的异常值。解决优先检查数据有没有inf或异常大值然后确认归一化区间是 [0,1] 而不是原始数值。把学习率从 1e-3 降到 3e-4并在每个 batch 后加梯度裁剪。如果还是 NaN把batch_size调小到 32 再试。这一套组合基本能解决九成以上的发散问题。5.4 把时间序列当普通样本随机切分现象用train_test_split(X, y, test_size0.2, random_state42)切数据验证集指标非常好但滚动预测时表现稀烂。原因随机切分会把时间上靠后的样本混进训练集。序列经过滑窗后每个样本的上下文跨越了不同时间段模型在训练时见过测试样本附近的上下文等于变相偷看了未来。这不是模型不行是实验设置本身就不成立。解决只按时间顺序切分先定三个区间的边界再做滑窗。验证集必须取训练集之后的数据测试集必须取验证集之后的数据。时间序列交叉验证和普通 K 折完全是两回事不要混用。5.5 数据量不够就堆复杂度现象先上两层 LSTM再挂注意力训练时间翻了几倍验证集 RMSE 反而比单层 LSTM 还高。原因交通流数据的有效规律主要来自周期性模型复杂度超过数据能承载的信息量之后就开始把噪声当特征记下来。深度学习在这个场景里不是越深越强很多人把这个方向做失败不是因为模型不够先进而是基线没跑通就急着加模块。解决把单层 LSTM/GRU 作为强制基线跑通并记录指标后每增加一个模块都单独做一次对比实验。新增模块带来的收益必须超过 5% 才算值得保留否则就删掉。以上五个坑在项目中常常结伴出现。比如数据随机切分和归一化泄漏同时存在时测试集指标可能高得离谱你会误以为是模型效果好直到滚动预测才暴露。建议把避坑检查做成固定的提交流程数据切分顺序、归一化 fit 范围、验证集 shuffle、梯度裁剪、基线模型指标逐项确认后再开下一轮实验。这套流程花不了十分钟但能省掉大量为玄学调参花费的时间。6. 从单步预测到多步预测滚动验证的进阶做法前面的代码都假设horizon1即只预测下一个时刻。实际交通系统更关心的是未来 15 分钟、30 分钟的路况这就要做多步预测。6.1 递归多步与直接多步的取舍最常见的做法是递归预测把模型上一步的输出当作下一步的输入一步一步往后滚。递归预测实现简单但误差会累积。第一步的误差进入第二步输入之后每一步都在放大。直接多步预测则让模型一次输出未来多个值比如预测未来 6 个时刻就把最后的Linear层输出维度从 1 改成 6再同时计算多个目标。这种方式误差不累积但模型要同时拟合多个未来时刻对数据量的要求更高。短时预测未来 3 到 6 步我一般用递归超过 6 步再考虑直接多步。6.2 滚动预测脚本模型能不能用的试金石单步测试集的 RMSE 再低也不能代表模型能上线。我用一个简单脚本做滚动验证模拟真实推理过程def rolling_predict(model, init_seq, steps, scaler): model.eval() seq list(init_seq) preds [] with torch.no_grad(): for _ in range(steps): x torch.tensor(seq[-seq_len:]).float().view(1, seq_len, 1) p model(x).item() preds.append(p) seq.append(p) return scaler.inverse_transform(np.array(preds).reshape(-1, 1)).ravel()seq[-seq_len:]每次都取最近一个窗口预测值p被追加回序列尾部模拟系统每 5 分钟做一次预测的真实流程。滚动 6 步的 RMSE 通常明显高于单步测试 RMSE这是正常现象如果滚动几步后误差快速膨胀到超过流量本身的量级基本可以断定模型没有学到时序结构只是在做短视的复制。我早期做预测实验只看测试集指标滚动验证跑出来的结果和单步差了一大截才意识到前面的对比实验结论站不住。后来把滚动预测固定为每个模型必跑的验收项很多自我感觉良好的模型在这一关被淘汰。这个习惯帮我省下了不少部署后才返工的时间。希望帮到你。本文还有配套的精品资源点击获取