时间序列预测六模型对比:MLP、CNN、LSTM、CNN-LSTM、ConvLSTM2D与SARIMA选型指南

发布时间:2026/10/26 13:43:40
时间序列预测六模型对比:MLP、CNN、LSTM、CNN-LSTM、ConvLSTM2D与SARIMA选型指南 简介这份资源面向深度学习入门与进阶开发者、时间序列预测方向的研究者及需要完成相关课程设计或竞赛任务的学生系统整理了基于神经网络与统计模型的时序预测实现方案。包内共538个文件以342个csv数据集、142个py源码脚本为主辅以txt说明、md文档及少量arff、data等格式文件压缩包约103.22MB涵盖数据预处理、模型定义、训练与评估等完整环节。内容覆盖MLP、CNN、LSTM、CNN-LSTM混合模型、ConvLSTM2D以及SARIMA等多种方法既包含神经网络的非线性与长依赖建模能力也保留经典统计模型对季节性模式的捕捉思路便于横向对比不同模型在时序任务中的表现。项目文件夹提供可本地复现的源码与训练脚本说明文档则给出安装配置与使用指引附赠文档补充模型选择建议与评估指标解读。目前已有89人学习适合希望快速搭建实验基线、理解多模型融合预测流程的读者参考。1. 时间序列预测的六种武器从 MLP 到 ConvLSTM2D 该怎么选手里只有一份带时间戳的数值表老板要你下周给出未来 30 天的走势还问“用深度学习是不是更准”——这是我过去几年反复遇到的场景。标题里这套组合拳本质上就是围绕同一个目标把历史序列映射到未来值区别只在于模型怎么看待时间与特征之间的关系。MLP 把时间窗拍平当普通特征CNN 在时间轴上滑窗提局部模式LSTM 用门控记住长期依赖CNN-LSTM 先卷积提特征再喂给 LSTMConvLSTM2D 则把时间也当成一个空间维度来处理SARIMA 是统计派的季节性基线。选错模型调参调到怀疑人生选对结构同样的数据误差能差一个量级。这篇笔记面向已经会用 Python 处理数据、想把这六种模型跑通并对比的从业者从数据切分讲到每个模型的实现细节和翻车点最后给一套可复现的对比流程。2. 数据切分与窗口构造六种模型共用的一套流水线2.1 为什么时间序列不能随机切分先把最容易翻车的地方说清楚时间序列的 train/test 划分绝对不能train_test_split(shuffleTrue)。随机打乱会让未来信息泄漏到训练集验证集上的 loss 好看得不像话一上真实预测就崩。常见做法是按时间顺序切比如前 70% 训练、中间 15% 验证、最后 15% 测试。如果做多步预测还要在切分点留出预测步长的 gap否则测试集的前几个点仍然和训练集尾部重叠。另一个坑是标准化。均值和方差必须只用训练集计算然后应用到验证和测试集。用全量数据算 scaler 是隐蔽的泄漏尤其在数据量小的时候会让结果虚高。我一般把 scaler 的 fit 和 transform 分开写fit 只在训练集上调用一次。import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler def split_and_scale(series, train_ratio0.7, val_ratio0.15): series: 一维或二维 numpy 数组形状 (T, F) 返回按时间顺序切分并仅用训练集拟合的 scaler n len(series) train_end int(n * train_ratio) val_end int(n * (train_ratio val_ratio)) train series[:train_end] val series[train_end:val_end] test series[val_end:] scaler MinMaxScaler(feature_range(0, 1)) train_scaled scaler.fit_transform(train) # 只在训练集上 fit val_scaled scaler.transform(val) test_scaled scaler.transform(test) return train_scaled, val_scaled, test_scaled, scaler逻辑说明fit_transform只在训练集调用验证和测试集只做transform。参数train_ratio和val_ratio根据总样本量调整样本少于 1000 时建议 80/10/10样本多时可以 70/15/15。feature_range默认 (0,1)如果数据有极端离群值改用StandardScaler更稳。2.2 滑动窗口构造look_back 和 horizon 怎么定所有六种模型都需要把序列转成监督学习格式。核心参数两个look_back回看多少步和horizon预测未来多少步。look_back 太小模型看不到周期性太大参数量和训练时间暴涨还容易过拟合。经验值如果数据有明显周周期look_back 至少覆盖 2 个完整周期日频数据做短期预测look_back 取 30 到 60 比较常见。def make_windows(data, look_back30, horizon1): data: 形状 (T, F) 的缩放后数组 返回 X: (N, look_back, F), y: (N, horizon, F) X, y [], [] for i in range(len(data) - look_back - horizon 1): X.append(data[i : i look_back]) y.append(data[i look_back : i look_back horizon]) return np.array(X), np.array(y) # 单变量示例 train_x, train_y make_windows(train_scaled, look_back30, horizon1) val_x, val_y make_windows(val_scaled, look_back30, horizon1) test_x, test_y make_windows(test_scaled, look_back30, horizon1) print(train_x.shape, train_y.shape) # (N, 30, 1) (N, 1, 1)逻辑说明循环从i到len(data) - look_back - horizon 1保证每个样本的标签都在数据范围内。horizon1是单步预测改成 7 就是预测未来 7 步。注意验证集和测试集的窗口构造要单独做不能把训练集尾部拼过来否则又引入泄漏。提示如果特征是多维的比如同时有温度、湿度、风速make_windows返回的 X 形状是 (N, look_back, F)LSTM 和 CNN 都能直接吃MLP 需要额外 reshape 成 (N, look_back*F)。3. MLP 与 CNN把时间序列当普通特征和局部模式3.1 MLP 做时间序列最简单的基线但别小看它MLP 处理时间序列的思路很直接把 look_back 个时间步的所有特征拍平成一个长向量然后接全连接层。它没有任何时间归纳偏置全靠数据量硬学。优点是实现快、调参少、训练稳定缺点是 look_back 一长输入维度爆炸而且它不知道相邻时间步的关系。import torch import torch.nn as nn class MLPForecaster(nn.Module): def __init__(self, look_back, n_features, horizon, hidden128): super().__init__() self.flatten nn.Flatten() self.net nn.Sequential( nn.Linear(look_back * n_features, hidden), nn.ReLU(), nn.Dropout(0.2), nn.Linear(hidden, hidden // 2), nn.ReLU(), nn.Linear(hidden // 2, horizon * n_features) ) self.horizon horizon self.n_features n_features def forward(self, x): # x: (B, look_back, n_features) x self.flatten(x) out self.net(x) return out.view(-1, self.horizon, self.n_features)逻辑说明Flatten把 (B, look_back, F) 变成 (B, look_back*F)。两层隐藏层加 Dropout 是常见配置hidden取 64 到 256 之间。输出层维度是horizon * n_features再 reshape 回 (B, horizon, F)。参数Dropout(0.2)是正则化数据量小于 5000 时建议保留数据量大可以降到 0.1 或去掉。MLP 的关键参数就三个look_back、hidden、dropout。我一般先用 look_back30、hidden128、dropout0.2 跑一版看验证集 loss 再调。如果验证 loss 远高于训练 loss先加 dropout 或减 hidden如果两个都高说明欠拟合加层或加 hidden。3.2 CNN 提局部模式一维卷积在时间轴上的滑窗CNN 处理时间序列用的是Conv1d卷积核在时间轴上滑动提取局部模式比如连续三天的上升趋势。相比 MLP它参数共享对局部形变更鲁棒而且能通过堆叠卷积层扩大感受野。常见结构是两到三层 Conv1d Pooling最后接全连接输出。class CNNForecaster(nn.Module): def __init__(self, look_back, n_features, horizon, channels(32, 64)): super().__init__() layers [] in_ch n_features for out_ch in channels: layers [ nn.Conv1d(in_ch, out_ch, kernel_size3, padding1), nn.ReLU(), nn.MaxPool1d(2) ] in_ch out_ch self.conv nn.Sequential(*layers) # 计算卷积后的时间维度 reduced_len look_back // (2 ** len(channels)) self.fc nn.Linear(reduced_len * channels[-1], horizon * n_features) self.horizon horizon self.n_features n_features def forward(self, x): # x: (B, look_back, F) - (B, F, look_back) x x.permute(0, 2, 1) x self.conv(x) x x.flatten(1) out self.fc(x) return out.view(-1, self.horizon, self.n_features)逻辑说明Conv1d要求输入形状 (B, C, L)所以先permute把特征维换到通道维。kernel_size3配padding1保持时间长度不变MaxPool1d(2)每次减半。两层池化后时间维度变成 look_back//4如果 look_back30就是 7。channels元组控制每层输出通道数常见 (32, 64) 或 (64, 128)。注意reduced_len必须算对否则全连接层维度对不上会直接报错。CNN 的坑在于kernel_size和padding的配合。kernel_size3 配 padding1 是保持长度的标准做法如果 padding0每层卷积会吃掉两个时间步堆几层后序列就没了。另外池化层不要太多时间序列本身就不长池化两次以上容易丢信息。3.3 MLP 和 CNN 的选型边界什么时候用 MLP数据量小、特征维度高、时间依赖弱、需要快速出基线。什么时候用 CNN序列有明显的局部模式比如突变、短周期波动、数据量中等、想比 MLP 提升但不想上 RNN。如果数据有强长期依赖比如季度周期CNN 的感受野可能不够这时候该上 LSTM 或 CNN-LSTM。注意MLP 和 CNN 的输出层都是全连接horizon 和 n_features 的乘积决定输出维度。多变量预测时如果某些特征不需要预测可以在 loss 里只对目标特征算误差但输出层仍然要保留全部维度否则推理时形状对不上。4. LSTM 与 CNN-LSTM门控记忆和混合结构的落地细节4.1 LSTM 的核心参数hidden_size、num_layers、dropoutLSTM 通过输入门、遗忘门、输出门控制信息流天然适合处理长期依赖。PyTorch 的nn.LSTM关键参数input_size特征数、hidden_size隐藏状态维度、num_layers堆叠层数、dropout层间 dropout、batch_first输入是否 (B, L, F)。做预测时通常取最后一个时间步的 hidden state接全连接输出。class LSTMForecaster(nn.Module): def __init__(self, n_features, horizon, hidden_size64, num_layers2, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizen_features, hidden_sizehidden_size, num_layersnum_layers, dropoutdropout if num_layers 1 else 0, batch_firstTrue ) self.fc nn.Linear(hidden_size, horizon * n_features) self.horizon horizon self.n_features n_features def forward(self, x): # x: (B, look_back, F) out, (h_n, c_n) self.lstm(x) last out[:, -1, :] # 取最后时间步 pred self.fc(last) return pred.view(-1, self.horizon, self.n_features)逻辑说明batch_firstTrue让输入保持 (B, L, F)不用手动 permute。num_layers2时dropout生效单层 LSTM 的 dropout 参数会被忽略。取out[:, -1, :]是标准做法也可以用h_n[-1]两者等价。hidden_size常见 32 到 128num_layers1 到 3 层再多容易过拟合。LSTM 最常翻车的地方是序列长度和 batch 的配合。如果 look_back 很长比如 200LSTM 训练会很慢而且梯度容易出问题。这时候可以先用 CNN 降维再喂 LSTM也就是 CNN-LSTM。4.2 CNN-LSTM 混合先卷积提特征再 LSTM 记时序CNN-LSTM 的结构是Conv1d 层提取局部特征把输出序列喂给 LSTM最后全连接输出。这样 CNN 负责降维和提局部模式LSTM 负责长期依赖两者互补。实现上就是把前面 CNN 的卷积部分和 LSTM 拼起来。class CNNLSTMForecaster(nn.Module): def __init__(self, n_features, horizon, cnn_channels32, hidden_size64): super().__init__() self.conv nn.Sequential( nn.Conv1d(n_features, cnn_channels, kernel_size3, padding1), nn.ReLU(), nn.MaxPool1d(2) ) self.lstm nn.LSTM( input_sizecnn_channels, hidden_sizehidden_size, num_layers1, batch_firstTrue ) self.fc nn.Linear(hidden_size, horizon * n_features) self.horizon horizon self.n_features n_features def forward(self, x): # x: (B, L, F) - (B, F, L) x x.permute(0, 2, 1) x self.conv(x) # (B, C, L//2) x x.permute(0, 2, 1) # (B, L//2, C) out, _ self.lstm(x) last out[:, -1, :] pred self.fc(last) return pred.view(-1, self.horizon, self.n_features)逻辑说明卷积后时间维度减半LSTM 的input_size等于cnn_channels。两次 permute 是为了适配 Conv1d 和 LSTM 的不同输入格式。cnn_channels一般取 32 或 64hidden_size取 64 或 128。这个结构比纯 LSTM 快因为卷积先把序列缩短了。CNN-LSTM 的坑在于卷积层和 LSTM 层的维度衔接。卷积输出是 (B, C, L)LSTM 要 (B, L, C)permute 顺序别搞反。另外如果池化层太多L 太小LSTM 就没东西可学了一般池化一次就够。4.3 训练循环和早停六种模型通用不管哪种模型训练循环的骨架是一样的。关键点用验证集做早停保存验证 loss 最低的权重而不是最后一个 epoch 的权重。def train_model(model, train_loader, val_loader, epochs100, lr1e-3, patience10): optimizer torch.optim.Adam(model.parameters(), lrlr) criterion nn.MSELoss() best_val float(inf) wait 0 best_state None for epoch in range(epochs): model.train() for xb, yb in train_loader: optimizer.zero_grad() loss criterion(model(xb), yb) loss.backward() optimizer.step() model.eval() val_loss 0 with torch.no_grad(): for xb, yb in val_loader: val_loss criterion(model(xb), yb).item() val_loss / len(val_loader) if val_loss best_val: best_val val_loss best_state model.state_dict() wait 0 else: wait 1 if wait patience: break model.load_state_dict(best_state) return model逻辑说明patience10表示验证 loss 连续 10 个 epoch 不降就停。best_state保存最优权重训练结束后加载回来。lr1e-3是 Adam 的常用起点如果 loss 震荡就降到 5e-4如果下降太慢就升到 2e-3。MSELoss适合回归如果数据有离群值可以换HuberLoss。5. ConvLSTM2D 与 SARIMA时空结构和统计基线5.1 ConvLSTM2D 的输入形状把时间当空间维度ConvLSTM2D 原本用于降水临近预报这类时空序列输入是 (B, T, H, W, C)。用在普通时间序列上需要把一维序列 reshape 成二维网格。常见做法是把 look_back 个时间步排成 (H, W) 的矩阵比如 look_back30 可以排成 5x6 或 6x5。这样卷积核同时在时间和特征维度上滑动能捕捉更复杂的局部时空模式。class ConvLSTM2DForecaster(nn.Module): def __init__(self, height, width, n_features, horizon, filters32): super().__init__() self.convlstm nn.ConvLSTM2d if hasattr(nn, ConvLSTM2d) else None # PyTorch 原生没有 ConvLSTM2d需要自定义或用第三方实现 # 这里用 Conv3d 模拟把时间维当深度 self.conv3d nn.Sequential( nn.Conv3d(n_features, filters, kernel_size(3, 3, 3), padding1), nn.ReLU() ) self.pool nn.AdaptiveAvgPool3d((1, 1, 1)) self.fc nn.Linear(filters, horizon * n_features) self.horizon horizon self.n_features n_features def forward(self, x): # x: (B, T, H, W, C) - (B, C, T, H, W) x x.permute(0, 4, 1, 2, 3) x self.conv3d(x) x self.pool(x).flatten(1) pred self.fc(x) return pred.view(-1, self.horizon, self.n_features)逻辑说明PyTorch 没有官方ConvLSTM2d常见做法是用Conv3d把时间维当深度或者用第三方实现。kernel_size(3,3,3)同时在时间、高、宽三个方向卷积。AdaptiveAvgPool3d((1,1,1))把时空维度压成 1再接全连接。这个结构参数量大适合数据量充足至少几万条的场景小数据上容易过拟合。ConvLSTM2D 的坑在于 reshape 方式。把一维序列排成二维网格时相邻时间步在网格上的位置关系会影响卷积效果。常见做法是按行填充比如 30 步排成 5x6第 1 到 6 步在第一行第 7 到 12 步在第二行。这样卷积核在行方向滑动时能捕捉跨周期的模式。如果随便排效果可能不如纯 LSTM。5.2 SARIMA 作为统计基线别跳过这一步SARIMA 是 Seasonal ARIMA参数 (p,d,q)(P,D,Q,s)其中 s 是季节周期。它不需要训练用统计方法拟合适合做基线。如果深度学习模型跑不赢 SARIMA说明数据里的线性成分占主导或者模型没调好。from statsmodels.tsa.statespace.sarimax import SARIMAX def fit_sarima(train_series, order(1,1,1), seasonal_order(1,1,1,7)): train_series: 一维训练序列 order: (p, d, q) 非季节部分 seasonal_order: (P, D, Q, s) 季节部分 model SARIMAX( train_series, orderorder, seasonal_orderseasonal_order, enforce_stationarityFalse, enforce_invertibilityFalse ) result model.fit(dispFalse) return result # 预测未来 30 步 # result fit_sarima(train_scaled.flatten()) # forecast result.forecast(steps30)逻辑说明order(1,1,1)是常见的 ARIMA 起点seasonal_order(1,1,1,7)表示周周期。enforce_stationarityFalse和enforce_invertibilityFalse能加快拟合但可能得到非平稳解。实际用的时候先用 ADF 检验确定 d再看 ACF/PACF 定 p 和 q。SARIMA 的预测是线性的对非线性模式无能为力但作为基线非常可靠。提示SARIMA 的输入必须是一维序列多变量场景要么对每个变量单独拟合要么用 SARIMAX 加外生变量。拟合时间可能较长数据量大时先用 auto_arima 搜参。5.3 六种模型的对比维度跑完六种模型后对比不能只看 MSE。我一般看四个指标MSE、MAE、MAPE、以及预测曲线的形状是否合理。MSE 对大误差敏感MAE 更稳健MAPE 是百分比误差跨数据集可比。另外要看模型在验证集和测试集上的表现是否一致差距大说明过拟合。模型优势劣势适用场景MLP实现快、调参少无时间归纳偏置小数据、快速基线CNN提局部模式、参数共享感受野有限局部突变、短周期LSTM长期依赖、门控记忆训练慢、易过拟合长周期、强依赖CNN-LSTM兼顾局部和长期结构复杂、调参多多尺度模式ConvLSTM2D时空联合建模参数量大、需大数据时空网格数据SARIMA无需训练、可解释只能线性统计基线6. 避坑与排查六个模型跑下来最容易翻车的五件事6.1 验证 loss 不降反升训练 loss 一路降现象训练集 loss 持续下降验证集 loss 先降后升差距越来越大。原因过拟合。解决先加 dropoutLSTM 和 MLP 都加再减 hidden_size 或 num_layers还不行就加 L2 正则weight_decay1e-4。如果数据量确实小考虑用 CNN 替代 LSTM或者用 SARIMA 做基线。6.2 预测值全是常数或接近均值现象模型输出的预测曲线几乎是一条水平线。原因MSE loss 下模型倾向于预测条件均值如果序列本身噪声大、可预测性低最优解就是均值。解决检查 look_back 是否太小增大到覆盖至少两个周期检查特征是否做了标准化如果序列本身接近随机游走任何模型都预测不了这时候该用差分或 SARIMA。6.3 ConvLSTM2D 的 reshape 顺序搞错现象模型能跑通但效果极差或者直接报维度错误。原因一维序列转二维网格时填充顺序和卷积核的滑动方向不匹配。解决固定一种填充方式比如按行在文档里写清楚推理时保持一致。如果 look_back 不能整除用 padding 补齐或在末尾截断。6.4 多变量预测时输出维度对不上现象训练时正常推理时形状报错。原因输出层维度是horizon * n_features但推理时只取了部分特征或者 batch 维度没处理好。解决统一用view(-1, horizon, n_features)推理时保持和训练一样的输入形状。如果只需要预测目标变量在 loss 里做 mask但输出层维度不变。6.5 SARIMA 拟合不收敛或预测发散现象model.fit()报错或预测值跑到无穷大。原因d 或 D 设置不当序列不平稳或者季节周期 s 和数据实际周期不匹配。解决先用 ADF 检验确定 d用季节分解确定 senforce_stationarityFalse和enforce_invertibilityFalse能缓解但不治本数据量太小时改用简单 ARIMA 或移动平均。7. 用滚动预测验证真实泛化能力单次切分的测试集评估有个问题只测了最后一个时间段如果那段恰好有异常结论就偏了。更稳的做法是滚动预测walk-forward validation每次用历史数据训练预测下一步然后把真实值加入历史再预测下一步。这样能模拟真实上线后的使用方式。def walk_forward_eval(model, series, look_back30, horizon1, n_steps100): series: 缩放后的一维或二维数组 返回每一步的预测值和真实值 preds, trues [], [] start len(series) - n_steps - horizon for i in range(start, len(series) - horizon): window series[i - look_back : i] x torch.tensor(window, dtypetorch.float32).unsqueeze(0) model.eval() with torch.no_grad(): pred model(x).squeeze(0).numpy() preds.append(pred) trues.append(series[i : i horizon]) return np.array(preds), np.array(trues)逻辑说明每次取look_back长度的窗口做输入预测horizon步然后窗口向前滑动。n_steps控制评估步数一般取测试集长度的 1 到 2 倍。这个评估方式计算量大但最能反映模型在真实场景下的表现。如果滚动预测的误差远大于单次切分说明模型对时间敏感上线后需要定期重训。我自己的习惯是先用 SARIMA 跑一个基线再用 MLP 和 CNN 快速试如果 CNN 比 MLP 好但还不够就上 LSTM 或 CNN-LSTM。ConvLSTM2D 只在数据量足够且确实有空间结构时才用否则参数量大、训练慢、效果还不一定好。每次调参只改一个变量记录验证 loss别一次改一堆然后不知道哪个起了作用。这套流程跑下来至少能保证你不被“深度学习一定比统计方法好”这种话带偏。希望帮到你。本文还有配套的精品资源点击获取