본문 바로가기

Machine Learning/머신러닝 완벽가이드 for Python

ch4.09 분류 실습-신용카드_사기검출

목표 : 우선 기본적인 모델 학습을 해보고
데이터 전처리(피처 엔지니어링)를 통해서 모델 성능이 향상되는지 확인해보자.

 

1  1. 데이터 로드 및 확인

import pandas as pd
import numpy as np 
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings("ignore")
%matplotlib inline
card_df = pd.read_csv('./creditcard.csv')

print(card_df.shape)
card_df.head(3)

>>> (284807, 31)

 

원본 DataFrame은 유지하고 데이터 가공을 위한 DataFrame을 복사하여 반환

from sklearn.model_selection import train_test_split

# 전처리 함수 : df의 Time 컬럼 삭제 
def get_preprocessed_df(df=None):
    df_copy = df.copy()
    df_copy.drop('Time', axis=1, inplace=True)
    return df_copy

 

 

 

3  3. Amount 피처 변환 후 다시 학습

 

# Amount feature의 분포도 확인
import seaborn as sns

plt.figure(figsize=(8, 4))
plt.xticks(range(0, 30000, 1000), rotation=60)

sns.distplot(card_df['Amount'])

-> Amount 피처는 롱테일 구조임을 알 수 있다.

 

 

3.1  (1) Amount 피처에 StandardScaler 적용

 

from sklearn.preprocessing import StandardScaler

# Amount 피처값을 StandardScaler 적용하는 함수
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

 

 

StandardScaler 변환 후 로지스틱 회귀 및 LightGBM 학습/예측/평가

 

# Amount를 정규분포 형태로 변환 후 로지스틱 회귀 및 LightGBM 수행. 
X_train, X_test, y_train, y_test = get_train_test_dataset(card_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)
### 로지스틱 회귀 예측 성능 ###
오차 행렬
[[85281    14]
 [   58    90]]
정확도: 0.9992, 정밀도: 0.8654, 재현율: 0.6081,    F1: 0.7143, AUC:0.9702 

### LightGBM 예측 성능 ###
오차 행렬
[[85290     5]
 [   37   111]]
정확도: 0.9995, 정밀도: 0.9569, 재현율: 0.7500,    F1: 0.8409, AUC:0.9779

-> StandardScaler를 적용해도 평가지표는 변화 없는 것을 알 수 있다.
정확도: 0.9992, 정밀도: 0.8762, 재현율: 0.6216,    F1: 0.7273, AUC:0.9592 

.

 

 

3.2  (2) Amount 피처를 로그 변환

 

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

 

# log1p 와 expm1 설명 
import numpy as np

# 0.0001 == 0.0 으로 인식하게 된다.
print(1e-1000 == 0.0)

 

print(np.log(1e-1000)) # 10의 -1000승 
-> 컴퓨터가 0으로 인식함 이런 상황이 발생하지 않도록 1을 더해준다.
print(np.log(1e-1000 + 1))

>>>
-inf
0.0


print(np.log1p(1e-1000))
>>> 0.0

# log1p를 통하여 이를 해결할 수 도 있다.

 

var_1 = np.log1p(100)
var_2 = np.expm1(var_1)
print(var_1, var_2)
# 로그와 지수는 서로 왔다갔다 하는 관계이다

>>> 4.61512051684126 100.00000000000003

 

# train, test 데이터 분리
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
### 로지스틱 회귀 예측 성능 ###
오차 행렬
[[85283    12]
 [   59    89]]
정확도: 0.9992, 정밀도: 0.8812, 재현율: 0.6014,    F1: 0.7149, AUC:0.9727 

### LightGBM 예측 성능 ###
오차 행렬
[[85290     5]
 [   35   113]]
정확도: 0.9995, 정밀도: 0.9576, 재현율: 0.7635,    F1: 0.8496, AUC:0.9796

-> Amount 피처를 log변환 해주었더니 전반적으로 모델 성능이 향상되었다.

LightGBM 정확도: 0.9995, 정밀도: 0.9573, 재현율: 0.7568,    F1: 0.8453, AUC:0.9790 

 

 

4  4. 이상치 데이터 제거 후 모델 학습/예측/평가

 

4.0.1  corr() : 각 피처들의 상관 관계. 결정 레이블인 class 값과 가장 상관도가 높은 피처 추출

import seaborn as sns

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

-> class와 상관관계가 높은 피처는 V12, V14, V17이 있다.
이 중 V14의 이상치를 제거해보자

 

 

 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)  # 1/4 분위
    quantile_75 = np.percentile(fraud.values, 75)  # 3/4 분위
    
    # 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

 

print(np.percentile(card_df['V14'].values, 100))
print(np.max(card_df['V14']))

>>>
10.5267660517847
10.5267660517847

outlier_index = get_outlier(df=card_df, column='V14', weight=1.5)
print('이상치 데이터 인덱스:', outlier_index)

>>>
이상치 데이터 인덱스: Int64Index([8296, 8615, 9035, 9252], dtype='int64')

-> 이상치가 4개가 나왔다. 추후 삭제 예정

 

4.0.2  로그 변환 후 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

 

-> 로지스틱 회귀의 재현율이 많이 높아졌다.
LightGBM의 재현율도 향상되었다.

 

 

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

# imbalanced-learn 패키지 설치하기
conda install -c conda-forge imbalanced-learn
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("오버 샘플링 적용 시 학습 데이터의 피처/레이블 차이")
print('오버 샘플링 적용 전 학습용 피처/레이블 데이터 세트: ', X_train.shape, y_train.shape)
print('오버 샘플링 적용 후 학습용 피처/레이블 데이터 세트: ', X_train_over.shape, y_train_over.shape, '\n')

print('오버 샘플링 적용 후 레이블 값 분포: \n', pd.Series(y_train_over).value_counts())


>>>
오버 샘플링 적용 시 학습 데이터의 피처/레이블 차이
오버 샘플링 적용 전 학습용 피처/레이블 데이터 세트:  (199362, 29) (199362,)
오버 샘플링 적용 후 학습용 피처/레이블 데이터 세트:  (398040, 29) (398040,) 

오버 샘플링 적용 후 레이블 값 분포: 
 1    199020
0    199020
Name: Class, dtype: int64

-> 오버 샘플링 했더니 레이블 분포가 균형이 맞춰졌다.

 

# 오버 샘플링 적용 전 레이블 값 분포 - 극심한 불균형 상태
y_train.value_counts()

>>>
0    199020
1       342
Name: Class, dtype: int64

 

5.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

-> 재현율은 높아졌으나, 정밀도는 낮아졌다.

 

# Precision-Recall 곡선 시각화
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] )

 

5.1.1  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

 

-> LightGBM 모델은 정밀도도 괜찮게 나왔다.