KaggleのCredit Card Fraud Detectionに挑戦!

Kaggleにある「Credit Card Fraud Detection」というデータセットをPythonで簡単に分析,詐欺予測を行いました.

1. データセットの分析

1-a. データセット

このデータセットはcreditcard.csvだけで構成されており,サイズも143.84MBとデータセットの中では比較的小さめです.2日間で起こった284,807件の取引のうち492件の詐欺が記録されています(めっちゃデータ偏ってる!).直接的な入力データは示さず,PCA変換したデータ28次元で示しています.これに加え,データ収集時からの秒数,取引の量(多分ユーロ?)で特徴が表現されていて,出力となるクラスでは”1″が不正取引,”0″が正常な取引を示しています.実際に,CSVをPandasを用いて,Dataframeという形式で,読み込みます.Timeが0~172792のように秒ごとに記録されています.確かに2日分の取引情報が記録されていることがわかります.

Input
import pandas as pd

transactions = pd.read_csv("creditcard.csv")
print(transactions)

#Output
#            Time         V1         V2  ...       V28  Amount  Class
#0            0.0  -1.359807  -0.072781  ... -0.021053  149.62      0
#1            0.0   1.191857   0.266151  ...  0.014724    2.69      0
#2            1.0  -1.358354  -1.340163  ... -0.059752  378.66      0
#3            1.0  -0.966272  -0.185226  ...  0.061458  123.50      0
#4            2.0  -1.158233   0.877737  ...  0.215153   69.99      0
#...          ...        ...        ...  ...       ...     ...    ...
#284802  172786.0 -11.881118  10.071785  ...  0.823731    0.77      0
#284803  172787.0  -0.732789  -0.055080  ... -0.053527   24.79      0
#284804  172788.0   1.919565  -0.301254  ... -0.026561   67.88      0
#284805  172788.0  -0.240440   0.530483  ...  0.104533   10.00      0
#284806  172792.0  -0.533413  -0.189733  ...  0.013649  217.00      0
#
#[284807 rows x 31 columns]

1-b. 特徴量とクラスの関係

各特徴量上の正・不正の分布を描画して各特徴量の関係とクラスの関係をSeabornという可視化ツールを使って見ていきたい.出力結果pairplot.pngでは,縦横軸,特徴量が並んでおり,縦軸は上から,横軸は左から,V1,V2・・・のように配置されています.各特徴量の交差しているところに対応している特徴同士の分布をプロットしています.青色が正常な取引で,オレンジ色が不正な取引を示しています.対角線上の分布は各特徴量の1変数のプロットを示しています.不正のクラスは正常なクラスに比べ,分布がまとまっていますが,多くの特徴次元では,正常な分布に内包されているか,重なっています.V3, V4, V9, V10, V11, V12, V14, V16, V17, V18は分布のズレを視覚的に確認できます.また,’amount’, V28, V23, V21, V20では分布が集中していることがわかります.これらの違いを多次元的に見るだけでもある程度の識別はできると考えられます.

PairPlot
import matplotlib.pyplot as plt
import seaborn as sns

sns.pairplot(transactions.iloc[:,1:],hue = "Class",diag_kind="kde")
plt.savefig('pairplot.png')

pairplot.png

1-c. 特徴量の時間変化の関係

1-bでは時間方向は独立として図示して,分析しました.次に,書く特徴を独立として扱うと正・不正がどのように分布するかをみてみます.まず,不正直前の取引がどのようになっているのかをプロットしてみます.不正を100プロット目として,それ以前の特徴量を描画しました.左上から,順に右下まで各特徴量の時間プロットを示しています.しかし,どの特徴量においても定性的に時系列方向に特徴を確認することができませんでした.まあ,取引した記録といえど,同じ人がしたとは限らないので,時系列的に特徴があるとは考えにくいですね.

TimeSerise
import numpy as np

fraud = transactions[transactions["Class"] == 1]
before = 100
wave = np.zeros([fraud.shape[0], fraud.shape[1], before+1])
for i in range(fraud.shape[0]):
    wave[i,:,:] = transactions.iloc[fraud.index[i]-before:fraud.index[i]+1,:].T
for j in range(fraud.shape[1]):
    df = pd.DataFrame(wave[:,j,:])
    plt.figure()
    plt.plot(df.T)
    plt.savefig('df_V'+str(j)+'.png')

2. 詐欺の識別

では,実際に機械学習による詐欺の識別を行っていきます.

ロジスティック回帰

1.の様子から見るに,時系列の機械学習よりは一つの取引をそれぞれ独立と扱う方が,いい気がするので,典型的なロジスティック回帰で分類を図ってみたいと思います.

各特徴量を正規化したのち,ランダムにデータを4つに分け,クロスバリデーションで学習しました.この識別器の性能を見るために,accuracy, precision, recall, f値, 係数,混合行列を出力しました.係数と混合行列はそれぞれボックスプロットとヒートマップを用いて表現しました.係数マップは横軸=特徴量,縦軸=係数で示しており,絶対値が高ければ,それだけその変数が識別に寄与していることが言えます.混同行列のヒートマップは,左上と右下の値が高く,左下と右上の値が小さいほど,良い識別器であることが言えます.

Resultsでは,accuracyは非常に高い値を示しいるのですが,precision, recall, f-scoreがそれに比べると低い値を示しています.また,混合行列のヒートマップでは,左上の値が非常に高いことがわかる.つまり,この結果は,正常と判断しやすい識別器になってしまっていることがこれらの結果から見えてきます.係数の図を見ると,実際に分類に寄与したのは,V4, V10, V14が大きいところであることが見えます.上記したPairplotと比較しても相違ないことが確認できます.

Results
accuracy = 0.9991819022411386
precision = 0.8687427926932224
recall = 0.6219512195121951
f-score = 0.722974390199201

係数マップ

LogisticRegression
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import accuracy_score, roc_auc_score, confusion_matrix, precision_score, recall_score, f1_score
from sklearn.preprocessing import StandardScaler

target =  transactions.iloc[:,30] #クラスだけを抽出
ss = StandardScaler()
transactions.loc[:,:] = ss.fit_transform(transactions) #正規化
data = transactions.iloc[:,1:30] #特徴量だけを抽出

kf = StratifiedKFold(n_splits = 4, shuffle = True) #クロスバリデーションの宣言
#結果を格納しておく空の変数を用意
coefs = np.zeros([4, 29])
intes = np.zeros([4, 1])
accuracy = np.zeros([4, 1])
precision = np.zeros([4, 1])
recall = np.zeros([4, 1])
fscore = np.zeros([4, 1])
confMs = np.zeros([4, 2, 2])
j = 0
for train, test in kf.split(data,target):
    lr = LogisticRegression() #ロジスティック回帰を宣言
    lr.fit(data.iloc[train], target.iloc[train]) #学習

    answer = target.iloc[test]
    predict = lr.predict(data.iloc[test])
    accuracy[j,:] = accuracy_score(answer, predict) #accuracyの算出
    precision[j,:] = precision_score(answer, predict) #precisionの算出
    recall[j,:] = recall_score(answer, predict) #recallの算出
    fscore[j,:] = f1_score(answer, predict) #F値の算出
    coefs[j,:] = lr.coef_ #係数を抽出
    intes[j,:] = lr.intercept_ #切片を抽出

    confMs[j,:,:] = confusion_matrix(answer, predict) #混合行列の算出
    j += 1

coefs_df = pd.DataFrame(coefs, columns=data.columns)
print(coefs_df)
print(intes)
print("accuracy = " + str(np.mean(accuracy)))
print("precision = " + str(np.mean(precision)))
print("recall = " + str(np.mean(recall)))
print("f-score = " + str(np.mean(fscore)))
plt.figure(figsize=(15,5))
sns.boxplot(x="features", y="coefficient", data=pd.melt(coefs_df, var_name="features", value_name="coefficient")) #係数をプロット
plt.savefig('coefficient.png')


confM = np.mean(confMs, axis = 0) 
print(confM.shape)

plt.figure()
xtics=["Normal", "Fraud"]
ytics=["Normal", "Fraud"]
sns.heatmap(confM,xticklabels=xtics,yticklabels=ytics, annot=True) #混合行列をプロット
plt.xlabel("predict")
plt.ylabel("target")
plt.tight_layout()
plt.savefig('cofusionMatrix.png')

この結果から,気に識別器は良いものではないことが見えるので,データセットの扱いを変える.両クラスが同じ割合になるように,正常な取引をランダムで間引いて,ロジスティック回帰を行い,同じ結果を算出してみる.間引きのコードを上記のプログラムの上流に加えます.以下に,各特徴量に対する係数,混合行列の図,precision,recall,f値を示します.これらの値が大幅に改善していることがわかります.

間引き
df = transactions.sort_values('Class')
normal = df.iloc[:284807-492,:].sample(n=492)
fraud = df.iloc[284807-492:,:]
trans = pd.concat([normal, fraud], axis=0).sample(frac=1)

Results
accuracy = 0.9420731707317074
precision = 0.9801689643602236
recall = 0.9024390243902439
f-score = 0.9395426498152178

係数マップ
混合行列

RandomForest

別のモデルでよく使われるモデルの1つであるRandomForestを試してみる.本来なら,パラメータチューニングを行うが,本記事は厳密なコンペではないため,割愛させていただきます.ロジスティック回帰と同様な前処理を施し,クロスバリデーション,評価の算出も同様です.プログラムはクロスバリデーション内のみ示します.結果をみると,ロジスティック回帰の結果とほぼ同様な結果であることが確認できます.

Results
accuracy = 0.9380081300813008
precision = 0.9715614267673764
recall = 0.9024390243902439
f-score = 0.9356403994574136

重要度マップ
混合行列

RandomForest
from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(max_depth=30, n_estimators=30)
rf.fit(data.iloc[train], target.iloc[train])

answer = target.iloc[test]
predict = rf.predict(data.iloc[test])
accuracy[j,:] = accuracy_score(answer, predict)
precision[j,:] = precision_score(answer, predict)
recall[j,:] = recall_score(answer, predict)
fscore[j,:] = f1_score(answer, predict)
coefs[j,:] = rf.feature_importances_

confMs[j,:,:] = confusion_matrix(answer, predict)
j += 1

XGBoost

次に,XGBoostというアルゴリズムを試してみる.これも同様な前処理,解析方法で結果を算出しました.これも,ロジスティック回帰,RandomForestとほとんど変わらない.

Results
accuracy = 0.9451219512195121
precision = 0.9762810544671484
recall = 0.9126016260162602
f-score = 0.9430332779180052

重要度マップ
混合行列

XGBoost
import xgboost as xgb

xg = xgb.XGBClassifier(objective='binary:logistic',
                        max_depth = 5,
                        learning_rate=0.1,
                        n_estimators=100)
xg.fit(data.iloc[train], target.iloc[train])

answer = target.iloc[test]
predict = xg.predict(data.iloc[test])
accuracy[j,:] = accuracy_score(answer, predict)
precision[j,:] = precision_score(answer, predict)
recall[j,:] = recall_score(answer, predict)
fscore[j,:] = f1_score(answer, predict)
coefs[j,:] = xg.feature_importances_

confMs[j,:,:] = confusion_matrix(answer, predict)
j += 1

SVM

最後にSVMを試してみる.これも同様な前処理,解析方法で結果を算出しました.この結果は,precisionに結果が偏っていることがわかります.

Results
accuracy = 0.8983739837398375
precision = 0.9948118279569893
recall = 0.8008130081300813
f-score = 0.8870274099648281

混合行列

SVM
from sklearn.svm import SVC

sv = SVC(gamma=0.001)
sv.fit(data.iloc[train], target.iloc[train])

answer = target.iloc[test]
predict = sv.predict(data.iloc[test])
accuracy[j,:] = accuracy_score(answer, predict)
precision[j,:] = precision_score(answer, predict)
recall[j,:] = recall_score(answer, predict)
fscore[j,:] = f1_score(answer, predict)
confMs[j,:,:] = confusion_matrix(answer, predict)

j += 1

まとめ

これまで,簡単な分析とロジスティック回帰,RandomForest,XGBoost,SVMを扱ってきました.このまとめとして,データ整形が分類精度に強く寄与していたことがわかります.このほかにもニューラルネットやエラスチック回帰などなど,様々な機械学習アルゴリズムやアンサンブル学習することで,もっと精度上げることができると考えられます.

最後まで読んでいただきありがとうございます!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA