K-겹 교차 검증 (K-fold)를 통한 머신러닝 모델 평가
April 4, 2022, 11:25 p.m.
머신러닝 모델을 구축하는 과정에서 반드시 필요한 과정이 바로 모델을 평가하는 과정입니다. 모델을 만들고 평가한 뒤 그 결과를 바탕으로 다시 모델을 수정하고 구축할 수 있기 때문이죠.
가장 기본적인 방법은 홀드 아웃 방법입니다. 전체 데이터셋을 훈련 셋과 테스트 셋으로 나누어서 훈련 셋으로 모델을 훈련 한 뒤 테스트 셋으로 모델을 테스트 하여 모델의 성능을 측정합니다.
모델을 수정하고 다시 훈련 셋으로 훈련, 테스트 세트로 성능 측정을 반복합니다. 이는 데이터 분리를 통해 일반화 성능을 예측해보려는 시도는 좋지만 반복하다보면 결국에는 전체 데이터셋에 과대적합되는 결과를 가져옵니다.
이번 포스트에서는 이를 개선한 k-겹 교차 검증, K-fold 에 대해서 알아보겠습니다.
1. K-fold란?
K-fold는 전체의 훈련 데이터셋을 k개의 폴드로 랜덤하게 나눈 뒤 그 중에 k-1개의 폴드를 훈련 셋으로, 나머지 1개를 테스트 셋으로 성능을 평가하는 방식입니다. 이 과정을 k번 반복해서 k개의 모델과 성능을 얻죠.
그리고 이것을 평균내어 모델의 성능을 알아냅니다.
이렇게 알아낸 성능을 모델의 하이퍼파라미터를 튜닝하는 기준으로 삼습니다. 즉, 모델을 튜닝할 때 주로 K-fold를 사용하는 것입니다.
파라미터 튜닝이 완료되면 이제 전체 데이터셋으로 최종적으로 모델을 훈련시킵니다.
K-fold는 홀드 아웃 방법에 비해 마지막에 전체 데이터셋으로 훈련을 진행할 수 있다는 것과 어떻게 데이터셋을 나누는지에 대해 영향을 받지 않는다는 장점이 있습니다.
여러 사람들이 경험적으로 보았을 때 K의 값은 10이 적당하다고 합니다. 너무 많으면 점점 모델이 과대적합 되기 때문입니다.
반대로 데이터셋의 양이 적을 경우에는 K의 값을 크게 해아 합니다. 그래야 각 훈련 폴드의 차이가 적어져 안정적인 일반화 성능을 추정할 수 있습니다. 대신에 분산을 높이죠.(과대적합)
데이터 셋의 양이 많을 때에는 k=5같은 10보다 작은 값을 사용해도 무방합니다. 이렇게 하면 일반화성능도 측정하면서 계산양도 줄일 수 있습니다.
2. Stratified K-fold(계층적 교차 검증)
계층적 교차 검증은 K-fold의 향상된 버전입니다. 이 방법은 폴드를 나눌 때 각 폴드간의 클래스 비율을 전체 훈련 세트의 클래스 비율과 일치하게 나눕니다. 이것은 결과적으로 모델이 좀더 나은 편향과 분산을 가지게 합니다.
3. 사이킷런으로 사용하기
사이킷런에는 Stratified K-fold가 구현이 되어 있습니다. 한번 직접 사용해 보겠습니다.
StratifiedKfold().split()은 데이터셋의 폴드를 나누고 이것을 각각의 인덱스가 담긴 튜플로(train, test) 반환합니다. 코드를 먼저 보겠습니다.
먼저 데이터를 가져오고 최종 훈련셋과 최종 테스트 셋으로 먼저 나누어줍니다.
import pandas as pd
df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data',header=None)
from sklearn.model_selection import train_test_split
X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)
그 다음 kfold를 적용합니다.
from sklearn.model_selection import StratifiedKFold
kfold = StratifiedKFold(n_splits=10).split(X_train, y_train)
for (train, test) in kfold:
print(len(train), len(test))
111 13
111 13
111 13
111 13
112 12
112 12
112 12
112 12
112 12
112 12
k가 10인 kfold 객체를 생성하고 폴드로 나누어진 인덱스를 얻었습니다. for문으로 각 인덱스의 길이를 출력해보니 대략 9:1로 잘 나누어진 모습을 볼 수 있겠네요. 계층적 K-fold이기 때문에 각 폴드의 클래스 비율도 동일할 것입니다.
이제 각 폴드로 모델을 훈련하고 평균을 내어 일반화 성능을 추정해야 합니다.
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(solver='liblinear', multi_class='auto',C=100.0, random_state=78)
scores = []
for (train, test) in kfold:
lr.fit(X_train[train],y_train[train])
scores.append(lr.score(X_train[test],y_train[test]))
print(np.mean(scores))
0.9512820512820512
로지스틱 회귀 모델을 만들고 각 폴드별로 총 10번 훈련후 평가를 진행하고 평균을 내었습니다.
하지만 사이킷런에서는 위와 같은 과정을 한번에 해주는 함수를 제공합니다. 바로 cross_val_score 입니다. 이 함수로 다시 평가해볼까요?
from sklearn.model_selection import cross_val_score
scores = cross_val_score(estimator=lr,X=X_train,y=y_train,cv=10)
print(scores)
print(np.mean(scores))
[1. 1. 0.84615385 1. 0.91666667 1.
0.83333333 0.91666667 1. 1. ]
0.9512820512820512
같은 결과를 보여주네요. 여기서 score를 구하는 방법에 유의해야합니다. 세상에는 모델의 정확도를 평가하는 다양한 방법이 있습니다. 여기서는 lr.score라는 함수를 사용했는데 이는 단순한 정확도를 의미합니다. 만약 다른 방법을 사용하고 싶을때는 cross_val_score대신에 cross_validate를 사용하면 됩니다.
from sklearn.model_selection import cross_validate
scores = cross_validate(estimator=lr,X=X_train,y=y_train, scoring=['accuracy'], cv=10, return_train_score=False)
print(scores['test_accuracy'])
print(np.mean(scores['test_accuracy']))
[1. 1. 0.84615385 1. 0.91666667 1.
0.83333333 0.91666667 1. 1. ]
0.9512820512820512
평가하는 다른 방법을 사용하고 싶다면 scoring에 ['accuracy'] 대신에 다른 값을 넣어주세요.
사이킷런 scikit-learn K-fold
kj
안녕하세요!
글 잘 읽었습니다.
궁금한게 있는데요.
k-fold를 했을 시, 평균 검증값만 산출되어 이를 하이퍼파라미터 튜닝에만 이용하는 것인가요?
아니면 k-fold를 통해 평균값으로 구한 새로운 모델이 구축되는 것인가요?
새롭게 구축이 된다면 이것을 다시 train dataset에 적용하여 학습을 다시 시키고
마지막으로 test dataset으로 평가를 하는 것인가요?
Feb. 13, 2023, 4:18 p.m.