fbprophet案例之python實現

目的

     上一篇博文翻譯了fbprophet所參考的文獻,本篇內容將給出模擬的時間序列,驗證下fbpropeht的精度,以及嘗試下如何調參;

1.正弦波和矩形波疊加

1.1 數據生成過程

     生成一個頻率爲15分鐘的時間序列,其中每一天的數據是一個正弦波,如果是週末,則需要在正弦波上加1,非週末加0,即正弦波的週期是96,矩形波的週期是96796·7,假設生成100天的數據,數據起點爲2013-01-01,其中2013-04-04以及之後的數據作爲樣本外檢驗樣本;
y(t)=sin(2πt96)+1t(t)y(t)=sin(\frac{2\pi t}{96})+1_t(t\in 週末)

1.2 數據模擬的python代碼

#!/usr/bin/env python
# coding: utf-8

import logging
import numpy as np
import pandas as pd
from fbprophet import Prophet
from matplotlib import pyplot as plt
import matplotlib.pyplot as plt
from fbprophet.diagnostics import cross_validation
logging.getLogger('fbprophet').setLevel(logging.ERROR)
import warnings


warnings.filterwarnings("ignore")
"""prophet的所有periods都默認爲最小1天爲單位
"""
d = 100*2*np.pi/(96*100-1)
y0 = np.sin(np.arange(0, 100*2*np.pi+d, d))
dates = pd.date_range(start='20130101', periods=len(y0), freq="15min")
tmp = np.zeros([len(y0)])
tmp[dates.weekday==0] = 1
tmp[dates.weekday==6] = 1
y = tmp + 1*y0

df = pd.DataFrame(y[0:len(dates)], index=dates).reset_index()
df.rename(columns={0:"y", "index":"ds"}, inplace=True)
df_train=df[df.ds<'2013-04-04']
df_test = df[df.ds>='2013-04-04']

plt.figure(1,figsize=(12,8))
plt.subplot(221)
plt.plot(df[0:96*14]["ds"], tmp[0:96*14], 'b--')
plt.xticks(rotation=45)
plt.subplot(222)
plt.plot(df[0:96*14]["ds"], y0[0:96*14], 'r--')
plt.xticks(rotation=45)
plt.subplot(212)
plt.plot(df[0:96*14]["ds"], df[0:96*14]["y"])
plt.xticks(rotation=45)
plt.show()

在這裏插入圖片描述

1.3 propeht模型擬合

     由於知道數據中含有兩個週期,即以96(1天)和96·7(7天)爲週期,那麼編寫prophet的模型也比較簡單,代碼如下:


m = Prophet(growth="linear", n_changepoints=0,
            yearly_seasonality=False,
            weekly_seasonality=False,
            daily_seasonality=False)

# 設置傅里葉級數的展開項數爲5,後期我們會發現,這個參數太小,不足以用正餘弦波來表示一個矩形波
m.add_seasonality(name='b', period=1, fourier_order=5)
m.add_seasonality(name='weekly', period=7, fourier_order=20)
m.fit(df_train)
# 由於是15分鐘頻率的數據,因此不能用propeht自帶的make_future_dataframe函數,
# 這個函數只支持生成日度數據的時間標籤
# future = m.make_future_dataframe(periods=96*7)
# 做未來7天的預測
# future = pd.date_range(start='20130101',periods=len(y)+7*96,freq="15min")
# future = pd.DataFrame(future,columns=["ds"])
# 這裏由於將df劃分爲樣本內和樣本外兩部分,因此,預測時,時間標籤就可以取df的時間標籤
future = df[["ds"]]
forecast = m.predict(future)
m.plot(forecast).show()
m.plot_components(forecast).show()
forecast["true"] = df["y"]
forecast.set_index("ds",inplace=True)
# 畫出樣本外的預測和真實值的對比圖
plt.figure(2, figsize=(12, 8))
plt.plot(forecast['2013-04-04':][["yhat","true"]])
plt.xticks(rotation=45)
plt.show()

可以看下模型對樣本外的預測,如下圖所示,預測曲線和實際曲線幾乎重合,說明prophet預測效果較好;
在這裏插入圖片描述

  • 也可以看看各個估計的成分,如下圖,雖然我們的數據沒有趨勢,但是propeht仍然估計出了一個趨勢,另外,我們的數據是一個正弦波和一個矩形波的疊加,但是prophet估計出來的兩個週期成分和我們的週期成分不符,原因也可以這樣理解,一個波本身是可以分解爲其他波的組合,也許propeht只是找出了波的某些成分的新組合;
  • 由於任意周期函數都可以展開成一個傅里葉級,有些函數可能需要較多的基函數來表達,比如矩形波,這時候在設置傅里葉變換的參數時,就需要設置大一點,否則會出現欠擬合現象,但是如果不存在這麼個週期,而我們又把該週期對應的傅里葉級數參數設置很大,就會出現過擬合現象,所以一定要用樣本外數據來驗證。
    在這裏插入圖片描述

2.ARMA過程

2.1 ARMA過程和隨機模擬器

     這裏先看一下ARMA(p,q)ARMA(p,q)過程:
Yt=β0+β1Yt1+β2Yt2+...+βpYtp+ϵt+α1ϵt1+α2ϵt2+αqϵtqY_t=\beta_0+\beta_1Y_{t-1}+\beta_2Y_{t-2}+...+\beta_pY_{t-p}+\epsilon_t+\alpha_1\epsilon_{t-1}+\alpha_2\epsilon_{t-2}+\alpha_q\epsilon_{t-q}
其中,ϵt\epsilon_{t}服從相互獨立的正態分佈;如果自相關係數之和小於1,則該自迴歸過程是平穩過程,非平穩的ARMA過程會有一個時間趨勢在裏面,如果用模擬器的話,就不存在一個收斂的點,模擬數據的前部分不可用,如果是平穩的時間序列就不存在這個問題,當然我們也可以先生成一個平穩的時間序列,然後再用這個時間序列構造一個不穩定的時間序列;下面給出一個基於python生成arma過程的代碼,已經和matlab自帶的模擬生成器驗證過,代碼沒有問題;

class arima_model(object):
    def __init__(self, Constant, AR, MA, Variance):
        """
        功能:模擬任意的sarima過程,經驗證,模擬結果和matlab自帶模擬器結果一致
        model = arima('Constant',0.5,'AR',{0.51,0.37},'MA',{0.75,0.1},'Variance',1);
        %% Step 2. Generate one sample path.
        rng('default')
        samples = 500000;
        Y = simulate(model,samples);
        figure
        plot(Y)
        xlim([0,samples])
        title('Simulated ARMA(2) Process')
        mean(Y)
        std(Y)
        參數說明:
        Constant:模型常數項
        AR:模型自相關係數,dict,example:
        AR={1:0.51,2:0.37,6:0.89}表示滯後1期、2期和6期的自迴歸係數分別爲0.51,0.37,0.89
        MA:模型移動平均係數,dict,example:
        MA={1:0.51,2:0.37,6:0.89}表示滯後1期、2期和6期的移動平均係數分別爲0.51,0.37,0.89
        Variance:信息分佈的方差,均值默認設置爲0,僅考慮正太分佈情況
        """
        self.Constant = Constant
        self.AR = AR
        self.MA = MA
        self.Variance = Variance
        
    def simulate(self, samples_length,path_number=1):
        """
        samples_length:1條路徑長度
        path_number:路徑數量,暫時不可用
        """
        get_param = lambda x:np.array([[lag,auto_regress_coff] for lag,auto_regress_coff in x.items()])
        # 自迴歸自大滯後階
        arparm = get_param(self.AR)
        auto_lag = arparm[:,0].astype(int)
        # 自迴歸最大滯後階
        maparm = get_param(self.MA)
        ma_lag = maparm[:,0].astype(int)
        samples_length = int(samples_length)
        max_lag = max(max(auto_lag),max(ma_lag))
        # 每一期的新息
        epsilon = np.random.randn(samples_length+max_lag+1)*self.Variance
        # 找出自相關係數
        auto_coff, mov_coff = arparm[:, 1], maparm[:, 1]
        limit = (1/(1-sum(auto_coff)))*self.Constant
        # 模擬的目標樣本np.zeros
        y = np.array([limit]*len(epsilon))
        for i in range(max_lag+1,len(y)):
            auto_index = i -auto_lag
            mov_index = i - ma_lag  #+ epsilon[i]
            # y[i] = self.Constant + np.dot(y[auto_index],auto_coff) + np.dot(epsilon[mov_index],mov_coff) + epsilon[i] + self.Variance*np.sin(np.pi*i/96)
            y[i] = self.Constant + np.dot(y[auto_index],auto_coff) + np.dot(epsilon[mov_index],mov_coff) + epsilon[i]

        return y[max_lag+1:]

2.2 生成一個平穩的ARMA過程並利用propeht預測

生成如下所示的時間序列:
Yt=12+0.47Yt1+0.39Yt2+ϵt+0.7ϵt1+0.4ϵt2Y_t=12+0.47Y_{t-1}+0.39Y_{t-2}+\epsilon_t+0.7\epsilon_{t-1}+0.4\epsilon_{t-2}

arima_model_object = arima_model(Constant=12, AR={1:0.47,2:0.39}, MA={1:1.0,2:0.7,3:0.4}, Variance=0.5)
simulate_samples = 96*100
simulate_y = arima_model_object.simulate(simulate_samples)
print(simulate_y.__len__())
scale_mean = simulate_y.mean()
scale_std = simulate_y.std()
print("mean:",scale_mean)
print("std:",scale_std)
dates = pd.date_range(start='20130101', periods=len(simulate_y), freq="15min")
y = simulate_y
df = pd.DataFrame(y[0:len(dates)], index=dates).reset_index()
df.rename(columns={0: "y", "index": "ds"}, inplace=True)
df_train = df[df.ds < '2013-04-04']
df_test = df[df.ds >= '2013-04-04']

plt.figure(1, figsize=(12, 8))
plt.plot(df["ds"],simulate_y)
plt.xticks(rotation=45)
plt.show()

在這裏插入圖片描述
下面我們利用prophet來預測這個時間序列,參數我們還採用第一個案例的參數:

m = Prophet(growth="linear", n_changepoints=0,
            yearly_seasonality=False,
            weekly_seasonality=False,
            daily_seasonality=False)

# m.add_seasonality(name='a', period=7*48, fourier_order=20)
m.add_seasonality(name='b', period=1, fourier_order=5)
m.add_seasonality(name='weekly', period=7, fourier_order=100)
m.fit(df_train)
# future = m.make_future_dataframe(periods=96*7)
# 第一中
# future = pd.date_range(start='20130101',periods=len(y)+7*96,freq="15min")
# future = pd.DataFrame(future,columns=["ds"])
# 預測種生成
future = df[["ds"]]
forecast = m.predict(future)
m.plot(forecast).show() #繪製預測效果圖
m.plot_components(forecast).show()#繪製成
forecast["true"] = df["y"]
forecast.set_index("ds",inplace=True)

plt.figure(3, figsize=(12, 8))
plt.plot(forecast['2013-04-04':][["yhat","true"]])
plt.xticks(rotation=45)
# plt.legend(loc='best')
plt.legend(['y_hat',"true"])
plt.show()

從下圖可以看出,prophet預測值是跟這個arma過程的收斂值一致,也就是說近乎用一個常數來預測該arma序列;該arma模型的收斂點是 85.79985020557761,如圖所示,prophet的預測值幾乎跟這個值一致;
在這裏插入圖片描述

2.3 生成一個帶趨勢的時間序列

仍然以案例二中的時間序列爲例,這次我們將arma過程變換成一個帶趨勢的過程,可以直接添加一個時間t的函數,也可以直接將案例2中的arma數據相加,爲了演示方便,我們將趨勢項調小,即將案例二中的constant參數調整爲0.1,此時生成的數據如下:

arima_model_object = arima_model(Constant=0.1, AR={1:0.47,2:0.39}, MA={1:1.0,2:0.7,3:0.4}, Variance=0.5)
simulate_samples = 96*100
simulate_y = arima_model_object.simulate(simulate_samples)
print(simulate_y.__len__())
scale_mean = simulate_y.mean()
scale_std = simulate_y.std()
print("mean:",scale_mean)
print("std:",scale_std)



# x = np.arange(0,1000,1/50)
dates = pd.date_range(start='20130101', periods=len(simulate_y), freq="15min")
# 平穩
# y = simulate_y
# 非平穩
y = np.cumsum(simulate_y)
df = pd.DataFrame(y[0:len(dates)], index=dates).reset_index()
df.rename(columns={0: "y", "index": "ds"}, inplace=True)
df_train = df[df.ds < '2013-04-04']
df_test = df[df.ds >= '2013-04-04']

plt.figure(1, figsize=(12, 8))
plt.plot(df["ds"],df["y"])
plt.xticks(rotation=45)
plt.show()

在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述

3.總結

  • 當時間序列含有固定週期成分,利用prophet可以較好的估計週期成分;但是需要正確的設置週期的大小和相應的傅里葉級數項數;
  • 當數據的生成過程是arma時,propeht基本上是按照arma模型的均值來預測,對於arma類的數據生成過程,這種預測是合理的;
  • 當生成非平穩的時間序列時,發現prophet在不調參的情況下,並沒有精確的預測數據的走勢,一方面由於我們生成的時間序列中的不確定性成分佔比較高,另一方面,我們也沒有做參數優化,達到目前的預測精度,是能接受的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章