- 교차검증
데이터를 단순히 학습 데이터와 테스트 데이터로만 분리하여 머신러닝 모델의 성능을 평가하는 것은 과적합(Overfit)에 취약점을 가질 수 있다. 과적합은 모델이 학습 데이터와 테스트 데이터에만 과도하게 최적화되어, 다른 데이터로 예측을 수행할 때 성능이 떨어지는 것을 말한다. 이러한 문제점을 해결하기 위해 교차 검증을 이용해 다양한 학습과 평가를 수행해야 한다.
교차 검증은 별도의 여러 세트로 구성된 학습 데이터 세트와 검증 데이터 세트에서 학습과 평가를 수행하는 것이다. 학습용 데이터 세트를 다시 학습용 데이터 세트와 검증용 데이터 세트로 분리하여 1차적으로 학습과 평가를 한 후에 테스트 데이터 세트에 적용을 하는 것이다.
- KFold 교차 검증
말 그대로 종이를 접어서 k등분 하듯이, 데이터 세트를 k개의 세트로 나누어 각 세트에 학습과 검증 평가를 반복적으로 수행하는 방법이다. KFold 교차 검증을 수행하는 과정은 다음과 같다.
KFold 클래스의 n_split값에 값을 입력하여 입력한 값만큼 데이터 세트를 분리한다. KFold 객체의 split()을 호출하여 폴드 별 학습용, 검증용 데이터 세트의 행 인덱스를 반환한다. 반환된 인덱스를 이용하여 폴드 별 학습용, 테스트용 세트를 만들어 학습과 예측을 반복하면 된다.
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
# 붓꽃 데이터 세트를 불러와서 피처와 레이블 저장하고, DecisionTreeClassifier 객체 생성
iris = load_iris()
features = iris.data
label = iris.target
tree = DecisionTreeClassifier(random_state=5)
# 5개의 폴드 세트로 분리하는 KFold 객체와 세트별 정확도를 담을 리스트 생성
kfold = KFold(n_splits=5)
k_accuracy= []
n = 1
# KFold 객체의 split()을 호출하면 폴드 별 학습용, 검증용 테스트의 행 인덱스를 array로 반환
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]
# 학습 및 예측
tree.fit(X_train, y_train)
pred = tree.predict(X_test)
# 정확도 측정하여 소수 4번째 자리까지 저장
accuracy = np.round(accuracy_score(y_test, pred), 4)
# 학습 데이터 크기, 검증 데이터 크기 저장
train_size = X_train.shape[0]
test_size = X_test.shape[0]
print('{0}번 교차 검증 정확도: {1}, 학습 데이터 크기: {2}, 테스트 데이터 크기:{3}'
.format(n, accuracy, train_size, test_size))
print('{0}번 검증 데이터 세트 인덱스:{1}\n'.format(n, test_index))
k_accuracy.append(accuracy)
n += 1
print('평균 검증 정확도:', np.mean(k_accuracy))
실행 결과

- Stratified KFold
불균형한 분포도를 가진 레이블 데이터 집합을 위한 KFold 방식이다. 위의 붓꽃 데이터 세트의 경우 총 150개의 데이터를 가지고 있고 레이블 값 3개가 각각 50개씩 분포되어 있다. 3번의 교차검증을 할 때, 위의 방식대로 인덱스를 순서대로 추출하게 될 경우 학습 데이터 분포도와 레이블 데이터 분포도는 다음과 같이 불균형해진다.
kfold = KFold(n_splits=3)
for train_index, test_index in kfold.split(features):
y_train, y_test = pd.Series(label[train_index]), pd.Series(label[test_index])
print('학습 레이블 데이터 분포도:\n', y_train.value_counts())
print('테스트 레이블 데이터 분포도:\n', y_test.value_counts())
실행 결과

이렇게 될 경우 1번과 2번의 레이블만 학습한 상태에서 0번을 예측하는 것은 불가능하다. 따라서 이 상태로 위의 코드 그대로 교차 검증을 수행할 경우 다음과 같이 정확도가 0이 나온다.

Stratified KFold를 이용하면 데이터가 고르게 분포되도록 나눌 수 있다. 단, StratifiedKfold는 레이블 데이터 분포도에 따라 학습/검증 데이터를 나누기 때문에 split() 메서드에 인자로 피처 데이터 세트와 레이블 데이터 세트를 모두 입력해줘야 한다. 다음은 위와 같이 3개의 폴드 세트로 나눈 결과이다.
from sklearn.model_selection import StratifiedKFold
# StratifiedKFold 객체 생성
skf = StratifiedKFold(n_splits=3)
for train_index, test_index in skf.split(features, label):
y_train, y_test = pd.Series(label[train_index]), pd.Series(label[test_index])
print('학습 레이블 데이터 분포도:\n', y_train.value_counts())
print('테스트 레이블 데이터 분포도:\n', y_test.value_counts())
실행 결과

StratifiedKFold의 경우 데이터가 고르게 분포된 것을 확인할 수 있다. 위의 KFold에서 교차검증을 했던 과정 그대로 StratifiedKFold에서 적용하면 다음과 같다.
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import StratifiedKFold
import numpy as np
# 붓꽃 데이터 세트를 불러와서 피처와 레이블 저장하고, DecisionTreeClassifier 객체 생성
iris = load_iris()
features = iris.data
label = iris.target
tree = DecisionTreeClassifier(random_state=5)
# 3개의 폴드 세트로 분리하는 StratifiedKFold 객체와 세트별 정확도를 담을 리스트 생성
skf = StratifiedKFold(n_splits=3)
skf_accuracy= []
n = 1
# StratifiedKFold 객체의 split()을 호출하면 폴드 별 학습용, 검증용 테스트의 로우 인덱스를 array로 반환
for train_index, test_index in skf.split(features, label):
# Stratifiedkfold.split()으로 반환된 인덱스를 이용해 학습용, 검증용 테스트 데이터 추출
X_train, X_test = features[train_index], features[test_index]
y_train, y_test = label[train_index], label[test_index]
# 학습 및 예측
tree.fit(X_train, y_train)
pred = tree.predict(X_test)
# 정확도 측정하여 소수 4번째 자리까지 저장
accuracy = np.round(accuracy_score(y_test, pred), 4)
# 학습 데이터 크기, 검증 데이터 크기 저장
train_size = X_train.shape[0]
test_size = X_test.shape[0]
print('{0}번 교차 검증 정확도: {1}, 학습 데이터 크기: {2}, 테스트 데이터 크기:{3}'
.format(n, accuracy, train_size, test_size))
print('{0}번 검증 데이터 세트 인덱스:{1}\n'.format(n, test_index))
skf_accuracy.append(accuracy)
n += 1
print('평균 검증 정확도:', np.mean(skf_accuracy))
실행 결과

일반적으로, 분류(Classification)에서는 StratifiedKFold를 사용하는 것이 좋다. 그러나 회귀에서는 결정값이 이산형이 아니라 연속형 값이기 때문에 의미가 없으므로 StratifiedKFold가 지원되지 않는다.
- cross_val_score()
위에서 수행했던 KFold와 StratifiedKFold 방식은 '폴드 세트 수 설정, 반복문으로 학습 및 테스트 데이터의 인덱스 추출, 추출된 학습 및 테스트 데이터에 대하여 학습/예측 및 평가'라는 세 단계를 거쳐야 하지만, cross_val_score()는 위의 과정을 한꺼번에 수행해주는 API이다. cross_val_score()의 주요 파라미터는 다음과 같다.
estimator: 예측 모델(객체)를 의미한다.
X: 피처 데이터 세트를 의미한다.
y: 레이블 데이터 세트를 의미한다.
scoring: 예측 성능 평가 지표를 입력한다.(예시: accuracy)
cv: 교차 검증 폴드 수를 의미한다.
cross_val_score()는 분류 모델이 입력되면 StratifiedKFold 방식으로 레이블값의 분포에 따라 학습/테스트 데이터를 분리하고, 회귀인 경우 KFold 방식으로 분할한다. 또한 결과값으로 scoring 값에 지정된 성능 지표 측정값을 배열 형태로 반환한다. 위의 StratifiedKFold 에서 했던 과정처럼 estimator를 DecisionTreeClassifier, 교차 검증 폴드 수를 3, 성능 평가 지표를 accuracy로 수행하면 다음과 같다.
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
import numpy as np
iris = load_iris()
tree = DecisionTreeClassifier(random_state=5)
data = iris.data
label = iris.target
# 성능 지표를 정확도(accuracy), 교차 검증 세트를 3개로 지정
scores = cross_val_score(estimator=tree, X=data, y=label, scoring='accuracy', cv=3)
print('교차 검증별 정확도:', scores)
print('평균 검증 정확도:', np.mean(scores))
실행 결과

cross_val_score()가 내부적으로 StratifiedKFold를 이용하기 때문에 정확도가 위에 StratifiedKFold로 수행한 결과와 일치하는 것을 확인할 수 있다.
- GridSearchCV
교차 검증과 하이퍼 파라미터 튜닝을 동시에 수행할 수 있는 API이다. 하이퍼 파라미터는 머신러닝 알고리즘을 구성하는 주요 구성 요소이며, 이 값을 조정하여 알고리즘의 예측 성능을 개선할 수 있다. GridSearchCV 클래스의 생성자로 들어가는 파라미터는 다음과 같다.
estimator: 예측 모델(객체)를 의미한다.
param_grid: 'key + 리스트' 값을 갖는 딕셔너리가 주어진다. estimator의 튜닝을 위해 파라미터명과 사용될 여러 파라미터 값을 지정할 수 있다.
scoring: 예측 성능 평가 지표를 입력한다.(예시: accuracy)
cv: 교차 검증 폴드 수를 의미한다.
refit: default값이 True이며 이 경우 생성 시 가장 최적의 하이퍼 파라미터를 찾은 뒤 입력된 estimator 객체를 해당 하이퍼 파라미터로 재학습시킨다.
DecisionTreeClassifier의 주요 하이퍼 파라미터에는 max_depth, min_samples_split 값이 있다. 하이퍼 파라미터 명칭과 하이퍼 파라미터의 값을 리스트로 저장한 딕셔너리 형태를 설정하면 순차적으로 파라미터 값을 적용하면서 최적 하이퍼 파라미터를 구할 수 있다.
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split, GridSearchCV
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.2, random_state=9)
tree = DecisionTreeClassifier()
# 파라미터를 딕셔너리 형태로 설정
parameters = {'max depth':[1, 2, 3], 'min_samples_split':[2, 3]}
max_depth에 3가지, min_samples_split에 2가지 값이 설정되어 있으므로 총 6회에 걸쳐 하이퍼 파라미터 값을 변경하면서 교차 검증 데이터 세트에 수행 성능을 측정한다.
import pandas as pd
# cv=3 이므로 6개의 하이퍼 파라미터 경우의 수에 대해 3개의 폴드 세트에 대하여 학습/평가를 하므로 총 18회 진행
grid_tree = GridSearchCV(tree, param_grid=parameters, cv=3)
grid_tree.fit(X_train, y_train)
# GridSearchCV 결과를 DataFrame으로 반환
scores_df = pd.DataFrame(grid_tree.cv.results_)
scores_df[['params', 'mean_test_score', 'rank_test_score',
'split0_test_score', 'split1_test_score', 'split2_test_score']]
실행 결과

DataFrame에서 params는 예측을 수행할 때 적용된 하이퍼 파라미터의 값, mean_test_score는 하이퍼 파라미터 별 각 폴드 세트에서 예측 성능의 평균, rank_test_score는 mean_test_score의 순위, splitn_test_score는 각 폴드 세트에서의 성능이다. cv의 값을 3으로 설정하였기 때문에 3개의 폴드 세트에 대해서만 학습/예측을 진행한 것이다. cv의 값을 4로 하면 split3_test_score열도 생성된다.
GridSearchCV객체에 fit()을 수행하면 최고 성능을 나타낸 하이퍼 파라미터의 값과 그때의 평가 결과 값이 각각 best_params_, best_score_속성에 저장된다.
print('최적 하이퍼 파라미터:', grid_tree.best_params_)
print('최고 정확도:', grid_tree.best_score_)
실행 결과

또한 GridSearchCV의 refit의 default값이 True이므로 GridSearchCV가 최적 성능을 나타내는 하이퍼 파라미터로 Estimator를 학습해 best_estimator_로 저장한다.
from sklearn.metrics import accuracy_score
estimator = tree.best_estimator_
pred = estimator.predict(X_test)
print('테스트 세트 데이터 정확도:', accuracy_score(y_test, pred))
실행 결과

일반적으로 학습 데이터를 GridSearchCV를 이용해 최적 하이퍼 파라미터 튜닝을 수행한 뒤에 별도의 테스트 데이터 세트에서 이를 평가한다.
'머신러닝(MachineLearning) > 사이킷런(scikit-learn)' 카테고리의 다른 글
사이킷런으로 지도학습 수행하기 (0) | 2022.07.22 |
---|