Credit Card Fraud Detection Modeling - 신용카드 부정 사용 검출
신용카드 부정 사용 검출
데이터 소개
- Kaggle 데이터: https://www.kaggle.com/MLG-ULB/CREDITCARDFRAUD
- 신용카드 사기 검출 분류 실습용 데이터
- 데이터에 class라는 이름의 컬럼이 사기 유무를 의미
- class 컬럼의 불균형이 극심해서 전체 데이터의 약 0.172%가 1(사기 fraud)를 가짐
데이터 특성
- 금융 데이터이고 기업의 기밀 보호를 위해 대다수 특성의 이름은 삭제되어 있음
- Amount: 거래 금액
- Class: Fraud이면 1, 아니면 0
Kaggle에 공유되어 있는 금융 데이터를 활용하여 신용카드 부정 사용자 검출 모델링을 진행해 볼 것이며, 이 과정에서 머신러닝 모델링간 어떤 데이터 전처리 방법들을 시도해 볼 수 있는지 알아보고 적용해 볼 것이다.
# read data
import pandas as pd
data_path = './creditcard.csv'
raw_data = pd.read_csv(data_path)
raw_data.head()
- 데이터의 특성은 여러 이유로 이름이 감춰져 있다.
raw_data['Class'].value_counts()
- 데이터 라벨의 불균형이 심하다 (전체 데이터의 0.17%가 1 = fraud)
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] # Time 피처 제외
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) # stratify
- 특히 fraud 같은 imbalanced 데이터에서 stratify해주는 것이 중요하다.
# train, test 데이터의 불균형 정도 조회
import numpy as np
np.unique(y_train, return_counts=True)
tmp = np.unique(y_train, return_counts=True)[1]
tmp[1]/len(y_train) * 100
np.unique(y_test, return_counts=True)
tmp = np.unique(y_test, return_counts=True)[1]
tmp[1]/len(y_test) * 100
- train, test 모두 target이 거의 동일한 비율로 분리되었다.
모델링
우선 가볍게 모델들을 돌려서 테스트해 보겠다.
# 분류기의 성능을 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)
# 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)
# 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)
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)
- 대체적으로, 모든 모델에서 Accuracy와 Precision 성능은 괜찮게 나왔지만, Recall은 비교적 낮게 나왔다.
- 사용자 입장에서는 Precision이 더 좋겠지만, 은행 입장에선 Recall 성능이 좋은 것이 더 좋다(신용카드 부정 사용인데, 부정 사용이 아니라고 예측해 버리면 안 되기 때문이다). 따라서, Accuracy와 Recall을 높이는 것을 목표로 모델링을 해보겠다.
# 분류기의 성능을 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
# 모델과 데이터를 주면 성능을 출력하는 함수
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으로 반환하는 함수
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 = ['LinearReg', '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
- 확실히 앙상블 계열의 성능이 우수하다.
데이터를 정규화 해서 다시 시도
# raw_data의 Amount 컬럼 확인
plt.figure(figsize=(10,5))
sns.distplot(raw_data['Amount'], color='r')
plt.show();
- 컬럼의 분포가 특정 대역이 아주 많다.
Amount 컬럼에 Standard Scaling 적용
Amount 데이터의 스케일링을 표준 정규 분포 형태로 맞추기 위해 Standard Scaling을 적용해서 진행해 보겠다.
# 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 = ['LinearReg', '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_curve(models, model_names, X_test, y_test):
plt.figure(figsize=(6,6))
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 guess')
plt.title('ROC')
plt.legend()
plt.grid()
plt.show()
draw_roc_curve(models, model_names, X_test, y_test)
Amount 컬럼에 Log Scaling 적용
이번에는 Amount 피처의 원래 값에 log 함수를 적용해 보다 정규분포에 가까운 형태로 값을 변환해 보겠다.
# log scale
amount_log = np.log1p(raw_data['Amount']) # log1p : log가 0일때 0을 지나도록 변경
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();
- Amount 피처의 분포가 변화하였다.
# 다시 성능 확인
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_curve(models, model_names, X_test, y_test)
데이터의 Outlier를 정리해서 다시 시도
# 특이 데이터
import seaborn as sns
plt.figure(figsize=(8,5))
sns.boxplot(data=raw_data[['V13','V14','V15']]);
- 문제가 있다고 알려져 있는 3개의 피처의 분포도 확인 결과, 이상치가 보인다.
# 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 제거
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 삭제 결과, 데이터가 (284807, 29)에서 (284803, 29)로 감소하였다.
# 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)
# 모델에 다시 평가
models = [lr_clf, dt_clf, rf_clf, lgbm_clf]
model_names = ['LinearReg', '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
Oversampling을 적용해서 다시 시도
Resampling이란 데이터의 불균형이 극심할 때 불균형한 두 클래스의 분포를 강제로 맞춰보는 작업이다.
크게 두 가지 방법이 있는데 :
Undersampling
- 많은 수의 데이터를 적은 수의 데이터로 강제로 조정한다.
- 이 데이터셋의 경우 Class 0의 데이터 양을 줄여서 Class 1의 데이터 양에 맞추는 것이다.
- 데이터가 충분히 많을 때 사용한다.
Oversampling
- 원본데이터의 피처 값들을 아주 약간 변경하여 증식시킨다.
- 이 데이터셋의 경우 Class 1의 데이터 양을 늘려서 Class 0의 데이터에 맞추는 것이다.
- 대표적으로 SMOTE(Synthetic Minority Over-sampling Technique) 방법이 있다.
- 적은 데이터 세트에 있는 개별 데이터를 k-최근접이웃(KNN) 방법으로 찾아서 데이터의 분포 사이에 새로운 데이터를 만드는 방식이다.
- imbalanced-learn 이라는 Python 패키지가 있다.
- train, test로 나눈 데이터에서 train 데이터에 대해서만 oversampling을 적용해야 한다.
이 데이터셋은 매우 imbalanced한 데이터이기 때문에, 리샘플링이 꼭 필요하다고 판단된다. Fraud를 의미하는 Class 1이 전체 데이터의 0.17% 밖에 안 되기 때문에, Undersampling을 하기엔 너무 많은 정보 손실을 가져올 것 같아, Oversampling을 하여 진행하겠다.
SMOTE 적용
# 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)
- 기존 데이터 ((199362, 29), (199362,))에서 ((398040, 29), (398040,))로 데이터가 증가되었다.
print(np.unique(y_train, return_counts=True))
print(np.unique(y_train_over, return_counts=True))
- Class 1의 데이터 양이 Class 0의 데이터 양에 맞춰졌다.
# 다시 학습을 돌려보자
models = [lr_clf, dt_clf, rf_clf, lgbm_clf]
model_names = ['LinearReg', '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_curve(models, model_names, X_test, y_test)
Random Oversampling 적용
# Random Oversampling 적용
from imblearn.over_sampling import RandomOverSampler
ros = RandomOverSampler(random_state=13)
X_train_over, y_train_over = ros.fit_resample(X_train, y_train)
y_train_over.value_counts()
- Class 1의 데이터 양이 Class 0의 데이터 양에 맞춰졌다.
# 다시 학습을 돌려보자
models = [lr_clf, dt_clf, rf_clf, lgbm_clf]
model_names = ['LinearReg', '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
- Oversampling을 적용하기 이전의 모델 성능과 비교해서 Recall 성능이 크게 증가하였다.
- SMOTE를 적용한 결과와 비교하면, RandomForest와 LightGBM의 Recall 성능은 조금 감소하였지만, Precision 성능은 더 증가하였다.
- 결과적으로, Oversampling을 적용한 것이 모델 성능을 크게 향상시켰다.
# ROC 커브 결과
draw_roc_curve(models, model_names, X_test, y_test)
Author And Source
이 문제에 관하여(Credit Card Fraud Detection Modeling - 신용카드 부정 사용 검출), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@eodud0582/Credit-Card-Fraud-Detection-Modeling저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)