0. 대회 소개

  • 캐글의 신용카드 데이터 세트를 이용해 신용카드 사기를 검출해내는 문제

  • 데이터 출처: https://www.kaggle.com/datasets/mlg-ulb/creditcardfraud


  • 해당 데이터 세트의 레이블인 Class 속성은 매우 불균형한 분포를 가지고 있음

    • Class: 0 vs 1

    • 0: 사기가 아닌 정상적인 신용카드 트랜잭션 데이터

    • 1: 신용카드 사기 트랜잭션

    • 전체 데이터의 약 0.172%만이 레이블 값이 1, 즉 사기 트랜잭션임

📌 언더 샘플링 & 오버 샘플링

  • 이상 레이블을 가지는 데이터 건수가 정상 레이블을 가진 데이터 건수에 비해 너무 적은 경우(레이블이 불균형한 분포를 가진 데이터 세트를 학습시키는 경우) 예측 성능의 문제가 발생할 수 있음

    • 이상 레이블을 가지는 데이터 건수는 매우 적기 때문에 제대로 다양한 유형을 학습하지 못함

    • 정상 레이블을 가지는 데이터 건수는 매우 많음 -> 일방적으로 정상 레이블로 치우친 학습을 수행

    제대로 된 이상 데이터 검출이 어려워지기 쉬움

  • 지도학습에서 극도로 불균형한 레이블 값 분포로 인한 문제점을 해결하기 위해서는 적절한 학습 데이터를 확보하는 방안이 요구됨

    • 오버 샘플링(Over Sampling)

    • 언더 샘플링(Under Sampling)

    • 오버 샘플링 방식이 예측 성능상 더 유리한 경우가 많아 주로 사용됨

a) 오버 샘플링(Over Sampling)

  • 이상 데이터와 같이 적은 데이터 세트를 증식하여 학습을 위한 충분한 데이터를 확보하는 방법

  • 동일한 데이터를 단순히 증식하면 과적합이 되기 쉬움

    • 원본 데이터의 피처 값들을 아주 약간만 변경하여 증식
  • 대표적으로 SMOTE(Synthetic Minority Over-sampling Technique) 방법이 O

    • 적은 데이터 세트에 있는 개별 데이터들의 K 최근접 이웃(K Nearest Neighbor)을 찾아서 이 데이터와 K개 이웃들의 차이를 일정 값으로 만들어서 기존 데이터와 약간 차이가 나는 새로운 데이터들을 생성하는 방식

b) 언더 샘플링(Under Sampling)

  • 많은 데이터 세트를 적은 데이터 세트 수준으로 감소시키는 방식

  • 정상 레이블 데이터를 이상 레이블 데이터 수준으로 줄여 버린 상태에서 학습을 수행하면 과도하게 정상 레이블로 학습/예측하는 부작용은 개선할 수 있지만, 너무 많은 정상 레이블 데이터를 감소시키기 때문에 정상 레이블의 경우 오히랴 제대로 된 학습을 수행할 수 없다는 단점이 있어 잘 적용하지 x

1. 데이터 일차 가공 및 모델 학습/예측/평가

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('/content/drive/MyDrive/Colab Notebooks/ESAA 8기/YB/10주차/data/creditcard.csv')
card_df.head(3)
Time V1 V2 V3 V4 V5 V6 V7 V8 V9 ... V21 V22 V23 V24 V25 V26 V27 V28 Amount Class
0 0.0 -1.359807 -0.072781 2.536347 1.378155 -0.338321 0.462388 0.239599 0.098698 0.363787 ... -0.018307 0.277838 -0.110474 0.066928 0.128539 -0.189115 0.133558 -0.021053 149.62 0
1 0.0 1.191857 0.266151 0.166480 0.448154 0.060018 -0.082361 -0.078803 0.085102 -0.255425 ... -0.225775 -0.638672 0.101288 -0.339846 0.167170 0.125895 -0.008983 0.014724 2.69 0
2 1.0 -1.358354 -1.340163 1.773209 0.379780 -0.503198 1.800499 0.791461 0.247676 -1.514654 ... 0.247998 0.771679 0.909412 -0.689281 -0.327642 -0.139097 -0.055353 -0.059752 378.66 0

3 rows × 31 columns

  • 보안상의 이유로 인헤 V로 시작하는 피처들의 의미는 알 수 없음

  • Time 피처의 경우 데이터 생성과 관련된 작업용 속성 -> 의미가 없기에 제거

  • Amount 피처: 신용카드 트랜잭션 금액

  • Class: 레이블(target), 0의 경우 정상, 1의 경우 사기 트랜잭션임

card_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 284807 entries, 0 to 284806
Data columns (total 31 columns):
 #   Column  Non-Null Count   Dtype  
---  ------  --------------   -----  
 0   Time    284807 non-null  float64
 1   V1      284807 non-null  float64
 2   V2      284807 non-null  float64
 3   V3      284807 non-null  float64
 4   V4      284807 non-null  float64
 5   V5      284807 non-null  float64
 6   V6      284807 non-null  float64
 7   V7      284807 non-null  float64
 8   V8      284807 non-null  float64
 9   V9      284807 non-null  float64
 10  V10     284807 non-null  float64
 11  V11     284807 non-null  float64
 12  V12     284807 non-null  float64
 13  V13     284807 non-null  float64
 14  V14     284807 non-null  float64
 15  V15     284807 non-null  float64
 16  V16     284807 non-null  float64
 17  V17     284807 non-null  float64
 18  V18     284807 non-null  float64
 19  V19     284807 non-null  float64
 20  V20     284807 non-null  float64
 21  V21     284807 non-null  float64
 22  V22     284807 non-null  float64
 23  V23     284807 non-null  float64
 24  V24     284807 non-null  float64
 25  V25     284807 non-null  float64
 26  V26     284807 non-null  float64
 27  V27     284807 non-null  float64
 28  V28     284807 non-null  float64
 29  Amount  284807 non-null  float64
 30  Class   284807 non-null  int64  
dtypes: float64(30), int64(1)
memory usage: 67.4 MB
  • 결측치는 존재하지 x

  • Class 레이블만 int형이고 나머지 피처들은 모두 float 형임

from sklearn.model_selection import train_test_split

### 인자로 입력받은 DataFrame을 복사 한 뒤 Time 컬럼만 삭제하고 복사된 DataFrame을 반환
def get_preprocessed_df(d = None):
    df_copy = df.copy()
    df_copy.drop('Time', axis = 1, inplace = True)

    return df_copy
### 사전 데이터 가공 후 학습과 테스트 데이터 세트를 반환하는 함수

def get_train_test_dataset(df = None):
    # 인자로 입력된 DataFrame의 사전 데이터 가공이 완료된 복사 DataFrame 반환
    df_copy = get_preprocessed_df(df)

    # DataFrame의 맨 마지막 컬럼이 label, 나머지는 feature들
    X_features = df_copy.iloc[:, :-1]
    y_target = df_copy.iloc[:, -1]

    # train_test_split( )으로 학습과 테스트 데이터 분할
    # stratify = y_target으로 Stratified 기반 분할 
    # -> 학습/테스트 데이터 세트의 레이블 값 분포를 동일하게
    
    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_dataset(card_df)
print('학습 데이터 레이블 값 비율')
print(y_train.value_counts()/y_train.shape[0] * 100)
print()
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
  • 학습 데이터 레이블 분포와 테스트 데이터 레이블의 분포가 비슷하게 분할되었다.

1-2. 학습/예측/평가

from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score, f1_score
from sklearn.metrics import roc_auc_score

### 예측 성능 평가를 위한 함수
def get_clf_eval(y_test, pred = None, pred_proba = None):
    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_score(y_test,pred)
    
    # ROC-AUC 추가 
    roc_auc = roc_auc_score(y_test, pred_proba)
    print('오차 행렬')
    print(confusion)
    
    # ROC-AUC print 추가
    print('정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f},\
    F1: {3:.4f}, AUC:{4:.4f}'.format(accuracy, precision, recall, f1, roc_auc))
### 1) 로지스틱 회귀


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, lr_pred, lr_pred_proba) # 평가
오차 행렬
[[85283    12]
 [   60    88]]
정확도: 0.9992, 정밀도: 0.8800, 재현율: 0.5946,    F1: 0.7097, AUC:0.9593
### 인자로 사이킷런의 Estimator 객체와 학습/테스트 데이터 세트를 입력 받아서 학습/예측/평가 수행

def get_model_train_eval(model, ftr_train = None, ftr_test = None, tgt_train = None, tgt_test = None):
    model.fit(ftr_train, tgt_train) # 학습
    pred = model.predict(ftr_test) # 예측
    pred_proba = model.predict_proba(ftr_test)[:, 1]
    get_clf_eval(tgt_test, pred, pred_proba) # 평가
### 2) 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 = X_test, 
                     tgt_train = y_train, tgt_test = y_test)
오차 행렬
[[85290     5]
 [   36   112]]
정확도: 0.9995, 정밀도: 0.9573, 재현율: 0.7568,    F1: 0.8453, AUC:0.9790
  • LightGBM에서 boost_from_average 파라미터의 디폴트 값은 True

  • 현재 예제와 같이 레이블 값이 극도로 불균형한 분포를 이루는 경우 해당 파라미터를 True로 설정하면 재현율 및 ROC-AUC 성능이 매우 크게 저하됨

    • False로 설정해야 함
  • 앞의 로지스틱 회귀에 비해서는 성능이 향상됨

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

  • 대부분의 선형 모델은 중요 피처들의 값이 정규 분포 형태를 유지하는 것을 선호함

  • 여러 피처들의 분포를 살펴보자.

2-1. 표준화(StandardScaler)

✔ Amount

import seaborn as sns

plt.figure(figsize = (8, 4))
plt.xticks(range(0, 30000, 1000), rotation = 60)
sns.distplot(card_df['Amount'])
<Axes: xlabel='Amount', ylabel='Density'>

  • 사용금액이 1000불 이하인 데이터가 대부분이며, 27000불까지 드물지만 많은 금액을 사용한 경우가 발생하여 꼬리가 긴 형태의 분포 곡선

  • Amount를 표준 정규 분포 형태로 변환

### Amount 피처를 정규 분포 형태로 변환

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(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
  • 변환 이전과 비교하여 성능이 크게 개선되지는 않았음

2-2. 로그 변환(Log Transformation)

  • 데이터 분포도가 심하게 왜곡되어 있을 경우 적용하는 중요 기법 중 하나

  • 원래 값을 log 값으로 변환해 원래 큰 값을 상대적으로 작은 값으로 변환하기 때문에 데이터 분포도의 왜곡을 상당 수준 개선해 줌

  • 넘파이의 np.log1p() 함수를 이용해 간단히 변환 가능

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
### 로그 변환 후 모델의 예측 성능 확인

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
  • 두 모델 모두 정밀도, 재현율, ROC-AUC에서 약간의 성능 개선이 있음을 확인할 수 있다.

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

3-1. 이상치 데이터(Outlier)

  • 전체 데이터의 패턴에서 벗어난 이상 값을 가진 데이터

  • 이상치로 인해 머신러닝 모델의 성능에 영향을 받는 경우가 발생하기 쉬움

  • IQR(Inter Quantile Range) 방식을 적용하여 이상치 탐색

    • 사분위값(Quantile) 값의 편차를 이용하는 기법

    • IQR = Q3 - Q1

    • 보통 IQR에 1.5를 곱해서 생성된 범위를 이용해 최댓값과 최솟값을 결정한 뒤 이를 벗어나는 데이터를 이상치로 간주하는 방식

    • 박스 플롯(Box plot) 방식으로 시각화 가능

  • 매우 많은 피처가 있을 경우 이들 중 결정값(= label)과 가장 상관성이 높은 피처들을 위주로 이상치를 검출하는 것이 좋음

    • 모든 피처들의 이상치를 검출하는 것은 시간이 많이 소모되며, 결정값과 상관성이 높지 않은 피처들의 경우는 이상치를 제거하더라도 크게 성능 향상에 기여햐지 않기 때문
### 각 feature 별 상관도 시각화

import seaborn as sns

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

  • 양의 상관관계가 높을수록 진한 파란색

  • 음의 상관관계가 높을수록 진한 빨간색

  • 상관관계 히트맵에서 맨 아래에 위치한 결정 레이블인 Class 피처와 음의 상관관계가 가장 높은 피처는 V14V17

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
### V14에서 이상치 찾기


outlier_index = get_outlier(df = card_df, column = 'V14', weight = 1.5)
print('이상치 데이터 인덱스:', outlier_index)
이상치 데이터 인덱스: Int64Index([8296, 8615, 9035, 9252], dtype='int64')
# 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
  • 모두 예측 성능이 크게 향상됨

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

  • SMOTE는 imbalanced-learn 패키지의 SMOTE 클래스를 이용해 간단하게 구현 가능

  • SMOTE 적용 시 반드시 학습 데이터 세트만 오버 샘플링 해야 함

    • 검증 데이터 세트나 테스트 데이터 세트를 오버 샘플링 할 경우 결국은 원본 데이터 세트가 아닌 데이터 세트에서 검증 또는 테스트를 수행하게 됨

    => 올바른 검증/테스트가 될 수 x

from imblearn.over_sampling import SMOTE

smote = SMOTE(random_state = 0)
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)
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
  • SMOTE 적용 후 데이터가 2배 가까이 증식

  • SMOTE 적용 후 레이블 값이 0과 1의 분포가 동일하게 생성됨

### 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
  • 재현율이 크게 증가하지만, 정밀도가 급격하게 저하됨

    • 모델이 오버 샘플링으로 인해 실제 원본 데이터의 유형보다 너무나 많은 Class = 1 데이터를 학습하면서 실제 테스트 데이터 세트에서 예측을 지나치게 Class = 1으로 예측하는 문제
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 이상에서는 반대로 재현율이 대폭 떨어지고 정밀도가 높아짐
### 2) 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
  • 재현율은 높아졌지만, 정밀도는 낮아짐

5. 결과 정리

image.png

  • SMOTE를 적용하면 재현율은 높아지나 정밀도는 낮아지는 것이 일반적임

  • 좋은 SMOTE 패키지일수록 재현율 증가율은 높이고 정밀도 감소율은 낮출 수 있도록 효과적으로 데이터를 증식함