異常検知入門1　基礎編

[Aidemy](https://aidemy.net/)　2020/11/10
#はじめに
　こんにちは、んがょぺです！バリバリの文系ですが、AIの可能性に興味を持ったのがきっかけで、AI特化型スクール「Aidemy」に通い、勉強しています。ここで得られた知識を皆さんと共有したいと思い、Qiitaでまとめています。以前のまとめ記事も多くの方に読んでいただけてとても嬉しいです。ありがとうございます！
　今回は、以上検知入門の一つ目の投稿になります。どうぞよろしくお願いします。

＊本記事は「Aidemy」での学習内容を「自分の言葉で」まとめたものになります。表現の間違いや勘違いを含む可能性があります。ご了承ください。

今回学ぶこと
・異常検知について
・ホテリング法、マハラノビス距離について
・単純ベイズ法について

#異常検知について
##異常検知とは
・__異常検知___とは、その名の通り__異常なデータを捉える__というものである。具体的には、医療現場の患者の異変を検知することや、システムの故障を検知することなど、幅広く使われている。
・この単元では__「異常検知の理論の理解」__と__「簡単な異常検知システムの実装」__を目指す。

##異常パターン
・検知すべき「異常」のパターンについて見ていく。
###外れ値
・1つ目が__「外れ値」__である。これまでも何度か出てきたものだが、外れ値とは__「値がほかの集合と比べて大きく離れているもの」__を指す。図示するとわかりやすい。

・図![スクリーンショット 2020-11-02 17.27.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/2755124d-bcd2-2987-4a35-ccbd77acea87.png)

・外れ値を検出する問題を、そのまま__「外れ値検出」__という。詳しくはChapter2で扱う。

###変化点
・2つ目が__「変化点」__である。変化点とは__「値がほかと異なった振る舞いをするもの」__を指す。以下の図のように、ある点から__値が急上昇している__場合、その点が変化点である。

・図
![スクリーンショット 2020-11-02 17.31.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/30c79f58-097c-608a-7819-0f3bcf3dd597.png)

・変化点を検出する問題を__「変化（点）検知」__あるいは__「異常部位検知」__という。詳しくはChapter3で扱う。

##異常検知における指標
・__値がどれだけ異常か__を定量的に示したものが__「異常度」__である。基本的には異常度が高いほどその値は異常であるといえる。
・また、__異常か正常かの境目となる点__のことを__「閾値」__という。
・以降の異常検知で行うことは全て__「異常度の定義→閾値の決定」__という流れになる。この閾値の決定の際に__「どの確率で異常とみなすか」__を定義することが多く、この確率のことを__「誤報率」__という。

・__「（実際に正常であるもののうち、正常であると判定できた数）/（実際に正常である標本の数）」__で算出されるものを__「正答率（正常標本精度）」__といい、__「1-正常率」__で表されるものが__誤報率__となる。つまり、誤報率は__「実際に正常であるもののうち、異常と判定してしまったものの割合」__である。
・これらの値は検出器の__精度評価にも使われる__。

・誤報率を計算するコード
![スクリーンショット 2020-11-02 18.29.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/8f0714c2-885f-eb4c-6246-dbae4b146800.png)

#ホテリング法
##ホテリング法とは
・__ホテリング法__とは、__外れ値検知の手法の一つ__である。先述の通り、異常度と閾値を定義する。
・扱うデータについて__「単一の正規分布から発生している」「異常な値をほとんど含まない」__という条件を満たす時のみ有効に適用される。

・ホテリング法の流れは以下のようになる。
　①.__誤報率__を自分で設定し、それに従って__閾値__を求める。
　②.正常と言える値の__平均値__と__共分散行列__を計算する。
　③.テストデータの__異常度__を計算し、__閾値を超えていたら異常と判断する__。

・以上が流れとなるが、__データはほとんど正常とみなされる__。また、ホテリング方は__教師なし学習である__と言える。

##マハラノビス距離
・ホテリング法の流れ③では異常度を計算するが、このとき使われる指標が__「マハラノビス距離」__というものになる。この距離は__「あるデータとデータ全体の平均との距離」__である。
・普通距離を計算するときには__「ユークリッド距離」__を使用するが、異常度には__「分散（スケール）の大きさによって値が大きく変わる」「変数同士の相関を反映できない」__という理由で__使用できない__。
・これに対しマハラノビス距離は、__逆共分散行列__で__正規化__することで__分散を考慮することを可能としている__。具体的には、分散の小さいものの影響を強くし、大きいものの影響を弱くすることで分散を考慮する。
・また、正規化によって__各特徴量間の相関を補正することもできる__。

・コードは以下の通りである（②で同様のことを行うため詳細は後述）。

![スクリーンショット 2020-11-02 21.01.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/0642d936-bde8-428f-0272-90f75ebe2af3.png)

##①.誤報率を自分で設定し、それに従って閾値を求める
・ここからは、実際にホテリング法を実装していく。__ホテリング法で外れ値の検出をすること__を__「T二乗法」__という。
・最初は__閾値を求めていく__のだが、求める上で必要な__「誤報率」は自分で設定する必要がある__。誤報率は高く設定すればより多くの異常を検知でき、低く設定すれば正常なデータをより残すことができる。このように__異常なデータの排除と正常なデータを残すことはトレードオフの関係にある__ので、場合に即した値を設定をすることが大切である。一般的には0.05や0.01などの値が使われる。

・誤情報を設定し、データの量も十分にあるとき、閾値は__「χ（カイ）二乗検定」__という手法を用いて設定する。
・この検定は、具体的に言うと、誤報率を0.05に設定した場合、__「正常時には0.05％未満でしか起こらないぐらい稀な値だから異常である」__と検定している。

・実装は__「st.chi2.ppf()」__で行う。第一引数には__「1-誤報率」__、第二引数には__次元数（変数の個数）__を渡す。

・コード（異常度aはマハラノビス距離で求めるが、今回は省略）
![スクリーンショット 2020-11-02 21.35.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/bbcb45fb-0e99-9875-b990-af26108d6534.png)

##②.正常と言える値の平均値と共分散行列を計算する
・③で__異常値__を計算するには、先述した__マハラノビス距離を求める必要がある__ので、この算出に必要な__「平均値」__と__「共分散行列」__を求める。
・平均は__np.mean(data,axis=0)__で求める。(axis=0で列ごとに求めている)
・データの共分散行列は__np.cov(data.T)__で求める。

・コード![スクリーンショット 2020-11-02 21.51.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/42a59e29-060e-420e-f9bf-a79bd4ffdcdf.png)

##③.テストデータの異常度を計算し、閾値を超えていたら異常と判断する
・最後に__distance.mahalanobis()__に__データ__（x）と__平均__（mean）と__逆共分散行列__（__np.linalg.pinv(cov)__）を渡すことで計算できる。
・この結果と、①で算出した__閾値（threshold）を比較__して、異常を検知する。

・コード![スクリーンショット 2020-11-02 22.07.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/4fec9fec-1ca8-52cb-0fa2-01002f1a9729.png)

##ホテリング法の実践
・以上の①〜③までを併せて行う。コードは以下の通り。
・（補足）各データの異常値を計算するときは__for文でX_testの要素を抽出する必要がある__。また、前項で行わなかった「閾値と異常値の比較」は、異常値が閾値より大なら「異常」、閾値以下なら「正常」とする。

![スクリーンショット 2020-11-02 23.20.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/35f2f42b-f75e-53a1-3bb1-27847a3e7491.png)

・結果（赤が外れ値（異常）、青が正常値）
![スクリーンショット 2020-11-02 23.21.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/3a5149b0-6a81-3425-17d9-f20685deaa14.png)

#単純ベイズ法
##単純ベイズ法とは
・ホテリング法を含む異常検知の手法は、__「変数が多くなる（次元が増える）と計算量が増え、複雑になりすぎる」__と言う欠点を持つ。これを解消するために、__多変数の問題を1変数（1次元）の問題とする__手法である__「単純ベイズ法」__が使われる。
・単純ベイズ法の基本的な考え方として、__「変数同士の相関がないため、複数の事象が起こる確率が、それぞれの確率の積で表される」__と言うものがある。例えば、コインを2回投げる問題で、2回とも表が出る確率は「1/2 * 1/2」で表される。これを応用して、データが異常か正常かの確率は、各変数の異常である確率（に重み付けをしたもの）の積で求めることができる。
・すなわち単純ベイズ法では、__異常度はデータと重みの内積で（次元を1にしつつ）求めることができる__。また、__閾値は__ホテリング法のようには求められず、検証用データを使って__最適化することで求められる__。
・単純ベイズ方が使える条件は__「データの変数が0以上の整数であること」__である。
・流れは以下の通りである。
　①ベイズ法で__訓練データから重みを計算する__
　②評価用データから__閾値を最適化する__
　③異常度を計算し、閾値と比較する

・以上の流れについて、Chapter2以降では、単純ベイズ法が使われる問題である__「文書の異常値検知」__で見ていく。

##単純ベイズ法の事前知識
・異常検知に限らず、データ処理においては__「データをベクトルとして考えたとき、適切なベクトルとの内積を取ると、そのデータの特徴を表す数値が得られる」__と言う考え方がある。この時の「適切なベクトル」のことを__「重みベクトル」__と言う。
・単純ベイズ法でも、③で異常値を求めるときは、重みベクトルさえわかれば、あとはそれとデータの内積を取れば簡単に異常値を求めることができると言える。そのため、__①ではこの重みベクトルを算出する__。

・今回行う「文書の異常値検知」について、メールのスパム判定のようなものを考えると、メール（文書）を形態素解析し、名詞など識別に使えそうなデータの出現個数を調べ、これとスパムかどうかのラベルを渡して重みを作成する、ということを行う。（つまり__教師あり学習__）
・あとは、新しく渡された文書を同じように形態素解析し、その出現回数と作成した重みを掛け合わせて異常値を算出する。

・また、文書の異常値検知のデータは、__「単語袋詰め表現」__として出現頻度を表す。具体的には以下のコードを参照。これは、"hoge"と"foo"は出現せず、"bar","po","do"はそれぞれ3,4,1回出現したことを表している。

![スクリーンショット 2020-11-03 14.19.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/9716e0e1-acef-9660-6c4f-9b4f699192bd.png)

##①ベイズ法で訓練データから重みを計算する
・__重みの算出__は、訓練データ「X_train」のうち、正常なデータ（X0）の重みを「w0」、異常なデータ（X1）の重みを「w1」としたとき、__「np.log(w1/w0)」__で求めることができる。
・よって、__w0とw1をそれぞれ求める__必要がある。__「（全正常データの各単語の出現総数）　÷　（全正常データの総出現単語数）」__で求めることができるが、np.logで対数を取るときに重みが0にならないように、__「alpha=1」__をたす。これを__「ゲタを履かせる」__と言う。
・各単語の出現総数は（正常データなら）__「np.sum(X0, axis=0)」__で求められる。総単語数は__「np.sum(X0)」__で求められる。

・コード![スクリーンショット 2020-11-03 15.00.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/aaae2407-1169-6915-8c1d-5e475662ef9e.png)

・結果（一部のみ）![スクリーンショット 2020-11-03 15.01.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/d12ccefe-4b34-d290-260d-60cc82c2744e.png)

##②評価用データから閾値を最適化する
・①の重み算出は訓練データ（X_train）で行ったが、閾値の最適化は評価用データ__「X_valid」__で行う。
・閾値の最適化は、__「metrics.roc_curve()」__で行う。第一引数には評価用データのラベル__「y_valid」__を渡し、第二引数には__評価用データの異常度__を渡せば良い。
・評価用データの異常度については次の項でも行うが、先述の通りデータと重みの内積を取れば良いので、__「np.dot()」__で算出できる。
・また、「metrics.roc_curve()」は__「偽陽性率」「真陽性率」「閾値候補」__の3つを返すので、それぞれ変数__「fpr」「tpr」「thr_arr」__に格納する。
・この3つの変数について、__「thr_arr[(tpr-fpr).argmax()]」__とすることで閾値（threshold）を算出できる。

・コード![スクリーンショット 2020-11-03 16.14.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/83c470ec-7235-3cd6-a7f0-97573bd33d21.png)

##③異常度を計算し、閾値と比較する
・最後に、前項でも触れた通り、異常度を__「np.dot()」__で算出し、閾値と比較する。(コードは省略)

#まとめ
・異常パターンには__「外れ値」「変化点」__がある。これを検出することが__異常検知__である。
・外れ値検出の手法の一つに、__「ホテリング法」__と言うものがある。ホテリング法では、まず「異常か正常か」の境目である__閾値__を決定し、次にデータの__平均と（逆）共分散行列から異常度を算出する__。この閾値と異常度を比べることで、異常か正常かを決定する。
・また、データが多次元であるときはこの手法が使えないので、__「単純ベイズ法」__を使って一次元のデータとして扱うことで異常検知を可能にする。
・単純ベイズ法では、__重みベクトルを算出__し、これと__データの内積を計算する__ことで異常度を算出し、また閾値を決定してこの2つを比べることで異常か正常かを決定する。

今回は以上です。最後まで読んでいただき、ありがとうございました。