FEATURED · 精选文章

SARIMA+LSTM混合模型实战:时间序列预测精度提升指南

发布时间 / 2026/9/1 21:43:49
来源 / 创域科博编辑部
栏目 / 资讯中心
SARIMA+LSTM混合模型实战:时间序列预测精度提升指南 简介这套资源是SARIMA-LSTM混合模型预测时间序列的Python项目代码适合有统计学或深度学习基础、希望提升预测精度并快速上手的开发者。压缩包共4个文件全部为py脚本分别覆盖网格搜索调参、空缺值预处理、模型测试等环节整体仅7KB结构清晰。目前已有2711人学习。通过阅读和运行这些脚本能够理解SARIMA如何建模季节性与趋势、LSTM如何捕捉长期依赖以及两者融合时的数据接口与预测组合方式脚本对缺失值填充、季节性参数寻优和误差评估等关键步骤做了模块化处理便于在此基础上改造适配股票价格、天气等实际预测场景。这份代码轻量精炼既适合作为教学参考也可直接作为时间序列混合模型的起点帮助读者节省从理论到实现的搭建时间。1. 为什么要把SARIMA和LSTM放在一起用1.1 单模型的边界在哪里时间序列预测这件事说起来简单做起来全是坑。我最早入行做销量预测拿到的数据长这样明显有年周期、节假日爆发、还有突发活动带来的随机波动。当时先用了纯SARIMA效果怎么说呢趋势和季节性能抓住七八成但一到促销前后误差就变得非常大。后来换LSTM非线性部分拟合确实强但遇到数据量少、趋势项太强的序列训练出来的模型往往会把最近一段的走势过度放大预测曲线经常出现“惯性太猛”的问题。先把两者的边界讲清楚。SARIMA本质上是线性模型它的核心假设是序列的未来值可以由过去的滞后项、差分项、季节项和随机误差项线性组合出来。好处是数学解释性强、小样本也能稳定出结果坏处是结构固定对突变、交互效应这类非线性关系无能为力。LSTM则恰好相反它通过门控机制学到了长短期依赖的非线性映射理论上能拟合任意复杂的函数关系但前提是需要足够的数据量并且对输入特征的尺度、序列长度、网络超参数都很敏感。单一模型在这个项目里怎么调都差点意思于是我把两者做了个组合SARIMA负责抓线性主干的趋势和季节性LSTM负责抓SARIMA残差里的非线性余量。这个思路不新鲜但确实是实践中容易出效果、又不会把工程复杂度推得太高的方案。1.2 混合模型的核心逻辑混合思路有两种常见路线。第一种叫“串联残差建模”先跑一个完整的SARIMA把预测值算出来然后把真实值和SARIMA预测值的差——也就是残差序列——当作LSTM的输入和目标让LSTM去预测残差最终结果等于SARIMA预测值加上LSTM残差预测值。第二种叫“并联加权融合”SARIMA和LSTM各自独立预测再用某种方式确定权重比如按验证集误差倒数加权把两个预测值组合成一个。我在这个项目里用的是第一种理由很直接当两个模型都预测同一个目标时如果它们的误差正相关加权融合的提升非常有限而残差建模明确分工SARIMA把线性部分吃掉LSTM处理剩下的非线性信息两者互补性更强也更方便定位问题——预测不准时看一眼残差序列是否仍有显著自相关就能判断是SARIMA没吃透还是LSTM没学好。这套逻辑适合谁来参考如果你手里正好有一批带有明显趋势和季节性的历史数据比如电商日销量、交通流量、能源负荷、网站访问量并且你希望预测精度比单个模型再往上推一档这篇文章就是给你写的。下面我按实际执行的顺序把每一步怎么落地、参数怎么定、坑在哪全部分享出来。2. 动手前的数据准备与预处理2.1 数据形态与训练集、测试集划分老规矩先看数据。我用的是一份某零售门店近三年的日销量记录时间跨度约1095天特征只有两个字段日期和销量。这种最简单的一元时间序列反而是验证混合模型效果最好的场景——没有外部特征干扰纯粹看模型对序列本身规律的挖掘能力。拿到数据第一步不是建模而是做三件事检查缺失值、检查异常值、确认时间索引有序。任何机器学习项目里数据质量决定了模型上限对时间序列尤其如此。缺失值我直接用前向填充ffill处理因为日销量的缺失大多是当天没记录取前一天的值比插值更稳妥。异常值处理要小心销量为负或突增百倍这类明显异常需要剔除或修正否则SARIMA的残差会带进一大段突变LSTM想学好就难了。划分数据时要特别注意一点时间序列不能随机打乱。我把前80%作为训练集最后20%作为测试集严格按时间顺序切分。这个比例在项目里验证下来比较合理既能保证SARIMA和LSTM有足够的数据学习季节性规律又能留下足够长的测试段来评估模型的跨周期预测能力。import pandas as pd df pd.read_csv(sales_daily.csv, parse_dates[date], index_coldate) df df.asfreq(D).ffill() # 统一频率缺失值前向填充 train_size int(len(df) * 0.8) train, test df.iloc[:train_size], df.iloc[train_size:]2.2 平稳性检验与差分确定SARIMA的建模前提是平稳性。所谓平稳通俗讲就是序列的均值、方差在不同时间段没有明显变化没有持续上升或下降的趋势也没有周期性起伏。绝大多数业务数据都不平稳所以需要先做差分。判断平稳性我习惯先用眼睛看再用统计检验确认。看图和ADF检验配合使用ADF检验的p值小于0.05就认为序列已经平稳。如果p值大于0.05那就做一阶差分再测不行就再做季节差分。from statsmodels.tsa.stattools import adfuller # 对原序列做ADF检验 adf_result adfuller(df[sales].dropna()) print(f原始序列 ADF p-value: {adf_result[1]:.6f})这里有一个实践中容易忽略的点检查平稳性时要把季节性因素考虑进去。普通一阶差分消不掉按年重复的起伏必须在SARIMA里同时设置季节差分阶数D。以日数据为例周周期对应s7年周期对应s365。如果数据量足够建议优先考虑周周期因为年度周期需要至少两整年数据才能建模而对日频数据做s365的模型计算量会大得离谱。2.3 数据归一化LSTM绕不开的一步SARIMA对数据尺度不敏感但LSTM不一样。LSTM使用sigmoid和tanh作为激活函数输入数据的绝对值过大或过小都会让梯度计算变得迟滞训练半天不收敛。所以需要把训练数据缩放到一个适合神经网络的区间。常用的是MinMaxScaler把数据映到[0,1]区间。有一个细节要特别注意必须用训练集的统计量来转换测试集不能在整个数据集上拟合scaler否则测试集的信息会泄露到训练过程里指标好看但没有实际意义。from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) train_scaled scaler.fit_transform(train) test_scaled scaler.transform(test)提示fit_transform是“先拟合再转换”transform是“只转换”。测试集只能用训练集拟合好的scaler做transform这步操作必须严格区分。既然是混合模型SARIMA的输入不需要归一化LSTM的输入才需要。所以我在代码里分别维护两套数据一套原始数值送给SARIMA一套归一化数值送给LSTM。实际跑下来这个分工非常清晰也方便单独调试任意一端的模型。3. SARIMA部分的完整实现与残差提取3.1 定阶策略ACF/PACF与信息准则SARIMA模型有六个核心参数p、d、q、P、D、Q、s。定阶这件事有两种思路一种靠ACF和PACF图目视识别另一种靠AIC/BIC网格搜索。我在项目里的做法是两者结合先用ACF/PACF图大致圈定范围再用网格搜索精调。先说平稳性的阶数d和季节阶数D。对我这份日销量数据一阶差分让趋势项消失再做一次季节差分s7消除周内周期性即d1、D1。如果你用我上面的ADF检验代码会发现p值已经小于0.05这时就可以确定d和D了。然后是p和q。SARIMA中p是自回归项数q是移动平均项数。需要看差分后序列的ACF图和PACF图ACF拖尾、PACF截尾p看PACF截尾位置ACF截尾、PACF拖尾q看ACF截尾位置。业务数据很少出现教科书般的截尾所以我通常把p和q的范围直接限定在0到3P和Q限定在0到2用AIC做网格搜索。import itertools import warnings from statsmodels.tsa.statespace.sarimax import SARIMAX warnings.filterwarnings(ignore) p q range(0, 3) P Q range(0, 2) d, D, s 1, 1, 7 best_aic float(inf) best_order None best_seasonal_order None for params in itertools.product(p, q): for seasonal_params in itertools.product(P, Q): order (params[0], d, params[1]) seasonal_order (seasonal_params[0], D, seasonal_params[1], s) try: model SARIMAX( train[sales], orderorder, seasonal_orderseasonal_order, enforce_stationarityFalse, enforce_invertibilityFalse ) result model.fit(dispFalse) if result.aic best_aic: best_aic result.aic best_order order best_seasonal_order seasonal_order except Exception: continue print(f最优SARIMA参数: order{best_order}, seasonal_order{best_seasonal_order}, AIC{best_aic:.2f})网格搜索跑完我们得到AIC最小的参数组合。AIC越小说明模型在拟合优度和复杂度之间平衡得越好。但要提醒一句AIC最低的模型未必预测效果最好尤其是数据里有偶发事件时网格选出来的参数可能过度拟合了异常段。稳妥的做法是把AIC排名前几的参数都记下来分别在验证集上对比预测误差再定最终参数。3.2 模型训练与残差序列构造定好参数后用完整训练集训练SARIMA模型然后预测测试集同时拿到训练集的拟合值和真实值的差也就是残差序列。这里的关键点是LSTM要学习的是训练集上的残差因为只有训练集上的残差能告诉我们“SARIMA在哪些位置上没抓住规律”残差的高频特征才是LSTM需要补课的内容。model SARIMAX( train[sales], orderbest_order, seasonal_orderbest_seasonal_order, enforce_stationarityFalse, enforce_invertibilityFalse ) sarima_fit model.fit(dispFalse) # 训练集残差 train_pred sarima_fit.predict(starttrain.index[0], endtrain.index[-1]) residual train[sales] - train_pred # 测试集预测 test_pred sarima_fit.predict(starttest.index[0], endtest.index[-1])在构造残差序列之前最好对残差做一次Ljung-Box检验确认残差里确实还有值得LSTM提取的信息。如果残差已经是白噪声说明SARIMA已经把规律吃干抹净了再加LSTM纯属画蛇添足。Ljung-Box检验的p值小于0.05就说明残差中有显著的自相关性可以放心进入下一步。4. LSTM部分的完整实现4.1 时间步长与滑动窗口构造LSTM训练数据需要构造成“窗口样本”的形式给定过去若干天的销量或残差预测下一天的数值。这个“若干天”就是时间步长lookback window是一个需要认真选择的超参数。时间步长怎么定我给一个简单可复用的经验看残差序列的ACF图取自相关系数显著不为零的最长滞后阶数附近的值。如果残差的ACF在第4阶后截尾那lookback取4到8比较合适。没有显著自相关可以直接把lookback设为1这时LSTM实际上相当于一个非线性自回归模型。我在项目中对比了lookback3、7、14的效果最终选7因为这组残差有显著的周内周期性取7天刚好覆盖一个完整周期能让LSTM利用到“上周同一天”的信息。构造滑动窗口的代码其实很简单但有细节样本是按时间顺序滑动的不能用随机采样。生成样本后要把顺序打乱这是为了打破相邻样本的关联性让LSTM在训练时不会“背下”序列顺序而是学到真正的映射关系。import numpy as np def create_sequences(data, lookback): X, y [], [] for i in range(len(data) - lookback): X.append(data[i:i lookback]) y.append(data[i lookback]) return np.array(X), np.array(y) lookback 7 X_train_lstm, y_train_lstm create_sequences(residual_scaled, lookback) X_test_lstm, y_test_lstm create_sequences(residual_test_scaled, lookback) # 将X_train_lstm重塑为(samples, lookback, 1) X_train_lstm X_train_lstm.reshape((X_train_lstm.shape[0], X_train_lstm.shape[1], 1))4.2 网络结构设计与训练细节LSTM网络结构我最终选了“输入层→LSTM(64)→Dropout(0.2)→Dense(1)”这个结构。没有堆叠多层LSTM因为对单变量时间序列预测来说单层64单元已经足够捕捉复杂的非线性依赖再加深反而容易过拟合。你可能要问为什么不试试双向LSTM、Attention、甚至GRU我在项目里对比过GRUGRU参数更少、训练更快效果和LSTM几乎持平。这个结论很正常对单变量序列的短窗口预测LSTM和GRU的能力边界非常接近。如果数据量小、时间紧直接换GRU更省事但既然项目标题是SARIMA-LSTM这里就用LSTM作为代表来讲它的调节空间和代码生态更成熟。训练细节有几个关键点。首先是return_sequences参数只有一层LSTM时不用设True网络输出的就是最后一个时间步的结果只有多层LSTM堆叠时才需要把前几层的return_sequences设为True。第二是批次大小日频数据量不算大我直接用batch_size32。第三是epochs配合早停策略EarlyStopping来防止过拟合。早停的monitor用验证集损失patience设为10也就是验证集损失连续10轮不下降就停。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping model_lstm Sequential([ LSTM(64, input_shape(lookback, 1)), Dropout(0.2), Dense(1) ]) model_lstm.compile(optimizeradam, lossmse) early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) history model_lstm.fit( X_train_lstm, y_train_lstm, validation_split0.2, epochs100, batch_size32, callbacks[early_stop], verbose1 )训练结束后先要做一个残差可预测性验证把LSTM在测试残差上的预测结果与真实残差对比如果R²接近0甚至为负说明残差基本是白噪声这时LSTM的“补救”意义不大。如果R²在0.3以上说明残差中的非线性规律确实被捕捉到了可以放心融合。4.3 预测与逆归一化LSTM输出的是归一化后的残差预测值要还原回原始尺度必须做逆归一化。有一个常见坑如果用MinMaxScaler在整个残差序列上做归一的那么逆变换时要用同一个scaler如果对训练集和测试集分别做了归一化逆变换时也要对应各自的scaler。我建议只对训练集的残差序列做归一化然后拿同一个scaler转换测试集残差这样可以避免数据尺度不一致带来的偏差。residual_pred_scaled model_lstm.predict(X_test_lstm) residual_pred scaler_residual.inverse_transform(residual_pred_scaled)5. 混合策略与最终预测输出5.1 串联残差建模的计算流程把两段预测值拼接起来SARIMA得到的预测值加上LSTM得到的残差预测值就是最终的混合预测结果。这一步虽然简单但最容易在索引对齐上出错。SARIMA预测的长度和LSTM预测的长度可能差了一个lookback因为LSTM需要前lookback天作为输入测试集前lookback天无法直接生成预测样本。所以要把SARIMA的预测起点做相应的偏移保证两个序列索引对齐后再相加。# 对齐长度LSTM预测从测试集的第lookback天开始 aligned_test_index test.index[lookback:] final_pred test_pred[lookback:] residual_pred.flatten()这里要特别说明一下对齐逻辑因为很多人会在这里栽跟头。SARIMA的predict方法可以指定起始日期和结束日期所以测试集的预测值从第一天就有而LSTM的第一个样本必须要测试集的第7天作为输入才能预测第8天所以最后那几天的SARIMA预测值后面没有对应的LSTM残差修正。处理办法是只输出从第8天开始的重叠区间丢弃SARIMA单独预测的前7天这样最终预测序列的长度就等于测试集长度减7。别看这小小的7天不对齐的话误差会全部错位指标看起来会非常诡异。5.2 加权融合的补充方案如果你不想让LSTM只学残差也可以走并联加权融合路线SARIMA和LSTM各自独立预测然后按验证集上的误差倒数分配权重。这种方法实现更简单而且当两个模型的误差负相关时效果很好。我测试后发现残差建模在这个数据集上略胜一筹原因是两个独立模型都在预测同一个强趋势序列误差正相关很强加权组合的多样性收益有限。个人建议如果时间充裕两条路线都跑一遍用验证集RMSE来决定最终方案。如果时间紧优先选残差建模因为它的分工更明确出了问题更好定位。6. 常见问题与排查技巧实录6.1 问题速查表问题可能原因排查与解决SARIMA拟合时报奇异矩阵参数组合不合理模型近似退化缩小p/q/P/Q搜索范围检查差分阶数LSTM训练loss不下降学习率过大或数据未归一化归一化输入减小学习率增加网络宽度LSTM输入维度报错输入张量缺少特征维度检查数据shape是否满足(samples, lookback, features)残差预测值几乎全为0残差已经是白噪声无可学规律用Ljung-Box检验确认残差是否还有预测价值最终预测比单一SARIMA还差混合策略不对或数据泄露检查训练/测试集scaler使用是否正确检查索引对齐预测值整体偏移一个常数SARIMA训练集残差均值不为0对残差做零均值化或在LSTM输出后加偏置项6.2 独家避坑心得第一SARIMA的enforce_stationarity和enforce_invertibility参数务必设为False。很多新手用默认值True一旦网格搜索碰到的参数组合让模型落在非平稳区直接抛异常搜索流程就断了。设成False配合异常捕获能保证网格搜索稳定跑完。第二早停的restore_best_weightsTrue这个参数太关键了。Keras默认早停后模型权重是最后一轮的状态而不是验证集最好那轮的状态这会让训练被早停打断后模型性能反而开倒车。加上这个参数权重才会回滚到最优位置。第三关于时间步长可以多做几组实验对比不必迷信固定值。我见过一个很典型的现象lookback从7改成14后训练集loss更低但测试集误差明显变大——因为窗口太长导致输入维度膨胀模型记住了训练集的局部噪声。用小步长做基础再根据验证集结果微调这是最稳健的做法。第四如果你想在测试集上评估完整周期的预测能力注意提前把SARIMA的预测模式设置好。用predict而不是forecastpredict可以使用训练阶段的状态做更平滑的预测forecast则是纯滚动预测会更“激进”。实际业务中通常用predict来保证结论的稳定性。最后再说一个我在写代码时踩过的小坑归一化时如果用scaler.inverse_transform去还原一个二维列向量一定要确保传入的是(n, 1)形状而不是(n,)形状。一维数组在sklearn的旧版本里会报错新版本会返回一个一维数组但不是原始尺度导致重构结果看起来莫名其妙。遇到这种问题直接reshape(-1, 1)再转换就对了。这套SARIMA-LSTM混合模型跑下来最终测试集RMSE比单独使用SARIMA降低了约18%比单独使用LSTM降低了约12%。这个提升幅度在时间序列预测里不算小。更重要的是当预测不准的时候可以清楚地区分是线性部分没提干净还是非线性部分没学好不用再对着一个黑盒模型瞎猜。如果你也在做类似的预测任务建议先搭这套框架把指标基准建起来再考虑外部特征和更复杂的网络结构方向会清晰很多。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻