[ISLR] Resampling(Validation Set Approach, LOOCV, k-fold CV,Bootstrap)

이 글은 Introduction to Statistical Learning with R (ISLR)의 내용을 바탕으로 작성되었습니다.
저자들은 웹사이트를 통해 pdf 파일과 예제데이터, 예제코드를 제공하고 있습니다.

Resampling은 ISLR 5장의 내용입니다. 5장에서는 4가지 resampling 기법을 설명하고 있습니다.

1. Validation Set Approach
2. Leave One Out Cross-Validation
3. k-fold Cross-Validation
4. Bootstrap


1. Validation Set Approach

Validation Set Approach는 데이터를 train과 test로 분할하는 가장 단순한 방법입니다. 데이터를 train과 test로 분할하여 train set으로 모델을 만들고 모델의 성능은 test set으로 검증합니다.

library(ISLR)
set.seed(1)
train=sample(392,196)

데이터를 랜덤하게 반으로 분할하기 위해서 sample 함수를 사용했습니다. train 변수에 들어가 있는 것은 train set을 구성할 데이터가 아니라 그 데이터를 뽑아줄 인덱스 입니다.

lm.fit=lm(mpg~horsepower,data=Auto,subset=train)

subset에 train을 인덱스로 지정해주면 train set의 데이터만 가지고 linear regression model을 fitting합니다.

attach(Auto)
mean((mpg-predict(lm.fit,Auto))[-train]^2)

test MSE를 구할때는 인덱스를 -train으로 설정해주면 됩니다.

set.seed(2)
train=sample(392,196)

lm.fit=lm(mpg~horsepower,subset=train)
mean((mpg-predict(lm.fit,Auto))[-train]^2) # 23.29559

lm.fit2=lm(mpg~poly(horsepower,2),data=Auto,subset=train)
mean((mpg-predict(lm.fit2,Auto))[-train]^2) # 18.90124

lm.fit3=lm(mpg~poly(horsepower,3),data=Auto,subset=train)
mean((mpg-predict(lm.fit3,Auto))[-train]^2) # 19.2574

1차, 2차, 3차 항을 가진 linear regression model을 fitting해서 test MSE를 비교해본 결과 2차항까지 사용하는 것이 가장 좋다는 결론을 내릴 수 있습니다.


2. Leave One Out Cross-Validation

앞에서 살펴본 Validation Set Approach의 단점은 분할을 한 번만 하기때문에 test MSE도 한 번만 계산하게 되고 결국 test MSE의 variation이 커지게 되는 현상이 발생한다는 것입니다.

LOOCV는 이러한 단점을 보완하기 위해 test MSE를 여러번 계산하여 그 평균값을 최종적인 test MSE로 사용합니다.

n개의 관측치가 있다면 총 n번의 계산을 수행하게 됩니다.

1 2 3 ... n
1 2 3 ... n
...
1 2 3 ... n

첫 번째 계산에서는 1을, 두 번째 계산에서는 2를 ... n번째 계산에서는 n을 test set으로 사용하는 것입니다.

LOOCV를 R에서 사용하려면 boot라이브러리가 필요합니다.

library(boot) 
glm.fit=glm(mpg~horsepower,data=Auto)
cv.err=cv.glm(Auto,glm.fit)
cv.err$delta

glm함수에서 family='binomial'인자를 설정해주지 않으면 lm처럼 linear regression 모델을 fitting해줍니다.

glm함수를 사용하는 이유는 cv.glm함수를 사용하기 위해서 입니다.

> cv.err$delta # LOOCV = 24.23151 , adj LOOCV = 24.23114
[1] 24.23151 24.23114

delta의 첫 번째 숫자는 보정을 가하지 않은 CV 추정치이고 두 번째 숫자는 보정을 가한 CV 추정치입니다.

이제 모델의 차수를 높이면서 test MSE를 비교해보겠습니다.

cv.error=rep(0,5)

for (i in 1:5){
 glm.fit=glm(mpg~poly(horsepower,i),data=Auto)
 cv.error[i]=cv.glm(Auto,glm.fit)$delta[1]
 }

cv.error
> cv.error
[1] 24.23151 19.24821 19.33498 19.42443 19.03321

결과를 보면 2차항 이상을 사용한 모델은 test MSE가 거의 비슷하다는 것을 알 수 있습니다. 따라서 가장 해석하기 쉬운 2차항까지 포함된 모델을 선택하는 것이 좋습니다.


3. k-fold Cross-Validation

LOOCV는 test MSE의 변동성을 줄여주는 장점이 있지만 n번의 계산을 해야하기 때문에 시간이 오래 걸리는 문제점이 있습니다. 이를 해결하기 위해 등장한 것이 k-fold Cross-Validation입니다.

k-fold Cross-Validation은 k번의 계산만 하면 됩니다. 데이터를 k개의 그룹으로 나누어 k번째 그룹을 test set으로 나머지를 train set으로 사용하는 것입니다. k=n인 경우 k-fold Cross-Validation은 LOOCV와 동일합니다.

k-fold CV에서도 LOOCV와 동일하게 boot 라이브러리의 cv.glm함수를 사용합니다. 다른점은 cv.glm함수에 K=k 조건을 넣는다는 것입니다. K의 디폴트 값은 n으로 설정되어 있어서 K를 지정해주지 않으면 cv.glm은 LOOCV로 계산한 결과를 반환하는 것입니다.

set.seed(17)
k=10
kfcv.error=rep(0,10) # 값을 저장할 벡터를 만들어줌

for (i in 1:10){
 glm.fit=glm(mpg~poly(horsepower,i),data=Auto)
 kfcv.error[i]=cv.glm(Auto,glm.fit,   K=k)$delta[1]
}

# LOOCV(392번 계산)보다는 훨씬 빠름 10-Fold CV(10번 계산)
kfcv.error # LOOCV 보다 계산이 더 빠른데 결과는 비슷함

 [1] 24.20520 19.18924 19.30662 19.33799 18.87911 19.02103 18.89609 19.71201 18.95140
[10] 19.50196

10-fold CV를 해보았습니다.

계산결과는 LOOCV와 큰 차이가 없지만 계산에 걸리는 시간이 LOOCV보다 훨씬 짧습니다.(물론 n이 작은 경우에는 거의 비슷합니다.)

LOOCV와 k-fold CV 사이에는 Bias-Variance tradeoff가 존재합니다. LOOCV는 bias가 매우 작지만 variance는 k-fold CV보다 큽니다. 반대로 k-fold CV는 bias는 중간정도이지만 LOOCV보다 variance가 작습니다.

4. Bootstrap

Bootstrap 기법은 이미 추출된 표본(n개의 관측치)을 모집단 처럼 생각하고 복원추출로 n개의 관측치를 뽑아 B개의 새로운 표본들을 만들어내는 것입니다. 복원추출을 하기 때문에 Bootstrap 데이터셋에 동일한 관측치가 두번 이상 포함될 수 있습니다.

Bootstrap기법을 간단하게 표현하면 다음과 같습니다.

x <- rexp(100,rate=1)
B <- 1000
ftn <- function(x,index){sum(x[index])/length(x[index])}
x.boot<-numeric(B)

set.seed(1)
for (i in 1:B){
  x.boot[i]<-ftn(x,sample(100,100,replace=T))
}

mean(x.boot)
sd(x.boot)

> mean(x.boot)
[1] 1.067265

> sd(x.boot)
[1] 0.11252

이 과정을 간단하게 해주는 것이 boot 라이브러리의 boot함수입니다.

set.seed(1)
boot(x,ftn,R=1000)

ORDINARY NONPARAMETRIC BOOTSTRAP
Call:
boot(data = x, statistic = ftn, R = 1000)
Bootstrap Statistics :
    original      bias    std. error
t1* 1.063539 0.003725453   0.1199081