
简介这份资源面向计算机、电子信息工程、数学等专业的大学生及算法入门者提供SARIMA-LSTM组合模型的完整时间序列预测实现方案可用于课程设计、期末大作业与毕业设计。模型将季节性差分自回归移动平均与长短期记忆神经网络结合兼顾线性趋势与非线性残差建模适合电力负荷、气象、交通流量等带季节性的单变量预测场景。压缩包共3个文件包含2个CSV数据集与1个Python源码文件整体约51KB数据文件用于训练与验证源码文件承载建模全流程。代码采用参数化编程参数修改方便且配有保姆级注释几乎一行一注释便于零基础读者理解每一步建模逻辑。目前已有742人学习下载作者为资深算法工程师具备多年Matlab与Python算法仿真经验。读者可据此掌握从数据读取、平稳性检验、SARIMA定阶到LSTM残差修正的完整链路并快速迁移到自己的预测任务中。1. 从一条震荡曲线说起SARIMA-LSTM 到底在解决什么很多人第一次做销量、流量、电力负荷这类预测都会经历同一个翻车现场拿 LSTM 直接怼原始序列训练集 loss 降得漂漂亮亮一到验证集就发散预测曲线要么一条直线要么滞后好几拍。问题往往不在网络而在序列里那层没被剥掉的季节性和趋势——LSTM 擅长记局部非线性模式却很难自己把「每年 7 月必涨、每季度末必冲高」这种周期结构从噪声里稳定分离出来。SARIMA-LSTM 组合模型就是冲着这个痛点来的先用 SARIMA 把线性可解释的部分趋势、季节差分后的自回归移动平均结构拟合掉再把残差交给 LSTM 去啃非线性成分最后把两段预测相加。它适合手里有单变量、带明显季节周期、样本量中等几百到几千个点的从业者比如零售销量、日活、设备温度、区域用电量。Python 生态里 statsmodels 负责 SARIMATensorFlow/Keras 或 PyTorch 负责 LSTM整条链路不需要 GPU 也能跑通。下面按「先立住原理、再动手复现、最后避坑」的顺序拆开讲。2. SARIMA 与 LSTM 的分工为什么不是二选一2.1 线性部分交给 SARIMA 的三个理由时间序列的经典分解是「趋势 季节 残差」。SARIMA(p,d,q)(P,D,Q)m 里的参数含义很直白p、q 管非季节的自回归和移动平均阶数d 是普通差分次数P、Q 管季节项D 是季节差分次数m 是周期长度月度数据 m12周数据 m7日数据按业务定 7 或 365。它本质是一个线性滤波器把能被历史值线性解释的部分抽走。为什么不让 LSTM 全包三个现实原因。第一LSTM 对长周期比如 m12 的季节需要很长的记忆窗口才能捕捉序列一长梯度就难传训练成本陡增。第二SARIMA 的参数有统计检验兜底ACF/PACF 图、ADF 检验、Ljung-Box 残差白噪声检验能让你判断「线性部分到底抽干净没有」这是黑箱网络给不了的。第三残差如果已经是白噪声说明线性结构被吃透了LSTM 只需处理剩下的非线性尾巴任务更轻、更稳。常见做法是先对原始序列做平稳性检验不平稳就差分直到 ADF 检验 p 值小于 0.05再用 ACF/PACF 或网格搜索定阶拟合后看残差是否接近白噪声。这一步做扎实后面 LSTM 的输入质量才有保证。2.2 残差为什么还值得再喂给 LSTMSARIMA 抽走线性成分后残差里通常还剩两类东西一是非线性交互比如促销叠加节假日带来的突变二是异方差波动幅度随时间变化。这两类正是 LSTM 的强项。LSTM 的门控结构遗忘门、输入门、输出门能选择性地记住长距离依赖对残差序列里的非线性模式比线性模型敏感得多。但要注意一个前提残差必须是「有结构可学」的。如果 SARIMA 残差已经通过白噪声检验理论上没有线性自相关但非线性自相关仍可能存在这时 LSTM 才有发挥空间。实操中我会先画残差的 ACF 和时序图如果残差看起来就是纯随机抖动硬上 LSTM 只会过拟合不如直接用 SARIMA。2.3 组合方式残差建模还是并联加权两种主流组合方式。第一种是串联残差建模SARIMA 预测线性部分LSTM 预测 SARIMA 残差最终预测 SARIMA 预测 LSTM 残差预测。这种方式逻辑清晰、易调试是绝大多数落地项目的选择。第二种是并联加权SARIMA 和 LSTM 各自对原始序列建模再按权重融合权重可以用误差倒数或简单平均。并联方式实现简单但两个模型都在拟合同一层信息容易重复劳动解释性也差。我一般选串联。原因是它把「线性」和「非线性」的职责切得干净出问题时能快速定位是 SARIMA 定阶不对还是 LSTM 过拟合。下面第 3 章就按串联方式给出完整可复现的代码链路。3. 用 Python 跑通 SARIMA-LSTM 的最小链路3.1 数据准备与平稳性检验先造一份带趋势和季节的模拟数据方便你验证每一步是否符合预期。真实项目里把data换成你的 CSV 列即可。import numpy as np import pandas as pd import matplotlib.pyplot as plt from statsmodels.tsa.stattools import adfuller from statsmodels.graphics.tsaplots import plot_acf, plot_pacf np.random.seed(42) n 240 # 20年月度数据 t np.arange(n) trend 0.05 * t season 10 * np.sin(2 * np.pi * t / 12) noise np.random.normal(0, 2, n) data trend season noise series pd.Series(data, indexpd.date_range(2005-01-01, periodsn, freqMS)) # 平稳性检验 def adf_test(s, nameseries): result adfuller(s.dropna(), autolagAIC) print(f{name} ADF统计量: {result[0]:.4f}, p值: {result[1]:.4f}) return result[1] adf_test(series, 原始序列) # 一阶差分后再检验 adf_test(series.diff().dropna(), 一阶差分) # 季节差分后再检验 adf_test(series.diff(12).dropna(), 季节差分)这段代码先做三次 ADF 检验原始序列、一阶差分、季节差分。判断标准是 p 值小于 0.05 才算平稳。模拟数据里原始序列带趋势和季节p 值通常很大一阶差分能去掉趋势季节差分去掉周期。真实数据里如果一阶差分后仍不平稳就叠加季节差分对应 SARIMA 的 d 和 D 参数。注意freqMS表示月初月度数据必须显式指定频率否则 statsmodels 会报警告甚至定阶出错。3.2 SARIMA 定阶与拟合定阶有两种路子看 ACF/PACF 图人工判断或用网格搜索按 AIC/BIC 选最优。人工判断快但主观网格搜索慢但可复现。我一般先用图定个大致范围再在小范围网格里搜。import itertools import warnings from statsmodels.tsa.statespace.sarimax import SARIMAX warnings.filterwarnings(ignore) p d q range(0, 3) P D Q range(0, 2) m 12 pdq list(itertools.product(p, d, q)) seasonal_pdq [(x[0], x[1], x[2], m) for x in itertools.product(P, D, Q)] best_aic np.inf best_order None best_seasonal None for order in pdq: for s_order in seasonal_pdq: try: model SARIMAX(series, orderorder, seasonal_orders_order, enforce_stationarityFalse, enforce_invertibilityFalse) result model.fit(dispFalse) if result.aic best_aic: best_aic result.aic best_order order best_seasonal s_order except Exception: continue print(f最优非季节阶数: {best_order}) print(f最优季节阶数: {best_seasonal}) print(f最优AIC: {best_aic:.2f})网格搜索遍历所有 p,d,q 在 0 到 2、P,D,Q 在 0 到 1 的组合用 AIC 选最优。参数说明enforce_stationarityFalse和enforce_invertibilityFalse是为了避免部分参数组合直接报错让搜索能跑完dispFalse关掉迭代日志。真实数据上这个双重循环可能跑几分钟建议先把范围缩小到 ACF/PACF 提示的阶数附近。选出的best_order和best_seasonal就是 SARIMA 的最终配置。3.3 提取残差并构造 LSTM 监督样本SARIMA 拟合完残差就是 LSTM 的输入。残差要重新标准化并做成滑动窗口监督样本。from sklearn.preprocessing import MinMaxScaler # 用最优参数重新拟合 final_model SARIMAX(series, orderbest_order, seasonal_orderbest_seasonal, enforce_stationarityFalse, enforce_invertibilityFalse) final_result final_model.fit(dispFalse) # 提取残差 resid final_result.resid resid resid[12:] # 去掉季节差分带来的前12个缺失值 # 标准化 scaler MinMaxScaler(feature_range(0, 1)) resid_scaled scaler.fit_transform(resid.values.reshape(-1, 1)).flatten() # 构造滑动窗口 def create_dataset(data, look_back12): X, y [], [] for i in range(len(data) - look_back): X.append(data[i:i look_back]) y.append(data[i look_back]) return np.array(X), np.array(y) look_back 12 X, y create_dataset(resid_scaled, look_back) X X.reshape((X.shape[0], X.shape[1], 1)) print(f样本形状: X{X.shape}, y{y.shape})resid[12:]这一步容易漏季节差分会让前 12 个残差为缺失值不切掉会污染训练。look_back12表示用过去 12 个残差预测下一个和月度周期对齐。标准化用 MinMaxScaler 而不是 StandardScaler是因为 LSTM 默认 tanh/sigmoid 激活对 0 到 1 区间更友好。样本形状必须是三维(样本数, 时间步, 特征数)这是 Keras LSTM 的硬性要求少一维直接报错。3.4 搭建 LSTM 并训练网络不用堆太深两层 LSTM 加 Dropout 足够处理残差这种低维序列。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(look_back, 1)), Dropout(0.2), LSTM(32, return_sequencesFalse), Dropout(0.2), Dense(1) ]) model.compile(optimizeradam, lossmse) early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) history model.fit(X, y, epochs100, batch_size16, validation_split0.2, callbacks[early_stop], verbose0)第一层 LSTM 64 单元、return_sequencesTrue把序列传给下一层第二层 32 单元、return_sequencesFalse只输出最后一步。Dropout 0.2 防过拟合残差序列噪声大时这个值很关键。EarlyStopping 监控验证 losspatience10 表示连续 10 轮不降就停并回滚最优权重这是防止过拟合的后悔药。batch_size 16 适合几百到几千样本的规模样本上万可以调到 32 或 64。3.5 预测还原与误差评估LSTM 输出的是标准化残差预测要反标准化后加回 SARIMA 预测才是最终结果。from sklearn.metrics import mean_absolute_error, mean_squared_error # SARIMA 预测 sarima_pred final_result.get_forecast(stepslen(y)).predicted_mean.values # LSTM 残差预测 lstm_pred_scaled model.predict(X, verbose0).flatten() lstm_pred scaler.inverse_transform(lstm_pred_scaled.reshape(-1, 1)).flatten() # 组合预测 final_pred sarima_pred lstm_pred # 真实值对齐 true_values series.values[look_back 12: look_back 12 len(final_pred)] mae mean_absolute_error(true_values, final_pred) rmse np.sqrt(mean_squared_error(true_values, final_pred)) print(fMAE: {mae:.4f}, RMSE: {rmse:.4f})对齐是这里最容易翻车的地方。SARIMA 的get_forecast从序列末尾往后预测LSTM 的样本从残差第look_back个开始而残差又切掉了前 12 个所以真实值起点是look_back 12。三个偏移量任何一个算错误差指标就完全失真。建议先打印三段序列的长度和首尾值核对再算 MAE/RMSE。评估时除了这两个指标还要看预测曲线和真实曲线的形状是否贴合光看数值会被平均效应骗过去。4. 避坑与排查SARIMA-LSTM 落地最常见的 5 个问题4.1 残差没切季节差分的缺失值LSTM 学了个寂寞现象LSTM 训练 loss 正常下降但组合预测误差比单用 SARIMA 还大。原因季节差分后残差前 m 个值是 NaN直接喂进网络会被填充成 0 或报错模型学到的是被污染的模式。解决拟合后立刻resid resid[m:]并在构造监督样本前确认np.isnan(resid).sum() 0。这一步我在三个项目里都见过有人漏掉。4.2 定阶网格太大跑到一半内存爆了现象网格搜索循环跑到一半进程被 kill或耗时几小时。原因p,d,q 各取 0 到 3、P,D,Q 各取 0 到 2组合数上百每个都要拟合一次 SARIMAX内存和时间都吃不消。解决先用 ACF/PACF 把范围缩到 p,q ≤ 2、P,Q ≤ 1d 和 D 由平稳性检验直接定死不参与搜索。这样组合数能压到几十个几分钟出结果。4.3 LSTM 过拟合训练集完美验证集发散现象训练 loss 降到很低验证 loss 先降后升预测曲线剧烈震荡。原因残差序列本身信噪比低网络容量过大或训练轮数过多。解决加 Dropout0.2 到 0.4、加 EarlyStopping、减小 LSTM 单元数64 降到 32、增大 batch_size。如果还不行说明残差里已经没有可学的非线性结构老老实实退回 SARIMA。4.4 预测对齐错位误差指标全是假的现象MAE 小得离谱或大得离谱和曲线图对不上。原因SARIMA 预测、LSTM 预测、真实值三者的时间起点没对齐偏移量算错。解决在代码里显式打印三者的长度和首尾时间戳用look_back m这个偏移公式核对。真实项目里建议把索引统一成 DatetimeIndex 再切片比纯位置切片可靠。4.5 数据频率没指定statsmodels 定阶结果飘忽现象同样的数据两次运行选出的最优阶数不一样。原因pd.Series没设freqstatsmodels 内部按整数索引处理季节周期 m 对不上真实时间间隔。解决读数据时用pd.read_csv(..., parse_dates[date], index_coldate)再asfreq(MS)或asfreq(D)显式声明频率。频率一确定季节周期才有意义定阶结果才可复现。5. 把组合模型用稳的两个进阶技巧第一个技巧是滚动预测代替一次性预测。上面第 3 章的评估是一次性预测未来 N 步真实业务里更常见的是滚动预测每次只预测下一步把真实值追加进历史再预测下一步。这样能模拟上线后的真实表现也能暴露模型在长跨度上的衰减。实现上把get_forecast(steps1)放进循环每轮更新 SARIMA 的append和 LSTM 的输入窗口即可。代价是计算量成倍增加但评估结果可信得多。第二个技巧是给组合加一个简单加权系数。串联模型默认 SARIMA 和 LSTM 权重各为 1但实际中残差预测往往偏弱直接相加可能放大误差。我一般会在验证集上搜一个系数 α最终预测 SARIMA 预测 α × LSTM 残差预测α 在 0 到 1 之间按 RMSE 最小选。α 接近 0 说明 LSTM 没贡献该退回单模型α 接近 1 说明残差非线性强组合有价值。这个系数不用交叉验证那么重留一段验证集网格搜就行。技巧适用场景关键参数代价滚动预测上线前评估、长跨度预测步长1窗口更新方式计算量成倍加权组合残差预测偏弱或偏强α ∈ [0,1]按 RMSE 选需留验证集最后说个我自己的习惯每次跑完组合模型我都会把 SARIMA 单独预测、LSTM 单独预测、组合预测三条曲线画在一张图上再叠上真实值。如果组合曲线没有明显比 SARIMA 单模型更贴我就不上组合宁可要一个简单可解释的模型。这套流程我在销量和负荷预测上复用过多次最深的教训是——SARIMA 那一步的平稳性检验和残差白噪声检验千万别省省下来的时间最后都会在调 LSTM 的时候加倍还回去。希望帮到你。本文还有配套的精品资源点击获取