Imbalanced Class#

데이터 준비 과정인 EDA(Exploratory Data Analysis) 에서는 분석에 맞는 데이터를 만들기 위해 많은 기법들을 통해 데이터를 정제합니다. 지도 학습을 통해 Classification 을 위한 데이터 준비 과정에서 일반적으로 발생하는 문제 중에 하나는 예측하고자 하는 Class 에 해당하는 데이터 수의 불균형을 들 수 있습니다.

예를 들어 공장에서 불량품을 분류하는 문제에 대한 모델을 만들 경우, 불량율이 1% 라고 한다면 양품과 불량품 Class 내의 데이터 비율은 99:1 로 불균형을 이루게 됩니다. 이는 모델 학습 및 평가에 많은 영향을 주게 됩니다. 학습 후 모델이 모든 예측을 양품으로 하더라도 단순히 정확도는 99% 가 되기 때문입니다. 물론 이 부분을 평가하는 다른 함수들이 존재하긴 하지만, 학습을 통해 좋은 모델을 얻기 위해서라도 Class내의 데이터 불균형 문제는 해결하는 것이 좋습니다.

Package: https://imbalanced-learn.org/stable/index.html

Over-sampling & Under-sampling#

Over-sampling 은 Class 내의 데이터 비율이 낮은 데이터를 여러 방법으로 샘플링하여 높은쪽 비율로 맞추는 방법이고, Under-sampling 은 Class 내의 데이터 비율이 높은 데이터를 여러 방법으로 샘플링하여 낮은쪽 비율로 맞추는 방법입니다.

Over-sampling Under-sampling

Over-sampling#

Class 내의 데이터 비율이 [0.01, 0.05, 0.94] 인 가상의 데이터를 만들고, 불균형 데이터를 통해 SVM 모델을 학습하여 평가한 결과와 Over-sampling 을 통해 균형 데이터로 만들고 학습한 결과입니다.

  • Random Sampling (RandomOverSampler)

  • The Synthetic Minority Oversampling Technique (SMOTE) CBHK2002

  • The Adaptive Synthetic (ADASYN) HBGL2008

%matplotlib inline
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings("ignore")

import numpy as np
from sklearn import datasets, svm, metrics
from imblearn import over_sampling
from collections import Counter

# 데이터
n_samples = 10000
np_data_xs, np_data_ys = datasets.make_classification(n_samples=n_samples, 
                                                      n_features=2, n_informative=2,
                                                      n_redundant=0, n_repeated=0, 
                                                      n_classes=3, n_clusters_per_class=1,
                                                      weights=[0.01, 0.05, 0.94],
                                                      class_sep=0.8, random_state=0)
print("np_data_ys={}".format(sorted(Counter(np_data_ys).items())))
idx = int(n_samples * 0.7)
np_train_xs = np_data_xs[:idx]
np_train_ys = np_data_ys[:idx]
np_test_xs = np_data_xs[idx:]
np_test_ys = np_data_ys[idx:]
print("np_train_ys={}".format(sorted(Counter(np_train_ys).items())))
print("np_test_ys={}".format(sorted(Counter(np_test_ys).items())))

# 불균형 데이터 학습
model = svm.LinearSVC()
model.fit(np_train_xs, np_train_ys) 

# 평가
np_pred_ys = model.predict(np_test_xs)
cr = metrics.classification_report(np_test_ys, np_pred_ys)
print("classification_report\n", cr)

# Over-sampling
samplers = [
    over_sampling.RandomOverSampler(random_state=0),
    over_sampling.SMOTE(random_state=0),
    over_sampling.ADASYN(random_state=0)
]

for sampler in samplers:
    np_train_resampled_xs, np_train_resampled_ys = sampler.fit_resample(np_train_xs, np_train_ys)

    print("np_train_resampled_ys={}".format(sorted(Counter(np_train_resampled_ys).items())))
    print("np_test_ys={}".format(sorted(Counter(np_test_ys).items())))

    # 균형 데이터 학습
    model = svm.LinearSVC()
    model.fit(np_train_resampled_xs, np_train_resampled_ys) 

    # 평가
    np_pred_ys = model.predict(np_test_xs)
    cr = metrics.classification_report(np_test_ys, np_pred_ys)
    print("classification_report\n", cr)
np_data_ys=[(0, 132), (1, 523), (2, 9345)]
np_train_ys=[(0, 89), (1, 380), (2, 6531)]
np_test_ys=[(0, 43), (1, 143), (2, 2814)]
classification_report
               precision    recall  f1-score   support

           0       0.00      0.00      0.00        43
           1       0.73      0.66      0.69       143
           2       0.98      1.00      0.99      2814

   micro avg       0.97      0.97      0.97      3000
   macro avg       0.57      0.55      0.56      3000
weighted avg       0.95      0.97      0.96      3000

np_train_resampled_ys=[(0, 6531), (1, 6531), (2, 6531)]
np_test_ys=[(0, 43), (1, 143), (2, 2814)]
classification_report
               precision    recall  f1-score   support

           0       0.46      0.63      0.53        43
           1       0.34      0.62      0.44       143
           2       0.99      0.94      0.96      2814

   micro avg       0.92      0.92      0.92      3000
   macro avg       0.60      0.73      0.65      3000
weighted avg       0.95      0.92      0.93      3000

np_train_resampled_ys=[(0, 6531), (1, 6531), (2, 6531)]
np_test_ys=[(0, 43), (1, 143), (2, 2814)]
classification_report
               precision    recall  f1-score   support

           0       0.46      0.63      0.53        43
           1       0.36      0.66      0.47       143
           2       0.99      0.94      0.96      2814

   micro avg       0.92      0.92      0.92      3000
   macro avg       0.60      0.74      0.65      3000
weighted avg       0.95      0.92      0.93      3000

np_train_resampled_ys=[(0, 6529), (1, 6527), (2, 6531)]
np_test_ys=[(0, 43), (1, 143), (2, 2814)]
classification_report
               precision    recall  f1-score   support

           0       0.30      0.79      0.44        43
           1       0.08      0.41      0.14       143
           2       0.99      0.78      0.87      2814

   micro avg       0.76      0.76      0.76      3000
   macro avg       0.46      0.66      0.48      3000
weighted avg       0.94      0.76      0.83      3000

단순히 정확도 면에서는 불균형 데이터가 더 정확하다고 나오지만, Class 0 의 예측 성능은 매우 안좋게 나오게 됩니다. 모델의 균형적 측면에서 정확도는 조금 낮지만 균형 데이터로 학습한 모델이 더욱 안정적인 성능을 나타냄을 알 수 있습니다.

Under-sampling#

Class 내의 데이터 비율이 [0.01, 0.05, 0.94] 인 가상의 데이터를 만들고, 불균형 데이터를 통해 SVM 모델을 학습하여 평가한 결과와 Under-sampling 을 통해 균형 데이터로 만들고 학습한 결과입니다.

  • Random Sampling (RandomUnderSampler)

  • K-means 알고리즘을 이용하여 sampling(ClusterCentroids)

%matplotlib inline
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings("ignore")

import numpy as np
from sklearn import datasets, svm, metrics
from imblearn import under_sampling
from collections import Counter

# 데이터
n_samples = 10000
np_data_xs, np_data_ys = datasets.make_classification(n_samples=n_samples, 
                                                      n_features=2, n_informative=2,
                                                      n_redundant=0, n_repeated=0, 
                                                      n_classes=3, n_clusters_per_class=1,
                                                      weights=[0.01, 0.05, 0.94],
                                                      class_sep=0.8, random_state=0)
print("np_data_ys={}".format(sorted(Counter(np_data_ys).items())))
idx = int(n_samples * 0.7)
np_train_xs = np_data_xs[:idx]
np_train_ys = np_data_ys[:idx]
np_test_xs = np_data_xs[idx:]
np_test_ys = np_data_ys[idx:]
print("np_train_ys={}".format(sorted(Counter(np_train_ys).items())))
print("np_test_ys={}".format(sorted(Counter(np_test_ys).items())))

# 불균형 데이터 학습
model = svm.LinearSVC()
model.fit(np_train_xs, np_train_ys) 

# 평가
np_pred_ys = model.predict(np_test_xs)
cr = metrics.classification_report(np_test_ys, np_pred_ys)
print("classification_report\n", cr)

# Under-sampling
samplers = [
    under_sampling.RandomUnderSampler(random_state=0),
    under_sampling.ClusterCentroids(random_state=0)
]

for sampler in samplers:
    np_train_resampled_xs, np_train_resampled_ys = sampler.fit_resample(np_train_xs, np_train_ys)

    print("np_train_resampled_ys={}".format(sorted(Counter(np_train_resampled_ys).items())))
    print("np_test_ys={}".format(sorted(Counter(np_test_ys).items())))

    # 균형 데이터 학습
    model = svm.LinearSVC()
    model.fit(np_train_resampled_xs, np_train_resampled_ys) 

    # 평가
    np_pred_ys = model.predict(np_test_xs)
    cr = metrics.classification_report(np_test_ys, np_pred_ys)
    print("classification_report\n", cr)
np_data_ys=[(0, 132), (1, 523), (2, 9345)]
np_train_ys=[(0, 89), (1, 380), (2, 6531)]
np_test_ys=[(0, 43), (1, 143), (2, 2814)]
classification_report
               precision    recall  f1-score   support

           0       0.00      0.00      0.00        43
           1       0.73      0.66      0.69       143
           2       0.98      1.00      0.99      2814

   micro avg       0.97      0.97      0.97      3000
   macro avg       0.57      0.55      0.56      3000
weighted avg       0.95      0.97      0.96      3000

np_train_resampled_ys=[(0, 89), (1, 89), (2, 89)]
np_test_ys=[(0, 43), (1, 143), (2, 2814)]
classification_report
               precision    recall  f1-score   support

           0       0.32      0.63      0.43        43
           1       0.52      0.64      0.57       143
           2       0.99      0.96      0.97      2814

   micro avg       0.94      0.94      0.94      3000
   macro avg       0.61      0.74      0.66      3000
weighted avg       0.96      0.94      0.95      3000

np_train_resampled_ys=[(0, 89), (1, 89), (2, 89)]
np_test_ys=[(0, 43), (1, 143), (2, 2814)]
classification_report
               precision    recall  f1-score   support

           0       0.47      0.63      0.53        43
           1       0.38      0.61      0.47       143
           2       0.99      0.95      0.97      2814

   micro avg       0.93      0.93      0.93      3000
   macro avg       0.61      0.73      0.66      3000
weighted avg       0.95      0.93      0.94      3000

단순히 정확도 면에서는 불균형 데이터가 더 정확하다고 나오지만, Class 0 의 예측 성능은 매우 안좋게 나오게 됩니다. 모델의 균형적 측면에서 정확도는 조금 낮지만 균형 데이터로 학습한 모델이 더욱 안정적인 성능을 나타냄을 알 수 있습니다.