hyeonzzz's Tech Blog

[파머완 2장] 4. Model Selection 모듈 소개 본문

Machine Learning

[파머완 2장] 4. Model Selection 모듈 소개

hyeonzzz 2024. 1. 9. 18:35

2. 사이킷런으로 시작하는 머신러닝 - Model Selection 모듈 소개

model_selection 모듈의 기능

  • 학습 데이터와 테스트 데이터 세트를 분리
  • 교차 검증 분할 및 평가
  • Estimator의 하이퍼 파라미터를 튜닝하기 위한 다양한 함수와 클래스 제공

학습/테스트 데이터 세트 분리 - train_test_split()

테스트 데이터 세트를 이용하지 않고 학습 데이터 세트만 학습하고 예측하면 정확도가 100%이다

모의고사 문제와 똑같은 본고사 문제가 출제된 것이랑 똑같다

따라서 예측을 수행하는 데이터 세트는 학습용 데이터 세트가 아닌 전용 테스트 데이터 세트여야 한다

 

1) 테스트 데이터 세트 30%, random_state=121

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

dt_clf = DecisionTreeClassifier( )
iris_data = load_iris()

X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, 
                                                    test_size=0.3, random_state=121)
  • test_size : 테스트 데이터 세트 크기. 디폴트는 0.25
  • train_size : 학습 데이터 세트 크기. 잘 사용되지 않는다
  • shuffle : 데이터를 미리 섞을지. 디폴트는 True
  • random_state : 호출할 때마다 동일한 학습/테스트용 데이터 세트 생성하도록
  • train_test_split( ) : 반환값은 튜플 형태

2) DecisionTreeClassifier 학습 + 모델을 이용해 예측 정확도를 측정

dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
print('예측 정확도: {0:.4f}'.format(accuracy_score(y_test,pred)))
예측 정확도: 0.9556

 

다양한 데이터를 기반으로 예측 성능을 평가해보는 것이 중요하다!

 


교차 검증

  • 학습 데이터와 테스트 데이터만 갖는 것은 Overfitting(과적합)에 취약한 약점을 가질 수 있다
  • 과적합 : 모델이 학습 데이터에만 과도하게 최적화되어, 실제 예측을 다른 데이터로 수행할 경우에는 예측 성능이 과도하게 떨어지는 것
  • 과적합을 교차 검증을 통해 해결할 수 있다!
  • 교차 검증은 본고사를 치르기 전에 모의고사를 여러 번 보는 것
  • 많은 학습과 검증 세트에서 알고리즘 학습과 평가를 수행하는 것
  • 각 세트에서 수행한 평가 결과에 따라 하이퍼 파라미터 튜닝 등의 모델 최적화를 손쉽게 할 수 있다
  • 교차 검증 기반으로 1차 평가 → 테스트 데이터 세트에 적용해 평가
  • 학습 /검증/ 테스트 데이터 세트로 나눈다

1) K 폴드

K개의 데이터 폴드 세트를 만들어서 K번만큼 각 폴트 세트에 학습과 검증 평가를 반복적으로 수행하는 방법

 

K=5일 때

  1. 데이터 세트를 5등분한다
  2. 처음부터 4개 등분을 학습 데이터 세트, 마지막 5번째 등분을 검증 데이터 세트로 설정하고 학습 데이터 세트에서 학습 수행, 검증 데이터 세트에서 평가를 수행한다
  3. 두 번째 반복에서는 처음부터 3개 등분까지와 5번째 등분을 학습 데이터 세트, 4번째 등분을 검증 데이터 세트로 설정하고 같은 작업을 수행한다
  4. 학습 데이터 세트와 검증 데이터 세트를 점진적으로 변경해서 5개의 예측 평가를 구한다

KFold

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])
붓꽃 데이터 세트 크기: 150

5개의 폴드 세트로 분리

 

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))
#1 교차 검증 정확도 :1.0, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#1 검증 세트 인덱스:[ 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]

#2 교차 검증 정확도 :0.9667, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#2 검증 세트 인덱스:[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]

#3 교차 검증 정확도 :0.8667, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#3 검증 세트 인덱스:[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]

#4 교차 검증 정확도 :0.9333, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#4 검증 세트 인덱스:[ 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]

#5 교차 검증 정확도 :0.7333, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#5 검증 세트 인덱스:[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.9

교차 검증 시마다 검증 세트의 인덱스가 달라짐을 확인할 수 있다

 

2) Stratified K 폴드

불균형한 분포도를 가진 레이블 데이터 집합을 위한 K 폴드 방식

 

예를 들어 대출 사기 데이터에서 대부분의 데이터는 정상 대출이지만 아주 작은 확률로 대출 사기 레이블이 존재한다. 이렇게 작은 비율의 레이블 값이 있다면 비율을 제대로 반영하지 못하는 경우가 쉽게 생긴다

Stratified K-Fold를 사용하면, 각 폴드에서의 클래스 비율이 전체 데이터셋과 유사하게 유지된다

 

KFold가 가진 문제

품종 0, 1, 2가 50개로 동일할 때, 문제를 발생하기 위해 3개의 폴드 세트를 KFold로 생성하고, 각 교차 검증 시마다 생성되는 train/test 레이블 데이터 값의 분포도를 확인해 보겠다

## 교차 검증: 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

첫 번째 교차 검증에서는 학습 레이블로 1, 2 값이 각각 50개 추출되었고, 검증 레이블로 0이 50개 추출되었다. 따라서 0의 경우는 전혀 학습하지 못한다.

 

Stratified KFold 사용하여 해결

split( ) 메서드에 인자로 feature 데이터 세트뿐만 아니라 label 데이터 세트도 반드시 필요하다

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())
## 교차 검증: 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

train 레이블과 test 레이블 데이터 값의 분포도가 거의 동일하게 할당되었다

 

Regression(회귀)에서는 Stratified KFold가 지원되지 않는다

Stratified K-Fold는 클래스 간의 분포를 유지하기 위한 방법이므로, 회귀 문제에서는 클래스가 아닌 연속적인 값이 예측 대상이기 때문에 사용할 수 없다

 


교차 검증을 간편하게 -  cross_val_score( )

classifier가 입력되면 Stratified K 폴드 방식으로 train/test 세트를 분할한다 (회귀는 KFold 방식으로 분할한다)

 

주요 파라미터

  • estimator : 분류 알고리즘 클래스인 Classifier 또는 회귀 알고리즘 클래스인 Regressor 의미
  • X : feature 데이터 세트
  • y : label 데이터 세트
  • scoring : 예측 성능 평가 지표
  • cv : 교차 검증 fold 수
  • 반환값 : scoring 파라미터로 지정된 성능 지표 측정값을 배열 형태로 반환

 

scores = cross_val_score(dt_clf , data , label , scoring='accuracy',cv=3)
  • dt_clf: 교차 검증을 수행할 분류 모델이다. 이 코드에서는 의사결정 트리 분류기(DecisionTreeClassifier)인 dt_clf를 사용하고 있다.
  • data: 모델을 학습시킬 특징 데이터(Feature)
  • label: 모델이 예측할 대상 레이블(Label)
  • scoring='accuracy': 모델의 성능을 측정하는 방법으로 정확도(accuracy)를 사용하겠다는 의미이다. 정확도는 전체 예측 중 올바르게 예측한 비율을 나타낸다.
  • cv=3: 교차 검증을 몇 개의 폴드로 나눌지를 결정하는 매개변수다. 여기서는 3-폴드 교차 검증을 의미한다.

 

cross_validate( )를 사용하면 여러개 평가 지표를 반환할 수 있다

 

 

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

하이퍼파라미터를 순차적으로 입력하면서 최적의 파라미터를 도출할 수 있다

하지만 수행시간이 오래 걸린다

 

### parameter 들을 dictionary 형태로 설정
parameters = {'max_depth':[1,2,3], 'min_samples_split':[2,3]}

max_depth와 min_samples_split 값을 변화시키면서 최적화

 

import pandas as pd

# param_grid의 하이퍼 파라미터들을 3개의 train, test set fold 로 나누어서 테스트 수행 설정.  
### refit=True 가 default 임. True이면 가장 좋은 파라미터 설정으로 재 학습 시킴.  
grid_dtree = GridSearchCV(dtree, param_grid=parameters, cv=3, refit=True)

# 붓꽃 Train 데이터로 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']]

 

  • rank_test_score : 하이퍼 파라미터별로 성능이 좋은 score 순위
  • mean_test_score : CV의 폴딩 테스트 세트에 대해 총 수행한 평가 평균값

 

print('GridSearchCV 최적 파라미터:', grid_dtree.best_params_)
print('GridSearchCV 최고 정확도: {0:.4f}'.format(grid_dtree.best_score_))
GridSearchCV 최적 파라미터: {'max_depth': 3, 'min_samples_split': 2}
GridSearchCV 최고 정확도: 0.9750

 

# 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