Gunosyデータ分析ブログ

Gunosyで働くデータエンジニアが知見を共有するブログです。

Facebookの予測ライブラリProphetを用いたトレンド抽出と変化点検知

Gunosyデータ分析部アルバイトの五十嵐です。

Gunosyには大規模なKPIの時系列データがあります。 今回はKPIの時系列分析を行なった際に得た知見についてまとめたいと思います。 具体的にはFacebookが開発した時系列予測ツール Prophetを用いて、KPIのトレンド分析を行いました。

時系列予測について

以前、 KPIのトレンド抽出について以下のブログで紹介しました。

data.gunosy.io

ここでは時系列データをトレンド成分と季節成分に分解し、トレンドの把握を容易にする分析を行なっていました。 KPIのトレンドを知ることでサービスの状態を把握することが目的でした。 今回はこの分析をさらに進め、トレンドの変化点を自動で検知する手法について紹介します。 変化点検知を自動化することで、トレンドに変化が発生した際に迅速に気づくことができるようになりました。 また、時系列データの分析が容易にできるライブラリProphetについても紹介します。

Prophetについて

ProphetはFacebookが開発した時系列予測ツールです。 Prophetの特徴は、時系列データのトレンド抽出ができるだけでなく、トレンドの変化点の検出が出来る点です。 これはProphetの時系列モデルが予測線をスプライン曲線として表現しているためです。 スプライン曲線であるため複数の制御点を有しており、各点での変化量を調べることによって変化点を検出できる仕組みとなっています。  y(t)を時刻 tにおける予測値としたモデル式は以下の通りです。

 y(t) = g(t) + s(t) + h(t) + \epsilon_t

 g(t)がトレンド成分、 s(t)が季節成分、 h(t)が祝日などのイレギュラーな日程における効果成分、 \epsilon_tが誤差項です。 この式の形は一般化加法モデル(Generalized Additive Model)と似た形となっています。 詳しくは、Prophetの原論文をご参照ください。

ProphetはRとPythonでのライブラリが公開されています。 今回はPythonで実装しました。 Prophetは以下のコマンドで簡単にインストールできます。

$ pip install fbprophet

実装例

今回はWikipediaのTwitterのページのアクセス数の時系列データを使って実装してみます。 データはWikipediaのPageviews Analysis を用いて収集し、2015/07/01 ~ 2017/12/05 の期間のアクセス数の日次データを利用しました。 データに関してはいつもの通りPandasを利用して集計しています。 data.gunosy.io

まずデータを可視化してみます。

import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

# read csv
df = pd.read_csv('pageviews-20161201-20171130.csv')

df['Date'] = pd.to_datetime(df['Date'])
df = df.set_index('Date')
df.plot()

f:id:yusuke-ikarashi:20171206181243p:plain

アクセス数は常に変動しており、どのような傾向があるかは目視では難しいと思います。 このデータに対してProphetを適用し、将来のアクセス数予測と、トレンド性と周期性の抽出と、変化点の抽出を行います。

モデルの適用

Prophetを使用する際において、トレンド性を線形か非線形に指定する必要があります。 どちらが最適かはデータに依存しており、今回は線形モデルで実装しました。 モデルの適用は以下の通りです。

from fbprophet import Prophet

df_input = df.reset_index().rename(columns={'Date':'ds', 'Twitter':'y'})
model = Prophet(growth='linear', daily_seasonality=False)
model.add_seasonality(name='monthly', period=30.5, fourier_order=5)
model.fit(df_input)

今回は時間帯別のデータは利用していないため、日周期daily_seasonalityは無効にしました。 また長期間のデータがあるため、年周期・週周期を有効にし、model.add_seasonalityで月周期性を追加しました。 使用するデータの期間によって、使える周期は異なるため、過剰適合しないような周期性の設定が必要です。

また見て分かる通り、scikit-learnの機械学習モデルと似た形です。 Prophetは専門知識がなくても、簡単に実装できるライブラリとなっています。

将来のアクセス数予測

先ほど作成し学習させたモデルから、将来のアクセス数を予測させてみます。 今回は90日後までのアクセス数を予測させました。

future = model.make_future_dataframe(periods=90)
forecast = model.predict(future)
model.plot(forecast)
plt.ylim(2000,7000)
plt.show()

f:id:yusuke-ikarashi:20171206181351p:plain

2017年12月 ~ 2018年2月までのアクセス数を予測させています。 年末からアクセス数が伸び日次アクセス数6,000を超える予測です。 またその後にアクセス数が減少し単調増加ではないように、周期性を用いて予測していることがわかります。

トレンド性と周期性の抽出

次に学習させたモデルから、トレンド性と周期性を取り出します。

model.plot_components(forecast)
plt.show()

f:id:yusuke-ikarashi:20171206183126p:plain

上から、トレンド性、月周期性、年周期性、週周期性のグラフです。 それぞれのグラフから読み取れることは以下の通りです。

  • [トレンド性] 2016年6月頃からトレンドが上昇傾向
  • [月周期] 月の真ん中と終わりにアクセス数の下降傾向
  • [年周期] 1月の初旬に急上昇する傾向や8月の中旬などに急下降する傾向
  • [週周期] 日曜日から金曜日にかけて徐々に下降していく傾向

アクセス数の予測において1月頃からアクセス数が急上昇したグラフを作成できたように、データの周期性をうまく抽出できています。 トレンド性においては、線形モデルの傾きが正のためアクセス数が上昇傾向にあることが把握できます。

変化点抽出

最後に、トレンドの変化点検知を行います。 先ほど作成したトレンド性のグラフを見れば2016年6月頃からトレンドが上昇傾向にあることが分かりますが、これを自動で行えるようにします。

Prophetはモデルを構築する際に、データの前半80%の部分に変化点候補を作成しています。 その変化点候補の中から変化量が高い点のみを変化点とすることで、自動的に変化点を発見することができます。 まずはそれぞれの変化点候補の変化量を可視化してみます。

import seaborn as sns

# add change rates to changepoints
df_changepoints = df_input.loc[model.changepoints.index]
df_changepoints['delta'] = model.params['delta'].ravel()

# get changepoints
df_changepoints['ds'] = df_changepoints['ds'].astype(str)
df_changepoints['delta'] = df_changepoints['delta'].round(2)
df_selection = df_changepoints[df_changepoints['delta'] != 0]
date_changepoints = df_selection['ds'].astype('datetime64[ns]').reset_index(drop=True)

# plot
sns.set(style='whitegrid')
ax = sns.factorplot(x='ds', y='delta', data=df_changepoints, kind='bar', color='royalblue', size=4, aspect=2)
ax.set_xticklabels(rotation=90)

f:id:yusuke-ikarashi:20171208123624p:plain

変化点を抽出するために、変化量が小数点第3位以下の点を切り落としました。 グラフを見ると、3つの期間で変化点が存在しています。 2015年9月頃にアクセス数の下降の変化があり、2016年6月から大きい上昇の変化があることが分かります。また2016年10月〜2017年2月にかけて上昇の変化があることも分かります。

この変化点をグラフと一緒にプロットしてみました。 変化点の位置を点線で表示しています。

figure = model.plot(forecast)
for dt in date_changepoints:
    plt.axvline(dt,ls='--', lw=1)
plt.ylim(2000,7000)
plt.show()

f:id:yusuke-ikarashi:20171206183149p:plain

周期性の変化が大きいため、目視では変化点付近でトレンドが変化していることはあまり分かりません。 しかしアクセス数が徐々に伸びてきているように、変化点からトレンドが変化しているようです。

Slackを利用した自動化

ここまでの分析を自動化し、変化点についての情報をSlackで通知しています。

f:id:yusuke-ikarashi:20171208123950p:plain:w300

これによって、手動でトレンドの分析を行わずに済むため、トレンドの変化に気づくことが楽になりました。 この例では最新の変化点が半年以上昔ですが、通知の対象を新しい変化点のみにすることで、新しい変化についても分かりやすくなります。

まとめ

Prophetを用いてページアクセス数のデータ対して時系列分析を行なってみたところ、トレンド性と周期性を把握することができ、変化点も特定することができました。この手法をKPIなどに用いることにより、データのトレンド性と周期性を把握できるだけでなく、トレンドの変化があった際に素早く気づくことができるようになります。

ただし、トレンドの変化に気づいたとしても、トレンドの変化の原因については時系列分析では分かりません。 トレンドの変化があった時に何が原因であったかを分析することが次のステップとして重要となるでしょう。 以下のブログで毎日の出来事を自動記録する事例を紹介しました。

data.gunosy.io

変化点を発見した後に、記録されたその日の出来事を確認することでトレンドの変化の原因の特定が簡単になりそうです。 これからも、プロダクトの改善を素早くできる環境を作っていきたいと思います。