金融AIにおけるアンサンブル学習を用いた高精度な不正検知アルゴリズム

金融AIの不正検知:不均衡データを攻略するアンサンブル学習の実装全コードとStacking戦略

約8分で読めます
文字サイズ:
金融AIの不正検知:不均衡データを攻略するアンサンブル学習の実装全コードとStacking戦略
目次

この記事の要点

  • 不均衡データ環境下での不正検知精度向上
  • アンサンブル学習(Voting/Stacking)の活用
  • LightGBMとXGBoostの組み合わせによる効果

金融不正検知では、全取引の大部分が正常であるため、単純な正解率ではモデル性能を正しく評価できません。このような不均衡データには、複数モデルを組み合わせるアンサンブル学習が有効です。異なるアルゴリズムの特性を活用し、単一モデルで捉えきれない不正パターンを検出し、過学習リスクを低減します。

主な手法として、複数モデルの予測を組み合わせるVoting(多数決)、訓練データをランダム抽出して予測の分散を抑えるBagging、予測結果を新特徴量としてメタモデルを学習させるStackingがあります。

本記事では、VotingとStackingの実装プロセスをPythonコードで解説し、Stackingにおけるデータリークのリスクと回避策を詳述します。まずは動くプロトタイプを作り、技術の本質とビジネスへの最短距離を探っていきましょう。

1. 金融データ特有の課題とアンサンブル戦略

実装の方向性を決めるのはドメインの深い理解です。まずは戦略の全体像を整理します。

正例0.1%未満の世界:不均衡データが招くモデルのバイアス

クレジットカード不正利用やマネーロンダリング検知のデータセットは極端に偏っており、これを不均衡データ(Imbalanced Data)と呼びます。

一般的な機械学習モデルは全体のエラー率を最小化するよう学習するため、多数派の「正常取引」を過剰学習し、少数派の「不正取引」をノイズとして無視する傾向があります。これが、正解率が99%を超えていても実用性のない「使えないモデル」が生まれる原因です。

なぜ単一モデルではなくアンサンブルなのか

アンサンブル学習は、複数のモデル(弱学習器)を組み合わせて強力なモデル(強学習器)を構築する手法です。金融不正検知で有効な理由は、主に2つの効果にあります。

  1. バイアスの分散: アルゴリズムごとに得意な不正パターンが異なります。決定木ベースのモデルが見逃したパターンを、線形モデルやニューラルネットワークが補完できます。チーム開発で多様な視点が活きるのと同じですね。
  2. バリアンス(分散)の抑制: 複数モデルの平均を取ることで、特定データセットへの過剰適合(Overfitting)リスクを緩和できます。

Voting、Bagging、Stackingの使い分け基準

一般的な開発現場での簡易的な判断基準は以下の通りです。

  • Voting (多数決): 既存の高精度モデルが複数あり、手軽に安定性を高めたい場合。
  • Bagging (Random Forestなど): モデルのバリアンスが高く、過学習を抑えたい場合。
  • Stacking (積み上げ): コンペティションや本番環境など、コンマ数パーセントの精度向上がビジネスインパクトに直結する場合。

今回は、実装難易度と効果のバランスが良い「Voting」と、精度向上を狙う「Stacking」に焦点を当てます。

2. 環境構築と不均衡データセットの準備

理論だけでなく「実際にどう動くか」を確認するため、scikit-learnmake_classificationを用いて、不正率約0.5%の極端な不均衡データを生成し、プロトタイプを構築します。

必要なライブラリ

# 必要なライブラリのインポート
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# モデル
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, VotingClassifier, StackingClassifier
import lightgbm as lgb
import xgboost as xgb

# データ生成・評価・前処理
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score
from sklearn.metrics import roc_auc_score, average_precision_score, classification_report, precision_recall_curve
from sklearn.preprocessing import StandardScaler

# 警告の抑制
import warnings
warnings.filterwarnings('ignore')

# シードの固定(再現性のため)
RANDOM_STATE = 42
np.random.seed(RANDOM_STATE)

擬似的な金融取引データの生成と前処理

ここでは、10万件の取引データのうち、不正取引(クラス1)がわずか500件(0.5%)という状況を作ります。

# 不均衡データの生成
X, y = make_classification(
    n_samples=100000,       # サンプル数
    n_features=20,          # 特徴量数
    n_informative=15,       # 有効な特徴量数
    n_redundant=5,          # 冗長な特徴量
    n_clusters_per_class=2, 
    weights=[0.995, 0.005], # 99.5%が正常(0)、0.5%が不正(1)
    flip_y=0.01,            # ノイズ混入
    random_state=RANDOM_STATE
)

# データの分割(層化抽出を行うことが重要)
# Stratify=yを指定することで、訓練データとテストデータの不正比率を維持します
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=RANDOM_STATE
)

# スケーリング(線形モデルを使用する場合に特に重要)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

print(f"Training set shape: {X_train.shape}")
print(f"Test set shape: {X_test.shape}")
print(f"Fraud cases in Train: {sum(y_train)} ({sum(y_train)/len(y_train):.2%})")
print(f"Fraud cases in Test: {sum(y_test)} ({sum(y_test)/len(y_test):.2%})")

評価指標:PR-AUCとF1-scoreを重視する理由

極端な不均衡データでは、False Positive(誤検知)が増加してもROC-AUCがあまり変化しない場合があります。

そのため金融不正検知の実務では、PR-AUC(Precision-Recall Curveの下面積)を重視します。これは「不正予測のうち本当に不正だった割合(Precision)」と「実際の不正のうち発見できた割合(Recall)」の関係を評価する指標です。ビジネス要件に直結する指標を選ぶことが、プロジェクト成功の鍵となります。

3. 【基本実装】Voting Classifierによる多数決アプローチ

環境構築と不均衡データセットの準備 - Section Image

基本的なアンサンブル手法であるVotingを実装します。「Hard Voting(単純多数決)」と「Soft Voting(確率の平均)」がありますが、金融スコアリングのように確率値が重要な場合はSoft Votingが適しています。

異質なモデルの組み合わせ

アンサンブルの効果を最大化するには、性質の異なるモデルを組み合わせることが重要です。ここでは以下の3つを混ぜてみます。

  1. Logistic Regression: 線形分離可能なパターンに強い(ベースライン)。
  2. Random Forest: 非線形な関係や相互作用に強い。
  3. LightGBM: 勾配ブースティングによる高い予測精度。
# 個別のモデル定義
clf1 = LogisticRegression(random_state=RANDOM_STATE, solver='liblinear')
clf2 = RandomForestClassifier(n_estimators=100, random_state=RANDOM_STATE, n_jobs=-1)
clf3 = lgb.LGBMClassifier(n_estimators=100, random_state=RANDOM_STATE, verbose=-1)

# Voting Classifierの定義 (voting='soft'を指定)
eclf = VotingClassifier(
    estimators=[('lr', clf1), ('rf', clf2), ('lgbm', clf3)],
    voting='soft', 
    weights=[1, 2, 3] # モデルの信頼度に応じて重み付けを変えることも可能
)

# 学習と評価
models = [clf1, clf2, clf3, eclf]
model_names = ['Logistic Regression', 'Random Forest', 'LightGBM', 'Soft Voting']

print(f"{'Model':<20} | {'ROC-AUC':<10} | {'PR-AUC':<10}")
print("-"*45)

for name, model in zip(model_names, models):
    # ロジスティック回帰の場合はスケーリング済みデータを使用
    if name == 'Logistic Regression':
        model.fit(X_train_scaled, y_train)
        y_prob = model.predict_proba(X_test_scaled)[:, 1]
    elif name == 'Soft Voting':
        # Voting内部でPipelineを使うのが正式ですが、ここでは簡易化のため生データで統一
        # 注意: 実務ではVotingClassifier内でPipelineを使ってスケーリングを分けるべきです
        model.fit(X_train, y_train)
        y_prob = model.predict_proba(X_test)[:, 1]
    else:
        model.fit(X_train, y_train)
        y_prob = model.predict_proba(X_test)[:, 1]
        
    roc = roc_auc_score(y_test, y_prob)
    pr = average_precision_score(y_test, y_prob)
    print(f"{name:<20} | {roc:.4f}     | {pr:.4f}")

コードのポイント解説

  • voting='soft': 各モデルの出力確率の平均(または重み付き平均)を最終予測確率とします。情報損失を防ぎ、滑らかな決定境界を得られます。
  • weights: [1, 2, 3]と設定し、高精度なLightGBMを重視するよう調整しています。この重みもチューニング対象です。

単体モデルよりVotingの方がPR-AUCがわずかに向上する傾向があり、大量の取引を処理するシステムではこの差が大きなビジネス価値を生みます。

4. 【応用実装】Stackingによる高精度化とリーク防止

次にStacking(スタッキング)を実装します。Votingが「フラットな話し合い」なら、Stackingは「現場の意見をマネージャーが統合判断する」構造と言えるでしょう。

Stackingの構造とリークのリスク

Stackingは2段階の構造を持ちます。

  1. Level 0 (Base Models): 学習データを用いて予測を行う。
  2. Level 1 (Meta Model): Level 0の「予測値」を入力とし、最終予測を行う。

ここでデータリーク(Data Leakage)に注意が必要です。Level 0が学習したデータに対する予測結果をLevel 1の学習に用いると、過学習を引き起こします。

これを防ぐため、Cross Validation(交差検証)の仕組みで未学習データへの予測値のみを集め、メタモデルの特徴量とするOut-of-Fold (OOF) 予測を使用します。

StackingClassifierを用いた実装

scikit-learnStackingClassifierを使用すると、OOF処理を内部で自動化でき、スピーディーな検証が可能です。

# ベースモデルの定義(多様性を意識)
level0_estimators = [
    ('rf', RandomForestClassifier(n_estimators=100, random_state=RANDOM_STATE, n_jobs=-1)),
    ('lgbm', lgb.LGBMClassifier(n_estimators=100, random_state=RANDOM_STATE, verbose=-1)),
    ('xgb', xgb.XGBClassifier(n_estimators=100, use_label_encoder=False, eval_metric='logloss', random_state=RANDOM_STATE))
]

# メタモデルの定義(通常はシンプルなモデルを使用)
# ロジスティック回帰が一般的です
level1_estimator = LogisticRegression(random_state=RANDOM_STATE)

# Stacking Classifierの構築
stacking_clf = StackingClassifier(
    estimators=level0_estimators,
    final_estimator=level1_estimator,
    cv=StratifiedKFold(n_splits=5, shuffle=True, random_state=RANDOM_STATE), # 内部でのCV分割
    n_jobs=-1,
    passthrough=False # Trueにすると元の特徴量もメタモデルに入力される
)

print("Training Stacking Classifier... This may take a while.")
stacking_clf.fit(X_train, y_train)

# 予測と評価
y_pred_stack = stacking_clf.predict_proba(X_test)[:, 1]

roc_stack = roc_auc_score(y_test, y_pred_stack)
pr_stack = average_precision_score(y_test, y_pred_stack)

print(f"\nStacking Model Results:")
print(f"ROC-AUC: {roc_stack:.4f}")
print(f"PR-AUC : {pr_stack:.4f}")

cv=StratifiedKFoldの指定により内部でデータを5分割し、リークを防ぎつつメタモデル用特徴量を生成するため、未知のデータにも高い汎化性能を発揮します。

5. 実務運用のための最適化とモデル解釈

【応用実装】Stackingによる高精度化とリーク防止 - Section Image

高精度なモデルも、実際のビジネス環境で稼働し、価値を提供できなければ意味がありません。実務運用に向けた最適化と解釈性の確保について解説します。

推論速度と精度のトレードオフ

Stackingモデルは複数モデルを稼働させるため推論時間が増加しやすく、リアルタイム決済承認などでは応答時間が課題となります。経営視点では、インフラコストとのバランスも考慮する必要があります。

モデルが重すぎる場合は以下の対策を検討します。

  • 知識蒸留 (Knowledge Distillation): Stackingモデルの予測結果を教師とし、単一のLightGBMモデルを学習させる。
  • モデルの間引き: 貢献度の低いベースモデルを削除する。

閾値(Threshold)調整によるリコール最大化

モデルは「不正の確率」を出力しますが、「不正としてアラートを出すか」は閾値で決まり、デフォルトの0.5が最適とは限りません。

「多少の誤検知を許容してでも不正利用を防ぎたい」場合、Precision-Recallカーブを確認して最適な閾値を決定します。

# Precision, Recall, Thresholdsの取得
precisions, recalls, thresholds = precision_recall_curve(y_test, y_pred_stack)

# F1スコアが最大になる閾値を探す例
f1_scores = 2 * (precisions * recalls) / (precisions + recalls + 1e-10)
best_idx = np.argmax(f1_scores)
best_threshold = thresholds[best_idx]

print(f"Best Threshold (Max F1): {best_threshold:.4f}")

# ビジネス要件に基づく閾値設定の例
# 「Recall(検知率)を最低でも80%確保したい」場合の閾値
target_recall = 0.8
# Recallが0.8以上の中で最大のPrecisionを持つポイントを探す
valid_indices = np.where(recalls[:-1] >= target_recall)[0]
if len(valid_indices) > 0:
    best_idx_recall = valid_indices[np.argmax(precisions[valid_indices])]
    threshold_for_recall = thresholds[best_idx_recall]
    print(f"Threshold for Recall >= {target_recall}: {threshold_for_recall:.4f}")
    print(f"Expected Precision: {precisions[best_idx_recall]:.4f}")
else:
    print("Target recall cannot be achieved.")

このように、ビジネスKPIから逆算して閾値を設定することが、技術を実益に結びつける最短距離です。

SHAP値を用いたアンサンブルモデルの要因分析

「なぜ不正と判定したのか」という説明責任も重要です。Stackingモデルはブラックボックス化しやすいですが、ベースモデルに対してSHAP値を計算することで要因分析が可能です。

アンサンブルモデル全体のSHAP値計算は困難なため、実務の現場では最も寄与度の高いシングルモデル(LightGBMなど)のSHAP値で代用することが一般的です。

まとめ

ベースモデルの定義(多様性を意識) - Section Image 3

不均衡データという金融特有の課題に対し、アンサンブル学習は極めて有効なソリューションです。

  • Votingで手軽にモデルの安定性を確保する。
  • StackingOOF予測で、リークを防ぎつつ精度を向上させる。
  • ビジネスKPIに基づいた閾値調整で、実用性を高める。

今回紹介したコードはプロトタイプとしての出発点です。実際のデータには時系列性や欠損値など、さらに多くのノイズが含まれる可能性があります。まずは手を動かして実装し、仮説検証のサイクルを高速に回していくことが、AIプロジェクト成功への近道となります。

金融AIの不正検知:不均衡データを攻略するアンサンブル学習の実装全コードとStacking戦略 - Conclusion Image

コメント

コメントは1週間で消えます
コメントを読み込み中...