🙇‍♂️

時系列データの変化点検知を試す

2024/06/28に公開

これはなに?

時系列解析や時系列予測のタスクとして変化点検知をおこないたい時があります。
変化点検知とは、時系列データの振る舞いが急激に変化する時点を検出する手法で、この記事は2種類の変化点検知手法を時系列データに適用して遊んでみた記事になります。

検知手法

今回遊んでみた変化点検知アルゴリズムは以下の2つです。

  • ChangeFinder
    • 変化点を連続した外れ値だと定義し、2度のSDARアルゴリズムで学習をおこない、スコアを求め、そのスコアが高いものを変化点とする
    • これが分かりやすかったので読んでみてください
  • Pelt(Pruned Exact Linear Time)
    • 制約付き最適化問題を解き、区間分けする。名前の通り、アルゴリズム内部で枝刈りをするので高速に変化点を検知できる
    • これが簡潔で分かりやすい

適用データ

必要なライブラリのインポートをします。
ここで同時に、ChangeFinderライブラリとPelt法の実装されているrupturesライブラリもインポートしておきます。

import changefinder
import ruptures as rpt
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

変化点検知を適用する対象データとして、今回は100時点、200時点、300時点と、3つの並ぶ変化点を持った系列データを作成しました。

np.random.seed(42)

n = 100
data = np.concatenate([
    np.random.normal(0, 1, n),
    np.random.normal(5, 1, n),
    np.random.normal(3, 1, n),
    np.random.normal(3.5, 1, n)
])

df = pd.DataFrame({'value': data})

plt.figure(figsize = (12,5))
plt.plot(df['value'])
plt.title('Dummy Data with Multiple Change Points')
plt.show()

最初の100時点目にある変化点はあからさまな変化点であり、検出はわりかし容易ではないかと思います。対してそれ以降の2つの変化点、特に最後の変化点は人間でも判断が分かれると思います。検出できるでしょうか。

結果

ChangeFinder

ChangeFinderにハイパーパラメータを渡し初期化し、時系列データを1時点ずつ与えスコアを計算していきます。ここでハイパーパラメータの詳細は以下です。今回は一番良さげに検知できているパターンを予め実験し、使用しました。

  1. r : 忘却係数、過去のデータポイントの重みを制御し、値が小さいほど最近のデータに重点が置かれます
  2. order : ChangeFinderが使用するARモデルの次数を指定します
  3. smooth : スコアを平滑化するためのウィンドウのサイズで、大きいほどスコアが滑らかになり、ノイズを減らすことができますが、変化点の検出が遅れる可能性もあります。
cf = changefinder.ChangeFinder(r=0.01, order=1, smooth=7)
scores = [cf.update(point) for point in data]

df = pd.DataFrame({'value': data, 'score': scores})

fig, ax1 = plt.subplots(figsize=(12, 5))

ax2 = ax1.twinx()
ax1.plot(df['value'], 'tab:blue')
ax2.plot(df['score'], 'tab:red')

ax2.set_ylabel('Change Point Score')

plt.show()

ChangeFinderで変化点検知を実行した結果が以下です。
青色が原系列、赤色が変化点である確信度を表すスコアになります。

最初の10時点周辺と100時点周辺で変化点スコアが高くなっています。どのスコアの閾値で変化点と見做すかにも寄りますが、ChangeFinderではこの2点が検出した変化点と呼べそうです。
データを作成する際に想定した2つ目,3つ目の変化点でのスコアが伸びず、変化点の検知が今回では上手くいきませんでした。
もしかしたら複数の変化点が存在しているとChnageFinderではうまくいかないのかもしれないです。ここは要実験かなと思います。

Pelt

今回はコスト関数としてl2ノルムを使用し、fit&predictをおこないます。これだけで変化点の検知が実行できます。

# コスト関数の設定
model = "l2"
# アルゴの設定と学習
algo = rpt.Pelt(model=model).fit(data)
# 変化点の検出
my_bkps = algo.predict(pen=10)
# 結果のプロット
rpt.show.display(data,  my_bkps, figsize=(10, 4))
plt.show()

Pelt法での結果が以下です。
色が変わっているところが、変化点であると検知した時点になります。

予め想定していた100、200、300時点から数時点ズレてはいますが、ChangeFinderでは検知できていなかった2、3つ目の変化点も検知できていそうです。特に変化が小さい3つ目の変化点でも見事変化点が取れています。

まとめ

rupturesライブラリ便利の舞💃💃💃

Goals Tech Blog

Discussion