ARIMA+CNN+LSTM混合模型:时间序列预测完整实战指南
1. 先把事情说清楚为什么要把ARIMA、CNN、LSTM凑到一块做时序预测的人大概率经历过这样的纠结刚入门时翻了一堆教程发现十个有八个在讲LSTM好像只要是个序列数据就能塞进神经网络跑一波等真正上手项目了又听说ARIMA这种传统统计模型在平滑数据上其实挺能打转头再看看CNN心里更嘀咕了——那不是搞图像识别的吗跟时间序列有什么关系这个标题把ARIMA、CNN、LSTM三个模型放进了同一个预测框架里乍一看像是各路技术大杂烩实际上是一个被反复验证过的思路传统统计模型和深度学习模型并不互斥它们擅长的事情不一样。ARIMA对线性的、平稳的趋势捕捉能力很强LSTM对长距离依赖和非线性模式很敏感CNN则擅长从局部窗口里提取特征。把三者组合起来本质上是让不同的模型各干各的活最后把各自的优势汇到一起。另一个促使我把它写成博文的原因是这类课题几乎属于学术项目中的经典款。在不少论文、毕业设计或者竞赛方案里ARIMA-CNN-LSTM这种组合都是被反复研究的热点对象。你翻开任何一个论文检索平台都能看到类似题目。但普遍的痛点是网上讨论零散要么只讲ARIMA不讲另两个要么只给一段LSTM代码草草了事真正把三者怎么结合、代码怎么落地的完整链路比较少。这篇博文会完整覆盖混合模型的整体设计思路、ARIMA的差分与定阶细节、CNN怎么处理一维时间序列、LSTM的训练要点、三者融合的两种主流方式以及一套可复现的Python代码。适合的人群很明确正在做时序预测相关课题的研究生、需要写预测模块的Python开发工程师还有对深度学习入门不久、想看看几个模型怎么协同工作的朋友。我用的Python版本是3.9以上PyTorch 2.0核心依赖是pandas、numpy、statsmodels、scikit-learn和matplotlib。整个项目在单卡CPU上就能跑起来不依赖高性能GPU。2. 模型的角色分配一台流水线上的三道工序2.1 ARIMA负责的是线性趋势不是全部很多人对ARIMA有误解觉得它是个过时模型。但在混合预测架构里ARIMA承担的任务很清晰先把时间序列里的线性趋势和周期性成分剥离出来让后续的神经网络去处理“剩下的”那些非线性残差。ARIMA的全称是自回归积分滑动平均模型三个字母分别对应AR自回归项用历史观测值预测当前值数学形式上就是当前值等于前面若干期值的线性组合I差分阶数让原本不平稳的序列变成平稳序列通常是做一阶或二阶差分MA滑动平均项当前值等于过去若干期预测误差的线性组合实操中要处理的核心问题只有一个p、d、q三个参数怎么定。这里面d比较直接看数据平稳性ADF检验的p值大于0.05就继续差分直到平稳为止。p和q可以选择看ACF和PACF图来人工判断也可以通过AIC或BIC准则做网格搜索自动选优。我自己在实际项目中不太喜欢纯看图定阶因为ACF和PACF的拖尾截尾判断对新手不友好而且不同人看同一张图结论可能不一样。所以我通常的做法是对p和q在0到5的范围内做遍历取AIC最小值对应的组合。虽然计算量大一点但结果稳定很多。ARIMA训练完之后对训练集做拟合、对测试集做预测这部分产生的预测值作为后续模型的一个输入特征同时残差序列真实值减预测值也单独保存下来。这是整个框架的关键一步。2.2 CNN在一维时间序列里的角色不是“识别”是“提炼”CNN在图像领域的成功太深入人心以至于很多人忽略了一个事实一维卷积天然适合处理序列数据。在混合预测模型里CNN的作用是对输入的时间窗口做局部特征提取捕捉相邻时间点之间的短期模式。一维卷积层的运作方式其实和你想象中一样一个卷积核在序列上滑动每次覆盖一个长度为kernel_size的窗口做加权求和然后步进到下一个窗口。这个操作本质上是把局部上下文压缩成一个更高阶的抽象特征。比如输入是一个60步的时间窗口经过Conv1D池化之后可能变成20步的特征序列但每个特征值都“看见了”原始数据里更广的邻域信息。在ARIMA-CNN-LSTM结构中我把CNN放在LSTM前面而不是反过来。原因在于LSTM擅长按顺序读取信息但如果输入的每个时间步都带有较长的局部噪声LSTM很难判断哪些局部变化是真正的信号先让CNN把局部窗口压缩成更紧凑的特征表示LSTM接收到的是“提纯过”的序列学习负担会小很多。这就像你给另一个人转述一段话与其原封不动地把每个语气词都传过去不如先自己提炼一下再讲效果会更好。2.3 LSTM是流水线的最后一道负责捕捉长期依赖LSTM的核心优势在于门控机制。它有三种门遗忘门决定过去的信息保留多少输入门决定当前信息写入多少输出门决定最终对外输出什么。这样的设计使得梯度可以在长序列中流动得更顺畅基本解决了普通RNN在长序列上梯度消失的毛病。在时序预测这个场景LSTM具体要做的就是读入CNN提取后的特征序列逐时间步更新隐藏状态和记忆单元最后把最后一个时间步的隐藏状态经过一个全连接层映射成预测值。LSTM有两个关键参数要在实践里反复调试隐藏层神经元数和层数。隐藏层太小模型容量不够学不到复杂模式太大则容易过拟合尤其是训练数据量有限时会出现训练集误差很低、测试集误差飙升的情况。层数方面二到三层是比较常见的配置再往上堆收益通常不大但训练耗时增长明显。3. 数据准备与ARIMA定阶一起来搭地基3.1 数据从哪里来长什么样为了把代码讲得更清楚我用一个公开可得的示例数据做演示Air Passengers数据集记录的是1949到1960年间每月国际航班旅客数共144个观测点。这是个经典的时序数据集能在statsmodels或seaborn里直接加载。当然实际项目里数据不会这么规整。真实业务数据往往存在缺失值、异常值、量纲差异等问题。预处理阶段至少要完成四件事缺失值处理如果缺失比例低可用前向填充或线性插值如果连续缺失较多谨慎对待可能需要业务规则修补异常值识别用移动平均和标准差做简单探测比如超过均值三倍标准差的值替换成邻近窗口均值避免异常点干扰模型训练数据划分按时间顺序划分不能用随机打乱。时序数据一旦顺序乱了信息泄露的问题立刻出现。通常的做法是用前70%-80%做训练后20%-30%做测试归一化LSTM对输入尺度敏感归一化是必须的。这里我用MinMaxScaler把数据缩放到[0,1]区间训练完再反归一化还原3.2 ARIMA定阶的实操流程加载数据后先做ADF平稳性检验。air passengers数据有明显上升趋势和周期波动因此大概率需要至少一阶差分才能平稳。我给个直观的检验逻辑from statsmodels.tsa.stattools import adfuller import pandas as pd def check_stationarity(series): result adfuller(series) print(fADF Statistic: {result[0]:.4f}) print(fp-value: {result[1]:.4f}) if result[1] 0.05: print(数据平稳不需要进一步差分) else: print(数据不平稳需要差分)通常跑完这个检验第一次p值远大于0.05差分一阶之后再看就变平稳了。因此d定为1。但要补一句的是d不一定要取到完全平稳。有时候过度差分反而会让序列丢失重要信息变得“过白噪声化”预测时误差反而变大。p和q的选择我用AIC网格搜索import warnings from statsmodels.tsa.arima.model import ARIMA warnings.filterwarnings(ignore) best_aic float(inf) best_order None best_model None for p in range(0, 6): for q in range(0, 6): try: model ARIMA(train_data, order(p, 1, q)) fitted model.fit() if fitted.aic best_aic: best_aic fitted.aic best_order (p, 1, q) best_model fitted except Exception as e: continue print(f最优阶数: {best_order}, AIC: {best_aic:.2f})这一步结束后我们把训练好的ARIMA模型保存下来后面还会用到。3.3 计算ARIMA预测值和残差为融合做准备在混合预测模型里ARIMA本身要做两个产出拟合值和预测值以及残差序列。残差序列的含义是原始数据中ARIMA无法解释的部分。这部分数据将交给深度学习模型去继续建模。这里有一个很多新手容易踩坑的点ARIMA的残差不是直接用训练集的ground truth减去训练集的拟合值还需要考虑预测阶段的表现。正确做法是用训练好的ARIMA模型对训练集末尾及测试集进行滚动预测或直接预测然后再计算对应的残差。实际代码中我倾向于这样处理# 对训练集做拟合 in_sample_pred best_model.predict(start0, endlen(train_data)-1) in_sample_residual train_data - in_sample_pred # 对测试集做预测 if len(test_data) 0: forecast best_model.forecast(stepslen(test_data)) forecast_residual test_data - forecast因为后续的深度学习模型要把ARIMA的预测值作为特征输入这里需要注意索引对齐。如果训练集长度为100测试集长度为44那么测试集的特征矩阵里ARIMA预测值那一列对应的是forecast的44个值。4. 融合方式与整体架构两种主流方案实测对比4.1 方案一残差预测路线这是最简单的一种融合方式。ARIMA做完预测后深度学习模型只负责预测ARIMA的残差。最终预测值等于ARIMA预测值加上深度学习预测的残差值。残差预测路线的优点是逻辑清晰训练目标明确代码也好写。缺点是如果ARIMA拟合能力很强残差接近纯白噪声深度学习模型能从中学到的就非常有限。我为它补充的改进思路是深度学习模型虽然目标是拟合残差但输入特征里除了历史残差窗口还把历史真实值窗口加进去。这样模型既能看到残差模式又能参照原始数据的水平信息。输入特征矩阵形状是滑动窗口加多特征列输出是一个残差值。4.2 方案二特征融合路线相对残差预测特征融合路线更“主流”一点。具体做法是ARIMA预测值不只是用作参照而是作为一个外生特征直接和CNN-LSTM的输入合并。也就是说深度学习模型的输入包含两部分历史真实值的滑窗序列、ARIMA对同一窗口的预测值序列。这个方案的逻辑导向是ARIMA的预测值承载着统计模型对整体趋势的判断如果把这种判断作为额外的特征通道交给神经网络网络就能在训练中学会什么时候信任ARIMA的判断什么时候调整它。这个机制比单一依赖残差更有解释力。实际结构中是这样的输入张量形状(batch_size, seq_len, 2) 其中两个通道分别是历史真实值和ARIMA预测值经过一维CNN层提取局部特征经过LSTM层进行时序建模经过全连接层输出预测值损失函数MSE4.3 两种方案的实测感受我在示例数据上分别跑了两个方案。先说残差预测路线整体收敛速度快测试集RMSE大概在42左右但它过度依赖ARIMA的底层拟合能力。如果ARIMA对测试集的预测偏差较大残差也大深度学习模型要弥补的坑就深效果容易不稳定。特征融合路线的表现要好一些。因为深度学习模型同时看到了原始数据和ARIMA的判断它能学到的映射关系更丰富。同样是这批数据RMSE降到了32左右。代价是训练时间变长了一点但对小规模数据集来说这个成本完全可以接受。所以这篇博文最终给出的主版本代码是特征融合路线。我自己在项目里的习惯也是优先用特征融合因为它留出了“如果ARIMA效果太差可以随时去掉ARIMA特征列”的退路模型复盘和消融实验都方便。5. 从零搭建CNN-LSTM模型详细代码与逐行讲解5.1 构建滑窗数据集滑窗是时序预测里被反复提起的概念理解它只需要一个类比你拿一个放大镜在时间轴上移动每次看到的是一小段历史试图猜出紧接着的下一个值。这个“放大镜窗口”的长度就是seq_len。代码里我先把训练集和测试集各自做成监督学习格式import numpy as np import torch from torch.utils.data import TensorDataset, DataLoader def create_sequences(data, arima_forecast, seq_len12): 构造滑窗序列数据 data: 原始序列归一化后 arima_forecast: ARIMA预测值序列归一化后 seq_len: 窗口大小默认12个时间步 X, Y [], [] for i in range(len(data) - seq_len): seq_data data[i:iseq_len] seq_arima arima_forecast[i:iseq_len] # 每个样本的形状是 (seq_len, 2) X.append(np.column_stack((seq_data, seq_arima))) Y.append(data[iseq_len]) return np.array(X), np.array(Y) seq_len 12 X_train, y_train create_sequences(train_values, train_arima_values, seq_len) X_test, y_test create_sequences(test_values, test_arima_values, seq_len)这里最重要的细节是arima_forecast在训练阶段必须是对训练集的拟合值或者滚动预测值不能拿测试集的预测值来构造训练集的样本。否则就是典型的信息泄露测试时模型会取巧短期指标好看上线就现原形。5.2 模型定义Conv1D加LSTM加全连接模型结构设计上我没有做得特别花哨原因很简单数据规模不大结构太复杂必然过拟合。一个卷积层加一个LSTM层加一个全连接输出层在绝大多数中小规模时序数据集上是性价比很高的组合。import torch.nn as nn class ARIMA_CNN_LSTM(nn.Module): def __init__(self, seq_len12, n_features2, hidden_size32, num_layers1, kernel_size3): super().__init__() self.conv1 nn.Conv1d(in_channelsn_features, out_channels16, kernel_sizekernel_size, padding1) self.relu nn.ReLU() self.pool nn.MaxPool1d(kernel_size2, stride2) # 经过conv和pool之后的序列长度需要重新计算 # seq_len12, pool后变成 6 self.lstm nn.LSTM(input_size16, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue) self.fc nn.Linear(hidden_size, 1) def forward(self, x): # x: (batch_size, seq_len, n_features) x x.permute(0, 2, 1) # Conv1d期望(batch, channels, seq_len) x self.conv1(x) x self.relu(x) x self.pool(x) # 转回LSTM期望的(batch, seq_len, features) x x.permute(0, 2, 1) lstm_out, _ self.lstm(x) # 取最后一个时间步的隐藏状态 last lstm_out[:, -1, :] out self.fc(last) return out.squeeze(-1)有个细节需要提一下Conv1D的输入形状是(batch, channels, seq_len)LSTM的输入形状是(batch, seq_len, features)两者不一致所以中间要做两次permute转置。很多教程直接照搬图像分类的代码在这个地方报错半天不知道原因。MaxPool1d的kernel_size2会把序列长度减半。我用的seq_len12为了对齐正好让12变成6。如果你改成seq_len30之类的非偶数pool之后长度会变成15那也没问题LSTM对序列长度没有固定要求不一定要凑整除。5.3 训练循环与损失函数训练代码我写成了一个通用函数方便换数据集时直接复用。关键点是LSTM类的模型在训练时一定要设置model.train()测试时设置model.eval()。这两个模式影响的不仅是BatchNorm和Dropout这些层在LSTM里还关系到是否保留梯度和记忆状态。import torch.optim as optim from sklearn.preprocessing import MinMaxScaler from sklearn.metrics import mean_squared_error, mean_absolute_error # 数据归一化 scaler MinMaxScaler() train_scaled scaler.fit_transform(train_values.reshape(-1, 1)).flatten() test_scaled scaler.transform(test_values.reshape(-1, 1)).flatten() # ARIMA预测值也需要用同一scaler做归一化 train_arima_scaled scaler.transform(train_arima_values.reshape(-1, 1)).flatten() test_arima_scaled scaler.transform(test_arima_values.reshape(-1, 1)).flatten() X_train, y_train create_sequences(train_scaled, train_arima_scaled, seq_len) X_test, y_test create_sequences(test_scaled, test_arima_scaled, seq_len) # 转成PyTorch张量 X_train_t torch.tensor(X_train, dtypetorch.float32) y_train_t torch.tensor(y_train, dtypetorch.float32) X_test_t torch.tensor(X_test, dtypetorch.float32) y_test_t torch.tensor(y_test, dtypetorch.float32) train_dataset TensorDataset(X_train_t, y_train_t) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue)训练循环的核心方法不采用一次性全量喂入而是使用DataLoader分批加载。shuffleTrue在训练时打乱样本顺序能提高模型的泛化能力这个和“时序数据不能打乱”并不冲突——那是在划分训练集和测试集时不能打乱整体顺序至于已经构造好的一个个滑窗样本被用来训练打乱它们之间的顺序是惯例做法。注意测试集DataLoader的shuffle必须设为False。否则你预测了半天结果顺序还是乱的绘图和误差计算都会出问题。model ARIMA_CNN_LSTM(seq_lenseq_len, n_features2, hidden_size32, num_layers1) criterion nn.MSELoss() optimizer optim.Adam(model.parameters(), lr0.001) epochs 100 for epoch in range(epochs): model.train() total_loss 0.0 for batch_x, batch_y in train_loader: optimizer.zero_grad() pred model(batch_x) loss criterion(pred, batch_y) loss.backward() optimizer.step() total_loss loss.item() * batch_x.size(0) avg_train_loss total_loss / len(train_loader.dataset) if (epoch 1) % 20 0: print(fEpoch {epoch1:3d}, Train Loss: {avg_train_loss:.6f})5.4 预测、反归一化与误差评估模型训练好之后对测试集做预测注意先跑model.eval()并且要在torch.no_grad()环境下这样可以省内存、加速推理。预测出来的值是在[0,1]区间上的归一化结果必须用scaler.inverse_transform还原成原始数据量纲否则误差指标根本没法读。model.eval() with torch.no_grad(): y_pred_scaled model(X_test_t).numpy() y_pred scaler.inverse_transform(y_pred_scaled.reshape(-1, 1)).flatten() y_true test_values[seq_len:] rmse np.sqrt(mean_squared_error(y_true, y_pred)) mae mean_absolute_error(y_true, y_pred) mape np.mean(np.abs((y_true - y_pred) / y_true)) * 100 print(fRMSE: {rmse:.2f}) print(fMAE: {mae:.2f}) print(fMAPE: {mape:.2f}%)提示inverse_transform接收的shape必须是二维按列还原。很多人在这一步写成(y_pred,)直接报错也是常规问题了。6. 完整训练链路一个可以直接跑的流程脚本为了避免各个片段拼不起来我把整个流程串成一个可执行脚本。数据文件放在data/文件夹下一列value列加一列date列即可。如果你用的是Air Passengers直接从seaborn加载后另存成csv就行。import numpy as np import pandas as pd import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset from sklearn.preprocessing import MinMaxScaler from sklearn.metrics import mean_squared_error, mean_absolute_error from statsmodels.tsa.arima.model import ARIMA from statsmodels.tsa.stattools import adfuller import warnings warnings.filterwarnings(ignore) # ---------- 1. 读取与预处理 ---------- df pd.read_csv(air_passengers.csv) series df[value].values.astype(float) # 缺失值线性插补 if df[value].isnull().any(): df[value] df[value].interpolate() # 划分训练测试集70%/30% split_idx int(len(series) * 0.7) train_values series[:split_idx] test_values series[split_idx:] # ---------- 2. ARIMA部分 ---------- result adfuller(train_values) # 差分阶数d根据ADF检验确定 if result[1] 0.05: d 1 # 差分一次后再检验 diff_once np.diff(train_values) result_diff adfuller(diff_once) if result_diff[1] 0.05: d 2 else: d 0 best_aic, best_order float(inf), None for p in range(0, 5): for q in range(0, 5): try: tmp_model ARIMA(train_values, order(p, d, q)).fit() if tmp_model.aic best_aic: best_aic tmp_model.aic best_order (p, d, q) except Exception: continue arima_model ARIMA(train_values, orderbest_order).fit() # 生成训练阶段拟合值和测试阶段预测值 train_arima_fit arima_model.predict(start0, endlen(train_values) - 1) forecast_steps len(test_values) test_arima_forecast arima_model.forecast(stepsforecast_steps) # 注意长度对齐test值要补齐ARIMA预测 train_arima_values np.concatenate([train_arima_fit, test_arima_forecast])我在这里多提一句ARIMA定阶过程中遇到过的一个典型bugstatsmodels的predict和forecast在返回类型上不太一样predict返回Series带索引forecast也返回Series但索引不同。如果你直接把两个结果concat在一起而不重置索引长度明明是对的可后续按位置取值时会因为索引不对齐而出错。保险起见取值时一律用.values先转为ndarray。模型训练和预测部分的代码直接复用前面第5节的函数定义即可。完整脚本跑通之后你得到的最终结果应该包括三张图真实值vs ARIMA预测值、真实值vs混合模型预测值、残差分布图。三张图足以支撑你对模型的判断。7. 训练结果怎么看、怎么判断模型好坏有些朋友跑完代码看到RMSE数值就完事了但对预测任务来说光看总误差不够还得看误差的分布形态。我一般会画三张图。第一张是拟合对比图。纵轴是真实值横轴是预测值如果三个模型ARIMA、纯LSTM、本文的混合模型的点都贴着对角线说明预测偏准。如果混合模型的散点比纯LSTM更集中说明引入ARIMA特征确实帮了忙。第二张是误差随时间变化的折线图。如果误差在特定时间段突然变大通常意味着这段时间存在训练集里没有覆盖的新模式比如某个月份出现了突发峰值。这是模型无法凭空学会的你要么增加历史数据的跨度要么在特征中加入周期性编码。第三张是预测残差的自相关图。理想情况下残差应该近似白噪声即各阶自相关都落在置信区间内。如果残差在lag1或lag12的位置出现明显超出置信区间的柱子说明模型没有完全捕捉到时序结构。这时你可以考虑增大CNN的kernel_size或者增加LSTM层数。拿Air Passengers数据集来说混合模型对比纯LSTM的提升主要体现在季节性拐点附近。LSTM单独训练时容易把拐点“磨平”而ARIMA的差分项对季节趋势的描述更精确等于提前给神经网络提示了这个时间点大概率要转折。8. 常见问题与排查技巧实录8.1 ARIMA预测值全是常数或者一直不变这个问题出现的频率相当高。ARIMA训练出来的模型如果预测结果在几步之后变成一条水平直线通常意味着模型退化成简单的均值回归。排查优先级差分阶数是否过大。d2时模型对趋势的假设已经非常刚性新数据一旦偏离既有趋势预测值很快收敛数据是否存在强季节性。ARIMA模型的季节性版本是SARIMA它才有季节分量普通ARIMA对周期成分无能为力。如果数据有明显的年度周期而你不做季节差分预测结果基本不可用8.2 训练损失正常下降但测试集RMSE异常大这个现象多半是过拟合而不是模型代码写错了。排查方向训练样本太少。Air Passengers只有144条数据滑窗后样本数量也就一百出头。这时可以适当缩小模型减少LSTM隐藏层神经元数或者干脆把LSTM的num_layers降到1特征列数要保守。有些朋友在特征融合路线里把移动平均、历史差值、一周前同值全都塞进去特征维度多了几倍模型参数量随之增加。中小数据集上这种做法得不偿失观察训练集和测试集的RMSE比值。比值超过2基本是过拟合需要加早停early stopping或者调低epoch数8.3 LSTM输入形状报错expected shape got another shape这是新手最高频的报错。核心记住两个形状约定Conv1D要求(batch_size, channels, seq_len)LSTM要求(batch_size, seq_len, features)如果报错信息里出现“Expected 3D input, got 2D”说明你的数据在某个环节少了一个维度。排查方法是打印一下每一步的shape对照上面的约定逐层检查大概率是池化之后忘记做permute。8.4 测试集误差很大但训练集很好而且用了同样的归一化这种情况要怀疑数据泄露。比如ARIMA的训练用了全部数据拟合才会导致训练误差段很好看测试集直接拉胯。ARIMA之所以要严格只用训练段数据就是怕模型已经“见过”测试段的信息。同理MinMaxScaler必须在训练段上fit在测试段上只transform。如果整体fit之后再做划分测试段的统计信息就从“未来”泄漏到了“过去”。严谨的复现流程里这是必须避免的。9. 这套模型还能往哪些方向扩展主题项目走到这里基本闭环了。但如果是毕业论文想加创新点或者工作中要复现一套更强的基础方案我觉得可以沿着三个方向做扩展。第一在ARIMA部分换成SARIMA或Prophet。Air Passengers这类数据季节性强SARIMA明显更合适。如果数据是日粒度周周期和年周期同时存在Prophet对多周期分解的支持更省心。这样一来深度学习模型的输入特征里外生信息就从“统计模型的判断”变成了“更精细的分解结果”。第二CNN部分加注意力机制。比如在LSTM输出之后接一个多头自注意力层让模型对不同时间步的隐藏状态做加权融合而不是只取最后一个时间步。这个小改动对长序列效果改善明显实现成本也不高。第三把固定滑窗改成多尺度滑窗。输入同时包含短期窗口比如最近7天和中期窗口比如最近30天分别走不同卷积核的CNN再拼接特征送入LSTM。这是时序预测领域常用且有效的多尺度思想适合作为论文的创新实验章节。10. 最后再分享一点个人实际操作中的体会我自己在多个时序预测项目里跑过这个框架最大的感受是不要指望混合模型是万能药。混合模型的收益取决于各子模型之间的能力互补程度。如果ARIMA本身已经拟合得非常好残差几乎是白噪声那么后接深度模型的提升空间就很有限甚至可能因为多余参数带来负优化。如果ARIMA效果极差深度模型又要多花很大力气去纠正它的错误。只有在ARIMA能把大趋势说个八九不离十、但细节和拐点说不准的情况下这个组合的收益才最大。所以拿到任何数据集先跑一个ARIMA基线再跑一个纯LSTM最后再看混合模型是否带来显著提升。做这个实验并不费事对比的结论还能写进论文的消融实验部分一举两得。另外一个小建议在代码里把随机种子固定到训练循环之前。PyTorch模型的初始化存在随机性如果不固定种子每次运行结果会有微小波动比自己预期的差异还要大。实验对比时固定种子能保证你的对照组和改进组之间的差异主要来自模型结构而不是运气。def set_seed(seed42): np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) set_seed(42)动手跑一遍把三个模型的预测曲线画在同一张图上。肉眼看清楚差距之后你对这套框架的理解会比只看代码和公式扎实得多。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →