前言
ARMA模型可以说是平稳时间序列建模中很常用的方法了,但是局限性也很明显---“平稳”。
一般生活中的数据很难满足平稳性要求,比较常用的转化为平稳序列的做法就是差分,一阶差分不行二阶差分,几次差分后终能平稳,所以ARIMA(p,d,q)在ARMA(p,q)的基础上把差分的过程包含了进来,多了一步差分过程,对应就多了一个参数d,也因此ARIMA可以处理非平稳时间序列。
因此ARIMA有一个不足之处,就是不能很好的处理周期型序列。虽说也可以用差分方式平稳化,但需要的是k步差分(季节差分,x = diff(x, k))。所以说ARIMA对周期型序列来说还有不足。
正文
SARIMA(Seasonal ARIMA):ARIMA的扩展版本,可以支持带有季节性成分的时间序列数据。在ARIMA(p,d,q)基础上又增加了3个超参数(P,D,Q),以及一个额外的季节性周期参数 s。
SARIMA(p,d,q)(P,D,Q,s)总共7个参数,可以分成2类,3个非季节参数(p,d,q),和4个季节参数(P,D,Q,s)。
非季节参数:
p: AR(p),非季节自回归的阶数
公式:402 Payment Required
对于AR模型,可以借助PACF确定阶数p(ACF:拖尾,PACF:p阶截尾)。d: I(d),一步差分的次数
q: MA(q),非季节移动平均的阶数
MA模型的自相关函数具有截尾性,即自相关函数在k>q后为零(PACF:拖尾,ACF:q阶截尾)。
季节参数:
P: 季节自回归的阶数(通常不会大于3)
402 Payment Required
和小p相似,同样可以从PACF推断,如果季节长度为12,看PACF图上滞后12阶、24阶、48阶时的偏自相关系数,如果滞后到24阶时表现显著,那么P等于2。D: 季节差分的次数(通常不会大于1)
一般就是 0 或 1,做了季节差分就是 1。Q: 季节移动平均的阶数(通常不会大于3)
同P,没有AR部分时,看ACF,滞后12阶、24阶、48阶、72阶...时的自相关系数,最大第几轮表现显著就是几。s: 季节长度,或者说是周期大小
P,D,Q清楚了,那它们是怎样作用在数据上的,或者说SARIMA的模型形式是怎样的?
看到很多文章,多使用数学符号进行简化,虽然公式清晰了,但理解上就又多了点弯弯绕。
如下:
定义延迟算子B,
,说的是延迟算子B作用在这个时刻上,相当于对后移(Back)一位,得到的结果就是;
就是对后移n次,得到结果就是;
表示差分;表示对做d次差分。
定义延迟算子多项式:
多项式 作用在时间序列 上:1作用在上还是,作用在上就是,一一对应就是以上公式。
AR模型(不考虑的情况下(零均值)):402 Payment Required
402 Payment Required
AR模型简化描述为:
ARMA简化描述版:
ARIMA简化描述版:
SARIMA简化描述版为 :
这里我标记上具体参数的位置,
看上去挺复杂的,简要描述就是对时间序列做D次季节差分(去周期)+d次差分(去趋势),得到新的序列,然后对差分后的{x_t}建立如下模型:
拆开来类似这个一个加性模型:
系数应该是非季节性和季节性参数的乘性组合。
statsmodels文档中这样描述SARIMA模型:
其中
为确定性趋势项,由参数
trend
控制。
以上,我们可以看到SARIMA在季节差分之外亦在模型中做了补充,不像ARMA->ARIMA一样,仅增加一步季节差分即可去除季节因素,还引入了季节性成分在模型里。个人觉得D次季节差分后,序列中可能仍然含有季节自相关的成分,因此这样SARIMA也能更好的表达周期型序列中样本间自相关的关系。
补充:实践中可以绘制ACF、PACF图或者通过ADF检验确定参数d和D,(p,q)(P,Q)则可以通过网格搜索的方式确定(评判准则aic/bic最小等)。python环境下也可直接调用pmdarima包中的auto_arima自动搜索参数。应用:Datadog(国外一家知名云监控公司)使用基于预测的方式进行异常检测,其中预测算法Agile版即为sarima。
CODE
基于Kaggle上的数据 https://www.kaggle.com/code/kashnitsky/topic-9-part-1-time-series-analysis-in-python/notebook
导入依赖的包
import warnings
import numpy as np
import pandas as pd
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
from statsmodels.tsa.statespace.sarimax import SARIMAX
import pmdarima as pm
from pmdarima.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import pdb
warnings.filterwarnings('ignore')
准备数据
读取数据,并做可视化展示,以及一并绘制自相关、偏自相关图,还有平稳性检验。
def read_data():
file = 'data/ads.csv'
df = pd.read_csv(file, index_col=['Time'], parse_dates=['Time'])
ts = df.Ads
return ts
def plot(ts):
results = adfuller(ts)
results_str = 'ADF test, p-value is: {}'.format(results[1])
grid = plt.GridSpec(2, 2)
ax1 = plt.subplot(grid[0, :])
ax2 = plt.subplot(grid[1, 0])
ax3 = plt.subplot(grid[1, 1])
ax1.plot(ts)
ax1.set_title(results_str)
plot_acf(ts, lags=int(len(ts) / 2 - 1), ax=ax2)
plot_pacf(ts, lags=int(len(ts) / 2 - 1), ax=ax3)
plt.show()
ts = read_data()
plot(ts)
嘿嘿,ADF检验平稳(p值约等于0远小于0.05),确实整体看没有上升或下降趋势均值不变,方差也比较稳定。但我们从图中能看出有明显的周期性,如果使用arima还是有必要进行一步季节差分。
模型验证(ARIMA版,手动进行季节差分)
先不直接上sarima,用arima先来个预热。手动季节差分,将差分后的序列喂给模型,模型预测时再进行差分还原。
def find_pq(ts, d=0, max_p=5, max_q=5):
best_p, best_q = 0, 0
best_aic = np.inf
for p in range(max_p):
for q in range(max_q):
model = ARIMA(ts, order=(p, d, q)).fit()
aic = model.aic
if aic < best_aic:
best_aic = aic
best_p = p
best_q = q
return best_p, best_q, best_aic
def version_arima_with_manual(ts):
"""
ARIMA(手动季节差分)
"""
# 周期大小
periods = 24
# 季节差分
ts_diff = ts - ts.shift(periods)
# 再次差分(季节差分后p值小于0.05-接近,可认为平稳,若要严格一点也可再做一次差分)
# ts_diff = ts_diff - ts_diff.shift(1)
# (训练数据中不能有缺失值,这里差分后前几个值为nan,故去除)
ts_diff = ts_diff[~pd.isnull(ts_diff)]
# 数据拆分
train, test = train_test_split(ts_diff, train_size=0.8)
# 模型训练(训练数据为差分后的数据-已平稳,所以d=0)
p, q, _ = find_pq(train)
model = ARIMA(train, order=(p, 0, q)).fit()
print(model.summary())
# 拟合结果
fitted = model.fittedvalues
# 模型预测
fcst = model.forecast(test.shape[0])
# 差分还原(拟合结果)
fitted += ts.shift(periods)
# 差分还原(预测结果)
tmp = ts.loc[train.index].values.tolist() + fcst.values.tolist()
for i in range(len(tmp) - fcst.shape[0], len(tmp)):
tmp[i] += tmp[i - periods]
fcst.loc[:] = tmp[-fcst.shape[0]:]
# 模型评估
rmse = np.sqrt(mean_squared_error(test, fcst))
print('RMSE: %.4f' % rmse)
# 可视化
plt.figure(figsize=(12, 4))
ts.plot(label='Ads')
fitted.plot(label='fitted')
fcst.plot(label='forecast')
plt.legend()
plt.grid(True)
plt.show()
version_arima_with_manual(ts)
AIC:3140.871,同时我也输出了验证集上的RMSE:129641.4004用作比较。可视化上看有一定误差,整体还好。
模型验证(SARIMA版,网格搜索参数)
设置参数d=0,D=1,(p,q)(P,Q)网格搜索。
def find_pq_PQ(ts, m, d, D, max_p=5, max_q=5, max_P=2, max_Q=2):
best_p, best_q = 0, 0
best_P, best_Q = 0, 0
best_aic = np.inf
for p in range(max_p):
for q in range(max_q):
for P in range(max_P):
for Q in range(max_Q):
model = SARIMAX(ts, order=(p, d, q), seasonal_order=(P, D, Q, m)).fit(disp=-1)
aic = model.aic
if aic < best_aic:
best_aic = aic
best_p = p
best_q = q
best_P = P
best_Q = Q
return best_p, best_q, best_P, best_Q, best_aic
def version_sarima_with_manual(ts):
"""
SARIMA(statsmodels)
"""
# 不提前做季节差分,让D=1
# 周期大小
periods = 24
# 数据拆分
train, test = train_test_split(ts, train_size=0.8)
# 模型训练
d, D = 0, 1
p, q, P, Q, _ = find_pq_PQ(ts, periods, d=d, D=D)
model = SARIMAX(train, order=(p, d, q), seasonal_order=(P, D, Q, periods)).fit(disp=-1)
print(model.summary())
# 拟合结果
fitted = model.fittedvalues
# 模型预测
fcst = model.forecast(test.shape[0])
# 模型评估
rmse = np.sqrt(mean_squared_error(test, fcst))
print('RMSE: %.4f' % rmse)
# 可视化
plt.figure(figsize=(12, 4))
ts.plot(label='Ads')
fitted.plot(label='fitted')
fcst.plot(label='forecast')
plt.legend()
plt.grid(True)
plt.title('SARIMA')
plt.show()
version_sarima_with_manual(ts)
AIC:3107.858,验证集上RMSE:6387.2040,均低于arima版本,看来sarima模型中季节项还是有些效果的。
模型验证(Auto-SARIMA,pmdarima)
直接使用的pmdarima自动调参(d通过ADF检验确定,D通过Canova-Hansen检验确定,(p,q)(P,Q)一样grid search)。
def version_sarima_with_pmdarima(ts):
"""
Auto-SARIMA(pmdarima)
"""
# 周期大小
periods = 24
# 数据拆分
train, test = train_test_split(ts, train_size=0.8)
# 模型训练
model = pm.auto_arima(train, seasonal=True, m=periods)
print(model.summary())
# 拟合结果
fitted = model.predict_in_sample()
fitted = pd.Series(fitted, index=train.index)
# 模型预测
fcst = model.predict(test.shape[0])
fcst = pd.Series(fcst, index=test.index)
# 模型评估
rmse = np.sqrt(mean_squared_error(test, fcst))
print('RMSE: %.4f' % rmse)
# 可视化
plt.figure(figsize=(12, 4))
ts.plot(label='Ads')
fitted.plot(label='fitted')
fcst.plot(label='forecast')
plt.legend()
plt.grid(True)
plt.title('pmdarima-SARIMA')
plt.show()
version_sarima_with_pmdarima(ts)
版 | 版 | 版 | |
AIC | 3140.871 | 3107.858 | 3569.904 |
RMSE | 129641.4004 | 6387.2040 | 10286.2152 |
以上结果可以看到SARIMA版,相较ARIMA版(手动季节差分)效果略有提升,说明SARIMA是能够更好的时间序列中的季节性。
但是Auto版本效果不如SARIMA版好,可以看到Auto-SARIMA的参数是d和D均为0,应该是被假设检验骗了,所以啊很多时候auto出来的结果真的比人工差。Auto也有优点,胜在省心省力还快。以前在工作的时候对效果有要求的项目手动建模,要求较低的项目上AutoML。未来在海量数据以及更强大的Auto模型面前,人还能干过机器吗?
参考链接
[1]https://www.docin.com/p-1309163264.html
[2]http://alkaline-ml.com/pmdarima/1.8.3/about.html
[3]https://mp.weixin.qq.com/s/0rBxPl9gnHcYEORgAUW0eQ