Energy Consumption (能源消耗)數據集預測(4):拯救prophet

前言

第一篇文章咱們用一個小時從安裝prophet 到完成預測,而且咱們將結果對比了naive的季節信號分解(這個能夠看做是簡版的prohet,純原創),發現效果略遜色於naive的方法。bash

後來咱們用lightgbm 再次預測一樣的數據集,發現結果遠遠賽過prophet。post

咱們也對比了咱們的預測結果與kaggle上公開的方法與測試結果,發現咱們的lightgbm的結果更好,多是由於咱們作了必定參數調優。學習

咱們的prophet 結果和kaggle大神的預測結果同樣「好」 (相比於lghtgbm都不好)。測試

可是facebook 大廠旗下prophet的super power 真的弱爆了嗎?ui

一條code挽救prophet

對於深度學習來講,沒有標準化會直接讓你的模型跑飛。url

對於樹模型來講,異常值有的時候讓你的feature 變得莫名奇妙的重要。spa

對於prohet 來講,彷佛沒有什麼特殊的注意事項,也沒有要清理數據的必要。3d

可是問題每每就隱藏在其中。code

讀取數據

代碼依然保持不變。component

import pandas as pd
from fbprophet import Prophet, make_holidays
from sklearn.metrics import mean_absolute_error
from fbprophet.plot import add_changepoints_to_plot


file = 'data/PJME_hourly.csv'
raw_df = pd.read_csv(file)
print(raw_df.head(5))
複製代碼

將string 時間轉成pandas Timestamp 格式,而且預覽head 和tail。

dt_format = '%Y-%m-%d %H:%M:%S'
raw_df['Datetime'] = pd.to_datetime(raw_df['Datetime'], format=dt_format)
raw_df.rename({'PJME_MW': 'y', 'Datetime': 'ds'}, axis=1, inplace=True)
print(raw_df.tail(5))
print(raw_df.describe())
複製代碼

能夠看到時間從2002年到2018年,並且順序彷佛也對。

ds        y
8734 2002-01-01 01:00:00  30393.0
8735 2002-01-01 02:00:00  29265.0
8736 2002-01-01 03:00:00  28357.0
8737 2002-01-01 04:00:00  27899.0
8738 2002-01-01 05:00:00  28057.0
                        ds        y
140250 2018-08-02 20:00:00  44057.0
140251 2018-08-02 21:00:00  43256.0
140252 2018-08-02 22:00:00  41552.0
140253 2018-08-02 23:00:00  38500.0
140254 2018-08-03 00:00:00  35486.0
複製代碼

爲了保險起見,咱們對時間軸進行排序。

raw_df = raw_df.sort_values(by=['ds']) # sort by time
print(raw_df.tail(5))
print(raw_df.describe())
複製代碼

根據時間點劃分測試集與訓練集

split_dt = pd.Timestamp('2015-01-01 00:00:00')
train_df = raw_df[raw_df['ds'] < split_dt]
test_df = raw_df[raw_df['ds'] >= split_dt]
複製代碼

prophet 建模

這裏咱們依然採用默認的參數。

model = Prophet()
model= model.fit(train_df)
y_hat  = model.predict(test_df)
y_test = model.predict(test_df)
fig = model.plot(y_test)
a = add_changepoints_to_plot(fig.gca(), model, y_test)
print(mean_absolute_error(test_df['y'].values, y_test['yhat']))
model.plot_components(y_test)
複製代碼

結果

咱們能夠看到偏差結果居然是3105。相比於咱們第2篇的模型以及kaggle大神的5183要好不少。

可是咱們的代碼幾乎沒有變,也沒有任何的參數調優啊?

3105.4775855473995
複製代碼

緣由解析

其實提升的緣由很簡單,數據有bug!!

其實不是bug,應該是髒數據,並且隱藏的有點深。 通常咱們預覽head(5) 和tail(5),看到順序是一致的,咱們內心就會默認這個數據是按照時間順序排列的。其實不是!

當咱們預覽前30行數據的時候就會發現,日期居然是倒序的。並且中間還加了莫名其妙的1月1號。

咱們代碼中不起眼的一句 sort_value 居然讓偏差巨幅下降。

而prophet 須要根據時間的前後順序,提取必定的週期信號。因此觀測對象在時間軸上的前後順序無疑起着決定性做用。

相比於第3 篇介紹的lightgbm,它不會考慮時間的連續性,所以須要咱們顯式的告訴它一些時間特性,好比咱們提取day,year 等信息。

因此prophet 沒有那麼差,只不過咱們須要對數據進行清理,尤爲是時間前後順序。

Datetime,PJME_MW
2002-12-31 01:00:00,26498.0
2002-12-31 02:00:00,25147.0
2002-12-31 03:00:00,24574.0
2002-12-31 04:00:00,24393.0
2002-12-31 05:00:00,24860.0
2002-12-31 06:00:00,26222.0
2002-12-31 07:00:00,28702.0
2002-12-31 08:00:00,30698.0
2002-12-31 09:00:00,31800.0
2002-12-31 10:00:00,32359.0
2002-12-31 11:00:00,32371.0
2002-12-31 12:00:00,31902.0
2002-12-31 13:00:00,31126.0
2002-12-31 14:00:00,30368.0
2002-12-31 15:00:00,29564.0
2002-12-31 16:00:00,29098.0
2002-12-31 17:00:00,30308.0
2002-12-31 18:00:00,34017.0
2002-12-31 19:00:00,34195.0
2002-12-31 20:00:00,32790.0
2002-12-31 21:00:00,31336.0
2002-12-31 22:00:00,29887.0
2002-12-31 23:00:00,28483.0
2003-01-01 00:00:00,27008.0
2002-12-30 01:00:00,27526.0
2002-12-30 02:00:00,26600.0
2002-12-30 03:00:00,26241.0
2002-12-30 04:00:00,26213.0
2002-12-30 05:00:00,26871.0

複製代碼

高階的prophet 參數配置

默認的prophet 配置可讓咱們快速的創建baseline,可是對於時間序列,咱們還能夠自定義參數。 這裏咱們貼出代碼,方便你們參考。

添加假日信息

好比咱們能夠添加美國的假日信息。該假日信息會傳遞給prophet holiday 參數

holidays = make_holidays.make_holidays_df(range(2002, 2018, 1), 'US')
複製代碼

自定義條件

對於同一個週期,咱們能夠定義一些條件進行區分。

  • 好比一週的週期都是7天,可是前5天是工做日,後兩天是週末。
  • 或者一天能夠分爲白天和晚上,它們的週期依然都是1天。
  • 一年可能會有淡季和旺季,好比夏天多是用電高峯期(製冷設備),咱們能夠這樣劃分
raw_df['on_season'] = (raw_df['ds'].dt.month > 4) | (raw_df['ds'].dt.month < 8)
raw_df['off_season'] = ~(raw_df['ds'].dt.month > 4) | (raw_df['ds'].dt.month < 8)


raw_df['night'] = (raw_df['ds'].dt.hour > 20) | (raw_df['ds'].dt.hour < 8)
raw_df['daylight'] = ~(raw_df['ds'].dt.hour > 20) | (raw_df['ds'].dt.hour < 8)

raw_df['weekend'] = raw_df['ds'].dt.week > 5
raw_df['workday'] = ~raw_df['weekend']

複製代碼

創建複雜prophet模型

咱們能夠將模型默認的yearly_seasonality,weekly_seasonality,daily_seasonality 設置爲False,而後添加本身定義的週期,或者疊加咱們定義的條件。 此外,對於每個週期,咱們能夠定義他的權重(prior_scale)。

model = Prophet(
    holidays=holidays,
    changepoint_prior_scale=0.5,
    holidays_prior_scale=10,
    seasonality_prior_scale=10,
    yearly_seasonality=False,
    weekly_seasonality=False,
    daily_seasonality=False)
model.add_country_holidays(country_name='US')
model.add_seasonality(name='half_daily',period=0.5,fourier_order=12)
model.add_seasonality(name='daylight_daily',period=1,fourier_order=24,condition_name='daylight')
model.add_seasonality(name='night_daily',period=1,fourier_order=24,condition_name='night')
model.add_seasonality(name='weekend_weekly',period=7,fourier_order=14,prior_scale=10,condition_name='weekend')
model.add_seasonality(name='workday_weekly',period=7,fourier_order=14,prior_scale=10,condition_name='workday')
model.add_seasonality(name='on_season_half_year',period=182.625,fourier_order=24,prior_scale=10,condition_name='on_season')
model.add_seasonality(name='off_season_half_year',period=182.625,fourier_order=24,prior_scale=10,condition_name='off_season')
model.add_seasonality(name='on_season_year',period=365.25,fourier_order=24,prior_scale=10,condition_name='on_season')
model.add_seasonality(name='off_season_year',period=365.25,fourier_order=24,prior_scale=10,condition_name='off_season')
複製代碼

運行結果爲:

3043.2366637873324
複製代碼

相比於默認的參數偏差有所下降。

咱們依然附上分解的結果:

總結

本文成功解決了prophet 建模時遇到的坑,將偏差從5000多下降到3000多。 經過本文能夠了解到:

  • 時間序列的順序對於prophet模型的重要性
  • 有必要對時間序列的順序作出驗證。
  • 能夠自定義假日,週期,權重等參數,讓模型更貼近預測的時間序列特性
相關文章
相關標籤/搜索