Prophet İle Zaman Serisi Tahmini

Veri analizi ve görselleştirme çalışmalarının kapsamını biraz daha genişletebilmek adına zaman serileri ve bu işlemlerin ardından yatan matematiksel ve istatiksel işlemleri anlamaya çalışıyorum. scikit-learn bu aşamada en çok karşılaştığım kütüphanelerden biri1. Ancak, bu süreci kolaylaştırmak ve hataları minimize edebilmek adına sunulan Prophet2, Darts3 ve Auto_TimeSeries gibi çeşitli yardımcı kütüphaneler de mevcut. Bu yazıda temel bir düzeyde de olsa Prophet ile ilgili notlarımı paylaşmaya çalışacağım.

AA

Zaman serisi tahmini belirli bir duruma ilişkin zamana bağlı olarak sıralanmış veriler baz alınarak gelecekteki durumlara ilişkin tahminler (forecast) yürütülebilmesini sağlayan kavramsal bir model olarak ifade edilebilir4 5. Hisse senetlerinin açılış fiyatı, bir işletmenin aylık veya yıllık ürün satış miktarı, kullanıcılar tarafından bir arama motoru aracılığı ile belirli konulara yönelik olarak yapılan aramalar, 2 nokta arasındaki uçak seferleri ve yolcuların sayısı, konut satışları, belirli bir zaman dilimi içerisindeki elektrik tüketimi, belirli bir bölgeye ait aylık ve yıllık yağış miktarları, bir süre boyunca her gün atılan adım miktarı gibi pek çok durum örnek olarak verilebilir6.

Prophet

Prophet, R ve Python programlama dilleri için hazırlanmış, Facebook Open Source tarafından geliştirilen, sklearn model API'i izleyen bir tahmin prosedürüdür. Prophet, ayrıştırılabilir (trend, mevsimsellik/sezonsallık, tatiller) bir zaman serisi modeli kullanmakta7.

Doğrusal olmayan zaman serisi verileri kullanılarak yıllık, dönemlik, haftalık ve günlük tahminlerde bulunulmasını mümkün kılan Prophet veri kayıpları ve uç değerleri de başarılı bir şekilde ele almakta.

Yönergeler bu 2 dil için oldukça anlaşılır ve güncel bir şekilde paylaşılmakta. Prosedür ile ilgili açıklama bölümünde ilgili işlemlerin olabildiğince doğru ve hızlı sonuçlar üretmesinin yanı sıra veri bilimcileri ve analistler tarafından kolay bir şekilde ayarlamalarının yapılabileceği bir yapı oluşturmaya çalışıldığı belirtilmekte8.

Aşağıdaki örnek işlemde bir web sitesine ait günlük syafa görüntüleme sayılarını zaman serisi tahmini için kullanacağım. Bu veriyi daha önce yayınladığım Python İle Google Analytics ve Search Console API İşlemleri başlıklı yazıdaki örnek kod parçacığı ile edindim. Ek olarak, tatil verileri ile ilgili olarak da Python İle PDF İçerisinden Metin Kazımak başlıklı yazımı inceleyebilirsiniz.

01-01-2018 ile 31-07-2021 tarihleri arasına ait 1307 gözlemden ve 2 sütundan ((1307, 2)) oluşan sayfa görüntüleme kayıtlarına ait özet:

1.01.2018;13053
2.01.2018;17263
3.01.2018;14330
4.01.2018;12155
5.01.2018;15418
...
27.07.2021;28519
28.07.2021;29134
29.07.2021;30600
30.07.2021;27762
31.07.2021;26816

Tatil verilerine ait özet;

2017-01-01
2017-04-23
2017-05-01
2017-05-19
2017-06-24
...
2021-07-19
2021-07-20
2021-07-21
2021-07-22
2021-07-23

İşlemler için sıklıkla kullanacağım pandas paketi ve bu pakete ait read_csv metodu ilgili CSV dosyalarının yüklenerek DataFrame haline getirilmesi ile başlayabiliriz.

Prophet için veri girişi ds ve y sütunlarını kullanılır. Sütun adlarındaki ds tarihsel değeri (Datetime) ve y ise sayısal olarak tahmin etmek istediğiniz değeri (target veya label) ifade eder.

import pandas as pd

df = pd.read_csv('/pageviews.csv', header=0, names=['ds','y'], delimiter=';')
holidays = pd.read_csv('/holidays.csv', header=0, names=['ds'], delimiter=',')

Python Datetime Modülü İle Tarih - Zaman İşlemleri başlıklı yazımda Python programlama dilinin ön tanımlı olarak tarih veri tipine sahip olmadığından ancak datetime ve pandas gibi paketler aracılığı ile tarihsel işlemler yapılabileceğinden, pandas'ın ayrıca tarih ve zaman veri tiplerini barındırdığından bahsetmiştim. Şimdi, bu bilgi doğrultusunda pandas'a ait to_datetime metodunu kullanalım.

df['ds'] = pd.to_datetime(df['ds'], format='%d.%m.%Y', errors='ignore')
df.head()
ds y
1302 2021-07-27 28519
1303 2021-07-28 29134
1304 2021-07-29 30600
1305 2021-07-30 27762
1306 2021-07-31 26816

Eksik bir veri olup olmadığını df.isna().any() ile kontrol edebiliriz. True değeri dönmesi belirtilen tarih(ler) için Google Analytics tarafında bir kayıt gerçekleştirilmediği ve/ya ilgili kaydın işlemler esnasında silindiği anlamına gelebilir. Buna bağlı olarak eksik veri için yakın ya da ortalama değer kullanılabilir.

Aynı şekilde, date_range metodu ile tarihleri de gözden geçirmemiz uygun olacaktır.

missing_dates = pd.date_range(start=df['ds'].min(), end=df['ds'].max()).difference(df['ds'])
missing_dates

'''
DatetimeIndex([], dtype='datetime64[ns]', freq='D')
'''

Veri ile ilgili istatistikleri son bir defa daha gözden geçirelim.

df.info()

'''
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1307 entries, 0 to 1306
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype         
---  ------  --------------  -----         
 0   ds      1307 non-null   datetime64[ns]
 1   y       1307 non-null   int64         
dtypes: datetime64[ns](1), int64(1)
memory usage: 30.6 KB
'''

df.describe()

'''
y
count   1307.000000
mean    27200.494262
std 17122.890352
min 993.000000
25% 11824.000000
50% 24451.000000
75% 44630.500000
max 65125.000000
'''

Bu aşamaya kadar aslında elimizdeki veriyi kontrol edip Prophet için uygun hale getirmiş olduk.

Bu aşamadan sonrası verinin görselleştirilmesi ve Prophet aracılığı ile işlemden geçirilmesi ile ilgili olacak. İlk olarak gerekli paketleri yükleyelim ve ardından verimize çizgi grafiği aracılığı ile bakalım.

from prophet import Prophet
import matplotlib.pyplot as plt

plt.figure(figsize=(10, 8), dpi=200)
plt.plot(df['ds'], df['y'])
plt.title('Pageview')
plt.xlabel('Date')
plt.ylabel('Views')
plt.show()
Grafik 1

Birkaç noktada ani düşüşler görünüyor. Pandas query metodu ile bu alanlara yakından bakalım.

df.query("ds > '10-10.2020' and ds < '30-11-2020'").plot(x='ds', y='y')
Grafik 2

Eldeki veri ile ilgili her şey yolunda ise Prophet ile ilgili düzenlemelere geçebiliriz. Prophet() ile tahmin modelini tanımlıyoruz. Bu aşamada kullanılabilecek bazı parametrelerden de faydalanabilmekteyiz.

m = Prophet()

Prophet nesnesi ile ilişkili parametreleri dir metodu ile listeleyebiliriz; dir(Prophet). Bu parametrelerden bazılarına aşağıda yer verdim.

Trend Parametreleri

Parametre Açıklama
growth linear ya da logistic değerlerini alır
changepoints Potansiyel dönüm noktalarını tanımlar. Eğer boş bırakılırsa otomatik olarak oluşturulur
n_changepoints Dönüm noktaları için dahil edilecek nokta sayısını belirtir
changepoint_prior_scale Değişim noktasının belirlenme esnekliğini tanımlar

Sezonsallık ve Tatil Parametreleri

Parametre Açıklama
daily_seasonality Günlük sezonsallığı tanımlar. True veya False değeri alır
weekly_seasonality Haftanın günlerine bağlı sezonsallığı tanımlar. True veya False değeri alır
yearly_seasonality Yıllık sezonsallığı tanımlar. True veya False değeri alır
holidays Tatil günlerini tanımlar; ad ve tarih içeren bir DataFrame olarak iletilir
holiday_prior_scale Tatillerin veri değişimdeki etkisini/gücünü belirlemek için kullanılır
seasonality_prior_scale Sezonsal değişimin veri değişimdeki etkisini/gücünü belirlemek için kullanılır

Maçlar ve benzeri dönemsel (aylık, haftalık) olarak söz konusu olan durumlarda weekly_seasonality için False değeri verilip ilgili sezonsallık ayrıca modele eklenebilir.

m = Prophet(weekly_seasonality=False)
m.add_seasonality(name='weekly_on_season', period=7, fourier_order=3, condition_name='on_season')
m.add_seasonality(name='weekly_off_season', period=7, fourier_order=3, condition_name='off_season')

Artık sahip olduğumuz veriler için fit metodu ile öğrenme işlemini başlatabiliriz.

m.fit(df)

Bu aşamadan sonra artık tahmin oluşturma işlemlerine geçebiliriz.

future = m.make_future_dataframe(periods=6, freq='M')

make_future_dataframe içerisinde periods ile tahminin genişliğini, freq ile periods ile belirtilen sayısal değerin gün (day), hafta (week), ay (month), çeyreklik (quarter) ve yıl (year) olarak birimini ifade eder. Örneğin, periods=6 ve freq='M' ile 6 aylık bir dönem belirtilmiş olacaktır. Tahminler için geçmiş tarihleri de kullanacaksak include_history için True değeri verilebilir.

Tahmin edilen veriye ait son 5 satırı görüntüleyelim.

forecast = m.predict(future)
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail()

yhat tahmin edilen değeri, yhat_lower tahmin edilen alt sınır değerini ve yhat_upper tahmin edilen üst sınır değerini ifade eder.

    ds  yhat    yhat_lower  yhat_upper
1308    2021-09-30  64361.761223    47748.397929    81143.462649
1309    2021-10-31  70216.208049    38084.941807    104641.692041
1310    2021-11-30  79226.657448    32424.925871    131655.565849
1311    2021-12-31  83051.808601    14001.976910    156144.670404
1312    2022-01-31  83874.889960    -10767.884182   179685.090374

Grafik aracılığı ile bakalım.

fig1 = m.plot(forecast, figsize=(12, 6))
Grafik 3

Yukarıda örneklendirilen veri kullanarak ve model için herhangi bir parametre tanımında bulunmadan 6 aylık bir tahmin oluşturmak istediğimizde aşağıdaki grafiğe ulaşılmakta.

Sezonsallık, tatiller ve değişim noktalarının veri üzerindeki etkileri oldukça düşük bir etki yaratmış gibi görünüyor. O halde, parametreler için değerler tanımlayarak bizim için en uygun (noktaların tamamına yakınını kapsayan) bir model oluşturmaya çalışalım9.

İlk olarak daha önce PDF içeriğinden kazıdığımız tatil verisini uygun gale getirelim.

holiday = pd.DataFrame({
  'ds': holidays['ds'],
  'holiday': 'start',
  'lower_window': 0,
  'upper_window': 2,
})

Bu arada, add_country_holidays metodu ile çeşitli ülkelere ait ön tanımlı tatil verilerine de ulaşmak mümkün. Örnek olarak Türkiye için tanımlı verileri listeleyelim10.

m.add_country_holidays(country_name='TR')
m.train_holiday_names

'''
0                                       start
1                              New Year's Day
2     National Sovereignty and Children's Day
3                                  Labour Day
4                    Commemoration of Ataturk
5                        Youth and Sports Day
6            Democracy and National Unity Day
7                                 Victory Day
8                                Republic Day
9                               Ramadan Feast
10                      Ramadan Feast Holiday
11                            Sacrifice Feast
12                    Sacrifice Feast Holiday
dtype: object
'''

Bu verileri tekrar eğitim için kullanalım.

m = Prophet(yearly_seasonality=True,
seasonality_prior_scale=5,
holidays=holiday,
holidays_prior_scale=1,
changepoint_prior_scale=5)

m.fit(df)

future = m.make_future_dataframe(periods=6, freq='M')

forecast = m.predict(future)
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail()

Elde edilen tahmin değerlerine bakıldığında, tahminin alt ve üst değerler açıklığının zamana bağlı olarak oldukça arttığı görülebilir.

    ds  yhat    yhat_lower  yhat_upper
1308    2021-09-30  64361.761223    46687.508813    78074.890175
1309    2021-10-31  70216.208049    38979.528898    97530.617444
1310    2021-11-30  79226.657448    30762.968989    126897.075345
1311    2021-12-31  83051.808601    13955.979289    152157.477989
1312    2022-01-31  83874.889960    -14840.257424   178205.721309

Grafik aracılığı ile bakalım.

Grafik 4

Parametreler için verilen değerlerde bir süre sonra yakın sonuçlara ulaşılmaya başlandığı için bir sonraki aşamaya geçip tatillerin, sezonsallık ve haftalık değişimlerin etkileri ayrı grafikler halinde görüntülemeye geçelim.

fig2 = m.plot_components(forecast, weekly_start=1, figsize=(12, 8))
Grafik 5

Grafiklerde de görüldüğü üzere hafta başından sonuna doğru ve yıllar bazında yaz aylarında sayfa görüntülemelerde belirgin bir düşüş görülmekte.

Son olarak değişim noktalarına bakalım.

from prophet.plot import add_changepoints_to_plot
fig = m.plot(forecast)
a = add_changepoints_to_plot(fig.gca(), m, forecast)
Grafik 6

Prophet ile ilgili farklı veriler üzerinden mümkün olduğunca çalışmaya devam edeceğim11 12 13 14 15. Bu süreçte hem Prophet ve benzeri çözümler hem de ML ile ilgili edindiğim bilgileri paylaşmaya devam edeceğim.

Son olarak, Python veya R ile uğraşmadan, bir UI aracılığı ile temel bir şekilde Prophet yeteneklerinden faydalanamak için forecastr'i kullanabilirsiniz14.