[BasicML] Credit Card Fault Detection 실습 02

3 분 소요

이 문서에 나오는 자료와 데이터는 권철민 저의 머신러닝 완벽가이드와 인프런의 강의를 바탕으로 정리하였습니다.
오류나 틀린 부분이 있을 경우 언제든지 댓글 혹은 메일로 남겨주세요 😄

Credit Card Fault Detection 실습 02

  • https://www.kaggle.com/mlg-ulb/creditcardfraud

카드사가 카드 사기건을 검출 및 방지하는 것은 고객이 구매하지 않는 물건에 대해 비용을 지불하지 않도록 하기 위한 매우 중요한 작업임. 따라서 이러한 Fault를 감지하기 위한 ML 모델을 실습을 통해 구축해보고, 성능 테스트를 진행해봄

** 각 피처들의 상관 관계를 시각화. 결정 레이블인 class 값과 가장 상관도가 높은 피처 추출 **

import seaborn as sns

plt.figure(figsize=(9, 9))
corr = card_df.corr()
sns.heatmap(corr, cmap='RdBu')

image

Class와 높은 상관관계를 가진 ‘V14’ 컬럼을 가지고 이상치 제거를 해보도록 하자.

여기서 정의한 이상치는 제1사분위수 - 1.5IQR, 제3사분위수 + 1.5IQR로 정의하였고, 이러한 데이터를 찾기 위한 함수를 구현하면 아래와 같음

** Dataframe에서 outlier에 해당하는 데이터를 필터링하기 위한 함수 생성. outlier 레코드의 index를 반환함. **

import numpy as np

def get_outlier(df=None, column=None, weight=1.5):
    # fraud에 해당하는 column 데이터만 추출, 1/4 분위와 3/4 분위 지점을 np.percentile로 구함. 
    fraud = df[df['Class']==1][column]
    quantile_25 = np.percentile(fraud.values, 25)
    quantile_75 = np.percentile(fraud.values, 75)
    
    # IQR을 구하고, IQR에 1.5를 곱하여 최대값과 최소값 지점 구함. 
    iqr = quantile_75 - quantile_25
    iqr_weight = iqr * weight
    lowest_val = quantile_25 - iqr_weight
    highest_val = quantile_75 + iqr_weight
    
    # 최대값 보다 크거나, 최소값 보다 작은 값을 아웃라이어로 설정하고 DataFrame index 반환. 
    outlier_index = fraud[(fraud < lowest_val) | (fraud > highest_val)].index
    
    return outlier_index
    
outlier_index = get_outlier(df=card_df, column='V14', weight=1.5)
print('이상치 데이터 인덱스:', outlier_index)
이상치 데이터 인덱스: Int64Index([8296, 8615, 9035, 9252], dtype='int64')

V12 컬럼의 8296,8615,9035,9252에 해당하는 인덱스가 앞에서 정의한 이상치에 해당함을 확인

로그 변환 후 V14 피처의 이상치 데이터를 삭제한 뒤 모델들을 재 학습/예측/평가

# get_processed_df( )를 로그 변환 후 V14 피처의 이상치 데이터를 삭제하는 로직으로 변경. 
def get_preprocessed_df(df=None):
    df_copy = df.copy()
    amount_n = np.log1p(df_copy['Amount'])
    df_copy.insert(0, 'Amount_Scaled', amount_n)
    df_copy.drop(['Time','Amount'], axis=1, inplace=True)
    
    # 이상치 데이터 삭제하는 로직 추가
    outlier_index = get_outlier(df=df_copy, column='V14', weight=1.5)
    df_copy.drop(outlier_index, axis=0, inplace=True)
    return df_copy

X_train, X_test, y_train, y_test = get_train_test_dataset(card_df)

print('### 로지스틱 회귀 예측 성능 ###')
get_model_train_eval(lr_clf, ftr_train=X_train, ftr_test=X_test, tgt_train=y_train, tgt_test=y_test)

print('### LightGBM 예측 성능 ###')
get_model_train_eval(lgbm_clf, ftr_train=X_train, ftr_test=X_test, tgt_train=y_train, tgt_test=y_test)

### 로지스틱 회귀 예측 성능 ###
오차 행렬
[[85281    14]
 [   48    98]]
정확도: 0.9993, 정밀도: 0.8750, 재현율: 0.6712,    F1: 0.7597, AUC:0.9743
### LightGBM 예측 성능 ###
오차 행렬
[[85290     5]
 [   25   121]]
정확도: 0.9996, 정밀도: 0.9603, 재현율: 0.8288,    F1: 0.8897, AUC:0.9780

각 모델별로 재현율이 많이 향상된 것을 확인할 수 있다. 다음은 SMOTE를 활용하여 오버 샘플링을 적용하여 모델을 학습하고 평가해보자.

SMOTE 오버 샘플링 적용 후 모델 학습/예측/평가

from imblearn.over_sampling import SMOTE

smote = SMOTE(random_state=0)
X_train_over, y_train_over = smote.fit_sample(X_train, y_train)
print('SMOTE 적용 전 학습용 피처/레이블 데이터 세트: ', X_train.shape, y_train.shape)
print('SMOTE 적용 후 학습용 피처/레이블 데이터 세트: ', X_train_over.shape, y_train_over.shape)
print('SMOTE 적용 후 레이블 값 분포: \n', pd.Series(y_train_over).value_counts())
SMOTE 적용 전 학습용 피처/레이블 데이터 세트:  (199362, 29) (199362,)
SMOTE 적용 후 학습용 피처/레이블 데이터 세트:  (398040, 29) (398040,)
SMOTE 적용 후 레이블 값 분포: 
 0    199020
1    199020
Name: Class, dtype: int64

이를 기존에 y_train.value_counts()해서 얻은 값과 비교해보면, 큰 차이가 있는걸 확인할 수 있음.

0    199020
1       342
Name: Class, dtype: int64

위 그림과 같이 1(부정 거래)값이 0.001정도로 분포되어 있었는데, 이를 오버 샘플링 방법을 통해 0 클래스 값과 1 클래스 값을 똑같이 만들어 줌

다시 로지스틱 회귀를 통해 모델을 평가해보자.

lr_clf = LogisticRegression()
# ftr_train과 tgt_train 인자값이 SMOTE 증식된 X_train_over와 y_train_over로 변경됨에 유의
get_model_train_eval(lr_clf, ftr_train=X_train_over, ftr_test=X_test, tgt_train=y_train_over, tgt_test=y_test)

오차 행렬
[[82937  2358]
 [   11   135]]
정확도: 0.9723, 정밀도: 0.0542, 재현율: 0.9247,    F1: 0.1023, AUC:0.9737

재현율을 높아졌지만, 정밀도가 매우 낮아서 부적한 모델인 것을 확인할 수 있음. 반면 LightGBM은 어떻게 될까?

lgbm_clf = LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1, boost_from_average=False)
get_model_train_eval(lgbm_clf, ftr_train=X_train_over, ftr_test=X_test,
                  tgt_train=y_train_over, tgt_test=y_test)
오차 행렬
[[85283    12]
 [   22   124]]
정확도: 0.9996, 정밀도: 0.9118, 재현율: 0.8493,    F1: 0.8794, AUC:0.9814

앞선 모델보다 정밀도는 떨어졌지만, 재현율이 높아진 것을 확인할 수 있음. 따라서 재현율이 상대적으로 중요한 모델이었기에, 정밀도를 어느 정도 희생하고 재현율을 높이는 초기 목적인 달성이 되었다고 할 수 있다.

각각의 성능을 데이터 가공 단계에 따라 정리해보자.

최종 정리

데이터 가공   정밀도 재현율 ROC-AUC
데이터 가공 없음 로지스틱 회귀 0.8462 0.5946 0.9601
데이터 가공 없음 LightGBM 0.9573 0.7568 0.9790
데이터 로그 변환 로지스틱 회귀 0.8812 0.6014 0.9727
데이터 로그 변환 LightGBM 0.9576 0.7635 0.9796
이상치 데이터 제거 로지스틱 회귀 0.8750 0.6712 0.9743
이상치 데이터 제거 LightGBM 0.9603 0.8288 0.9780
SMOTE 오버 샘플링 로지스틱 회귀 0.0542 0.9247 0.9737
SMOTE 오버 샘플링 LightGBM 0.9118 0.8493 0.9814

초기 데이터 가공이 없었을때와 모델의 성능과 비교하였을 때, 데이터 가공의 효과는 크다고 말할 수 있다. 특히 이상치 데이터 제거를 통해 재현율을 높일 수 있었으며, 이를 통해 이상치 제거는 모델의 성능을 좋게 만드는 것의 기초라고 생각이 된다.

다음에는 모델을 쌓아 성능을 높이는 스태킹 모델에 대해서 배워보도록 한다.