时间序列预测 | SARIMA(P、D、Q了解一下)

824426bf056c1e192d38f43e6aaf2c7d.gif

前言

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自动搜索参数。
ef343a25fec416fd351e3032d94304e7.png

应用:Datadog(国外一家知名云监控公司)使用基于预测的方式进行异常检测,其中预测算法Agile版即为sarima。
5521f6850c12d7cfc7b48c6aaf1ba36c.png

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)
d40d78b4d780cf70d47a0d84f14d4f87.png

嘿嘿,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)
38e397066beea8270a384d0790b8cec2.png 27d49faa09a9a0da3e00a33371e34920.png

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)
780b1a4826f12558c84f74c8182e1c38.png b7188dad66d11224c7b4ce06ee744767.png

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)
e60107adbe121c2a24409456f27af8a2.png dc790610c3941cc2433d361fa462a569.png

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

[4]https://blog.csdn.net/itnerd/article/details/104715508

推荐阅读:

我的2022届互联网校招分享

我的2021总结

浅谈算法岗和开发岗的区别

互联网校招研发薪资汇总

2022届互联网求职现状,金9银10快变成铜9铁10!!

公众号:AI蜗牛车

保持谦逊、保持自律、保持进步

7e36847da28d112c9b2b915a78c01b9c.jpeg

发送【蜗牛】获取一份《手把手AI项目》(AI蜗牛车著)

发送【1222】获取一份不错的leetcode刷题笔记

发送【AI四大名著】获取四本经典AI电子书

猜你喜欢

转载自blog.csdn.net/qq_33431368/article/details/125630843