티스토리 뷰

클래스불균형(Imbalanced Class)에 대해 공부한 것을 공유하고자 합니다.

머신러닝에는 크게 회귀(Regression)와 분류(Classification)가 있습니다.
클래스불균형은 분류에서 발생하는 문제로 분류할려는 항목간 데이터 수가 큰 차이를 보이는 경우를 말합니다.

예를 들어 신용카드 사기에 대해 생각해보면, 엄청나게 많은 일반 거래(레이블:0)와 아주 조금의 사기거래(레이블:1)가 있을 것입니다. 
클래스는 레이블이 0과 1로 나누어지는 이진분류입니다.
(아래의 설명은 모두 이 예를 토대로 진행하겠습니다.)

머신러닝 학습은 레이블 0과 레이블 1이 어느정도 균형을 맞춰야 학습이 잘 이뤄집니다.
잘 이뤄진 학습은 분류하고자 하는 데이터를 좀더 정확히 분류할 수 있게 해 줍니다.

머신러닝 학습을 하기 전 클래스불균형을 다소 해소하면 좀더 좋은 훈련결과를 가져올 수 있습니다.
그 방법입니다.

첫째. 가중치를 다르게 부여하는 방법입니다.
레이블 0에 해당하는 데이터에는 가중치를 작게, 레이블 1에 해당하는 데이터에는 가중치를 크게 부여하여 수적으로 차이나는 것을 다소 해소할 수 있습니다.

둘째. Undersampling방법입니다.
레이블 0을 가진 많은 데이터 중에서 임의로 레이블 1의 수만큼을 추출하여 훈련을 시키는 방법입니다.
이 방법의 단점은 추출되지 않은 데이터 중에 양질의 데이터가 있을 수 있습니다.
물론 레이블 0에서 추출하는 데이터의 수는 조절할 수 있습니다.

셋째. Oversampling방법입니다.
먼저 Oversampling 방법은 레이블 1에 해당하는 데이터를 레이블 0에 해당하는 수만큼 반복하여 사용하는 방법이 있습니다.
같은 데이터를 사용하기에 문제가 발생할 가능성이 아주 큽니다.
다른 방법으로 SMOTE(Synthetic Minority Over-sampling TEchnique) 방법이 있습니다.
SMOTE 방법은  레이블 1을 가진 데이터 값과 비슷한 값을 가지는 데이터를 생성하는 방법입니다.

 


그림은 10,000개의 데이터 중 레이블 0이 9,900개(파란색), 레이블 1이 100개(주황색)인 데이터를 생성했습니다.
주황색 데이터 두 개를 선택하여 그 사이에 있는 값인 새로운 데이터(빨간색)를 생성하여 레이블 1인 데이터를 증가시키는 방법입니다.

이외의 다른 방법들도 있지만 오늘 말씀드릴 주된 내용이 SMOTE이므로 이에 관한 설명을 계속하겠습니다.

먼저 제가 작성한 코드 전체를 올립니다.

(패키지 설치 : pip install -U imbalanced-learn

                       conda install -c conda-forge imbalanced-learn)

 

 

# 0번
from collections import Counter
from sklearn.datasets import make_classification
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.pipeline import Pipeline
from numpy import where

from sklearn.model_selection import train_test_split
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score
from sklearn.metrics import confusion_matrix, f1_score

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 1번
def drawConfusionMatrix(conf, title="Confusion Matrix"):
    title = title
    cmap=plt.cm.viridis
    plt.figure(figsize=(6, 6))
    plt.imshow(conf, interpolation='nearest', cmap=cmap)  # , cmap=plt.cm.Greens
    plt.title(title, size=12)
    plt.colorbar(fraction=0.05, pad=0.05)
    plt.rc("font", size=20)
    tick_marks = np.arange(2, 2)
    plt.xticks(np.arange(2), ('0', '1'))
    plt.yticks(np.arange(2), ('0', '1'))

    fmt = "d"

    for i in range(conf.shape[0]):
        for j in range(conf.shape[1]):
            plt.text(j, i, format(conf[i, j], fmt),
                    ha="center", va="center",
                    color="red")
   
    plt.show()  

# ========================================================================================
# 2번

# define dataset
X, y = make_classification(n_samples=1000000, n_features=2, n_redundant=0,
    n_clusters_per_class=1, weights=[0.99], flip_y=0, random_state=1)
# summarize class distribution

counter = Counter(y)
print("클래스별 수량: ", counter)

X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)

counter = Counter(y_tr)
print("훈련/시험데이터 분리 후 클래스별 수량: ", counter)

model = XGBClassifier()
model.fit(X_tr, y_tr)

y_pred = model.predict(X_te)

evaluate = {}
evaluate["accuracy"]  = accuracy_score(y_te, y_pred)
evaluate["precision"] = precision_score(y_te, y_pred)
evaluate["recall"]    = recall_score(y_te, y_pred)
evaluate["f1_score"]  = f1_score(y_te, y_pred)

print(evaluate)

print("="*50)

# ========================================================================================
# 3번

# define pipeline
over = SMOTE(sampling_strategy=0.2)
under = RandomUnderSampler(sampling_strategy=0.4)

steps = [('o', over), ('u', under)]
pipeline = Pipeline(steps=steps)
# transform the dataset
X, y = pipeline.fit_resample(X_tr, y_tr)
# summarize the new class distribution
counter = Counter(y)
print("SMOTE 후 클래스별 수량: ", counter)

model_smote = XGBClassifier()
model_smote.fit(X, y)

y_pred_smote = model_smote.predict(X_te)

evaluate_smote = {}
evaluate_smote["accuracy"]  = accuracy_score(y_te, y_pred_smote)
evaluate_smote["precision"] = precision_score(y_te, y_pred_smote)
evaluate_smote["recall"]    = recall_score(y_te, y_pred_smote)
evaluate_smote["f1_score"]  = f1_score(y_te, y_pred_smote)

print(evaluate_smote)

print("="*50)

# ========================================================================================
# 4번

conf = confusion_matrix(y_te, y_pred)
print(conf)
title = "Confusion Matrix"
drawConfusionMatrix(conf, title)

conf = confusion_matrix(y_te, y_pred_smote)
print(conf)
title = "Confusion Matrix - SMOTE"
drawConfusionMatrix(conf, title)

 

다섯 부분으로 나눠 설명드리겠습니다.

 

# 0번
이 부분은 필요한 패키지를 불러오는 부분입니다.

# 1번
혼돈행렬(Confusion Matrix)를 도식화하는 함수를 구현한 것입니다.

# 2번 
X, y = make_classification(n_samples=1000000, n_features=2, n_redundant=0,
    n_clusters_per_class=1, weights=[0.99], flip_y=0, random_state=1)
- 데이터셋을 생성하는 부분입니다.
- n_samples : 전체 데이터의 개수입니다. 백만개를 생성합니다. 행렬에서 행에 해당합니다.
- n_features : 각 데이터별 특성 수입니다. 행렬에서 열에 해당합니다.
- weights=[0.99] : 전체에서 레이블 1이 차지하는 비율입니다. 100개 중 한 개가 레이블 1입니다.

counter = Counter(y)
print("클래스별 수량: ", counter)
<결과> 클래스별 수량:  Counter({0: 990000, 1: 10000})

X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)
- 데이터셋을 훈련(train data)용과 시험(test data)용으로 나눕니다.
- 시험데이터가 20%가 됩니다.

- stratify=y : 훈련데이터와 시험데이터의 레이블 비율이 처음 생성했던 것(99대 1의 비율)처럼 유지되도록 분할합니다.
- 이 시험데이터는 그대로 둡니다. 훈련 결과를 평가하는 평가지표로 사용하고 비교하기 위함입니다.

counter = Counter(y_tr)
print("훈련/시험데이터 분리 후 클래스별 수량: ", counter)
<결과> 훈련/시험데이터 분리 후 클래스별 수량:  Counter({0: 792000, 1: 8000})

model = XGBClassifier()
model.fit(X_tr, y_tr)
- 모델은 XGBoost 분류 모델을 사용했습니다.
- model.fit : 모델을 훈련시킵니다.

y_pred = model.predict(X_te)
- 처음 시험 데이터로 예측합니다.
- 아랫부분의 네 항목은 평가지표입니다. 혼돈행렬부분에서 설명합니다.


# 3번
over = SMOTE(sampling_strategy=0.2)
- 클래스 1에 해당하는 데이터의 수가 클래스 0의 데이터 수의 20%가 되도록 클래스 1의 데이터를 증가시킵니다.

under = RandomUnderSampler(sampling_strategy=0.4)
- 클래스 1에 해당하는 데이터 수가 클래스 0의 데이터의 40%가 되도록 클래스 0의 데이터수를 줄입니다.

- 클래스 1에 해당하는 데이터 수가 증가된 것을 기준으로 줄입니다.

X, y = pipeline.fit_resample(X_tr, y_tr)
- 위의 두 과정을 파이프를 통과시키듯이 차례로 실행합니다.

counter = Counter(y)
print("SMOTE 후 클래스별 수량: ", counter)
<결과> SMOTE 후 클래스별 수량:  Counter({0: 396000, 1: 158400})

model_smote = XGBClassifier()
model_smote.fit(X, y)
- 변경된 데이터로 훈련합니다.

y_pred_smote = model_smote.predict(X_te)
- 변경된 데이터셋으로 훈련한 모델로 예측합니다.
- X_te : 같은 시험데이터로 예측하여 비교할 예정입니다.


# 4번
혼돈행렬을 도식화하는 함수를 호출하여 그림으로 표현합니다.

 

{'accuracy': 0.99042, 'precision': 0.6099476439790575, 'recall': 0.1165, 'f1_score': 0.1956339210747271}

정확도 : 99.04% - (197851+233) / (197851+149+1767+233)

정밀도 : 60.99% - 233 / (149+233)

재현율 : 11.65% - 233 / (1767+233)

 

불균형클래스에서 정확도는 큰 의미가 없습니다.

예를 들어 10,000개 중 10개가 클래스 1일 때 "모든 데이터가 클래스 0이다라고 판정하면 정확도는 (10000-10)/10000 = 99.9%입니다.

잘못 판단한 것이 10개라는 의미입니다. 이렇게 많은 데이터에, 클래스가 불균형을 이룰 때는 정확도 의미가 중요하지 않게 됩니다.

정밀도 : 클래스 1이라고 판정한 것 중 실제 클래스가 1이 되는 비율입니다. 조건부 확률이니다.

재현율 : 실제 클래스가 1인데 클래스 1이라고 판정할 확률입니다. 이것도 조건부 확률입니다.

 

신용카드 사기를 예측하는 모델에서는 정밀도가 많이 중요합니다.

암진단을 예측하는 모델에서는 재현율과 정밀도가 중요합니다.

이렇게 평가지표는 상황에 따라 중요도가 달라집니다.

 

 

{'accuracy': 0.947825, 'precision': 0.15534853313720684, 'recall': 0.9505, 'f1_score': 0.2670506426915783}

정확도 : 94.78%

정밀도 : 15.53%

재현율 : 95.05%

 

혼돈행렬과 평가지표인 정밀도, 재현율에 관한 내용은 제가 작성한 글을 참조하시거나 다른 글을 참조하면 됩니다.

https://jjanhan.tistory.com/9?category=909371 

 

AI 기초 수학 정리 공책_05 조건부확률과 F1-score

1. 들어가며 결정트리기반 알고리즘을 통해서 분류를 하고 이 결과에 대한 분석을 합니다. 분석은 수치로 표현이 되고 이 값들에 대한 빠른 이해가 필요하기도 합니다. 많이 사용되는 결과 수치

jjanhan.tistory.com

 

 

제 설명은 여기까지이고 더 연습을 해보고 싶으신 분은,

1. 피쳐수를 조정해보시고,

2. 클래스수를 늘려서 훈련해 보시길 권합니다.

 

짧은 지식으로 올리는 것이라 설명이 제대로 되었는지 잘 모르겠습니다.

다음에 또다른 주제로 글을 올리겠습니다.

 

 

'machineLearning > machineLearning' 카테고리의 다른 글

제미나이(Gemini)와 이미지 처리에 대한 대화  (4) 2025.08.29
GPT와 대화하기  (1) 2024.03.28
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/12   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함