K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

異常検知①

中級

02.8.1

異常検知①

最終更新 2020-01-29 読了時間 3 分
まとめ
ADTK(Anomaly Detection Toolkit)は、時系列の異常検知を「特徴量変換 + 閾値判定」の部品の組み合わせとして書けるライブラリです。このページでは NAB の時系列(5 分間隔・2 週間分)に LevelShiftAD・SeasonalAD・PersistAD・InterQuartileRangeAD・AutoregressionAD の 5 つの検出器を順に当て、同じデータでも検出器ごとに赤く印が付く場所が変わることを図で確かめます。読み終えると、「値が大きい」「直前から急に変わった」「周期パターンから外れた」という異常の定義の違いが、それぞれどの検出器と係数 c に対応するかが分かります。

直感 #

毎日 8 時間だけ負荷が上がるサーバーの使用率を考えると、「80% は異常か」という問いには答えられません。深夜の 80% は異常でも、昼間の 80% は毎日のことだからです。ADTK は「モデルを 1 つ学習して終わり」ではなく、InterQuartileRangeAD なら値の大きさ、PersistAD なら直前との差、SeasonalAD なら周期パターンからの残差、というように異常の定義を検出器として選びます。対象データの周期性や変動幅に合わせて検出器と係数 c を選ぶことで、運用で使える異常検知パイプラインを作れます。

何が起きているか(ステップで見る) #

コードを読む前に、この手法が図の上で何をしているかを追ってみましょう。「再生」を押すか、スライダーを動かすと 1 手順ずつ進みます。下の図は同じ章の Isolation Forest が 2 次元の外れ値を分割で孤立させる様子で、時系列でも「周りと違う点を 1 点だけ切り出す」という目的は同じです。

見るポイント:少ない回数で孤立する点ほど異常。外れ値(赤)は 1 回の分割で 1 点になるが、密集した点(青)は 8 回切っても 5 点が残る。このページの InterQuartileRangeAD は値の軸だけで同じ「切り出し」を行い、PersistAD や SeasonalAD は時間方向の文脈を使って切り出す。

詳細な解説 #


1. 実験データの準備 #

ここでは Numenta Anomaly Benchmark (NAB) のデータを使い、
ADTK (Anomaly Detection Toolkit) で異常検知を試します。次のコードは CSV を timestamp 列を索引とする時系列に読み込み、ADTK が扱える形に整えます。

1
2
3
4
5
6
7
8
import pandas as pd
from adtk.data import validate_series

s_train = pd.read_csv(
    "./training.csv", index_col="timestamp", parse_dates=True, squeeze=True
)
s_train = validate_series(s_train)
print(s_train.head())
1
2
from adtk.visualization import plot
plot(s_train)

2. 代表的な異常検知手法 #

ADTK には多くの検出器が用意されています。ここでは代表的な 5 種類を同じ系列に当て、fit_detect が返す異常フラグを赤い印として重ねて描きます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import matplotlib.pyplot as plt
from adtk.detector import (
    AutoregressionAD,
    InterQuartileRangeAD,
    LevelShiftAD,
    PersistAD,
    SeasonalAD,
)

model_dict = {
    "LevelShiftAD": LevelShiftAD(window=5),
    "SeasonalAD": SeasonalAD(),
    "PersistAD": PersistAD(c=3.0, side="positive"),
    "InterQuartileRangeAD": InterQuartileRangeAD(c=1.5),
    "AutoregressionAD": AutoregressionAD(n_steps=14, step_size=24, c=3.0),
}

for model_name, model in model_dict.items():
    anomalies = model.fit_detect(s_train)
    plot(s_train, anomaly=anomalies, anomaly_color="red", anomaly_tag="marker")
    plt.title(model_name)
    plt.show()

3. 各手法の直感と数式 #

(1) LevelShiftAD(水準の変化) #

データの平均値が急に変化するかどうかを見ます。センサー値がある時点で急に上がった、または下がった場面に対応し、時刻 \(t\) の前後 \(w\) 点ずつの平均の差を取ります。

$$ \Delta_t = \bar{x}_{t:t+w} - \bar{x}_{t-w:t} $$

\(|\Delta_t|\) が大きければ異常と判定します。コードの window=5 が \(w\) にあたります。


(2) SeasonalAD(季節性とのズレ) #

周期性を学習し、そのパターンから外れるデータを検知します。「毎日のアクセス数が、その時間帯にしては多すぎる」という判断で、周期パターンによる予測値 \(\hat{x}_t^{(\text{seasonal})}\) との差を見ます。

$$ e_t = x_t - \hat{x}_t^{(\text{seasonal})} $$

誤差 \(e_t\) が大きければ異常です。SeasonalAD() は周期を自己相関から自動で推定するので、1 日 288 点の周期を自分で指定する必要はありません。


(3) PersistAD(値の持続) #

「直前の値から大きく乖離したか」を判定します。

$$ |x_t - x_{t-1}| > c \cdot \sigma $$

閾値 \(c\)(コードでは c=3.0)を超えると異常です。side="positive" を付けると \(x_t - x_{t-1}\) が正の側だけを見ます。


(4) InterQuartileRangeAD(四分位範囲) #

時間の情報を使わず、値の分布の Q1・Q3 だけから外れ値を検出します。データ分布の第 1 四分位 \(Q1\)、第 3 四分位 \(Q3\)、四分位範囲 \(IQR = Q3 - Q1\) を用い、

$$ x_t < Q1 - c \cdot IQR \quad \text{または} \quad x_t > Q3 + c \cdot IQR $$

を満たす点を異常とします。c=1.5 は箱ひげ図のひげと同じ基準です。


(5) AutoregressionAD(自己回帰) #

AutoregressionAD は過去の値から現在の値を線形回帰で予測し、予測との誤差が大きい点を異常とします。

$$ x_t \approx \sum_{i=1}^p \phi_i x_{t-i} + \epsilon_t $$

残差 \(\epsilon_t\) が大きいと異常です。コードの n_steps=14 が \(p\)、step_size=24 が「何ステップおきの過去を使うか」にあたります。


4. 可視化結果 #

5 枚の図はすべて同じ系列ですが、赤い印の付く場所が検出器ごとに違います。LevelShiftAD は毎日の立ち上がりと立ち下がりの両方に印を付けます。4 月 11 日の低い山の端も拾っています。SeasonalAD は毎日の山の頂上付近に加えて、4 月 11 日の「いつもより 40 低い山」を 1 つのかたまりとして赤くするので、この日が周期パターンから外れていることが最もはっきり見える図です。PersistAD は side="positive" で上向きの急変だけを見るため、立ち上がり付近と山の中の跳ねに印が付き、下降側には付きません。InterQuartileRangeAD は 0 件です。値の分布だけで判断するため、毎日現れる 80 台も「普通の値」に含まれるからです。AutoregressionAD は 4 月 11 日の低い山と、その翌日以降の立ち上がり・立ち下がりをまとめて赤くし、2 時間おきの過去 14 点のパターンから外れた区間を捉えています。

可視化結果 可視化結果 可視化結果 可視化結果 可視化結果

まとめ #

ADTK の検出器は「何を異常と呼ぶか」の定義そのものです。値の大きさで決めるなら InterQuartileRangeAD(c=1.5)、直前からの急変なら PersistAD(c=3.0)、水準の切り替わりなら LevelShiftAD(window=5)、周期パターンからの逸脱なら SeasonalAD() や AutoregressionAD(n_steps=14, step_size=24) を選びます。このデータで 4 月 11 日の異常な 1 日を見つけられたのは周期や自己回帰を使う検出器だけで、値の分布しか見ない IQR 法は 0 件でした。係数 c を上げれば赤い印は減って見逃しが増え、下げれば印が増えて誤検知が増えます。運用では、人が確認できる通知件数から逆算して c を決めます。異常検知② では 2 列目を足し、多次元の検出器(LOF・回帰・PCA)に進みます。