进阶篇 1:超越基准 - 指数平滑 (ETS) 模型详解
进阶篇 1:超越基准 - 指数平滑 (ETS) 模型详解
(图片来源: rawpixel.com on Pexels)
欢迎来到时间序列分析的进阶之旅!在我们的零基础入门系列中,我们学习了朴素预测、平均法等基准模型。虽然它们是理解预测的起点,但在面对真实世界中普遍存在的趋势 (Trend) 和 季节性 (Seasonality) 时,这些简单方法往往力不从心,预测效果难以令人满意。
是时候掌握更强大、更智能的预测武器了!指数平滑 (Exponential Smoothing, ETS) 就是其中一大利器。它基于一个直观的思想:越近的观测值包含的关于未来的信息越多,因此应该被赋予更高的权重。 ETS 模型家族通过不同的组合方式,能够优雅地处理时间序列中的水平 (Level)、趋势 (Trend) 和 季节性 (Seasonality) 成分。
本篇,我们将深入探讨 ETS 模型家族:
- 简单指数平滑 (SES): 处理水平数据。
- 霍尔特线性趋势模型 (Holt’s Linear Trend): 加入趋势处理。
- 霍尔特-温特斯季节性模型 (Holt-Winters’ Seasonal): 最全面的成员,同时处理趋势和季节性。
- 模型选择与诊断: 如何为你的数据选择合适的 ETS 模型并评估它?
准备好升级你的预测工具箱了吗?
核心思想:智能的加权平均
想象一下简单平均法,它给所有历史观测值相同的权重。而指数平滑则认为,预测未来时,昨天的销售额显然比一年前的销售额更具参考价值。它通过一个或多个平滑参数 (Smoothing Parameters)(通常用 α, β, γ 表示,取值在 0 到 1 之间)来实现这种指数级递减的加权。
- α (alpha): 控制水平 (Level) 的平滑程度。α 越大,模型越重视近期的观测值,对变化的反应越快;α 越小,平滑程度越高,对噪声越不敏感。
- β (beta): 控制趋势 (Trend) 的平滑程度。β 越大,趋势的估计越依赖近期的趋势变化。
- γ (gamma): 控制季节性 (Seasonality) 的平滑程度。γ 越大,季节性模式的估计越依赖近期的季节性表现。
1. 简单指数平滑 (SES) - 处理水平数据
- 原理: 最基础的 ETS 模型,只估计序列的水平 (Level)。它假设数据没有明显的趋势和季节性,只是围绕一个(可能缓慢变化的)水平波动。预测值就是当前估计的水平。
- 公式概要:
Level(t) = α * y(t) + (1 - α) * Level(t-1)
Forecast(t+1) = Level(t)
- 适用场景: 需求稳定、没有明显增长或下降趋势,也没有季节性规律的数据(例如,某个成熟产品的稳定日销量)。
- Python 实现 (
statsmodels
):
import pandas as pd
import numpy as np
from statsmodels.tsa.holtwinters import SimpleExpSmoothing
import matplotlib.pyplot as plt
import seaborn as sns# --- 1. 生成示例数据 (无明显趋势和季节性) ---
np.random.seed(42)
level = 50
data_ses = [level + np.random.normal(0, 5)] # 起始点
for _ in range(100):# 水平有微小随机波动level += np.random.normal(0, 0.5)data_ses.append(level + np.random.normal(0, 5)) # 在当前水平上加噪声
index_ses = pd.date_range(start='2023-01-01', periods=len(data_ses), freq='D')
series_ses = pd.Series(data_ses, index=index_ses)# --- 2. 拟合 SES 模型 ---
# 创建模型实例
model_ses = SimpleExpSmoothing(series_ses)
# 拟合模型,让 statsmodels 自动寻找最优 alpha
results_ses = model_ses.fit(optimized=True)# --- 3. 查看结果与预测 ---
print("--- Simple Exponential Smoothing Results ---")
print(results_ses.summary())# 绘制拟合值与原始值
plt.figure(figsize=(12, 6))
plt.plot(series_ses, label='Original Data')
plt.plot(results_ses.fittedvalues, label='Fitted (SES)', linestyle='--')
# 预测未来 10 个点
forecast_ses = results_ses.forecast(10)
plt.plot(forecast_ses, label='Forecast (SES)', linestyle='-.', marker='o')
plt.title('Simple Exponential Smoothing (SES)')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.show()# [此处应显示生成的 SES 拟合与预测图]
# 图中应显示原始数据、模型的拟合线(平滑后的),以及对未来的水平预测(一条直线)
results_ses.summary()
会告诉你模型自动找到的最优 α 值以及其他信息。预测结果是一条水平线,因为 SES 不包含趋势信息。
2. 霍尔特线性趋势模型 (Holt’s Linear Trend) - 加入趋势
- 原理: 在 SES 的基础上增加了对线性趋势 (Trend) 的估计和预测。它假设趋势会持续下去。
- 趋势类型:
- 加法趋势 (Additive Trend): 趋势的增量是固定的(例如,每月固定增长 100 用户)。
Level + Trend
- 乘法趋势 (Multiplicative Trend): 趋势是按比例增长的(例如,每月按 5% 增长)。
Level * Trend
(较少见,且可能不稳定,statsmodels
的Holt
类默认是加法)。
- 加法趋势 (Additive Trend): 趋势的增量是固定的(例如,每月固定增长 100 用户)。
- 适用场景: 数据有持续的上升或下降趋势,但没有明显的季节性(例如,新产品发布初期的用户增长)。
- Python 实现 (
statsmodels
):
from statsmodels.tsa.holtwinters import Holt# --- 1. 生成示例数据 (带线性趋势) ---
np.random.seed(42)
level_holt = 50
trend_holt = 2 # 初始趋势
data_holt = [level_holt + np.random.normal(0, 5)]
for _ in range(100):level_holt += trend_holt + np.random.normal(0, 0.5) # 水平加上趋势trend_holt += np.random.normal(0, 0.1) # 趋势自身也可能微小波动data_holt.append(level_holt + np.random.normal(0, 5))
index_holt = pd.date_range(start='2023-01-01', periods=len(data_holt), freq='D')
series_holt = pd.Series(data_holt, index=index_holt)# --- 2. 拟合 Holt 模型 (加法趋势) ---
model_holt = Holt(series_holt)
# 让 statsmodels 自动寻找最优 alpha 和 beta
results_holt = model_holt.fit(optimized=True)# --- 3. 查看结果与预测 ---
print("\n--- Holt's Linear Trend Results ---")
print(results_holt.summary())plt.figure(figsize=(12, 6))
plt.plot(series_holt, label='Original Data')
plt.plot(results_holt.fittedvalues, label='Fitted (Holt)', linestyle='--')
forecast_holt = results_holt.forecast(10)
plt.plot(forecast_holt, label='Forecast (Holt)', linestyle='-.', marker='o')
plt.title("Holt's Linear Trend Model")
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.show()# [此处应显示生成的 Holt 拟合与预测图]
# 图中应显示带趋势的原始数据、模型的拟合线,以及对未来的线性趋势预测(一条斜线)
你会看到预测结果是一条斜线,反映了模型捕捉到的趋势。summary()
会给出最优的 α 和 β。
3. 霍尔特-温特斯季节性模型 (Holt-Winters’ Seasonal) - 全面出击
- 原理: 这是 ETS 家族的“完全体”,在 Holt 模型的基础上进一步加入了对季节性 (Seasonality) 的处理。
- 季节性类型:
- 加法季节性 (Additive Seasonality): 季节性波动幅度不随数据水平变化(例如,每年夏天固定多卖 1000 台空调)。
Level + Trend + Seasonal
- 乘法季节性 (Multiplicative Seasonality): 季节性波动幅度与数据水平成比例(例如,旺季销售额是淡季的 1.5 倍)。
(Level + Trend) * Seasonal
或Level * Trend * Seasonal
。乘法季节性更常见。
- 加法季节性 (Additive Seasonality): 季节性波动幅度不随数据水平变化(例如,每年夏天固定多卖 1000 台空调)。
- 阻尼趋势 (Damped Trend): 一个非常有用的选项!它引入一个阻尼参数 φ (phi, 0<φ<1),使得长期预测的趋势逐渐减弱并趋于平稳,避免了线性趋势无限增长/下降的不实现实情况。
- 适用场景: 同时存在趋势和季节性的数据,这是非常常见的情况(例如,航空乘客量、零售销售额、电力消耗等)。
- Python 实现 (
statsmodels
- 使用通用接口):
ExponentialSmoothing
是statsmodels
中最通用的 ETS 接口,可以构建上述所有模型及组合。
from statsmodels.tsa.holtwinters import ExponentialSmoothing# --- 1. 生成示例数据 (带趋势和乘法季节性) ---
np.random.seed(42)
level_hw = 100
trend_hw = 1
seasonal_periods = 7 # 假设是周季节性
seasonal_factors = [0.8, 0.9, 1.0, 1.1, 1.2, 1.1, 0.9] # 乘法季节因子
data_hw = []
current_seasonal = seasonal_factors * (100 // seasonal_periods + 1) # 重复因子for i in range(100):value = (level_hw + trend_hw) * current_seasonal[i] + np.random.normal(0, 5)data_hw.append(value)level_hw += trend_hw + np.random.normal(0, 0.2)# 趋势自身有微小变化,更真实# trend_hw += np.random.normal(0, 0.05)index_hw = pd.date_range(start='2023-01-01', periods=len(data_hw), freq='D')
series_hw = pd.Series(data_hw, index=index_hw)# --- 2. 拟合 Holt-Winters 模型 ---
# 指定趋势为加法 ('add'),季节性为乘法 ('mul'),周期为 7
# (可尝试 'add'/'mul'/'damped' 等组合)
model_hw = ExponentialSmoothing(series_hw,trend='add',seasonal='mul',seasonal_periods=seasonal_periods)
results_hw = model_hw.fit(optimized=True)# --- 3. 查看结果与预测 ---
print("\n--- Holt-Winters' Seasonal Results ---")
print(results_hw.summary()) # 会给出 alpha, beta, gammaplt.figure(figsize=(14, 7))
plt.plot(series_hw, label='Original Data')
plt.plot(results_hw.fittedvalues, label='Fitted (Holt-Winters)', linestyle='--')
forecast_hw = results_hw.forecast(2 * seasonal_periods) # 预测未来 2 个周期
plt.plot(forecast_hw, label='Forecast (Holt-Winters)', linestyle='-.', marker='o')
plt.title("Holt-Winters' Seasonal Model (Additive Trend, Multiplicative Seasonality)")
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.show()# [此处应显示生成的 Holt-Winters 拟合与预测图]
# 图中应显示带趋势和季节性的原始数据、模型的拟合线,以及对未来的趋势+季节性预测(波动的斜线)# --- 4. (重要) 检查残差 ---
residuals_hw = results_hw.resid
plt.figure(figsize=(12, 6))
plt.subplot(211)
plt.plot(residuals_hw)
plt.title('Residuals')
plt.subplot(212)
# 绘制残差的 ACF 图,看是否接近白噪声
from statsmodels.graphics.tsaplots import plot_acf
plot_acf(residuals_hw, lags=30)
plt.tight_layout()
plt.show()# [此处应显示生成的残差图和残差 ACF 图]
# 理想情况下,残差图应无明显模式,ACF 图应在 0 轴附近快速衰减并在置信区间内。
4. 模型选择与诊断
面对这么多选项(加法/乘法趋势、加法/乘法季节性、是否阻尼),如何选择?
- 可视化判断:
- 观察原始序列图:季节性波动幅度是否随水平变化(乘法 vs. 加法)?趋势是直线还是趋于平缓(是否需要阻尼)?
- 观察分解图(来自入门系列的知识):分解出的季节性成分是稳定幅度还是变化幅度?
- 信息准则 (Information Criteria):
- 尝试几种合理的模型组合(例如 ‘add’,‘add’, ‘mul’,‘add’, ‘add’,‘mul’, ‘mul’,‘mul’ 等)。
- 比较不同模型的 AIC (Akaike Information Criterion) 或 BIC (Bayesian Information Criterion) 值(通常在
results.summary()
中给出)。选择 AIC/BIC 最小的模型,它代表了对数据拟合度和模型复杂度之间较好的平衡。
- 残差诊断 (Crucial!):
- 一个好的模型,其残差 (Residuals = Actual - Fitted) 应该类似于白噪声 (White Noise),即没有可预测的模式,均值为 0,方差恒定,且相互独立。
- 检查方法:
- 绘制残差图:看是否有明显的趋势、季节性或其他模式残留。
- 绘制残差的 ACF 图:看是否存在显著的自相关(理想情况是只有 lag 0 的 ACF 值显著,其他都在置信区间内)。
- 进行 Ljung-Box 检验 (
statsmodels.stats.diagnostic.acorr_ljungbox
): 检验残差是否存在自相关(原假设是残差独立,P 值大则无法拒绝)。
如果残差不满足白噪声假设,说明模型未能完全捕捉数据的结构,需要重新选择模型或调整参数。
总结
指数平滑 (ETS) 模型是一个强大且灵活的时间序列预测工具箱:
- SES: 适用于无趋势、无季节性的数据。
- Holt: 适用于有趋势、无季节性的数据。
- Holt-Winters: 适用于同时有趋势和季节性的数据(最常用)。
- 通过合理选择加法/乘法成分和阻尼趋势选项,可以适应多种数据模式。
- 模型选择可依据可视化、信息准则 (AIC/BIC),残差诊断是确保模型有效性的关键步骤。
相比基准模型,ETS 能提供更准确、更可靠的预测,尤其是在处理具有明显结构(趋势、季节性)的数据时。
下一篇预告
掌握了 ETS,我们已经能解决一大类时间序列问题。但还有另一大类经典模型——ARIMA 模型家族,它们从不同的角度(数据的自相关性)来建模。要理解 ARIMA,首先必须深刻理解它的两个“侦察兵”:ACF (自相关函数) 和 PACF (偏自相关函数)。下一篇,我们将深入解读这两个关键工具,为学习 ARIMA 打下坚实基础。
敬请期待!
(尝试用你自己的数据应用 ETS 模型,哪种组合效果最好?残差看起来像白噪声吗?欢迎在评论区分享你的实践和发现!)