機械学習モデル、決定木(ディシジョン・ツリー)による分析を活用した金融詐欺検知の大規模展開

Databricks の Notebook を試してみる

人工知能(AI)を活用した金融不正行為検知の大規模展開は、いかなるユースケースにおいても容易なことではありません。膨大の履歴データの取捨選択、絶えず進化する機械学習と深層学習技術の複雑さ、不正行為の実例の少なさなどが、不正行為パターンの検知を困難にしています。金融サービス業界においては、セキュリティに対する懸念の高まりや、不正行為がどのように特定されたかを説明することの重要性が加わり、複雑さがさらに増大しています。

一般的に、検知パターンを作成するために、まずはドメインエキスパートが不正行為者が行うであろう行為を想定して一連のルールを作成します。ワークフローに金融詐欺検知の専門家を含めて、特定の動作に関する要件をまとめる場合もあります。その後、データサイエンティストは、利用可能なデータのサブサンプルを取得し、これらの要件と、場合によっては既存の金融不正事例を参照して、深層学習または機械学習アルゴリズムのセットを選択します。そして、データエンジニアが、この検知パターンを本番環境で使用するために、結果として生じるモデルを、しきい値(条件分岐の境目となる値)を持つルールセットに変換します。これには通常、SQL を使用します。

このアプローチにより、金融機関は一般データ保護規則 ( GDPR ) に準拠した不正取引を特定する明確な特性を提示することができます。しかし、このアプローチにもいくつか課題があります。まず、ハードコードされたルールセットを使用した不正検出システムの実装は非常に脆弱です。不正パターンに変更を加えると、更新に非常に時間がかかってしまうため、現在の市場で起こっている不正行為の変化に追いついて、対応することが困難です。

さらに、上記のワークフローのシステムは各々がサイロ化(孤立化)されることが多く、ドメインエキスパート、データサイエンティスト、データエンジニアが全て区分化されています。データエンジニアの重要な役割は、膨大な量のデータを管理し、ドメインエキスパートやデータサイエンティストの作業を本番レベルのコードに変換することです。共通のプラットフォームがない場合、ドメインエキスパートとデータサイエンティストは、分析のために単一のマシンに適合するサンプリングされたダウンデータに頼るしかありません。このようなサイロ化された環境では、お互いのコミュニケーションが難しくなり、コラボレーションの欠如につながります。

このブログでは、データブリックスを採用して、不正検出のキープレイヤーであるドメインエキスパート、データサイエンティスト、データエンジニアを統合し、いくつかのルールベースの検出ユースケースをデータブリックスのプラットフォーム上の機械学習ユースケースに変換する方法を紹介します。具体的には、大規模なデータセットからモジュラー機能を構築するフレームワークを活用して、機械学習で不正検出データパイプラインを作成し、リアルタイムでデータを視覚化・分析する方法、また、決定木(ディシジョン・ツリー)や Apache Spark MLlib を採用して不正を検出する方法・メリット・特徴を解説します。その後、MLflow を使用してモデルの反復処理と改良を行い、精度を向上させます。

機械学習によるソリューション

金融業界では、機械学習モデルの採用に比較的消極的な見方があります。それは、特定された不正なケースを正当化できない「ブラックボックス」ソリューションと考えられているためです。GDPR 要件と金融規制にデータサイエンスの能力を活用することは一見不可能です。しかし、いくつかの成功したユースケースでは、機械学習を適用して大規模に不正行為を検出することで、上述した多くの問題を解決できることが示されています。

 

実際に確認された不正行為の事例が少ないため、金融不正を検出する「教師あり機械学習モデル」をトレーニングすることは困難です。しかし、特定の不正行為を識別する既知のルールセットの存在は、合成ラベルセットと特徴量の初期セットを作成するのに役立ちます。また、この分野のドメインエキスパートが開発した検出パターンの出力は、適切な承認プロセスを経て本番環境で運用されているはずです。これは、予想される不正行為フラグを生成するので、機械学習モデルをトレーニングするための出発点として使用することができます。これにより、次の 3 つの懸念が同時に軽減されます。

  1. トレーニングラベルの欠如
  2. 使用する特徴量の決定
  3. モデルに適したベンチマークの有無

ルールベースの不正行為フラグを認識する機械学習モデルをトレーニングすると、混同行列を介して予想される出力と直接比較できます。結果がルールベースの検出パターンと適合していれば、このアプローチによって、不正防止に機械学習を採用することへの信頼を得ることができます。また、このモデルの出力の解釈は非常に簡単なため、元の検出パターンと比較した場合の予想される検出漏れと誤検出の基本的な議論に役立つかもしれません。

決定木(ディシジョン ツリー)モデルの活用

機械学習モデルの解釈が難しいという懸念は、初期の機械学習モデルとして決定木モデルを使用することで解決できるかもしれません。決定木モデルは一連のルールに従ってトレーニングされているため、決定木は他の機械学習モデルよりも優れています。決定木モデルを使用するさらなるメリット・特徴としては、モデルの最大限の透過性です。このモデルは基本的に不正の意思決定プロセスを示しますが、人間の介入や、ルールやしきい値のハードコーディングを不要にします。もちろん、モデルの将来の反復過程では、最大の精度にするために異なるアルゴリズムを利用する可能性も理解する必要があります。アルゴリズムに組み込まれた特徴を理解しなければ、透過的なモデルは実現できないからです。解釈可能な特徴を持つことで、解釈可能で制御可能なモデルの結果が得られます。

機械学習アプローチを採用する最大のメリットは、初期のモデリング作業の後は、将来の反復がモジュール化され、ラベル、特徴量、あるいはモデルタイプのセットが非常に簡単でシームレスに更新され、運用までの時間が短縮される点です。この作業は、データブリックスの統合分析プラットフォームの採用でさらに促進されます。このプラットフォームでは、ドメインエキスパート、データサイエンティスト、データエンジニアが同じデータセットを大規模に処理し、Notebook環境で直接共同作業を行うことができます。それでは始めましょう。

決定木モデル構築のためのデータの取り込みと探索

この例では、合成データセットを使用します。データセットを自分でロードするには、Kaggleからローカルマシーンにダウンロードし、AzureAWS 経由でデータをインポートしてください。

PaySimデータは、アフリカのある国で実施されたモバイルマネーサービスの1ヶ月の財務ログから抽出した、実際の取引のサンプルに基づくモバイルマネー取引をシミュレーションしたものです。次の表は、データセットが提供する情報を示しています。

データの探索

DataFrames を作成します。Databricks ファイルシステム( DBFS )にデータをアップロードしたので、Spark SQL を使って  DataFrames を迅速かつ容易に作成できます。

# Create df DataFrame which contains our simulated financial fraud detection dataset
df = spark.sql("select step, type, amount, nameOrig, oldbalanceOrg, newbalanceOrig, nameDest, oldbalanceDest, newbalanceDest from sim_fin_fraud_detection")

DataFrame を作成したので、スキーマと最初の1000 行をみてデータを確認します。

# Review the schema of your data
df.printSchema()
root
|-- step: integer (nullable = true)
|-- type: string (nullable = true)
|-- amount: double (nullable = true)
|-- nameOrig: string (nullable = true)
|-- oldbalanceOrg: double (nullable = true)
|-- newbalanceOrig: double (nullable = true)
|-- nameDest: string (nullable = true)
|-- oldbalanceDest: double (nullable = true)
|-- newbalanceDest: double (nullable = true)

取引の種類

データをわかりやすいように視覚化して、データが捉えた取引の種類と、全体の取引件数に対する割合をみてみましょう。

また、ここで取り上げているデータの金額がどのくらいかを理解するために、取引の種類と、現金の動きに基づいたデータ(つまり、総取引金額)を視覚化します。

ルールベースのモデル

モデルのトレーニングをするために、既存の不正事例の大規模なデータセットを使用することはほとんどありません。多くの実用的なアプリケーションでは、ドメインエキスパートによって確立された一連のルールで不正な検出パターンを識別します。ここでは、こうしたルールに基づくラベルと呼ばれる列を作成します。

# Rules to Identify Known Fraud-based
df = df.withColumn("label", 
                   F.when(
                     (
                       (df.oldbalanceOrg <= 56900) & (df.type == "TRANSFER") & (df.newbalanceDest <= 105)) | ( (df.oldbalanceOrg > 56900) & (df.newbalanceOrig <= 12)) | ( (df.oldbalanceOrg > 56900) & (df.newbalanceOrig > 12) & (df.amount > 1160000)
                           ), 1
                   ).otherwise(0))

ルールによってフラグを立てたデータの視覚化

これらのルールは、多くの場合、かなりの数の不正なケースにフラグを立てます。フラグが立てられた取引の数を視覚化してみると、ルールは、取引の約 4% 、ドル総額の 11% を不正としてフラグを立てたことがわかります。

適切な機械学習モデルの選択

In many cases, a black box approach to fraud detection cannot be used. First, the domain experts need to be able to understand why a transaction was identified as fraudulent. Then, if action is to be taken, the evidence has to be presented in court. The decision tree is an easily interpretable model and is a great starting point for this use case.

トレーニングセットの作成

機械学習モデルを構築して検証するには、.randomSplitを使って 80/20 分割を行います。これにより、ランダムに選択されたデータの 80% がトレーニング用に、残りの 20% が結果の検証用に確保されます。

# Split our dataset between training and test datasets
(train, test) = df.randomSplit([0.8, 0.2], seed=12345)

機械学習モデルパイプラインの作成

モデルのデータを準備するには、まず、 .StringIndexerを使用してカテゴリ変数を数値に変換します。次に、モデルで使用する機能を全て組み立てる必要があります。決定木モデルに加えて、これらの特徴量の準備手順を含むパイプラインを作成し、さまざまなデータセットでこれらの手順を繰り返せるようにします。最初にパイプラインをトレーニングデータに適合させ、後の段階でそれを使ってテストデータを変換するので注意してください。

from pyspark.ml import Pipeline
from pyspark.ml.feature import StringIndexer
from pyspark.ml.feature import VectorAssembler
from pyspark.ml.classification import DecisionTreeClassifier

# Encodes a string column of labels to a column of label indices
indexer = StringIndexer(inputCol = "type", outputCol = "typeIndexed")

# VectorAssembler is a transformer that combines a given list of columns into a single vector column
va = VectorAssembler(inputCols = ["typeIndexed", "amount", "oldbalanceOrg", "newbalanceOrig", "oldbalanceDest", "newbalanceDest", "orgDiff", "destDiff"], outputCol = "features")

# Using the DecisionTree classifier model
dt = DecisionTreeClassifier(labelCol = "label", featuresCol = "features", seed = 54321, maxDepth = 5)

# Create our pipeline stages
pipeline = Pipeline(stages=[indexer, va, dt])

# View the Decision Tree model (prior to CrossValidator)
dt_model = pipeline.fit(train)

モデルの視覚化

パイプラインの最終ステージで決定木モデルである display() を呼び出すと、各ノードで選択した決定を含む初期の適合モデルを表示します。これにより、アルゴリズムが結果の予測にどのように到達したのかを理解しやすくなります。

display(dt_model.stages[-1])

決定木モデルの視覚的表現

モデルのチューニング

最適ツリーモデルを確保するために、複数のパラメータのバリエーションを用いてモデルをクロス検証します。データが 96% の負のケースと 4% の正のケースで構成されていることを考えると、不均衡な分布を説明するために、適合率・再現率(PR )の評価指標を使用します。

from pyspark.ml.tuning import CrossValidator, ParamGridBuilder

# Build the grid of different parameters
paramGrid = ParamGridBuilder() \
.addGrid(dt.maxDepth, [5, 10, 15]) \
.addGrid(dt.maxBins, [10, 20, 30]) \
.build()

# Build out the cross validation
crossval = CrossValidator(estimator = dt,
                          estimatorParamMaps = paramGrid,
                          evaluator = evaluatorPR,
                          numFolds = 3)  
# Build the CV pipeline
pipelineCV = Pipeline(stages=[indexer, va, crossval])

# Train the model using the pipeline, parameter grid, and preceding BinaryClassificationEvaluator
cvModel_u = pipelineCV.fit(train)

 

モデルの性能

モデルを評価するには、トレーニングセットとテストセットの適合率・再現率(PR)と ROC 曲線下の面積(AUC)メトリクスを比較します。PRと AUC は共にかなり高いようです。

# Build the best model (training and test datasets)
train_pred = cvModel_u.transform(train)
test_pred = cvModel_u.transform(test)

# Evaluate the model on training datasets
pr_train = evaluatorPR.evaluate(train_pred)
auc_train = evaluatorAUC.evaluate(train_pred)

# Evaluate the model on test datasets
pr_test = evaluatorPR.evaluate(test_pred)
auc_test = evaluatorAUC.evaluate(test_pred)

# Print out the PR and AUC values
print("PR train:", pr_train)
print("AUC train:", auc_train)
print("PR test:", pr_test)
print("AUC test:", auc_test)

---
# Output:
# PR train: 0.9537894984523128
# AUC train: 0.998647996459481
# PR test: 0.9539170535377599
# AUC test: 0.9984378183482442

モデルが結果を誤って分類した過程を確認するために、matplotlib と pandas を使用して、混同行列を視覚化しましょう。

 

クラスのバランスをとる

このモデルは、識別された元のルールよりも 2421 件多い事例を識別していることがわかります。より多くの潜在的な不正事例を検出することは良いことかもしれないので、この結果に関してそれほど心配する必要はありません。しかし、アルゴリズムによって検出されなかったが、最初に識別されていた事例が58 件あります。ここで私たちが試みているのは、アンダーサンプリングを使用してクラスのバランスを取り、予測をさらに改善することです。 つまり、全ての不正事例は保持し、不正でない事例をダウンサンプルして数を一致させ、バランスの取れたデータセットの取得です。新しいデータセットを視覚化すると、「はい」と「いいえ」の事例は半々であることがわかりました。

# Reset the DataFrames for no fraud (`dfn`) and fraud (`dfy`)
dfn = train.filter(train.label == 0)
dfy = train.filter(train.label == 1)

# Calculate summary metrics
N = train.count()
y = dfy.count()
p = y/N

# Create a more balanced training dataset
train_b = dfn.sample(False, p, seed = 92285).union(dfy)

# Print out metrics
print("Total count: %s, Fraud cases count: %s, Proportion of fraud cases: %s" % (N, y, p))
print("Balanced training dataset count: %s" % train_b.count())

---
# Output:
# Total count: 5090394, Fraud cases count: 204865, Proportion of fraud cases: 0.040245411258932016
# Balanced training dataset count: 401898
---

# Display our more balanced training dataset
display(train_b.groupBy("label").count())

パイプラインの更新

では、機械学習パイプラインを更新し、新しいクロス検証を作成しましょう。機械学習パイプラインを使用しているため、新しいデータセットで更新するだけで、同じパイプラインステップをすぐに繰り返すことができます。

# Re-run the same ML pipeline (including parameters grid)
crossval_b = CrossValidator(estimator = dt,
estimatorParamMaps = paramGrid,
evaluator = evaluatorAUC,
numFolds = 3)
pipelineCV_b = Pipeline(stages=[indexer, va, crossval_b])

# Train the model using the pipeline, parameter grid, and BinaryClassificationEvaluator using the `train_b` dataset
cvModel_b = pipelineCV_b.fit(train_b)

# Build the best model (balanced training and full test datasets)
train_pred_b = cvModel_b.transform(train_b)
test_pred_b = cvModel_b.transform(test)

# Evaluate the model on the balanced training datasets
pr_train_b = evaluatorPR.evaluate(train_pred_b)
auc_train_b = evaluatorAUC.evaluate(train_pred_b)

# Evaluate the model on full test datasets
pr_test_b = evaluatorPR.evaluate(test_pred_b)
auc_test_b = evaluatorAUC.evaluate(test_pred_b)

# Print out the PR and AUC values
print("PR train:", pr_train_b)
print("AUC train:", auc_train_b)
print("PR test:", pr_test_b)
print("AUC test:", auc_test_b)

---
# Output: 
# PR train: 0.999629161563572
# AUC train: 0.9998071389056655
# PR test: 0.9904709171789063
# AUC test: 0.9997903902204509

結果の確認

それでは、新しい混同行列の結果をみてみましょう。モデルが不正事例を誤認したのは 1 件のみでした。クラスのバランスを取ることで、モデルが改善されたことがわかります。

モデルのフィードバックとMLflow の利用

生産のためにモデルを選択したら、継続的にフィードバックを収集して、モデルが目的の動作をさらに特定しているかを確認していきます。私たちは、ルールベースのラベルから始めたので、人間のフィードバックに基づく検証済みの真のラベルを将来のモデルに提供したいと考えます。この段階は、機械学習プロセスにおける信頼性を維持するために極めて重要です。アナリストは全てのケースを確認することができないので、モデルの出力を検証できるように慎重に選択したケースを提示することが大切です。たとえば、モデルの確実性が低い予測は、アナリストによるレビューに適しています。このようなフィードバックを追加することで、モデルは変化する状況に合わせて確実に改善され、進化し続けます。

MLflowは、さまざまなモデルのバージョンをトレーニングする際に、このサイクル全体で役立ちます。MLflow を使用すると、異なるモデル構成とパラメータの結果を比較して、実験の経過を追跡できます。たとえば、ここではMLflow UI を使用して、均衡なデータセットと不均衡なデータセット上でトレーニングされたモデルのPRとAUCを比較することができます、データサイエンティストは、MLflowを使用して、さまざまなモデルメトリックスや追加の視覚化やアーティファクトの経過を追跡し、本番環境で展開するモデルの決定に役立てることができます。そして、データエンジニアは、.jar ファイルとしてトレーニングに使われるライブラリーのバージョンと併せて、選択したモデルを容易に取り込み、本番環境の新しいデータにデプロイすることが可能です。このように、モデルの結果をレビューするドメインエキスパート、モデルを更新するデータサイエンティスト、本番でモデルを展開するデータエンジニアの間の共同作業は、この反復プロセスを通じて強化されます。

まとめ

このブログでは、ルールベースの不正検出ラベルの使い方、MLflowを使用できるデータブリックスで機械学習モデルに変換する方法の例を検証してきました。このアプローチによって、絶えず変化する不正行動パターンに対応できる、スケーラブルなモジュールソリューションを構築できます。不正を特定する機械学習モデルを構築することで、モデルを進化させ、新しい潜在的な不正パターンを特定できるフィードバックループを作成できます。特に、解釈性と優れた精度を持つ決定木モデルが、機械学習を不正検出プログラムに導入する際の出発点として適していることを解説しました。

この取り組みに、データブリックスのプラットフォームを使用する主なメリットは、データサイエンティスト、エンジニア、ビジネスユーザーがプロセス全体でシームレスに連携できることです。データの準備、モデルの構築、結果の共有、本番環境へのモデルの導入は同じプラットフォームで実行され、これまでにないコラボレーションが可能になります。このアプローチにより、これまでサイロ化していたチーム間の信頼関係が築かれ、効果的で動的な不正検出プログラムの確立を実現します。本ブログが、AI(人工知能)の金融へのアプローチ事例、参考になれば幸いです。

無料トライアルにサインアップすると、この Notebook をお試しいただけます。ぜひ独自のモデル作成を開始してください。

Databricks 無料トライアル 使ってみる

ご登録

機械学習ライフサイクルを標準化する方法をお読みください。
ダウンロードする