본문 바로가기
인공지능/데이터 분석

[사이킷런] 과적합의 문제와 교차 검증 모델(KFold, StratifiedKFold, cross_val_score)

by julysein 2021. 8. 16.
728x90

 

학습 데이터와 테스트 데이터와 분리하여 학습 후 테스트를 수행하는 경우 '과적합'이라는 문제가 발생한다.

 

과적합이란 학습 데이터에만 맞춰서 모델링을 진행하다 보니 특정 데이터에만 맞게 학습이 진행되어 새로운 데이터가 들어왔을 때 그 데이터에 대해서는 제대로 예측을 수행하지 못하는 문제가 발생하는 것을 의미한다.

이를 해결하기 위해 '교차검증'이라는 방법을 통해 학습을 수행한다.

 

교차검증이란 학습 데이터를 다시 학습 데이터와 검증 데이터 세트로 분할하여 학습과 검증을 여러번 진행한 후에 최종적으로 테스트 데이터를 이용해서 정확도를 평가하는 모델이다. 이를 통해 여러 데이터들을 바탕으로 모델링을 함으로서 과적합의 문제를 해결할 수 있다.

 

교차 검증의 가장 대표적인 예로 K 폴드 교차 검증이 있다. 학습 데이터를 k개의 데이터로 나눈 뒤에 k 개의 데이터셋들을 돌아가면서 모두 검증 데이터로 활용한다. 이때 (k-1)/k 는 학습 데이터, 1/k는 검증 데이터로 이용하여 검증하는 과정을 k번 거치는 모델이다.

사이킷 런에서는 이 모델을 KFold 와 StratifiedKFold 클래스를 통해 제공한다.

 

KFold

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
//DecisionTree 객체를 만들어 주고 KFold 를 이용해 5등분된 객체를 생성해준다.
df_clf = DecisionTreeClassifier(random_state=156)
kfold = KFold(n_splits=5)

//n_iter은 KFold의 인덱스 숫자를 나타낼 것이고 cv_accuracy는 세트별 정확도를 담을 리스트이다.
n_iter = 0
cv_accuracy = []

for train_index, test_index in kfold.split(features):
    //train_index에는 테스트할 데이터들의 index 값이 list로 저장되고 test_index에는 검증할 데이터들의 index값이 list 형태로 저장된다.
    //X_train에는 features 중에서 train_index 값의 인덱스들이 X_train에 저장되는 식이다. train_index = [0,1,2]면 X_train에는 [featuers[0],features[1],features[2]]가 저장됨
    X_train, X_test = features[train_index], features[test_index]
    Y_train, Y_test = label[train_index], label[test_index]
    //X_train, Y_train 을 이용해 학습시킨다
    df_clf.fit(X_train, Y_train)
    //검증 데이터를 이용해 예측
    pred = df_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에 저장
    cv_accuracy.append(accuracy)

//정확도 들의 평균으로 정확도 평가
print('\n## 평균 검증 정확도:', np.mean(cv_accuracy))

결과 :

#1 교차 검증 정확도 : 1.0, 학습데이터 크기: 120, 검증데이터 크기: 30
#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]

#2 교차 검증 정확도 : 0.9667, 학습데이터 크기: 120, 검증데이터 크기: 30
#2 검증 세트 인덱스: [30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
 54 55 56 57 58 59]

#3 교차 검증 정확도 : 0.8667, 학습데이터 크기: 120, 검증데이터 크기: 30
#3 검증 세트 인덱스: [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]

#4 교차 검증 정확도 : 0.9333, 학습데이터 크기: 120, 검증데이터 크기: 30
#4 검증 세트 인덱스: [ 90  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107
 108 109 110 111 112 113 114 115 116 117 118 119]

#5 교차 검증 정확도 : 0.7333, 학습데이터 크기: 120, 검증데이터 크기: 30
#5 검증 세트 인덱스: [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.9

 

문제점

불균형한 분포도를 가진 레이블 데이터 집합에 대한 정확도 감소

분류를 해야 하는 데이터셋에서 라벨이 비율이 불균형한 경우에 문제가 발생한다. 라벨에 0,1,2 로 이루어진 데이터가 있다고 할 때 0이 극도로 적거나 극도로 많은 경우에 데이터셋을 랜덤으로 분류해버리면 0이 전체를 차지하거나(극도로 많을 경우) 0이 아예 없는 경우(극도로 적은 경우)가 발생할 수 있다. 0이라는 데이터의 존재가 중요한 경우에 이렇게 되면 0에 대한 정확도가 현저히 떨어질 수 있다. 사기와 같은 경우에 사기를 당하는 경우를 0 당하지 않는 경우를 1이라고 라벨링 하면 0은 극도로 적은 확률에 불과하지만 0이라는 라벨을 판별하는 것이 중요할 수 있다. 이러한 경우에 KFold 클래스는 라벨의 비율을 전혀 고려하지 않고 데이터셋을 나누기 때문에 문제가 발생한다.

 

StratifiedKFold

출처 입력

이러한 KFold의 문제점을 보완하기 위해 이 클래스가 나왔다. 사용법은 KFold와 거의 동일하지만 이 모델을 라벨의 원본 데이터의 비율을 맞춰 데이터들을 분리한다.

차이점

1. StritifiedKfold(n_splits=5) 를 통해 객체를 생성한다.

2. split 함수 사용 시 파라미터로 레이블 도 넣어 준다.

from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import StratifiedKFold
import numpy as np

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

//---------차이점 StratifiedKFold 객체 생성------------
skfold = StratifiedKFold(n_splits=5)

n_iter = 0
cv_accuracy = []

//---------차이점 split함수의 인자로 라벨 데이터도 넣어주어야 한다------------
for train_index, test_index in skfold.split(features, label):
    //train_index에는 테스트할 데이터들의 index 값이 list로 저장되고 test_index에는 검증할 데이터들의 index값이 list 형태로 저장된다.
    //X_train에는 features 중에서 train_index 값의 인덱스들이 X_train에 저장되는 식이다. train_index = [0,1,2]면 X_train에는 [featuers[0],features[1],features[2]]가 저장됨
    X_train, X_test = features[train_index], features[test_index]
    Y_train, Y_test = label[train_index], label[test_index]
    //X_train, Y_train 을 이용해 학습시킨다
    df_clf.fit(X_train, Y_train)
    //검증 데이터를 이용해 예측
    pred = df_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에 저장
    cv_accuracy.append(accuracy)

//정확도 들의 평균으로 정확도 평가
print('\n## 평균 검증 정확도:', np.mean(cv_accuracy))

 

결과 :

#1 교차 검증 정확도 : 0.9667, 학습데이터 크기: 120, 검증데이터 크기: 30
#1 검증 세트 인덱스: [  0   1   2   3   4   5   6   7   8   9  50  51  52  53  54  55  56  57
  58  59 100 101 102 103 104 105 106 107 108 109]

#2 교차 검증 정확도 : 0.9667, 학습데이터 크기: 120, 검증데이터 크기: 30
#2 검증 세트 인덱스: [ 10  11  12  13  14  15  16  17  18  19  60  61  62  63  64  65  66  67
  68  69 110 111 112 113 114 115 116 117 118 119]

#3 교차 검증 정확도 : 0.9, 학습데이터 크기: 120, 검증데이터 크기: 30
#3 검증 세트 인덱스: [ 20  21  22  23  24  25  26  27  28  29  70  71  72  73  74  75  76  77
  78  79 120 121 122 123 124 125 126 127 128 129]

#4 교차 검증 정확도 : 0.9667, 학습데이터 크기: 120, 검증데이터 크기: 30
#4 검증 세트 인덱스: [ 30  31  32  33  34  35  36  37  38  39  80  81  82  83  84  85  86  87
  88  89 130 131 132 133 134 135 136 137 138 139]

#5 교차 검증 정확도 : 1.0, 학습데이터 크기: 120, 검증데이터 크기: 30
#5 검증 세트 인덱스: [ 40  41  42  43  44  45  46  47  48  49  90  91  92  93  94  95  96  97
  98  99 140 141 142 143 144 145 146 147 148 149]

## 평균 검증 정확도: 0.9600200000000001

 

cross_val_score

 

위처럼 KFold 기법을 for 문을 활용하지 않고 한번에 해주는 함수이다.

함수의 형태를 다음과 같다.

cross_val_score(estimator, X, y=None, *, groups=None, scoring=None, cv=None, n_jobs=None, verbose=0, fit_params=None, pre_dispatch='2*n_jobs', error_score=nan)

 

파라미터

1) estimator : Classifier 과 Regression 중 하나로 Classifier 선택 시 StratifiedKFold 클래스가 적용된다.

2) X: fit 함수에 넣을 데이터셋, 즉 feature을 의미한다.

3) y: target vaiable 예측되는 값, 즉 label을 의미한다. (output, default = None)

4) scoring : 예측값을 평가하는 방법에 대한 것이다. default 는 estimator 별로 기본값이 들어간다.

(https://scikit-learn.org/stable/modules/model_evaluation.html#scoring-parameter)

 

ex)Classification

‘accuracy’ metrics.accuracy_score
‘balanced_accuracy’ metrics.balanced_accuracy_score
‘top_k_accuracy’ metrics.top_k_accuracy_score
‘average_precision’ metrics.average_precision_score
‘neg_brier_score’ metrics.brier_score_loss

 

 

ex)Regression

‘explained_variance’ metrics.explained_variance_score
‘max_error’ metrics.max_error
‘neg_mean_absolute_error’ metrics.mean_absolute_error
‘neg_mean_squared_error’ metrics.mean_squared_error
‘neg_root_mean_squared_error’ metrics.mean_squared_error

 

5) cv : 교차 검증할 폴드 수 (default = 5)

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

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

data = iris_data.data
label = iris_data.target

scores = cross_val_score(df_clf, data, label, scoring='accuracy', cv=5)
print('교차 검증별 정확도:', np.round(scores,4))
print('평균 검증 정확도:', np.round(np.mean(scores), 4))

결과 :

교차 검증별 정확도: [0.9667 0.9667 0.9    0.9667 1.    ]
평균 검증 정확도: 0.96

 

 

 

출처 : 파이썬 머신러닝 완벽 가이드

 

728x90