04 - 时序模型与ARIMA¶
🕐 时间序列概述¶
什么是时间序列?¶
定义: 按时间顺序排列的一系列数据点
Text Only
示例:
- 每日股价: [100, 102, 98, 105, 110, ...]
- 每小时温度: [22, 23, 21, 20, 19, ...]
- 每月销售额: [1000, 1200, 1150, 1300, ...]
时间序列组成要素¶
Text Only
时间序列 = 趋势 + 季节性 + 周期性 + 随机性
趋势 (Trend):
长期上升或下降方向
示例: 股票长期上涨
季节性 (Seasonality):
固定周期的重复模式
示例: 夏天冰淇淋销量高,冬天低
周期性 (Cyclical):
不固定周期的波动
示例: 经济周期 (繁荣→衰退→复苏)
随机性/残差 (Residual):
无法预测的噪声
示例: 突发事件影响
可视化示例¶
Text Only
温度数据:
30℃ ────────────🔥🔥🔥───────
│ 🔥🔥🔥
20℃ ───────────🔥──🔥────────
│ 🔥────🔥
10℃ ────────🔥────────🔥─────
│ 🔥──────────🔥
0℃ ────🔥─────────────🔥───
春 夏 秋 冬 春 夏
趋势: 整体平稳
季节性: 夏高冬低
📈 ARIMA 模型¶
ARIMA 是什么?¶
全称: AutoRegressive Integrated Moving Average (自回归整合移动平均)
组成部分: - AR: 自回归 - 用过去的值预测现在 - I: 差分 - 使数据平稳 - MA: 移动平均 - 用过去的误差预测现在
记法: ARIMA(p, d, q) - p: AR项数 (自回归阶数) - d: 差分次数 - q: MA项数 (移动平均阶数)
AR (自回归) - AutoRegressive¶
核心思想¶
用过去的时间点值预测当前值
Text Only
AR(1) 模型:
x_t = c + φ₁·x_{t-1} + ε_t
AR(p) 模型:
x_t = c + φ₁·x_{t-1} + φ₂·x_{t-2} + ... + φ_p·x_{t-p} + ε_t
其中:
- x_t: 当前时刻的值
- x_{t-1}, x_{t-2}: 过去的值
- φ₁, φ₂: 自回归系数
- ε_t: 白噪声误差
直观理解¶
例子: 今日股价预测
Text Only
假设: AR(1)模型, c = 20
今日股价 = 20 + 0.8 × 昨日股价 + 噪声
如果昨天股价是100元:
预测今天 = 20 + 0.8 × 100 = 100元
(假设噪声为0,此模型均值为 c/(1-φ) = 20/0.2 = 100)
φ = 0.8 < 1: 模型平稳
φ > 1: 模型发散 (不现实)
判断AR阶数: PACF (偏自相关函数)¶
Text Only
PACF图:
|
1.0 │ ●
│ ●
0.5 │ ● ●
│ ●
0.0 │─────────
│
-0.5
如果PACF在p阶后截尾 (突然接近0),
则AR阶数为p
示例: PACF在1阶后接近0 → AR(1)
MA (移动平均) - Moving Average¶
核心思想¶
用过去的预测误差预测当前值
Text Only
MA(1) 模型:
x_t = μ + θ₁·ε_{t-1} + ε_t
MA(q) 模型:
x_t = μ + θ₁·ε_{t-1} + θ₂·ε_{t-2} + ... + θ_q·ε_{t-q} + ε_t
其中:
- ε_{t-1}: 过去的预测误差
- θ₁, θ₂: 移动平均系数
直观理解¶
例子: 温度预测
判断MA阶数: ACF (自相关函数)¶
I (差分) - Integration¶
为什么需要差分?¶
平稳性: 时间序列的统计特性 (均值、方差) 不随时间变化
Text Only
非平稳数据 (有趋势):
100, 102, 105, 108, 110, ...
↑ 上升趋势,均值在变化
一阶差分:
+2, +3, +3, +2, ...
→ 变成平稳!
二阶差分:
+1, +0, -1, ...
→ 更平稳
差分公式¶
Text Only
一阶差分:
Δx_t = x_t - x_{t-1}
二阶差分:
Δ²x_t = Δx_t - Δx_{t-1} = (x_t - x_{t-1}) - (x_{t-1} - x_{t-2})
= x_t - 2x_{t-1} + x_{t-2}
季节差分:
Δ_s x_t = x_t - x_{t-s}
(s: 季节周期,如12个月)
判断差分次数¶
Text Only
方法1: 观察法
- 有明显趋势 → 差分1次
- 差分后仍有趋势 → 差分2次
- 一般不超过2次
方法2: ADF检验 (Augmented Dickey-Fuller)
- H0: 数据非平稳
- H1: 数据平稳
- p < 0.05: 拒绝H0,数据平稳
ARIMA 完整建模流程¶
步骤1: 可视化和平稳性检验¶
Python
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller
# 加载数据
df = pd.read_csv('data.csv', parse_dates=['date'], index_col='date')
# 绘制时序图
plt.figure(figsize=(12, 6))
plt.plot(df['value'])
plt.title('时间序列可视化')
plt.show()
# ADF检验
result = adfuller(df['value'])
print(f'ADF统计量: {result[0]:.4f}')
print(f'p值: {result[1]:.4f}')
if result[1] < 0.05:
print('数据平稳')
else:
print('数据非平稳,需要差分')
步骤2: 确定差分阶数 d¶
Python
# 绘制ACF和PACF
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))
plot_acf(df['value'], lags=20, ax=ax1)
plot_pacf(df['value'], lags=20, ax=ax2)
plt.show()
# 如果ACF缓慢衰减 → 需要差分
# 差分后再看ACF/PACF
df_diff = df['value'].diff().dropna()
plot_acf(df_diff, lags=20)
plot_pacf(df_diff, lags=20)
步骤3: 确定 p 和 q¶
Text Only
ACF (自相关函数):
- 在q阶后截尾 → MA(q)
- 缓慢衰减 → AR或ARMA
PACF (偏自相关函数):
- 在p阶后截尾 → AR(p)
- 缓慢衰减 → AR或ARMA
示例:
ACF在1阶后截尾, PACF缓慢衰减 → MA(1)
ACF缓慢衰减, PACF在2阶后截尾 → AR(2)
两者都缓慢衰减 → ARMA(p,q)
步骤4: 拟合ARIMA模型¶
Python
from statsmodels.tsa.arima.model import ARIMA
# ARIMA(p,d,q)
model = ARIMA(df['value'], order=(2, 1, 1)) # AR(2), 差分1次, MA(1)
results = model.fit()
# 查看摘要
print(results.summary())
# 关键信息:
# - AIC/BIC: 越小越好 (用于模型选择)
# - 系数显著性: p < 0.05
# - 残差检验: 应该是白噪声
步骤5: 模型诊断¶
Python
# 残差分析
residuals = results.resid
# 绘制残差
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))
ax1.plot(residuals)
ax1.set_title('残差图')
# 残差ACF (应该都是0,无显著相关)
plot_acf(residuals, lags=20, ax=ax2)
plt.show()
# Ljung-Box检验 (残差是否白噪声)
from statsmodels.stats.diagnostic import acorr_ljungbox
lb_test = acorr_ljungbox(residuals, lags=10)
print(lb_test)
# p值 > 0.05: 残差是白噪声 (好!)
步骤6: 预测¶
Python
# 预测未来10个时间点
forecast = results.forecast(steps=10)
# 绘制预测
plt.figure(figsize=(12, 6))
plt.plot(df['value'], label='历史数据')
plt.plot(forecast, label='预测', color='red')
plt.legend()
plt.title('ARIMA预测')
plt.show()
# 置信区间
forecast_ci = results.get_forecast(steps=10).conf_int()
plt.fill_between(forecast_ci.index,
forecast_ci.iloc[:, 0],
forecast_ci.iloc[:, 1],
alpha=0.3)
SARIMA (季节性ARIMA)¶
当数据有季节性时使用
记法: SARIMA(p, d, q)(P, D, Q, s) - (p, d, q): 非季节部分 - (P, D, Q, s): 季节部分 - P: 季节AR阶数 - D: 季节差分次数 - Q: 季节MA阶数 - s: 季节周期 (12=月度, 4=季度)
Python
from statsmodels.tsa.statespace.sarimax import SARIMAX
# 月度数据,年度季节性
model = SARIMAX(df['value'],
order=(1, 1, 1), # (p,d,q)
seasonal_order=(1, 1, 1, 12)) # (P,D,Q,s)
results = model.fit()
forecast = results.forecast(steps=12)
🧠 神经网络时序模型¶
RNN (循环神经网络, Recurrent Neural Network)¶
核心思想¶
通过循环连接处理序列数据,隐藏状态传递历史信息
Text Only
传统NN:
x₁ → [NN] → y₁
x₂ → [NN] → y₂ (每个样本独立处理)
RNN:
x₁ → [RNN] → h₁ → y₁
↑ ↓
x₂ → [RNN] → h₂ → y₂
↑ ↓
x₃ → [RNN] → h₃ → y₃
h_t: 隐藏状态 (记忆)
h_t = f(h_{t-1}, x_t)
数学公式¶
Text Only
h_t = tanh(W_hh · h_{t-1} + W_xh · x_t + b_h)
y_t = W_hy · h_t + b_y
其中:
- h_t: 当前隐藏状态
- h_{t-1}: 上一时刻隐藏状态
- x_t: 当前输入
- y_t: 当前输出
代码实现 (PyTorch)¶
Python
import torch
import torch.nn as nn
class SimpleRNN(nn.Module): # 继承nn.Module定义神经网络层
def __init__(self, input_size, hidden_size, output_size): # __init__构造方法,创建对象时自动调用
super().__init__() # super()调用父类方法
self.hidden_size = hidden_size
self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# x: (batch, seq_len, input_size)
out, h_n = self.rnn(x)
# out: (batch, seq_len, hidden_size)
# h_n: (1, batch, hidden_size)
# 取最后一个时间步
out = out[:, -1, :]
out = self.fc(out)
return out
# 使用
model = SimpleRNN(input_size=1, hidden_size=32, output_size=1)
RNN的问题: 梯度消失/爆炸¶
Text Only
问题:
长序列时,梯度在反向传播中不断乘以权重矩阵
- 权重 < 1: 梯度指数衰减 → 梯度消失
- 权重 > 1: 梯度指数增长 → 梯度爆炸
结果:
无法学习长期依赖 (RNN的记忆只有短期)
LSTM (长短期记忆网络, Long Short-Term Memory)¶
核心思想¶
通过门控机制选择性记忆和遗忘
Text Only
LSTM细胞结构:
输入 x_t ─┬─→ 遗忘门 ─┬─→ 细胞状态 C_{t-1} ──┐
│ ↓ ↓
└─→ 输入门 → [候选记忆] → 更新 C_t │
│
输出门 ←────────────────────────────┘
↓
隐藏状态 h_t
三大门¶
1. 遗忘门 (Forget Gate): 决定丢弃什么信息
2. 输入门 (Input Gate): 决定存储什么信息
3. 输出门 (Output Gate): 决定输出什么信息
细胞状态更新:
代码实现¶
Python
class LSTMModel(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, output_size):
super().__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.lstm = nn.LSTM(input_size, hidden_size, num_layers,
batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# x: (batch, seq_len, input_size)
out, (h_n, c_n) = self.lstm(x)
# out: (batch, seq_len, hidden_size)
# h_n: (num_layers, batch, hidden_size)
# c_n: (num_layers, batch, hidden_size)
# 取最后一个时间步
out = out[:, -1, :]
out = self.fc(out)
return out
# 使用
model = LSTMModel(input_size=1, hidden_size=64,
num_layers=2, output_size=1)
GRU (门控循环单元, Gated Recurrent Unit)¶
LSTM的简化版¶
Text Only
区别:
- GRU只有2个门 (重置门、更新门)
- LSTM有3个门 (遗忘门、输入门、输出门)
- GRU不单独维护细胞状态
公式:
重置门: r_t = σ(W_r · [h_{t-1}, x_t])
更新门: z_t = σ(W_z · [h_{t-1}, x_t])
候选隐藏: h̃_t = tanh(W · [r_t ⊙ h_{t-1}, x_t])
最终隐藏: h_t = (1 - z_t) ⊙ h_{t-1} + z_t ⊙ h̃_t
LSTM vs GRU¶
| 特性 | LSTM | GRU |
|---|---|---|
| 门数量 | 3 | 2 |
| 细胞状态 | 有 | 无 |
| 参数量 | 更多 | 更少 |
| 训练速度 | 更慢 | 更快 |
| 表达能力 | 更强 | 略弱 |
| 应用 | 长序列 | 中等序列 |
📊 模型对比¶
ARIMA vs 神经网络¶
Text Only
ARIMA:
优点:
- 数学原理清晰
- 可解释性强
- 适合小数据集
- 训练快
缺点:
- 假设线性关系
- 难建模长期依赖
- 需要手动确定p,d,q
- 不适合多变量
LSTM:
优点:
- 能建模非线性关系
- 自动学习特征
- 能处理长期依赖
- 支持多变量
缺点:
- 需要大量数据
- 训练慢
- 黑盒 (难解释)
- 需要调参
选择建议¶
Text Only
使用ARIMA:
- 数据量小 (< 1000个点)
- 单变量时间序列
- 需要可解释性
- 线性关系
使用LSTM/GRU:
- 数据量大 (> 1000个点)
- 多变量或非线性
- 不需要可解释性
- 长期依赖
🎯 实践建议¶
时序预测通用流程¶
Text Only
1. 数据探索:
- 可视化趋势、季节性
- 检查平稳性
- 异常值处理
2. 特征工程:
- 滞后特征 (lag)
- 滚动统计 (均值、标准差)
- 时间特征 (星期、月份)
3. 模型选择:
- 简单基线: 移动平均、ARIMA
- 复杂模型: LSTM、Transformer
4. 评估:
- 时间序列交叉验证
- 指标: MAE, RMSE, MAPE
5. 部署:
- 滚动预测
- 定期重训练
下一步¶
继续阅读 05-深度学习.md,学习CNN和更多深度学习知识!