더북(TheBook)

cvTools::cvFolds()

cvTools는 교차 검증을 위한 패키지로, cvFolds( ) 함수를 사용해 데이터의 분할fold을 만드는 기능을 제공한다.

표 9-17 cvTools를 사용한 교차 검증

cvTools::cvFolds : n개의 관찰을 K겹 교차 검증의 R회 반복으로 분할한다.

cvTools::cvFolds(
  n,   # 관찰(Observation)의 수 또는 데이터의 크기
  K=5, # K겹 교차 검증
  R=1, # R회 반복
  # 분할 방법을 지정. 기본 방식은 임의(random)다. 연속(consecutive)은 각 K마다
  # 연속된 데이터를 검증 데이터로 선택한다. 상호 배치(interleaved)는 연속된 데이터를 서로 다른
  # K번째 교차 검증에서 검증 데이터로 사용한다.
  type=c("random", "consecutive", "interleaved")
)

반환 값은 cvFolds 객체로 n, K, R, subsets(색인의 나열을 컬럼으로 한 행렬), which(각 나열이 어느 분할에 해당하는지를 뜻하는 색인)가 저장되어 있다.

example(cvFolds)에 있는 내용으로 cvFolds의 동작 방식을 확인해보자. 다음은 10개 데이터를 K=5, R=1, 임의random 방식으로 실행한 결과다.

> install.packages("cvTools")
> library(cvTools)
> cvFolds(10, K=5, type="random")
5-fold CV:
Fold Index
   1     5
   2     2
   3     6
   4     1
   5     9
   1    10
   2     4
   3     7
   4     3
   5     8

cvFolds는 각 K마다 검증 데이터로 사용할 값을 반환한다. 위 결과에서 Fold가 1일 때 Index는 5, 10이었다. 따라서 5겹 교차 검증에서 K=1일 때 5번, 10번 데이터를 검증 데이터로 사용하고 나머지를 훈련 데이터로 사용하면 된다. 마찬가지로 K=2일 경우 2번, 4번 데이터를 검증 데이터로 사용한다.

다음으로 cvFolds에서 연속consecutive과 상호 배치interleaved의 차이를 살펴보자.

> cvFolds(10, K=5, type="consecutive")
5-fold CV:
Fold Index
   1     1
   1     2
   2     3
   2     4
   3     5
   3     6
   4     7
   4     8
   5     9
   5    10

> cvFolds(10, K=5, type="interleaved")
5-fold CV:
Fold Index
   1     1
   2     2
   3     3
   4     4
   5     5
   1     6
   2     7
   3     8
   4     9
   5    10

위 결과를 보면 consecutive는 K=1일 때 1번, 2번 데이터를 검증 데이터로 선택하고 K=2일 때 3번, 4번을 선택했다. 이처럼 consecutive는 연속된 데이터를 차례로 검증 데이터로 사용한다. 반면 interleaved는 1번 데이터를 K=1일 때, 2번 데이터를 K=2일 때 검증 데이터로 사용하는 방식으로 연속된 데이터를 차례로 서로 다른 K의 검증 데이터로 할당한다.

다음은 아이리스 데이터에 대해 10겹 교차 검증을 3회 반복 수행하기 위해 cvFolds( )를 사용한 예다. cvFolds( ) 실행 전에 호출한 set.seed( )는 난수를 생성하는 초깃값seed을 지정하기 위해 사용했다. cvFolds( )는 난수를 사용하여 데이터를 분리하므로 매 호출 시마다 서로 다른 분할을 결과로 내놓는다. 하지만 seed를 지정해주면 매번 같은 folds를 결과로 내놓게 되어 교차 검증을 수회 반복하더라도 같은 분할을 사용해 안정적으로 모델을 개선할 수 있다.

> set.seed(719)
> (cv <- cvFolds(NROW(iris), K=10, R=3))

Repeated 10-fold CV with 3 replications:
Fold      1   2   3
1        92   4  86
2        52   3 144
3        17  75   5
4        13  98  61
5        61  30  16
6         9 129 148
7         8 121  49
8        31  89  37
9       136 140  82
10       37 102 141
1        90  51  78
2       119  80 132
3       116  70  97
4        11  29  93
5        39  72  45
6        94 125 114
7        68  84  25
8        75 123  69
9       131  73  87
10      100 132  63
...
6        35 137 139
7        73 133  50
8       133  64   7
9       111  74 134
10       66  27 138

위 결과에서 Fold는 K겹 교차 검증의 K를 의미하고 컬럼 1, 2, 3은 반복 R을 의미한다. 따라서 1행 1열의 92는 K=1, R=1일 때 검증 데이터로 아이리스의 92번째 데이터를 사용하라는 의미다. 1행 2열은 4다. 이는 K=1, R=2일 때 검증 데이터로 4번째 데이터를 사용하라는 의미다.

위 표의 Fold에 해당하는 부분은 cv$which에, 실제 선택할 행을 저장한 부분은 cv$subset에 다음과 같이 저장되어 있다.

> head(cv$which, 20)
[1] 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10
> head(cv$subset)
    [,1] [,2] [,3]
[1,]  92    4   86
[2,]  52    3  144
[3,]  17   75    5
[4,]  13   98   61
[5,]  61   30   16
[6,]   9  129  148

따라서 첫 번째 반복의 K=1에서 검증 데이터로 사용해야 할 행의 번호는 다음과 같이 구할 수 있다. 아래 코드에서 which( ) 함수는 주어진 조건을 만족하는 행의 번호를 얻기 위해 사용했다.

> (validation_idx <- cv$subset[which(cv$which == 1), 1])
[1]  92  90  14 105  85  95 128  56 121 112 144   5  65 146   4

첫 번째 반복 R=1의 K=1에서 훈련, 검증 데이터는 다음과 같이 구한다.

> train <- iris[-validation_idx, ]
> validation <- iris[validation_idx, ]

이를 사용해 K겹 교차 검증을 반복하는 전체 코드의 모습을 그려보면 다음과 같다.

> library(foreach)
> set.seed(719)
> R = 3
> K = 10
> cv <- cvFolds(NROW(iris), K=K, R=R)
> foreach(r=1:R) %do% {
+   foreach(k=1:K, .combine=c) %do% {
+     validation_idx <- cv$subsets[which(cv$which == k), r]
+     train <- iris[-validation_idx, ]
+     validation <- iris[validation_idx, ]
+     # 데이터 전처리
+
+     # 모델 훈련
+
+     # 예측
+
+     # 성능 평가
+     return(성능 값)
+   }
+ }
> # foreach의 반환 값으로부터 성능이 가장 뛰어난 모델링 방법 식별
> # 아이리스 데이터 전체에 대해 해당 방법으로 모델 생성

코드에서 주목할 만한 점은 for 대신 foreach를 사용한 점이다. foreach( )는 값을 반환할 수 있어 모델을 평가한 결과를 한 번에 모으는 데 유용하다. 또, foreach( ) 사용 시 내부의 foreach( )에서는 .combine에 c를 지정하여 결과가 리스트가 아닌 벡터로 되게 했다. 이렇게 하면 결과가 리스트의 리스트가 아니라 벡터의 리스트가 되어 조작이 용이하다.

신간 소식 구독하기
뉴스레터에 가입하시고 이메일로 신간 소식을 받아 보세요.