본문 바로가기
머신러닝 with Python

[머신러닝 with Python] Light GBM 실습 / 신용카드 사기 검출 데이터(Credit Card Fraud) 활용(2)

by CodeCrafter 2023. 9. 17.
반응형

이번에는 지난 포스팅에 이어서 신용카드 사기 검출 데이터 (Credict Card Fraud Detection Data)를 Light GBM으로 분석해보겠습니다.

 

[머신러닝 with Python] Light GBM 실습 / 신용카드 사기 검출 데이터(Credit Card Fraud) 활용

 

[머신러닝 with Python] Light GBM 실습 / 신용카드 사기 검출 데이터(Credit Card Fraud) 활용

이번 포스팅에서는 지난번에 알아봤던 Light GBM을 활용해서 실습을 진행해보겠습니다. 지난 포스팅에서는 작은 수의 데이터를 활용했기에 Light GBM의 성능향상을 크게 확인하지는 못했는데요. 이

jaylala.tistory.com

 

이번 포스팅에서는 지난 포스팅에서 말씀드렸던 것처럼, 

 

1) 데이터 분포도 변환 후 모델 학습/예측/평가(불균형 데이터의 정규화 후 재학습)

2) 이상치(Outlier) 제거 후 재학습

3) 불균형 데이터 중 숫자가 적은 Class를 OverSampling 기법으로 향상 시킨 후 재학습

 

위 3가지를 추가적으로 알아보도록 하겠습니다. 물론, 파이썬 코드와 함께 말이죠!

 

*** 아래 코드는 위 포스팅에 나온 코드가 선행이 되어야지 오류 없이 진행될 수 있습니다 ***

1. 데이터 분포도 변환 후 모델 학습/예측/평가

- 지난번 분석간 사용했던 로지스틱 회귀는 선형 모델입니다. 대부분의 선형 모델은 중요 피처들의 값이 정규 분포 형태를 유지할 경우 그 성능이 더 좋은 경우가 많은데요.

 

- 여러 개의 피처들(features) 중 PCA로 도출되지 않았으면서 가장 중요한 정보중 하나인 Amount의 분포에 대해서 알아보겠습니다.

 

import seaborn as sns

plt.figure(figsize=(8, 4))
plt.xticks(range(0, 30000, 1000), rotation=60)
sns.distplot(df['Amount'])

- 위 결과를 확인해보면, 대부분 1,000불 이하의 데이터로 구성되었다는 것을 알 수 있으며, 매우 소수이지만 27,000불의 경우도 있음을 알 수 있습니다.

 

- 위에서 말씀드렸던 것 처럼 로지스틱 회귀의 성능을 향상시키기 위해 해당 데이터를 정규화(Standardization)을 시킨 뒤, 로지스틱 회귀와 Light GBM을 학습시켜 보겠습니다.

 

from sklearn.preprocessing import StandardScaler
# 사이킷런의 StandardScaler를 이용하여 정규분포 형태로 Amount 피처값 변환하는 로직으로 수정.
def get_preprocessed_df(df=None):
    df_copy = df.copy()
    scaler = StandardScaler()
    amount_n = scaler.fit_transform(df_copy['Amount'].values.reshape(-1, 1))
    # 변환된 Amount를 Amount_Scaled로 피처명 변경후 DataFrame맨 앞 컬럼으로 입력
    df_copy.insert(0, 'Amount_Scaled', amount_n)
    # 기존 Time, Amount 피처 삭제
    df_copy.drop(['Time','Amount'], axis=1, inplace=True)
    return df_copy
# Amount를 정규분포 형태로 변환 후 로지스틱 회귀 및 LightGBM 수행.
X_train, X_test, y_train, y_test = get_train_test_dataset(df)

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

print('### 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, ftr_test=X_test, tgt_train=y_train, tgt_test=y_test)

* 예측했던 대로 로지스틱 회귀의 분류 결과는 상승되었으나, Light GBM은 대부분 변화가 없고 AUC 스코어가 오히려 조금 감소한 것을 알수 있습니다.

 

- 이번에는 Amount를 정규화(Stadardizaion)이 아닌 Log로 변환 시켜보겠습니다. log 함수의 경우 입력값이 클수록 출력값이 이에 비례한 경우보다 더 작게나오는 모습을 보이는데요. 이러한 특성덕분에 데이터 분포도의 왜곡이 큰 경우 이를 해결하는데 자주 사용되는 함수입니다.

 

def get_preprocessed_df(df=None):
    df_copy = df.copy()
    # 넘파이의 log1p( )를 이용하여 Amount를 로그 변환
    amount_n = np.log1p(df_copy['Amount'])
    df_copy.insert(0, 'Amount_Scaled', amount_n)
    df_copy.drop(['Time','Amount'], axis=1, inplace=True)
    return df_copy
import numpy as np

X_train, X_test, y_train, y_test = get_train_test_dataset(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)

* 로지스틱 회귀의 경우 정밀도는 소폭 상승하였으나 재현율은 소폭 감소하였고, AUC 스코어는 상승했음을 알 수 있습니다.

* Light GBM의 경우 정밀도와 재현율이 상승하였고, AUC 스코어도 상승했음을 확인할 수 있습니다. 

* 분류 타겟이 되는 변수의 불균형이 심할수록, AUC 스코어를 통한 분류평가가 더 의미가 있는데요, 로그 변환을 했을 경우 두 모델 모두 AUC 스코어가 상승했습니다.

 

 

2. 이상치(Outlier) 제거 후 학습  +  SMOTE로 오버 샘플링 후 모델 학습/예측/평가

- 이번에는 이상치(Outlier)를 제거 후 학습해보겠습니다. 

- 모델 학습과 예측에 사용되는 피쳐들(Features)이 많기때문에 모든 경우에서 이상치를 제거하는 것은 불필요하고, 사기여부에 영향도가 큰 피쳐에 대해 이상치를 제거 후 학습시켜보겠습니다. 

- 이를 위해 전체 데이터의 상관관계 Heatmap을 만들어보겠습니다.

import seaborn as sns

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

- 히트맵의 색깔에 따라 Class(사기여부)에 대한 연관도를 알 수 있으며, 파랄수록 양의 상관관계가, 적색에 가까울수록 음의 상관관계가 높다는 것을 알 수있습니다.

- Class와 음의 상관관계가 가장 높은 피처는 V14와 V17입니다. 이 중 V14에 대해서만 처리를 해보겠습니다.

 

* 해당 데이터 분포에서 1/4지점과 3/4지점의 차이를 나타낸 것을 IQR(Inter Quantile Range)라 하며, 이 IQR을 활용해 이상치를 지정할 때는 IQR에 1.5를 곱한 값을 1/4 분위수에서는 빼주고, 3/4 분위수에서는 더해줘 구간을 만들고, 이 구간을 벗어나는 것을 이상치로 설정하였다.

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=df, column='V14', weight=1.5)
print('이상치 데이터 인덱스:', outlier_index)

* 이를 통해 도출된 이상치 인덱스는 위와같이 4개가 나왔다.

 

# 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(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)

* 이를 통해, 로지스틱 회귀와 Light GBM 모두 예측 성능이 크게 향상되었다.

* 로지스틱 회귀의 경우 재현율이 60.81%에서 67.12%로 / Light GBM의 경우 재현율이 76.35%에서 82.88%로 향상되었다. 사기가 발생하는 1건이라도 발생할 경우 문제가 커지는 위와 같은 이진분류에서는 재현율의 상승이 꽤나 중요하다는 점에서 이상치 제거는 매우 유용한 방법 중 하나로 평가해볼 수 있다.

 

 

- 다음은 SMOTE로 사기건수에 대한 데이터를 합성하여 오버샘플링 한 뒤 모델을 학습시켜보겠습니다.

[불균형데이터처리] 오버샘플링(Oversampling) / SMOTE

 

[불균형데이터처리] 오버샘플링(Oversampling) / SMOTE

이번에 알아볼 것은 불균형 데이터(Imbalanced Data) 처리에 대해서 알아보겠습니다. 불균형 데이터는 모델 학습에 좋지 않은 영향을 미치게되어 그 모델의 신뢰성을 떨어뜨리곤 하는데요. 그렇다면

jaylala.tistory.com

 

- SMOTE에 대한 설명은 위 포스팅을 참조해주시면 되겠습니다.

 

 

* SMOTE를 적용하고 생성된 데이터의 양을 확인해보았습니다. test 데이터는 실제 비교를 해야하기에 제외하고, 학습 데이터로 분류된 것들에만 Class의 소수인 사기 건수를 중심으로 SMOTE를 해보았습니다.

from imblearn.over_sampling import SMOTE

# Create an instance of the SMOTE class
smote = SMOTE(random_state=0)

# Use the fit_resample method to apply SMOTE
X_train_over, y_train_over = smote.fit_resample(X_train, y_train)

print('SMOTE 적용 전 학습용 피처/레이블 데이터 세트: ', X_train.shape, y_train.shape)
print('SMOTE 적용 후 학습용 피처/레이블 데이터 세트: ', X_train_over.shape, y_train_over.shape)

 

* SMOTE를 적용 전 학습 데이터 세트가 199,362건에서, 그 두 배인 398,040으로 증가하였습니다.

 

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)

* 이를 바탕으로 로지스틱 회귀를 진행하였고, 정확도와 재현율이 급격하게 증가하였지만, 정밀도가 급격히 떨어졌습니다. 이는 로지스틱 회귀 모델이 오버 샘플링으로 인해 실제 원본 데이터의 유형보다 너무나 많은 사기 발생 데이터를 학습하면서 실제 테스트 데이터에서 예측을 지나치게 사기가 발생했다는 것으로 적용하면서 발생한 현상입니다. 

 

- 분류 결정 임곗값에 따른 정밀도와 재현율 곡선을 통해 SMOTE로 학습된 로지스틱 회귀 모델에 어떤 문제가 발생하고 있는지 시각화 해보겠습니다. 

 

 

 

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from sklearn.metrics import precision_recall_curve
%matplotlib inline

def precision_recall_curve_plot(y_test , pred_proba_c1):
    # threshold ndarray와 이 threshold에 따른 정밀도, 재현율 ndarray 추출.
    precisions, recalls, thresholds = precision_recall_curve( y_test, pred_proba_c1)

    # X축을 threshold값으로, Y축은 정밀도, 재현율 값으로 각각 Plot 수행. 정밀도는 점선으로 표시
    plt.figure(figsize=(8,6))
    threshold_boundary = thresholds.shape[0]
    plt.plot(thresholds, precisions[0:threshold_boundary], linestyle='--', label='precision')
    plt.plot(thresholds, recalls[0:threshold_boundary],label='recall')

    # threshold 값 X 축의 Scale을 0.1 단위로 변경
    start, end = plt.xlim()
    plt.xticks(np.round(np.arange(start, end, 0.1),2))

    # x축, y축 label과 legend, 그리고 grid 설정
    plt.xlabel('Threshold value'); plt.ylabel('Precision and Recall value')
    plt.legend(); plt.grid()
    plt.show()
precision_recall_curve_plot( y_test, lr_clf.predict_proba(X_test)[:, 1] )

* 임계값이 0.99이하에서는 재현율이 좋고 정밀도가 극단적으로 낮다가 0.99 이사에서는 반대로 재현율이 대폭 떨어지고 정밀도가 높아집니다. 분류 결정 임계값을 조정하더라도 임계값의 민감도가 너무 심해 올바른 재현율/정밀도 성능을 얻을 수 없으므로 로지스틱 회귀 모델의 경우 SMOTE 적용 후 올바른 예측 모델이 생성되지 못했음을 알 수 있습니다.

 

 

- 이번에는 Light GBM을 활용하여 알아보겠습니다.

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)

* Light GBM의 경우 이상치만 제거한 경우보다 재현율과 정확도 AUC 스코어에서 높은 모습을 보이지만, 정밀도는 낮아지는 모습을 보입니다.

* SMOTE를 적용하면 재현율이 높아지고 정밀도가 낮아지는 것은 일반적인 현상입니다. 

* 정밀도와 재현율의 Trade off를 잘 고려하여 위에서 설명드린 방법들을 적용해보는 것이 좋겠습니다.

반응형

댓글