资讯动态

Python实现ARIMA-LSTM组合模型:时间序列预测完整源码与避坑指南

发布时间:2026/10/12 20:33:09 来源:尧图企业网站定制
简介这份资源面向计算机、电子信息工程、数学等专业的大学生及算法入门者提供ARIMA-LSTM组合时间序列预测的完整Python实现方案可用于课程设计、期末大作业或毕业设计。压缩包共3个文件包含1个py源码与2个csv数据集整体约48KB源码基于Anaconda、PyCharm与TensorFlow环境编写采用参数化编程思路参数可灵活调整并配有保姆级注释几乎一行一注释便于零基础读者理解ARIMA与LSTM的融合建模流程。数据集可直接用于训练与验证帮助读者快速跑通从数据读取、差分平稳化、残差建模到LSTM预测的完整链路。目前已有1642人学习下载作者为某大厂资深算法工程师具备八年Matlab与Python算法仿真经验擅长智能优化算法、神经网络预测与信号处理等方向能为读者提供清晰的排错思路与代码结构参考。1. ARIMA-LSTM 组合模型单靠一个模型总差一口气问题出在哪销量预测、电力负荷、GDP 走势这类带明显周期又夹杂突变的序列单用 ARIMA 或单用 LSTM 都容易在某个区间翻车。ARIMA 擅长抓线性趋势和季节项残差里剩下的非线性波动它管不了LSTM 能拟合复杂非线性但对平稳趋势的建模不如统计模型稳。把两者串起来让 ARIMA 先吃掉线性部分LSTM 再对残差做二次建模是工业界做时间序列预测时最常见、也最容易复现的混合方案。这篇笔记围绕 Python 实现 ARIMA-LSTM 时间序列预测的完整源码和数据展开从环境搭建、数据预处理、两个模型的接口对齐到残差拼接和评估每一步都给可抄的命令和参数。适合已经会写 Python、想把这套组合模型真正跑通并落到自己数据集上的从业者新手跟着步骤也能走完。2. 环境与数据把 ARIMA-LSTM 跑起来前必须对齐的几件事2.1 依赖安装与版本约束这套方案依赖 statsmodels 做 ARIMA、TensorFlow 或 PyTorch 做 LSTM、pandas 和 numpy 做数据处理。statsmodels 的 ARIMA 接口在 0.12 之后从ARIMA换成了SARIMAX网上很多老代码还在用旧接口直接抄会报AttributeError。我一般固定用下面这套组合避免版本玄学。pip install numpy1.24.3 pandas2.0.3 statsmodels0.14.0 pip install scikit-learn1.3.0 tensorflow2.13.0 matplotlib3.7.2逻辑说明numpy 锁 1.24 是因为 TensorFlow 2.13 对 numpy 2.x 兼容性差装最新版会在 import 阶段就报_ARRAY_API not found。statsmodels 0.14 的SARIMAX已经稳定支持order参数直接传 ARIMA 阶数。scikit-learn 只用来做归一化和评估指标不参与建模。参数说明如果机器没有 GPUTensorFlow 会自动回落到 CPU训练速度慢但结果一致。用 PyTorch 的话把最后一行换成torch2.0.1LSTM 层写法不同但拼接逻辑完全一样。2.2 数据格式与平稳性检查ARIMA 对输入序列的平稳性有硬要求LSTM 虽然不强制但归一化后训练更稳。假设数据是一列带时间索引的 CSV字段为date和value先做差分和 ADF 检验。import pandas as pd import numpy as np from statsmodels.tsa.stattools import adfuller df pd.read_csv(data/series.csv, parse_dates[date], index_coldate) series df[value].astype(float) # 一阶差分 diff_series series.diff().dropna() # ADF 检验p 值小于 0.05 认为平稳 result adfuller(diff_series) print(fADF Statistic: {result[0]:.4f}) print(fp-value: {result[1]:.4f})逻辑说明ADF 检验的原假设是序列存在单位根非平稳。p 值小于 0.05 才能拒绝原假设说明差分后平稳可以喂给 ARIMA。如果 p 值还是很大说明需要二阶差分或者序列有结构性断点这时候硬上 ARIMA 会得到一堆无效参数。参数说明diff()默认一阶差分dropna()去掉第一个 NaN。ADF 的autolag默认用 AIC 自动选滞后阶数一般不用改。差分次数记为d后面 ARIMA 的 order 里要用。2.3 训练集与测试集的切分方式时间序列不能随机切分必须按时间顺序切。常见做法是留最后 20% 做测试但 ARIMA-LSTM 有个坑LSTM 需要构造滑动窗口切分点如果正好落在窗口中间测试集第一个样本会用到训练集末尾的数据造成信息泄漏。train_size int(len(series) * 0.8) train, test series[:train_size], series[train_size:] # 归一化只用训练集的 min/max避免测试集信息泄漏 from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) train_scaled scaler.fit_transform(train.values.reshape(-1, 1)) test_scaled scaler.transform(test.values.reshape(-1, 1))逻辑说明fit_transform只在训练集上调用测试集用transform这样 scaler 的参数完全来自训练集。如果对全量数据做fit_transform测试集的极值会污染归一化范围评估结果虚高。参数说明feature_range默认 (0,1)LSTM 的 tanh 激活在 0 附近梯度最好所以这个范围够用。切分比例 0.8 是经验值序列短于 200 个点时建议 0.7否则测试集样本太少评估不可靠。3. ARIMA 建模定阶、拟合与残差提取的完整链路3.1 用 ACF/PACF 和网格搜索定阶ARIMA 的 order 是 (p, d, q)d 已经由差分次数确定p 和 q 靠 ACF 和 PACF 图初判再用 AIC 网格搜索精调。纯靠肉眼看图容易定偏我一般直接跑一个小范围网格。import warnings from statsmodels.tsa.arima.model import ARIMA warnings.filterwarnings(ignore) best_aic np.inf best_order None for p in range(0, 4): for q in range(0, 4): try: model ARIMA(train, order(p, 1, q)) fitted model.fit() if fitted.aic best_aic: best_aic fitted.aic best_order (p, 1, q) except Exception: continue print(fBest order: {best_order}, AIC: {best_aic:.2f})逻辑说明d1来自前面的差分检验。循环里用 try-except 是因为某些 (p,q) 组合会导致模型不收敛直接跳过。AIC 越小说明模型在拟合优度和复杂度之间平衡得越好。参数说明p 和 q 的范围 0 到 3 覆盖大多数场景序列周期性强的话可以扩到 5但计算量会明显上升。ARIMA的fit()默认用最大似然数据量大时可以用methodcss加速但精度略降。3.2 拟合、预测与残差序列的生成定好阶之后用全量训练集拟合然后对训练集做一步预测预测值和真实值的差就是残差。这个残差序列是 LSTM 的输入。final_model ARIMA(train, orderbest_order) final_fitted final_model.fit() # 训练集上的拟合值in-sample prediction train_pred final_fitted.predict(starttrain.index[0], endtrain.index[-1]) residuals train - train_pred # 测试集预测 test_pred final_fitted.forecast(stepslen(test)) test_residuals test - test_pred.values逻辑说明predict做样本内预测得到每个训练点的拟合值残差就是真实值减拟合值。forecast做样本外预测步长等于测试集长度。注意test_pred返回的是 pandas Series和test相减时索引要对齐否则会得到一堆 NaN。参数说明forecast(stepsn)是静态预测每一步都用模型参数算不滚动更新。如果要做滚动预测得用append逐点更新模型计算量大但更贴近实盘。残差序列residuals要检查是否还有自相关用 Ljung-Box 检验p 值大于 0.05 说明 ARIMA 已经把线性部分抽干净了。3.3 残差的白噪声检验残差如果还是白噪声LSTM 就学不到东西。这一步很多人跳过结果 LSTM 训练半天 loss 不降回头查才发现残差本身没规律。from statsmodels.stats.diagnostic import acorr_ljungbox lb_test acorr_ljungbox(residuals, lags[10], return_dfTrue) print(lb_test)逻辑说明Ljung-Box 检验残差在 lag10 内是否联合为白噪声。p 值小于 0.05 说明残差还有自相关LSTM 有得学p 值大于 0.05 说明 ARIMA 已经抽干了这时候上 LSTM 意义不大得回头检查 ARIMA 的阶数是不是选低了。参数说明lags一般取 10 到 20取决于序列周期。如果序列有 12 个月的周期lag 至少取 12。这个检验不是必须的但能帮你判断组合模型是否值得做。4. LSTM 残差建模滑动窗口、网络结构与训练参数4.1 滑动窗口构造与批次生成LSTM 的输入是三维张量(样本数, 时间步, 特征数)。残差序列是一维的需要用滑动窗口切成样本。窗口长度决定 LSTM 能看多远的过去。def create_sequences(data, window_size): X, y [], [] for i in range(len(data) - window_size): X.append(data[i:i window_size]) y.append(data[i window_size]) return np.array(X), np.array(y) window_size 10 residuals_scaled scaler.fit_transform(residuals.values.reshape(-1, 1)) X_train, y_train create_sequences(residuals_scaled, window_size) X_train X_train.reshape((X_train.shape[0], X_train.shape[1], 1))逻辑说明create_sequences把一维序列切成(N-window, window)的矩阵标签是窗口后一个点。reshape把二维转成三维第三维是特征数单变量就是 1。残差单独做一次归一化因为残差的量纲和原始序列不同。参数说明window_size取 10 是经验起点序列周期性强的话取一个周期长度比如月度数据取 12。窗口太大 LSTM 训练慢且容易过拟合太小则学不到长期依赖。样本数少于 500 时窗口别超过 20。4.2 LSTM 网络结构与编译参数网络不需要太深两层 LSTM 加一层全连接足够处理残差这种低维信号。层数多了反而在小数据上过拟合。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam model Sequential([ LSTM(64, return_sequencesTrue, input_shape(window_size, 1)), Dropout(0.2), LSTM(32, return_sequencesFalse), Dropout(0.2), Dense(1) ]) model.compile(optimizerAdam(learning_rate0.001), lossmse) model.summary()逻辑说明第一层 LSTM 设return_sequencesTrue把序列传给下一层第二层设 False 只输出最后一步。Dropout 放在两层之间防止过拟合。最后一层 Dense(1) 输出单值预测。参数说明第一层 64 个单元、第二层 32 个是残差建模的常用配置。学习率 0.001 是 Adam 的默认值loss 用 mse 因为残差是连续值。如果训练 loss 震荡把学习率降到 0.0005如果 loss 下降太慢加到 0.002 试试。4.3 训练过程与早停策略残差序列通常不长训练轮数不用太多配合早停防止过拟合。from tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) history model.fit( X_train, y_train, epochs100, batch_size32, validation_split0.1, callbacks[early_stop], verbose1 )逻辑说明validation_split0.1从训练集末尾切 10% 做验证不碰测试集。EarlyStopping监控验证 loss连续 10 轮不降就停并恢复最优权重。这样不用手动调 epochs。参数说明batch_size32适合几百到几千个样本样本少时降到 16。patience10是经验值数据噪声大可以加到 15。训练完把history.history[loss]和val_loss画出来两条线如果分叉明显说明过拟合得加 Dropout 或减层数。5. 组合预测与评估把两条预测结果拼回去5.1 残差预测与反归一化LSTM 输出的是归一化后的残差预测值要先反归一化再加回 ARIMA 的预测值。# 测试集残差预测 test_residuals_scaled scaler.transform(test_residuals.values.reshape(-1, 1)) X_test, y_test create_sequences(test_residuals_scaled, window_size) X_test X_test.reshape((X_test.shape[0], X_test.shape[1], 1)) lstm_pred_scaled model.predict(X_test) lstm_pred scaler.inverse_transform(lstm_pred_scaled).flatten() # 对齐长度ARIMA 预测从 window_size 之后开始拼 final_pred test_pred.values[window_size:] lstm_pred actual test.values[window_size:]逻辑说明测试集残差也要用训练集的 scaler 做transform不能用fit_transform。LSTM 预测的长度比测试集少window_size个点所以 ARIMA 的预测值也要从window_size处切。拼接时注意顺序ARIMA 预测的是趋势LSTM 预测的是残差修正。参数说明inverse_transform把 0-1 范围还原成原始量纲。如果拼接后结果偏差很大先检查test_pred和lstm_pred的索引是否对齐这是最常见的翻车点。5.2 评估指标与对比基线光看组合模型的误差不够要和单 ARIMA、单 LSTM 对比才知道组合到底有没有用。from sklearn.metrics import mean_absolute_error, mean_squared_error def evaluate(actual, pred, name): mae mean_absolute_error(actual, pred) rmse np.sqrt(mean_squared_error(actual, pred)) mape np.mean(np.abs((actual - pred) / actual)) * 100 print(f{name} - MAE: {mae:.4f}, RMSE: {rmse:.4f}, MAPE: {mape:.2f}%) evaluate(actual, final_pred, ARIMA-LSTM) evaluate(actual, test_pred.values[window_size:], ARIMA only)逻辑说明MAE 和 RMSE 衡量绝对误差MAPE 衡量相对误差。三个指标一起看避免某个指标被极值带偏。和单 ARIMA 对比时测试集长度要对齐都从window_size处开始。参数说明MAPE 在真实值接近 0 时会爆炸如果序列有零值或接近零的值改用 SMAPE。RMSE 对大误差更敏感MAE 更稳健两个都报。5.3 预测结果可视化画图不是为了好看是为了看组合模型在哪些区间修正了 ARIMA 的偏差。import matplotlib.pyplot as plt plt.figure(figsize(12, 5)) plt.plot(actual, labelActual, colorblack) plt.plot(test_pred.values[window_size:], labelARIMA, linestyle--) plt.plot(final_pred, labelARIMA-LSTM, linestyle-) plt.legend() plt.title(ARIMA vs ARIMA-LSTM) plt.tight_layout() plt.savefig(output/prediction.png, dpi150)逻辑说明黑线是真实值虚线是单 ARIMA实线是组合模型。重点看组合模型在拐点和突变处是否比 ARIMA 更贴近真实值。如果两条线几乎重合说明残差没有非线性可学组合模型白做。参数说明dpi150保证图片清晰度够放进报告。tight_layout防止标签被截断。保存路径提前建好output目录否则报FileNotFoundError。6. 避坑与排查ARIMA-LSTM 最容易翻车的 5 个点6.1 现象LSTM 训练 loss 不降预测结果是一条直线原因残差序列本身接近白噪声LSTM 学不到任何模式输出退化成均值。或者残差归一化时用了全量数据的 min/max导致训练集和测试集分布不一致。解决先跑 Ljung-Box 检验p 值大于 0.05 就别上 LSTM 了回头调 ARIMA 阶数。归一化严格只用训练集 fit测试集 transform。另外检查残差序列有没有被错误地做成了全零差分和预测的索引对齐很容易出这个问题。6.2 现象组合模型预测值比单 ARIMA 还差原因ARIMA 的样本外预测和 LSTM 的残差预测在时间轴上错位了。forecast(stepsn)返回的索引从测试集起点开始而 LSTM 的预测从window_size之后开始直接相加会把不同时间点的值拼在一起。解决拼接前打印两个序列的 shape 和索引确认对齐。统一从window_size处切或者把 ARIMA 预测也往后推window_size步。这个坑我踩过两次血泪经验是拼接前先 assert 长度相等。6.3 现象测试集 MAPE 异常低但实际预测图惨不忍睹原因测试集里包含接近零的真实值MAPE 的分母极小导致指标虚低。或者归一化时测试集的极值泄漏到了 scaler 里。解决改用 SMAPE 或直接看 RMSE。归一化用fit_transform只在训练集上调用测试集必须用transform。如果序列本身有零值考虑先加一个常数再归一化预测完再减回去。6.4 现象ARIMA 拟合时报LinAlgError: Singular matrix原因差分次数过多导致序列接近常数或者 p 和 q 同时设得太大自回归和移动平均项共线。解决减少差分次数先做 ADF 检验确认 d 的最小值。网格搜索时把 p 和 q 的范围缩小或者加正则化。statsmodels 的SARIMAX有enforce_stationarity参数设 False 可以绕过但结果不可靠不推荐。6.5 现象LSTM 预测结果比真实值滞后一个窗口原因滑动窗口构造时标签取的是data[i window_size]但预测时用的是最后一个窗口导致输出对应的是窗口末尾的下一个点而实际评估时对齐错了。解决检查create_sequences的索引逻辑确保X[i]对应y[i]是窗口后一个点。预测时用测试集最后一个窗口生成第一个预测值然后滚动更新窗口。如果不想滚动至少保证评估时 actual 和 pred 的起始位置一致。7. 进阶技巧用滚动预测和超参搜索把误差再压一压静态预测只跑一次模型滚动预测每预测一个点就把真实值加回历史重新拟合。实盘场景下滚动预测更贴近真实表现代价是计算量成倍增加。我一般先在测试集上跑静态预测看整体趋势如果组合模型确实比单 ARIMA 好再上滚动预测做最终评估。def rolling_forecast(series, train_size, order, window_size, lstm_model, scaler): history list(series[:train_size]) predictions [] for t in range(train_size, len(series)): # ARIMA 滚动拟合 arima_model ARIMA(history, orderorder).fit() arima_pred arima_model.forecast(steps1)[0] # 残差 LSTM 预测 residuals np.array(history) - arima_model.predict(start0, endlen(history)-1).values res_scaled scaler.transform(residuals[-window_size:].reshape(-1, 1)) res_input res_scaled.reshape((1, window_size, 1)) lstm_res scaler.inverse_transform(lstm_model.predict(res_input, verbose0))[0][0] # 组合预测 final arima_pred lstm_res predictions.append(final) history.append(series[t]) return np.array(predictions)逻辑说明每步用当前历史重新拟合 ARIMA取一步预测同时用最近window_size个残差喂给 LSTM 得到残差修正两者相加作为当前步的最终预测然后把真实值追加到历史里进入下一步。这样每一步都用了最新的真实信息。参数说明滚动预测的计算量是静态预测的 N 倍N 是测试集长度。序列超过 1000 个点时建议只在最后 100 个点上滚动。verbose0关掉 LSTM 每步的进度条否则输出刷屏。超参搜索方面ARIMA 的 (p,d,q) 用 AIC 网格已经够用LSTM 的窗口大小、单元数、学习率可以用 Optuna 做小规模搜索。但别指望调参能把误差降一个数量级组合模型的上限由 ARIMA 的线性拟合质量决定LSTM 只是补漏。我自己的习惯是先把 ARIMA 的残差检验做扎实残差确实是白噪声了再考虑 LSTM 的结构否则调参就是浪费时间。数据量少于 500 个点时LSTM 层数别超过两层单元数别超过 64不然过拟合比欠拟合更难处理。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑