library(tidyverse)
Randomly Over Sampling Examples(ROSE)
ROSE는 smooth boostrap을 적용하여 리샘플링하는 방법입니다. smooth boostrap이란 resampling된 관측치에 평균이 0이고 정규분포를 따르는 아주 작은 양의 random noise을 더해 새로운 표본을 생성하는 원리를 따릅니다. ROSE는 oversampling과 undersampling 기법을 결합하여 소수 클래스는 늘리고, 다수클래스는 줄인 새로운 샘플을 생성합니다. 어떻게 계산되는지 알아보겠습니다.
먼저 iris 데이터를 불러오겠습니다.
2차원 시각화를 위해서 Sepal.Length, Sepal.Width, Species 변수만 이용하겠습니다.
$Species %>% unique() iris
[1] setosa versicolor virginica
Levels: setosa versicolor virginica
<- iris[, c(1, 2, 5)] %>%
X filter(Species %in% c("setosa", "versicolor") & row_number() %in% c(1:20, 51:100)) %>%
mutate(Species = factor(Species, labels = c("No", "Yes")))
%>%
X ggplot(aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
geom_point()
No는 소수 클래스이고, Yes는 다수 클래스입니다. ROSE를 적용하고 결과를 살펴보겠습니다.
ROSE는 rose.real()
함수를 통해서 계산됩니다. 하나씩 어떻게 계산되는지 살펴보겠습니다.
<- function(X, hmult=1, n, q = NCOL(X), ids.class, ids.generation)
rose.real
{<- data.matrix(X)
X <- length(ids.generation)
n.new <- (4/((q+2)*n))^(1/(q+4))
cons.kernel
if(q!=1)
<- hmult*cons.kernel*diag(apply(X[ids.class,], 2, sd), q) # 3x3
H else
<- hmult*cons.kernel*sd(X[ids.class,])
H
<- matrix(rnorm(n.new*q), n.new, q)%*%H
Xnew.num <- data.matrix(Xnew.num + X[ids.generation,])
Xnew.num
Xnew.num }
rose.real(X, hmult=1, n, q, ids.class, ids.generation)
X : numeric matrix
hmult : smoothing parameter
n : 소수 클래스의 수
q : 수치형 칼럼의 수
ids.class : 소수 클래스의 index
ids.generation : 복원 추출한 소수클래스의 index
<- sapply(as.data.frame(X), class)
classx classx
Sepal.Length Sepal.Width Species
"numeric" "numeric" "factor"
<- which(classx=="numeric" | classx=="integer")
id.num id.num
Sepal.Length Sepal.Width
1 2
<- NCOL(X)
d = 1
hmult
<- 0.5
p <- nrow(X)
N <- sum(rbinom(N, 1, p)) #number of new minority class examples
n.mino.new n.mino.new
[1] 37
<- N-n.mino.new #number of new minority class examples
n.majo.new n.majo.new
[1] 33
<- "Yes" # major label
majoY <- "No" # minor label
minoY <- which(X$Species == minoY)
ind.mino <- which(X$Species == majoY)
ind.majo ind.mino
[1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
ind.majo
[1] 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
[26] 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
<- length(ind.majo)
n <- d-length( which(classx=="factor"))
q q
[1] 2
복원추출을 활용하여, 소수 클래스는 관측치의 수를 늘리고, 다수 클래스는 관측치의 수를 줄입니다.
<- sample(ind.majo, n.majo.new, replace=TRUE)
id.majo.new <- sample(ind.mino, n.mino.new, replace=TRUE) id.mino.new
rose.real()
함수에서 cons.kernel 값은 n(소수 클래스의 수)
와 q(수치형 칼럼의 수))
로 계산됩니다.
<- length(ind.mino)
n <- d-length( which(classx=="factor"))
q <- (4/((q+2)*n))^(1/(q+4))
cons.kernel
cons.kernel # > cons.kernel
# [1] 0.6069622
수치형 변수만 있어야 하므로 X에서 수치형 변수만 filtering 해줍니다. X matrix의 각 칼럼별 표준편차를 구한 후 사전에 지정한 hmult
와 cons.kernel
과 함께 곱합 matrix H를 생성합니다.
<- X[,id.num]
X 1:3, ]
X[# Sepal.Length Sepal.Width
# 1 5.1 3.5
# 2 4.9 3.0
# 3 4.7 3.2
<- hmult*cons.kernel*diag(apply(X[ind.mino,], 2, sd), q)
H # > H
# [,1] [,2]
# [1,] 0.2592196 0.0000000
# [2,] 0.0000000 0.2472168
matrix H는 synthetic sample을 생성할 때, random noise의 크기를 조절하는 역할을 합니다. 따라서
<- length(id.mino.new)
n.new <- matrix(rnorm(n.new*q), n.new, q)%*%H
Xnew.num # > Xnew.num %>% head()
# [,1] [,2]
# [1,] 0.04899544 -0.31797929
# [2,] 0.11534283 -0.04240093
# [3,] -0.05767842 -0.11889093
# [4,] -0.02200680 0.18249786
# [5,] 0.13977148 0.09139938
# [6,] 0.16442458 0.06085837
평균이 0인 정규분포와 H matrix를 곱한 random noise matrix를 생성합니다.
<- data.matrix(Xnew.num + X[id.mino.new,])
Xnew.num # > Xnew.num %>% head()
# Sepal.Length Sepal.Width
# 5 5.048995 3.282021
# 6 5.515343 3.857599
# 9 4.342322 2.781109
# 1 5.077993 3.682498
# 12 4.939771 3.491399
# 5.1 5.164425 3.660858
# > X %>% head()
# Sepal.Length Sepal.Width
# 1 5.1 3.5
# 2 4.9 3.0
# 3 4.7 3.2
# 4 4.6 3.1
# 5 5.0 3.6
# 6 5.4 3.9
random noise를 더한 새롭게 생성한 관측치와 기존의 관측치 값을 대략적으로 비교해보면 관측치 주변 이웃으로 새로운 관측치가 생성되는 것을 볼 수 있습니다.
<- rose.real(X[,id.num],
rose_result hmult=1,
n=length(ind.mino),
q=q,
ids.class=ind.mino,
ids.generation=id.mino.new)
%>%
X ggplot() +
geom_point(aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
geom_point(aes(x = Sepal.Length, y = Sepal.Width), data = rose_result %>% as.data.frame())
hmult=0.01
로 설정하면 기존 관측치와 가까운 새로운 관측치를 생성하는 것을 볼 수 있습니다.
<- rose.real(X[,id.num],
rose_result2 hmult=0.01,
n=length(ind.mino),
q=q,
ids.class=ind.mino,
ids.generation=id.mino.new)
%>%
X ggplot() +
geom_point(aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
geom_point(aes(x = Sepal.Length, y = Sepal.Width), data = rose_result2 %>% as.data.frame())
<- data.frame(X[c(id.majo.new, id.mino.new),])
Xnew %>% dim() Xnew
[1] 70 3
= 1
hmult.majo = 1 hmult.mino
소수 클래스와 다수 클래스 각각에 rose.real()
함수를 적용합니다. 소수 클래스는 늘리고, 다수 클래스는 줄여서 Xnew
데이터를 새롭게 생성합니다.
1:n.majo.new, id.num] <- rose.real(X[,id.num], hmult=hmult.majo, n=length(ind.majo), q=q, ids.class=ind.majo, ids.generation=id.majo.new)
Xnew[#Xnew[1:n.majo.new, id.num] %>% dim() : 80, 2
#
+1):N, id.num] <- rose.real(X[,id.num], hmult=hmult.mino, n=length(ind.mino), q=q, ids.class=ind.mino, ids.generation=id.mino.new) Xnew[(n.majo.new
%>%
X ggplot() +
geom_point(aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
geom_point(aes(x = Sepal.Length, y = Sepal.Width, color = Species), data = Xnew)
%>%
Xnew ggplot() +
geom_point(aes(x = Sepal.Length, y = Sepal.Width, color = Species))
$Species %>% table() X
.
No Yes
20 50
$Species %>% table() Xnew
.
No Yes
37 33
ROSE package 이용
<- ROSE::ROSE(Species~., data = X)
result $data %>%
resultggplot() +
geom_point(aes(x = Sepal.Length, y = Sepal.Width, color = Species))
$data$Species %>% table() result
.
Yes No
42 28
$Species %>% table() X
.
No Yes
20 50
참고 자료
https://github.com/cran/ROSE/blob/master/R/data_balancing_funcs.R
Citation
@online{don2022,
author = {Don, Don and Don, Don},
title = {ROSE},
date = {2022-11-16},
url = {https://dondonkim.netlify.app/posts/2022-11-16-ROSE/rose.html},
langid = {en}
}