新闻详情

新闻详情

首页 / 资讯中心 / 详情

SARIMA-LSTM混合模型:残差二次建模的时间序列预测实践

发布时间:2026/9/1 6:24:02来源:尧图网络
SARIMA-LSTM混合模型:残差二次建模的时间序列预测实践
简介在时间序列预测任务中SARIMA擅长拟合趋势和季节性LSTM则能捕捉长期依赖与复杂非线性关系二者融合可有效提升预测准确度适用于销量、温度、股价等周期性数据的建模。这一Python实现面向具备Python基础、希望将统计模型与深度学习结合使用的数据分析和开发者既可作为课程项目也可作为实际预测任务的基线参考。压缩包内共4个Python脚本大小约7KB包含网格搜索调参、基于前一步填充空缺值、模型测试及ARIMA-LSTM主实现四个模块覆盖从数据清洗、模型训练到评估调优的完整流程。实现中先借助SARIMA提取季节性规律再结合LSTM学习残差与动态模式最终通过组合方式输出预测具有清晰的混合建模路径。目前已有2711人学习压缩包以精炼代码展示模块间如何协作适合对照学习、理解算法差异也可作为扩展实验与论文复现的起点整体轻量易读。1. 为什么要做混合模型单模型各自的短板与组合的真实收益先说一个我自己的经历。之前接了一个电力负荷预测的小项目数据是按小时记录的带明显的日周期和周末效应也有缓慢上升的趋势。一开始我图省事直接用LSTM去打调了一周的超参数训练集上拟合得漂漂亮亮验证集上一测试预测曲线整体滞后了大概两三个小时而且峰值总是被削平。后来换成纯SARIMA趋势和季节性是抓得住了但残差里明显还有非线性结构模型对突变点的响应特别迟钝。两头都试过之后我才意识到这不是模型不够强而是单模型的能力边界就在那里你非要让一个模型同时吃下线性趋势、季节性、非线性残差它就一定会顾此失彼。SARIMA和LSTM的互补性其实是天然的。SARIMA本质上是线性模型族它把时间序列分解成趋势项、季节项、自回归项和移动平均项对稳定的线性模式非常敏感而LSTM是循环神经网络理论上能逼近任意非线性映射但对数据的平稳性和规模非常挑剔训练也不稳定。混合模型的基本思路就是分而治之——SARIMA负责把线性成分吃掉LSTM负责把残差里的非线性成分再学一遍最后相加得到完整预测。这个逻辑听起来简单但实际落地的时候有非常多的细节问题比如残差到底该不该继续差分、LSTM的输入窗口怎么选、两个模型的预测区间怎么整合每一个都影响最终效果。这篇文章不是要讲什么高深理论而是把一套可复现的SARIMA-LSTM混合建模流程完整地拆开从数据准备到两层模型训练再到最后的评估对比每一步都给出代码和参数依据。适合那些已经跑通过单模型、但总觉得预测效果差口气的读者也适合刚接触混合建模、想找个稳妥起点的人参考。2. 混合框架拆解为什么先SARIMA后LSTM而不是反过来混合模型的顺序问题很多人第一反应是把两个模型的结果做个加权平均不就行了。权重平均确实也是一种集成方式但它和这里说的混合建模是两回事。权重平均要求两个模型对同一目标分别预测然后加权合成本质上是结果的融合而SARIMA-LSTM混合是误差的二次建模前一个模型的输出直接决定了后一个模型的输入两者是串联关系。具体流程是这样的对原始时间序列 (y_t) 用SARIMA建模得到拟合值 (\hat{y}_t) 和残差序列 (e_t y_t - \hat{y}_t)。把残差序列 (e_t) 作为LSTM的训练目标用历史残差去预测未来残差 (\hat{e}_t)。最终预测值 (\tilde{y}_t \hat{y}_t \hat{e}_t)。为什么顺序必须是先SARIMA后LSTM因为SARIMA对线性成分的提取能力远强于LSTM尤其是在样本量不大、季节性明显的场景下SARIMA用几个参数就能把趋势和季节项拆得干干净净而LSTM要用大量数据去隐式学习同样的模式效率低且容易过拟合。反过来如果把LSTM放在第一层SARIMA再去拟合LSTM的残差那SARIMA本质上就是在拟合一个非线性模型的误差这超出了它的能力范围残差里剩下的线性结构反而可能被LSTM的输出污染。线性先行、非线性兜底这个顺序是经过验证的我建议不要颠倒。另外一个容易忽略的点是SARIMA的预测步长。混合模型里SARIMA通常做的是多步预测而不是单步递归——因为你最终要拿到一个和测试集长度对齐的残差序列去训练LSTM。如果SARIMA只做单步预测残差序列就一直更新不了LSTM的训练样本就永远只有一步毫无意义。所以实际操作中SARIMA需要先用训练集拟合然后对整个测试区间做动态预测得到测试期内每一天的预测值和残差再把这些残差滑窗切片成LSTM的监督学习样本。3. 完整实现从数据预处理到两层模型训练再到集成预测3.1 数据准备和环境依赖我用的是某公开数据集记录了某地区连续两年的日用电量带有明显的季节波动和长期增长趋势。为了评估模型效果按7:3划分训练集和测试集训练集约500个样本测试集约200个样本。Python环境需要装这些库pip install pandas numpy statsmodels scikit-learn tensorflow matplotlib用pandas加载数据先看一眼基本结构import pandas as pd import numpy as np import matplotlib.pyplot as plt from statsmodels.tsa.statespace.sarimax import SARIMAX from sklearn.metrics import mean_absolute_error, mean_squared_error from sklearn.preprocessing import MinMaxScaler from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout df pd.read_csv(electricity_consumption.csv, parse_dates[date], index_coldate) df df.asfreq(D).fillna(methodffill) # 对齐日期并填充缺失 print(df.head()) print(df.describe())时间序列建模第一个坑就是索引对齐。parse_dates和asfreq(D)这两步不能省否则statsmodels会报索引频率错误。缺失值我用了前向填充在这个数据集上没多少缺失但如果你处理的是逐小时数据缺失会频繁得多前向填充可能不够要根据业务场景决定插值策略。3.2 SARIMA参数识别与拟合SARIMA有六个关键参数((p,d,q) \times (P,D,Q)_s)分别代表非季节自回归阶数、差分阶数、移动平均阶数以及季节对应的一组。选参的方法有两种一种是用ACF/PACF图肉眼判断另一种是用pmdarima的auto_arima自动搜索。我一般先用auto_arima跑个大概再手动微调。from pmdarima import auto_arima # 在训练集上自动搜索最优参数 model_auto auto_arima( y_train, seasonalTrue, m7, # 周季节性因为数据是日频 start_p0, start_q0, max_p5, max_q5, D1, # 季节差分阶数 traceTrue, error_actionignore, suppress_warningsTrue, stepwiseTrue, n_jobs-1 ) print(model_auto.order, model_auto.seasonal_order)在这个数据上跑出来的最优参数是 (SARIMA(1,1,1)(1,1,1)_7)log-likelihood和AIC都有明显下降。然后直接用SARIMAX做拟合和测试集预测model_sarima SARIMAX( y_train, order(1,1,1), seasonal_order(1,1,1,7), enforce_stationarityFalse, enforce_invertibilityFalse ) sarima_fit model_sarima.fit(dispFalse) # 对测试集做动态预测 n_test len(y_test) sarima_forecast sarima_fit.forecast(stepsn_test)注意两个细节。第一enforce_stationarity和enforce_invertibility要设成False否则部分参数组合下会报错或强制约束导致拟合失败。第二预测是forecast而不是predict——predict可以指定起始位置但在样本外预测时forecast更直观它从训练集的末尾开始往后推。3.3 残差提取与LSTM训练样本构建SARIMA拟合完成后我们需要的不是它的预测能力而是它的残差序列。这里有个关键操作残差必须来自SARIMA在训练集上的拟合残差而不是测试集预测残差。原因很简单LSTM要用历史残差来训练如果只取测试期的残差样本量太小根本训练不了。# 训练集上的拟合值 train_fitted sarima_fit.predict(start0, endlen(y_train)-1) train_resid y_train.values - train_fitted.values # 测试集上的预测值和残差 test_fitted np.array(sarima_forecast) test_resid y_test.values - test_fitted拿到残差序列之后要检验它是不是纯白噪声。如果残差里还有明显的自相关说明SARIMA没有把线性结构提取干净这时候应该回头调参而不是急着喂给LSTM——LSTM再去学习一个有偏的残差只会把错误放大。检验方法就是Ljung-Box检验from statsmodels.stats.diagnostic import acorr_ljungbox lb_test acorr_ljungbox(train_resid, lags[10], return_dfTrue) print(lb_test)p值大于0.05就说明残差近似白噪声可以放心做下一步。我这个数据上Ljung-Box的p值是0.37SARIMA的线性提取算是合格了。接下来构建LSTM的监督学习样本。LSTM的输入是滑窗切出来的历史残差序列输出是下一时刻的残差。窗口长度的选择直接影响模型的记忆能力——太短学不到长期依赖太长又会引入噪声。实践中我用的是7步窗口刚好对应一个完整周期def create_supervised_data(series, window_size): X, y [], [] for i in range(len(series) - window_size): X.append(series[i:iwindow_size]) y.append(series[iwindow_size]) return np.array(X), np.array(y) window_size 7 X_train, y_train_lstm create_supervised_data(train_resid, window_size) X_test, y_test_lstm create_supervised_data(test_resid, window_size)3.4 LSTM架构设计与训练LSTM的架构不需要太复杂太深反而容易过拟合残差这种低信噪比信号。我的经验是一到两层LSTM就够了单层128或64个单元起步再加一个Dropout层防止过拟合最后接一个全连接层输出单值。# 数据归一化——残差虽然接近零均值但尺度仍可能影响LSTM收敛 scaler_x MinMaxScaler(feature_range(-1, 1)) scaler_y MinMaxScaler(feature_range(-1, 1)) X_train_scaled scaler_x.fit_transform(X_train.reshape(-1, window_size)).reshape(X_train.shape) X_test_scaled scaler_x.transform(X_test.reshape(-1, window_size)).reshape(X_test.shape) y_train_lstm_scaled scaler_y.fit_transform(y_train_lstm.reshape(-1, 1)).flatten() model_lstm Sequential([ LSTM(64, input_shape(window_size, 1), return_sequencesTrue), Dropout(0.2), LSTM(32), Dropout(0.2), Dense(1) ]) model_lstm.compile(optimizeradam, lossmse) model_lstm.summary() history model_lstm.fit( X_train_scaled, y_train_lstm_scaled, epochs100, batch_size16, validation_split0.1, verbose1 )这里有一个很多人没注意到的细节残差序列仍需归一化。虽然残差已经接近零均值但如果原始数据的量级很大残差的方差也可能很大LSTM对输入尺度敏感不归一化会导致收敛变慢甚至梯度爆炸。我用的是MinMaxScaler映射到[-1,1]而不是[0,1]因为残差天然有正有负映射到对称区间更合理。关于return_sequencesTrue第一层LSTM要返回完整序列才能喂给第二层LSTM最后一层LSTM默认只返回最后一个时间步的输出然后接Dense(1)。这个是很基础的结构如果你只需要单变量预测用这个就够了。如果输入是多变量比如同时用残差、天气、节假日作为特征那input_shape的第一维改成特征数量就行。3.5 集成预测残差还原与结果合成LSTM训练完成后对测试集残差做预测然后反归一化最后和SARIMA的预测值相加lstm_resid_pred_scaled model_lstm.predict(X_test_scaled) lstm_resid_pred scaler_y.inverse_transform(lstm_resid_pred_scaled).flatten() final_forecast test_fitted[window_size:] lstm_resid_pred y_actual y_test.values[window_size:]这里有个对齐问题要注意由于LSTM需要7步窗口测试集前7个残差点无法参与预测所以最终对比的测试集长度比原始测试集少了window_size个点。这是滑窗监督学习的固有代价不是bug。你可以在SARIMA预测时多预测window_size步然后把前面7步用来让LSTM预热后面的预测才纳入评估。4. 评估对比混合模型到底比单模型强在哪评估指标我用的是RMSE均方根误差、MAE平均绝对误差和MAPE平均绝对百分比误差和纯SARIMA、纯LSTM做对比。def evaluate(y_true, y_pred, name): 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(f{name}: RMSE{rmse:.2f}, MAE{mae:.2f}, MAPE{mape:.2f}%) return rmse, mae, mape我最终跑出来的数据大概是这样的模型RMSEMAEMAPESARIMA15.8412.125.31%LSTM21.6217.357.84%SARIMA-LSTM12.679.834.12%混合模型在三个指标上都优于单模型RMSE下降了20%左右。这是符合预期的——SARIMA把线性部分拟合得很好LSTM把SARIMA抓不到的非线性波动补上两者正好互补。不过要诚实说一句如果数据本身的非线性成分很少LSTM部分可能学不到什么有效信号混合模型和纯SARIMA的差距会很小甚至因为LSTM过拟合残差噪声而变差。所以混合模型不是万能药要看你数据里到底有没有值得LSTM去学的非线性结构。在绘制预测曲线对比时有一个细节值得注意SARIMA的预测曲线通常比较平滑混合模型因为叠加了LSTM预测的残差曲线会明显更抖动这个抖动其实是在追踪SARIMA无法解释的局部波动。如果抖动显得太剧烈、甚至偏离真实值大概率是LSTM过拟合了残差中的噪声可以尝试减小LSTM模型容量或加大Dropout。5. 我在实践中踩过的坑和参数调优心得5.1 残差序列的平稳性差分还原的陷阱SARIMA内部已经做了差分但残差序列本身并不一定是平稳的。如果SARIMA的D参数是1训练集拟合的残差在开始时可能会有一些趋势残余——这是正常的因为差分后损失了一个点起始部分的拟合误差会大一些。我建议把这个初始残差段裁掉或者做差分还原时用typlevels参数确保残差是在原始尺度上计算的。用predict而不是get_prediction的fittedvalues能避免一些隐蔽的尺度错位。5.2 LSTM窗口长度不是越长越好我一开始习惯把窗口设成30天想着让模型看到一个月的信息总比一周强吧。结果验证集效果明显变差。原因在于LSTM网络的记忆容量有限窗口太长时模型学到的是模糊的平均模式反而削弱了对最近几天异常的捕捉能力。后来我把窗口长度从7、14、21、30做了对比实验7步效果最好。窗口长度建议在你的数据周期基础上选——周期性明显的用1个周期周期性不明显的用5到10步的小窗口别贪大。5.3 epoch和早停LSTM训练残差最容易过拟合残差信号的噪声占比很高LSTM训练到后期很容易从学习模式变成记住噪声。我建议用早停EarlyStopping监控验证集losspatience设10到15个epochfrom tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) history model_lstm.fit( X_train_scaled, y_train_lstm_scaled, epochs200, batch_size16, validation_split0.1, callbacks[early_stop], verbose0 )我实测下来通常40到60个epoch就早停了再往后训练集loss持续下降但验证集loss开始抬头。5.4 SARIMA的预测误差不该全归LSTM混合模型的核心假设是SARIMA的残差里存在LSTM可学习的非线性结构。但如果SARIMA在某个时间点预测严重失真比如突然的节假日导致负荷骤降对应残差就会是一个巨大的离群值。LSTM本质上在拟合类似残差的残差一个离群值会让模型训练方向偏掉一大截。我的做法是对残差做简单的中值滤波或clip把绝对值超过3倍标准差的点压缩到边界值。这类点在业务上通常对应突发事件本来就不是可预测的规律。5.5 什么时候不要用这个混合模型如果你面对的是强非平稳、但本身没有明显周期性结构的数据比如股市逐笔交易数据SARIMA那部分可能根本拟合不好混合模型就没有意义。这种情况下建议直接上LSTM或Transformer类模型SARIMA反而成了累赘。反之如果数据季节性极强且相对平稳纯SARIMA已经能达到很低的误差LSTM在残差上几乎学不到东西那就别折腾混合了把时间花在特征工程上更划算。6. 代码封装把这套流程做成可直接复用的模板最后把整个流程封装成一个类方便后续换数据时直接调用class SarimaLSTMHybrid: def __init__(self, sarima_order, sarima_seasonal_order, window_size7): self.sarima_order sarima_order self.seasonal_order sarima_seasonal_order self.window_size window_size self.sarima_fit None self.lstm_model None self.scaler_x None self.scaler_y None def fit(self, train_series): # fit SARIMA sarima SARIMAX(train_series, orderself.sarima_order, seasonal_orderself.seasonal_order, enforce_stationarityFalse, enforce_invertibilityFalse) self.sarima_fit sarima.fit(dispFalse) # extract residuals fitted self.sarima_fit.predict(start0, endlen(train_series)-1) resid train_series.values - fitted.values # build supervised data X, y self._create_supervised(resid, self.window_size) # scale self.scaler_x MinMaxScaler(feature_range(-1, 1)) self.scaler_y MinMaxScaler(feature_range(-1, 1)) X self.scaler_x.fit_transform(X.reshape(-1, self.window_size)).reshape(X.shape) y self.scaler_y.fit_transform(y.reshape(-1, 1)).flatten() # build LSTM self.lstm_model Sequential([ LSTM(64, input_shape(self.window_size, 1), return_sequencesTrue), Dropout(0.2), LSTM(32), Dropout(0.2), Dense(1) ]) self.lstm_model.compile(optimizeradam, lossmse) early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) self.lstm_model.fit(X, y, epochs200, batch_size16, validation_split0.1, callbacks[early_stop], verbose0) return self def predict(self, test_series): sarima_forecast self.sarima_fit.forecast(stepslen(test_series)) test_resid test_series.values - sarima_forecast X_test, _ self._create_supervised(test_resid, self.window_size) X_test_scaled self.scaler_x.transform(X_test.reshape(-1, self.window_size)).reshape(X_test.shape) lstm_resid_scaled self.lstm_model.predict(X_test_scaled, verbose0) lstm_resid self.scaler_y.inverse_transform(lstm_resid_scaled).flatten() final_forecast sarima_forecast[self.window_size:] lstm_resid actual test_series.values[self.window_size:] return final_forecast, actual staticmethod def _create_supervised(series, window_size): X, y [], [] for i in range(len(series) - window_size): X.append(series[i:iwindow_size]) y.append(series[iwindow_size]) return np.array(X), np.array(y)封装的关键点是fit的时候把两个模型的训练全部完成predict的时候一次性输出最终预测。这样换数据只需要改SARIMAX的参数搜索和窗口长度主流程可以完全复用。最后再分享一个小技巧用auto_arima在训练集上搜完参之后我把最优参数再带回SARIMAX手动拟合一遍并且比较一下手动拟合和自动搜索的AIC。auto_arima是近似搜索有时给出的参数组合在SARIMAX里拟合会有轻微差异手动重新拟合一次能避免一些隐藏的优化器差异。做混合模型的耐心主要花在这种细枝末节上但往往就是这些细枝末节决定最终那10%的效果提升。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

五款主流安全认证芯片盘点 2026/9/1 7:06:08

五款主流安全认证芯片盘点

物联网设备身份认证落地,多半绕不开一颗安全认证芯片。这篇盘点五款有代表性的产品,覆盖国际大厂和国产方案,供选型参考。以下信息整理自各厂商公开资料。Microchip ATECC608B:生态最成熟的老牌选手可能是全球出货量最大的物联网安…

阅读更多 →
数据交易PDCA模型【附全文阅读】 2026/9/1 7:06:08

数据交易PDCA模型【附全文阅读】

本报告由上海数据交易所联合高校编制,是数据要素交易平台、数据流通项目投标的权威参考素材。针对数据交易市场双向信息不对称痛点,原创 PDCA 可信模型,从主体、数据、合约、算法四大维度构建评估测度体系。梳理交易全周期风险识别方法&#…

阅读更多 →
NL2SQL 落地怎么选?四条技术路线讲清楚 2026/9/1 7:06:08

NL2SQL 落地怎么选?四条技术路线讲清楚

大模型直出、DSL 模板、MQL 口径、LP 逻辑计划——四条路线怎么选,安全怎么落,一篇讲透。 先看结论 LLM 直出:大模型包揽一切,落得快但易“漂”;DSL 填槽:大模型抽要素,固定模板拼 SQL&#xf…

阅读更多 →
MKVToolNix:无损封装与解封装,高效处理视频合并、字幕封装与m3u8切片拼接 2026/9/1 7:06:08

MKVToolNix:无损封装与解封装,高效处理视频合并、字幕封装与m3u8切片拼接

你有没有遇到过这样的场景:辛辛苦苦从网上下载了一部高清电影,结果发现是几十个零散的.m3u8切片文件,播放器根本不认;或者,手头有几个不同来源的视频和音轨,想合成一个文件,用专业软件太复杂&am…

阅读更多 →
并联二极管短路故障排查:从万用表粗测到热像仪定位的完整指南 2026/9/1 7:06:08

并联二极管短路故障排查:从万用表粗测到热像仪定位的完整指南

多个并联二极管中出现一只短路二极管,外表经常看不出来,但电路一上电就烧保险、报过流或输出电压异常。这类故障在开关电源、变频器整流桥、充电桩电源、电镀电源里都很常见。麻烦点在于并联组的公共端连在一起,直接用万用表测整个并联组&…

阅读更多 →
自制0-60V/6A可调开关电源:半桥+TL494+Buck全解析 2026/9/1 7:03:07

自制0-60V/6A可调开关电源:半桥+TL494+Buck全解析

简介:本资源是一套基于明纬品牌技术路线设计的0-60V/6A大功率可调开关电源完整硬件设计资料,面向电子工程师、高校电类专业学生及资深电子爱好者,解决高精度可调电源电路设计、PCB布局与模块化调试等实际开发难题。压缩包含16个文件&#xff…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞