불균형 데이터(Imbalanced data)
- 클래스 간의 데이터 수가 균일하게 분포되어 있지 않은 경우를 말한다.
- 의료데이터의 경우 데이터셋 내에서 발병율에 따라 클래스 불균형이 필연적으로 발생
- 신용카드 사용자의 사기탐지 경우, 정상 거래가 비정상 거래보다 훨씬 많다.
📌 문제점
- 소수 클래스에 속한 데이터들은 다수 클래스에 속한 데이터보다 오분류될 가능성이 높다
- 정확도가 높더라도 재현율 또는 민감도가 낮아질 수 있다.
불균형 데이터를 해결하기 위한 방법
- 두 클래스중 어느 클래스의 데이터 수를 조절하느냐에 따라 언더샘플링 기법과 오버샘플링 기법으로 분류할 수 있다.
언더샘플링은 소수 클래스의 데이터 수에 맞도록 다수 클래스의 데이터를 제거하는 방식
언더샘플링 기법들은 데이터를 제거함으로 정보가 손실되는 문제점이 있다.
랜덤 언더샘플링(Random Under-sampling), Tomek-links, EasyEnsemble 등의 기법이 존재
다수 클래스 데이터 수에 맞춰 소수 클래스의 데이터를 생성하는 방식
정보 손실을 해결할 수 있지만, 데이터의 증가로 인한 계산 시간 증가, 과적합 등의 문제가 발생할 수 있다.
랜덤 오버샘플링, SMOTE, ADASYN (He, H. 등, 2008), Borderline-SMOTE 등의 다양한 기법이 존재
import numpy as np
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import matplotlib.pyplot as plt
import seaborn as sns
from collections import Counter
data = pd.read_csv('wisconsin dataset.csv')
data['diagnosis'] = data['diagnosis'].replace({'M' : 1 , 'B' : 0}) # M을 1로, B를 0으로 바꿔줌
data.head()
X = data.drop(columns={'diagnosis'})
y = data['diagnosis']
y_count = data['diagnosis'].value_counts()
print(y_count)
0 357
1 38
Name: diagnosis, dtype: int64
IR이 약 9.4 (IR=357/38=9.3947)로 심한 불균형을 보인다.
fig = sns.countplot(data['diagnosis'])
fig.set_xticklabels(['Benign(0)', 'Malignant(1)'])
plt.show()
1. Random oversampling
from imblearn.over_sampling import RandomOverSampler
counter = Counter(y)
print('Before',counter)
ros = RandomOverSampler(random_state=0)
x_ros, y_ros = ros.fit_resample(X, y)
counter = Counter(y_ros)
print('After',counter)
# 결과
Before Counter({0: 357, 1: 38})
After Counter({1: 357, 0: 357})
# df_ros로 만들어줌
df_ros = x_ros.copy()
df_ros['y'] = y_ros
2. SMOTE
from imblearn.over_sampling import SMOTE
counter = Counter(y)
print('Before',counter)
smote = SMOTE(random_state=0)
x_smote, y_smote = smote.fit_resample(X,y)
counter = Counter(y_smote)
print('After',counter)
# 결과
Before Counter({0: 357, 1: 38})
After Counter({1: 357, 0: 357})
# df_smote로 만들어줌
df_smote = x_smote.copy()
df_smote['y'] = y_smote
3. BorderlineSMOTE
from imblearn.over_sampling import BorderlineSMOTE
counter = Counter(y)
print('Before',counter)
B_SMOTE = BorderlineSMOTE(random_state=0)
x_b_smote, y_b_smote = B_SMOTE.fit_resample(X, y)
counter = Counter(y_b_smote)
print('After',counter)
# 결과
Before Counter({0: 357, 1: 38})
After Counter({1: 357, 0: 357})
# df_b_smote로 만들어줌
df_b_smote = x_b_smote.copy()
df_b_smote['y'] = y_b_smote
4. ADASYN
from imblearn.over_sampling import ADASYN
counter = Counter(y)
print('Before',counter)
adasyn = ADASYN(random_state=0)
x_adasyn, y_adasyn = adasyn.fit_resample(X, y)
counter = Counter(y_adasyn)
print('After',counter)
# 결과
Before Counter({0: 357, 1: 38})
After Counter({0: 357, 1: 353})
# df_adasyn로 만들어줌
df_adasyn = x_adasyn.copy()
df_adasyn['y'] = y_adasyn
f, axes = plt.subplots(2,3,figsize=(10, 5), dpi=100)
sns.despine()
sns.scatterplot(x='radius_mean', y='texture_mean', hue = y, data=data, ax=axes[0,0])
axes[0,0].set_title('Original dataset', fontsize=10)
axes[0,0].legend(['minority class(1)', 'majority class(0)'], fontsize=7)
axes[0,0].set_xlabel(None)
axes[0,0].set_ylabel(None)
sns.scatterplot(x='radius_mean', y='texture_mean', hue = 'y', data=df_ros, ax=axes[0,1], legend=False)
axes[0,1].set_title('Resampling with ROS', fontsize=10)
axes[0,1].set_xlabel(None)
axes[0,1].set_ylabel(None)
sns.scatterplot(x='radius_mean', y='texture_mean', hue = 'y',data=df_smote, ax=axes[0,2], legend=False)
axes[0,2].set_title('Resampling with SMOTE', fontsize=10)
axes[0,2].set_xlabel(None)
axes[0,2].set_ylabel(None)
sns.scatterplot(x='radius_mean', y='texture_mean',hue = 'y', data=df_adasyn, ax=axes[1,0], legend=False)
axes[1,0].set_title('Resampling with ADASYN', fontsize=10)
axes[1,0].set_xlabel(None)
axes[1,0].set_ylabel(None)
sns.scatterplot(x='radius_mean', y='texture_mean', hue = 'y', data=df_b_smote, ax=axes[1,1], legend=False)
axes[1,1].set_title('Resampling with B_SMOTE', fontsize=10)
axes[1,1].set_xlabel(None)
axes[1,1].set_ylabel(None)
plt.setp(axes, yticks=[])
plt.tight_layout()
plt.show()