불균형 데이터 SMOTE 코드 위주 실습과 결과 해석
이전 시간에 "불균형 데이터"가 왜 문제가 되고, 어떻게 처리해야 하는지, 어떤 평가 지표를 봐야하는지에 대해 공부했습니다.
오늘은 간단하게 코드로 구현해보려고 합니다. 불균형 데이터의 앞단 전처리가 완료되어 모델을 학습하는 것부터 진행해보도록 하겠습니다.
※ 데이터 : 캐글 Credit Card Fraud Detection https://www.kaggle.com/datasets/mlg-ulb/creditcardfrau
1. 필요한 라이브러리 불러오기
from imblearn.over_sampling import SMOTE
from sklearn.model_selection import train_test_split
from lightgbm import LGBMClassifier
from sklearn import metrics
2. train, test set으로 나누기 (불균형 데이터에서는 층화추출로 같은 비율로 나누는 것을 추천합니다)
X_train, X_test, y_train, y_test = train_test_split(x_data, y_data,
test_size=0.3,
stratify=y_data)
3. SMOTE로 train data를 오버 샘플링 합니다. (ADASYN, SMOTE-Tomek도 참고)
# 모델 객체 생성
smote = SMOTE(random_state=42)
# SMOTE는 반드시 train data만 오버 샘플링합니다!!!
X_train_over, y_train_over = smote.fit_sample(X_train, y_train)
print('SMOTE-sampling 적용 전 :', X_train.shape, y_train.shape)
print('SMOTE-sampling 적용 후 :', X_train_over.shape, y_train_over.shape)
from imblearn.over_sampling import ADASYN
adasyn = ADASYN(random_state=22)
X_ad, Y_ad = adasyn.fit_resample(X_train, y_train)
from imblearn.combine import SMOTETomek
from imblearn.under_sampling import TomekLinks
smoteto = SMOTETomek(tomek=TomekLinks(sampling_strategy='majority'))
X_smt, Y_smt = smoteto.fit_resample(X_train, y_train)
4. 오버 샘플링하기 전과 후를 LGBM 모델을 만들고 성능을 평가해보겠습니다.
model_without_smote = LGBMClassifier(n_estimators=1000,
num_leaves=64,
boost_from_average=False)
model_without_smote.fit(X_train, y_train) # Over-sampling 적용 전
pred = model_without_smote.predict(X_test)
pred_proba = model_without_smote.predict_proba(X_test)[:, 1]
confusion = metrics.confusion_matrix(y_test, pred)
accuracy = metrics.accuracy_score(y_test, pred)
precision = metrics.precision_score(y_test, pred)
recall = metrics.recall_score(y_test, pred)
f1 = metrics.f1_score(y_test, pred)
roc_auc = metrics.roc_auc_score(y_test, pred_proba)
print('Confusion matrix:')
print(confusion)
print()
print('Accuracy :', accuracy)
print('Precision :', precision)
print('Recall :', recall)
print('F1 :', f1)
print('ROC_AUC :', roc_auc)
model_with_smote = LGBMClassifier(n_estimators=1000,
num_leaves=64,
boost_from_average=False)
model_with_smote.fit(X_train_over, y_train_over) # Over-sampling 적용 후
pred = model_with_smote.predict(X_test)
pred_proba = model_with_smote.predict_proba(X_test)[:, 1]
confusion = metrics.confusion_matrix(y_test, pred)
accuracy = metrics.accuracy_score(y_test, pred)
precision = metrics.precision_score(y_test, pred)
recall = metrics.recall_score(y_test, pred)
f1 = metrics.f1_score(y_test, pred)
roc_auc = metrics.roc_auc_score(y_test, pred_proba)
print('Confusion matrix:')
print(confusion)
print()
print('Accuracy :', accuracy)
print('Precision :', precision)
print('Recall :', recall)
print('F1 :', f1)
print('ROC_AUC :', roc_auc)
SMOTE 오버샘플링 전 | SMOTE 오버샘플링 후 |
![]() |
![]() |
![]() |
- Precision = TP/(TP+FP) - Recall = TP/(TP+FN) - 일반적으로 Precision 과 Recall 는 trade-off 관계 |
→ 사기 거래 문제는 사기 거래가 아닌데 사기라고 예측하는 것보다 실제 사기인데 사기가 아니라고 예측하는 FN 에 민감합니다. → SMOTE 오버샘플링 전에는 총 146개의 사기거래 중에 115개를 사기 거래라고 잘 맞추고, 31개는 잘 못 맞췄습니다. - Recall = 115 / (115+31) = 0.7876
→ SMOTE 오버샘플링 후에는 총 146개의 사기거래 중에 120개를 사기 거래라고 잘 맞추고, 26개는 잘 못 맞췄습니다
- Recall = 120 / (120+26) = 0.8219
→ 비록 5개 더 많이 예측했지만, 실제 상황에서는 5개조차 중요한, 카드사 입장에서는 손실이 크며 아주 중요한 문제입니다. 또한 이는 5/146 = 3.4%입니다. 데이터 양에 비해 더 많이 예측하게 된 것입니다.
→ 실제 사기인데 사기라고 예측한 비율이 늘어나면서 Recall 이 상승했습니다.
→ 하지만 Precision이 낮아진 것을 볼 수 있을텐데, 일반적으로 Recall과 Precision는 임계값 조절에 의해 Trade-off 관계를 갖게 됩니다. 모델이 예측한 확률이 일정 임계값을 넘으면 양성으로 분류하는데, 이 임계값을 높이면 양성으로 분류하는 경우가 줄어들어 FP가 줄어들고 Precision 값이 높아집니다(Precision = TP/(TP+FP)). 하지만 음성으로 분류하는 경우가 늘면서 음성으로 잘못 분류되는 경우가 높아져 FN이 높아지고 Recall은 낮아집니다.
→ 사기 탐지의 경우, 실제 사기를 탐지하는 것이 더 중요하기 때문에, 임계값을 낮춰 모델을 설정하는 것이 유리하며 Recall을 높이는 것이 더 중요합니다.
※ 분류모델 정형 데이터에서 데이터가 적을 때 SMOTE 기법을 쓴다면, 이미지 분류에서 데이터가 적을 때는 Augmentation을 사용해 데이터를 조금씩 변형한다.