[ML] Credit Card Fraud Detection

박미영·2023년 5월 25일
0

DataSchool StudyNote - ML

목록 보기
17/19

📌Credit Card Fraud Detection

신용카드 부정 사용자 검출

- 데이터 소개

  • 신용카드 부정사용 검출에 관한 논문의 한 예

  • 데이터 다운로드
    데이터 받은 후 압축을 풀고 소스코드 폴더에 옮겨 두자



- 데이터 개요

  • 신용카드 사기 검출 분류 실습용 데이터
  • 데이터에 class라는 이름의 컬럼이 사기 유무를 의미
  • class 컬럼의 불균형이 극심해서 전체 데이터의 약 0.172%가 1 (사기 Fraud)을 가짐



- 데이터 특성

  • 데이터의 불균형이 극심하다



- 데이터 읽고 관찰

  • 데이터 읽기
import pandas as pd

data_path = '../datasets/creditcard.csv'
raw_data = pd.read_csv(data_path)
raw_data.head()



raw_data.columns

raw_data['Class'].value_counts()

frauds_rate = round(raw_data['Class'].value_counts()[1]/len(raw_data) * 100, 2)
print('Frauds', frauds_rate, '% of the dataset')

  • 데이터 라벨의 불균형이 정말 심하다
  • Fraud 비율이 전체 비율의 0.17%이다.



import seaborn as sns
import matplotlib.pyplot as plt

sns.countplot(x='Class', data=raw_data)
plt.title('Class Distribution \n {0: No Fraud || 1: Fraud}', fontsize=14)
plt.show()

그래프로 표현되기도 힘들다.



  • X, y 데이터 선정
X = raw_data.iloc[:, 1:-1]
y = raw_data.iloc[:, -1]

X.shape, y.shape



  • 데이터 나누기
from sklearn.model_selection import train_test_split

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



  • 나눈 데이터의 불균형 정도 확인
import numpy as np

np.unique(y_train, return_counts=True)

tmp = np.unique(y_test, return_counts=True)[1]
tmp[1]/len(y_test) * 100



📍1st Trial(단순 무식)

- 분류기의 성능 return 함수 생성

from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score

def get_clf_eval(y_test, pred):
    acc = accuracy_score(y_test, pred)
    pre = precision_score(y_test, pred)
    re = recall_score(y_test, pred)
    f1 = f1_score(y_test, pred)
    auc = roc_auc_score(y_test, pred)

    return acc, pre, re, f1, auc



- 성능 출력 함수 생성

from sklearn.metrics import confusion_matrix

def print_clf_eval(y_test, pred):
    confusion = confusion_matrix(y_test, pred)
    acc, pre, re, f1, auc = get_clf_eval(y_test, pred)

    print('=> confusion matrix')
    print(confusion)
    print('===========')

    print('Accuracy: {0:.4f}, Precision: {1:.4f}'.format(acc, pre))
    print('Recall: {0:.4f}, F1: {1:.4f}, AUC: {2:.4f}'.format(re, f1, auc))



- Logistic Regression

from sklearn.linear_model import LogisticRegression

lr_clf = LogisticRegression(random_state=13, solver='liblinear')
lr_clf.fit(X_train, y_train)
lr_pred = lr_clf.predict(X_test)

print_clf_eval(y_test, lr_pred)

92%가 나왔다 하지만 성능이 좋다고 할 수 있는가??
85295 정상 데이터에서 11를 Fraud 데이터라고 오해했다.
하지만 Fraud 데이터 148개 중 60개를 정상 데이터라고 오해했다.
즉 recall(실제 1 중 몇개를 맞췄나)이 59% 밖에 되지 않는다.



- Decision Tree

from sklearn.tree import DecisionTreeClassifier

dt_clf = DecisionTreeClassifier(random_state=13, max_depth=4)
dt_clf.fit(X_train, y_train)
dt_pred = dt_clf.predict(X_test)

print_clf_eval(y_test, dt_pred)

recall이 71%로 Logistic Regression보다는 높게 나왔다.



- Random Forest

from sklearn.ensemble import RandomForestClassifier

rf_clf = RandomForestClassifier(random_state=13, n_jobs=-1, n_estimators=100)
rf_clf.fit(X_train, y_train)
rf_pred = rf_clf.predict(X_test)

print_clf_eval(y_test, rf_pred)

Decision Tree 보다 시간은 좀 더 걸렸지만 recall은 좀 더 올랐다.



- LightGBM

from lightgbm import LGBMClassifier

lgbm_clf = LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1, boost_from_average=False)
lgbm_clf.fit(X_train, y_train)
lgbm_pred = lgbm_clf.predict(X_test)

print_clf_eval(y_test, lgbm_pred)



💡Recall과 Precision의 의미

  • 은행 입장에서는 Recall이 좋을 것이다.
    - 모든 Fraud를 잡고 싶으니까
  • 사용자 입장에서는 Precision이 좋지 않을까?
    - Recall이 높아지면 정상 사용자이지만 Fraud 사용자로 오해 받을 수 있다.



- 성능 출력 함수

모델과 데이터를 주면 성능을 출력하는 함수

def get_result(model, X_train, y_train, X_test, y_test):
    model.fit(X_train, y_train)
    pred = model.predict(X_test)

    return get_clf_eval(y_test, pred)



- 성능을 정리하여 DataFrame으로 반환하는 함수

다수의 모델의 성능을 정리해서 DataFrame으로 반환하는 함수

def get_result_pd(models, model_names, X_train, y_train, X_test, y_test):
    col_names = ['accuracy', 'precision', 'recall', 'f1', 'roc_auc']
    tmp = []

    for model in models:
        tmp.append(get_result(model, X_train, y_train, X_test, y_test))

    return pd.DataFrame(tmp, columns=col_names, index=model_names)



- 4개의 분류 모델 표 정리

import time

models = [lr_clf, dt_clf, rf_clf, lgbm_clf]
model_names = ['LogisticRegression', 'DecisionTree', 'RandomForest', 'LightGBM']

start_time = time.time()
results = get_result_pd(models, model_names, X_train, y_train, X_test, y_test)

print('Fit time : ', time.time() - start_time)
results






📍2nd Trial

데이터를 정리해서 다시 도전


  • Amount 컬럼 확인
plt.figure(figsize=(10, 5))
sns.distplot(raw_data['Amount'], color='r')

plt.show()

컬럼의 분포가 특정 대역이 아주 많다



- StandardScaler

Amount 컬럼에 StandardScaler 적용

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
amount_n = scaler.fit_transform(raw_data['Amount'].values.reshape(-1, 1))

raw_data_copy = raw_data.iloc[:, 1:-2]
raw_data_copy['Amount_Scaled'] = amount_n
raw_data_copy.head()



- 데이터 나누기

X_train, X_test, y_train, y_test = train_test_split(raw_data_copy, y, test_size=0.3, random_state=13, stratify=y)



- 모델 평가

models = [lr_clf, dt_clf, rf_clf, lgbm_clf]
model_names = ['LogisticRegression', 'DecisionTree', 'RandomForest', 'LightGBM']

start_time = time.time()
results = get_result_pd(models, model_names, X_train, y_train, X_test, y_test)

print('Fit time : ', time.time() - start_time)
results



- 모델별 ROC 커브

from sklearn.metrics import roc_curve

def draw_roc_corve(models,model_names,  X_test, y_test):
    plt.figure(figsize=(10, 10))
    
    for model in range(len(models)):
        pred = models[model].predict_proba(X_test)[:, 1]
        fpr, tpr, thresholds = roc_curve(y_test, pred)
        plt.plot(fpr, tpr, label=model_names[model])

    plt.plot([0, 1], [0, 1], 'k--', label='random quess')
    plt.title('ROC')
    plt.legend()
    plt.grid()
    plt.show()

draw_roc_corve(models, model_names, X_test, y_test)



- log scale

amount_log = np.log1p(raw_data['Amount'])

raw_data_copy['Amount_Scaled'] = amount_log
raw_data_copy.head()

plt.figure(figsize=(10, 5))
sns.distplot(raw_data_copy['Amount_Scaled'], color='r')

plt.show()

log가 적용됐기 때문에 큰값은 상대적으로 낮은 값으로 바꿔주고 낮은 값은 그대로 사용하게끔 하는 형태(x가 커질수록 y를 억제하는 경향)



- 성능 확인

X_train, X_test, y_train, y_test =\
        train_test_split(raw_data_copy, y, test_size=0.3, random_state=13, stratify=y)

start_time = time.time()
results = get_result_pd(models, model_names, X_train, y_train, X_test, y_test)

print('Fit time : ', time.time() - start_time)
results

미세한 변화가 보이지만 확실한 변화는 관찰되지 않는다.



- ROC 커브 결과

draw_roc_corve(models, model_names, X_test, y_test)





📍3rd Trial

데이터의 Outlier를 정리해보자



- 특이 데이터(Outlier)

import seaborn as sns
plt.figure(figsize=(10, 7))
sns.boxplot(data=raw_data[['V13', 'V14', 'V15']]);



- Outlier의 인덱스를 파악

Outlier를 정리하기 위해 Outlier의 인덱스를 파악

def get_outlier(df=None, column=None, weight=1.5):
    fraud = df[df['Class']==1][column]
    quantile_25 = np.percentile(fraud.values, 25)
    quantile_75 = np.percentile(fraud.values, 75)

    iqr = quantile_75 - quantile_25
    iqr_weight = iqr * weight
    lowest_val = quantile_25 - iqr_weight
    highest_val = quantile_75 + iqr_weight

    outlier_index = fraud[(fraud < lowest_val) | (fraud > highest_val)].index

    return outlier_index



- Outlier 찾기

get_outlier(df=raw_data, column='V14', weight=1.5)



- Outlier 제거

raw_data_copy.shape
outlier_index = get_outlier(df=raw_data, column='V14', weight=1.5)
raw_data_copy.drop(outlier_index, axis=0, inplace=True)
raw_data_copy.shape

Outlier 4개가 줄어들었다.



- Outlier 제거 후 데이터 나누기

X = raw_data_copy

raw_data.drop(outlier_index, axis=0, inplace=True)
y = raw_data.iloc[:, -1]

X_train, X_test, y_train, y_test =\
        train_test_split(X, y, test_size=0.3, random_state=13, stratify=y)

scaler를 적용한 후 데이터 raw_data_copy -> y 값이 없었다.
따라서 y 데이터는 raw_data에서 받아온다.



- 모델 평가

models = [lr_clf, dt_clf, rf_clf, lgbm_clf]
model_names = ['LogisticRegression', 'DecisionTree', 'RandomForest', 'LightGBM']

start_time = time.time()
results = get_result_pd(models, model_names, X_train, y_train, X_test, y_test)

print('Fit time : ', time.time() - start_time)
results



- ROC 커브

draw_roc_corve(models, model_names, X_test, y_test)






📍4th Trial

SMOTE Oversampling



- Undersampling vs Oversampling

  • 데이터의 불균형이 극심할 때 불균형한 두 클래스의 분포를 강제로 맞춰보는 작업

  • 언더샘플링 : 많은 수의 데이터를 적은 수의 데이터로 강제로 조정

  • 오버샘플링 :
    - 원본데이터의 피처 값들을 아주 약간 변경하여 증식
    - 대표적으로 SMOTE(Synthetic Minority Over-sampling Technique) 방법이 있음
    - 적은 데이터 세트에 있는 개별 데이터를 k-최근접이웃 방법으로 찾아서 데이터의 분포 사이에 새로운 데이터를 만드는 방식
    - imbalanced-learn 이라는 Python pkg가 있음



- INSTALL

!pip install imbalanced-learn



- SMOTE 적용

from imblearn.over_sampling import SMOTE

smote = SMOTE(random_state=13)
X_train_over, y_train_over = smote.fit_resample(X_train, y_train)



⭐중요한 점

데이터를 조작하는 것은 train 데이터에 대해서이다.
절대 test 데이터를 조작해서는 안된다.
(스케일러는 예외를 두기도 한다.)



  • 데이터 증강 효과 확인
X_train.shape, y_train.shape
X_train_over.shape, y_train_over.shape

  • 결과
print(np.unique(y_train, return_counts=True))
print(np.unique(y_train_over, return_counts=True))



- 학습하기

models = [lr_clf, dt_clf, rf_clf, lgbm_clf]
model_names = ['LogisticRegression', 'DecisionTree', 'RandomForest', 'LightGBM']

start_time = time.time()
results = get_result_pd(models, model_names, X_train_over, y_train_over, X_test, y_test)

print('Fit time : ', time.time() - start_time)
results

시간은 조금 오래 걸리지만 recall은 확실히 좋아진다.



- ROC 커브

draw_roc_corve(models, model_names, X_test, y_test)




"이 글은 제로베이스 데이터 취업 스쿨 강의를 듣고 작성한 내용으로 제로베이스 데이터 취업 스쿨 강의 자료 일부를 발췌한 내용이 포함되어 있습니다."

0개의 댓글