時間序列神器之爭:prophet VS lstm

1、需求背景

咱們福祿網絡致力於爲廣大用戶提供智能化充值服務,包括各種通訊充值卡(好比移動、聯通、電信的話費及流量充值)、遊戲類充值卡(好比王者榮耀、吃雞類點券、AppleStore充值、Q幣、鬥魚幣等)、生活服務類(好比肯德基、小鹿茶等),網娛類(好比QQ各種鑽等),做爲一個服務提供商,商品質量的穩定、持續及充值過程的便捷一直是咱們在業內的口碑。
在整個商品流經過程中,如何作好庫存的管理,以充分提升庫存運轉週期和資金使用效率,一直是個難題。基於此,咱們提出了智能化的庫存管理服務,根據訂單數據及商品數據,來預測不一樣商品隨着時間推移的平常消耗狀況。
python

2、算法選擇

目前成熟的時間序列預測算法不少,但商業領域性能優越的卻很少,通過多種嘗試,給你們推薦2種時間序列算法:facebook開源的Prophet算法和LSTM深度學習算法。
現將我的理解的2種算法特性予以簡要說明:
git

  • (1)、在訓練時間上,prophet幾十秒就能出結果,而lstm每每須要1個半小時,更是隨着網絡層數和特徵數量的增長而增長。
  • (2)、Prophet是一個爲商業預測而生的時間序列預測模型,所以在不少方便都有針對性的優化,而lstm的初衷是nlp。
  • (3)、Prophet無需特徵處理便可使用,參數調優也明確簡單。而lstm則須要先進行必要的特徵處理,其次要進行正確的網絡結構設計,所以lstm相對prophet更爲複雜。
  • (4)、Lstm須要更多的數據進行學習,不然沒法消除欠擬合的情形。而prophet不一樣,prophet基於統計學,有完整的數學理論支撐,所以更容易從少許的數據中完成學習。
  • (5)、傳統的時間序列預測算法只支持單緯度,但LSTM能支持多緯度,也就是說LSTM能考慮促銷活動,目標用戶特性,產品特性等

3、數據來源

  • (1)、訂單數據
  • (2)、產品分類數據

4、數據形式

time,product,cnt
2019-10-01 00,**充值,6
2019-10-01 00,***遊戲,368
2019-10-01 00,***,1
2019-10-01 00,***,11
2019-10-01 00,***遊戲,17
2019-10-01 00
,三網***,39
2019-10-01 00,**網,6
2019-10-01 00,***,2

字段說明:github

  • Time:小時級時間
  • Product:產品名稱或產品的分類名稱,目前使用的是產品2級分類,名稱
  • Cnt:成功訂單數量
    目前的時間序列是由以上time和cnt組成,product是用於區分不一樣時間序列的字段。

5、特徵處理

時間序列通常不進行特徵處理,固然能夠根據具體狀況進行歸一化處理或是取對數處理等。算法

6、算法選擇

目前待選的算法主要有2種:網絡

  • (1)、Prophet
    Facebook開源的時間序列預測算法,考慮了節假日因素。
  • (2)、LSTM
    優化後的RNN深度學習算法。

7、算法說明

7.1 prophet

7.1.1Prophet的核心是調參,步驟以下:
  • 一、首先咱們去除數據中的異常點(outlier),直接賦值爲none就能夠,由於Prophet的設計中能夠經過插值處理缺失值,可是對異常值比較敏感。
  • 二、選擇趨勢模型,默認使用分段線性的趨勢,可是若是認爲模型的趨勢是按照log函數方式增加的,可設置growth='logistic'從而使用分段log的增加方式
  • 三、 設置趨勢轉折點(changepoint),若是咱們知道時間序列的趨勢會在某些位置發現轉變,能夠進行人工設置,好比某一天有新產品上線會影響咱們的走勢,咱們能夠將這個時刻設置爲轉折點。若是本身不設置,算法會本身總結changepoint。
  • 四、 設置週期性,模型默認是帶有年和星期以及天的週期性,其餘月、小時的週期性須要本身根據數據的特徵進行設置,或者設置將年和星期等週期關閉。
    設置節假日特徵,若是咱們的數據存在節假日的突增或者突降,咱們能夠設置holiday參數來進行調節,能夠設置不一樣的holiday,例如五一一種,國慶一種,影響大小不同,時間段也不同。
  • 五、 此時能夠簡單的進行做圖觀察,而後能夠根據經驗繼續調節上述模型參數,同時根據模型是否過擬合以及對什麼成分過擬合,咱們能夠對應調節seasonality_prior_scale、holidays_prior_scale、changepoint_prior_scale參數。

以上是理論上的調參步驟,但咱們在實際狀況下在建議使用grid_search(網格尋參)方式,直接簡單效果好。當機器性能不佳時網格調參配合理論調參方法能夠加快調參速度。建議初學者使用手動調參方式以理解每一個參數對模型效果的影響。app

holiday.csv

框架

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from fbprophet import Prophet

data = pd.read_csv('../data/data2.csv', parse_dates=['time'], index_col='time')


def get_product_data(name, rule=None):
    product = data[data['product'] == name][['cnt']]
    product.plot()
    
if rule is not None:
        product = product.resample(rule).sum()
    product.reset_index(inplace=True)
    product.columns = ['ds', 'y']
    return product


holidays = pd.read_csv('holiday.csv', parse_dates=['ds'])
holidays['lower_window'] = -1

holidays = holidays.append(pd.DataFrame({
    'holiday': '雙11',
    'ds': pd.to_datetime(['2019-11-11', '2020-11-11']),
    'lower_window': -1,
    'upper_window': 1,
})).append(pd.DataFrame({
    'holiday': '雙12',
    'ds': pd.to_datetime(['2019-12-12', '2020-12-12']),
    'lower_window': -1,
    'upper_window': 1,
})
)
def predict(name, rule='1d', freq='d', periods=1, show=False):
    ds = get_product_data(name, rule=rule)
    if ds.shape[0] < 7:
        return None
    m = Prophet(holidays=holidays)
    m.fit(ds)
    future = m.make_future_dataframe(freq=freq, periods=periods)  # 創建數據預測框架,數據粒度爲天,預測步長爲一年
    forecast = m.predict(future)
    if show:
        m.plot(forecast).show()  # 繪製預測效果圖
        m.plot_components(forecast).show()  # 繪製成分趨勢圖
    mse = forecast['yhat'].iloc[ds.shape[0]] - ds['y'].values
    mse = np.abs(mse) / (ds['y'].values + 1)
    return [name, mse.mean(), mse.max(), mse.min(), np.quantile(mse, 0.9), np.quantile(mse, 0.8), mse[-7:].mean(),
            ds['y'].iloc[-7:].mean()]
if __name__ == '__main__':
    products = set(data['product'])
    p = []
    for i in products:
        y = predict(i)
        if y is not None:
            p.append(y)
    df = pd.DataFrame(p, columns=['product', 'total_mean', 'total_max', 'total_min', '0.9', '0.8', '7_mean',
       '7_real_value_mean'])
    df.set_index('product', inplace=True)
    product_sum: pd.DataFrame = data.groupby('product').sum()
    df = df.join(product_sum)
    df.sort_values('cnt', ascending=False, inplace=True)
    df.to_csv('result.csv', index=False)

結果以下:因爲行數較多這裏只展現前1行

函數

根據結果,對比原生數據,能夠得出以下結論:
就算法與產品的匹配性可分爲3個類型:
post

  • (1)與算法較爲匹配,算法的歷史偏差8分爲數<=0.2的
  • (2)與算法不太匹配的,算法的歷史偏差8分爲數>0.2的
  • (3)數據過少的,沒法正常預測的。目前僅top10就能佔到總體訂單數的90%以上。
7.1.2 部分紅果展現

A. 因素分解圖
undefined
上圖中主要分爲3個部分,分別對應prophet 3大要素,趨勢、節假日或特殊日期、週期性(包括年週期、月週期、week週期、天週期以及用戶自定義的週期)
下面依照上面因素分解圖的順序依次對圖進行說明:


性能

  • (1)、Trend:
    即趨勢因素圖。描述時間序列的趨勢。Prophet支持線性趨勢和logist趨勢。經過growth參數設置,固然模型能本身根據時間序列的走勢判斷growth類型。這也是prophet實現的比較智能的一點。
  • (2)、Holidays
    即節假日及特殊日期因素圖。描述了節假日及用戶自定義的特殊日期對時間序列的影響。正值爲正影響,負值爲負影響。從圖中能夠看出這個商品對節假日比較敏感。節假日是根據holidays參數設置的。
  • (3)、weekly
    星期週期性因素圖。正常狀況下,若是是小時級別數據將會有天週期圖。有1年以上完整數據而且時間序列有典型的年週期性會有年週期圖。若是你以爲這個有年週期,但模型並不這麼認爲,你能夠經過設置yearly_seasonality設置一個具體的數值。這個數值默認狀況下爲10(weekly_seasonality默認爲3),這個值表明的是傅里葉級數的項數,越大模型越容易過擬合,太小則會致使欠擬合,通常配合seasonality_prior_scale使用。
    B.預測曲線與實際值對比
    undefined


7.2 lstm

LSTM(長短記憶網絡)主要用於有前後順序的序列類型的數據的深度學習網絡。是RNN的優化版本。通常用於天然語言處理,也可用於時間序列的預測。
undefined

簡單來講就是,LSTM一共有三個門,輸入門,遺忘門,輸出門, i 、o、 f 分別爲三個門的程度參數, g 是對輸入的常規RNN操做。公式裏能夠看到LSTM的輸出有兩個,細胞狀態c 和隱狀態 h,c是經輸入、遺忘門的產物,也就是當前cell自己的內容,通過輸出門獲得h,就是想輸出什麼內容給下一單元。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import torch
from torch import nn

from sklearn.preprocessing import MinMaxScaler

ts_data = pd.read_csv('../data/data2.csv', parse_dates=['time'], index_col='time')


def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):
    n_vars = 1 if type(data) is list else data.shape[1]
    df = pd.DataFrame(data)
    cols, names = list(), list()
    # input sequence (t-n, ... t-1)
    for i in range(n_in, 0, -1):
        cols.append(df.shift(i))
        names += [('var%d(t-%d)' % (j + 1, i)) for j in range(n_vars)]
    # forecast sequence (t, t+1, ... t+n)
    for i in range(0, n_out):
        cols.append(df.shift(-i))
        if i == 0:
            names += [('var%d(t)' % (j + 1)) for j in range(n_vars)]
        else:
            names += [('var%d(t+%d)' % (j + 1, i)) for j in range(n_vars)]
    # put it all together
    agg = pd.concat(cols, axis=1)
    agg.columns = names
    # drop rows with NaN values
    if dropnan:
        agg.dropna(inplace=True)
    return agg


def transform_data(feature_cnt=2):
    yd = ts_data[ts_data['product'] == '移動話費'][['cnt']]
    scaler = MinMaxScaler(feature_range=(0, 1))
    yd_scaled = scaler.fit_transform(yd.values)
    yd_renamed = series_to_supervised(yd_scaled
, n_in=feature_cnt).values.astype('float32')

    n_row = yd_renamed.shape[0]

    n_train = int(n_row * 0.7)

    train_X, train_y = yd_renamed[:n_train, :-1], yd_renamed[:n_train, -1]
    test_X, test_y = yd_renamed[n_train:, :-1], yd_renamed[n_train:, -1]

    # 最後,咱們須要將數據改變一下形狀,由於 RNN 讀入的數據維度是 (seq, batch, feature),因此要從新改變一下數據的維度,這裏只有一個序列,因此 batch 是 1,而輸入的 feature 就是咱們但願依據的幾天,這裏咱們定的是兩個天,因此 feature 就是 2.
    train_X = train_X.reshape((-1, 1, feature_cnt))
    test_X = test_X.reshape((-1, 1, feature_cnt))
    print(train_X.shape, train_y.shape, test_X.shape, test_y.shape)

    # 轉化成torch 的張量
    train_x = torch.from_numpy(train_X)
    train_y = torch.from_numpy(train_y)
    test_x = torch.from_numpy(test_X)
    test_y = torch.from_numpy(test_y)
    return scaler, train_x, train_y, test_x, test_y


scaler, train_x, train_y, test_x, test_y = transform_data(24)


# lstm 網絡
class lstm_reg(nn.Module):  # 括號中的是python的類繼承語法,父類是nn.Module類 不是參數的意思
    def __init__(self, input_size, hidden_size, output_size=1, num_layers=2):  # 構造函數
        # inpu_size 是輸入的樣本的特徵維度, hidden_size 是LSTM層的神經元個數,
        # output_size是輸出的特徵維度
        super(lstm_reg, self).__init__()  # super用於多層繼承使用,必需要有的操做

        self.rnn = nn.LSTM(input_size, hidden_size, num_layers)  # 兩層LSTM網絡,
        self.reg = nn.Linear(hidden_size, output_size)  # 把上一層總共hidden_size個的神經元的輸出向量做爲輸入向量,而後迴歸到output_size維度的輸出向量中

    
def forward(self, x):  # x是輸入的數據
        x, _ = self.rnn(x)  # 單個下劃線表示不在乎的變量,這裏是LSTM網絡輸出的兩個隱藏層狀態
        s, b, h = x.shape
        x = x.view(s * b, h)
        x = self.reg(x)
        x = x.view(s, b, -1)  # 使用-1表示第三個維度自動根據原來的shape 和已經定了的s,b來肯定
        return x


def train(feature_cnt, hidden_size, round, save_path='model.pkl'):
    # 我使用了GPU加速,若是不用的話須要把.cuda()給註釋掉
    net = lstm_reg(feature_cnt, hidden_size)
    criterion = nn.MSELoss()
    optimizer = torch.optim.Adam(net.parameters(), lr=1e-2)
    for e in range(round):
        # 新版本中能夠不使用Variable了
        #     var_x = Variable(train_x).cuda()
        #     var_y = Variable(train_y).cuda()

        # 將tensor放在GPU上面進行運算
        var_x = train_x
        var_y = train_y

        out = net(var_x)
        loss = criterion(out, var_y)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        if (e + 1) % 100 == 0:
            print('Epoch: {}, Loss:{:.5f}'.format(e + 1, loss.item()))
    # 存儲訓練好的模型參數
    torch.save(net.state_dict(), save_path)
    return net


if __name__ == '__main__':
    net = train(24, 8, 5000)
    # criterion = nn.MSELoss()
    # optimizer = torch.optim.Adam(net.parameters(), lr=1e-2)
    pred_test = net(test_x)  # 測試集的預測結果

    pred_test = pred_test.view(-1).data.numpy()  # 先轉移到cpu上才能轉換爲numpy

    # 乘以原來歸一化的刻度放縮回到原來的值域
    origin_test_Y = scaler.inverse_transform(test_y.reshape((-1,1)))
    origin_pred_test = scaler.inverse_transform(pred_test.reshape((-1,1)))

    # 畫圖
    plt.plot(origin_pred_test, 'r', label='prediction')
    plt.plot(origin_test_Y, 'b', label='real')
    plt.legend(loc='best')
    plt.show()

    # 計算MSE
    # loss = criterion(out, var_y)?
    true_data = origin_test_Y
    true_data = np.array(true_data)
    true_data = np.squeeze(true_data)  # 從二維變成一維
    
MSE = true_data - origin_pred_test
    MSE = MSE * MSE
    MSE_loss = sum(MSE) / len(MSE)
    print(MSE_loss)

8、兩種算法的比較

  • (1)在訓練時間上,prophet幾十秒就能出結果,而lstm每每須要1個半小時,更是隨着網絡層數和特徵數量的增長而增長。
  • (2)Prophet是一個爲商業預測而生的時間序列預測模型,所以在不少方便都有針對性的優化,而lstm的初衷是nlp。
  • (3)Prophet無需特徵處理便可使用,參數調優也明確簡單。而lstm則須要先進行必要的特徵處理,其次要進行正確的網絡結構設計,所以lstm相對prophet更爲複雜。
  • (4)Lstm須要更多的數據進行學習,不然沒法消除欠擬合的情形。而prophet不一樣,prophet基於統計學,有完整的數學理論支撐,所以更容易從少許的數據中完成學習。
    參考文獻:
    【1】Prophet官方文檔:https://facebook.github.io/prophet/
    【2】Prophet論文:https://peerj.com/preprints/3190/
    【3】Prophet-github:https://github.com/facebook/prophet
    【4】LSTM http://colah.github.io/posts/2015-08-Understanding-LSTMs/
    【5】基於LSTM的關聯時間序列預測方法研究 尹康 《北京交通大學》 2019年 cnki地址:http://cdmd.cnki.com.cn/Article/CDMD-10004-1019209125.htm





相關文章
相關標籤/搜索