1. 为什么要把ARIMA和LSTM“撮合”到一起?

大家好,我是老张,在数据分析和预测这个行当里摸爬滚打了十来年。今天想跟大家聊聊一个特别有意思的话题:怎么把ARIMA和LSTM这两个看似八竿子打不着的模型,给“撮合”到一块儿,让它们联手干活,实现更高精度的时间序列预测。这事儿听起来有点玄乎,但实操下来,效果往往比单独用任何一个模型都要好。

咱们先来打个比方。ARIMA模型就像一位经验丰富的老会计,他特别擅长处理那些有规律可循的账目,比如每个月固定的收入增长趋势、每年重复的季节性波动。他看数据的方式很“传统”,基于严格的数学统计,算出来的结果稳定、可解释,但缺点是比较“死板”,对于数据里那些突如其来的、不按常理出牌的“小脾气”(比如突发的市场波动、非线性变化),老会计可能就有点力不从心了。

而LSTM模型呢,更像是一个嗅觉灵敏、学习能力超强的年轻侦探。它不关心什么固定的公式,而是通过大量的历史数据去“感受”和“学习”其中的复杂模式和长期依赖关系。哪怕数据的变化再诡异、再没有明显规律,这位侦探都有可能从中找出线索。但侦探也有短板,如果数据本身有很强的趋势和季节性(比如老会计最擅长处理的那种),侦探一开始可能得花更长时间去“理解”这些基础规律,有时候甚至会因为过度关注细节而“想多了”,导致预测结果不稳定。

所以你看,一个擅长捕捉线性规律(趋势、季节性),一个擅长挖掘非线性、复杂的长期依赖。如果我们能让老会计先把数据里那些明显的线性趋势和季节性成分给“熨平”了,剩下的、那些难以捉摸的“残差”部分,再交给侦探去深度挖掘,这不就是强强联合吗?这就是ARIMA-LSTM融合模型最核心的思想:用ARIMA处理线性部分,用LSTM攻克非线性残差,实现1+1>2的预测效果

我在好几个实际项目里都试过这种组合拳,比如预测某个城市的空气质量指数(AQI)、电商平台的日销售额、服务器的负载流量。实测下来,尤其是在数据同时包含明显趋势和复杂波动的情况下,融合模型的预测精度,相比单一模型,通常能有比较显著的提升。接下来,我就手把手带你用Python把这一套流程实现出来,从原理到代码,从踩坑到调优,咱们一次聊透。

2. 动手之前:理解核心思想与准备工作

2.1 融合模型的工作流程拆解

在撸起袖子写代码之前,咱们必须把融合模型的整个工作流在脑子里过一遍,这样写代码时才能心中有数。整个流程可以清晰地分为四个阶段:

第一阶段:数据准备与探索 这是所有数据工作的基础。你需要拿到你的时间序列数据,比如每天的温度、每小时的销量。然后,用Pandas加载数据,进行初步的观察:有没有缺失值?需不需要做填充?数据的基本走势如何?强烈建议在这个阶段画几个图,比如折线图观察整体趋势,用季节分解图看看季节性是否明显。这个阶段的目标是了解你的数据,为后续模型选择提供依据。

第二阶段:ARIMA建模与线性部分提取 这是融合模型的关键第一步。我们使用ARIMA模型对原始时间序列进行拟合。ARIMA模型会尽力捕捉数据中的自回归(AR)、差分(I)和移动平均(MA) 成分,说白了就是去拟合数据里那些可以用线性模型描述的部分,比如稳定的上升/下降趋势、固定的周期波动。拟合完成后,ARIMA模型会对历史数据做出一个“预测”(更准确说是“拟合值”)。然后,我们用原始数据减去ARIMA的拟合值,得到的就是残差序列

这个残差序列非常重要!它代表了ARIMA模型“解释不了”的那部分信息。理论上,如果ARIMA模型完美捕捉了所有线性规律,那么残差应该是一个没有明显趋势和季节性的、近似于白噪声的序列。但实际上,残差里往往还蕴藏着复杂的、非线性的模式,而这正是LSTM的用武之地。

第三阶段:LSTM建模学习非线性残差 我们将上一步得到的残差序列,作为新的时间序列,喂给LSTM模型。LSTM是循环神经网络(RNN)的一种,它的核心是“门控机制”,能够学习长期依赖关系。在这一步,我们需要构建LSTM网络,把残差序列按照时间步长(比如用过去10天的残差预测下一天的残差)整理成监督学习的数据格式,然后划分训练集和测试集,训练模型。LSTM的目标是学会从历史残差中预测未来的残差。

第四阶段:结果融合与最终预测 这是收获果实的阶段。对于未来的某个时间点,我们同时进行两个操作:1. 用训练好的ARIMA模型预测出该时间点的线性部分值;2. 用训练好的LSTM模型预测出该时间点的残差值。最后,将这两个预测值简单相加,就得到了融合模型的最终预测结果。最终预测值 = ARIMA预测值 + LSTM残差预测值。这个简单的加法,正是融合思想的直观体现。

2.2 环境搭建与依赖库安装

工欲善其事,必先利其器。咱们这个项目需要用到几个核心的Python库,我建议你创建一个新的虚拟环境来管理依赖,避免和本地其他项目冲突。打开你的终端(或Anaconda Prompt),跟着我一步步来。

# 创建并激活一个名为ts_fusion的虚拟环境(使用conda)
conda create -n ts_fusion python=3.8
conda activate ts_fusion

# 安装核心依赖库
pip install pandas numpy matplotlib seaborn
pip install scikit-learn
pip install statsmodels
pip install tensorflow  # 或者使用 pip install torch 如果你偏爱PyTorch

这里重点说两个库:

  • statsmodels:这是咱们ARIMA模型的主力库,它提供了非常完整的统计模型工具,包括时间序列分析。安装时如果遇到问题,可以试试 pip install statsmodels --user
  • tensorflow:这是实现LSTM的主流深度学习框架之一。如果你的机器没有独立显卡,或者想快速开始,安装CPU版本即可:pip install tensorflow。当然,你也可以选择PyTorch,逻辑是相通的。

安装完成后,可以在Python中导入它们测试一下:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.arima.model import ARIMA
import tensorflow as tf
from tensorflow import keras
print("所有库导入成功!")
print("TensorFlow版本:", tf.__version__)

3. 实战第一步:用ARIMA捕捉线性规律

3.1 数据加载与预处理

咱们用一组模拟的、带有趋势和季节性的销售数据来演示,这样效果更直观。在实际项目中,你只需要把pd.read_csv()这里的路径换成你自己的数据文件就行。

# 生成示例数据:一个带有趋势和季节性的时间序列
np.random.seed(42)
dates = pd.date_range(start='2020-01-01', periods=365*3, freq='D') # 3年的日数据
trend = np.linspace(0, 20, len(dates)) # 线性趋势
seasonality = 10 * np.sin(2 * np.pi * dates.dayofyear / 365) # 年季节性
noise = np.random.normal(0, 2, len(dates)) # 随机噪声
sales = 100 + trend + seasonality + noise # 合成序列

# 创建DataFrame
df = pd.DataFrame({'date': dates, 'sales': sales})
df.set_index('date', inplace=True)

# 查看数据前几行和绘制图形
print(df.head())
plt.figure(figsize=(12, 6))
plt.plot(df.index, df['sales'], label='原始销售额')
plt.title('原始销售数据(含趋势和季节性)')
plt.xlabel('日期')
plt.ylabel('销售额')
plt.legend()
plt.grid(True)
plt.show()

运行这段代码,你会看到一条明显呈上升趋势,并且每年规律波动的曲线。我们的目标就是预测这条曲线未来的走势。

平稳性检验与差分:ARIMA模型要求输入的数据是“平稳”的,即均值和方差不随时间变化。对于有明显趋势的数据,我们需要通过“差分”来消除它。statsmodels提供了方便的adfuller检验(ADF检验)来判断平稳性。

from statsmodels.tsa.stattools import adfuller

# ADF检验函数
def adf_test(timeseries):
    print('ADF检验结果:')
    result = adfuller(timeseries, autolag='AIC')
    print(f'ADF统计量: {result[0]:.4f}')
    print(f'p值: {result[1]:.4f}')
    print('临界值:')
    for key, value in result[4].items():
        print(f'\t{key}: {value:.4f}')
    if result[1] <= 0.05:
        print("-> p值小于0.05,拒绝原假设,数据是平稳的。")
    else:
        print("-> p值大于0.05,无法拒绝原假设,数据是非平稳的。")

print("对原始序列进行ADF检验:")
adf_test(df['sales'])

如果检验结果显示非平稳(p值>0.05),我们就需要进行差分。通常一阶差分就能消除趋势。

# 一阶差分
df['sales_diff'] = df['sales'].diff().dropna()
print("\n对一阶差分序列进行ADF检验:")
adf_test(df['sales_diff'].dropna())

# 绘制差分后的序列
plt.figure(figsize=(12, 6))
plt.plot(df.index[1:], df['sales_diff'].dropna(), label='一阶差分后序列')
plt.title('一阶差分后的销售数据')
plt.xlabel('日期')
plt.ylabel('差分销售额')
plt.legend()
plt.grid(True)
plt.show()

3.2 ARIMA模型定阶、训练与残差提取

确定差分阶数d之后(这里d=1),我们还需要确定自回归阶数p和移动平均阶数q。一个常用的方法是观察自相关图(ACF)和偏自相关图(PACF)

from statsmodels.graphics.tsaplots import plot_acf, plot_pacf

fig, axes = plt.subplots(1, 2, figsize=(12, 4))
plot_acf(df['sales_diff'].dropna(), lags=40, ax=axes[0])
plot_pacf(df['sales_diff'].dropna(), lags=40, ax=axes[1])
plt.show()

通过看图,可以大致判断p和q。但更省事、更精确的方法是使用网格搜索,寻找在评价指标(如AIC)下最优的p, d, q组合。为了节省时间,我这里假设通过初步分析,我们确定了一个相对合理的参数 (p=2, d=1, q=2)。在实际项目中,强烈建议你对一个参数范围进行网格搜索。

# 划分训练集和测试集(最后90天作为测试)
train_size = len(df) - 90
train, test = df['sales'].iloc[:train_size], df['sales'].iloc[train_size:]

# 拟合ARIMA模型
model_arima = ARIMA(train, order=(2, 1, 2))
model_arima_fit = model_arima.fit()
print(model_arima_fit.summary())

查看模型摘要,关注AIC/BIC值(越小越好),以及检查残差是否接近白噪声。接下来,我们要获取ARIMA模型对训练集的拟合值,并计算残差。注意,这里不是预测未来,而是看模型对历史数据的拟合情况。

# 获取训练集上的拟合值(注意,ARIMA(2,1,2)拟合的是差分后的序列,需要还原)
fitted_values = model_arima_fit.fittedvalues
# fittedvalues是差分后的拟合值,需要将其累积求和以还原到原始尺度
# 由于我们做了1阶差分,还原公式为:原始值拟合 ≈ 初始值 + 差分拟合值的累积和
# 更稳健的做法是使用get_prediction方法
arima_train_pred = model_arima_fit.get_prediction(start=train.index[0], end=train.index[-1])
arima_train_pred_values = arima_train_pred.predicted_mean

# 计算残差序列(ARIMA解释不了的部分)
residuals = train - arima_train_pred_values

# 绘制原始训练集、ARIMA拟合值和残差
plt.figure(figsize=(14, 8))
plt.subplot(2,1,1)
plt.plot(train.index, train, label='训练集真实值')
plt.plot(train.index, arima_train_pred_values, label='ARIMA拟合值', alpha=0.8)
plt.title('ARIMA模型在训练集上的拟合效果')
plt.legend()
plt.grid(True)

plt.subplot(2,1,2)
plt.plot(train.index, residuals, label='残差序列', color='red')
plt.axhline(y=0, color='black', linestyle='--', linewidth=0.5)
plt.title('ARIMA模型拟合后的残差序列')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

print("残差序列描述性统计:")
print(residuals.describe())

现在,我们手头有了一个关键的产物:residuals(残差序列)。这个序列看起来应该比原始数据“平缓”很多,没有明显的趋势和季节性。它就是我们要喂给LSTM的“食材”。

4. 实战第二步:用LSTM攻克非线性残差

4.1 将残差序列转换为LSTM可用的格式

LSTM是神经网络,它需要数据以“样本-标签”的形式输入。对于时间序列,我们通常采用滑动窗口的方法来构建特征和标签。例如,用过去N个时间点的残差值,来预测下一个时间点的残差值。

def create_dataset(data, time_steps=1):
    """
    将时间序列转换为监督学习数据集。
    data: 输入序列(如残差序列)
    time_steps: 用过去多少个时间点预测下一个点
    """
    X, y = [], []
    for i in range(len(data) - time_steps):
        X.append(data[i:(i + time_steps)])
        y.append(data[i + time_steps])
    return np.array(X), np.array(y)

# 准备LSTM数据
TIME_STEPS = 10 # 这是一个超参数,表示用过去10天的残差预测第11天
X_full, y_full = create_dataset(residuals.values, TIME_STEPS)

# 划分训练集和验证集(注意:这里我们仍在用训练集产生的残差)
val_size = int(len(X_full) * 0.2)
X_train_lstm, X_val_lstm = X_full[:-val_size], X_full[-val_size:]
y_train_lstm, y_val_lstm = y_full[:-val_size], y_full[-val_size:]

print(f'LSTM训练集形状: X={X_train_lstm.shape}, y={y_train_lstm.shape}')
print(f'LSTM验证集形状: X={X_val_lstm.shape}, y={y_val_lstm.shape}')

LSTM对输入数据的尺度比较敏感,通常我们需要进行归一化,将数据缩放到一个较小的范围(如0到1之间),这能加速模型训练并提升稳定性。

from sklearn.preprocessing import MinMaxScaler

# 归一化
scaler = MinMaxScaler(feature_range=(0, 1))
# 注意:只使用训练集的数据来拟合scaler,避免数据泄露
scaler.fit(X_train_lstm.reshape(-1, 1)) # 将三维数据展平以拟合

X_train_scaled = scaler.transform(X_train_lstm.reshape(-1, 1)).reshape(X_train_lstm.shape)
X_val_scaled = scaler.transform(X_val_lstm.reshape(-1, 1)).reshape(X_val_lstm.shape)

# 标签也需要归一化(使用同一个scaler)
y_train_scaled = scaler.transform(y_train_lstm.reshape(-1, 1)).flatten()
y_val_scaled = scaler.transform(y_val_lstm.reshape(-1, 1)).flatten()

4.2 构建、训练与调优LSTM模型

现在来搭建我们的LSTM网络。网络结构不需要太复杂,一个或两个LSTM层加上全连接层通常就能取得不错的效果。

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
from tensorflow.keras.callbacks import EarlyStopping

# 构建模型
model_lstm = Sequential()
# 第一层LSTM,设置return_sequences=True以便堆叠下一层LSTM
model_lstm.add(LSTM(units=50, return_sequences=True, input_shape=(TIME_STEPS, 1)))
model_lstm.add(Dropout(0.2)) # Dropout层防止过拟合
# 第二层LSTM
model_lstm.add(LSTM(units=50, return_sequences=False))
model_lstm.add(Dropout(0.2))
# 全连接输出层
model_lstm.add(Dense(units=1))

# 编译模型
model_lstm.compile(optimizer='adam', loss='mean_squared_error')

# 打印模型结构
model_lstm.summary()

这里有几个关键点:

  • units=50:这是LSTM层的神经元数量,是一个重要的超参数,可以调整(如30, 100)。
  • Dropout(0.2):在训练过程中随机“丢弃”20%的神经元,是防止模型在训练集上过拟合的有效手段。
  • input_shape=(TIME_STEPS, 1):输入形状为(时间步长, 特征数)。我们这里每个时间步只有一个特征(残差值)。
  • optimizer='adam':Adam优化器在大多数情况下表现良好。
  • loss='mean_squared_error':对于回归预测问题,均方误差是常用的损失函数。

接下来,我们加入EarlyStopping回调函数,它可以在验证集损失不再下降时自动停止训练,防止过拟合,并保存训练过程中验证集上表现最好的模型。

# 设置早停
early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)

# 训练模型
history = model_lstm.fit(
    X_train_scaled, y_train_scaled,
    epochs=100, # 设置一个较大的epoch,让早停机制发挥作用
    batch_size=32,
    validation_data=(X_val_scaled, y_val_scaled),
    callbacks=[early_stop],
    verbose=1 # 显示训练进度条
)

# 绘制训练损失和验证损失曲线
plt.figure(figsize=(10, 5))
plt.plot(history.history['loss'], label='训练损失')
plt.plot(history.history['val_loss'], label='验证损失')
plt.title('LSTM模型训练过程')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True)
plt.show()

观察损失曲线,理想情况是训练损失和验证损失都稳步下降,并最终趋于平稳。如果训练损失持续下降而验证损失开始上升,说明模型可能过拟合了,需要增加Dropout比例、减少网络复杂度或获取更多数据。

5. 实战第三步:融合预测与效果评估

5.1 在测试集上进行多步预测

模型训练好了,现在到了最激动人心的环节:预测未来。我们留出了最后90天作为测试集。预测需要分三步走:

  1. 用ARIMA预测测试集的线性部分
  2. 用LSTM预测测试集的残差部分
  3. 将两部分预测相加,得到最终融合预测值

这里有一个技术细节:在预测第t天的残差时,LSTM需要用到前TIME_STEPS天(比如前10天)的真实残差作为输入。但在测试集开始时,我们并没有未来的真实残差。因此,我们需要采用滚动预测的方式:用模型预测出的残差值,作为后续预测的输入的一部分。这是一个递归的过程。

# 第一步:用ARIMA预测测试集的线性部分
arima_forecast = model_arima_fit.get_forecast(steps=len(test))
arima_pred_test = arima_forecast.predicted_mean
arima_pred_test_index = test.index

# 第二步:准备用LSTM滚动预测测试集的残差
# 首先,我们需要一个初始的“历史残差”序列,用于启动第一次LSTM预测。
# 这个历史序列应该来自训练集末尾的残差。
last_train_residuals = residuals.values[-TIME_STEPS:].reshape(1, -1, 1) # 形状为(1, TIME_STEPS, 1)
last_train_residuals_scaled = scaler.transform(last_train_residuals.reshape(-1, 1)).reshape(last_train_residuals.shape)

lstm_pred_residuals_scaled = [] # 存储LSTM预测的(缩放后的)残差

current_batch = last_train_residuals_scaled

for i in range(len(test)):
    # 用当前批次预测下一个点的残差
    current_pred = model_lstm.predict(current_batch, verbose=0)[0]
    lstm_pred_residuals_scaled.append(current_pred)
    # 更新批次:移除最旧的点,加入最新的预测点
    current_batch = np.append(current_batch[:, 1:, :], [[current_pred]], axis=1)

# 将预测的残差反归一化到原始尺度
lstm_pred_residuals = scaler.inverse_transform(np.array(lstm_pred_residuals_scaled).reshape(-1, 1)).flatten()

# 第三步:融合预测
fusion_pred_test = arima_pred_test + lstm_pred_residuals

5.2 效果评估与可视化

现在,我们有了测试集的真实值(test)、ARIMA的单独预测(arima_pred_test)、LSTM的单独预测(这里LSTM单独预测的是整个序列,操作类似但需用原始数据训练,我们略过),以及融合模型的预测(fusion_pred_test)。是时候看看谁更厉害了。

from sklearn.metrics import mean_absolute_error, mean_squared_error, mean_absolute_percentage_error

# 计算各项误差指标
def evaluate_predictions(true, pred, model_name):
    mae = mean_absolute_error(true, pred)
    mse = mean_squared_error(true, pred)
    rmse = np.sqrt(mse)
    mape = mean_absolute_percentage_error(true, pred) * 100
    print(f'{model_name} 评估结果:')
    print(f'  平均绝对误差(MAE): {mae:.2f}')
    print(f'  均方误差(MSE): {mse:.2f}')
    print(f'  均方根误差(RMSE): {rmse:.2f}')
    print(f'  平均绝对百分比误差(MAPE): {mape:.2f}%')
    return mae, rmse, mape

print("="*50)
mae_arima, rmse_arima, mape_arima = evaluate_predictions(test.values, arima_pred_test.values, 'ARIMA模型')
print("-"*30)
mae_fusion, rmse_fusion, mape_fusion = evaluate_predictions(test.values, fusion_pred_test, 'ARIMA-LSTM融合模型')
print("="*50)

# 创建一个对比DataFrame
comparison_df = pd.DataFrame({
    '真实值': test.values,
    'ARIMA预测': arima_pred_test.values,
    '融合预测': fusion_pred_test
}, index=test.index)

# 绘制对比图
plt.figure(figsize=(14, 7))
plt.plot(comparison_df.index, comparison_df['真实值'], label='真实值', linewidth=2, alpha=0.8)
plt.plot(comparison_df.index, comparison_df['ARIMA预测'], label='ARIMA预测', linestyle='--')
plt.plot(comparison_df.index, comparison_df['融合预测'], label='融合模型预测', linestyle='--', linewidth=2)
plt.title('测试集预测效果对比')
plt.xlabel('日期')
plt.ylabel('销售额')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

# 绘制误差对比柱状图
metrics = ['MAE', 'RMSE', 'MAPE (%)']
arima_scores = [mae_arima, rmse_arima, mape_arima]
fusion_scores = [mae_fusion, rmse_fusion, mape_fusion]

x = np.arange(len(metrics))
width = 0.35

fig, ax = plt.subplots(figsize=(10, 6))
rects1 = ax.bar(x - width/2, arima_scores, width, label='ARIMA', color='skyblue')
rects2 = ax.bar(x + width/2, fusion_scores, width, label='融合模型', color='lightcoral')

ax.set_ylabel('误差值')
ax.set_title('ARIMA与融合模型误差指标对比')
ax.set_xticks(x)
ax.set_xticklabels(metrics)
ax.legend()

# 在柱子上标注数值
def autolabel(rects):
    for rect in rects:
        height = rect.get_height()
        ax.annotate(f'{height:.2f}',
                    xy=(rect.get_x() + rect.get_width() / 2, height),
                    xytext=(0, 3), # 3 points vertical offset
                    textcoords="offset points",
                    ha='center', va='bottom', fontsize=9)

autolabel(rects1)
autolabel(rects2)
fig.tight_layout()
plt.show()

从图表和指标上,你应该能清晰地看到融合模型的优势。在我的这次模拟实验中,融合模型的RMSE和MAPE通常比单独的ARIMA模型要低。这意味着融合模型预测的点,整体上离真实值更近。尤其是在数据波动更复杂、非线性特征更强的区段,LSTM学习残差的能力就体现出来了,它能对ARIMA的线性预测进行有效的“修正”。

6. 避坑指南与关键调优技巧

走完整个流程,你可能已经成功了。但想在实际项目中获得稳定、卓越的效果,还需要注意下面这些我踩过好几次的“坑”。

第一个大坑:数据泄露。 这是新手最容易犯的致命错误。切记,任何从数据中学习规律的过程,都只能基于训练集。具体到我们的流程:

  • ARIMA模型的参数(p,d,q)选择,应该基于训练集进行网格搜索。
  • 归一化器(MinMaxScaler)的fit,必须且只能用在训练数据上(包括为LSTM准备残差数据时)。然后用这个训练好的scaler去转换验证集和测试集。
  • LSTM模型在训练时,早停(EarlyStopping)所监控的val_loss,是基于验证集的,这本身是防止过拟合的正规操作,不属于数据泄露。

第二个难点:超参数调优。 融合模型涉及两套超参数,调优是个耐心活。

  • ARIMA部分:核心是(p, d, q)d可以通过差分至平稳来确定。pq可以通过ACF/PACF图初步判断,但最好使用pmdarima库的auto_arima函数进行自动搜索,它能帮你省下大量时间。
  • LSTM部分:超参数更多,影响也很大。
    • 时间步长(TIME_STEPS):用过去多少期数据预测下一期?太小可能信息不足,太大可能引入噪声且增加计算量。可以尝试[7, 14, 30, 60]等值。
    • 网络结构:LSTM层数(1-3层足够)、每层神经元数(如50, 100, 150)、Dropout比率(0.1-0.5)。
    • 训练参数:批次大小(batch_size,如16, 32, 64)、优化器学习率(Adam默认通常不错,也可尝试调整)。

我的经验是,先花时间把ARIMA部分调好,确保残差序列尽可能“干净”。然后固定ARIMA,集中精力调LSTM。可以使用Keras Tunerscikit-learnGridSearchCV(需结合TimeSeriesSplit)进行自动化搜索,但计算成本较高。

第三个注意点:残差序列的质量。 这是融合模型成功的基石。在把残差交给LSTM之前,务必检查它:

  1. 均值是否在0附近?
  2. 是否没有明显的趋势和季节性?(可以再次画图或做ADF检验)
  3. 自相关性是否较弱?(可以用plot_acf检查)

如果残差质量很差,说明ARIMA模型没有很好地捕捉线性部分,这时融合效果会打折扣。你需要返回去重新审视ARIMA模型的选择。

第四个实战技巧:融合方式的变体。 我们用的是“残差相加”法,这是最直接的方式。还有一些变体可以尝试:

  • 权重融合:不是简单相加,而是给ARIMA预测和LSTM预测(注意,这里是LSTM对整个序列的预测,而非仅对残差)分配一个权重,权重可以通过优化得到。最终预测 = w * ARIMA预测 + (1-w) * LSTM预测
  • 序列到序列融合:用ARIMA的预测结果作为额外特征,和原始序列一起输入到LSTM中。这要求LSTM的输入维度增加。

对于大多数情况,本文介绍的残差相加法已经足够有效且易于理解。它清晰地划分了模型的职责,是我在时间序列预测任务中的首选融合方案之一。当你拿到一份新的数据,不妨先用单一模型跑个基线,再试试这个融合套路,对比一下效果,很多时候会有惊喜。

更多推荐