AI
2024/10/21
小山 佳祐

【機械学習】異常検知入門

AI

機械学習分野の一つとして異常検知が大きく注目を集めています。異常検知とは「データを基にした客観的な基準により、正常なデータから外れるデータを検出すること」です。客観的な視点により通常とは異なる振る舞いをするデータを検出しようということですね。異常検知手法には深層学習系の手法、正常データの領域を推定しそこから飛び出たデータを異常とみなす手法など、様々ありますが今回のブログでは正常データの背後にある確率分布を仮定し、その確率分布から発生確率を求め、発生確率の低い事象は異常であるとみなす、統計モデル手法を解説します。ここで想定する確率分布は混合分布や正規分布などが考えられますが、正規分布を仮定するホテリング理論について詳しく記載します。

はじめに

機械学習分野の一つとして異常検知が大きく注目を集めています。異常検知とは「データを基にした客観的な基準により、正常なデータから外れるデータを検出すること」です。客観的な視点により通常とは異なる振る舞いをするデータを検出しようということですね。異常検知手法には深層学習系の手法、正常データの領域を推定しそこから飛び出たデータを異常とみなす手法など、様々ありますが今回のブログでは正常データの背後にある確率分布を仮定し、その確率分布から発生確率を求め、発生確率の低い事象は異常であるとみなす、統計モデル手法を解説します。ここで想定する確率分布は混合分布や正規分布などが考えられますが、正規分布を仮定するホテリング理論について詳しく記載します。

異常検知の例

では、具体的な例を挙げて異常検知を考えていきましょう。ここで健康診断の例を挙げます。




一学年200人の身長と体重、問診結果を確認することが出来る。




ここで「普通とは変わった振る舞い」を見つけ出す規則を作ることを考える。





例として「体重が80kg以上ならば太りすぎ」とします。この規則は直観に反していないように思いますが、実際のところ身長の高い人は体重も多くなるので体重と身長を考慮した指標が必要です。よってWHOでは以下の指標を定めています。



BMI = 体重[kg] / 身長[m]の二乗



また、この指標が25を超えると太りすぎと定義しています。


ではなぜ、25を超えると太りすぎと考えるのでしょうか。それは、正常で健康の人の多くが大体25未満に収まっているデータを持っているからです。


このように異常検知の基本は正常となるモデル(基準)をデータから作成し、そのモデル(基準)から外れるものを異常とします。


上記の例では、健康な人であればBMIの値がこれくらいの範囲に収まるといった知識を用いて正常と異常を分離していましたが、結局のところ機械学習で異常検知を行うとは、このような「知識を機械学習の手法を通じてデータから見つけ出させること」に他なりません。

正規分布に従うデータの異常検知

では、ここから正常データが正規分布従うと仮定した異常検知手法を見ていきましょう。


今回使用するデータは200人の体重に関するデータです。




このデータは約60kg付近を中心に一山になっていることから正規分布を仮定します。


正規分布とは


では正規分布とはどんな分布であったでしょうか。



正規分布は上記のように平均を中心に左右対称になっている分布です。


確率密度関数は以下のようであり


$$

f(x|\mu,\sigma) = \frac{1}{\sqrt{2 \pi \sigma^2}}exp(-\frac{(x-\mu)^2}{2\sigma^2})

$$


平均と標準偏差が決定されると分布の形が決定します。すなわち正常データの分布が推定できます。詳細は省きますが平均と分散は次の式で推定します。


$$

\mu = \frac{1}{n}\sum^n_{i=1}x_i

$$


$$

\sigma^2 = \frac{1}{n}\sum^n_{i=1}(x_i-\mu)^2

$$


標準偏差は分散の根号を取ることで求められます。


異常度の定義


異常度とは各データの異常度合いを数値化したものです。ここでは、出現確率の高い観測値は異常度が低く、出現確率の低い観測値は異常度が高く設定します。そこで以下の式がよく用いられます。


$$

A(x_i) = -logP(x_i|D)

$$


Aは各データに対する異常値を表し、PはデータDから作成された確率密度関数です。


このグラフは



となり出現確率が大きいほど異常度が下がり、出現確率が小さいほど異常度が高くなり、適切に表現されています。この式に正規分布の確率密度関数を代入し、整理すると以下のように異常度が求められます。


$$

A(x) = \frac{(x-\mu)^2}{\sigma^2}

$$


この異常度は平均から離れるほど異常度が高く、分散が大きいほど(データが散らばっているほど)異常度が低くなると理解でき、直観的にも理解しやすい値になっています。


 

閾値計算

ここまでで任意の観測値に対して異常度を計算することができます。


詳細は省きますがこの異常度がある分布に従うことを利用して正常異常を判定します。



ホテリング統計量の分布(簡易版)


データ数が十分に大きいとき、前に定義した異常度は自由度1のカイ二乗に従う。


$$

A(x) \sim\chi^2(1)

$$



例えば体重が69.8kgの時


その異常度は2.0と計算することが出来きます。この異常度2.0がどの程度ありえないのかをカイ二乗分布を使用して求め、事前に設定した閾値と比べ異常判定を行います。例えば閾値を5%とします。


また、異常度が2.0の場合、下図は自由度1のカイ二乗分布を表しており、以下のピンク色の部分の面積を計算することで確率を求めることが出来ます。



これを計算すると15.7%となり、閾値5%より大きいので「異常ではない」と判定されます。


 

Rでの実装

ここでは体重のデータセットを使用して、70kg、110kgのデータが異常か異常ではないかを判定していきます。上記の正常データが正規分布に従うことを仮定した手法になるので見比べながらコードを見ていくと理解しやすいともいます。


必要パッケージのダウンロード



# 必要なパッケージをインストール(すでにインストール済みの場合はスキップされます)
if (!require(car)) {
install.packages("car", repos = "http://cran.rstudio.com/")
}
if (!require(mvoutlier)) {
install.packages("mvoutlier", repos = "http://cran.rstudio.com/")
}
# 必要なパッケージを読み込み
library(car)
library(mvoutlier)

データの読み込み抽出



# Davisデータセットを読み込み
data(Davis)

# 体重データを抽出
weights <- Davis$weight

平均と分散の算出



# 1. 平均と分散を計算
mean_weight <- mean(weights, na.rm = TRUE)
var_weight <- var(weights, na.rm = TRUE)

異常値を計算する関数



# 2. ホテリングT²スコアを計算する関数
hotelling_t2 <- function(value, mean, var) {
t2_value <- (value - mean)^2 / var
return(t2_value)
}

カイ二乗から正常異常を判断する関数



# 3. カイ二乗分布を使って異常を検知する関数
detect_anomaly <- function(value, mean, var, significance_level = 0.01) {
# 一次元データの自由度は1なので、カイ二乗分布の臨界値を計算
critical_value <- qchisq(1 - significance_level, df = 1)
# ホテリングT²値を計算
t2_value <- hotelling_t2(value, mean, var)
# T²値が臨界値を超えれば異常と判断
if (t2_value > critical_value) {
return(TRUE) # 異常
} else {
return(FALSE) # 正常
}
}

70kgが異常か判定する



# 4. 新しい体重データ70kgが異常かどうかを検知
new_weight <- 70
is_anomalous <- detect_anomaly(new_weight, mean_weight, var_weight)
# 結果を表示
if (is_anomalous) {
cat(sprintf("新しい体重データ %d kg は異常です。\n", new_weight))
} else {
cat(sprintf("新しい体重データ %d kg は異常ではありません。\n", new_weight))
}

結果



新しい体重データ70kgは異常ではありません。



110kgが異常か判定する



# 4. 新しい体重データ110kgが異常かどうかを検知
new_weight <- 110
is_anomalous <- detect_anomaly(new_weight, mean_weight, var_weight)
# 結果を表示
if (is_anomalous) {
cat(sprintf("新しい体重データ %d kg は異常です。\n", new_weight))
} else {
cat(sprintf("新しい体重データ %d kg は異常ではありません。\n", new_weight))
}

結果



新しい体重データ110kgは異常です。



したがって作成したモデルによると70kgの体重は正常であり、110kgの体重は異常であると判定されました。








終わりに

今回は、異常検知手法の最も基本である正規分布に従うデータの異常検知を扱いました。異常検知手法は深層学習系のオートエンコーダ、分離曲面を考えるOneClassSVMなどがあり、状況に「よって適切に使い分けなければいけません。今後も異常検知手法について扱っていくので一つずつ学んでいきましょう。

参考文献

入門 機械学習による異常検知 Rによる実践ガイド 著者:井出剛

New call-to-action