Szeregi czasowe z wykorzystaniem Propheta

prop_zajawka
Analiza danych Data Science Szeregi czasowe Sztuczna Inteligencja
2022-07-30

Prophet to model opublikowany przez zespół Facebooka w 2017 roku. Mimo, że trochę czasu minęło od jego oficjalnej publikacji, to odnoszę wrażenie, że w 2020 roku zrobił największy szum wokół siebie. Do dziś jest powszechnie używany i zachwalany!

Czym właściwie jest Prophet i jak działa?

Prophet jest modelem służącym do przewidywania szeregów czasowych. Nie jest to jednak kolejny zwykły model statystyczny.

Co go wyróżnia?

  • Jest stworzony z myślą o typowych problemach spotykanych w biznesie.
  • Z łatwością dostosowuje się do zmian w trendzie (czego niestety nie można powiedzieć np. o ARIMA).
  • Mimo, że jest to model przystosowany do predykcji szeregów czasowych (a te z natury są univariate), to istnieje możliwość uwzględnienia zewnętrznych czynników w predykcji.
  • Uwzględnia różne sezonowości (dzienną, tygodniową i roczną) podczas gdy standardowe modele uzwględniają zazwyczaj tylko roczną.
  • Dodatkowo uwzględnia dni wolne (holidays), co również nie jest spotykane w standardowych modelach statystycznych.
  • Większość hiperparametrów jest intuicyjna, więc da się z niego łatwo korzystać bez szczegółowej znajomości teorii.

Prophet wykorzystuje dekompozycję szeregu czasowego na trzy główne komponenty: trend, sezonowość i dni wolne. Wzór przedstawia się w następujący sposób:

gdzie g(t) reprezentuje trend, s(t) – sezonowość, a h(t) reprezentuje wpływ dni wolnych. Osoby zainteresowane matematyczną teorią kryjącą się za każdą z trzech funkcji odsyłam prosto do źródła: Prophet paper.

Prophet w praktyce

Do czego wykorzystać Propheta? Oczywiście do predykcji szeregów czasowych! Ale nie tylko! Prophet oprócz predykcji dostarcza również przedziały ufności. Możemy je wykorzystać do detekcji outlierów (wartości odstających). Dobra, dobra dość gadania, popatrzmy sobie na przykłady!

Rozważymy sobie dynamikę produktu krajowegi brutto w USA. Dane nt. PKB w USA można pobrać tutaj.

Rozważymy dane kwartalne. Interesuje nas dynamika zatem musimy nieco przekształcić oryginalne dane – podzielimy je przez wartość z tego samego kwartału poprzedniego roku.

# imports
import time

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from prophet import Prophet as fbProphet
from sklearn.metrics import mean_squared_error
from sktime.forecasting.arima import AutoARIMA
from sktime.forecasting.fbprophet import Prophet
from sktime.performance_metrics.forecasting import MeanAbsolutePercentageError as MAPE


# read and set frequency
df = pd.read_csv('./GDP.csv')
df.columns = [item.lower() for item in df.columns]
df['date'] = pd.to_datetime(df['date'])
df.set_index('date', inplace=True)
df = df.resample('Q').sum() # change frequency from quarter beginning to quarter end

# use only after 2000 year
df = df[df.index>=pd.to_datetime('2000-01-01')]
df['gdp_prev'] = df['gdp'].shift(4)
df['dynamic'] = df['gdp']/df['gdp_prev']

# plot data
df['dynamic'].plot(figsize=(12,8))
plt.plot()

Na wykresie doskonale widać wpływ Covida na PKB. Oczywiście tak nietypowego zachowania nie da się przywidzieć. Dlatego też dla celów predykcji skupimy się na danych tylko do 2019 roku. Dokładniej nasz model będzie się uczył na danych do końca 2015 roku i spróbujemy przewidzieć dynamikę w latach 2016-2019.

Stwórzmy sobie model:

# define y_train and y_test
y = df['dynamic'].dropna()
y_train = y[y.index < pd.to_datetime('2016-01-01')]
y_test = y[(y.index >= pd.to_datetime('2016-01-01'))&(y.index < pd.to_datetime('2020-01-01'))]

# define and fit model
model_prophet = Prophet(freq='Q', yearly_seasonality='auto', weekly_seasonality=False, daily_seasonality=False)
model_prophet.fit(y_train)

# prediction
y_pred_proph = model_prophet.predict(y_test.index)

# plot results
plt.figure(figsize=(12, 8))
plt.plot(y[y.index < pd.to_datetime('2020-01-01')], label='actual')
plt.plot(y_pred_proph, label='prediction Prophet')
plt.show()

Wynik predykcji nie zachwyca jakoś w szczególności, ale nie jest też bardzo zły. Całkiem dobrym sposobem porównania byłoby zestawienie z wynikiem z modelu, który jest alternatywą dla Propheta. Spróbujmy zatem przeprowadzić dokładnie taką samą symulację dla ARIMA.

Po wykresie od razu widać przewagę Propheta, ale zróbmy sobie bardziej szczegółowe porównanie:

  • Czas uczenia Propheta – 0.9 sek
  • Czas uczenia ARIMA – 34.2 sek
  • MAPE Prophet – 0.95%
  • MAPE ARIMA – 1.05%

Uczenie ARIMA zajęło ponad 35 razy dłużej a osiągnęła gorszy wynik. Zdecydowanie wskazuje to na wyższość Propheta 😁 W dodatku nie skorzystaliśmy z niektórych możliwości jakie daje Prophet – np. wykrzystanie zewnętrznego regressora, który mógłby nasz wynik dodatkowo poprawić.

Prophet jako detektor outlierów

Prophet oprócz tego, że może dostarczyć nam całkiem niezłą predykcję w stosunkowo krótkim czasie, to jeszcze może nam pomóc w eliminacji obserwacji odstających (outlierów). Czasami nie przykłada się dużej wagi do outlierów. Takie podejście jest błędne bo mogą one baaaardzo mocno zaburzyć predykcję. Jednak dokładne omówienie tej kwestii zostawmy sobie na osobny wpis.

Teraz zastanówmy się nad tym jak Prophet może nam pomóc w identyfikacji takich obserwacji. Zdefiniujmy sobie funkcję, która pomoże nam wyłonić outliery. Za outliery uważamy te obserwacje, które znajdują się poza przedziałami ufności Propheta. Uczymy model korzystając z biblioteki prophet. W ten sposób otrzymamy ramkę danych z informacją o predykcji modelu dla poszczególnych punktów oraz górnej i dolnej granicy przedziału ufności. Na tej podstawie możemy wyznaczyć, które obserwacje są outlierami. Jeżeli nie chcemy usuwać takich obserwacji z danych tylko obniżyć ich wartość, to możemy wykorzystać wartości zwrócone dla nich przez Propheta.

def prophet_adjust(y, interval_width=0.8, freq='Q'):
    prophet_mod = fbProphet(yearly_seasonality='auto', seasonality_mode='additive', 
                          interval_width=interval_width)
    y_proph = y.reset_index()
    y_proph.columns = ['ds', 'y']
    prophet_mod.fit(y_proph)
    pred = prophet_mod.predict(y_proph)[['ds', 'yhat_lower', 'yhat_upper', 'yhat']]
    pred['actual'] = y_proph['y']
    pred['is_outlier'] = (pred['actual'] < pred['yhat_lower']) | (pred['actual'] > pred['yhat_upper'])
    pred['adjusted'] = pred['actual'].mask(pred['is_outlier'], pred['yhat'])
    return pred.set_index('ds').asfreq(freq)
    

# find outliers
outliers_df = prophet_adjust(y, interval_width=0.9)
outliers = outliers_df['actual'].where(outliers_df['is_outlier'])

# plot results
plt.figure(figsize=(12,8))
plt.plot(outliers_df['actual'], label='actual')
plt.fill_between(outliers_df.index, outliers_df['yhat_lower'], outliers_df['yhat_upper'], 
                    color='#d6d6d6', label='confidence intervals')
plt.scatter(outliers_df.index, outliers, color='red', label='outliers')
plt.legend()
plt.show()

Na wykresie wyraźnie widać, że taki model poradził sobie bardzo dobrze. Prawie wszystkie “wyłapane” obserwacje są outlierami. Wątpliwości budzi jedynie obserwacja z 2004 roku. Okazuje się, że Prophet sprawdza się bardzo dobrze w detekcji outlierów w szeregach czasowych, co na ogół jest sporym wyzwaniem, ponieważ musimy mieć na uwadze, że zachodzi zależność danych od czasu.

Podsumowanie

Zrobiliśmy sobie krótki przegląd podstawowych możliwości Propheta. Podchodząc do tego w bardzo prosty sposób udało nam się bardzo dużo dowiedzieć o danych – całkiem trafna predykcja czy wyłonienie obserwacji odstających. Warto przypomnieć, że to co pokazaliśmy to nie wszystko co Prophet potrafi, a już w takiej postaci daje zadawalające efekty. Warto pamiętać o tym modelu gdy pracujemy z szeregami czasowymi. Mam nadzieję, że przedstawiona wiedza przyda Ci się przy pracy z szeregami czasowymi. Dzięki, że jesteś tu ze mną 😉 Będę ogromnie wdzięczna za pozostawienie po sobie śladu w komentarzu 😊😊😊

Subskrybuj
Powiadom o
guest
2 komentarzy
Najnowsze
Najstarsze Najczęściej oceniane
Zobacz wszystkie komentarze
Maciej
Gość
Maciej
11 miesięcy temu

Chyba to było za skomplikowane, albo wymaga większego opisu-wnioskuję z braku komentarzy.