第一篇文章咱們用一個小時從安裝prophet 到完成預測,而且咱們將結果對比了naive的季節信號分解(這個能夠看做是簡版的prohet,純原創),發現效果略遜色於naive的方法。bash
後來咱們用lightgbm 再次預測一樣的數據集,發現結果遠遠賽過prophet。post
咱們也對比了咱們的預測結果與kaggle上公開的方法與測試結果,發現咱們的lightgbm的結果更好,多是由於咱們作了必定參數調優。學習
咱們的prophet 結果和kaggle大神的預測結果同樣「好」 (相比於lghtgbm都不好)。測試
可是facebook 大廠旗下prophet的super power 真的弱爆了嗎?ui
對於深度學習來講,沒有標準化會直接讓你的模型跑飛。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]
複製代碼
這裏咱們依然採用默認的參數。
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 配置可讓咱們快速的創建baseline,可是對於時間序列,咱們還能夠自定義參數。 這裏咱們貼出代碼,方便你們參考。
好比咱們能夠添加美國的假日信息。該假日信息會傳遞給prophet holiday 參數
holidays = make_holidays.make_holidays_df(range(2002, 2018, 1), 'US')
複製代碼
對於同一個週期,咱們能夠定義一些條件進行區分。
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']
複製代碼
咱們能夠將模型默認的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多。 經過本文能夠了解到: