이번 시간에는 교차검증에 대하여
다루어보도록 하겠습니다. 교차검증은 ML에서 굉장히 중요한
개념이고 실제로 모델을 학습할 때
매번 반복적으로 수행해야되는 것이기 때문에
최대한 이해하기 쉽게 자세히 정리해보도록 하겠습니다.
이는 '파이썬 머신러닝 완벽가이드'를 정리한 내용입니다.
1. 교차 검증의 필요성
교차검증은 매우 중요한 개념이라고 여러번 강조했습니다. 그럼 교차검증이 무엇이고 왜 그렇게 중요한 것인가 이야기해보도록 하겠습니다.
먼저 교차검증이란 학습데이터를 학습데이터 세트와 검증데이터 세트로 분리하여 다양한 학습과 평가를 반복적으로 실시하는 것을 말합니다.
그럼 왜 이런 교차검증을 사용하는 것일까요? 바로 과적합 때문입니다. 과적합이란 모델이 학습데이터에만 과도하게 학습되어 실제 다른 데이터를 예측할때 성능이 과도하게 떨어지는 것을 말합니다. 즉, 모델이 학습데이터의 값을 달달 외워버려서 조금만 문제가 바뀌더라도 풀지 못하는 상태가 되는 것입니다.
하지만 여기서 한가지 의문점이 들것입니다. 그냥 학습 데이터만 반복적으로 바꾸주면 되는 것이지 왜 검증데이터가 필요한거지?
그 이유는 바로 고정된 학습 데이터와 테스트 데이터를 평가하다보면 테스트 데이터에만 최적의 성능을 발휘하는 문제가 생기게 됩니다. 이를 해결하기 위해 여러번의 모의고사를 지속적으로 수행하는 검증데이터가 필요한 것입니다.
2. K폴드 교차 검증
그럼 이제 본격적으로 어떻게 교차검증을 수행하는지 가장 보편적인 교차검증인 K 폴드 교차 검증에 대하여 배워보겠습니다. K 폴드 교차 검증이란, K개의 데이터 폴드 세트를 만들어서 K번만큼 각 폴트 세트에 학습과 검증 평가를 반복적으로 수행하는 방법입니다.
앞서 실습한 붓꽃 데이터를 K폴드 교차검증을 통해 실습해보도록 하겠습니다.
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
KFold() 메스드를 사용하여 데이터를 5개의 데이터 세트로 분류하겠다고 선언합니다.
#데이터 생성 및 모델 정의
iris=load_iris()
features=iris.data
label=iris.target
dt_clf=DecisionTreeClassifier(random_state=156)
# 5개의 KFold 객체 생성
kfold = KFold(n_splits=5)
cv_accuracy=[]
이후 kfold.split()를 통해 train 데이터와 검증데이터를 분류합니다. kfold.split()는 train data 와 test data의 인덱스를 array 형태로 반환합니다. 그 예시는 아래와 같습니다. 각 해당하는 행의 데이터가 학습데이터, 검증 데이터가 되는 것입니다.
학습 데이터의 인덱스: [ 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 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 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 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]
n_iter =0
# KFold 교차검증을 통한 학습 진행
for train_index, test_index in kfold.split(features):
# kfold.split()으로 반환된 인덱스를 이용하여 학습용, 검증용 테스트 데이터 추출
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)
n_iter += 1
# 매 반복에서 정확도 측정
accuracy = np.round(accuracy_score(y_test,pred), 4)
cv_accuracy.append(accuracy)
# 개별 iteration별 정확도를 합하여 평균 정확도 계산
print('\n## 평균 검증 정확도:', np.mean(cv_accuracy))
평균 검증 정확도: 0.9
이렇게 학습 데이터와 검증 데이터를 바꿔가며 학습과 평가를 여러번 수행합니다.
3. Stratified K 폴드
Stratified K 폴드는 불균형한 분포도를 가지는 레이블 데이터 집합을 위한 k폴드 방식입니다. 데이터 분포는 학습에서 매우 중요합니다.
예를 들어 신용카드 결제의 오류 발생률이라는 데이터가 있다고 가정해봅시다. 그런데 이때 실제로는 오류 발생률이 0.0001%에 불과한데 K폴드로 데이터를 나누는 과정에서 어떤 데이터셋에는 유독 오류 데이터가 많이 분포하여 해당 데이터의 오류 발생률은 0.1%가 될 수 있습니다. 이러한 경우 본래 의도와 다른 방향으로 학습이 이루어질 수 있습니다. 이러한 상황을 방지하기 위해 Stratified K 폴드를 사용합니다.
그럼 붓꽃 데이터에 대한 아래 코드를 통해 불균형한 분포가 무엇인지 살펴봅시다!
kfold=KFold(n_splits=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())
print('\n')
## 교차 검증: 1
학습 레이블 데이터 분포:
1 50
2 50
Name: label, dtype: int64
검증 레이블 데이터 분포:
0 50
Name: label, dtype: int64
## 교차 검증: 2
학습 레이블 데이터 분포:
0 50
2 50
Name: label, dtype: int64
검증 레이블 데이터 분포:
1 50
Name: label, dtype: int64
## 교차 검증: 3
학습 레이블 데이터 분포:
0 50
1 50
Name: label, dtype: int64
검증 레이블 데이터 분포:
2 50
Name: label, dtype: int64
붓꽃 데이터를 3개의 폴드로 분리한 위 결과값을 보면 첫번째 폴드는 0,1에 대해서만 학습을 하고 2에 대해서 검증을 하는 것을 볼 수있습니다. 또한 나머지 데이터셋을 보면 모두 서로 다른 두가지의 레이블에 대해서만 학습을 하고 이와 무관한 나머지 하나의 레이블에 대하여 평가를 진행하는 것을 볼 수 있습니다. 이제 Stratified K 폴드를 수행한 아래 코드의 결과를 살펴봅시다.
from sklearn.model_selection import StratifiedKFold
skf=StratifiedKFold(n_splits=3)
n_iter =0
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())
여기서 주의할 점은 Stratified K 폴드는 레이블의 분포도를 고려하여 데이터 폴드를 나누기 때문에 .split()시 반드시 레이블 값을 인자로 넣어줘야합니다!!!
## 교차 검증: 1
학습 레이블 데이터 분포:
2 34
0 33
1 33
Name: label, dtype: int64
검증 레이블 데이터 분포:
0 17
1 17
2 16
Name: label, dtype: int64
## 교차 검증: 2
학습 레이블 데이터 분포:
1 34
0 33
2 33
Name: label, dtype: int64
검증 레이블 데이터 분포:
0 17
2 17
1 16
Name: label, dtype: int64
## 교차 검증: 3
학습 레이블 데이터 분포:
0 34
1 33
2 33
Name: label, dtype: int64
검증 레이블 데이터 분포:
1 17
2 17
0 16
Name: label, dtype: int64
위의 결과값을 보면 0,1,2에 대하여 균일하게 학습을 하고 테스트를 진행하는 것을 볼 수 있습니다. 이렇듯 Stratified K 폴드는 레이블 데이터 집합이 균형있는 분포를 가지게 해줍니다. 그럼 이제 Stratified K 폴드를 이용하여 붓꽃 데이터를 다시한번 예측해보도록 하겠습니다.
dt_clf = DecisionTreeClassifier(random_state=156)
skfold = StratifiedKFold(n_splits=3)
n_iter=0
cv_accuracy=[]
# StratifiedKFold의 split( ) 호출시 반드시 레이블 데이터 셋도 추가 입력 필요
for train_index, test_index in skfold.split(features, label):
# split( )으로 반환된 인덱스를 이용하여 학습용, 검증용 테스트 데이터 추출
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)
# 반복 시 마다 정확도 측정
n_iter += 1
accuracy = np.round(accuracy_score(y_test,pred), 4)
cv_accuracy.append(accuracy)
# 교차 검증별 정확도 및 평균 정확도 계산
print('\n## 교차 검증별 정확도:', np.round(cv_accuracy, 4))
print('## 평균 검증 정확도:', np.mean(cv_accuracy))
교차 검증별 정확도: [0.98 0.94 0.98]
평균 검증 정확도: 0.9666666666666667
4. cross_val_score()
지금까지 교차검증을 수행한 과정을 살펴 보면 폴드 세트 설정, 학습/ 검증 데이터 추출, 반복적인 학습과 예측을 통해 예측 성능 반환으로 이루어졌습니다. 이러한 어떻게 보면 복잡한 과정을 한번에 실시해주는 아주 편리한 API가 있는데 바로 cross_val_score()입니다. 아래 예제를 통해 cross_val_score()에 대하여 설명해보도록 하겠습니다.
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()
dt_clf = DecisionTreeClassifier(random_state=156)
data=iris_data.data
label=iris_data.target
scores = cross_val_score(dt_clf, data, label, scoring='accuracy',cv=3)
print('교차 검증별 정확도:',np.round(scores, 4))
print('평균 검증 정확도:', np.round(np.mean(scores), 4))
cross_val_score의 주요 인자에 대하여 설명하도록 하겠습니다.
estimator(=dt_clf) : Classifier 알고리즘 또는 Regressor 알고리즘
X(=data) : feature 데이터 세트
y(=label) : 레이블 데이터 세트
scoring(='accuracy') : 예측 성능 평가 지표
cv(=3) : 폴드의 개수
교차 검증별 정확도: [0.98 0.94 0.98]
평균 검증 정확도: 0.9667
cross_val_score는 cv로 지정된 횟수만큼 지정된 평가지표를 반환합니다. 근데 반환된 결과값이 익숙하지 않나요? 바로 이전에 실습한 Stratified K 폴드와 동일한 결과값을 보이는데요. 그 이유는 바로 cross_val_score는 Classifier을 Stratified K 폴드 방식으로 수행하기 때문입니다. 이는 참고사항으로 알고있으면 좋을 것 같습니다.
5. GridSearchCV
이제 교차 검증의 마지막!!! GridSearchCV에 대하여 설명하도록 하겠습니다. GridSearchCV는 교차검증과 최적의 하이퍼파라미터 튜닝을 한번에 진행해주는 API입니다. 여기서 하이퍼 파라미터란, 모델의 예측 성능을 향상 시키기 위해 조정해야할 중요한 인자를 말합니다. 하이퍼파라미터에 대한 자세한 내용은 뒤에서 더 다루도록 하겠습니다. GridSearchCV는 최적의 파라미터를 찾아준다는 편리함이 존재하지만 그만큼 시간이 오래거린다는 단점이 있습니다.
이렇게 설명했을 때 최적의 하이퍼파라미터를 찾는다는 것이 무엇인지, GridSearchCV가 어떻게 수행되는지 이해하기 어렵기 때문에 코드와 결과값을 보면서 설명하도록 하겠습니다. 우리는 하이퍼파라미터로 DecisionTreeClassifier의 중요한 파라미터인 max_depth와 min_samples_split의 값을 변화시키며 최적화를 진행해보겠습니다.
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.metrics import accuracy_score
import pandas as pd
# 학습데이터와 테스트 데이터 분리
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target,
test_size=0.2, random_state=121)
dtree = DecisionTreeClassifier()
# 하이퍼파라미터들을 dictionary 형태로 설정
parameters = {'max_depth':[1, 2, 3], 'min_samples_split':[2,3]}
# 나올 수 있는 하이파라미터의 모든 조합(3X2가지)에 대하여 3개의 fold로 나누어 학습을 진행
grid_dtree=GridSearchCV(dtree, param_grid=parameters, cv=3, refit=True, return_train_score=True)
# 하이퍼 파라미터들을 순차적으로 학습/평가
grid_dtree.fit(X_train, y_train)
# GridSearchCV 결과는 cv_results_ 라는 딕셔너리로 저장됨. 이를 DataFrame으로 변환
scores_df = pd.DataFrame(grid_dtree.cv_results_)
scores_df[['params', 'mean_test_score', 'rank_test_score',
'split0_test_score', 'split1_test_score', 'split2_test_score']]
위의 코드의 결과값을 살펴보면 하이퍼파라미터로 정의한 두 개의 변수(max_depth, min_samples_split)에 대하여 나올 수 나올 수 있는 모든 조합의 예측 결과를 볼 수 있습니다. 이렇듯 GridSearchCV는 정의한 하이퍼파라미터에 대하여 어떤 파라미터가 최적의 성능을 보이는지 찾아줍니다. 위의 DataFrame 칼럼에 대하여 간단하게 설명하겠습니다.
split0_test_score : 첫번째 폴드의 예측결과
split1_test_score : 두번째 폴드의 예측결과
split2_test_score : 세번째 폴드의 예측결과
mean_test_score : 위에서 구한 예측결과의 평균
rank_test_score : 예측결과의 평균을 바탕으로 매긴 순위
GridSearchCV에서 중요한 인자 중 하나는 바로 refit이라는 인자인데 True라고 설정되어 있으면 최적의 성능의 하이퍼 파라미터로 모델을 학습시켜 best_estimator_로 저장합니다.
그럼 이렇게 튜닝한 모델을 이전에 train_test_split()로 분리한 별도의 테스트 세트로 평가를 진행해보도록 하겠습니다. 이렇게 교차검증을 통해 모델을 튜닝하고 별도의 테스트세트로 모델을 평가하는 것이 일반적인 방법입니다.
# GridSearchCV의 refit으로 이미 학습이 된 estimator 반환
estimator = grid_dtree.best_estimator_
# GridSearchCV의 best_estimator_는 이미 최적 하이퍼 파라미터로 학습이 됨
pred = estimator.predict(X_test)
print('테스트 데이터 세트 정확도: {0:.4f}'.format(accuracy_score(y_test,pred)))
테스트 데이터 세트 정확도: 0.9667
이렇게 교차검증에 대한 정리를 마무리하도록 하겠습니다.
이렇게 정리하고 보니 생각보다 내용이 정말 많은 것 같습니다.
다음번에는 데이터 전처리에 대하여 정리해보도록 하겠습니다.
이 책을 완강하는 날까지 화이팅!!!
할 수 있겠지....해야만해.....
사진 출처
'파이썬 머신러닝 완벽가이드' 카테고리의 다른 글
3.2 평가(F1 스코어, ROC와 AUC) (0) | 2021.11.21 |
---|---|
3.1 평가(정밀도/재현율) (0) | 2021.11.21 |
2.4 사이킷런으로 수행하는 타이타닉 생존자 예측 (0) | 2021.11.19 |
2.3 데이터 전처리 (0) | 2021.11.14 |
2.1 첫 번째 머신러닝 만들어 보기 - 붓꽃 품종 예측하기 (0) | 2021.11.13 |