구분할 각 분류에 해당하는 데이터의 비율이 반반이 아닌 경우 훈련 데이터 내 비율이 높은 분류 쪽으로 결과를 내놓는 모델을 만들게 될 수 있다. 이런 상황을 클래스 불균형(Class Imbalance)이라고 한다. 이 절에서는 클래스 불균형의 예와 해결 방법을 알아본다.
예를 들어, 다음의 유방암 데이터를 보자.
> library(mlbench)
> data(BreastCancer)
> table(BreastCancer$Class)
benign malignant
458 241
유방암 데이터의 양성(benign)의 수는 458, 악성(malignant)의 수는 241이다. 이 데이터로 기계 학습 모델을 만들면 주어진 입력에 대해 benign을 결과로 줄 확률이 malignant를 줄 확률에 비해 높아진다. 왜냐하면 주어진 데이터를 무조건 benign으로만 예측해도 65.5% 의 정확도를 확보하게 되기 때문이다. 즉, malignant의 특성이 무엇인지를 학습 알고리즘이 잘 배우지 않게 될 가능성이 높아진다.
클래스 불균형을 해결하는 한 가지 방법은 관찰 데이터가 적은 쪽의 데이터에 더 큰 가중치(Weight)를 주거나, 데이터가 적은 쪽을 잘못 분류했을 때 더 많은 비용(Cost 또는 Loss)을 부과하는 것이다. 보통 모델링 함수의 param, loss, cost 등의 파라미터에 이런 값들을 지정할 수 있다.
또 다른 방법은 모델을 만드는 훈련 데이터를 직접 조절하는 방법이다. 이에는 업 샘플링(up sampling), 다운 샘플링(down sampling), SMOTE(Synthetic Minority Oversampling Technique)가 있으며, 이 절에서는 이들 세 가지 방법을 다룬다.
업 샘플링, 다운 샘플링
업 샘플링은 해당 분류에 속하는 데이터가 적은 쪽을 표본으로 더 많이 추출하는 방법이며, 다운 샘플링은 데이터가 많은 쪽을 적게 추출하는 방법이다.
예를 들어, 총 6개의 데이터가 있고 각각의 분류가 ‘0, 0, 0, 0, 1, 1’이라고 하자. 이 데이터를 그대로 쓰면 0을 예측할 확률이 큰 모델이 만들어지므로, 그대로 쓰지 않고 표본을 추출한다. 복원 추출 방식으로 분류 0과 1에서 각각 표본을 4개씩 뽑으면 ‘0, 0, 0, 0, 1, 1, 1, 1’이 되어 각 분류에 해당하는 데이터의 개수가 같아진다. 이것이 업 샘플링 방법이다. 반대로 다운 샘플링에서는 총 2개씩의 데이터를 0과 1에서 표본으로 뽑아 ‘0, 0, 1, 1’이 된다.
caret 패키지의 upSample( ), downSample( )이 이러한 표본 추출 방법을 지원한다.
- caret::upSample : 업 샘플링을 수행한다.
caret::upSample(
x, y # 데이터
)
- caret::downSample : 다운 샘플링을 수행한다.
caret::downSample(
x, y # 데이터
)
upSample( )과 downSample( )의 사용법은 유사하므로 여기서는 upSample( )의 예만 살펴보자. upSample( )은 인자로 설명 변수(X)와 예측 대상이 되는 분류(Y)를 인자로 받아 변환된 데이터를 결과로 출력한다.
다음은 BreastCancer 데이터를 대상으로 upSample( )을 수행한 예다.
>install.packages("caret")
>library(caret)
> x <- upSample(subset(BreastCancer, select=-Class), BreastCancer$Class)
> table(BreastCancer$Class)
benign malignant
458 241
> table(x$Class)
benign malignant
458 458
분할표(교차표)를 살펴보면 업 샘플링 이전에는 458:241의 비율이던 데이터가 458:458로 균형 잡히게 변한 것을 볼 수 있다. upSample( )은 이를 위해 단순히 적은 쪽에 해당하는 분류의 데이터를 중복하여 추출하는 방식을 사용한다. 이는 다음을 통해 확인할 수 있다.
> NROW(x)
[1] 916
> NROW(unique(x))
[1] 691
x 내 행의 수는 916이지만 이들 중 상당수는 중복된 것이다.
다음은 upSample( )을 훈련 데이터에 적용한 경우와 그렇지 않은 경우 각각 의사 결정 나무의 성능을 비교한 코드다.
> library(party)
> # Id는 식별자므로 피처로는 적절치 않아 제외한다. 그렇지 않으면 Id에 따라 예측을 수행하는
> # 의미 없는 모델이 만들어진다.
> data <- subset(BreastCancer, select=-Id)
> parts <- createDataPartition(data$Class, p=.8) # 80%의 훈련 데이터와 20%의 테스트 데이터
> data.train <- data[parts$Resample1,]
> data.test <- data[-parts$Resample1, ]
> m <- rpart(Class ~., data=data.train)
> confusionMatrix(data.test$Class,
+ predict(m, newdata=data.test, type="class"))
Confusion Matrix and Statistics
Reference
Prediction benign malignant
benign 86 5
malignant 3 45
Accuracy : 0.9424
95% CI : (0.8897, 0.9748)
No Information Rate : 0.6403
P-Value [Acc > NIR] : <2e-16
Kappa : 0.874
Mcnemar's Test P-Value : 0.7237
Sensitivity : 0.9663
Specificity : 0.9000
Pos Pred Value : 0.9451
Neg Pred Value : 0.9375
Prevalence : 0.6403
Detection Rate : 0.6187
Detection Prevalence : 0.6547
`Positive' Class : benign
> data.up.train <- upSample(subset(data.train, select=-Class),
+ data.train$Class)
> m <- rpart(Class ~., data=data.up.train)
> confusionMatrix(data.test$Class,
+ predict(m, newdata=data.test, type="class"))
Confusion Matrix and Statistics
Reference
Prediction benign malignant
benign 83 8
malignant 2 46
Accuracy : 0.9281
95% CI : (0.8717, 0.965)
No Information Rate : 0.6115
P-Value [Acc > NIR] : <2e-16
Kappa : 0.8455
Mcnemar's Test P-Value : 0.1138
Sensitivity : 0.9765
Specificity : 0.8519
Pos Pred Value : 0.9121
Neg Pred Value : 0.9583
Prevalence : 0.6115
Detection Rate : 0.5971
Detection Prevalence : 0.6547
`Positive' Class : benign
분할표에서 볼 수 있듯이 원본 데이터를 사용한 경우 malignant로 예측한 수는 45개였으나, upSample( ) 이후에는 46개로 늘었다. 이로 인해 Sensitivity도 0.9663에서 0.9765로 올랐으며, 대신 Specificity는 0.9000에서 0.8519로 내렸다.
createDataPartition( )은 훈련 데이터와 검증 데이터의 분리를 위해 사용했으며, 이 예에서는 80%의 데이터를 훈련 데이터, 나머지 20%를 검증 데이터로 사용하기 위해 p=0.8을 지정했다.
SMOTE
SMOTE는 비율이 낮은 분류의 데이터를 만들어내는 방법이다. SMOTE는 먼저 분류 개수가 적은 쪽의 데이터의 샘플을 취한 뒤 이 샘플의 k 최근접 이웃(k nearest neighbor)을 찾는다. 그리고 현재 샘플과 이들 k개 이웃 간의 차(difference)를 구하고, 이 차이에 0 ~ 1 사이의 임의의 값을 곱하여 원래 샘플에 더한다. 이렇게 만든 새로운 샘플을 훈련 데이터에 추가한다. 결과적으로 SMOTE는 기존의 샘플을 주변의 이웃을 고려해 약간씩 이동시킨 점들을 추가하는 방식으로 동작한다.
DMwR 패키지의 SMOTE( ) 함수는 이 알고리즘의 구현으로 비율이 낮은 분류의 데이터를 생성하는 기능과 비율이 높은 쪽 데이터를 적게 샘플링하는 기능을 제공한다.
- DMwR::SMOTE : SMOTE를 수행한다.
DMwR::SMOTE(
form, # 모델 포뮬러
data, # 포뮬러를 적용할 데이터
perc.over=200, # 적은 쪽의 데이터를 얼마나 추가로 샘플링해야 하는지
k=5, # 고려할 최근접 이웃의 수
# 적은 쪽의 데이터를 추가로 샘플링할 때 각 샘플에 대응해서 많은 쪽의 데이터를
# 얼마나 샘플링할지 지정
perc.under=200
)
SMOTE는 숫자형 데이터를 기본으로 하여 작성된 알고리즘이므로 BreastCancer를 예로 사용하기 곤란하다. 따라서 여기서는 example(SMOTE)의 예를 기반으로 한 설명을 하도록 하겠다. 다음은 예시를 위해 사용할 데이터를 만드는 코드다. 아이리스에서 setosa 종은 rare로, 그 외의 versicolor, virginica는 common으로 바꿨다. 그 결과 common에는 100개의 데이터가, rare에는 50개의 데이터가 지정되었다.
> data(iris)
> data <- iris[, c(1, 2, 5)]
> data$Species <- factor(ifelse(data$Species == "setosa","rare","common"))
> table(data$Species)
common rare
100 50
이 데이터에 SMOTE를 사용하여 common과 rare의 개수를 대략 맞춘 결과는 다음과 같다.
> newData <- SMOTE(Species ~ ., data, perc.over = 600, perc.under=100)
> table(newData$Species)
common rare
300 350
perc.over는 개수가 적은 분류로부터 얼마나 많은 데이터를 생성해낼지(over sampling)를 조정하는 변수며, perc.under는 개수가 많은 분류의 데이터를 얼마나 적게 샘플링할지(under sampling)를 조정하는 변수다. 이들 파라미터는 시행착오를 통해 지정해도 되고, 다음에 설명하는 방법을 참고해서 정해도 된다.
perc.over는 보통 100 이상으로 정하는 값으로 적은 쪽의 데이터 한 개당 perc.over/100개의 추가 데이터가 생성되어 샘플링된다. perc.under는 많은 쪽의 데이터 중 얼마만큼의 비율을 샘플링할 것인지를 정하는데 이 비율은 perc.over에 의해 추가로 생성된 데이터와 비례해서 정해진다. 예를 들어, perc.over에 의해 200개의 추가 데이터가 적은 쪽 분류에 추가로 생성되었고, perc.over가 100이라면 많은 쪽 분류에 속하는 데이터로부터도 200개의 데이터가 취해진다.
R을 이용한 데이터 처리&분석 실무 中
'R > R을 이용한 데이터 처리&분석 실무' 카테고리의 다른 글
서포트 벡터 머신 (0) | 2020.02.13 |
---|---|
신경망 (0) | 2020.02.12 |
의사 결정 나무 (0) | 2020.02.12 |
다항 로지스틱 회귀 분석 (0) | 2020.02.12 |
로지스틱 회귀 모델 (0) | 2020.02.12 |