4、经典预测模型:ARIMA模型原理、模型定阶(AIC/BIC准则)、Python实现与残差检验

说到时间序列预测,ARIMA模型绝对是个绕不开的老朋友。我记得刚入行那会儿,带我的老工程师就说:「先把ARIMA玩明白,再谈什么深度学习。」当时我不太服气,后来在好几个能源负荷预测项目里碰了壁,才明白这句话的分量。

说白了,ARIMA就是一套「让非平稳数据变平稳,然后找规律」的成熟方法论。咱们今天就把它的原理、定阶、代码实现和检验,一次性讲透。

4.1 ARIMA模型原理:三个字母的拆解

ARIMA的全称是 AutoRegressive Integrated Moving Average,也就是自回归积分滑动平均模型。它由三部分组成:

  • AR(自回归):用过去时刻的值预测当前值。比如今天的负荷,跟昨天、前天的负荷有关系。
  • I(差分):让非平稳序列变平稳。说白了就是「减一减」,把趋势去掉。
  • MA(滑动平均):用过去的预测误差来修正当前预测。相当于给模型加了个「纠错机制」。

数学表达式长这样:

ARIMA(p,d,q) 模型:
(1 - φ₁B - φ₂B² - ... - φₚBᵖ)(1-B)ᵈ yₜ = (1 + θ₁B + θ₂B² + ... + θₙBⁿ) εₜ

其中 B 是滞后算子,p 是自回归阶数,d 是差分阶数,q 是滑动平均阶数。

核心理解:ARIMA(p,d,q) 的本质,就是先用 d 次差分把数据变平稳,然后用 p 阶自回归和 q 阶滑动平均来拟合这个平稳序列。

4.2 模型定阶:AIC与BIC准则

定阶是ARIMA里最让人头疼的一步。p、d、q到底取多少?我见过有人靠「玄学」猜,结果模型过拟合得一塌糊涂。

我个人习惯用两步走:

  1. 先定 d:通过单位根检验(ADF检验)确定差分阶数。一般 d=0 或 d=1 就够用,能源负荷数据很少需要 d>1。
  2. 再定 p 和 q:看自相关图(ACF)和偏自相关图(PACF),然后结合 AIC/BIC 准则选最优组合。

这里重点说说 AIC(赤池信息准则)BIC(贝叶斯信息准则)

准则 公式 特点
AIC AIC = 2k - 2ln(L) 倾向于选择更复杂的模型,k为参数个数
BIC BIC = k·ln(n) - 2ln(L) 对复杂模型惩罚更大,n为样本量

我在项目中遇到过这样的情况:AIC选出来的模型参数偏多,预测时抖动很大;BIC选出来的更简洁,泛化能力反而更好。所以我的建议是——优先看BIC,尤其是样本量大的时候。

避坑指南:我曾经在一个光伏预测项目里,AIC选出了 p=7,q=5 的模型,结果测试集上误差爆炸。后来用BIC选了 p=2,q=2,效果反而稳定。记住:复杂不等于好。

4.3 Python实现:从数据到模型

咱们直接上代码。假设你手头有一份某区域过去1000天的日负荷数据,存在 load_data.csv 里。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.arima.model import ARIMA
import warnings
warnings.filterwarnings('ignore')

# 1. 加载数据
df = pd.read_csv('load_data.csv', parse_dates=['date'], index_col='date')
y = df['load']

# 2. 平稳性检验(ADF检验)
def adf_test(series):
    result = adfuller(series.dropna())
    print(f'ADF Statistic: {result[0]:.4f}')
    print(f'p-value: {result[1]:.4f}')
    return result[1] < 0.05  # p<0.05 表示平稳

# 3. 差分处理
d = 0
y_diff = y.copy()
while not adf_test(y_diff):
    d += 1
    y_diff = y.diff(d).dropna()
    print(f'进行了 {d} 次差分')

print(f'最终确定 d = {d}')

# 4. 画ACF和PACF图,辅助定p和q
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6))
plot_acf(y_diff, lags=30, ax=ax1)
plot_pacf(y_diff, lags=30, ax=ax2)
plt.show()

嗯,这里要注意:ACF图看拖尾还是截尾,PACF图也是。如果ACF在q阶后突然截尾,那q就取那个值;如果PACF在p阶后截尾,p就取那个值。但说实话,实际数据很少这么理想,所以咱们还是用AIC/BIC来定。

# 5. 网格搜索最优(p,q)
import itertools

best_aic = np.inf
best_bic = np.inf
best_order_aic = None
best_order_bic = None

# 设定搜索范围(根据ACF/PACF初步判断)
p_range = range(0, 5)
q_range = range(0, 5)

for p, q in itertools.product(p_range, q_range):
    try:
        model = ARIMA(y, order=(p, d, q))
        result = model.fit()
        aic = result.aic
        bic = result.bic
        
        if aic < best_aic:
            best_aic = aic
            best_order_aic = (p, d, q)
        
        if bic < best_bic:
            best_bic = bic
            best_order_bic = (p, d, q)
            
    except:
        continue

print(f'AIC最优阶数: {best_order_aic}, AIC={best_aic:.2f}')
print(f'BIC最优阶数: {best_order_bic}, BIC={best_bic:.2f}')

# 6. 训练最终模型(用BIC选出来的)
final_order = best_order_bic
model = ARIMA(y, order=final_order)
result = model.fit()
print(result.summary())

注意:网格搜索时一定要加 try-except,因为某些(p,q)组合会导致模型无法收敛。我刚开始写代码时没加这个,程序跑一半就崩了,排查了半天才发现是参数组合的问题。

4.4 残差检验:模型好不好,残差说了算

模型训练完了,别急着用。你想想看,如果残差里还有明显的模式,说明模型没把信息提取干净。

残差检验主要看三点:

  1. 残差是否白噪声:用Ljung-Box检验,p值大于0.05说明残差是白噪声。
  2. 残差是否正态分布:画Q-Q图或做Shapiro-Wilk检验。
  3. 残差是否独立:看残差的自相关图,不应该有明显的自相关。
# 7. 残差检验
residuals = result.resid

# 7.1 Ljung-Box检验
from statsmodels.stats.diagnostic import acorr_ljungbox
lb_test = acorr_ljungbox(residuals, lags=[10, 20, 30], return_df=True)
print('Ljung-Box检验结果:')
print(lb_test)

# 7.2 画残差图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))

# 残差时序图
axes[0, 0].plot(residuals)
axes[0, 0].set_title('残差时序图')
axes[0, 0].axhline(y=0, color='r', linestyle='--')

# 残差直方图
axes[0, 1].hist(residuals, bins=30, edgecolor='black', alpha=0.7)
axes[0, 1].set_title('残差分布直方图')

# 残差ACF图
plot_acf(residuals, lags=30, ax=axes[1, 0])
axes[1, 0].set_title('残差自相关图')

# Q-Q图
from scipy import stats
stats.probplot(residuals, dist="norm", plot=axes[1, 1])
axes[1, 1].set_title('残差Q-Q图')

plt.tight_layout()
plt.show()

# 7.3 判断
lb_pvalue = lb_test['lb_pvalue'].values
if all(p > 0.05 for p in lb_pvalue):
    print('✅ 残差通过白噪声检验,模型拟合良好')
else:
    print('⚠️ 残差未通过白噪声检验,建议调整模型参数')

我在一个风电预测项目里遇到过这种情况:模型AIC很低,但残差检验死活过不了。后来发现是数据里有个异常点没处理,去掉之后模型就正常了。所以记住:残差检验是模型质量的最后一道防线,别偷懒。

总结一下ARIMA建模流程

  • 数据预处理 → 平稳性检验 → 差分定d
  • ACF/PACF辅助判断 → 网格搜索定p,q(优先BIC)
  • 模型训练 → 残差检验(Ljung-Box + 可视化)
  • 通过则用,不通过则调整参数或数据
ARIMA模型知识体系 ARIMA(p,d,q) AR:自回归 I:差分 MA:滑动平均 用过去值预测当前值 差分使序列平稳 用过去误差修正预测 定阶:AIC / BIC 准则 检验:残差白噪声检验

好了,ARIMA模型的核心内容就这些。从原理到定阶,从代码实现到残差检验,每一步都有坑,但也都有解法。你把这个流程跑通一遍,以后遇到时间序列预测问题,心里就有底了。

专注资料整理