ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Python的ARIMA-CNN-LSTM组合预测模型实战解析

基于Python的ARIMA-CNN-LSTM组合预测模型实战解析 做预测的人迟早会遇到那么一个时刻你手里有一列时间序列数据跑了一版ARIMA拟合效果不错拿着预测曲线给业务方看对方来一句“最近这段走势好像和以前不太一样模型还能跟上吗”这时候你就知道纯统计模型的边界在哪儿了。反过来如果一上来就上LSTM训练效果也许不错但别人让你解释预测依据你又很难三言两语讲清楚。所以这几年我做时序预测项目越来越倾向组合方案。这个项目——“基于ARIMA-CNN-LSTM预测模型研究Python代码实现”——就是从这种实际需求里长出来的。简单说就是用Python把ARIMA、CNN、LSTM三种模型串成一个完整预测流程ARIMA负责稳住线性趋势和周期成分CNN负责自动抓局部变化模式LSTM负责记住长期依赖最后融合三家的预测结果输出。这篇文章适合两类人一类是刚开始做时序预测、只会单一模型、想看看组合预测怎么落地的学生或工程师另一类是已经跑通单一模型、但想提升预测稳健性、又不确定该怎么融合多模型结果的从业者。我会把整套思路、代码结构、参数细节和踩过的坑都摊开讲你可以直接照着复现。1. 这个项目到底在做什么组合思路与整套设计的底层逻辑先说清楚一个前提组合模型不是炫技是为了补盲区。ARIMA、CNN、LSTM这三类模型各有擅长和不擅长的数据形态单拿出来都能做预测但都做不到“通吃”。ARIMA的长处是数学结构清晰对线性趋势、周期性、平稳序列的刻画非常稳可一到非线性、突变、波动聚集这类形态就捉襟见肘LSTM对非线性特征很敏感理论上能学任意复杂的映射关系但需要足够多的数据而且可解释性差CNN看起来跟序列预测不搭但它的卷积核天然适合提取局部特征在序列窗口内发现“最近几期变化的组合模式”非常有效。把这三种东西拼在一起本质上是在做信息互补。我的理解是ARIMA用统计范式刻画整个序列的主干走向CNN在局部窗口内捕捉短期形态LSTM在更长的时间跨度上记忆依赖关系。三者输出的预测值再做融合等于让三个不同视角的模型投票表决。整个项目的核心不是去PK谁更准而是验证“这样一套组合流程能不能比其中任何一个单一模型都更稳”。整个实现流程可以拆成五个环节数据准备与预处理包括平稳性检验、缺失值处理、标准化、序列切分。ARIMA部分确定差分阶数d定阶p和q拟合模型输出线性预测。CNN-LSTM部分构建滑动窗口数据集搭建CNN卷积层加LSTM循环层的混合网络训练后输出非线性预测。组合预测把两路预测结果按一定权重融合输出最终结果。评估验证在测试集上计算误差指标和单一模型结果做对比判断组合是否有效。这个流程设计有一个关键原则两套模型的输入输出必须对齐。ARIMA是一步一步递推预测CNN-LSTM是基于窗口预测如果不把两边的预测长度和评估基准统一后面的融合就是空谈。我习惯的做法是统一用“滚动预测”的形态来对齐。ARIMA从历史数据拟合未来CNN-LSTM用固定长度的历史窗口预测下一期输出维度都是一列未来值这样组合时就非常直接。再说说适用边界这点很重要。这套组合模型适合中等长度的单变量时间序列比如季度GDP、月度销量、日度电力负荷这类数据量在几百到几千条的区间。它不太适合超高频数据比如毫秒级交易数据或者超长序列几百万条那种场景通常要用更轻量的工程方案。组合模型也要求你对数据形态有基本判断如果数据本身就是白噪声那再怎么组合也只是在拟合噪声。2. 理论拆解ARIMA、CNN、LSTM各自在预测任务里扮演什么角色2.1 ARIMA线性趋势与自回归的统计功底ARIMA全称是自回归积分滑动平均模型核心思想是认为当前值可以由过去若干期的值加上过去若干期预测误差的线性组合来解释。它的全称拆开来看AR部分用历史观察值的滞后项做自回归I部分对非平稳序列做差分让它平稳MA部分用误差项的滞后项修正预测。三个参数p、d、q分别对应自回归阶数、差分阶数、移动平均阶数。选择ARIMA放在组合模型里最大的理由在于它对趋势项的建模能力非常可靠。像GDP、销售额这类带明显上升趋势的数据ARIMA通过一步步差分把趋势消掉再用平稳序列的统计规律外推这种做法即使在样本外也有很强的数学保障。而且预测结果是点估计跟任何深度学习模型融合都方便。但ARIMA的缺点也很直白它假设序列可以用线性关系描述。一旦序列出现结构性突变比如突发的促销冲量、政策影响、异常事件冲击ARIMA的后验误差会骤然拉大预测曲线往往会向回归均值方向收缩跟不上实际波动。2.2 LSTM记忆时序依赖的核心网络LSTM是长短期记忆网络的缩写属于循环神经网络RNN的改进版本。RNN的初衷是让网络在处理当前输入时能利用上一时刻的隐藏状态从而具备“记忆”能力。但普通RNN有个致命伤——当序列较长时梯度在反向传播中会指数级衰减或爆炸导致早期的信息根本传不到后期。LSTM的解决办法是引入门控机制输入门决定当前信息有多少写入记忆单元遗忘门决定之前的记忆保留多少输出门控制记忆单元向外输出的比例。这一套设计让LSTM能在几十甚至几百步的序列中保持稳定梯度长期依赖因此可以被学习到。在预测任务里LSTM擅长捕捉的是非线性时序依赖比如数据中存在的周期性波动、均值回归行为、波动聚集特性等。你给它一个窗口的数据它能学到“上一段的走势和下一段之间有某种复杂关系”这种关系不需要你用公式表达出来网络自己拟合。LSTM的问题在于数据饥渴和训练不稳。窗口大小、层数、神经元数量、学习率这些超参数对结果影响极大调参成本高数据量不足时特别容易过拟合训练损失很低、测试集表现稀烂这在时间序列里尤其隐蔽。2.3 CNN用卷积核捕捉局部趋势模式CNN通常给人印象是处理图像但在这套模型里它干的是一件更轻巧的事一维卷积。你可以把卷积核理解为一个专门扫时间轴的“探测器”它在序列上滑动每个位置做一次局部加权求和。假设窗口长度是30卷积核大小是5那么卷积操作就是在连续5个时间点上做一个线性变换输出的序列就代表了不同位置的局部模式特征。为什么要加这一层因为LSTM虽然能记忆长期依赖但对“最近几期的细微形态”并不敏感它接收的是一个完整的序列向量内部会做加权整合这种整合是全局的。CNN则相反它天然关注局部每个卷积核会学到一种局部形态比如最近三天是否连续上升、是否出现V型反转、是否存在斜率变化。这些局部形态对预测下一期非常关键——很多时间序列在短期内的走势惯性是预测的有效线索。CNN和LSTM配合还有一个工程优势卷积层能够压缩序列长度。比如窗口长度是30经过一个步长合适的卷积层后输出的序列长度变短了但特征更浓缩后面的LSTM就处理序列更短训练更快也不容易记住无用噪声。2.4 三者的分工和衔接关系组合模型里三个部分的角色很像一个分析团队ARIMA是负责趋势判断的统计分析师逻辑严密、重视基础趋势CNN是观察微观动量的分析师盯住近期发生了什么变化LSTM是把握全局节奏的分析师把时间跨度上的依赖关系摸清楚。最后三个人各自把对下一期的判断交出来由你决定怎么加权。在代码实现上这套衔接的核心在于接口设计。我的做法是ARIMA预测结果记为pred_arima它的量纲和原始数据一致CNN-LSTM的预测结果记为pred_nn它的量纲是标准化之后的需要用scaler.inverse_transform还原到原始量纲。之后两者在原始量纲空间上做融合。这个设计细节很多人第一次写组合模型时一定会忽略导致融合结果完全乱掉。3. 环境准备与数据预处理3.1 Python环境与依赖库安装项目开发环境我用的是Python 3.10配合PyTorch 2.x。建议你至少用Python 3.9以上版本避免一些老版本在Windows下编译依赖库时出现莫名其妙的问题。安装依赖库直接一条命令pip install numpy pandas matplotlib statsmodels pmdarima scikit-learn torch其中statsmodels和pmdarima是ARIMA部分的核心pmdarima是statsmodels的封装可以自动搜索最优的p和qscikit-learn提供标准化工具MinMaxScaler和评估指标torch负责搭建CNN-LSTM网络。如果你希望用GPU训练先确认机器有合适的NVIDIA驱动并安装与CUDA版本匹配的torch版本这里不展开。注意statsmodels 的API在0.14.x版本后有调整ARIMA类和SARIMAX类都在statsmodels.tsa.arima.model下。网上很多老教程用的是statsmodels.tsa.arima_model.ARIMA那个模块已经废弃安装新版本后import会报错。3.2 数据的选择与获取为了专注理解模型本身推荐用几类公开数据集而不是一上来就处理脏数据经典示例statsmodels内置的AirPassengers是月度国际航班乘客数非常经典。公开数据源Tushare、AkShare等国内接口可以获取股票、宏观经济数据。模拟数据用正弦波加噪声生成序列方便调试代码。我自己测试时用了太阳黑子数据和一段销量数据做实验。太阳黑子数据有明显的周期性和非线性波动ARIMA单独预测容易滞后CNN-LSTM单独预测容易过拟合组合起来反而更稳。数据获取的代码片段如下演示用AkShare拉取一组日度数据import akshare as ak import pandas as pd # 获取沪深300指数的日线数据仅作为示例 df ak.stock_zh_index_daily(symbolsh000300) df df[close].dropna() df.index pd.to_datetime(df.index) df df.sort_index() # 取最近1000个交易日 series df.tail(1000)如果你是新手第一次跑可以先不接真实数据直接生成模拟序列来验证流程import numpy as np np.random.seed(42) t np.arange(0, 800) series 10 0.05 * t 5 * np.sin(2 * np.pi * t / 50) np.random.normal(0, 1, sizelen(t))这里加入线性趋势、周期和噪声正好能测试三个模型的分工效果。3.3 预处理平稳化、标准化与序列切分的防泄漏设计预处理是整套流程里最容易犯错的环节我这里先讲三个硬性原则。第一个原则是平稳性优先。ARIMA要求序列平稳CNN-LSTM虽然不要求输入平稳但平稳后的序列更容易训练。判断方法用ADF检验p值小于0.05说明序列平稳否则说明需要差分。差分阶数一般取1到2差了太多会丢失长期信息。第二个原则是标准化参数只能用训练集拟合。这个坑极其隐蔽但杀伤力极大。如果你对整个序列做标准化然后再切分训练集和测试集那么测试集的均值方差信息已经通过scaler混进了训练过程这叫信息泄漏会导致测试集误差虚低。正确做法是from sklearn.preprocessing import MinMaxScaler train_size int(len(series) * 0.7) val_size int(len(series) * 0.15) train_data series[:train_size] val_data series[train_size:train_size val_size] test_data series[train_size val_size:] scaler MinMaxScaler() train_scaled scaler.fit_transform(train_data.values.reshape(-1, 1)) val_scaled scaler.transform(val_data.values.reshape(-1, 1)) test_scaled scaler.transform(test_data.values.reshape(-1, 1))注意val和test用的是同一个scaler变换绝不能再调用fit_transform。第三个原则是序列切分必须保证时间顺序不能随机打乱。时间序列的一个核心假设就是“过去决定未来”随机打乱会破坏所有时间依赖关系也会让验证集失去“预测未来”的评估意义。我一般按7:2:1或者7:1.5:1.5切分具体比例看数据总量。4. Python代码实现ARIMA部分的定阶、训练与预测4.1 平稳性检验与差分处理ARIMA第一步是确定差分阶数。先用ADF检验看原始序列是否平稳from statsmodels.tsa.stattools import adfuller def test_stationarity(ts, max_d2): d 0 current ts.copy() while d max_d: p_value adfuller(current)[1] print(fd{d}, ADF p-value{p_value:.4f}) if p_value 0.05: return d current current.diff().dropna() d 1 return d这个函数会从d0开始如果p值大于0.05就继续差分直到序列平稳。注意每次diff后都要dropna删除NaN否则后面ADF检验会报错。4.2 定阶pmdarima自动搜索与自己判断确定d之后剩下的p和q可以通过两种方式确定。我建议新手用pmdarima的auto_arima直接搜索from pmdarima import auto_arima model_arima auto_arima( train_data, start_p0, max_p5, start_q0, max_q5, dd, seasonalFalse, traceTrue, stepwiseTrue, suppress_warningsTrue, ) print(model_arima.order)stepwiseTrue可以显著加快搜索速度。如果你数据量在几千条左右这套搜索几秒就能完成。auto_arima的AIC准则会自动权衡拟合精度和模型复杂度选出的阶数通常比较合理。如果你对细节有追求也可以自己画ACF和PACF图来手动定阶ACF截尾定qPACF截尾定p。但没有统计功底的话automatic搜索是更可靠的选择不推荐人工硬猜。4.3 ARIMA训练与预测定了阶之后用statsmodels的ARIMA建模from statsmodels.tsa.arima.model import ARIMA order model_arima.order # (p, d, q) arima_model ARIMA(train_data, orderorder) arima_fit arima_model.fit() # 预测测试集长度 n_test len(test_data) pred_arima arima_fit.forecast(stepsn_test)这里你需要明白forecast和predict的区别。forecast(stepsn)是从训练集末端直接外推n步使用的是递推方式每一步的预测值作为下一步的输入。predict(start,end)则是在已有时间段内生成拟合值供回测使用不能作为未来预测的替代品。在组合模型里我们要的是forecast的结果。还有一个细节ARIMA虽然内部做了差分和还原预测结果自动在原始量纲上这一点对后续融合极其友好不需要做额外还原。4.4 ARIMA实操中必踩的坑第一个坑是收敛警告。statsmodels拟合ARIMA时偶尔会输出类似“Non-stationary starting autoregressive parameters”的警告这常见于训练数据长度不足或者阶数过高。处理办法是限制p和q的最大值不需要追求AIC最低别超过数据量级的10%。比如长时间序列用500个点训练p和q别超过10。第二个坑是预测后期偏平化。ARIMA在多步预测时预测值的方差会逐渐增大曲线趋向于渐进线这在长周期预测里几乎无法避免。所以ARIMA在组合模型里适合做中短期预测长期预测的权重应该更低。第三个坑是残差检验。ARIMA建模后建议检查残差是否为白噪声这一步能帮你判断模型是否充分提取了信息import statsmodels.api as sm residuals arima_fit.resid sm.stats.durbin_watson(residuals) # 接近2则说明残差没有一阶自相关DW值明显偏离2比如小于1或大于3说明残差中还有结构信息可能是阶数不够也可能是数据里存在ARIMA无法建模的非线性成分。后者正是我们引入CNN-LSTM的意义。5. Python代码实现CNN-LSTM混合网络的关键环节5.1 滑动窗口数据集的构建CNN-LSTM不接收原始序列它接收的是固定长度的历史窗口。构建滑动窗口数据集是这个部分的基础。窗口长度seq_len是超参数直接影响短期记忆的长度。我测试下来窗口长度取预测长度的3到5倍比较稳妥比如预测未来7天窗口就取21到35个点。构建函数如下def create_sequences(data, seq_len30): X, y [], [] for i in range(seq_len, len(data)): X.append(data[i - seq_len:i, 0]) y.append(data[i, 0]) return np.array(X), np.array(y) seq_len 30 X_train, y_train create_sequences(train_scaled, seq_len) X_val, y_val create_sequences(val_scaled, seq_len) X_test, y_test create_sequences(test_scaled, seq_len)注意这里的输入X的形状是(样本数, 30)LSTM要求输入是(batch, seq_len, input_size)所以后面reshape一下即可。还要注意如果用前30个点预测第31个点那么训练集里的窗口和标签之间有重叠这种重叠是正常的因为本质是一个滑动窗口的监督学习。5.2 CNN与LSTM的层间设计这个环节是CNN-LSTM的核心网络结构的设计直接决定组合模型的表现。我用的结构是一维卷积层 LSTM层 全连接输出层。import torch import torch.nn as nn class CNNLSTM(nn.Module): def __init__(self, seq_len30, cnn_filters64, lstm_hidden64, dropout0.2): super().__init__() self.conv1 nn.Conv1d(in_channels1, out_channelscnn_filters, kernel_size5, stride1, padding2) self.conv2 nn.Conv1d(in_channelscnn_filters, out_channelscnn_filters * 2, kernel_size3, stride1, padding1) self.lstm nn.LSTM(input_sizecnn_filters * 2, hidden_sizelstm_hidden, num_layers2, batch_firstTrue, dropoutdropout) self.fc nn.Linear(lstm_hidden, 1) self.dropout nn.Dropout(dropout) def forward(self, x): # x shape: (batch, seq_len, 1) x x.permute(0, 2, 1) # 转为 (batch, 1, seq_len)适配Conv1d x torch.relu(self.conv1(x)) x torch.relu(self.conv2(x)) x x.permute(0, 2, 1) # 转回 (batch, seq_len, cnn_filters*2) out, _ self.lstm(x) out out[:, -1, :] # 只取最后一个时间步的输出 out self.dropout(out) return self.fc(out).squeeze(-1)逐行拆开看Conv1d默认输入是(batch, channels, length)而LSTM默认输入是(batch, length, features)所以中间需要两次permute转换维度。卷积层的padding参数保持序列长度不变第一层卷积核5个点抓局部趋势第二层卷积核3个点再抽象一次。LSTM用两层结构hidden_size取64输出的时候只取最后一个时间步的hidden state这个state已经汇总了整个窗口的信息再接全连接层输出预测值。有人会问为什么不用池化层。我的回答是在时序预测里池化会丢失位置信息。最大池化只保留最强的响应但一个突变点在窗口开头还是结尾对预测下一期的影响完全不同所以这里刻意不用池化让LSTM自己去学习位置依赖。5.3 训练过程与早停机制训练过程相对标准但有三个细节我要强调。一是数据要转成PyTorch的Tensor和DataLoader二是要有早停机制防过拟合三是最小化验证集损失而不是训练集损失。from torch.utils.data import TensorDataset, DataLoader X_train_t torch.tensor(X_train, dtypetorch.float32).unsqueeze(-1) y_train_t torch.tensor(y_train, dtypetorch.float32) X_val_t torch.tensor(X_val, dtypetorch.float32).unsqueeze(-1) y_val_t torch.tensor(y_val, dtypetorch.float32) train_dataset TensorDataset(X_train_t, y_train_t) val_dataset TensorDataset(X_val_t, y_val_t) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) val_loader DataLoader(val_dataset, batch_size64, shuffleFalse)shuffleTrue在时序窗口数据里可以用因为这个shuffle只影响训练批次顺序不会跨样本乱序但有些严格的场景也会选择不shuffle理由是要保持数据在时间顺序上的连续性。我的建议是训练集shuffle能加快收敛且不引入泄漏问题可以用。训练循环model CNNLSTM() optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.MSELoss() best_val_loss float(inf) best_epoch 0 patience 20 for epoch in range(200): model.train() train_loss 0.0 for X_batch, y_batch in train_loader: optimizer.zero_grad() pred model(X_batch) loss criterion(pred, y_batch) loss.backward() optimizer.step() train_loss loss.item() model.eval() val_loss 0.0 with torch.no_grad(): for X_batch, y_batch in val_loader: pred model(X_batch) val_loss criterion(pred, y_batch).item() avg_val_loss val_loss / len(val_loader) if avg_val_loss best_val_loss: best_val_loss avg_val_loss best_epoch epoch torch.save(model.state_dict(), best_model.pth) if epoch - best_epoch patience: print(fEarly stopping at epoch {epoch}) break早停机制的意义在于深模型在时间序列上极容易过拟合训练集loss持续降低验证集loss却开始反弹。patience设20表示连续20个epoch验证集loss没有创新低就停止再加载最优的best_model.pth参数。这才是稳定的产出物。5.4 从训练好的模型得到预测结果测试阶段要注意模型训练时输入是标准化后的数据输出也是标准化后的数据。得到预测后必须还原到原始量纲model.load_state_dict(torch.load(best_model.pth)) model.eval() X_test_t torch.tensor(X_test, dtypetorch.float32).unsqueeze(-1) with torch.no_grad(): y_pred_scaled model(X_test_t).numpy().reshape(-1, 1) y_pred_nn_raw scaler.inverse_transform(y_pred_scaled).flatten()如果你想要多步预测比如一次预测未来n期而不是单期工程上最稳妥的方式是滚动预测先用已知窗口预测第一期然后把第一期预测值接入窗口末尾丢弃最旧的一期再预测第二期重复执行。这种做法的缺点是误差会累积越往后越偏所以实际项目中我倾向于单步预测后拼接输出或者用多条独立预测曲线做集成具体看你的业务容忍度。6. 组合预测权重融合与评估验证6.1 量纲统一与两种融合策略两路模型的预测结果都拿到后第一件事是确认它们都在原始数据的量纲下。ARIMA的输出天然是原始量纲CNN-LSTM经过scaler还原后也是原始量纲这时候两个序列才可以做运算。融合策略三种最常用等权平均y_final 0.5 * y_arima 0.5 * y_nn。简单、稳定、不出错适合刚跑通模型的第一步验证。验证集最优权重在验证集上搜索权重w最小化w * y_arima (1 - w) * y_nn与真实值的误差。w可以用网格搜索跑一遍。元学习器融合把两路预测作为特征真实值作为标签训练一个线性回归或简单MLP来决定组合方式。我强烈建议你从验证集最优权重开始因为元学习器在这种小样本预测中很容易过拟合而且解释性变差。6.2 权重搜索代码实现用验证集搜索权重非常简单best_w 0.5 best_rmse float(inf) for w in np.arange(0.0, 1.01, 0.05): y_ensemble w * pred_arima_val (1 - w) * pred_nn_val rmse np.sqrt(np.mean((y_ensemble - y_true_val) ** 2)) if rmse best_rmse: best_rmse rmse best_w w print(fBest w {best_w:.2f}, RMSE {best_rmse:.4f})这个小循环会告诉你一个事实最优权重可能不在0.5附近。比如ARIMA在短期趋势预测上强w可能是0.7或者CNN-LSTM对波动形态跟得紧w可能只有0.3。用验证集确定权重正是为了让组合模型吸收了两者最强的地方。6.3 评价指标与可视化检查评估组合模型效果我用三个指标RMSE、MAE、MAPE。RMSE对大的离群点更敏感MAE更稳健MAPE能反映百分比误差、便于解释但数据存在接近0的值时MAPE会爆炸需慎用。from sklearn.metrics import mean_squared_error, mean_absolute_error rmse np.sqrt(mean_squared_error(y_test, y_final)) mae mean_absolute_error(y_test, y_final) print(fRMSE: {rmse:.4f}, MAE: {mae:.4f})比数字更重要的是看图。我每次都把三件事画出来真实值vs组合预测值曲线、预测残差分布直方图、残差自相关图。如果组合预测在峰值处仍然明显低估说明某个模型对极端值的把握不够可能需要调整窗口长度或者增大LSTM容量。残差如果还存在强自相关说明模型没有提取干净数据结构此时增加CNN的感受野增大kernel_size往往比增加LSTM层数更有效。6.4 组合模型的效果验证与合理预期组合模型不是魔法。我跑过很多组实验组合模型赢过单一模型大概七成剩下三成要么是数据太干净、单一模型已经足够好要么是验证集最优权重在测试集上失效这种情况多半是发生了分布变化。建议你在报告结果时至少把三个基线的指标同时列出来单一ARIMA、单一CNN-LSTM、组合模型。用一张表格展示模型RMSEMAE说明ARIMA8.216.05直线趋势稳峰值滞后CNN-LSTM7.645.88波动跟踪好偶发抖动组合模型6.875.21两方互补整体最稳用这个方式表达“组合有效”比单纯说“效果不错”要有说服力得多。7. 从零到一跑通项目的经验清单与典型坑7.1 时间线与里程碑建议如果是第一次写这套组合模型我建议按三个里程碑推进而不是一口气全写完。第一个里程碑只跑通ARIMA部分。把数据准备、定阶、拟合、预测、还原成图全流程走到确保统计模型这一半是可靠的。这个阶段大约需要半天到一天。第二个里程碑只跑通CNN-LSTM部分。重点是构建窗口数据集、定义网络结构、训练、早停、还原。这个阶段大约需要一两天因为超参数需要初步调试。第三个里程碑融合与评估。把两路预测拿来做权重搜索、指标计算和可视化再对比基线模型。这个阶段一天足够。按这个顺序做出错时能快速定位是在统计部分、深度学习部分还是融合部分避免问题混在一起难排查。7.2 高频踩坑与排查技巧下面这些坑是我实际跑代码时反复踩过的按出现频率排序。第一标准化泄漏导致指标虚高。你发现CNN-LSTM在测试集上表现神勇仔细一查原来scaler是在整体序列上拟合的。排查方法是训练时故意改写数据生成方式比如将测试集的真实值全部加一个常数如果模型预测也跟着变了说明泄漏存在。第二预测结果出现严重相位偏移。真实值的波峰比预测值的波峰晚出现几个时间点通常是窗口长度太短或者是只用了LSTM最后一个时间步输出导致短视。扩大seq_len到预测长度的5到7倍或者增加CNN卷积核尺寸相位偏移通常会减轻。第三ARIMA与CNN-LSTM预测结果的尺度不对齐。CNN-LSTM预测还在0到1之间ARIMA已经在几百的量级。回到第5.4节检查是否在inverse_transform之前把预测结果reshape错了。scaler.inverse_transform要求输入列数跟fit时一致如果fit时是1000个点、1列输入也必须是单列很多人在这里维度报错。第四LSTM训练损失不降。常见原因是学习率太大或者数据量级太小。先检查输入数据是否被标准化到0到1再把学习率从1e-3降到1e-4试一次如果还不降把LSTM层数从2降到1减小初始模型复杂度。时间序列任务里多一层LSTM带来的提升远没有数据质量提升来得实在。第五验证集权重高但测试集效果差。这是分布变化的信号。解决办法不是调权重而是要缩短测试区间或者使用滚动时间窗口的验证策略让验证集更接近测试集的时间段。具体做法是训练集用最近N个周期的数据验证集用紧接着的下一个周期这样才符合业务上的“最近规律更可信”假设。7.3 可复现性设置研究性质的项目可复现性是底线。在文件开头设置随机种子import torch import numpy as np def set_seed(seed42): np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True set_seed(42)注意即便设了随机种子GPU上的训练结果仍可能有细微波动因为你没有固定cuDNN的算法选择。torch.backends.cudnn.deterministic True能进一步保证但会牺牲少量训练速度。如果你需要严格复现数值建议直接在CPU上跑最终验证速度慢一点但结果完全确定。最后分享一个我在实际项目中反复体会到的点组合模型的权重不应该是一次定死的东西。随着数据形态变化ARIMA和CNN-LSTM的贡献比例会变化最优w可能从0.7漂移到0.3。把这套权重搜索的逻辑包装成一个函数固定每个周期重新计算一次你会意外收获一个对环境变化适应度更高的预测系统。这个函数本身不复杂就是第6.2节那个循环值钱的是“定期重估”这个意识。做预测的人永远不会只靠一次调参吃一辈子。
返回列表