본문 바로가기

R/R을 이용한 데이터 처리&분석 실무

모델 평가 방법 - 2

반응형

교차 검증

주어진 데이터 전체를 사용해 모델을 만들 경우, 해당 데이터에는 잘 동작하지만 새로운 데이터에는 좋지 않은 성능을 보이는 모델을 만들 가능성이 있다. 이러한 현상이 발생하는 주요 이유 중 하나가 과적합이다. 과적합 발생 여부를 알아내려면 주어진 데이터 중 일부는 모델을 만드는 훈련 데이터로 사용하고, 나머지 일부는 테스트 데이터로 사용해 모델을 평가해야 한다.

 

테스트 데이터는 모델의 파라미터를 정하는 데도 필요하다. 예를 들어, k 최근접 이웃 알고리즘을 적용할 때 몇 개의 이웃을 보도록 k를 설정해야 하는지의 문제를 생각해보자. k가 크면 여러 이웃을 볼 것이고, k가 작으면 적은 개수의 이웃을 보게 된다. 그리고 적절한 k 값은 데이터에 따라 다르다. 따라서 어떤 k를 사용해야 하는지는 테스트 데이터를 사용해서 알 수 있다.

 

교차 검증(Cross Validation)은 훈련 데이터와 테스트 데이터를 분리하여 모델을 만드는 방법 중 가장 자주 사용하는 기법으로, 데이터를 다수의 조각으로 나누어 훈련과 테스트를 반복하는 기법이다.

 

이제 과적합, 테스트 데이터, 교차 검증에 대해 좀 더 자세히 알아보자. 그런 다음 caret을 사용한 교차 검증 코딩에 대해 설명할 것이다.

 

 

과적합(Overfitting)

과적합은 주어진 데이터로부터 보장되는 것 이상으로 모델을 만들 때 발생한다. 그림 9-24는 과적합의 예를 보여주는 그림이다. 좌표 평면 위의 점들은 주어진 데이터를 뜻하며, 직선과 점선은 해당 점들을 설명하는 모델을 의미한다.

그림 9-24

그림 9-24에서 직선으로 표현된 모델 1은 단순한 직선으로 데이터의 (X, Y)의 관계를 표현한 모델이다. 반면 점선으로 표현한 모델 2는 높은 차수의 다항식으로 데이터를 모델링한 예다. 단순히 주어진 데이터에 대한 정확도로만 따지면 모델 1이 모델 2에 비해 정확하지 않다. 그러나 데이터의 분포를 보건데 복잡한 곡선으로부터 나온 데이터가 아니라 단순 선형 관계로부터 나온 데이터일 가능성이 높아 보인다. 즉, 모델 1이 모델 2에 비해 좀 더 일반적인 모델일 가능성이 있고, 따라서 새로운 점이 위치할 만한 좌표를 더 잘 표현하는 모델일 수 있다. 반면 모델 2는 데이터를 관찰하면서 끼어든 노이즈를 모델에 반영하느라 관찰 데이터 자체는 충실히 표현하지만 데이터에 내제된 일반적인 구조를 표현하는 데는 실패했다.

 

이러한 가정이 참이라고 할 때 모델 2에는 과적합이 발생했다고 말한다.

 

 

테스트 데이터(Test Data)

직선을 선택할 것인지 또는 고차의 다항식을 선택할 것인지의 문제처럼 여러 가지 모델을 놓고 어떤 모델이 더 잘 동작할지를 추정해야 할 때가 있다. 또, 한 가지 모델을 놓고도 모델의 파라미터를 어떻게 설정하는 것이 더 나은지 결정해야 할 상황이 있을 수 있다. 마지막으로 특정 모델이 더 잘 동작할 것으로 보인다면, 과연 새로운 데이터가 주어졌을 때 얼마나 잘 동작할 것인지 그 성능을 추정할 필요도 있다.

 

데이터가 새로운 데이터에 얼마나 잘 동작할 것인지를 판단하는 방법 중 한 가지는 데이터의 일부를 따로 테스트 데이터로 떼어놓고 모델 평가에 사용하는 것이다. 이 방법의 수행 단계는 다음과 같다.

 

  • 데이터의 일부를 훈련 데이터(Training Data), 나머지를 테스트 데이터(Test Data)로 분리한다.
  • 훈련 데이터로부터 모델을 만든다.
  • 만들어진 모델을 테스트 데이터에 대해 적용해 성능을 평가한다. 성능이 만족스럽지 않다면 2단계로 돌아간다.
  • 어떻게 모델을 만들어야 할지 결정되었으므로 전체 데이터로부터 모델을 만들고 이를 최종 모델로 정한다.

그림 9-25

교차 검증(Cross Validation)

데이터의 어느 정도를 훈련 데이터로 하고, 또 어느 정도를 테스트 데이터로 할지는 결정하기 쉽지 않다. 만약 훈련 데이터의 크기를 너무 작게 한다면 모델링에 사용할 데이터가 적어 실제 도달 가능한 성능보다 낮은 성능의 모델이 만들어질 것이다. 이 경우 테스트 데이터로부터 추정한 성능은 실제보다 낮은 값이 된다. 반대로 훈련 데이터의 크기를 너무 크게 한다면 테스트 데이터의 크기가 작아진다. 이 경우 적은 수의 데이터로만 테스트를 수행하게 되어 계산한 성능의 신뢰도가 낮아진다.

 

훈련 데이터와 테스트 데이터의 분리 방법도 문제가 된다. 만약 우연히 테스트 데이터에서의 성능이 잘 나오도록 훈련 데이터와 테스트 데이터를 분리한다면 적절치 않은 성능 평가가 될 것이다. 반대의 경우도 마찬가지다.

 

이러한 문제들을 개선하는 한 가지 방법이 교차 검증이다. 교차 검증은 데이터를 훈련 데이터와 검증 데이터(Validation Data)로 나누어 모델링 및 평가하는 작업을 K회 반복하는 것으로, 이를 K겹 교차 검증(K-fold Cross Validation)이라 한다. 보통 K 값은 10으로 지정한다. 다음은 10겹 교차 검증(10-fold Cross Validation)의 수행 단계다.

 

  • 데이터를 10등분하여 D1, D2, …, D10으로 분할한다.
  • K 값을 1로 초기화한다.
  • DK를 검증 데이터, 그 외의 데이터를 훈련 데이터로 하여 모델을 생성한다.
  • 검증 데이터 DK를 사용해 모델의 성능을 평가한다. 평가된 모델의 성능을 PK라 한다.
  • K가 9 이하인 값이면 K = K + 1을 하고 3단계로 간다. 만약 K = 10이면 종료한다.

예를 들어, K=1이라면 D1이 검증 데이터, D2, D3, …, D10이 훈련 데이터로 사용된다. 만약 K=2라면 D2가 검증 데이터, D1, D3, D4, …, D10이 훈련 데이터로 사용된다. 이처럼 각 K마다 DK를 검증 데이터로, 나머지를 훈련 데이터로 사용한다. 최종적으로 K=10일 경우 D10이 검증 데이터, D1, D2, D3, …, D9가 훈련 데이터로 사용된다.

 

이 단계를 거치면 모델의 성능이 P1, P2, …, P10으로 구해지며, 최종 성능은 이들의 산술 평균으로 정할 수 있다.

 

그림 9-26에 8겹 교차 검증의 예를 보였다. 그림으로부터 K=4, 5, 6, 7, 8일 때의 검증 데이터 역시 쉽게 짐작할 수 있을 것이다.

그림 9-26

경우에 따라서는 평가의 정확도를 더 높이기 위해 K겹 교차 검증을 R회 반복할 수 있다. 예를 들면, 10겹 교차 검증을 3회 반복하는 식이다.

 

교차 검증을 통해 최선의 모델을 만드는 방법을 결정하고 나면 전체 데이터를 사용해 모델을 만든 뒤 이 모델을 결과로 제출한다.

 

 

교차 검증 후 테스트

교차 검증도 검증 데이터를 반복 사용한다는 문제가 있다. K겹 교차 검증 결과를 들여다보면서 모델링 방법을 개선해나가는 과정을 반복하다 보면 결국 검증 데이터 역시 또 다른 훈련 데이터에 지나지 않게 된다. 그러므로 검증 데이터를 사용해 계산한 성능(예를 들면, Accuracy 등)은 실제 모델의 성능과는 다르게 (보통은 더 좋게) 나올 가능성이 있다. 따라서 테스트 데이터 단계를 추가로 두게 된다.

 

테스트 데이터는 모델링이 별 문제없이 진행되었는지를 검토하는 목적으로도 사용한다. 만약 교차 검증 데이터로부터 예상한 성능과 테스트 데이터로부터 계산한 성능의 차이가 너무 크다면 모델링 코드에 버그가 있음을 짐작할 수 있다.

 

테스트 데이터는 원 데이터로부터 최초 분리 후 모델링 과정에서 이용되지 않다가 최종적으로 모델을 확정지었을 때 해당 모델의 성능을 평가하는 목적으로 단 한차례만 이용된다. 테스트 데이터를 사용한 모델링 단계는 다음과 같다.

 

  • 데이터를 훈련 데이터와 테스트 데이터로 분리한다.
  • 훈련 데이터에 대해 K겹 교차 검증을 수행하여 어떤 모델링 기법이 가장 우수한지를 결정한다.
  • 해당 모델링 기법으로 훈련 데이터 전체를 사용해 최종 모델을 만든다.
  • 테스트 데이터에 최종 모델을 적용해 성능을 평가하고, 그 결과를 최종 모델과 함께 제출한다.

 

cvTools::cvFolds()

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

 

-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")
)

 

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

> library(cvTools)
> library(cvTools)
> cvFolds(10,K=5,type="random")

5-fold CV:    
Fold   Index
   1       4
   2       7
   3       1
   4       2
   5       3
   1      10
   2       5
   3       8
   4       9
   5       6

 

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      85  91  24
  2      71  68   6
  3      80  78  85
  4     126 150  38
  5      11 109   1
  6     135  66  88
  7      16  46  11
  8     144  69  95
  9      24  79 150
 ...
  6      32   4  21
  7     100 120  46
  8      57  65   4
  9      34  12 107
  10    107  75 121

 

위 결과에서 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,]   85   91   24
[2,]   71   68    6
[3,]   80   78   85
[4,]  126  150   38
[5,]   11  109    1
[6,]  135   66   88

 

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

> (validation_idx <- cv$subset[which(cv$which == 1), 1])
 [1]  85  15  37   7 114 111  27  72 146 142  50  74 129 106  76

 

첫 번째 반복 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를 지정하여 결과가 리스트가 아닌 벡터로 되게 했다. 이렇게 하면 결과가 리스트의 리스트가 아니라 벡터의 리스트가 되어 조작이 용이하다.

 

 

caret::createDataPartition(), createFolds(), createMultiFolds()

cvTools를 사용한 교차 검증은 데이터의 속성에 대한 고려 없이 무작위로 데이터를 나눈다. 그러나 좋은 모델 성능 평가가 되려면 예측하고자 하는 분류(Y)에 대한 고려가 필요하다. 예를 들어, 검증 데이터의 Species에 setosa는 너무 많고 versicolor, virginica는 너무 적다면 그 평가가 공정하지 않을 것이기 때문이다.

 

caret의 createDataPartition( ), createResample( ), createFolds( ), createMultiFolds( ), createTimeSlices( )는 Y 값을 고려한 훈련 데이터와 테스트 데이터의 분리를 지원하며, 이들 함수를 사용해 분리한 데이터는 Y 값의 비율이 원본 데이터와 같게 유지된다.

 

createDataPartition( )은 데이터를 훈련 데이터와 테스트 데이터로 분할한다. 부트스트래핑 을 사용한 샘플링은 createResample( )에서 지원된다. 교차 검증을 원한다면 createFolds( ), creaetMultiFolds( )를 사용한다. 가장 기본이 되는 createDataParitition( )과 교차 검증에 대해 살펴보자.

 

-caret::createDataPartition : 데이터를 훈련 데이터와 테스트 데이터로 분할한다.

caret::createDataPartition(
  y,          # 분류(또는 레이블)
  times=1,    # 생성할 분할의 수
  p=0.5,      # 훈련 데이터에서 사용할 데이터의 비율
  list=TRUE,  # 결과를 리스트로 반환할지 여부. FALSE면 행렬을 반환한다.
)

 

-caret::createFolds : 데이터를 K겹 교차 검증으로 분할한다.

caret::createFolds(
  y,
  k=10,  # K 겹 교차 검증
  list=TRUE,
  # 훈련 데이터 색인을 반환할지 여부. FALSE면 검증 데이터 색인을 반환한다.
  returnTrain=FALSE
)

 

-caret::createMultiFolds : 데이터를 K겹 교차 검증의 times 반복으로 분할한다.

caret::createMultiFolds(
  y,
  k=10,
  times=5  # 반복 횟수
)

 

 

다음은 createDataPartition( )을 사용해 아이리스 데이터의 80%를 훈련 데이터, 나머지 20%를 검증 데이터로 분리한 예다.

> library(caret)
> (parts<-createDataPartition(iris$Species,p=0.8))
$Resample1
  [1]   1   2   3   4   6   7   8   9  10  11  12  13  14  15  16  18  20
 [18]  22  23  24  25  26  27  30  31  35  36  37  38  39  40  41  42  44
 [35]  45  46  47  48  49  50  51  52  53  56  57  58  59  60  61  62  63
 [52]  64  65  68  69  70  72  73  74  76  78  79  81  82  83  84  85  86
 [69]  87  88  89  90  92  93  94  95  96  97  98 100 101 102 103 104 105
 [86] 106 107 108 109 110 111 112 113 114 115 116 119 122 124 125 126 127
[103] 128 130 131 132 133 134 135 136 138 139 140 141 142 143 146 148 149
[120] 150

> table(iris[parts$Resample1, "Species"])

    setosa versicolor  virginica 
        40         40         40 

 

예에서 createDataPartition( )은 Species를 고려하여 데이터를 분리하고, 각 Species마다 40개씩을 훈련 데이터로 추출했다. parts$Resample1에 포함되지 않은 행들은 검증 데이터로 사용하면 되며, 다음에서 볼 수 있듯이 검증 데이터에서는 Species마다 10개씩 데이터가 할당된다.

> table(iris[-parts$Resample1, "Species"])

    setosa versicolor  virginica 
        10         10         10 

 

createFolds( )는 K겹 교차 검증을 지원한다. 다음은 아이리스 데이터를 10겹 교차 검증한 예다. 리스트의 각 요소 1, 2, … 등에는 검증 데이터로 사용할 데이터의 색인이 저장되어 있다.

> createFolds(iris$Species, k=10)
$Fold01
 [1]  19  22  28  30  50  52  72  77  91  93 105 129 137 140 142

$Fold02
 [1]  15  20  29  43  45  61  67  71  87  88 101 123 125 132 134

$Fold03
 [1]   4   5   9  12  26  55  57  58  60  63 103 112 135 141 147

$Fold04
 [1]  21  31  40  48  49  56  69  78  83  96 113 122 126 133 149

$Fold05
 [1]   2   3  14  23  37  53  59  73  90  99 109 111 121 128 146

$Fold06
 [1]   7  18  35  42  46  51  64  70  82  97 114 117 124 127 148

$Fold07
 [1]  10  11  17  32  47  66  75  76  79 100 102 120 139 143 145

$Fold08
 [1]   1   6  27  33  36  54  65  74  80  84 107 108 116 119 150

$Fold09
 [1]   8  13  25  38  44  68  81  86  89  95 104 106 115 130 138

$Fold10
 [1]  16  24  34  39  41  62  85  92  94  98 110 118 131 136 144

 

createMultiFolds( )는 K겹 교차 검증의 times회 반복을 지원한다. 예를 들어, 다음은 아이리스에 대한 10겹 교차 검증의 3회 반복이다.

> createMultiFolds(iris$Species, k=10, times=3)
$Fold01.Rep1
  [1]   1   2   3   4   5   6   7   8   9  10  11  12  13  14  17  18
 [17]  19  21  22  23  24  25  26  27  28  29  31  32  33  34  35  36
 [33]  37  38  39  41  42  43  44  45  46  47  48  49  50  51  52  54
 [49]  56  57  58  59  60  61  62  64  65  66  67  69  70  71  72  73
 [65]  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89
 [81]  90  91  92  93  95  96  97  98  99 100 101 102 103 104 105 106
 [97] 107 108 109 110 111 113 114 115 116 117 118 119 120 121 124 125
[113] 126 127 128 129 130 131 132 133 134 135 136 137 139 140 141 142
[129] 143 144 145 146 147 148 149

$Fold02.Rep1
  [1]   2   3   4   5  6   7    8   9  10  11  12  13  15  16  17  18
 [17]  19  20  21  22  23  25  26  27  28  29  30  31  32  33  35  36
 [33]  37  38  39  40  41  42  43  44  45  46  47  48  50  51  52  53
 [49]  54  55  56  57  58  59  60  61  63  64  65  66  68  69  71  72
 [65]  73  74  75  76  77  78  79  80  82  83  84  85  86  87  88  90
 [81]  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 107
 [97] 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
[113] 125 127 128 129 130 131 132 134 135 136 137 138 139 141 142 143
[129] 144 145 146 147 148 149 150

...

$Fold10.Rep3
  [1]   1   2   3   4   5   6   9  10  11  12  13  14  15  16  17  18
 [17]  19  20  21  23  24  25  26  27  28  29  30  31  32  33  34  36
 [33]  37  38  39  40  41  42  44  45  46  47  48  49  50  51  52  53
 [49]  54  55  57  58  60  61  62  63  64  65  66  67  68  69  71  72
 [65]  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88
 [81]  89  91  92  93  94  95  96  98  99 100 102 103 104 105 106 107
 [97] 108 109 110 111 112 113 114 115 116 117 118 120 121 122 123 124
[113] 125 126 127 128 130 132 134 135 136 137 138 139 140 141 142 143
[129] 144 145 146 147 148 149 150

 

리스트의 각 요소는 ‘Fold 번호.Rep 번호’ 형태로 이름이 붙어 있으며 훈련 데이터로 사용할 데이터의 색인이 저장되어 있다. 실제 사용할 때는 리스트의 각 셀에 부여된 이름과 무관하게 형태로 색인을 가져와서 훈련 데이터와 검증 데이터를 분리하면 된다.

k <- 10
times <- 3
set.seed(137)
cv <- createMultiFolds(iris$Species, k, times)

for (i in 1:times) {
  for (j in 1:k) {
    train_idx <- cvNaN
    iris.train <- iris[train_idx, ]
    iris.validation <- iris[-train_idx, ]
    # 모델링 수행
    ...
    # 평가
    ...
  }
}

 

R을 이용한 데이터 처리&분석 실무 中

반응형

'R > R을 이용한 데이터 처리&분석 실무' 카테고리의 다른 글

다항 로지스틱 회귀 분석  (0) 2020.02.12
로지스틱 회귀 모델  (0) 2020.02.12
모델 평가 방법  (0) 2020.02.11
전처리-2  (0) 2020.02.10
전처리-1  (0) 2020.02.09