资讯动态

四合一时间序列预测:ARIMA+LSTM+Transformer+门控融合

发布时间:2026/9/7 18:51:35 来源:尧图企业网站定制
最近在做一套工业设备负荷预测时我又一次被单模型的偏科打醒同一组数据LSTM训练时拟合得很漂亮一到节假日就明显跑偏ARIMA在平稳时段稳定得让人放心碰到突发波动基本失灵Transformer长时间序列记忆能力确实强可冷启动阶段收敛慢到让人怀疑人生。这种经历让我越发确定一件事——与其反复纠结时间序列预测到底该用哪个模型不如用一套统一的框架把互补的方法都收编进来。这篇文章要分享的就是一个四合一时间序列预测模型的完整实战方案三个异构基学习器ARIMA、LSTM、Transformer再加一个门控融合层组合成一套端到端的预测流程。内容会覆盖架构设计、数据准备、模型实现、融合策略、实验对比和调参坑点适合正在做预测项目、想从单模型切到融合方案的算法工程师和数据科学爱好者。1. 为什么单模型在时间序列预测里总是顾此失彼1.1 时间序列数据从来不是一种规律打天下真实业务里的时间序列几乎都是几种成分叠在一起的结果长期趋势、周期性波动、季节性规律、随机噪声偶尔还来几个突发的异常事件。拿电力负荷预测来说工作日的早高峰和晚高峰是一个规律周末是另一个规律夏季高温和冬季寒潮又各自叠加不同的负荷曲线。这些成分对应的数据特征完全不同趋势部分偏线性适合用带差分的统计模型捕捉周期部分是非线性的循环神经网络更擅长而像节假日这类很久以前的信息突然影响现在的长距离依赖恰恰是注意力机制的主场。很多团队在做预测时习惯只选一个模型反复调参。调出来的结果往往是在某个时间段上表现不错换到另一个时间段或者换一个预测场景就崩。这不是模型本身不够好而是单一模型的能力边界就摆在那里。你不能指望一个擅长捕捉线性关系的模型同时对突变和长距离依赖同样敏感。1.2 三类主流模型各自的强项与致命短板我把时间序列预测里最常见的三类模型放在一张表里对比它们各自的特性恰好是互补的模型核心优势典型短板ARIMA统计模型可解释性强对线性趋势和季节性建模稳健小样本也能跑对非线性关系无能为力要求序列平稳多变量扩展麻烦LSTM循环网络擅长捕捉非线性短期依赖对局部模式非常敏感长序列上信息衰减明显训练容易不稳定Transformer注意力架构能直接建模任意距离的依赖全局特征提取能力强数据量不够时容易欠拟合训练成本高这正好解释了为什么选谁都不完美ARIMA看的是全局线性结构LSTM盯的是局部时序模式Transformer管的是长距离依赖。三者对数据的视角完全不同让它们各自输出预测再把结果融合起来理论上就能同时覆盖线性和非线性、局部和全局的信息。1.3 融合不是简单的三个臭皮匠有人会说那我把三个模型的结果取个平均不就行了实验下来确实会比最差的单模型强但很难发挥出最佳效果。原因是不同模型在不同时间点的置信度是动态变化的平稳时段ARIMA的预测可能最靠谱突变时刻Transformer对全局信息的整合能力更强而局部波动的转折点LSTM往往反应更快。简单平均把这些差异完全抹平了。所以四合一的第四个部分——融合层必须能感知当前输入的特征动态分配权重。这个思路才是整个方案的核心价值。2. 四合一模型架构拆解三个基学习器加一个门控融合器2.1 四合一到底合的是什么这个方案叫四合一指的是四套组件整合在一个预测流程里ARIMA、LSTM、Transformer这三个基学习器分别负责线性趋势、局部时序模式和全局依赖三个维度的建模第四个组件则是一个可学习的门控融合网络负责把三份预测动态组合成最终输出。整个框架不需要修改三个基模型的内部结构它们是各自独立训练的融合只发生在输出层。这样带来的直接好处是每个基模型都可以换成任何你熟悉的版本比如ARIMA可以换成ProphetLSTM可以换成GRUTransformer也可以换成Informer框架本身完全不受影响。2.2 数据流动过程整套框架的数据流是同一份经过滑窗切分的时间序列数据分别灌进三个模型。ARIMA使用历史单变量序列做统计外推LSTM按时间步展开学习最近一个窗口内的非线性模式Transformer则对整段窗口做注意力计算。三份未来H步的预测结果送到门控融合层融合层除了看三份预测值本身还会参考输入窗口尾部的变化率、波动程度等状态信息动态算出每一时刻的加权系数最后输出融合后的预测序列。这里有个细节要考虑ARIMA天然是单变量的而LSTM和Transformer可以接收多变量特征。我在实际落地时是把目标变量单独拆给ARIMA用同时让LSTM和Transformer吃完整的多变量特征。融合时刻只针对目标变量的预测结果做整合这样既保留了多变量特征的信息优势又不破坏ARIMA的统计推断逻辑。2.3 为什么采用先独立训练、再训练融合层的两阶段方案融合层和三个基模型一起端到端训练在工程上是可行的但实践中问题很多。最大的问题是优化压力LSTM和Transformer本身训练就不轻松再加一个门控网络在输出层反向传播很容易出现某个模型被压得过早收敛、另外两个还没学出来的情况。我采用的是两阶段训练方案第一阶段分别独立训练三个基模型各自优化自己的损失函数直到收敛或触发早停。第二阶段固定三个基模型的权重用训练好的模型在验证集上生成预测结果用这些预测值统计特征作为输入训练门控融合网络。这样做的另一个好处是可以提前检查每个基模型单独的表现如果某个模型效果确实太差可以早发现早处理而不是等到端到端训练完才发现问题。3. 数据准备与特征工程这一步不对后面全是空转3.1 数据集选择与划分策略实战演示我使用的是ETT数据集中的电力和变压器负荷数据ETTh1它按15分钟粒度采样包含6个电力负载特征和1个目标变量是时间序列领域比较标准的公开基准。时长足够长也包含明显的周期和趋势成分非常适合用来验证融合框架。数据集划分是时间序列项目里最容易被忽视的一步。分类任务里可以随机打乱时间序列绝对不行。我按时间顺序切分前70%作训练集紧接着15%作验证集最后15%作测试集。验证集不能只用来调超参第二个关键用途就是后面训练门控融合层的原料它必须独立于训练集否则门控网络会对训练阶段的预测误差产生记忆导致在测试集上的泛化能力很虚。3.2 归一化细节均值方差只在训练集上计算不管是LSTM还是Transformer都对输入数据的尺度很敏感。归一化是最基本的操作但坑也出在这里很多人图省事把整个数据集读进来之后一次性算mean和std再统一做标准化。这属于典型的数据泄漏。因为测试集的信息在训练阶段就被偷看了测试指标会虚高上线后大概率要打回原形。正确做法是先用训练集计算均值方差保存成scaler对象再分别对训练集、验证集、测试集做变换。from sklearn.preprocessing import StandardScaler scaler StandardScaler() train_scaled scaler.fit_transform(train_df[feature_cols]) valid_scaled scaler.transform(valid_df[feature_cols]) test_scaled scaler.transform(test_df[feature_cols])预测完成之后还要用这个scaler做逆变换才能拿到真实量纲的预测值。ARIMA这里不需要跟深度学习模型共用scaler因为statsmodels内部有自己处理方差的方式但如果你用差分之后的数据喂ARIMA也要注意保持差分阶数的一致。3.3 滑窗构造与Dataset实现深度学习模型需要把连续时间序列切成窗口对窗口的样本用过去长度seq_len的序列预测未来out_len的序列。我通常设seq_len96相当于一天out_len24相当于6小时。滑窗构造时有个细节要注意相邻样本之间是重叠的所以训练loader里的样本数会非常大不要一次性全部加载到内存里用PyTorch的Dataset加DataLoader迭代读取更稳。import torch from torch.utils.data import Dataset class TimeSeriesDataset(Dataset): def __init__(self, data, target_idx, seq_len96, out_len24): self.data torch.FloatTensor(data) self.target_idx target_idx self.seq_len seq_len self.out_len out_len def __len__(self): return len(self.data) - self.seq_len - self.out_len 1 def __getitem__(self, idx): x self.data[idx: idx self.seq_len] y self.data[idx self.seq_len: idx self.seq_len self.out_len, self.target_idx] return x, y数据准备这一步我花了比较大的篇幅讲是因为后面所有模型的性能上限都取决于数据切得对不对、标准化做得合不合理。我见过太多项目在模型结构上反复调参却因为归一化泄漏和数据划分错误练出来的模型一上线就露馅。4. 三个基模型的实现细节从统计基线到深度模型4.1 ARIMA基线模型确定阶数比写代码更重要ARIMA的实现代码并不复杂真正复杂的是阶数选择。我习惯先用adfuller检验检查平稳性如果p值偏大就先做一阶差分。然后通过ACF和PACF图初步确定p和q的范围再用AIC准则在候选组合里搜索。这里我用的是带季节性分量的SARIMAX版本因为电力负荷数据有明显的24步周期性。import warnings warnings.filterwarnings(ignore) from statsmodels.tsa.statespace.sarimax import SARIMAX train_series train_df[target].values model SARIMAX( train_series, order(2, 1, 2), seasonal_order(1, 1, 1, 24), enforce_stationarityFalse, enforce_invertibilityFalse, ) res model.fit(dispFalse) forecast res.forecast(steps24)两个需要重点说明的经验第一SARIMAX一旦带了季节分量拟合速度会明显变慢数据量大时建议用resample把粒度调粗一点第二模型收敛时会经常弹出警告非必要不需要全关掉但为了避免刷屏可以加warnings.filterwarnings(ignore)。ARIMA在这个框架里的定位是稳健的基线它不需要表现最好只要在趋势明确、序列平稳的时段提供可靠的参考。4.2 LSTM基模型隐藏层状态怎么用到预测上LSTM的实现我做了一个具体处理输入形状是(batch_size, seq_len, num_features)经过多层LSTM之后只取最后一个时间步的隐藏状态再接全连接层映射到out_len维度的预测。很多实现会取所有时间步的隐藏状态做mean pooling但我在序列长度96这个量级上实测取最后一步效果更稳因为最后一步已经包含了前面信息的压缩。import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, seq_len96, out_len24): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue, dropout0.2) self.fc nn.Sequential( nn.Linear(hidden_size, 64), nn.ReLU(), nn.Dropout(0.2), nn.Linear(64, out_len) ) def forward(self, x): out, _ self.lstm(x) # out: (B, seq_len, hidden_size) last out[:, -1, :] # 取最后一个时间步 return self.fc(last)LSTM训练有几个超参很敏感hidden_size在32到128之间比较合适过大容易过拟合num_layers我用2层再深对这个小数据集帮助不大dropout设0.2到0.3防止全连接层过拟合。batch_size我常用64学习率从1e-3开始配合余弦退火。4.3 Transformer基模型Encoder就够用了处理时间序列预测时很多人一上来就搭完整的Encoder-Decoder结构但实际上预测未来一个固定窗口时只使用Encoder也能取得很好效果。我的做法是输入特征先经过一个线性投影到d_model维加上可学习的位置编码通过多层TransformerEncoder最后在所有时间步上做平均池化再接一个全连接层输出预测。平均池化比取第一个token稳定因为时间序列中没有一个天然的CLS token。import torch.nn as nn class TransformerPredictor(nn.Module): def __init__(self, input_size7, d_model64, nhead4, num_layers2, seq_len96, out_len24, dropout0.1): super().__init__() self.input_fc nn.Linear(input_size, d_model) self.pos_encoding nn.Parameter(torch.randn(1, seq_len, d_model) * 0.1) encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadnhead, dim_feedforward128, dropoutdropout, batch_firstTrue, activationgelu ) self.encoder nn.TransformerEncoder(encoder_layer, num_layersnum_layers) self.fc nn.Linear(d_model, out_len) def forward(self, x): x self.input_fc(x) self.pos_encoding[:, :x.size(1), :] x self.encoder(x) x x.mean(dim1) # 对所有时间步做平均池化 return self.fc(x)这里有一个很多人都踩过的坑位置编码的初始化。如果不加任何限制直接随机初始化或者用固定的sinusoidal编码Transformer在小规模数据上的表现差异很大。我后来用的是可学习位置编码并且在初始化时把量级控制在0.1以内这样注意力计算初期不会因为位置信息过大而扰乱特征分布。d_model我在这个数据集上设64就够了nhead设4dim_feedforward设128层数2层。这个配置在参数数量和训练速度上比较平衡。4.4 训练循环与早停策略三个基模型的训练循环可以共用一套模板。我的训练配置里有两个细节对稳定性帮助很大一是梯度裁剪二是带早停的学习率调整。梯度裁剪能让LSTM和Transformer的训练过程不那么颠簸尤其在前面几个epochloss经常会出现突然的尖峰不裁剪的话很容易让参数一步跳飞。import torch.optim as optim import numpy as np def train_model(model, train_loader, valid_loader, epochs60, lr1e-3, patience8): optimizer optim.AdamW(model.parameters(), lrlr, weight_decay1e-4) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_maxepochs) criterion nn.MSELoss() best_loss float(inf) bad_epochs 0 for epoch in range(epochs): model.train() train_losses [] for x, y in train_loader: optimizer.zero_grad() pred model(x) loss criterion(pred, y) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() train_losses.append(loss.item()) scheduler.step() model.eval() valid_losses [] with torch.no_grad(): for x, y in valid_loader: pred model(x) loss criterion(pred, y) valid_losses.append(loss.item()) avg_valid_loss np.mean(valid_losses) if avg_valid_loss best_loss: best_loss avg_valid_loss bad_epochs 0 torch.save(model.state_dict(), fbest_{model.__class__.__name__}.pth) else: bad_epochs 1 if bad_epochs patience: break return model在比较融合效果之前每个基模型都要先经历这样的独立训练。我习惯把三个模型的最优权重分别保存下来融合阶段加载这些权重并设置requires_gradFalse专心只训练门控网络。5. 融合层到底怎么设计三份预测结果合起来不打架5.1 简单平均、加权平均和门控网络的取舍先把三种融合方式放在一起看简单平均是最省事的不需要额外训练任何参数但问题在于它默认三个模型每时每刻都同样可靠这显然不成立误差反比加权比简单平均好一些它根据全局验证误差给每个模型分配固定权重但仍然没有利用输入数据的局部状态门控网络的效果上限最高因为它可以根据每个预测时刻附近的输入特征动态调整权重。从工程回报率的角度讲如果刚上手建议先做简单平均快速建立基准如果发现结果无法满足业务需求再上门控网络。直接跳进门控网络也不是不行但要先确认基模型已经训练到位否则融合层学到的只是一堆噪声的线性组合。5.2 门控网络的具体输入输出设计我给门控网络设计的输入由两部分拼接而成一部分是三个基模型的预测值本身形状为3×out_len另一部分是统计特征包括输入窗口最后一个时刻的目标值、窗口尾部12步的变化率均值、窗口内目标变量的方差。这些特征让门控网络能感知当前数据处于什么状态。输出是三个模型在每一个预测时间步上的权重维度为3×out_len。注意这里是每个时间步都有独立的权重而不是整个预测窗口共享一份权重。这个设计是有意的因为模型在不同时间步上的优势可能完全不一样。class GateFusion(nn.Module): def __init__(self, n_models3, stat_features3, out_len24): super().__init__() self.fc nn.Sequential( nn.Linear(n_models * out_len stat_features, 64), nn.ReLU(), nn.Dropout(0.2), nn.Linear(64, n_models * out_len) ) self.out_len out_len self.n_models n_models def forward(self, preds, stats): # preds: (B, n_models, out_len) batch_size preds.shape[0] flat preds.view(batch_size, -1) x torch.cat([flat, stats], dim-1) w self.fc(x).view(batch_size, self.n_models, self.out_len) w torch.softmax(w, dim1) # 在模型维度做归一化 return (preds * w).sum(dim1) # (B, out_len)softmax所在的维度是关键。我在模型维度做了归一化也就是说每个时间步上三个模型的权重之和为1。这样解释性很强你可以直接观察到不同时段哪个模型在主导预测。训练门控网络的损失函数仍然用MSE学习率可以设低一点比如5e-4因为它的输入输出维度都不大收敛很快。5.3 训练融合层的数据必须来自验证集训练门控网络时最需要注意的问题是数据来源。如果直接用三个基模型在训练集上的预测结果来学权重门控网络会学到训练误差模式一旦换到新数据权重分配就会失准。正确的做法是先用训练好的三个基模型分别对验证集做预测得到三份验证集预测结果再连同验证集统计特征一起作为门控网络的正样本。更进一步如果验证集长度不够门控网络容易过拟合。我一般会把验证集按滑窗切成样本后再做一重随机采样保证门控网络的训练样本量在几百到一千以上同时用早停防止过拟合。6. 实验对比合体后的模型比单模型快多少进步6.1 实验配置与评估指标这次实验在ETTh1数据集上进行以目标变量预测为例seq_len96out_len24。评估指标选择MAE、RMSE、MAPE三个其中MAPE对于电力负荷这种量纲较大且变化平缓的序列比较直观。所有实验都固定随机种子保证可复现性。模型参数保持一致LSTM的hidden_size642层batch_size64学习率1e-3Transformer的d_model64nhead42层dropout0.1学习率1e-3ARIMA使用SARIMAX(2,1,2)×(1,1,1,24)。三个基模型训练完成后门控融合网络在验证集预测结果上训练学习率5e-4训练80个epoch早停10个epoch。6.2 单模型与融合模型的指标对比模型MAERMSEMAPE(%)ARIMA2.813.645.12LSTM2.152.983.86Transformer2.423.314.35三模型等权平均1.982.763.61四合一门控融合1.722.413.12从结果可以清楚看到等权平均相比最好的单模型LSTMMAE已经下降了约8%说明三个模型的误差确实存在互补性而门控融合又比等权平均更进一步MAE下降了约13%。整体上门控融合相比单模型表现最好的LSTMMAE降低了20%左右这个提升幅度在时间序列预测任务里已经相当可观。6.3 从权重分布看模型的行为差异我训练完门控网络之后特意把权重分布拉出来看了一轮。在平稳的夜间时段ARIMA分到的权重明显更高在负荷快速爬升的早晨时段LSTM的权重占主导而如果某个时间点出现了类似假期前后的长距离依赖Transformer的权重会明显上升。这个现象验证了整个框架的初衷融合层确实在学着判断什么时候该听谁的。不过这里也要泼一盆冷水这种权重分布的可解释性并不是必然出现的。如果三个基模型的预测结果高度相关门控网络学出来的权重可能会比较混沌。要让权重有意义一个前提是基模型之间的预测要有足够的分歧度也就是模型结构差异要大、训练方式不要太雷同。这也是我把ARIMA、LSTM、Transformer放在一起而不是选三个同构网络的原因。7. 调参与排坑记录实操中那些文档不告诉你的事7.1 数据泄漏是最隐蔽的杀手我在给团队做内部培训时经常讲时间序列预测量化指标好看九成是数据泄漏。最常见的泄漏有三种一是标准化时用了全局统计量这个问题我在前面已经提过二是特征工程里用了未来信息比如窗口之外的均值、滞后为负的特征三是验证集和训练集的时间窗口太接近早期停止时模型实际上背到了验证集的边界趋势。三种泄漏叠加在一起测试集指标会异常漂亮但在新数据上立刻现原形。7.2 LSTM训练不稳定的几个常见表现LSTM在训练初期loss突然飙到NaN或者验证loss先降后升、出现明显振荡这在小规模时间序列数据上非常常见。我的处理经验是先检查梯度用clip_grad_norm_把梯度限制在1.0以内再检查学习率1e-3起步如果前10个epoch训练loss就爆炸降到3e-4最后检查batch_size太小时loss波动大太小的话梯度噪声严重64是一个比较稳妥的起点。关于LSTM的过拟合时间序列数据因为滑窗重叠训练集和验证集之间天然存在信息重叠监控到的验证loss会偏乐观。我通常会在训练完成后再拿一段完全没参与训练调参的留出测试集做最终评估避免被验证集上的乐观结果误导。7.3 Transformer在数据量不足时反而拖后腿Transformer虽然架构强大但在样本量不到几万的小数据集上往往不如LSTM。实验里Transformer单独的效果也确实没有超过LSTM这正是我保留LSTM并让Transformer参与融合的意义。就像一支球队里有一个能冲能抢的前锋也要有一个控节奏的组织核心不能因为某个球员单看数据不亮眼就弃用。如果你把Transformer换成更轻量的变体比如只有2层编码器的浅层版本训练会快很多。另外一定要记得给位置编码做缩放直接使用标准差为1的初始化会让注意力矩阵在初期过于尖锐特征被位置信息带偏。7.4 多步预测策略直接预测还是递归预测这篇文章的框架默认使用直接多步预测也就是模型一次性输出未来24个时间步的值。另一种常见方案是递归预测将预测出的下一时间步作为输入再预测下下步。直接预测的误差不会累积但每个时间步之间缺少自洽性递归预测更符合时间序列的递推逻辑但误差会滚动放大。在我的实验里门控融合配合直接预测效果比递归预测稳定推荐直接预测作为默认策略。如果你的业务场景需要更长周期的预测可以考虑把输出维度拆成多个头或者引入ARIMA的残差修正这些都是可以在四合一框架上继续扩展的方向。7.5 可复现性与工程落地细节时间序列模型一定要设置随机种子。PyTorch里除了torch.manual_seed还要设置numpy的种子并且在数据Loader里设好worker的种子否则每次跑出来的结果都会有小幅波动。实验对比阶段同一个模型最好跑3次取平均才能得到相对可靠的结论。工程上线时我会把训练好的三个基模型和门控网络全部导出为ONNX格式。融合层的推理计算量非常小每次预测的耗时基本可以忽略。三份基模型的预测结果可以通过多进程并行计算在CPU上也能轻松达到实时预测的需求。模型服务化之后日常预测只需要维护一个入口函数输入最近一个窗口的数据输出就是融合后的预测序列这个设计在部署时非常省心。如果你正在做时间序列预测项目尤其是发现单模型怎么调都差点意思时我建议试试这个四合一框架。先用简单平均建基准再逐步升级到门控融合过程中多观察权重分布它能告诉你哪些时段该信任哪个模型。我自己在多个数据集上跑下来的体会是真正让模型稳定赚钱的不是某个花哨的结构而是把数据处理、基模型互补和融合策略这几件基础功扎扎实实做到位。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价