Isolation Forest
「不正検知には大きく2つのアプローチがある。教師あり学習と、異常検知だ。」
田中VPoEがホワイトボードに図を描く。
「まずはIsolation Forestから始めよう。ラベルなしでも使える強力な異常検知アルゴリズムだ。新しい不正パターンにも対応できる可能性がある。」
異常検知アプローチの利点
教師あり学習はラベル付きデータが必要だが、異常検知は「正常」のパターンを学習し、そこから外れるものを異常とみなす。
教師あり学習:
「不正とはこういうものだ」を学習
→ 既知の不正パターンに強い
→ 未知のパターンに弱い
異常検知:
「正常とはこういうものだ」を学習
→ 正常から外れれば未知の不正も検知可能
→ 偽陽性が多くなりやすい
Isolation Forestの原理
基本アイデア
「異常なデータは、ランダムな分割で素早く孤立(Isolate)できる」
正常データ: 密集しているため、何度も分割しないと孤立できない
異常データ: 外れた位置にあるため、少ない分割で孤立できる
→ 孤立に必要な分割回数(パスの長さ)が短い = 異常
アルゴリズム
Isolation Forest アルゴリズム:
1. サブサンプリング: データからランダムにサンプルを抽出
2. Isolation Tree の構築:
a. ランダムに特徴量を1つ選択
b. その特徴量の最小値〜最大値の間でランダムに分割点を選択
c. データを左右に分割
d. 各サンプルが孤立するか、最大深さに達するまで繰り返し
3. 複数のIsolation Treeでアンサンブル
4. 異常スコア = 各Treeでの平均パス長の逆数
可視化イメージ
通常のデータ点(密集): 異常なデータ点(孤立):
●●●● ○
●●●●
●●●● ←分割1回で孤立!
●●●●
↑
何回も分割が必要
scikit-learnでの実装
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler
import numpy as np
# データ準備
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Isolation Forest
iso_forest = IsolationForest(
n_estimators=200, # ツリーの数
max_samples='auto', # サブサンプルサイズ(auto=256)
contamination=0.002, # 異常の想定割合(不正率)
max_features=1.0, # 使用する特徴量の割合
random_state=42,
n_jobs=-1
)
# 学習(正常データのみで学習するのが理想的)
iso_forest.fit(X_scaled)
# 予測: -1=異常, 1=正常
predictions = iso_forest.predict(X_scaled)
# 異常スコア(低いほど異常)
anomaly_scores = iso_forest.decision_function(X_scaled)
# score_samples() はより生のスコアを返す
raw_scores = iso_forest.score_samples(X_scaled)
ハイパーパラメータ
| パラメータ | 説明 | 推奨値 | 注意点 |
|---|---|---|---|
| n_estimators | ツリーの数 | 100〜300 | 多いほど安定するが計算コスト増 |
| max_samples | サブサンプルサイズ | 256 or ‘auto’ | 大きすぎると正常パターンに過適合 |
| contamination | 異常の想定割合 | 実際の不正率に近い値 | 閾値に影響する重要パラメータ |
| max_features | 使用特徴量の割合 | 0.5〜1.0 | 低いと多様性が増す |
contaminationの調整
# contamination は閾値を決めるパラメータ
# 実際の不正率がわかっている場合はそれを設定
# 方法1: 既知の不正率を使用
iso_forest_1 = IsolationForest(contamination=0.0017)
# 方法2: 閾値を自分で設定(推奨)
iso_forest_2 = IsolationForest(contamination='auto')
iso_forest_2.fit(X_train_scaled)
# スコアを取得して独自の閾値を設定
scores = iso_forest_2.decision_function(X_test_scaled)
# パーセンタイルで閾値設定
threshold = np.percentile(scores, 0.5) # 下位0.5%を異常とする
predictions = (scores < threshold).astype(int)
閾値設定の戦略
スコア分布の分析
import matplotlib.pyplot as plt
# 正常と不正のスコア分布
scores_normal = anomaly_scores[y == 0]
scores_fraud = anomaly_scores[y == 1]
plt.figure(figsize=(10, 5))
plt.hist(scores_normal, bins=100, alpha=0.7, label='正常', density=True)
plt.hist(scores_fraud, bins=50, alpha=0.7, label='不正', density=True, color='red')
plt.xlabel('Anomaly Score')
plt.ylabel('Density')
plt.title('Isolation Forest: 異常スコアの分布')
plt.legend()
plt.show()
# 不正取引のスコアが低い(異常側)に分布していることを確認
コストベース閾値最適化
# 閾値を変えてビジネスコストを最小化
thresholds = np.linspace(scores.min(), scores.max(), 200)
best_cost = float('inf')
best_threshold = None
for t in thresholds:
y_pred = (scores < t).astype(int)
fn = ((y_test == 1) & (y_pred == 0)).sum()
fp = ((y_test == 0) & (y_pred == 1)).sum()
cost = fn * 50000 + fp * 500
if cost < best_cost:
best_cost = cost
best_threshold = t
print(f"最適閾値: {best_threshold:.4f}")
print(f"最小コスト: {best_cost:,.0f}円")
Isolation Forestの長所と短所
| 長所 | 短所 |
|---|---|
| ラベルなしで使える | 教師あり学習より精度が劣る傾向 |
| 線形時間で学習(高速) | 特徴量の重要度がわかりにくい |
| 未知の不正パターンに対応可能 | 閾値設定が難しい |
| 次元の呪いに比較的強い | 局所的な異常を見逃す場合がある |
まとめ
| 項目 | ポイント |
|---|---|
| 原理 | 異常データはランダム分割で素早く孤立できる |
| 利点 | ラベル不要、高速、未知パターン対応 |
| 重要パラメータ | contamination、n_estimators、max_samples |
| 閾値設定 | コストベースで最適化するのが実用的 |
チェックリスト
- Isolation Forestの原理を説明できる
- 「パスの長さが短い = 異常」の意味を理解した
- contamination パラメータの役割を説明できる
- コストベースの閾値最適化を実装できる
次のステップへ
異常検知アプローチを理解したところで、次は教師あり学習のアプローチで不正検知モデルを構築しよう。
推定読了時間: 30分