본문 바로가기

Data Science/Machine Learning 구현

[ML] Cross validation과 GridSearch하는 방법

반응형

이번 포스팅에서는 머신러닝 모델의 성능을 중간에 평가하는 즉, 해당 모델을 Test 데이터로 검증하기 전에 모델의 성능을 중간점검 하는 하나의 객관적인 방법으로서 교차 검증(Cross validation)을 Scikit-learn 라이브러리를 이용해서 어떻게 해보는지에 대해 다루려고 한다. 그리고 모델링의 가장 최적의 하이퍼파라미터 값을 찾기 위해 일일이 모두 다 해보는 방법인 GridSearch를 코드상에서 어떻게 수행할 수 있는지 소개한다.

 

여기서 소개할 교차 검증방법으로는 일반적으로 사용되는 K-Fold 방법Stratified K-Fold를 소개한다. 그리고 앞의 교차 검증방법을 하나의 함수로 단 한번에 해결할 수 있는 Scikit-learn 라이브러리의 cross_val_score함수에 대해 알아본다. 목차는 다음과 같다.

 

1. 일반적인 K-fold 

2. Stratified K-fold

3. 단 한번에, cross_val_score !

4. GridSearchCV 

 

1. 일반적인 K-fold

일반적인 K-fold 교차 검증 방법은 아래의 그림과 같다. 

 

http://ethen8181.github.io/machine-learning/model_selection/model_selection.html

 

위 그림 처럼 Fold는 파란색 네모를 의미한다. 즉 만약 Fold 개수(K값)를 5로 설정한다면 위 그림 처럼 5번의 모델링 검증을 시행할 것이다. 하지만 1번 검증을 시행할 때마다 검증용 데이터가 각각 파란색으로 매번 달라진다. 그리고 모든 교차검증이 종료되고 즉, 5번의 검증이 종료된 후 각 검증마다 도출된 정확도들의 평균값으로 최종적인 정확도를 계산하게 된다.

 

위의 방법을 Scikit-learn 라이브러리를 이용하는 실습은 아래 코드와 같다. 데이터는 sklearn에 내장되어 있는 Iris 데이터로 진행하였다.

# cross validation
# 일반적인 K-Fold cv
import pandas as pd
import numpy as np
from sklearn.model_selection import KFold

kf = KFold(n_splits=5)
all_acc = []
fold_idx = 0

features = iris.data
labels = iris.target

for train_idx, test_idx in kf.split(features):
    
    train_x, train_y = features[train_idx], labels[train_idx]
    test_x, test_y = features[test_idx], labels[test_idx]
    
    model = DecisionTreeClassifier()
    model.fit(train_x, train_y)
    pred_y = model.predict(test_x)
    acc = accuracy_score(test_y, pred_y)
    
    fold_idx += 1
    
    all_acc.append(acc)

print(f"KFold 모두 수행 후 평균 예측도 : {np.mean(all_acc)}")

출력 결과물은 다음과 같다.

 

출력화면

 

2. Stratified K-fold

다음은 Stratified K-fold 방법이다. 'Stratified'의 사전적 정의는 '층화된' 이라는 의미이다. 사전적인 의미만 봐서는 피부에 와닿게 이해가 되지 않는다. Stratified K-fold 방법은 Train 데이터에서의 label(정답) 분포가 Test 데이터에서의 label(정답) 분포와 유사하도록 맞춰주는 교차검증 방법이다.

 

단적인 예로 원본 데이터의 label의 종류에 ['개', '고양이', '원숭이'] 가 있다고 하자. 모델이 학습하는 Train 데이터에서의 label 종류가 ['개', '고양이'] 밖에 없는 상태로 학습이 진행되었다. 그리고 이제 Test 데이터로 검증을 실시해야 한다. 그런데 Test 데이터로 검증할 때 갑자기 ['원숭이'] 라는 label이 등장한다. 모델은 학습할 때 분명히 '개' 또는 '고양이' 밖에 보지 못했는데 '원숭이'가 등장했을 때 몹시 당황할 것이고 아마 정답을 맞추지 못할 것이다. 매우 극단적인 예시를 들었지만 앞서 설명했던 상황을 방지하기 위해서 Stratified K-fold 방법은 Train, Test 데이터 각각에 ['개', '고양이', '원숭이'] label들을 유사한 비율로 분포하도록 Train, Test 데이터를 나누고 모델 검증을하게 된다.

 

Stratified K-fold는 친절하게도 Scikit-learn에서 제공하고 있으며 코드상에서 구현하는 방법에 대해 알아보자. 데이터는 일반적인 K-fold 때와 마찬가지로 iris 데이터를 사용하였다.

# Stratified Cross validation
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=5)
all_acc = []
fold_idx = 0

features = iris.data
labels = iris.target

for train_idx, test_idx in skf.split(features, labels):
    
    train_x, train_y = features[train_idx], labels[train_idx]
    test_x, test_y = features[test_idx], labels[test_idx]
    
    model = DecisionTreeClassifier()
    model.fit(train_x, train_y)
    pred_y = model.predict(test_x)
    acc = accuracy_score(test_y, pred_y)
    
    fold_idx += 1
    
    all_acc.append(acc)

print(f"KFold 모두 수행 후 평균 예측도 : {np.mean(all_acc)}")

결과물은 다음과 같다. 일반적인 K-fold 때와 비교해보면 예측도가 더 올라갔음을 알 수가 있다.

 

출력화면

3. 단 한번에, cross_val_score !

지금까지 살펴보았던 일반적인 K-fold와 Stratified K-fold를 구현하기 위해서 어느정도 코드를 입력해야 하는 수고(?)가 든다. 하지만 이를 단 하나의 함수로 해결해줄 수 있는 함수가 있다. 바로 cross_val_score 함수이다. 이를 구현하기 위한 코드는 다음과 같다.

# cross_val_score로 집약해서 교차검증
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score, cross_validate
import numpy as np

iris_data = load_iris()
features = iris_data.data
labels = iris.target

model = DecisionTreeClassifier()

scores = cross_val_score(model, features, labels,
                        scoring='accuracy', cv=5)

print(f"한 번씩 검증 때마다 accuracy : {scores}")
print()
print(f"5번 모두 검증한 accuracy 총 평균 : {np.mean(scores)}")

딱 봐도 코드상에서 몇 줄 줄어든 느낌이 느껴진다. 그렇다. cross_val_score 함수를 사용하게 된다면 이전처럼 for 문을 사용하지않은 것이 확 눈에 띈다. 즉 cross_val_score은 인자에 설정한 모델, feature, label을 설정해주고 또 어떤 metric을 사용할지(위 예시에서는 분류 metric인 accruacy를 사용했다.), 교차검증을 몇 번 수행할지까지 설정해준다. 참고로 cross_val_score에서 수행해주는 Cross validation방법은 일반적인 K-fold가 아닌 Stratified K-fold 검증방법을 사용한다는 것을 알아두자.

 

4. GridSearchCV(GridSerach + Cross Validation)

GridSearch란, 모델링시 필요한 하이퍼파라미터를 설정할 때 가장 최적의 파리미터값을 찾아주는 방법 중 하나이다. 그 중에서도 GridSearch방법은 이른바 '무식하게 하나 하나 다 해보기' 이다. 즉, 파라미터 후보값들을 사전에 정의해주고 이를 일일이 for 문 처럼 돌려서 적용해보는 것을 대신해주는 방법이다. 그리고 추가적으로 교차검증까지 수행해준다.  이것 또한 scikit-learn에서 제공해주면 코드로 구현하면 다음과 같다.

# 최고의 Hyperparameter 찾기 위한 GridsearchCV 사용
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_data = load_iris()
train_x, test_x, train_y, test_y = train_test_split(iris_data.data,
                                                   iris_data.target,
                                                   test_size=0.2,
                                                   random_state=45)
model = DecisionTreeClassifier()
# parameter 넣어줄 값들 dict 형태로 정의해주기
h_para = {'max_depth':[1,2,3], 'min_samples_split':[2,3]}

grid_dtree = GridSearchCV(model, param_grid=h_para,
                         cv=5, refit=True, return_train_score=True)
# GridSearchCV 인자설명
# cv = 하나의 파라미터 쌍으로 모델링할 때 train, test 교차검증을 3번실시하겠다는 뜻
# refit=True : GridSearch한 후 가장 최고로 좋은 파라미터로 학습시켜 놓겠다.
# ㄴ> 이것 때문에 애초에 GridSearchCV 적용한 객체만으로 최적의 파라미터 적용된 모델로드 가능

# GridSearch 하면서 모든 파라미터값들에 대해 학습 수행
grid_dtree.fit(train_x, train_y)

# 각 파라미터값들에 대한 모델 결과값들이 cv_results_ 객체에 할당됨
scores_df = pd.DataFrame(grid_dtree.cv_results_)

# score 결과값(ndarray형태로 할당됨) 중 특정 칼럼들만 가져오기 
scores_df[['params', 'mean_test_score', 'rank_test_score', 
           'split0_test_score', 'split1_test_score', 'split2_test_score']]

마지막 결과값인 DataFrame을 출력하면 아래의 그림과 같다. 아래의 DataFrame은 각각의 하이퍼파라미터 조합을 일일이 적용해서 도출된 각 모델링들의 성능을 결과표로 정리한 것이다.(원래 반환해주는 결과값이 n차원의 array 형태라서 직접 pandas를 이용해 DataFrame을 만들어준 것이다.(참고로 파라미터값의 종류 즉, max_depth, min_samples_split인 이유는 위에서 사용하는 모델이 Decision Tree이기 때문이다.)

 

결과 화면

 

이제 위에서 GridSearch를 수행한 여러개의 모델 중 가장 성능이 좋은 모델을 출력하기 위해 다음과 같은 코드를 실행시킨다.

# 최적의 파라미터는 best_params_에 할당되어있음
print(f"최적의 파라미터 : {grid_dtree.best_params_}")
print(f"최적의 파라미터로 모델의 정확도 : {grid_dtree.best_score_}")

print()
print()

# 최적의 파라미터로 학습되어 있는 모델링 할당
estimator = grid_dtree.best_estimator_

# 최적의 모델로 예측해보고 실제값이랑 정확도 비교
pred_y = estimator.predict(test_x)
print(f"실제값과 예측값 정확도 : {accuracy_score(test_y, pred_y)}")

결과화면은 다음과 같다.

 

결과 화면

 

반응형