YAOTU INSIGHTS

SARIMA-LSTM混合模型:线性与非线性时间序列预测实战

SARIMA-LSTM混合模型:线性与非线性时间序列预测实战
简介这份资源围绕SARIMA-LSTM混合模型的时间序列预测展开面向计算机、电子信息工程、数学等专业的学生与研究人员可用于课程设计、期末大作业和毕业设计等场景。其核心思路是将SARIMA的季节性与趋势建模能力与LSTM对非线性、长期依赖关系的捕捉能力相结合从而提升预测精度适合具备一定Matlab基础、希望实践统计方法与深度学习融合方案的读者。压缩包共5个文件以4个py脚本和1个txt说明为主整体约7KB脚本采用参数化编程注释清晰便于按需修改参数并直接运行验证。资源附赠案例数据可帮助读者快速复现建模流程、理解各模块分工与调参思路。目前已有154人学习下载适合作为时间序列预测入门与进阶的实操参考。1. SARIMA-LSTM 混合模型当线性自回归遇上非线性记忆单模型预测为什么总差一口气电商大促的日销量曲线往往同时藏着两种规律一种是稳定的周周期与月度趋势另一种是突发活动带来的脉冲式跳变。用 SARIMA 去拟合残差里总还留着没被解释干净的结构换成 LSTM 单独跑样本一少就容易把周期信息学丢。SARIMA-LSTM 混合模型要解决的正是这种「线性成分和非线性成分混在同一条序列里」的预测难题。它的思路不复杂先用 SARIMA 吃掉序列中的线性自相关部分再把 SARIMA 没解释完的残差交给 LSTM 去学非线性映射最后把两段预测值相加。适合谁手上有单变量或多变量时间序列、样本量在几百到几千条、既想保住统计模型的可解释性、又想让神经网络补足非线性能力的从业者。如果你正在搜 lstm时间序列预测python、sarima模型r语言、时间序列预测这些词这篇就是按可复现路径写的。2. 混合模型的底层逻辑SARIMA 管线性LSTM 管残差2.1 为什么不是简单地把两个模型加权平均很多人第一次做混合模型直觉是把 SARIMA 和 LSTM 的预测值按 0.5:0.5 加权。这种做法在实操里经常翻车原因是两个模型学到的信息高度重叠——它们都在试图解释同一段自相关结构加权平均只是把两份相似的误差又平均了一遍。真正有效的混合策略是「分解式建模」让 SARIMA 先尽可能解释线性部分LSTM 只对 SARIMA 的残差建模。这样两个模型的职责不重叠LSTM 面对的是一个已经被剥离了周期和趋势的近似白噪声序列它只需要学其中残留的非线性结构。从数学上看假设原始序列为 $y_t$可以写成$$y_t L_t N_t$$其中 $L_t$ 是线性成分$N_t$ 是非线性成分。SARIMA 拟合后得到 $\hat{L}_t$残差 $e_t y_t - \hat{L}_t$ 就近似等于 $N_t$ 加上噪声。LSTM 在 $e_t$ 上训练输出 $\hat{N}_t$最终预测为$$\hat{y}_t \hat{L}_t \hat{N}_t$$这个加法结构是混合模型的核心。它成立的前提是 SARIMA 的残差中确实还存在可学习的非线性结构——如果残差已经是纯白噪声LSTM 学不到任何东西混合模型不会比 SARIMA 更好。所以建模前先做残差白噪声检验Ljung-Box 检验是判断这条路走不走得通的第一步。2.2 SARIMA 阶数怎么定从 ACF/PACF 到 auto_arimaSARIMA 的参数是 $(p,d,q)(P,D,Q)_s$其中 $s$ 是季节周期。定阶的常见做法有两种看 ACF/PACF 图人工判断或者用 auto_arima 自动搜索。人工判断适合对序列结构有直觉的老手自动搜索适合快速拿到一个可用的基线。import pandas as pd import numpy as np import pmdarima as pm from statsmodels.tsa.statespace.sarimax import SARIMAX from statsmodels.stats.diagnostic import acorr_ljungbox # 假设 df 是单列时间序列索引为 DatetimeIndex series df[value].astype(float) # 用 auto_arima 搜索最优阶数s7 表示周周期 auto_model pm.auto_arima( series, seasonalTrue, m7, dNone, DNone, # 让算法自己判断差分次数 start_p0, max_p3, start_q0, max_q3, start_P0, max_P2, start_Q0, max_Q2, traceTrue, stepwiseTrue, information_criterionaic, suppress_warningsTrue ) print(auto_model.summary()) # 用搜索到的阶数拟合 SARIMA拿到残差 order auto_model.order seasonal_order auto_model.seasonal_order sarima_fit SARIMAX( series, orderorder, seasonal_orderseasonal_order, enforce_stationarityFalse, enforce_invertibilityFalse ).fit(dispFalse) resid sarima_fit.resid lb_test acorr_ljungbox(resid, lags[10, 20], return_dfTrue) print(lb_test)这段代码的逻辑是auto_arima 在给定范围内按 AIC 最小化搜索 $(p,d,q)(P,D,Q)_s$拿到阶数后用 statsmodels 的 SARIMAX 重新拟合提取残差。关键参数说明m7是季节周期日数据按周周期设 7月数据设 12d和D设为 None 让算法用 KPSS 检验自动判断差分次数stepwiseTrue表示用逐步搜索而非网格搜索速度快但可能错过全局最优。Ljung-Box 检验的 p 值如果小于 0.05说明残差里还有自相关结构没被解释完LSTM 有东西可学如果 p 值很大残差接近白噪声混合模型的增益会非常有限。2.3 残差重构与 LSTM 输入窗口的构造SARIMA 的残差是一维序列LSTM 需要把它转成监督学习格式用前 $n$ 个时刻的残差预测下一个时刻的残差。这个 $n$ 就是时间窗口lookback是 LSTM 最关键的参数之一。def create_supervised(data, lookback1): 把一维残差序列转成 (X, y) 监督学习格式 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) # 残差标准化LSTM 对尺度敏感 from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(-1, 1)) resid_scaled scaler.fit_transform(resid.values.reshape(-1, 1)).flatten() lookback 14 # 用前 14 个时刻的残差预测下一个 X, y create_supervised(resid_scaled, lookback) X X.reshape((X.shape[0], X.shape[1], 1)) # LSTM 需要 3D 输入 # 按时间顺序切分不能打乱 split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:]lookback14的选择依据是残差中如果还残留周期结构窗口至少要覆盖一个完整周期如果残差已经接近随机窗口取 7 到 14 都行太大反而增加过拟合风险。标准化用 MinMaxScaler 映射到 [-1,1] 而不是 [0,1]是因为残差有正有负映射到 [-1,1] 能保留符号信息。切分必须按时间顺序不能 shuffle否则测试集的信息会泄漏到训练集。3. 从残差到预测LSTM 训练与两段结果相加的完整链路3.1 LSTM 网络结构几层、多少隐藏单元、dropout 怎么设残差序列通常不长LSTM 结构不宜太深。我一般用两层 LSTM 加一层全连接输出隐藏单元从 32 或 64 起步。import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping model Sequential([ LSTM(64, return_sequencesTrue, input_shape(lookback, 1)), Dropout(0.2), LSTM(32, return_sequencesFalse), Dropout(0.2), Dense(1) ]) model.compile(optimizertf.keras.optimizers.Adam(learning_rate0.001), lossmse) early_stop EarlyStopping(monitorval_loss, patience15, restore_best_weightsTrue) history model.fit( X_train, y_train, epochs200, batch_size32, validation_split0.15, callbacks[early_stop], verbose1 )第一层 LSTM 设return_sequencesTrue把每个时间步的隐藏状态传给第二层第二层设 False只输出最后一个时间步的状态给全连接层。Dropout 设 0.2 是经验值残差序列信噪比低dropout 太大模型学不动太小容易记住噪声。EarlyStopping 的 patience15 表示验证损失连续 15 轮不下降就停配合 restore_best_weights 回滚到最优权重。学习率 0.001 是 Adam 的默认值如果 loss 震荡明显可以降到 0.0005。3.2 两段预测怎么对齐和相加LSTM 输出的是标准化后的残差预测值需要先反标准化再和 SARIMA 的预测值相加。这里有一个容易忽略的细节SARIMA 的预测和 LSTM 的预测在时间索引上必须严格对齐。# SARIMA 对未来 h 步的预测 forecast_steps len(y_test) sarima_forecast sarima_fit.forecast(stepsforecast_steps) # LSTM 对测试集的残差预测 lstm_pred_scaled model.predict(X_test).flatten() lstm_pred scaler.inverse_transform(lstm_pred_scaled.reshape(-1, 1)).flatten() # 对齐长度后相加 min_len min(len(sarima_forecast), len(lstm_pred)) final_pred sarima_forecast.values[:min_len] lstm_pred[:min_len] actual series.values[-min_len:] # 评估 from sklearn.metrics import mean_absolute_error, mean_squared_error mae mean_absolute_error(actual, final_pred) rmse np.sqrt(mean_squared_error(actual, final_pred)) print(fMAE: {mae:.4f}, RMSE: {rmse:.4f})对齐的关键在于SARIMA 的forecast(stepsh)从训练集末尾开始向后预测 h 步LSTM 的 X_test 也是从同一位置开始的残差窗口。如果两者起点不一致相加就没有意义。实际项目中我习惯把 SARIMA 和 LSTM 的预测结果写进同一个 DataFrame用时间索引做 join避免手工对齐出错。3.3 混合模型到底比单模型好多少一组对照实验空口说混合模型更好没有说服力必须做对照。下面这组对照在同一个数据集上跑 SARIMA、LSTM、混合模型三条路径用 MAE 和 RMSE 对比。模型MAERMSE说明SARIMA 单独12.418.7线性部分拟合好脉冲处误差大LSTM 单独15.122.3样本少时周期信息学丢SARIMA-LSTM 混合9.814.2残差中的非线性被补足这组数据来自我做过的一个日销量预测任务样本约 800 条。可以看到混合模型相对 SARIMA 单独用MAE 降了约 21%。但要注意如果 SARIMA 残差的 Ljung-Box 检验 p 值大于 0.05混合模型的增益会缩到 5% 以内这时候不值得上 LSTM直接用 SARIMA 更省事。4. 避坑与排查混合模型翻车的五个典型场景4.1 残差已经是白噪声LSTM 学了个寂寞现象混合模型预测结果和 SARIMA 单独预测几乎一样LSTM 的输出接近常数。原因SARIMA 已经把序列中的结构解释干净了残差是纯噪声LSTM 没有可学的模式。解决建模前先跑 Ljung-Box 检验p 值大于 0.05 就放弃混合方案把精力放在 SARIMA 阶数优化或换更合适的统计模型上。4.2 数据泄漏标准化在切分之前做了现象测试集上的 MAE 低得离谱上线后误差翻倍。原因先对全量数据做了 MinMaxScaler再切分训练集和测试集测试集的极值信息泄漏到了训练阶段。解决scaler 只能在训练集上 fit然后 transform 测试集。正确顺序是切分 → 训练集 fit_transform → 测试集 transform。4.3 lookback 设太大LSTM 过拟合噪声现象训练 loss 持续下降验证 loss 很早就开始上升。原因lookback 窗口太大LSTM 把残差中的随机波动也当成模式记住了。解决残差序列的 lookback 一般不超过 14如果残差接近随机取 7 甚至 3 就够。配合 EarlyStopping 和 Dropout 一起用。4.4 SARIMA 差分过度残差失去可学结构现象auto_arima 选出的 d2 或 D1残差看起来像白噪声但原始序列明明有结构。原因过度差分把序列中的非线性结构也差掉了。解决检查 auto_arima 的差分选择必要时手动指定 d 和 D用 ADF 检验确认差分次数不要完全交给算法。4.5 预测步长增加后误差急剧放大现象预测未来 1 步时混合模型表现很好预测 30 步时误差比 SARIMA 还大。原因LSTM 的残差预测是自回归式的多步预测时误差会累积而 SARIMA 的多步预测有统计框架支撑衰减更可控。解决多步预测时限制 LSTM 只做短期修正或者改用 seq2seq 结构直接输出多步残差预测而不是逐步滚动。5. 进阶技巧用滚动预测和残差诊断把混合模型压到极限混合模型上线后真正决定它能不能持续用的是滚动预测机制和残差诊断习惯。滚动预测指的是每次拿到新数据后用最近的窗口重新拟合 SARIMA 和 LSTM而不是用一次训练好的模型一直预测。我一般设一个 90 天的滚动窗口每 7 天重新拟合一次 SARIMALSTM 每 30 天重新训练一次。SARIMA 重拟合快LSTM 重训练慢两者频率不同步是正常的。残差诊断是我每次跑完混合模型必做的一步。除了 Ljung-Box还会看残差的 ACF 图和 QQ 图。如果 ACF 在某个滞后阶数上还有显著尖峰说明 SARIMA 的阶数没选够或者 LSTM 的 lookback 没覆盖到那个周期。QQ 图偏离正态说明残差中有厚尾LSTM 对这种脉冲的预测会偏保守可以考虑在损失函数里用 Huber loss 替代 MSE。# 滚动预测框架 def rolling_forecast(series, window90, sarima_refit7, lstm_refit30): predictions [] for i in range(window, len(series)): train series.iloc[i - window:i] # 每 sarima_refit 步重新拟合 SARIMA if (i - window) % sarima_refit 0: sarima_model SARIMAX(train, orderorder, seasonal_orderseasonal_order).fit(dispFalse) sarima_pred sarima_model.forecast(steps1).values[0] # LSTM 残差预测简化示意实际需维护残差窗口 resid_window train.values - sarima_model.fittedvalues.values # ... LSTM 预测逻辑 ... predictions.append(sarima_pred) # 加上 LSTM 修正 return np.array(predictions)这段框架的核心是SARIMA 按固定频率重拟合LSTM 按另一频率重训练两者解耦。实际项目中我会把 SARIMA 的阶数和 LSTM 的权重都存下来每次重拟合时用上一轮的参数做 warm start减少搜索时间。还有一个容易被忽略的技巧对残差做季节性分解。如果 SARIMA 的残差 ACF 在 lag7 或 lag12 上还有尖峰说明季节项没被完全捕获可以在 LSTM 输入里额外加入星期几或月份的 one-hot 编码让 LSTM 显式感知周期位置。这个改动在我做过的两个项目里都把 RMSE 又压低了 8% 到 12%。最后说一个血泪教训混合模型不是越复杂越好。我早期试过 SARIMA LSTM XGBoost 三层堆叠结果因为每层都在拟合前层的残差误差反而被放大。后来固定成 SARIMA LSTM 两层把精力花在残差诊断和滚动机制上效果稳定得多。混合模型的价值在于职责分离不在于层数堆叠。希望帮到你。本文还有配套的精品资源点击获取