メインコンテンツへスキップ

銀行におけるデータサイエンス:不正検知

銀行業界でデータサイエンスがどのように実装されているかを、不正検知という最も一般的なユースケースの一つを通じて学びます。
更新 2026年8月31日  · 11 分 読む

AIで探索

ChatGPTClaudePerplexity

フィンテックのコンセプトイラスト

銀行は、歴史的に大量の構造化データが収集されてきた幸運な分野の一つであり、データサイエンス技術が最初に適用された分野の一つでもあります。

では、銀行ではデータサイエンスがどのように使われているのでしょうか。今日では、この領域においてデータは最も価値のある資産となっています。銀行が競合に歩調を合わせ、より多くの顧客を惹きつけ、既存顧客のロイヤルティを高め、より効率的なデータドリブンの意思決定を行い、事業を強化し、業務効率を高め、既存のサービス/製品を改善して新たな提供を実現し、セキュリティを強化し、その結果として収益を増やすためには、データサイエンスが不可欠です。データサイエンスの求人需要の大半が銀行から生じているのも不思議ではありません。

データサイエンスにより、銀行業界は以下のような数多くのタスクをうまく遂行できます。 

  • 投資リスク分析
  • 顧客生涯価値(CLV)の予測
  • 顧客セグメンテーション
  • 顧客離反率の予測
  • パーソナライズド・マーケティング
  • 顧客感情分析
  • バーチャルアシスタントとチャットボット

以下では、銀行における最も一般的なデータサイエンスのユースケースの一つを詳しく見ていきます。

銀行におけるデータサイエンスのユースケース:不正検知 

不正行為は、銀行だけでなく、政府、保険、公共部門、販売、ヘルスケアなど多くの分野で難しい課題となっています。オンライン取引が多いビジネスは、重大な不正リスクを抱えます。金融犯罪には、クレジットカードの不正利用、小切手の偽造、脱税、マネーロンダリング、サイバー攻撃、顧客アカウントの窃取、合成アイデンティティ、虚偽申請、詐欺など、さまざまな形態があります。

不正検知とは、不正行為と金銭的損失を特定・防止するための能動的な取り組みの総称です。主な分析手法は次の2つに大別されます。

  • 統計手法:統計パラメータの算出、回帰、確率分布、データマッチング
  • 人工知能(AI):データマイニング、機械学習、ディープラーニング

機械学習は不正検知の重要な柱です。そのツールキットには2つのアプローチがあります。

  • 教師あり手法:k 近傍法、ロジスティック回帰、サポートベクターマシン、決定木、ランダムフォレスト、時系列分析、ニューラルネットワークなど
  • 教師なし手法:クラスタ分析、リンク分析、自己組織化マップ、主成分分析、異常検知など

不正検知に万能で信頼できる機械学習アルゴリズムは存在しません。実務のユースケースでは、複数の手法やその組み合わせを試し、モデルの予測精度を算出して、最適なアプローチを選択するのが一般的です。

不正検知システムの主な課題は、絶えず変化する不正パターンや手口に素早く適応し、ますます巧妙化する新たなスキームを迅速に見抜くことです。不正の事例は常に少数派であり、実際の取引の中に巧妙に紛れています。

データセットの準備

ここでは、Python を使ってクレジットカード不正検知の機械学習実装を試してみます。使用する creditcard_data データセットは、Kaggle のCredit Card Fraud Detectionのサンプルを加工したものです。元データは、2013年9月の2日間にヨーロッパのカード保有者が行ったクレジットカードの取引を表しています。

データをインポートして、ざっと確認してみましょう。

import pandas as pd

creditcard_data = pd.read_csv('creditcard_data.csv', index_col=0)
print(creditcard_data.info())
print('\n')
pd.options.display.max_columns = len(creditcard_data)
print(creditcard_data.head(3))
<class 'pandas.core.frame.DataFrame'>
Int64Index: 5050 entries, 0 to 5049
Data columns (total 30 columns):
#   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
0   V1      5050 non-null   float64
1   V2      5050 non-null   float64
2   V3      5050 non-null   float64
3   V4      5050 non-null   float64
4   V5      5050 non-null   float64
5   V6      5050 non-null   float64
6   V7      5050 non-null   float64
7   V8      5050 non-null   float64
8   V9      5050 non-null   float64
9   V10     5050 non-null   float64
10  V11     5050 non-null   float64
11  V12     5050 non-null   float64
12  V13     5050 non-null   float64
13  V14     5050 non-null   float64
14  V15     5050 non-null   float64
15  V16     5050 non-null   float64
16  V17     5050 non-null   float64
17  V18     5050 non-null   float64
18  V19     5050 non-null   float64
19  V20     5050 non-null   float64
20  V21     5050 non-null   float64
21  V22     5050 non-null   float64
22  V23     5050 non-null   float64
23  V24     5050 non-null   float64
24  V25     5050 non-null   float64
25  V26     5050 non-null   float64
26  V27     5050 non-null   float64
27  V28     5050 non-null   float64
28  Amount  5050 non-null   float64
29  Class   5050 non-null   int64 
dtypes: float64(29), int64(1)
memory usage: 1.2 MB


        V1        V2        V3        V4        V5        V6        V7  \
0  1.725265 -1.337256 -1.012687 -0.361656 -1.431611 -1.098681 -0.842274  
1  0.683254 -1.681875  0.533349 -0.326064 -1.455603  0.101832 -0.520590  
2  1.067973 -0.656667  1.029738  0.253899 -1.172715  0.073232 -0.745771  

        V8        V9       V10       V11       V12       V13       V14  \
0 -0.026594 -0.032409  0.215113  1.618952 -0.654046 -1.442665 -1.546538  
1  0.114036 -0.601760  0.444011  1.521570  0.499202 -0.127849 -0.237253  
2  0.249803  1.383057 -0.483771 -0.782780  0.005242 -1.273288 -0.269260  

        V15       V16       V17       V18       V19       V20       V21  \
0 -0.230008  1.785539  1.419793  0.071666  0.233031  0.275911  0.414524  
1 -0.752351  0.667190  0.724785 -1.736615  0.702088  0.638186  0.116898  
2  0.091287 -0.347973  0.495328 -0.925949  0.099138 -0.083859 -0.189315  

        V22       V23       V24       V25       V26       V27       V28  \
0  0.793434  0.028887  0.419421 -0.367529 -0.155634 -0.015768  0.010790  
1 -0.304605 -0.125547  0.244848  0.069163 -0.460712 -0.017068  0.063542  
2 -0.426743  0.079539  0.129692  0.002778  0.970498 -0.035056  0.017313  

  Amount  Class 
0  189.00      0 
1  315.17      0 
2   59.98      0 

このデータセットには次の変数が含まれています。

  • 数値化された V1〜V28 は、PCA 変換で得られた主成分です。機密保持のため、元の特徴量に関する背景情報は提供されていません。
  • Amount 変数は取引金額を表します。
  • Class 変数は、取引が不正(1)か否か(0)を示します。

不正発生は本質的に、どの取引リストでも幸い少数です。しかし、機械学習アルゴリズムは、データセット内の各クラスがある程度均等に含まれているときに最も良く機能するのが一般的です。そうでないと、学習できるデータがほとんどありません。この問題はクラス不均衡と呼ばれます。

データセット内の不正の計算

データセット内の総取引数に対する不正取引の割合を計算してみましょう。

round(creditcard_data['Class'].value_counts()*100/len(creditcard_data)).convert_dtypes()
0    99
1     1
Name: Class, dtype: Int64

そして、不正と正常のデータ点を可視化するプロットを作成します。

import matplotlib.pyplot as plt
import numpy as np

def prep_data(df):
    X = df.iloc[:, 1:28]
    X = np.array(X).astype(float)
    y = df.iloc[:, 29]
    y = np.array(y).astype(float)
    return X, y

def plot_data(X, y):
    plt.scatter(X[y==0, 0], X[y==0, 1], label='Class #0', alpha=0.5, linewidth=0.15)
    plt.scatter(X[y==1, 0], X[y==1, 1], label='Class #1', alpha=0.5, linewidth=0.15, c='r')
    plt.legend()
    return plt.show()

X, y = prep_data(creditcard_data)

plot_data(X, y)

不正取引

SMOTE を用いたデータの再バランス 

ここまでで、不正取引の比率が非常に低く、クラス不均衡の問題があることを確認できました。これを是正するには、SMOTE(Synthetic Minority Over-sampling Technique、合成少数クラス過サンプリング手法)を用いてデータを再バランスできます。ランダムなオーバーサンプリングと異なり、SMOTE は単に観測値の完全なコピーを作るのではありません。不正事例の最近傍の特徴を用いて、新しい合成サンプルを生成し、少数クラスの既存観測値にかなり近いサンプルを作ります。では、クレジットカードデータに SMOTE を適用してみましょう。

from imblearn.over_sampling import SMOTE

method = SMOTE()
X_resampled, y_resampled = method.fit_resample(X, y)
plot_data(X_resampled, y_resampled)

SMOTE グラフ

ご覧のとおり、SMOTE によって少数クラスの観測値が一気に増えました。さらに結果をわかりやすくするため、元データと比較してみます。

def compare_plot(X, y, X_resampled, y_resampled, method):
    f, (ax1, ax2) = plt.subplots(1, 2)
    c0 = ax1.scatter(X[y==0, 0], X[y==0, 1], label='Class #0',alpha=0.5)
    c1 = ax1.scatter(X[y==1, 0], X[y==1, 1], label='Class #1',alpha=0.5, c='r')
    ax1.set_title('Original set')
    ax2.scatter(X_resampled[y_resampled==0, 0], X_resampled[y_resampled==0, 1], label='Class #0', alpha=.5)
    ax2.scatter(X_resampled[y_resampled==1, 0], X_resampled[y_resampled==1, 1], label='Class #1', alpha=.5,c='r')
    ax2.set_title(method)
    plt.figlegend((c0, c1), ('Class #0', 'Class #1'), loc='lower center', ncol=2, labelspacing=0.)
    plt.tight_layout(pad=3)
    return plt.show()

print(f'Original set:\n'
      f'{pd.value_counts(pd.Series(y))}\n\n'
      f'SMOTE:\n'
      f'{pd.value_counts(pd.Series(y_resampled))}\n')

compare_plot(X, y, X_resampled, y_resampled, method='SMOTE')
Original set:
0.0    5000
1.0      50
dtype: int64


SMOTE:
0.0    5000
1.0    5000
dtype: int64

Smote 手法のグラフ

このように、SMOTE によってデータは完全にバランスされ、少数クラスは多数クラスと同じサイズになりました。

SMOTE の実践的な適用には後ほど戻るとして、まずは元のデータに立ち返り、不正事例の検知を試みます。「オールドスクール」なやり方では、不正を捕捉するルールを作成する必要があります。例えば、取引場所の異常や取引頻度の不自然な多さなどに関するルールです。一般的な統計、しばしば観測値の平均に基づいてしきい値を設定し、そのしきい値を特徴量に適用して不正を検知する、という考え方です。

print(creditcard_data.groupby('Class').mean().round(3)[['V1', 'V3']])
        V1     V3
Class             
0      0.035  0.037
1     -4.985 -7.294

このケースでは、V1 < -3 かつ V3 < -5 という条件を適用してみます。次に、このアプローチの性能を評価するため、フラグ付けされた不正事例と実際の不正を比較します。

creditcard_data['flag_as_fraud'] = np.where(np.logical_and(creditcard_data['V1']<-3, creditcard_data['V3']<-5), 1, 0)
print(pd.crosstab(creditcard_data['Class'], creditcard_data['flag_as_fraud'], rownames=['Actual Fraud'], colnames=['Flagged Fraud']))
Flagged Fraud     0   1
Actual Fraud          
0              4984  16
1                28  22

ロジスティック回帰の適用 

50件の不正のうち22件を検知できましたが、残り28件は見逃し、誤検知も16件発生しました。機械学習手法を使えば、これらの結果を上回れるか見てみましょう。

ここでは、クレジットカードデータに単純なロジスティック回帰の分類アルゴリズムを実装して不正発生を識別し、その結果を混同行列で可視化します。

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)

lr = LogisticRegression()
lr.fit(X_train, y_train)
predictions = lr.predict(X_test)
print(pd.crosstab(y_test, predictions, rownames=['Actual Fraud'], colnames=['Flagged Fraud']))
Flagged Fraud   0.0  1.0
Actual Fraud           
0.0            1504    1
1.0             1      9

ここで混同行列の観測数が少ないのは、モデル結果の計算にテストセット、すなわちデータ全体の30%のみを使用しているためである点に注意してください。

捕捉できた不正の割合は 90%(10件中9件)で、先ほどの 44%(50件中22件)よりも高くなりました。誤検知も大幅に減っており、改善が見られます。

続いて、先に述べたクラス不均衡の問題に立ち戻り、ロジスティック回帰モデルと SMOTE によるリサンプリングを組み合わせることで、予測結果をさらに向上できるか検討します。効率的に一度で実行するため、パイプラインを定義してデータに適用します。

from imblearn.pipeline import Pipeline

# Defining which resampling method and which ML model to use in the pipeline
resampling = SMOTE()
lr = LogisticRegression()

pipeline = Pipeline([('SMOTE', resampling), ('Logistic Regression', lr)])
pipeline.fit(X_train, y_train)
predictions = pipeline.predict(X_test)
print(pd.crosstab(y_test, predictions, rownames=['Actual Fraud'], colnames=['Flagged Fraud']))
Flagged Fraud   0.0  1.0
Actual Fraud           
0.0            1496    9
1.0               1    9

ご覧のとおり、今回のケースでは SMOTE による改善は見られませんでした。依然として不正の 90% を捕捉できていますが、誤検知がやや増えています。これは、リサンプリングが常に良い結果をもたらすとは限らないためです。不正事例がデータ全体に広く分散している場合、その最近傍が必ずしも不正とは限らず、SMOTE の使用によりバイアスが入り得ます。

まとめ 

次の一手として、ロジスティック回帰モデルの精度を高めるために、アルゴリズムの各種パラメータをチューニングする方法が考えられます。データセットを単純に二分割する代わりに、K 分割交差検証を検討してもよいでしょう。最後に、他の機械学習アルゴリズム(例:決定木やランダムフォレスト)も試し、より良い結果が得られるか確認することができます。 

不正検知モデルの理論面・技術面についてさらに学ぶには、Fraud Detection in Python コースの教材を参照してください。

トピック
データサイエンス
機械学習