AI 공부 저장소

[파이썬 머신러닝 완벽가이드] Chap02-3. Model Selection 모듈 소개 본문

Artificial Intelligence/[파이썬 머신러닝 완벽가이드]

[파이썬 머신러닝 완벽가이드] Chap02-3. Model Selection 모듈 소개

aiclaudev 2022. 1. 6. 21:45

 

- 본 글은 파이썬 머신러닝 완벽 가이드 (권철민 지음)을 공부하며 내용을 추가한 정리글입니다. 개인 공부를 위해 만들었으며, 만약 문제가 발생할 시 글을 비공개로 전환함을 알립니다.

 

 

 

[1] 학습/테스트 데이터 세트를 분리하자! - train_test_split( )

이에 대해 알아보기 이 전, 왜 학습/테스트 데이터를 분리해야 하는지부터 알아봅시다. 아래 코드를 봐주세요!

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score

iris = load_iris()
data = iris.data
label = iris.target

dt_clf = DecisionTreeClassifier()
dt_clf.fit(data, label)

# 모든 데이터를 통해 예측 수행
pred = dt_clf.predict(data)
print("Accuray : ", accuracy_score(label, pred))

모든 데이터를 통해서 학습시키니, Accuracy가 1.0이네요! 훌륭한 결과라고 할 수 있을까요?

절대 그렇지 않습니다. 공부할 때 특정 20문제만 공부하였는데, 이 20문제가 정확히 본 시험에서 나온 것과 유사한 상황입니다. 즉, Accuracy가 높게 나올 수 밖에 없다는 것이죠. 이러한 경우 객관적인 평가라고 절대 할 수 없습니다. 

 

따라서, 저희는 데이터를 학습용/테스트용 으로 나누어서 학습용 데이터는 기계를 학습시키는 데에 사용하고, 테스트 데이터는 완성된 모델을 평가할 때 사용해야합니다. (학생을 공부시킬 때 주는 문제 자료와, 본 시험에서의 문제를 다르게 내야하는 것이죠!) 

 

 

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

iris = load_iris()
data = iris.data
label = iris.target

#학습용/테스트용 데이터 분리하기
train_X, test_X, train_y, test_y = train_test_split(data, label, test_size = 0.3, random_state = 121)

#모델 학습시키기
dt_clf = DecisionTreeClassifier()
dt_clf.fit(train_X, train_y)

#학습된 모델을 토대로 예측하기
pred = dt_clf.predict(test_X)

#평가하기
print("Accuracy : ", accuracy_score(test_y,pred))

위 코드가 올바른 방식의 코드입니다. train_test_split( )이라는 함수를 통해 학습용/테스트용 데이터셋을 분리한 것을 알 수 있죠!

 

ㆍtrain_test_split( )

위 함수는 tuple형을 반환합니다. 순차적으로 학습용 데이터의 피처 데이터 세트, 테스트용 데이터의 피처 데이터 세트, 학습용 데이터의 레이블 데이터 세트, 테스트용 데이터의 레이블 데이터 세트를 반환합니다.

첫 번째 parameter로 피처 데이터 세트, 두 번째 parameter로 레이블 데이터 세트를 입력합니다. 그리고, 선택적으로 아래의 parameter를 입력합니다.

 

1) test_size : 전체 데이터에서 테스트 데이터 세트 크기를 얼마로 샘플링할 것인가를 결정합니다. default값은 0.25입니다! 즉, 100개의 row가 있는 데이터셋에 적용시킨다면, 학습용 데이터는 75개의 row이고 테스트용 데이터는 25개의 row로 이루어지겠네요.

 

2) train_size : 위와 비슷하게, 학습용 데이터 세트 크기를 얼마로 샘플링할 것인지 결정합니다. 보통 test_size가 사용되기에 자주 사용되지 않습니다.

 

3) shuffle : 데이터를 분리하기 전에 데이터를 미리 섞을지를 결정합니다. default는 True입니다. 데이터를 분산시켜서 좀 더 효율적인 학습 및 테스트 데이터 세트를 만드는 데 사용됩니다.

 

4) random_state : 랜덤의 시드값을 설정하는 부분입니다. 이 parameter의 값을 입력하지 않는다면 train_test_split( )함수를 실행할 때 마다 데이터가 분리되는 결과가 달라질 것이고, parameter값을 입력한다면, 즉 동일한 랜덤 시드값을 입력한다면 데이터가 분리되는 결과가 같아집니다!

 

 

 

[2] 교차 검증이란?

앞서 객관적인 평가를 위해 학습용 데이터와 테스트용 데이터를 split하였습니다. 이렇게만 하면 정확한 모델을 만들어낼 수 있을까요? 그렇지 않습니다! [1]에서 다룬 방법은 효과적이지만, 단점이 있습니다. 바로 과적합(Overfitting)의 위험성이 있다는 것입니다. 과적합(Overfitting)이란 기계가 데이터에 너무 잘 맞아버린다는 의미입니다. 그만큼 학습을 잘했다는 거니까 좋은 것 아니냐고요? 아쉽게도 아닙니다. 예시를 들어보죠.

데이터를 기계에 학습시키고 평가를 해보았더니 Accuracy가 0.5정도 나와버렸습니다. 너무 낮죠? 여러가지 조작을 한 뒤 다시 평가를 합니다. Accuarcy가 0.8이 되었네요! 한번 더 여러 조치를 취합니다. 0.95정도의 훌륭한 Accuracy가 결과로 나왔네요! 이렇게 학습된 기계를 실제 현장에 적용할 수 있냐고 물어보신다면 그렇지 않습니다. 저희는 하나의 Test 데이터셋만 가지고 있죠? 이 한가지의 Test 데이터셋으로만 평가를 진행하고, Accuracy를 높이려하다 보니 저희의 기계는 자연스럽게 우리가 가진 Test 데이터셋을 통한 평가 결과가 좋아지는 방향으로 흘러갑니다. 즉, 일반적인 특성만이 학습되는 것이 아닌, 저희의 Test 데이터셋만이 가지고 있는 Unique한 특성까지 학습되어버릴 수도 있다는 것이죠. 

따라서, 여러 테스트 데이터셋을 확보하는 것이 중요하지만 아쉽게도 이는 어렵습니다. 테스트용으로 사용할 데이터가 많아질수록 학습용으로 사용 할 데이터가 적어지기 때문이죠. 그래서 훌륭한 선배 연구자분들은 교차검증 이라는 방법을 만들어 주셨습니다. 

 

대부분의 ML 모델의 성능 평가는 교차검증을 기반으로 1차 평가를 한 뒤에 최종적으로 테스트 데이터세트에 적용해 평가하는 프로세스입니다. 즉, 테스트 데이터 세트 외에 별도의 검증 데이터 세트를 둬서 최종 평가 이전에 학습된 모델을 다양하게 평가하는 데 사용합니다. 비유를 하자면, 검증 데이터라는 모의고사를 통해 학습을 최대한 시킨 뒤에, 테스트 데이터라는 본 시험으로 평가를 하는 것이죠!

 

이전엔 하나의 데이터를 학습용/테스트용 데이터로 나누었었죠? 이제는 검증용 데이터까지 필요합니다. 최종평가를 위한 테스트 데이터는 일정 수 이상을 확보해놓아야하기 때문에, 검증용 데이터는 학습용 데이터로부터 분리합니다.

 

학습용 데이터 학습용 데이터
검증용 데이터
테스트 데이터 테스트 데이터

                                                                                

 

[2]-1. K 폴드 교차 검증

K 폴드 교차 검증은 가장 보편적으로 사용되는 교차 검증 기법입니다. 먼저 K개의 데이터 폴드 세트를 만들어서 K번만큼 각 폴드 세트에 학습과 검증 평가를 반복적으로 수행하는 방법입니다.

 

5 폴드 교차 검증에 대해 알아봅시다. (즉, K가 5) 먼저 전체 학습용 데이터를 5 등분 한다고 생각하시면 되는데요, 전체 데이터 중 150개의 row를 학습용 데이터로 확보해놓았다고 해봅시다. 그리고 이를 5등분 하면 아래와 같이 나누어지겠네요.

1~30 31~60 61~90 91~120 121~150

 

이 때, 5 등분 중 한 개의 부분 씩 검증 데이터로 선정하고, 나머지 부분은 학습용 데이터로 선정합니다.

검증 학습 학습 학습 학습

 

이후, 검증 데이터를 변경해가며 학습과 검증을 반복합니다.

검증 학습 학습 학습 학습
학습 검증 학습 학습 학습
학습 학습 검증 학습 학습
학습 학습 학습 검증 학습
학습 학습 학습 학습 검증

 

총 5개의 폴드 셋이 만들어 졌네요. (각 행을 하나의 폴드라고 부릅니다.)

교차 검증 최종 평가는, 전체 교차 검증 Accuracy의 평균입니다! 

더보기

교차 검증 최종 Accuracy = Average(교차검증1, 교차검증2, ... , 교차검증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

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

#5개의 폴드 세트로 분리하는 KFold 객체와 폴드 세트별 정확도를 담을 리스트 객체 생성.
kfold = KFold(n_splits=5)
cv_accuracy = []
print('붓꽃 데이터 세트 크기 : ', features.shape[0])

KFold(n_splits=5)로 KFold 객체를 생성했으니. 이제 생성된 KFold 객체의 split( )을 호출해 전체 붓꽃 데이터를 5개의 폴드 데이터 세트로 분리합니다. 붓꽃 데이터 세트 크기가 150이니, 학습용 데이터는 120, 검증용 데이터는 30으로 분할되겠네요! 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

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

#5개의 폴드 세트로 분리하는 KFold 객체와 폴드 세트별 정확도를 담을 리스트 객체 생성.
kfold = KFold(n_splits=5)
cv_accuracy = []
print('붓꽃 데이터 세트 크기 : ', features.shape[0])

n_iter = 0

#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]
    
    #학습 및 예측
    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)
    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.append(accuracy)
    
# 개별 iteration별 정확돌르 합하여 평균 정확도 계산
print('\n## 평균 검증 정확도 : ', np.mean(cv_accuracy))

 

 

 

 

[2]-2. Stratified K 폴드

Stratified K 폴드는 불균형한(imbalanced) 분포도를 가진 레이블 데이터집합을 위한 K 폴드 방식입니다. 불균형한 분포도를 가진 레이블 데이터 집합은 특정 레이블 값이 특이하게 많거나 매우 적어서 값의 분포가 한쪽으로 치우치는 것을 말합니다. 말이 이해가 잘 안가시죠? 실제 코드를 통해 이해해봅시다.

 

import pandas as pd

iris = load_iris()
iris_df = pd.DataFrame(iris.data, columns = iris.feature_names)
iris_df['label'] = iris.target
print("iris_df['label'].value_counts() : \n",iris_df['label'].value_counts())
print('\n')
iris_df['label']

레이블 값은 0, 1, 2값 모두 50개로 동일한 것을 볼 수 있네요. 또한 앞에서부터 50개는 0, 중간은 1, 마지막 50개는 2로 배치되어있음을 확인할 수 있습니다. Imbalanced 분포를 가진 레이블에서의 문제점을 확인하기 위해 K=3인 KFold를 생성하고 교차검증시 데이터 분포를 확인해봅시다.

kfold = KFold(n_splits=3)
features = iris.data
label = iris.target
n_iter = 0

for train_index, test_index in kfold.split(features) :
    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과 2밖에 없습니다. 또한 검증 레이블 데이터는 0밖에 없네요.

1과 2만을 학습시켜주었기 때문에 0의 경우는 전혀 학습하지 못합니다. 반대로 검증 레이블은 0밖에 없으므로 학습 모델은 절대 0을 예측하지 못합니다. 이렇게 교차 검증 데이터 세트를 분할하면 검증 예측 정확도는 0이 될 수 밖에 없습니다. (미적분 Chapter 1-2를 가르쳐놓고서 시험문제로 Chapter3을 내는 느낌이겠네요..) 그렇다면 이 데이터에 대해선 K=3으로 설정 후 교차검증은 하지 못하는 것일까요? 아니면, 레이블 값이 0 1 2 순으로 정렬되어있는 데이터를 조작해주어야하는 것일까요? 

 

StratifiedKFold는 이렇게 KFold로 분할된 레이블 데이터 세트가 전체 레이블 값의 분포도를 반영하지 못하는 문제를 자동적으로 해결해줍니다. 즉, K=3으로 설정하여도 학습용 데이터와 검증용 데이터의 레이블 값이 균형적으로 배정됩니다.

StratifiedKFold를 사용하는 방법은 KFold를 사용하는 방법과 거의 비슷합니다. 단 하나의 큰 차이는 StratifiedKFold는 레이블 데이터 분포도에 따라 학습/검증 데이터를 나누기 때문에 split( )메서드에 인자로 피처 데이터 세트뿐만 아니라 레이블 데이터 세트도 반드시 필요하다는 사실입니다. K=3으로 설정한 StratifiedKFold코드는 아래와 같습니다.

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']) : # iris_df['label']을 활용하여 균형적으로 split한다.
    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')

출력 결과를 보면 학습 레이블과 검증 레이블 데이터 값의 분포도가 동일하게 할당됐음을 알 수 있습니다. 이렇게 분할이 되어야 레이블 값 0, 1, 2를 모두 학습할 수 있고 이에 따라 레이블 값 0, 1, 2에 대해 검증을 진행할 수 있겠죠?

이제 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

iris = load_iris()
features = iris.data
label = iris.target
dt_clf = DecisionTreeClassifier(random_state = 156)
skfold = StratifiedKFold(n_splits = 3)
n_iter = 0
cv_accurac6 = []

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]
    n_iter += 1
    
    #학습 및 예측
    dt_clf.fit(X_train, y_train)
    pred = dt_clf.predict(X_test)
    
    #반복 시마다 정확도 측정
    accuracy = np.round(accuracy_score(y_test, pred), 4)
    cv_accuracy.append(accuracy)
    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('\n#{0} 검증 세트 인덱스 : {1}'.format(n_iter, test_index))
    cv_accuracy.append(accuracy)
    print('\n')
    
# 교차 검증별 정확도 및 평균 정확도 계산
print('\n ## 교차 검증별 정확도 : ', np.round(cv_accuracy), 4)
print('## 평균 검증 정확도 : ', np.mean(cv_accuracy))

Stratified K 폴드의 경우 원본 데이터의 레이블 분포도 특성을 반영한 학습 및 검증 데이터 세트를 만들 수 있으므로 왜곡된 레이블 데이터 세트에서는 반드시 Stratified K 폴드를 이용해 교차 검증해야 합니다.

 

사실, 일반적으로 분류(Classification)에서의 교차 검증은 K 폴드가 아니가 Stratified K 폴드로 분할돼야 합니다.

회귀(Regression) 에서는 Stratified K 폴드가 지원되지 않습니다. 회귀의 결정값은 이산값 형태의 레이블이 아니라 연속된 숫자값이기 때문에 결정값 별로 분포를 정하는 의미가 없기 때문입니다. 

 

 

 

[2]-3. 교차 검증을 더욱 쉽게! cross_val_score( )

사이킷런은 교차 검증을 좀 더 편리하게 수행할 수 있게 해주는 API를 제공합니다. 대표적으로 cross_val_score( )가 있습니다. KFold로 데이터를 학습하고 예측하는 전반적인 과정을 보면 아래와 같습니다.

 

① 폴드 세트를 설정하고 ② for 루프에서 반복으로 학습 및 테스트 데이터의 인덱스를 추출한 뒤 ③ 반복적으로 학습 및 예측을 수행하고 예측 성능 반환

 

cross_val_score( )는 이런 일련의 과정을 한꺼번에 수행해주는 API입니다.

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

위와 같은 cross_val_scores( ) 원형에서 핵심적인 parmeter는 아래와 같습니다.

 

ㆍestimator : 사이킷런의 분류 알고리즘 클래스인 Classifier 또는 회귀 알고리즘 클래스인 Regressor 객체를 입력

ㆍX : Feature 데이터 세트 입력

ㆍy : Label 데이터 세트 입력

ㆍscoring : 예측 성능 평가 지표 입력 (accuracy 등)

ㆍcv : 교차 검증 폴드 수를 입력 (K값 입력)

 

cross_val_score( )는 scoring 파라미터로 지정된 성능 지표 측정값을 배열 형태로 반환합니다. 

또한, estimator가 Classfier라면 Stratified K Fold를 수행하고, Regressor라면 K Fold를 수행합니다! 

 

이제 아래 코드를 통해 사용법을 알아볼까요? 굉장히 간단합니다!

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score

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

#성능 지표는 정확도(Accuracy), 교차 검증 세트는 3개
score = cross_val_score(dt_clf, features, label, scoring = 'accuracy', cv = 3)
print('교차 검증별 정확도 : ', np.round(score, 4))
print('평균 검증 정확도 : ', np.round(np.mean(score), 4))

정말 간단하죠? StratifiedKFold와 같은 객체를 생성하여 for문 안에서 train_index와 test_index로 직접 학습과 평가를 수행하는 등의 과정이 한번에 압축되었네요.

cross_val_score( )는 cv로 지정된 횟수만큼 scoring 파라미터로 지정된 평가 지표로 평가 결과값을 배열로 반환합니다.

 

비슷한 API로 cross_validate( )가 있습니다. cross_val_score( )는 단 하나의 평가 지표만 가능하지만, cross_validate( )는 여러 개의 평가 지표를 반환할 수 있습니다. 또한 학습 데이터에 대한 성능 평가 지표와 수행 시간도 같이 제공합니다. 하지만 보통 cross_val_score( )로 충분하기에 알고만 갑시다! 

 

 

 

[3] 교차 검증과 최적 하이퍼 파라미터 튜닝을 한 번에! - GridSearchCV

하이퍼 파라미터는 머신러닝 알고리즘을 구성하는 주요 구성 요소이며, 이 값을 조정의 알고리즘의 성능을 개선할 수 있습니다! 

사이킷런은 GridSearchCV API를 이용해 Classifier나 Regressor와 같은 알고리즘에 사용되는 하이퍼 파라미터를 순차적으로 입력하면서 편리하게 최적의 파라미터를 도출할 수 있는 방안을 제공합니다. (Grid는 격자라는 뜻이죠? 격자처럼 촘촘하게 파라미터를 입력하면서 테스트하는 방식입니다)

 

grid_parameters = {'max_depth' : [1, 2, 3],
                  'min_samples_split' : [2, 3]}

예를 들어, DecisionTreeClassifier에서 여러 하이퍼 파라미터를 순차적으로 변경하면서 최고 성능을 가지는 파라미터 조합을 찾고자 한다면, 위와 같은 파라미터 집합을 만들고 이를 적용하면서 최적화를 수행할 수 있습니다.

 

순번 max_depth min_samples_split
1 1 2
2 1 3
3 2 2
4 2 3
5 3 2
6 3 3

총 6개의 조합이 나오네요! GridSearchCV는 교차 검증을 기반으로 이 하이퍼 파라미터의 최적 값을 찾게 해줍니다. 즉, 데이터 세트를 cross-validation을 위한 학습/검증 세트로 자동으로 분할한 뒤에 하이퍼 파라미터 그리드에 기술된 모든 파라미터를 순차적으로 적용해 최적의 파라미터를 찾을 수 있게 해줍니다. 하지만 한 가지 문제점이 있죠. 만약 CV=3이라면, 폴딩 세트가 3개가 나올 텐데요. 각 폴딩 세트에 대해 6번 하이퍼 파라미터를 적용해 성능을 평가하니 3*6 = 18회의 학습 및 평가가 이루어집니다. 따라서 계산 시간이 오래걸리겠죠?

 

GridSearchCV 클래스의 생성자로 들어가는 주요 파라미터는 아래와 같습니다.

ㆍestimator : classifier, regressor, pipeline이 사용될 수 있습니다.

ㆍparam_grid : key + 리스트 값을 가지는 딕셔너리가 주어집니다. estimator의 튜닝을 위해 파라미터명과 사용될 여러 파라미터 값을 지정합니다.

ㆍscoring : 예측 성능을 측정할 평가 방법을 지정합니다.

ㆍcv : 교차 검증을 위해 분할되는 학습/검증 세트의 개수를 지정합니다 (K값)

ㆍrefit : 디폴트가 True이며 True로 생성 시 가장 최적의 하이퍼 파라미터를 찾은 뒤 입력된 estimator 객체를 해당 하이퍼 파라미터로 재학습 시킵니다. *주의 : 입력된 estimator 자체를 재학습 시키는 것이 아닌, 이 estimator를 재학습 시킨 것을 GridSearchCV.best_estimator_에 저장합니다. 즉, estimator 인자에 입력한 객체는 학습되지 않습니다.

 

이제 간단한 예제를 통해 GridSearchCV API의 사용법을 익혀봅시다.

train_test_split( )을 이용해 학습 데이터와 테스트 데이터를 먼저 분리하고 학습 데이터에서 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
import pandas as pd

#데이터를 로딩하고 학습 데이터와 테스트 데이터를 분리
iris = load_iris()
features = iris.data
label = iris.target
X_train, X_test, y_train, y_test = train_test_split(features, label, test_size = 0.2, random_state = 121)

dtree = DecisionTreeClassifier()

#하이퍼 파라미터를 딕셔너리형으로 설정
grid_parameters = {'max_depth' : [1, 2, 3],
                  'min_samples_split' : [2, 3]}

#param_grid의 하이퍼 파라미터를 3개의 train, test set fold로 난어 테스트 수행 설정.
##refit=True가 디폴트임. True이면 가장 좋은 파라미터 설정으로 estimator를 재학습 시킨 후 best_estimator_로 저장
grid_dtree = GridSearchCV(dtree, param_grid = grid_parameters, cv = 3, refit = True)

#붓꽃 학습 데이터로 param_grid의 하이퍼 파라미터를 순차적으로 학습/평가
grid_dtree.fit(X_train, y_train)

#GridSearchCV 결과를 추출해 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']]

학습 데이터 세트를 GridSearchCV 객체의 fit 메서드에 인자로 입력합니다. GridSearchCV 객체의 fit 메소드를 수행하면 학습 데이터를 cv에 기술된 폴딩 세트로 분할해 param_grid에 기술된 하이퍼 파라미터를 순차적으로 변경하면서 학습/평가를 수행하고, 그 결과를 cv_results_속성에 기록합니다. cv_results_는 gridsearchcv의 결과 세트로서 딕셔너리 형태로 key값과 리스트 형태의 value를 가집니다. 이를 DataFrame형식으로 바꾸면 더 쉽게 결과를 볼 수 있겠죠?

 

print("GridSearchCV 최적 파라미터 : ", grid_dtree.best_params_)
print("GridSearchCV 최고 정확도 : ", grid_dtree.best_score_)

GridSearchCV 객체의 fit( )을 수행하면 최고 성능을 나타낸 하이퍼 파라미터의 값과 그때의 평과 결과 값이 각각 best_params_, best_score_ 속성에 기록됩니다. 아래에 정리된 것을 보시죠!

 

ㆍGridSearchCV.cv_results_ : GridSearchCV 결과를 저장한다.

ㆍGridSearchCV.best_params_ : 최적의 하이퍼 파라미터가 저장되어있다.

ㆍGridSearchCV.best_score_ : 최적의 하이퍼 파라미터일 때의 성능이 저장되어있다.

ㆍGridSearchCV.best_estimator_ : 최적 성능을 나타내는 하이퍼 파라미터로 학습된 estimator가 저장되어있다.

 

#best_estimator_로 이미 학습된 estimator를 반환
estimator = grid_dtree.best_estimator_

#estimator로 예측 및 성능평가
pred = estimator.predict(X_test)
print('테스트 데이터 세트 정확도 : {}'.format(accuracy_score(y_test, pred)))

일반적으로 학습 데이터를 GridSearchCV를 이용해 최적 하이퍼 파라미터 튜닝을 수행한 뒤에 별도의 테스트 세트에서 이를 평가하는 것이 일반적인 머신러닝 모델 적용 방법입니다.

 

 

 

- 본 글은 파이썬 머신러닝 완벽 가이드 (권철민 지음)을 공부하며 내용을 추가한 정리글입니다. 개인 공부를 위해 만들었으며, 만약 문제가 발생할 시 글을 비공개로 전환함을 알립니다.