Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

statduck

베이지안을 활용한 비즈니스 시계열 예측 본문

Bayes Stat

베이지안을 활용한 비즈니스 시계열 예측

statduck 2023. 2. 14. 23:07

Prophet Model

Bayesian Concept을 활용해 비즈니스 시계열 추정을 하는 가장 대표적인 모형이 페이스북에서 릴리즈한 Prophet Model이다.

https://peerj.com/preprints/3190/

y(t)=g(t)+s(t)+h(t)+εt

 

- g(t): 추세 함수(비주기적 변화 포착)

- s(t): 주기적 변화(예) 주간 혹은 연간 계절성)

- h(t): 비규칙적인 이벤트

 

세 가지의 성분을 덧셈으로 연결하였다.(GAM: 일반화가법모형) GAM은 반응평균의 함수를 독립변수들의 함수꼴로 모형화시킨것을 말한다. 컨셉은 결국 시계열 그래프를 가장 그럴듯한 함수 개형을 찾아서 피팅시키는 건데, 그 함수 개형을 크게 3개로 나눠 전반적인 트렌드, 반복하는 시즈널리티, 불규칙적인 이벤트 이렇게 세가지 함수를 각각 찾아서 더해주자는 것이다.

g(E(Y|X))=β0+f1(X1)+f2(X2)++fp(Xp)

 

https://github.com/facebook/prophet/tree/main/python/prophet

(implementation code)

 

먼저 함수에서 굵직한 트렌드를 어떻게 베이지안으로 잘 찾아낼 수 있는건지, 다음을 보자.

Trend Model (logistic growth model)

g(t)=C1+exp(k(tm))

g(t) 함수 form을 이와 같이 가정하자. k는 성장률(growth rate), m은 위치조정 파라미터(offset parameter), C는 자연 성장 시 최대값(carrying capacity)을 의미한다. 자연 성장세를 가정하여 로지스틱 함수 꼴을 이용하였다.

 

기본적인 로지스틱 함수 1 / (1+e^(-x)) 의 개형이다

 

기본적인 개형에서 1), 2)의 변화를 줘서 조금 더 유연한 모형을 만들어보자.

 

1) C의 경우 시간이 변함에 따라 곱해주는 상수 값이다. 전체적인 타겟 볼륨이 시간이 흐름에 따라 커질 수 있으므로(예시) 타겟이 금액인 경우, 시간이 흐름에 따라 사람들의 전체적인 소비액이 증가한다고 가정) 상수 대신 변수 C(t)로 대체한다.

 

2) k의 경우 값이 커질수록 기울기가 가파르게 변한다. 변화율을 조절하는 파라미터인 k도 시간에 따라 바뀔 수 있다. 다시 말해서 시점에 따라서 성장세(그래프 기울기)를 조절해주는 방식이다. 따라서 k대신 k+a(t)Tδ가 들어가야한다. 

 

시점에 따라 성장세를 조절해주는 방식이 중요한 컨셉인 것 같은데, 그러한 시점들은 어떻게 정할 수 있을까?

 

Trend changepoints

aj(t)={1,iftsj0,o.w.

 

δ는 벡터인데 각 성분은 sj시간에 벌어진 사건의 변화율(change in rate)을 의미한다. 즉 a(t)Tδ는 현재 시점(t)기준으로 이전 시점들(sj)에 벌어진 사건의 영향력(비율에 미치는 영향 δ)의 합을 의미한다.

 

δ는 sparse prior로 δjLaplace(0,τ) 를 준다. (라플라스 분포는 정규 분포가 더 뾰족해진 형태이다.)

이 때 sj는 성장률을 변화시키는 사건이나 여러 이벤트들을 반영해주는 식이다. 디폴트 세팅으로는 라플라스 분포를 사전 분포로 부여하고 hierarchical bayesian 방식으로 학습한다.

 

위의 사항을 반영한 최종 모형은 다음과 같다.

g(t)C(t)1+exp((k+a(t)Tδ)(t(m+a(t)Tγ))

 

그리고 모형에서는 오버피팅을 방지하기 위해 디폴트로 전체 시계열 자료의 앞 80%에서 변화점을 찾는다. 그리고 디폴트로는 최대 25개의 점을 찾는다. 이 점들은 결국 함수의 기울기가 급격하게 변화하는 점을 의미하는 것이다. 

 

Seasonality

주기성 반영을 위해 아래 푸리에 급수를 이용한다. 

s(t)=Nn=1(ancos(2πntP)+bnsin(2πntP))=X(t)β

P는 우리가 반영을 원하는 주기이다(7이면 주별 효과, 365.25이면 연도별효과)

β=[a1,b1,,aN,bN]T 

 

예를 들어, N=10이고 연도 효과를 반영하는 경우는 다음과 같다.

s(t)=[cos(2π(1)t365.25,,sin(2π(10)t365.25)]

이 때 계수는 generative model로서 βN(0,σ2)를 취한다.

 

Holidays and Events

Z(t)=[1(tD1),...,1(tDL)]h(t)=Z(t)κ,κN(0,ν2)

Di는 과거 이벤트들 목록이다. 각 이벤트는 κiN(0,ν2i)를 가진다. 해당 파트가 prophet의 큰 장점 중 하나다. prophet은 결국 GAM 모형이기에 이런 공휴일 효과를 회귀로 반영해 줄 수 있다. 그 반영 효과는 ν에 의해 좌우되는데 이는 각 이벤트가 가지고 있을거라고 예상되는 효과(정규분포의 분산)이다. 

 

깃헙 코드구조는 다음과 같다.

construct_holiday_dataframe(self,dates): make_holidays_df

make_holiday_features(self, dates, holidays): holidays는 construct_holiday_dataframe return all_holidays

 

make_holiday_features를 돌린 결과 서로 다른 연도의 공휴일이더라도 이름이 같으면 같은 공휴일로 인식되고 window에 따라 다르게 반영됨을 확인할 수 있다.(추후 확인 한 번 더 필요)

 

Implementation

https://facebook.github.io/prophet/docs/quick_start.html#python-api

실제 파이썬에서의 동작은 다음과 같이 이루어진다.

Pythono 3.7.9 

 

# Setting
import pandas as pd
from prophet import Prophet

# 외부 url로 데이터 불러올 때 에러 방지
import ssl
ssl._create_default_https_context = ssl._create_unverified_context

# 데이터 수입
df = pd.read_csv('https://raw.githubusercontent.com/facebook/prophet/main/examples/example_wp_log_peyton_manning.csv')

# 모델링
m = Prophet()
m.fit(df)

# periods 기준으로 기존 데이터 프레임에 날짜를 더하여 날짜 테이블을 만듬
future = m.make_future_dataframe(periods=365)
future.tail()

forecast = m.predict(future)
'''
forecast의 열은 다음으로 이루어짐
Index(['ds', 'trend', 'yhat_lower', 'yhat_upper', 'trend_lower', 'trend_upper',
       'additive_terms', 'additive_terms_lower', 'additive_terms_upper',
       'weekly', 'weekly_lower', 'weekly_upper', 'yearly', 'yearly_lower',
       'yearly_upper', 'multiplicative_terms', 'multiplicative_terms_lower',
       'multiplicative_terms_upper', 'yhat']
'''

# 시각화 (plotly로 동적시각화도 가능)
m.plot(forecast)
m.plot_components(forecast)

# 성장 총량

m = Prophet(growth='logistic')
# growth 파라미터가 결국 위 식의 g(t) 개형을 결정
m.fit(df)

# 최소 최대를 설정: 로지스틱 회귀의 경우에만 적용이 가능
df['floor'] = 1.5
future['cap'] = 6
future['floor'] = 1.5
m = Prophet(growth='logistic')
m.fit(df)
fcst = m.predict(future)
fig = m.plot(fcst)

# Trend changepoints
from prophet.plot import add_changepoints_to_plot
fig = m.plot(forecast)
a = add_changepoints_to_plot(fig.gca(), m, forecast)

# 변화점 직접 명시하는 방식
m = Prophet(changepoints=['2014-01-01'])
forecast = m.fit(df).predict(future)
fig = m.plot(forecast)

 

- cap, floor가 어떻게 범위를 결정짓는지?

로지스틱으로 함수 개형이 설정되어있고, cap이 지정된 경우 위와 같은 방식으로 범위 밖의 변수를 조절한다.

 

 

 

Comments