Competition/Kaggle

[kaggle][필사] Credit Card Fraud Detection (1)

bisi 2021. 2. 1. 20:08

 

이번주제는 신용카드 거래가 사기거래인지, 정상거래인지 식별한다.

 

신용카드 회사가 사기 신용카드 거래를 인식 하여 고객이 구매하지 않은 항목에 대해서는 비용이 청구되지 않도록 하는 것이 목표다. 

 

데이터 세트는 2일동안 발생한 거래를 보여주며, 248,807건의 거래중 492건의 사기가 있다. 

데이터 세트는 매우 불균형하며 positive class(Fruad)는 모든 거래의 0.172%를 차지한다.

 

feature 데이터는 기밀 유지 문제로 데이터에 대한 원래 내용과 추가 배경정보는 제공하지 않는다. 

변수명은 V1~ V28로 구성되어, PCA로 한번 가공된 구성요소 이다.

유일하게 변환되지 않은 변수는 '시간'과 '금액'이다. 

타켓 클래스는 응답 변수이며 1이면 사기, 0이면 정상으로 구분한다.

 

필사한 코드는 Janio Martinez Bachmann님의 Credit Fraud || Dealing with Imbalanced Datasets 를 참고했다.

 

총 5가지 부분으로 나눴으며 순서는 아래와 같다. 

 

1) 데이터 이해하기

2) 데이터 전처리

3) 랜덤 UnderSampling and OverSampling

4) 상관행렬

5) 테스팅


 

 

 

 

출처 : https://www.kaggle.com/janiobachmann/credit-fraud-dealing-with-imbalanced-datasets

신용 사기 탐지기(Credit card fraud detection)

신용 카드 회사는 사기 신용 카드 거래를 인식할 수 있으므로 고객이 구매하지 않은 항목에 대해 비용이 청구되지 않도록 하는 것이 중요하다.

이 커널에서 우리는 다양한 예측 모델을 사용하여 트랜잭션이 정상적인 결제인지 아니면 사기인지 탐지하는 데 얼마나 정확한지 확인할 것이다. 데이터 세트에 설명된 대로 기능이 확장되고 개인 정보 보호 이유로 인해 기능 이름이 표시되지 않는다. 그럼에도 불구하고, 우리는 여전히 데이터 세트의 몇 가지 중요한 측면을 분석할 수 있다.

  • 목표

    • 우리에게 제공된 "작은" 데이터의 작은 분포를 이해한다.
    • "사기" 및 "비사기" 트랜잭션의 50/50 하위 데이터 프레임 비율을 생성한. (NearMiss 알고리즘)
    • 사용할 분류기를 결정하고 정확도가 더 높은 분류기를 결정한다.
    • 신경망(Neural Network)을 만들고 정확도를 최상의 분류기와 비교한다.
    • 불균형 데이터셋으로 인한 일반적인 오류를 이해합니다.
  • 개요

    • 데이터 이해하기
    • 전처리
      • scaling and Distributing
      • 데이터 나누기
    • 랜덤 UnderSampling and Oversampling
      • Distributing and Correlating
      • 이상 감지(Anomaly Detection)
      • Dimensionality Reduction and Clustering(t-SNE)
      • 분류(Classifiers)
      • 로지스틱 회귀
      • SMOTE와 Oversampling
    • 테스팅
      • 로지스틱 회귀 테스트
      • 신경망 테스트
 

1. 데이터 이해하기

데이터 세트에는 2013년 9월 유럽 카드 소지자가 신용카드로 만든 거래가 포함되어 있다. 이 데이터 세트는 2일 동안 발생한 거래르 보여 주며, 284,807 건의 거래 중 492건의 사기가 있었다. 데이터 세트는 매우 불균형하며 모지티브 클래스(사기)는 모드 거래의 0.172%를 차지한다.

PCA 변환 결과 인 숫자 입력 변수만 포함한다. 안타깝게도 기밀 유지 문제로 각 컬럼이 무슨 의미를 하는지는 알 수없다. PCA로 변환되지 않는 유일한 기능은 '시간'과 '양'이다. '시간'에는 각 트랜잭션과 데이터 세트의 첫번째 트랜잭션 사이에 경과 된 시간(초)이 포함된다. '금액',Amount는 거래 금액이며, 이 기능은 예에따라 비용에 민감한 학습에 사용할 수 있다.

'Class' 변수는 부정행위를 하면 1이고, 그렇지 않으면 0을 사용한다.

  • 데이터 요약
    • 거래 금액은 상대적으로 적고, 모든 마운트의 평균은 대략 USD 88정도 이다. NULL 값이 없으므로 대체할 방법을 찾지 않아도 된다.
    • 대부분 트랜잭션은 부정행위(99.83%)가 아닌 반면 부정 행위 트랜잭션은 데이터 프레임에서 시간(0.17%)이 발생한다.

변수에 적용된 기술

- PCA : 모든 변수가 PCA(Dimensionality Reduction)변환을 거쳤다.(time, amount는 제외)
- Scaling : PCA 변환 기능을 구현하려면 이전에 Scaling을 해야 한다.
In [1]:
# 기본 라이브러리
import numpy as np
import pandas as pd
import tensorflow as tf
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA, TruncatedSVD
import matplotlib.patches as mpatches
import time

# model 관련

from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.neighbors import  KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
import collections

# 다른 라이브러리
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from imblearn.pipeline import make_pipeline as imbalanced_make_pipeline
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import NearMiss
from imblearn.metrics import classification_report_imbalanced
from sklearn.metrics import precision_score, recall_score, f1_score, roc_auc_score, accuracy_score, classification_report
from collections import Counter
from sklearn.model_selection import  KFold, StratifiedKFold
import warnings
warnings.filterwarnings("ignore")
In [2]:
df = pd.read_csv('../data/creditcard.csv')
df.head()
Out[2]:
  Time V1 V2 V3 V4 V5 V6 V7 V8 V9 ... V21 V22 V23 V24 V25 V26 V27 V28 Amount Class
0 0.0 -1.359807 -0.072781 2.536347 1.378155 -0.338321 0.462388 0.239599 0.098698 0.363787 ... -0.018307 0.277838 -0.110474 0.066928 0.128539 -0.189115 0.133558 -0.021053 149.62 0
1 0.0 1.191857 0.266151 0.166480 0.448154 0.060018 -0.082361 -0.078803 0.085102 -0.255425 ... -0.225775 -0.638672 0.101288 -0.339846 0.167170 0.125895 -0.008983 0.014724 2.69 0
2 1.0 -1.358354 -1.340163 1.773209 0.379780 -0.503198 1.800499 0.791461 0.247676 -1.514654 ... 0.247998 0.771679 0.909412 -0.689281 -0.327642 -0.139097 -0.055353 -0.059752 378.66 0
3 1.0 -0.966272 -0.185226 1.792993 -0.863291 -0.010309 1.247203 0.237609 0.377436 -1.387024 ... -0.108300 0.005274 -0.190321 -1.175575 0.647376 -0.221929 0.062723 0.061458 123.50 0
4 2.0 -1.158233 0.877737 1.548718 0.403034 -0.407193 0.095921 0.592941 -0.270533 0.817739 ... -0.009431 0.798278 -0.137458 0.141267 -0.206010 0.502292 0.219422 0.215153 69.99 0

5 rows × 31 columns

In [3]:
# 각 컬럼의 기초 통계값 확인1
df.describe()
Out[3]:
  Time V1 V2 V3 V4 V5 V6 V7 V8 V9 ... V21 V22 V23 V24 V25 V26 V27 V28 Amount Class
count 284807.000000 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 ... 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 2.848070e+05 284807.000000 284807.000000
mean 94813.859575 1.165980e-15 3.416908e-16 -1.373150e-15 2.086869e-15 9.604066e-16 1.490107e-15 -5.556467e-16 1.177556e-16 -2.406455e-15 ... 1.656562e-16 -3.444850e-16 2.578648e-16 4.471968e-15 5.340915e-16 1.687098e-15 -3.666453e-16 -1.220404e-16 88.349619 0.001727
std 47488.145955 1.958696e+00 1.651309e+00 1.516255e+00 1.415869e+00 1.380247e+00 1.332271e+00 1.237094e+00 1.194353e+00 1.098632e+00 ... 7.345240e-01 7.257016e-01 6.244603e-01 6.056471e-01 5.212781e-01 4.822270e-01 4.036325e-01 3.300833e-01 250.120109 0.041527
min 0.000000 -5.640751e+01 -7.271573e+01 -4.832559e+01 -5.683171e+00 -1.137433e+02 -2.616051e+01 -4.355724e+01 -7.321672e+01 -1.343407e+01 ... -3.483038e+01 -1.093314e+01 -4.480774e+01 -2.836627e+00 -1.029540e+01 -2.604551e+00 -2.256568e+01 -1.543008e+01 0.000000 0.000000
25% 54201.500000 -9.203734e-01 -5.985499e-01 -8.903648e-01 -8.486401e-01 -6.915971e-01 -7.682956e-01 -5.540759e-01 -2.086297e-01 -6.430976e-01 ... -2.283949e-01 -5.423504e-01 -1.618463e-01 -3.545861e-01 -3.171451e-01 -3.269839e-01 -7.083953e-02 -5.295979e-02 5.600000 0.000000
50% 84692.000000 1.810880e-02 6.548556e-02 1.798463e-01 -1.984653e-02 -5.433583e-02 -2.741871e-01 4.010308e-02 2.235804e-02 -5.142873e-02 ... -2.945017e-02 6.781943e-03 -1.119293e-02 4.097606e-02 1.659350e-02 -5.213911e-02 1.342146e-03 1.124383e-02 22.000000 0.000000
75% 139320.500000 1.315642e+00 8.037239e-01 1.027196e+00 7.433413e-01 6.119264e-01 3.985649e-01 5.704361e-01 3.273459e-01 5.971390e-01 ... 1.863772e-01 5.285536e-01 1.476421e-01 4.395266e-01 3.507156e-01 2.409522e-01 9.104512e-02 7.827995e-02 77.165000 0.000000
max 172792.000000 2.454930e+00 2.205773e+01 9.382558e+00 1.687534e+01 3.480167e+01 7.330163e+01 1.205895e+02 2.000721e+01 1.559499e+01 ... 2.720284e+01 1.050309e+01 2.252841e+01 4.584549e+00 7.519589e+00 3.517346e+00 3.161220e+01 3.384781e+01 25691.160000 1.000000

8 rows × 31 columns

In [4]:
# null 값 체크
df.isnull().sum().max()
Out[4]:
0
In [5]:
df.columns
Out[5]:
Index(['Time', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'V7', 'V8', 'V9', 'V10',
       'V11', 'V12', 'V13', 'V14', 'V15', 'V16', 'V17', 'V18', 'V19', 'V20',
       'V21', 'V22', 'V23', 'V24', 'V25', 'V26', 'V27', 'V28', 'Amount',
       'Class'],
      dtype='object')
In [6]:
print('No Frauds', round(df['Class'].value_counts()[0] / len(df) *100,2), '%of the dataset')
print('Frauds', round(df['Class'].value_counts()[1] / len(df) *100,2), '%of the dataset')
 
No Frauds 99.83 %of the dataset
Frauds 0.17 %of the dataset
 

위의 데이터를 보면 원본 데이터 세트가 얼마나 불균형한지 주목해야 한다. 대부분의 거래는 사기가 아니다. 이 데이터 프레임을 예측 모델 및 분석의 기반으로 사용하면 오류가 많이 발생할 수 있으며 대부분의 트랜잭션이 부정 행위가 아니라고 "가정"하기 때문에 알고리즘이 지나치게 적합할 수 있습니다. 하지만 우리는 우리 모델이 부정행위의 징후를 보이는 패턴을 탐지하기를 원하지 않는다.

In [7]:
colors = ["#0101DF", "#DF0101"]

sns.countplot('Class', data=df, palette=colors)
plt.title('Class Distribution \n (0: No Fraud || 1:Fraud', fontsize=14)
Out[7]:
Text(0.5, 1.0, 'Class Distribution \n (0: No Fraud || 1:Fraud')
 
 

분포를 보면 이러한 현상이 얼마나 치우쳐 있는지 알 수 있으며, 다른 형상의 추가 분포도 볼수 있다. 향후 이 노트북에서 구현될 배포의 왜곡을 줄이는 데 도움이 될 수 있는 기술들이 있다.

In [8]:
fig, ax = plt.subplots(1,2,figsize=(20,10))

amount_val = df['Amount'].values
time_val = df['Time'].values

sns.distplot(amount_val, ax=ax[0], color='r')
ax[0].set_title('Distribution of Transaction Amount', fontsize=14)
ax[0].set_xlim([min(amount_val), max(amount_val)])

sns.distplot(time_val, ax=ax[1], color='b')
ax[1].set_title('Distribution of Transaction Time', fontsize=14)
ax[1].set_xlim([min(time_val), max(time_val)])

plt.show()
 
 

2. 전처리

2.1 scaling and Distributing

커널의 이 단계에서는 먼저 time과 amount으로 구성된 열을 확장할 것입니다. time과 amount은 다른 열과 같이 조정해야 합니다. 한편, 우리는 동일한 양의 부정 행위 및 비 부정 행위 사례를 갖기 위해 데이터 프레임의 하위 샘플을 생성하여 트랜잭션이 부정 행위인지 여부를 결정하는 패턴을 더 잘 이해할 수 있도록 도와야 한다.

  • sub-Sample 이란 무엇이냐?

    • 이 시나리오에서 우리의 하위 샘플은 50/50의 부정 행위 및 비사기 거래 비율을 가진 데이터 프레임이 될 것이다.
    • 즉, sub-Sample은 동일한 양의 부정 행위 및 비 부정 행위 트랜잭션을 가집니다.
  • sub-Sample 을 만드는 이유는 무엇입니까? 앞에서 원본 데이터가 심하게 불균형한 것을 확인했다. 이 데이터를 그대로 사용하면 다음과 같은 문제가 발생한다.

    • 과적합(overfitting): 우리의 분류 모델은 대부분의 경우 사기는 없다고 가정할 것이다. 우리가 우리 모델에 원하는 것은 사기가 발생했을 때 확실해지는 것이다.
    • 잘못된 상관 관계: 주어진 변수들이 이 무엇을 의미하는지 알 수는 없지만, 클래스 및 기능 간의 실제 상관 관계를 확인할 수 없는 불균형 데이터 프레임을 통해 이러한 각 기능이 결과에 어떤 영향을 미치는지 이해하는 것이 유용할 것입니다(사기 또는 부정 행위 없음).
  • 요약:

    • Scaled amount과 Scaled time은 scaled value을 가진 컬럼입니다.
    • 데이터 세트에 492건의 부정 행위 사례가 있으므로 492건의 부정 행위 사례를 무작위로 가져와 새로운 sub dataframe을 생성할 수 있다.
    • 492건의 사기 및 비사기 사례를 연결하여 새로운 sub-sample을 생성한다.
In [9]:
from sklearn.preprocessing import StandardScaler, RobustScaler
# RobustScaler 가 outlier 값에 덜 노출 된다.

std_scaler = StandardScaler()
rob_scaler = RobustScaler()

df['scaled_amount'] = rob_scaler.fit_transform(df['Amount'].values.reshape(-1,1))
df['scaled_time'] = rob_scaler.fit_transform(df['Time'].values.reshape(-1,1))

# 기존 컬럼은 삭제
df.drop(['Time', 'Amount'], axis=1, inplace=True)
In [10]:
scaled_amount = df['scaled_amount']
scaled_time = df['scaled_time']

# 다시 dataframe 정렬
df.drop(['scaled_amount', 'scaled_time'], axis=1, inplace=True)
df.insert(0, 'scaled_amount', scaled_amount)
df.insert(1, 'scaled_time', scaled_time)

df.head()
Out[10]:
  scaled_amount scaled_time V1 V2 V3 V4 V5 V6 V7 V8 ... V20 V21 V22 V23 V24 V25 V26 V27 V28 Class
0 1.783274 -0.994983 -1.359807 -0.072781 2.536347 1.378155 -0.338321 0.462388 0.239599 0.098698 ... 0.251412 -0.018307 0.277838 -0.110474 0.066928 0.128539 -0.189115 0.133558 -0.021053 0
1 -0.269825 -0.994983 1.191857 0.266151 0.166480 0.448154 0.060018 -0.082361 -0.078803 0.085102 ... -0.069083 -0.225775 -0.638672 0.101288 -0.339846 0.167170 0.125895 -0.008983 0.014724 0
2 4.983721 -0.994972 -1.358354 -1.340163 1.773209 0.379780 -0.503198 1.800499 0.791461 0.247676 ... 0.524980 0.247998 0.771679 0.909412 -0.689281 -0.327642 -0.139097 -0.055353 -0.059752 0
3 1.418291 -0.994972 -0.966272 -0.185226 1.792993 -0.863291 -0.010309 1.247203 0.237609 0.377436 ... -0.208038 -0.108300 0.005274 -0.190321 -1.175575 0.647376 -0.221929 0.062723 0.061458 0
4 0.670579 -0.994960 -1.158233 0.877737 1.548718 0.403034 -0.407193 0.095921 0.592941 -0.270533 ... 0.408542 -0.009431 0.798278 -0.137458 0.141267 -0.206010 0.502292 0.219422 0.215153 0

5 rows × 31 columns

 

2.2 데이터 나누기

Random UnderSampling을 적용하기 전에 원래 데이터 프레임을 분리해야 한다. 테스트 목적으로 랜덤 언더 샘플링 또는 오버 샘플링 기술을 구현할 때,데이터를 분할한다. 하지만, 이러한 기술 중 하나에 의해 생성된 데이터세트가 아닌 원래 데이터 세트에서 모델을 테스트하고자 하는 이유를 기억해야한다. 이렇게 테스트를 하는 이유는 표본이 부족했던 데이터 프레임에 모델을 적용시키고 원래 테스트 데이터에서 테스트를 진행하기 위해서이다.

In [11]:
from sklearn.model_selection import train_test_split
from sklearn.model_selection import StratifiedShuffleSplit

print('No Frauds', round(df['Class'].value_counts()[0]/len(df) * 100,2), '% of the dataset')
print('Frauds', round(df['Class'].value_counts()[1]/len(df) * 100,2), '% of the dataset')

X = df.drop('Class', axis=1)
y = df['Class']

sss = StratifiedKFold(n_splits=5, random_state=None, shuffle=False)

for train_index, test_index in sss.split(X,y):
    print("Train:", train_index, "Test:", test_index)
    original_Xtrain, original_Xtest = X.iloc[train_index], X.iloc[test_index]
    original_ytrain, original_ytest = y.iloc[train_index], y.iloc[test_index]
 
No Frauds 99.83 % of the dataset
Frauds 0.17 % of the dataset
Train: [ 30473  30496  31002 ... 284804 284805 284806] Test: [    0     1     2 ... 57017 57018 57019]
Train: [     0      1      2 ... 284804 284805 284806] Test: [ 30473  30496  31002 ... 113964 113965 113966]
Train: [     0      1      2 ... 284804 284805 284806] Test: [ 81609  82400  83053 ... 170946 170947 170948]
Train: [     0      1      2 ... 284804 284805 284806] Test: [150654 150660 150661 ... 227866 227867 227868]
Train: [     0      1      2 ... 227866 227867 227868] Test: [212516 212644 213092 ... 284804 284805 284806]
In [12]:
original_Xtrain = original_Xtrain.values
original_Xtest = original_Xtest.values
original_ytrain = original_ytrain.values
original_ytest = original_ytest.values

train_unique_label, train_counts_label = np.unique(original_ytrain, return_counts=True)
test_unique_label, test_counts_label = np.unique(original_ytest, return_counts=True)

print('-'*100)

print('Label Distributions: \n')
print(train_counts_label / len(original_ytrain))
print(test_counts_label / len(original_ytest))
 
----------------------------------------------------------------------------------------------------
Label Distributions: 

[0.99827076 0.00172924]
[0.99827952 0.00172048]
 

3. 랜덤 UnderSampling and Oversampling

프로젝트의 이 단계에서는 우리는 보다 균형 잡힌 데이터 세트를 갖기 위해 기본적으로 데이터를 제거하여 모델이 과적합 되지 않도록 하기 위해 구성된 "Random UnderSampling"을 구현할 것이다.

  • 샘플링 방법
    • 언더 샘플링(undersampling)
      • 많은 데이터 세트를 적은 데이터 수준으로 감소 시키는 것.
      • 즉, 정상 레이블을 가진 데이터 10,000건 이상 레이블을 가진 데이터가 100건이 있으면 정상 레이블 데이터를 100건으로 줄여버리는 방식.
      • 단점 : 과도하게 정상 레이블로 학습/예측하는 부작용을 개선할 순 있지만, 너무 많은 데이터를 감소시키기 때문에 정상 레이블의 경우 오히려 제대로 된 학습을 수행할 수 없다.
    • 오버 샘플링(oversampling)
      • 이상 데이터와 같이 적은 데이터 세트를 증식하여 학습을 위한 충분한 데이터를 확보하는 방법
      • 원본 데이터의 피처 값들을 아주 약간만 변경하여 증식함.
      • 대표적으로 SMOTE(Synthetic Minority Over-Sampling Technique)방법이 있음.
        • SMOTE: 적은 데이터 세트에 있는 개별 데이터들의 K 최근접 이웃(K Nearest Neighbor)을 찾아서 이 데이터와 K개 이웃들의 차이를 일정 값으로 만들어서 기존 데이터와 약간 차이가 나는 새로운 데이터를 생성하는 방식.
In [13]:
df = df.sample(frac=1)

fraud_df = df.loc[df['Class'] == 1]
non_fraud_df  = df.loc[df['Class'] == 0][:492]

normal_distributed_df= pd.concat([fraud_df, non_fraud_df])

new_df = normal_distributed_df.sample(frac=1, random_state=42)

new_df.head()
Out[13]:
  scaled_amount scaled_time V1 V2 V3 V4 V5 V6 V7 V8 ... V20 V21 V22 V23 V24 V25 V26 V27 V28 Class
271805 -0.279746 0.940601 -1.884596 1.917237 0.397137 -1.345021 0.901485 -0.618572 1.880378 -1.815193 ... 0.280778 -0.057670 -0.132690 0.053564 0.050694 -1.392314 -0.356587 -2.810152 -0.797600 0
150669 2.326836 0.107708 -10.632375 7.251936 -17.681072 8.204144 -10.166591 -4.510344 -12.981606 6.783589 ... -0.810146 2.715357 0.695603 -1.138122 0.459442 0.386337 0.522438 -1.416604 -0.488307 1
50409 0.964158 -0.472444 -1.034058 0.794847 -0.366211 0.284920 2.533730 4.361385 -0.868533 -1.986624 ... 0.648424 -1.421613 0.253240 -0.368613 1.031479 -0.101026 -0.310346 -0.017136 -0.102407 0
81186 0.330329 -0.303927 -4.384221 3.264665 -3.077158 3.403594 -1.938075 -1.221081 -3.310317 -1.111975 ... -0.141533 2.076383 -0.990303 -0.330358 0.158378 0.006351 -0.493860 -1.537652 -0.994022 1
192382 -0.279466 0.528390 0.753356 2.284988 -5.164492 3.831112 -0.073622 -1.316596 -1.855495 0.831079 ... 0.285792 0.382007 0.033958 0.187697 0.358433 -0.488934 -0.258802 0.296145 -0.047174 1

5 rows × 31 columns

 

3.1 Distributing and Correlating

이제 데이터 프레임의 균형이 올바르게 잡혔으므로 분석 및 데이터 사전 처리를 더 진행할 수 있다.

In [14]:
print('Distribution of the Classes in the subsample dataset')
print(new_df['Class'].value_counts()/len(new_df))

sns.countplot('Class', data=new_df, palette=colors)
plt.title('Equally Distribution Classes', fontsize=14)
plt.show()
 
Distribution of the Classes in the subsample dataset
1    0.5
0    0.5
Name: Class, dtype: float64
 

소스 코드 git hub 바로가기