저번 포스팅에 이어서 prophet의 seasonality를 조절하는 방법에 대해서 알아보려고 한다.
계절성이란 일정한 빈도를 가지고 주기적으로 반복되는 패턴으로, 해마다 특정한 때 혹은 1주일마다 특정 요일에 나타나는 것 같은 요인이 시계열에 영향을 줄 때 계절성 패턴이 나타난다.
prophet에서는 아무런 설정도 하지 않은 경우 자동으로 연간, 주간 계절성을 파악해서 맞춘다. 그리고 이 내용에 대해서는 plot_yearly, plot_weekly를 이용해서 확인할 수 있다.
from prophet.plot import plot_yearly
plot_yearly(m)
학습을 한 모델을 plot_yearly에 넣으면 다음과 같이 그래프가 나온다.
Prophet에서 계절성을 추정하기 위해서 부분 푸리에 합을 사용한다고 한다. yearly_seasonality 옵션의 디폴트 값은 10으로 설정이 되어 있는데, 만약 그 값을 높게 설정하게 된다면 더 유연하게 설정된 그래프를 볼 수 있다.
예를들어 yearly_seasonality = 20인 경우에는
이런 그래프를 볼 수 있고,
yearly_seasonality = 30인 경우에는
더더욱 디테일적인 그래프를 볼 수 있다.
from prophet.plot import plot_weekly
plot_weekly(m)
weekly_seasonality의 디폴트 값은 3으로, 기본값을 가지고 학습을 한 경우를 그래프로 그리면
이렇게 표현이 되고,
값을 8로 조절을 해보면
모양 자체는 동일하지만 y축의 값이 바뀐 것을 볼 수 있다.
숫자를 높게 설정해서 모델을 학습하게 되면 오버피팅의 위험성이 커지기 때문에 적당한 숫자로 설정하는 것이 필요하다.
기본으로 설정되는 주간, 연간을 제외하고 다른 시간 단위에서의 계절성이 필요하다면 prophet인스턴스를 생성 한 뒤 학습을 하기 전에 add_seasonality를 이용해서 설정할 수 있다.
m_add_sesonality = Prophet()
m_add_sesonality.add_seasonality(name='monthly', period=30.5, fourier_order=5)
m_add_sesonality.add_seasonality(name='weekly', period=7, fourier_order=5)
만약 prophet인스턴스를 생성하기 전에 계절성을 설정하지 못했다면 name = 'weekly', name = 'yearly'등을 통해서 수정할 수 있다. 또한, 지금처럼 일 단위의 데이터가 아니라 시간/분/초단위의 데이터라면 하루 단위라도 계절성이 나타날 수 있는데, 이럴 때에는 name='daily'로 설정하고 period를 1로 설정하면 될 것 같다.
여기서 period는 단어 그대로 기간을 뜻하는데, 내가 설정하는 월간 / 주간 / 일간의 단위가 어떻게 될 지를 정하는 것이다. 예시에서 monthly는 30.5로 되어 있는데 30일인 기간, 31일인 기간이 있어서 30.5로 하는 것이 아닐까 싶다.
그리고 fourier_order의 숫자는 위의 yearly_seasonality, weekly_seasonality를 설정할 때 사용했던 부분 푸리에 합을 의미한다. 숫자가 커지면 학습 데이터셋에 더 세밀하게 맞게 학습이 되고, 숫자가 작아지면 조금 더 러프하게 학습이 된다.
물론 주간,월간,연간 단위로 계절성이 나타날 수도 있지만 상황에 따라서는 2주 단위의 계절성이 나타날 수도 있고, 또는 비수기 / 성수기 나뉘어서 계절성이 나타날 수도 있다. prophet에서는 이러한 경우를 고려해 조건에 따라서 계절성을 부여할 수도 있다.
이런 경우에는 해당 시즌인 경우 True로 표시하는 컬럼하나, 시즌이 아닌 경우 True로 표시하는 컬럼 하나 총 두 개를 만들어서 활용한다.
def is_nfl_season(ds):
date = pd.to_datetime(ds)
return (date.month > 8 or date.month < 2)
df['on_season'] = df['ds'].apply(is_nfl_season)
df['off_season'] = ~df['ds'].apply(is_nfl_season)
예시에서는 NFL시즌인지 / 아닌지에 따라서 계절성을 다르게 부여하고자 해서 is_nfl_season컬럼을 만들고, on_season / off_season 컬럼 두 개를 만들었다.
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')
# 예측을 하는 경우
future['on_season'] = future['ds'].apply(is_nfl_season)
future['off_season'] = ~future['ds'].apply(is_nfl_season)
forecast = m.fit(df).predict(future)
이렇게 시즌별로 나누어서 주간 계절성을 주고 싶은 경우, 인스턴스를 생성할 때 weekly_seasonality를 False로 주어야 한다. 그 뒤에 add_seasonality의 condition_name를 이용해서 on_season / off_season 따로 조건 컬럼을 적용시켜 주면 된다.
이전에 했던 holidays와 동일하게, 해당 효과를 포함시켜서 예측을 원한다면 예측하려는 날짜에도 on_season / off_season 조건 컬럼을 만들어 줘야 적용이 된다.
m.plot_components(forecast)
이후 구성 요소를 그림으로 살펴보면 조건에 따라서 다른 plot을 보여주기 때문에 시즌별로 다른 주기를 살펴보기에도 아주 좋다.
holidays와 seasonality가 너무 과적합이 된다고 생각된다면, 그 강도를 조절할 수 있다. holidays의 경우, holidays는 holidays_prior_scale, seasonality는 prior_scale 매개변수를 설정하면 된다.
# holidays의 경우
m = Prophet(holidays=holidays, holidays_prior_scale=0.05).
# seasonality의 경우
m = Prophet()
m.add_seasonality(
name='weekly', period=7, fourier_order=3, prior_scale=0.1)
이 파라미터의 기본 값은 10으로, 0.1 / 0.05등으로 설정하면 다른 요소들에 비해서 확연히 적은 영향을 미치는 것을 볼 수 있다.
온시즌 / 오프시즌을 나누어서 계절성을 적용할 수 있다는 것을 새롭게 알았다. 마침 weekly_seasonality가 조금 강하게 들어가고 있지않나 하는 고민을 하고 있었는데, prior_scale을 조절해봐야겠다는 생각을 했다.