728x90

참고서적-파이썬 머신러닝 가이드

저번 블로그의 XGBoost, LightGBM 실습에 이어서 신용카드 사기 검출 실습을 하면서 공부를 해봤습니다~!

 

1. Import

import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
os.chdir('./Data/creditcard')
credit_df=pd.read_csv('creditcard.csv')

#의미없는 Time 변수 제거
del credit_df['Time']

 

2. 데이터 분리

#데이터프레임 복사후 데이터 가공처리
def get_train_test_split(df=None):
    #인자로 입력된 DataFrame의 사전 데이터 가공(Time 변수 삭제)이 완료된 복사 DataFrame 반환
    df_copy=credit_df
    
    #DataFrame의 맨 마지막 칼럼이 레이블, 나머지는 피처들
    x_features=df_copy.iloc[:,:-1]
    y_target=df_copy.iloc[:,-1]
    
    #데이터 분리, stratify 기반으로 분리-train,test 비율 동일하게
    from sklearn.model_selection import train_test_split
    x_train,x_test,y_train,y_test= train_test_split(x_features,y_target, test_size=0.3, random_state=0,stratify=y_target)
    
    #데이터 반환 
    return x_train,x_test,y_train,y_test
    
x_train,x_test,y_train,y_test=get_train_test_split(credit_df)

Time 변수를 제거 후에 feature와 label을 분리하는데 분리 시 샘플링 방식은 나뉟stratify로 해줍니다.

stratify 값을 y_target으로 지정해주면 각각의 class 비율(ratio)을 train / validation에 유지해 줍니다. (한 쪽에 쏠려서 분배되는 것을 방지합니다) 

 

# 레이블 값이 비율을 백분율로 환산하여 서로 비슷하게 분활됐는지 확인
print('학습 데이터 레이블 값 비율')
print(y_train.value_counts()/y_train.shape[0]*100)
print('테스트 데이터 레이블 값 비율')
print(y_test.value_counts()/y_test.shape[0]*100)

학습 데이터 레이블 값 비율
0    99.827451
1     0.172549
Name: Class, dtype: float64
테스트 데이터 레이블 값 비율
0    99.826785
1     0.173215
Name: Class, dtype: float64

 

3. 로지스틱 회귀 VS LightGBM 성능 비교

def get_clf_eval(y_test,pred,pred_proba):
    from sklearn.metrics import f1_score, accuracy_score,precision_score,recall_score,confusion_matrix, roc_auc_score
    confusion = confusion_matrix(y_test, pred)
    accuracy= accuracy_score(y_test, pred)
    precision=precision_score(y_test, pred)
    recall= recall_score(y_test, pred)
    #F1스코어 추가
    f1 =f1_score(y_test,pred)
    print('오차 행렬')
    print(confusion)
    #ROC-AUC 
    roc_auc=roc_auc_score(y_test,pred_proba)
    print('정확도 {0:.4f}, 정밀도 {1:.4f}, 재현율 {2:.4f}, F1:{3:.4f}, AUC:{4:.4f}'.format(accuracy,precision,recall,f1,roc_auc))

성능 평가에 사용되는 함수를 먼저 만들어줍니다.

 

1. 정밀도 = TP/(FP+TP) - 예측을 기준으로 

FP+TP : 예측을 positive로 한 전체 데이터의 수 

TP: 예측과 실제 값이 positive로 맞춘 데이터의 수

-예측을 positive로 한 것들 중에 예측과 실제의 값이 positive로 일치한 데이터의 비율을 말합니다

-일반 메일을 스팸메일이라고 잘못 분류하는 경우에는 재현율 보다 정밀도를 우선 시 합니다.

2. 재현율= TP/(FN+TP) - 실제 값을 기준으로 

FN+TP : 실제 값이 positive인 데이터의 수

- 실제 값이 positive인 것들 중에  예측과 실제의 값이 positive로 일치한 데이터의 비율을 말합니다

  민감도 또는 TPR(true positive Rate) 이라고도 불립니다

- 특히 재현율은 암 판단 모델을 평가할 때입니다. 실제로 양성인 환자를 음성으로 잘못 판단 내리면

  치명적이기 때문에 재현율 평가가 중요합니다

 

위 내용을 정리를 하다 보니까 추가적인 설명이 필요할 것 같아서 제 생각을 적어봤습니다

-정밀도와 재현율의 차이는  분모가 FT 냐 FN 이냐의 차이입니다. 

정밀도의 FT은 False인 값을 True로 예측을 한 것이고

재현율 FN은 False을 올바르게 Negative로 예측했느냐 차이인 것 같습니다

 

위 예시처럼 재현율의 암 판단은 양성을 양성이라고 제대로 판단해야 하기 때문에 FN이 중요하고

정밀도는 실제로 양성이 아닌, 메일로 따지면 스팸메일이 아닌 정상메일의 데이터 예측을 positive 양성으로

예측해서 스팸메일로 분류하는 경우입니다. 

 

3. AUC= ROC 곡선 면적 밑의 면적

AUC 스코어는 이진 분류의 예측 성능 지표입니다.  

간단히 적어보면, ROC 곡선은 FPR (x축)의 변함에 따라 TRP(y축)의 곡선 형태를 보여줍니다.

FPR(False positive Rate) = FP / (FP+TN) = 1-TNR = 1- 특이성

TRP(True positive Rate) = TP / (FN+TP) = 재현율이며 민감 도라고 불리기도 합니다.

TNR(True Negative Rate) = TRP에 대응하는 지표로 특이성이라 실제 값이 음성(Negative)을 정확히 예측해야

수준을 나타냅니다 (코로나 음성인 사람은 음성으로 정확히 예측해야 한다)

반대로, 코로나 양성인 사람을 양성으로 정확히 예측해야 하는 건 당연히 TRP, 재현율 일 겁니다 

 

그러면 FPR은 1-특이성이므로 음성인 사람을 양성이라고 예측한 값일 겁니다

ROC는 이 FRP을 0~1까지 변경하면서 TRP의 변화 값을 구하여 곡선으로 나타냅니다.

 

 

로지스틱 회귀

from sklearn.linear_model import LogisticRegression

lr_clf= LogisticRegression()
lr_clf.fit(x_train,y_train)
lr_pred=lr_clf.predict(x_test)
lr_pred_proba=lr_clf.predict_proba(x_test)[:,1]

get_clf_eval(y_test,pred,lr_pred_proba)

오차 행렬
[[85281    14]
 [   57    91]]
정확도 0.9992, 정밀도 0.8667, 재현율 0.6149, F1:0.7194, AUC:0.9564

 

로지스틱 실행결과 재현율이 0.61로 다른 평가지표에 비해 낮아 보입니다.  이번엔 LightGBM 모델 결과를 보기 앞서서

get_model_train_eval 함수를 만들어서 학습 및 평가를 간단히 하겠습니다

 

# 함수로 만들기
def get_model_train_eval(model, ftr_train=None,ftr_test=None, tar_train=None,tar_test=None):
    model.fit(x_train,y_train)
    pred=model.predict(x_test)
    pred_proba=model.predict_proba(x_test)[:,1]
    get_clf_eval(tar_test,pred,pred_proba)

LightGBM

 

from lightgbm import LGBMClassifier

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=y_train, tar_train=x_test,tar_test=y_test)

오차 행렬
[[85290     5]
 [   36   112]]
정확도 0.9995, 정밀도 0.9573, 재현율 0.7568, F1:0.8453, AUC:0.9790

로지스틱 모델보다 전반적인 성능이 개선되었습니다.

amount 변수의 분포를 확인 후, 값이 한쪽으로 치우쳐 있다면 scaling을 진행하여 다시 예측해보겠습니다

 

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

import seaborn as sns
plt.figure(figsize=(8,4))
plt.xticks(range(0,30000,1000), rotation=60)
#히시토그램 시각화
sns.distplot(credit_df['Amount'])

# 1000불 이하가 대부분이고, 2700불은 드물지만 존재함, 이를 stardardScaler로 표준 정규 분포 형태로 변환 후 예측성능 비교실시

전처리 작업으로 scaling 하는 함수를 만듭니다

def get_preprocessed_df(df=None):
    from sklearn.preprocessing import StandardScaler
    df_copy=credit_df
    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(["Amount"], axis=1, inplace=True)
    return df_copy

scaling은 평균 0, 분산 1을 만드는 StandardScaler를 사용합니다.

스케일링 후 로지스틱, LightGBM을 다시 실행해 봅니다

 

#amount를 정규 분포 형태로 변환 후 로지스틱 회귀 및 LightGBM 수행
x_train,x_test,y_train,y_test = get_train_test_split(df_copy)

print("### 로지스틱 회귀 예측 성능 ###")
lr_clf= LogisticRegression()
get_model_train_eval(lr_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)

print("### lightGBM 예측 성능 ###")
lightgbm_clf=LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1,boost_from_average=False)
get_model_train_eval(lightgbm_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)

### 로지스틱 회귀 예측 성능 ###
오차 행렬
[[85282    13]
 [   56    92]]
정확도 0.9992, 정밀도 0.8762, 재현율 0.6216, F1:0.7273, AUC:0.9647

### lightGBM 예측 성능 ###
오차 행렬
[[85290     5]
 [   36   112]]
정확도 0.9995, 정밀도 0.9573, 재현율 0.7568, F1:0.8453, AUC:0.981

 

스케일링 전후 차이가 미비했습니다(교재와는 다른 결과를 보였습니다.

amount를 log을 취해서 정규분포와 근사하게 만든 후 다시 모델을 실행해 보겠습니다.

 

로지스틱 & LightGBM(amount 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(["Amount"], axis=1, inplace=True)
    return df_copy

df_copy=get_preprocessed_df(credit_df)

#amount를 정규 분포 형태로 변환 후 로지스틱 회귀 및 LightGBM 수행
x_train,x_test,y_train,y_test = get_train_test_split(df_copy)

print("### 로지스틱 회귀 예측 성능 ###")
lr_clf= LogisticRegression()
get_model_train_eval(lr_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)

print("### lightGBM 예측 성능 ###")
lightgbm_clf=LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1,boost_from_average=False)
get_model_train_eval(lightgbm_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)

### 로지스틱 회귀 예측 성능 ###
오차 행렬
[[85282    13]
 [   56    92]]
정확도 0.9992, 정밀도 0.8762, 재현율 0.6216, F1:0.7273, AUC:0.9647

### lightGBM 예측 성능 ###
오차 행렬
[[85290     5]
 [   36   112]]
정확도 0.9995, 정밀도 0.9573, 재현율 0.7568, F1:0.8453, AUC:0.9812

amount의 log변환도 같은 위 결과와 같은 성능을 보였습니다.

개선이 미비한 원인 중 이상치의 존재일 가능성이 있으므로 이를 제거 후 성능 비교를 다시 해봅니다.

 

이상치 제거 후 성능 비교

-IQR로 이상치를 파악하기 전에 먼저 corr()를 이용해서 레이블의 중요 피처를 구별하여 해당되는 것들만 제거합니다.

# 상관도
import seaborn as sns

try:
    del credit_df['Amount_scaled']
except:
    pass
plt.figure(figsize=(16,16))
corr=credit_df.corr()
sns.heatmap(corr, cmap="RdBu")

레이블인 class와 상관관계가 높은 것은 v14,17인데, 이 중에서 v14 만 이상치 제거 실시합니다

 

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
    
    #최대값보다 크거나, 최솟값보다 작은 값을 이상치 데이터로 설정하고 DataFram3 index 반환
    outlier_index = fraud[(fraud< lowest_val) | (fraud> highest_val)].index
    return outlier_index

 get_outlier 함수를 통해 V14칼럼의 이상치를 제거해줍니다

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

이상치 데이터 인덱스: Int64Index([8296, 8615, 9035, 9252], dtype='int64')
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)
    try:
        df_copy.drop(["Time","Amount"], axis=1, inplace=True)
    except:
        df_copy.drop(["Amount"], axis=1, inplace=True)  
    
    #이상치 제거하는 로직 추가
    outlier_index=get_outlier(df=df, column='V14', weight=1.5)
    df_copy.drop(outlier_index, axis=0, inplace=True)
    return df_copy

 

이상치를 제거한 로지스틱 VS LightGBM 성능 비교 

#amount를 정규 분포 형태로 변환 후 로지스틱 회귀 및 LightGBM 수행
df_copy=get_preprocessed_df(credit_df)
x_train,x_test,y_train,y_test = get_train_test_split(df_copy)

print("### 로지스틱 회귀 예측 성능 ###")
lr_clf= LogisticRegression()
get_model_train_eval(lr_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)
print('\n')

print("### lightGBM 예측 성능 ###")
lightgbm_clf=LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1,boost_from_average=False)
get_model_train_eval(lightgbm_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)

### 로지스틱 회귀 예측 성능 ###
오차 행렬
[[85281    14]
 [   57    91]]
정확도 0.9992, 정밀도 0.8667, 재현율 0.6149, F1:0.7194, AUC:0.9564

### lightGBM 예측 성능 ###
오차 행렬
[[85290     5]
 [   36   112]]
정확도 0.9995, 정밀도 0.9573, 재현율 0.7568, F1:0.8453, AUC:0.9790

이상치 제거 후 성능 비교를 해본 결과 눈에 띄는 개선 점은 없었습니다.

이번엔 smote(오버 샘플링)을 적용하여 모델 성능 비교를 해보겠습니다.

 

from imblearn.over_sampling import SMOTE

#smote는 train 데이터에만 적용한다
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 적용 전 학습용 피처/레이블 데이터 세트: (199364, 29) (199364,)
SMOTE 적용 후 학습용 피처/레이블 데이터 세트: (398040, 29) (398040,)
SMOTE 적용 후 레이블 값 분포: 
 1    199020
0    199020
Name: Class, dtype: int64

smote는 학습 데이터 세트에만 적용시켜야 합니다. smote을 적용하여 199364건->398040건으로 2배 가까이 증가했습니다. 이 데이터를 기반으로 모델을 실행하겠습니다.

 

#로지스틱
lr_clf=LogisticRegression()
get_model_train_eval(lr_clf, ftr_train=x_train_over,ftr_test=x_test, tar_train=y_train_over,tar_test=y_test)

오차 행렬
[[85281    14]
 [   57    91]]
정확도 0.9992, 정밀도 0.8667, 재현율 0.6149, F1:0.7194, AUC:0.9564
#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, tar_train=y_train_over,tar_test=y_test)

오차 행렬
[[85290     5]
 [   36   112]]
정확도 0.9995, 정밀도 0.9573, 재현율 0.7568, F1:0.8453, AUC:0.9790

smote 적용 후, 성능 결과를 보면 이전과 동일하거나 미비한 변화를 보입니다

서적에는 모델마다 변화량이 조금씩 달랐는데 제가 실습하는 동안의 성능 비교는 비슷하거나 변화가 없었습니다.

728x90

+ Recent posts