ML/사이킷런

K폴드 교차 검증이란?

야뤼송 2024. 1. 2. 12:27
반응형

1. 교차 검증의 필요성

머신 러닝 모델을 학습시킬 때 학습을 위한 학습 데이터, 그리고 예측 성능을 평가하기 위한 테스트 데이터가 있다.
그러나 이렇게 위의 방법은 과적합(Overfitting)의 위험이 있다. 
과적합이란, 모델이 학습 데이터를 과도하고 학습하여 실제 데이터에 대해 오차가 증가하는 현상이다. 이로 인해 예측을 다른 데이터로 수행할 경우에는 예측 성능이 떨어지게 되는 것을 말한다.

 

이를 개선하기 위한 방법으로는 교차 검증이 있다.

교차검증은 학습 데이터를 다시 분할하여 학습 데이터와 학습된 모델의 성능을 일차 평가하는 검증데이터로 나눈다. 그리고 테스트 데이트 세트는 모든 학습/검증 과정이 완료된 후 최종적으로 성능을 평가하기 위한 데이터 세트이다.

 

수능을 예시로 들면 수능을 보기 위해 학습하는 것을 학습 데이터 세트이다. 그리고 여러번의 모의고사를 보는 것을 검증 데이터 세트로 이해하면 된다. 그리고 마지막 수능을 보는 것을 테스트 데이트 세트이다.

 

 

2. K폴드 교차 검증

 K번의 학습과 검증 평가를 반복 수행하는 검증이다.  

K=6을 지정하여 교차 검증을 수행한다고 하면 전체 데이터를 6개의 폴드(Fold)로 나누 후 총 6번 동안 각기 다른 폴드를 테스트 세트로 지정한다. 그리고 남은 4개의 폴드를 통해 학습을 진행한다. 6번의 학습과 평가 과정을 거친 후에 6개의 예측 평가를 평균내서 K폴드 결과 값으로 반영한다.

 

 

앞서 진행했던 붓꽃 데이터 세트를 활용하여 교차 검증을 적용한 후 정확도를 확인하면 다음과 같다.

먼저 KFold() 함수를 이용하여 6개의 폴드로 나눈다.

 

붓꽃 데이트 세트의 크기는 150개로 6개로 나누게 되면 학습용 데이터 세트는 125개(5/6 *  150), 검증용 데이터 세트는 25개로 분리가 된다.

 

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold
import numpy as np

iris = load_iris()
features = iris.data
label = iris.target
dt_clf = DecisionTreeClassifier(random_state=156)

# 6개의 폴드 세트로 분리하는 KFold 객체와 폴드 세트별 정확도를 담을 리스트 객체 생성.
kfold = KFold(n_splits=6)
cv_accuracy = []
print('붓꽃 데이터 세트 크기:',features.shape[0])

n_iter = 0

# KFold객체의 split( ) 호출하면 폴드 별 학습용, 검증용 테스트의 로우 인덱스를 array로 반환  
for train_index, test_index  in kfold.split(features): 
    # kfold.split( )으로 반환된 인덱스를 이용하여 
    # 학습용 데이터 세트(X_train)와 X_test(검증용 테스트 데이터)를 추출한다.
    X_train, X_test = features[train_index], features[test_index]
    # 마찬가지로 학습용 타겟값(y_train), 검증용 타겟값(y_test)를 가져온다.
    y_train, y_test = label[train_index], label[test_index]

    
    #학습 및 예측 
    dt_clf.fit(X_train , y_train)    
    pred = dt_clf.predict(X_test)
    n_iter += 1
    
    # 반복 시 마다 정확도 측정 
    accuracy = np.round(accuracy_score(y_test,pred), 4)
    train_size = X_train.shape[0]
    test_size = X_test.shape[0]
    print('\n#{0} 교차 검증 정확도 :{1}, 학습 데이터 크기: {2}, 검증 데이터 크기: {3}'
          .format(n_iter, accuracy, train_size, test_size))
    print('#{0} 검증 세트 인덱스:{1}'.format(n_iter,test_index))
    
    cv_accuracy.append(accuracy)
    
# 개별 iteration별 정확도를 합하여 평균 정확도 계산 
print('\n## 평균 검증 정확도:', np.mean(cv_accuracy))

 

교차 검증을 통해 수행한 정확도는 약 90%로 나타난다.

붓꽃 데이터 세트 크기: 150

#1 교차 검증 정확도 :1.0, 학습 데이터 크기: 125, 검증 데이터 크기: 25
#1 검증 세트 인덱스:[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24]

#2 교차 검증 정확도 :1.0, 학습 데이터 크기: 125, 검증 데이터 크기: 25
#2 검증 세트 인덱스:[25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49]

#3 교차 검증 정확도 :0.84, 학습 데이터 크기: 125, 검증 데이터 크기: 25
#3 검증 세트 인덱스:[50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74]

#4 교차 검증 정확도 :0.92, 학습 데이터 크기: 125, 검증 데이터 크기: 25
#4 검증 세트 인덱스:[75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99]

#5 교차 검증 정확도 :0.88, 학습 데이터 크기: 125, 검증 데이터 크기: 25
#5 검증 세트 인덱스:[100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124]

#6 교차 검증 정확도 :0.8, 학습 데이터 크기: 125, 검증 데이터 크기: 25
#6 검증 세트 인덱스:[125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149]

## 평균 검증 정확도: 0.9066666666666666

 

3. Stratified K폴드 교차 검증

불균형한 분포도를 가진 레이블(결정 클래스) 데이터 집합을 위한 K 폴드 방식이다.

 

예를 들어 2만건의 데이터 중 100건이 사기 데이터가 있다고 가정한다. 사기 데이터는 전체 데이터 중에 0.5%로 매우 작은 비율이다. 일반적인 K폴드 검증을 적용하여 4/5.. 1/5 로 나눠버리면 100건에 대해 제대로 검증이 안되게 된다. 왜냐하면  사기 데이터에 대한 데이터 분포가 한 곳에 몰려 있을 수도 있기 때문이다.

 

Stratified K폴드는 원본 데이터의 레이블 분포를 먼저 고려하고 나서 이 분포와 동일하게 학습 데이터와 검증 데이터 세트를 분배한다.

그리고 나서 학습 데이터와 검증 데이터 세트 가지는 레이블 분포도가 유사하도록 검증 데이터를 추출하는 방식이다.

 

위의   K폴드 예시와 동일하게 붓꽃 데이터를 이용하여 K폴드와 Stratified K폴드의 데이터 비교를 하면 다음과 같다.

1) K 폴드 데이터 분포 및 정확도

from sklearn.datasets import load_iris
import pandas as pd
from sklearn.model_selection import KFold
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
import numpy as np

iris = load_iris()
features = iris.data
label = iris.target
dt_clf = DecisionTreeClassifier(random_state=156)
cv_accuracy = []

iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df['label']=iris.target
iris_df['label'].value_counts()


kfold = KFold(n_splits=3)
# kfold.split(X)는 폴드 세트를 3번 반복할 때마다 달라지는 학습/테스트 용 데이터 로우 인덱스 번호 반환. 
n_iter =0
for train_index, test_index  in kfold.split(iris_df):
    n_iter += 1
    label_train= iris_df['label'].iloc[train_index]
    label_test= iris_df['label'].iloc[test_index]
    print('## 교차 검증: {0}'.format(n_iter))
    print('학습 레이블 데이터 분포:\n', label_train.value_counts())
    print('검증 레이블 데이터 분포:\n', label_test.value_counts())

    X_train, X_test = features[train_index], features[test_index]
    y_train, y_test = label[train_index], label[test_index]
    
    #학습 및 예측 
    dt_clf.fit(X_train , y_train)    
    pred = dt_clf.predict(X_test)
    
    # 반복 시 마다 정확도 측정 
    accuracy = np.round(accuracy_score(y_test,pred), 4)
    train_size = X_train.shape[0]
    test_size = X_test.shape[0]
    print('\n#{0} 교차 검증 정확도 :{1}, 학습 데이터 크기: {2}, 검증 데이터 크기: {3}'
          .format(n_iter, accuracy, train_size, test_size))
    print('#{0} 검증 세트 인덱스:{1}'.format(n_iter,test_index))
    
    cv_accuracy.append(accuracy)

    print('-------------------------------------------------------------')
    
# 개별 iteration별 정확도를 합하여 평균 정확도 계산 
print('\n## 평균 검증 정확도:', np.mean(cv_accuracy))

 

K폴드의 경우 레이블 값의 분포도를 확인한 결과 Setosa, Versicolor, Virgincia 품종 모두 50개로 동일한 값을 가지고 레이블 값이 Setosa, Versicolor, Virgincia을 모두 가지지 않는다.

 

첫번째 교차 검증을 보면 학습 레이블은 1,2만 존재한다. 그렇기 때문에 0은 전혀 학습을 하지 못하게 된다.

검증 레이블의 경우에도 0 값만 존재하기 때문에 1, 2를 학습했다 할지라도 검증 예측 정확도는 0이 될 수밖에 없다.

## 교차 검증: 1
학습 레이블 데이터 분포:
 1    50
2    50
Name: label, dtype: int64
검증 레이블 데이터 분포:
 0    50
Name: label, dtype: int64

#1 교차 검증 정확도 :0.0, 학습 데이터 크기: 100, 검증 데이터 크기: 50
#1 검증 세트 인덱스:[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 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 46 47 48 49]
-------------------------------------------------------------
## 교차 검증: 2
학습 레이블 데이터 분포:
 0    50
2    50
Name: label, dtype: int64
검증 레이블 데이터 분포:
 1    50
Name: label, dtype: int64

#2 교차 검증 정확도 :0.0, 학습 데이터 크기: 100, 검증 데이터 크기: 50
#2 검증 세트 인덱스:[50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99]
-------------------------------------------------------------
## 교차 검증: 3
학습 레이블 데이터 분포:
 0    50
1    50
Name: label, dtype: int64
검증 레이블 데이터 분포:
 2    50
Name: label, dtype: int64

#3 교차 검증 정확도 :0.0, 학습 데이터 크기: 100, 검증 데이터 크기: 50
#3 검증 세트 인덱스:[100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149]
-------------------------------------------------------------

## 평균 검증 정확도: 0.0

 

2) Stratified K폴드

from sklearn.datasets import load_iris
import pandas as pd
from sklearn.model_selection import StratifiedKFold
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
import numpy as np

iris = load_iris()
features = iris.data
label = iris.target
dt_clf = DecisionTreeClassifier(random_state=156)
cv_accuracy = []

skf = StratifiedKFold(n_splits=3)
n_iter=0

iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df['label']=iris.target
iris_df['label'].value_counts()

for train_index, test_index in skf.split(iris_df, iris_df['label']):
    n_iter += 1
    label_train= iris_df['label'].iloc[train_index]
    label_test= iris_df['label'].iloc[test_index]
    print('## 교차 검증: {0}'.format(n_iter))
    print('학습 레이블 데이터 분포:\n', label_train.value_counts())
    print('검증 레이블 데이터 분포:\n', label_test.value_counts())

    X_train, X_test = features[train_index], features[test_index]
    y_train, y_test = label[train_index], label[test_index]
    
    #학습 및 예측 
    dt_clf.fit(X_train , y_train)    
    pred = dt_clf.predict(X_test)
    
    # 반복 시 마다 정확도 측정 
    accuracy = np.round(accuracy_score(y_test,pred), 4)
    train_size = X_train.shape[0]
    test_size = X_test.shape[0]
    print('\n#{0} 교차 검증 정확도 :{1}, 학습 데이터 크기: {2}, 검증 데이터 크기: {3}'
          .format(n_iter, accuracy, train_size, test_size))
    print('#{0} 검증 세트 인덱스:{1}'.format(n_iter,test_index))
    
    cv_accuracy.append(accuracy)

    print('-------------------------------------------------------------')
    
# 개별 iteration별 정확도를 합하여 평균 정확도 계산 
print('\n## 평균 검증 정확도:', np.mean(cv_accuracy))

 

Stratified K폴드의 경우 교차 검증을 수행한 결과 평균 정확도가 약 96%로 나타났다.

위의 K폴드 방식과 다르게 불균형한 분포도를 가진 레이블에서는 원본 데이터의 레이블 분포도 특성을 반영하여 학습 및 검증 데이터 세트를 만들 수 있는 Stratified K폴드 검증 방식을 사용해야 한다.

## 교차 검증: 1
학습 레이블 데이터 분포:
 2    34
0    33
1    33
Name: label, dtype: int64
검증 레이블 데이터 분포:
 0    17
1    17
2    16
Name: label, dtype: int64

#1 교차 검증 정확도 :0.98, 학습 데이터 크기: 100, 검증 데이터 크기: 50
#1 검증 세트 인덱스:[  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115]
-------------------------------------------------------------
## 교차 검증: 2
학습 레이블 데이터 분포:
 1    34
0    33
2    33
Name: label, dtype: int64
검증 레이블 데이터 분포:
 0    17
2    17
1    16
Name: label, dtype: int64

#2 교차 검증 정확도 :0.94, 학습 데이터 크기: 100, 검증 데이터 크기: 50
#2 검증 세트 인덱스:[ 17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  67 68  69  70  71  72  73  74  75  76  77  78  79  80  81  82 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132]
-------------------------------------------------------------
## 교차 검증: 3
학습 레이블 데이터 분포:
 0    34
1    33
2    33
Name: label, dtype: int64
검증 레이블 데이터 분포:
 1    17
2    17
0    16
Name: label, dtype: int64

#3 교차 검증 정확도 :0.98, 학습 데이터 크기: 100, 검증 데이터 크기: 50
#3 검증 세트 인덱스:[ 34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149]
-------------------------------------------------------------

## 평균 검증 정확도: 0.9666666666666667

 

4. cross_val_score()

 

싸이킷런에서는 교차 검증을 보다 간편하게 하는 함수인 cross_val_score함수를 제공한다.

 

기존 교차 검증의 프로세스를 다시 한번 보면 아래와 같이 3단계에 걸쳐 수행하는데 이는 매우 번거로운 작업이다.

  1. 폴드 세트 설정
  2. For 루프에서 반복적으로 학습/검증 데이터 추툴 및 학습과 예측 수행
  3. 폴드 세트별로 예측성능을 평균허ㅏ여 최종 성능 평가

cross_val_score() 함수를 사용하면 위의 3단계를 아주 간단하게 적용할 수 있다. 

 

cross_val_score 함수는 아래와 같은 파라미터를 가지는데 그 중 중요한거 몇개만 살펴보면 다음과 같다.

  • estimator : 분류 알고리즘(Classifier) 또는 회귀 알고리즘(Regressor) Object
  • X : feature 데이터 세트
  • y : label 데이터 세트
  • socring : 예측 성능 평가 지표(다양한 지표 존재. 여기서는 정확도 accuracy 사용)
  • cv : 교차 검증에 사용할 폴드 수

cross_val_score함수를 적용한 코드는 다음과 같다.

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score 
from sklearn.datasets import load_iris
import numpy as np

iris_data = load_iris()
dt_clf = DecisionTreeClassifier(random_state=156)

data = iris_data.data
label = iris_data.target

# 성능 지표는 정확도(accuracy) , 교차 검증 세트는 3개 
scores = cross_val_score(dt_clf , data , label , scoring='accuracy',cv=6)
print(scores, type(scores))
print('교차 검증별 정확도:',np.round(scores, 4))
print('평균 검증 정확도:', np.round(np.mean(scores), 4))

 

위에서 Stratified K폴드를 직접 구현했을 때의 정확도와 일치하는 것을 확인할 수 있다.

[0.96 1.   0.92 0.92 0.88 1.  ] <class 'numpy.ndarray'>
교차 검증별 정확도: [0.96 1.   0.92 0.92 0.88 1.  ]
평균 검증 정확도: 0.9467
반응형