본문 바로가기
ML | DL | Big data/Data Science

불균형 클래스 분류(Imbalanced Classification)를 위한 4가지 방법

by 썽하 2020. 9. 4.

머신러닝으로 불균형 데이터를 분류하는 대부분의 예시는 이항 클래스 분류에 초점을 맞추고 있다. 그래서 이번엔 다중 클래스 불균형 데이터(Multi-class imbalanced data)를 처리하는 포스팅을 기록해보고자 한다.

 

이번 포스팅에서 다뤄볼 분균형 데이터 처리 방법은 다음 네 가지이다.

  • Under Sampling | 언더 샘플링
  • Simple Over Sampling | 단순 오버 샘플링
  • Algorithm Over Sampling | 알고리즘을 통한 오버샘플링(SMOTE, ADASYN)
  • Cost-sensitive learning | 뭐라고 번역하지

시작해보자.

 


개발환경

  • Python 3.6.11
  • imblearn 0.7.0

 

Glass Multi Class Classification Dataset

이 포스팅에서는 "유리 식별" 혹은 유리라고 하는 불균형 다중 클래스 분류 데이터를 중점적으로 다룰 예정이다. 이 데이터셋은 유리의 화학적 특성이 포함되어있고, 각 특성들을 이용해서 유리 샘플을 7가지 클래스 중 하나로 분류하는 것을 최종 목표로 한다.

 

데이터는 여기에서 받을 수 있다. 나는 코드에서 바로 읽어올 예정이므로 직접 다운로드하지 않는다.

특성은 9가지가 있으며 다음과 같다.

  • RI: 굴절률
  • Na: 나트륨
  • Mg: 마그네슘
  • Al: 알루미늄
  • SI: 실리콘
  • K: 칼륨
  • Ca: 칼슘
  • Ba: 바륨
  • Fe: 철

화학성분 특성은 산화물의 중량 퍼센트로 기록되어있다.

 

우리가 예측할 클래스는 7가지이다

  • Class 1: 빌딩 창문(float processed)
  • Class 2: 빌딩 창문(non-float processed)
  • Class 3: 자동차 창문(float processed)
  • Class 4: 자동차 창문 (non-float processed)
  • Class 5: 컨테이너
  • Class 6: 식기류
  • Class 7: 헤드램프

* float process란 유리를 처리하는 한 가지 공법이라고 한다.(이상 유알못)

 

데이터를 읽어와 보자

# 필요 모듈 임포트
import imblearn
print(imblearn.__version__)

import pandas as pd
from collections import Counter
from matplotlib import pyplot


# 필요 함수 정의
def count_and_plot(y): 
    counter = Counter(y)
    for k,v in counter.items():
        print('Class=%d, n=%d (%.3f%%)' % (k, v, v / len(y) * 100))
    pyplot.bar(counter.keys(), counter.values())
    pyplot.show()
    
    
#데이터 읽어오기
url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/glass.csv'
df = pd.read_csv(url, header=None)
data = df.values
X, y = data[:, :-1], data[:, -1]

 

각각의 클래스는 데이터수가 다음과 같다.

count_and_plot(y)

* 4번 클래스 데이터는 존재하지 않는다

 

 

데이터를 수치를 확인해보면 모든 특성은 숫자이고 마지막 컬럼이 클래스라는 것을 알 수 있다.

어느 정도 데이터를 확인해보았으니 이제 불균형 데이터를 처리해보자.

 

미리 말하자면 Under sampling과 단순 Over Sampling은 권장하지 않는 방법이다.
내가 이 포스팅에서 이에 관해서 글을 쓰는 이유는 부작용을 설명하기 함이다.

 

Under sampling | 언더 샘플링

언더 샘플링 전후 비교 예시

다음 세줄로 무작위 언더 샘플링을 진행 후 결과를 확인해보자.

데이터가 없는 4번 클래스를 제외하고 6번 클래스가 9개로 제일 적으니 모든 클래스의 데이터 개수가 9개로 맞춰질 것이다.

from imblearn.under_sampling import RandomUnderSampler
X_resampled, y_resampled = RandomUnderSampler(random_state=0).fit_resample(X, y)
count_and_plot(y_resampled)

결과는 다음과 같다.

 

언더 샘플링의 단점은 잠재적으로 정보의 가치가 높은 데이터도 버려서 유용한 데이터가 사라지는 위험이 있다는 것이다.

이 예제만 보아도 그 많던 데이터가 60여 개 밖에 남지 않았다. 여기서는 무작위로 언더 샘플링했지만,

imblearn 라이브러리 에는 아래와 같이 많은 언더 샘플링 방식이 있으니 관심 있으면 사용해보길 바란다.

 

Over Sampling | 오버 샘플링

Over Sampling 전후 비교 예시

오버샘플링은 상대적으로 적은 클래스의 데이터를 뻥튀기시켜 늘리는 방식이다. 위 그림을 보면 데이터가 똑같은 것처럼 보이겠지만 동일한 위치에 데이터가 생겼기 때문에 중복해서 표기된 것이다.

 

다음 세줄로 단순한 오버샘플링을 해보자.

from imblearn.over_sampling import RandomOverSampler
X_resampled, y_resampled = RandomOverSampler(random_state=0).fit_resample(X, y)
count_and_plot(y_resampled)

결과는 다음과 같다.

모든 클래스의 데이터가 76개로 통일되었다.

오버 샘플링 방식에는 여러 가지가 있지만 가장 단순한 방식은 동일한 데이터를 똑같이 복사해서 수만 늘리는 것이다. 이럴 경우 오버 피팅의 위험이 생긴다.

 

Algorithm Over Sampling | 알고리즘을 통한 오버샘플링

SMOTE와 ADASYN 알고리즘 Over sampling 전후 비교

 

오버 샘플링 알고리즘 중 가장 많이 쓰이는 것은 SMOTE와 ADASYN이다.

 

SMOTE는 

매우 간단하다. 다음 두 차례에 걸쳐서 일어난다.

  1. 먼저 소수 클래스에서 각각의 샘플들의 knn(k-nearest neighbors)을 찾는다.
  2. 그리고 그 이웃들 사이에 선을 그어 무작위 점을 생성한다.

SMOTE 예시

이렇게 생성하면 완전히 동일하진 않지만 샘플들 사이의 특성들을 반영한 데이터가 생성되기 때문에

오버 피팅에 나름대로 강한 데이터가 생성된다.(없지는 않다)

다음과 같이 실행하면 된다.

from imblearn.over_sampling import SMOTE
X_resampled, y_resampled = SMOTE(random_state=0).fit_resample(X, y)
count_and_plot(y_resampled)

ADASYN은

SMOTE의 개선된 버전이다. 동일한 프로세스를 진행한 후 점들에 임의의 작은 값을 더해줌으로써 조금 더 사실적인 데이터가 생성된다. 즉 모든 표본이 약간 더 분산된다.

다음과 같이 실행할 수 있다.

from imblearn.over_sampling import ADASYN
strategy = {1:76, 2:76, 3:76, 5:76, 6:76, 7:76}
X_resampled, y_resampled = ADASYN(sampling_strategy=strategy).fit_resample(X, y)
count_and_plot(y_resampled)

내 환경에서 아래와 같이 에러가 나는데 이유를 모르겠다...

No samples will be generated with the provided ratio settings.

 

상황에 따라 가장 많은 클래스 데이터 수로 지정할 수도 있지만 더 많은 숫자로 지정해서 모든 클래스를 오버 샘플링할 수도 있다. 이건 모델 개발자가 상황을 잘 판단해서 하는 수밖에 없다.

 

Cost-sensitive learning

Cost sensitive learning은 데이터 자체를 생성하진 않는다. 다만 머신러닝을 학습할 때 소수의 클래스에 대한 cost값에 가중치를 더 많이 주어 균형 잡힌 학습이 가능하게 하는 방법이다.

이것저것 다 사용해본 결과 어떤 때는 Cost-sensitvie learning이 더 효과가 좋았고, 어떨 때는 알고리즘을 이용한 오버 샘플링이 좋았다. 둘 다 사용해보고 상황에 맞는 결정을 해야 할 듯하다.

 

아래와 같이 랜덤 포레스트를 실행해서 가중치를 준 것과 안 준 것을 비교해보자.

from numpy import mean, std
from sklearn.model_selection import RepeatedStratifiedKFold, cross_val_score
from sklearn.ensemble import RandomForestClassifier

def evaluate_model(X_, y_, model_):
    cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=3, random_state=1)
    scores = cross_val_score(model_, X_, y_, scoring='accuracy', cv=cv, n_jobs=-1)
    return scores

model = RandomForestClassifier(n_estimators=1000)
scores = evaluate_model(X, y, model)
print('Mean Accuracy No weight: %.3f (%.3f)' % (mean(scores), std(scores)))

weights = {1:1.0, 2:1.0, 3:2.0, 5:2.0, 6:2.0, 7:2.0}
model = RandomForestClassifier(n_estimators=1000, class_weight=weights)
scores = evaluate_model(X, y, model)
print('Mean Accuracy    weight: %.3f (%.3f)' % (mean(scores), std(scores)))

크지는 않지만 가중치를 주었을 때 정확도가 더 높다!

랜덤 포레스트도 뿐만 아니라 xgboost에서는 scale_pos_weight 나 weight 같은 파라미터로 지정해 줄 수 있다.

 

 

 


Refernece

https://imbalanced-learn.readthedocs.io/en/stable/user_guide.html

 

댓글