Data Science/Machine Learning

Sklearn 익히기 - train/test data set 분리 및 Cross Validation

HJChung 2020. 9. 28. 16:25

권철민 강사님의 '파이썬 머신러닝 완벽 가이드'을 학습하고 정리한 것입니다. 배우는 중이라 잘못된  내용이 있을 수 있으며 계속해서 보완해 나갈 것입니다. :)) 

Sklearn 소개

- 파이썬 기반의 다른 머신런닝 패키지도 사이킷런 스타일의 API를 지향할 정도로 쉽고 가장 파이썬 스러운 API 를 제공한다. 

- 머신런닝을 위한 매우 다양한 알고리즘과 개발을 위한 편리한 프레임워크와 API 를 제공한다. 

- 주로 Numpy와 Scipy 기반 위에서 구축된 라이브러리이다.

출처: 파이썬 머신러닝 완벽 가이드
출처: 파이썬 머신러닝 완벽 가이드

붓꽃 데이터를 예측하는 문제를 통해 Sklearn의 사용에 대해서 배운 것을 정리해보고자 한다. 

붓꽃 데이터 세트는 sklearn의 내장 예제 데이터셋이며 구성은 이렇게 dictionary 형태로 되어 있다. 

출처: 파이썬 머신러닝 완벽 가이드

데이터 셋을 코드로 살펴보자. 

Sklearn 내장 예제 - 붓꽃 데이터

In [1]:
from sklearn.datasets import load_iris

iris_data = load_iris()
print(type(iris_data))
<class 'sklearn.utils.Bunch'>
In [2]:
keys = iris_data.keys()
print('붓꽃 데이터 세트의 키들:', keys) #dictionary 형태로 되어있다고 했으니까. 
붓꽃 데이터 세트의 키들: dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names', 'filename'])

키는 보통 data, target, target_name, feature_names, DESCR로 구성

개별 키가 가리키는 의미는 다음과 같다.

  • data는 피처의 데이터 세트
  • target은 분류 시 레이블 값, 회귀일 때는 숫자 결괏값 데이터 세트
  • target_names는 개별 레이블의 이름
  • feature_names는 피처의 이름
  • DESCR은 데이터 세트에 대한 설명과 각 피처의 설명
In [3]:
print('\n feature_names 의 type:',type(iris_data.feature_names))
print(' feature_names 의 shape:',len(iris_data.feature_names))
print(iris_data.feature_names)

print('\n target_names 의 type:',type(iris_data.target_names))
print(' feature_names 의 shape:',len(iris_data.target_names))
print(iris_data.target_names)

print('\n data 의 type:',type(iris_data.data))
print(' data 의 shape:',iris_data.data.shape)
print(iris_data['data'][:5])

print('\n target 의 type:',type(iris_data.target))
print(' target 의 shape:',iris_data.target.shape)
print(iris_data.target[:5])
 feature_names 의 type: <class 'list'>
 feature_names 의 shape: 4
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

 target_names 의 type: <class 'numpy.ndarray'>
 feature_names 의 shape: 3
['setosa' 'versicolor' 'virginica']

 data 의 type: <class 'numpy.ndarray'>
 data 의 shape: (150, 4)
[[5.1 3.5 1.4 0.2]
 [4.9 3.  1.4 0.2]
 [4.7 3.2 1.3 0.2]
 [4.6 3.1 1.5 0.2]
 [5.  3.6 1.4 0.2]]
 
 target 의 type: <class 'numpy.ndarray'>
 target 의 shape: (150,)
[0 0 0 0 0]
 

붓꽃 데이터 세트는 꽃잎의 길이와 너비, 꽃받침의 길이와 너비 feature을 기반으로 폼의 품종을 예측하기 위한 것이다. 

정리하면, 

- 지도학습 중 분류

- 독립변수(x): 꽃잎의 길이, 너비, 꽃받침의 길이, 너비

- 종속변수(y): 품종( Setosa, Vesicolor, Virginica)

 

붓꽃 데이터 분류 예측 프로세스는 아래와 같이 이루어 질 수 있다. 

1. 데이터 세트 분리: 데이터를 training data와 test data로 분리

2. 모델 학습: training data를 기반을로 ML 알골리즘을 적용하여 모델을 학습

3. 예측 수행: 학습된 ML 모델을 이용해 test data의 분류를 예측

4. 평가: 이렇게 예측된 결과값과 test data의 실제 결괏값을 비교해 ML 모델 성능을 평가

사이킷런을 사용해서 어떻게 구현 할 수 있는지 알아보자. 

1) train/test data set 분리

데이터를 training data와 test data로 분리

대표적으로 sklearn.model_selection의 train__test_split() 함수를 사용한다. 

앞서 살펴 본 것처럼 iris data의 데이터는 numpy의 ndarray의 형태로 되어있다. 이 경우 아래와 같이 training/test data로 분리 할 수 있다. 

1. numpy의 ndarray의 형태의 training/test data로 분리

X_train, X_test, y_test, y_test = train_test_split(iris__data, iris_data.target, test_sizee=0.3, random_state=121)
# test_size: 전체 데이터에서 test data 세트의 크기. 디폴트는 0.25(25%)
# train_size: 전체 데이터에서 training data 세트의 크기. 
# shuffle: 데이터를 분리하기 전에 미리 섞을지를 결정. 디폴트는 True
# random_state: 호출시마다 동일한 training/테스트용 데이터 세트를 생성하기 위해 주어지는 난수값. 지정하지 않으면 수행시마다 training/테스트 세트가 달라짐

 numpy의 ndarray의 형태 뿐만 아니라 pandas의 DataFrame/Series 형태도  train__test_split() 함수를 통해 training/test data로 분리 할 수 있다. 

2. pandas의 DataFrame/Series 형태의 training/test data로 분리

iris_df = pd.DataFrame(iris_data.data, columns = iris_data.feature_names)
iris_df['target'] = iris_data.target
# iris_df.head()

# featrue 데이터셋과 target 데이터셋을 각각 DataFrame으로 만든다. 
feature_df = iris_df.iloc[:, :-1]
target_df = iris_df.iloc[:, -1]
X_train, X_test, y_train, y_test = train_test_split(feature_df, target_df, test_size=0.3, random_state=121)

2) 교차 검증 (Cross Validation)

※ Overfitting 개선 방법이 될 수 있음

위에선 데이터를 training/test data로 구성하였다. 그러면 모델 성능 평가를 test data로 진행하게 된다. 이렇게 고정된 test data로 모델 성능을 평가하고, 이 데이터에서만 최적의 성능을 발휘 할 수 있도록 파라미터가 최적화되면 결국 '이' test data 에만 성능이 좋은 과적합된 모델이 만들어진다. 

이런 데이터 편증을 막기 위해서 별도의 여러 세트로 구성된 training data 세트와 validation data 세트에서 학습 및 평가를 수행하고, 

test data 세트는 모든 학습 및 검증 과정이 끝난 후 최종적으로 성능을 평가할 때 사용하는 것이 교차 검증이다. 

그래서 아래의 그림과 같은 flow를 가진다. 

출처: https://scikit-learn.org/stable/modules/cross_validation.html

 

1.  교차 검증의 대표적인 방법

① K 폴드 교차 검증

K 폴드 교차 검증은 가장 보편적으로 사용되는 방법이다. Training data를 k개의 데이터 폴드로 분할하고, k번 반복해서 training data와  validation data을 변경해가며(아래의 그림을 보면 이해가 더 쉽다) 로 학습 및 모델 성능 검증 평가를 진행한다. 그리고 이렇게 k 번 검증 평가 결과를 평균하여 k 폴드 평가 결과를 낸다. 

출처: https://scikit-learn.org/stable/modules/cross_validation.html

In [1]:
# 붓꽃 예측을 위해서 필요한 sklearn 모듈 불러오기
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier #DecisionTree모델을 사용할 것임.
#from sklearn.model_selection import train_test_split
from sklearn.model_selection import KFold
from sklearn.metrics import accuracy_score

#그 외 
import pandas as pd
import numpy as np
In [2]:
iris_data = load_iris()
features = iris_data.data
target = iris_data.target
print('붓꽃 데이터 세트 크기:',features.shape[0])
In [3]:
# 5개의 폴드 세트로 분리하는 KFold 객체와 폴드 세트별 정확도를 담을 리스트 객체 생성. # Default는 k=3
kfold = KFold(n_splits=5)

classifier = DecisionTreeClassifier(random_state=156)

n_iter = 0
cv_accuracy = []
# kfold.split(features): KFold객체의 split( ) 호출하면 fold 별 학습용, 검증용 테스트의 로우 인덱스를 array로 반환  
for train_index, val_index in kfold.split(features):
    # 전체 붓꽃 데이터가 모두 150 개이므로 
    #print(train_index, test_index) #이를 출력해보면, 정말 training data 120개, val data 30개의 index가 출력된다. 
    X_train, X_val = features[train_index], features[val_index]
    y_train, y_val = target[train_index], target[val_index]
    
    #교차 검증시(반복문이 한 번 수행 될 때마다)마다 학습과 검증평가를 반복하여 예측 정확도를 측정할 수 있다. 
    classifier.fit(X_train, y_train)
    pred = classifier.predict(X_val)
    n_iter += 1
    
    # 반복 시 마다 정확도 측정 
    accuracy = np.round(accuracy_score(y_val,pred), 4)
    train_size = X_train.shape[0]
    val_size = X_val.shape[0]
    print('{0} 교차 검증 정확도 :{1}, 학습 데이터 크기: {2}, 검증 데이터 크기: {3}'
          .format(n_iter, accuracy, train_size, val_size))
    print('{0} 검증 세트 인덱스:{1}'.format(n_iter,val_index))
    
    cv_accuracy.append(accuracy)
    
# 개별 iteration별 정확도를 합하여 "평균" 정확도 계산 
print('평균 검증 정확도:', 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

② Stratified K 폴드 교차 검증

※ imbalanced한 label 데이터 집합을 위한 K 폴드 방식

 imbalanced한 label 데이터 집합은 특정 label값이 상대적으로 너무 많거나 너무 적어서 값의 분포가 치우쳐 불균형한 것을 말한다. 이런 경우 랜덤하게 training/test data를 나누더라도 (분류 문제라고 치면, ) label이 거의 모두 다 0일 수도 있고, 거의 모두 1일 수도 있고,.. 비율을 제대로 맞출 수 없을 것이다. 

Stratified K 폴드 교차 검증 방법은 전체 데이터의 label분포를 먼저 고려한 뒤, 이 분포와 동일하게 training/ validation data 세트로 분배해준다. 

출처: https://m.blog.naver.com/PostView.nhn?blogId=ckdgus1433&logNo=221599517834&proxyReferer=https:%2F%2Fwww.google.com%2F

Stratified K 폴드 교차 검증 코드)

In [1]:
 # 붓꽃 예측을 위해서 필요한 sklearn 모듈 불러오기
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier #DecisionTree모델을 사용할 것임.
#from sklearn.model_selection import train_test_split
#from sklearn.model_selection import KFold
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import accuracy_score

#그 외 
import pandas as pd
import numpy as np
In [2]:
iris_data = load_iris() 
iris = load_iris()
features = iris.data
target = iris.target
In [3]:
skfold = StratifiedKFold(n_splits=5)

classifier = DecisionTreeClassifier(random_state=156)

n_iter = 0
cv_accuracy = []
# StratifiedKFold의 split( ) 호출시 반드시 label 데이터도 추가 입력 필요 
# 왜냐면 label 데이터의 분포를 보고, 이를 참고해서 나누기 때문
for train_index, val_index in skfold.split(features,target):
    # 전체 붓꽃 데이터가 모두 150 개이므로 
    #print(train_index, test_index) #이를 출력해보면, 정말 training data 120개, val data 30개의 index가 출력된다. 
    X_train, X_val = features[train_index], features[val_index]
    y_train, y_val = target[train_index], target[val_index]
    
    #교차 검증시(반복문이 한 번 수행 될 때마다)마다 학습과 검증평가를 반복하여 예측 정확도를 측정할 수 있다. 
    classifier.fit(X_train, y_train)
    pred = classifier.predict(X_val)
    n_iter += 1
    
    # 반복 시 마다 정확도 측정 
    accuracy = np.round(accuracy_score(y_val,pred), 4)
    train_size = X_train.shape[0]
    val_size = X_val.shape[0]
    print('\n#{0} 교차 검증 정확도 :{1}, 학습 데이터 크기: {2}, 검증 데이터 크기: {3}'
          .format(n_iter, accuracy, train_size, val_size))
    print('#{0} 검증 세트 인덱스:{1}'.format(n_iter,val_index))
    
    cv_accuracy.append(accuracy)
    
# 개별 iteration별 정확도를 합하여 "평균" 정확도 계산 
print('\n## 평균 검증 정확도:', np.mean(cv_accuracy)) 
#1 교차 검증 정확도 :0.9667, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#1 검증 세트 인덱스:[  0   1   2   3   4   5   6   7   8   9  50  51  52  53  54  55  56  57
  58  59 100 101 102 103 104 105 106 107 108 109]

#2 교차 검증 정확도 :0.9667, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#2 검증 세트 인덱스:[ 10  11  12  13  14  15  16  17  18  19  60  61  62  63  64  65  66  67
  68  69 110 111 112 113 114 115 116 117 118 119]

#3 교차 검증 정확도 :0.9, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#3 검증 세트 인덱스:[ 20  21  22  23  24  25  26  27  28  29  70  71  72  73  74  75  76  77
  78  79 120 121 122 123 124 125 126 127 128 129]

#4 교차 검증 정확도 :0.9667, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#4 검증 세트 인덱스:[ 30  31  32  33  34  35  36  37  38  39  80  81  82  83  84  85  86  87
  88  89 130 131 132 133 134 135 136 137 138 139]

#5 교차 검증 정확도 :1.0, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#5 검증 세트 인덱스:[ 40  41  42  43  44  45  46  47  48  49  90  91  92  93  94  95  96  97
  98  99 140 141 142 143 144 145 146 147 148 149]

## 평균 검증 정확도: 0.9600200000000001

※ 일반적으로 분류(Classificaation)문제는 교차 검증시 Stratified K 폴드로 데이터세트를 분할하여 학습과 검증을 진행해야한다. 그러나 회귀(Regression)의 경우 Stratified K 폴드가 지원되지 않는다. 왜냐하면 회귀의 label들은 이산값 형태가 아니라 연속된 숫자이기 때문에 결정값별로 분포를 정하는 의미가 없기 때문이다. 

2. 교차 검증을 위한 Sklearn API 

① cross_val_score

scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_score.html

위의 (Stratified) K 폴드 교차 검증 코드를 보면  아래의 3단계로 이루어진다. 이 과정을 한꺼번에 수행해주는 API가 cross_val_score이다. 즉, 이 API 내에서 분할,학습, 예측, 평가를 다 수행해준다. 

1. 폴드 세트를 설정하고 

2. for 루프에서 반복적으로 학습 및 테스트 데이터의 인덱스를 추출한 뒤

3. 반복적으로 학습과 예측을 수행하고 예측 성능을 반환

sklearn.model_selection.cross_val_score(estimator, X, y=None, *, groups=None, scoring=None, 
	cv=None, n_jobs=None, verbose=0, fit_params=None, pre_dispatch='2*n_jobs', error_score=nan)

주요 Parameters)

estimator: 알고리즘 클래스 ex) Classifier 또는 Regressor가 될 수 있다. Classifier이면 Stratified  K 폴드 방식, Regressor이면 K 폴드 방식,으로 분할한다.

X: feature dataet

y: label dataset

scoring: 예측 성능 평가 지표

cv: 교차 검증 폴드 수

Return 값)

scoring 파라미터로 지정된 성능 지표 측정값의 배열 형태

그래서 사용시에는 이 Return 된 배열 값을 평균해서 사용한다. 

cross_val_score 코드)

In [1]:
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score , cross_validate
from sklearn.datasets import load_iris
import numpy as np

iris_data = load_iris()
dt_clf = DecisionTreeClassifier(random_state=156)

data = iris_data.data
label = iris_data.target

# 성능 지표는 정확도(accuracy) , 교차 검증 세트는 3개 
scores = cross_val_score(dt_clf , data , label , scoring='accuracy',cv=3)
print('교차 검증별 정확도:',np.round(scores, 4))
print('평균 검증 정확도:', np.round(np.mean(scores), 4))
교차 검증별 정확도: [0.98 0.94 0.98]
평균 검증 정확도: 0.9667

② GridSearchCV

scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html

하이퍼 파라미터를 저정하면서 알고리즘의 예측 성능을 개선할 수 있다. 그러나 매번 하나씩 값을 변경해가면서 그때마다 결과를 보는 것을 반복하기에는 너무 번거롭다. 알고리즘에 사용되는 하이퍼 파라미터를 순차적으로 입ㄹ력하면서 편리하게 최적의 파라미터를 도출해 낼 수 있게 해주는 API가 GridSearchCV이다. 

class sklearn.model_selection.GridSearchCV(estimator, param_grid, *, scoring=None, 
     n_jobs=None, iid='deprecated', refit=True, cv=None, verbose=0,
     pre_dispatch='2*n_jobs', error_score=nan, return_train_score=False)

주요 Parameters)

estimator: 알고리즘 클래스 ex) Classifier 또는 Regressor 또는 pipeline가 될 수 있다. 

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

scoring: 예측 성능 평가 지표

cv: 교차 검증 폴드 수

refit: 디폴트가 True이며 True로 생성시 가장 최적의 하이퍼 파라미터를 찾은 뒤 입력된 estimator객첼를 해당 하이퍼 파라미터로 재학습 시킨다. 

Return 값)

scoring 파라미터로 지정된 성능 지표 측정값의 배열 형태

그래서 사용시에는 이 Return 된 배열 값을 평균해서 사용한다. 

GridSearchCV 코드)

In [1]:
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

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

#classifier는 DecisionTree 사용
dtree = DecisionTreeClassifier()

#실험하고 싶은 DicisionTree의여러 하이퍼 파라미터를 딕셔너리 형태로 설정
parameters = {'max_depth': [1, 2, 3], 'min_samples_split': [2, 3]}
In [2]:
# param_grid의 하라퍼 파라미터를 3개의 trian, test set fold로 나누어 테스트를 수행
## refit = True이면 가장 좋은 파라미터 설정으로 재학습시킴
grid_dtree = GridSearchCV(dtree, param_grid=parameters, cv=3, refit=True)

#.fit은 train data를 cv에 폴딩 세트로 분할해서 param_grid 하이퍼 파라미터들을 순찹적으로 변경하면서 학습/ㅍ펴가를 수행하고, 
grid_dtree.fit(X_train, y_train)

# 그 결과를 cv_resuls_ 속성에 기록한다.
# cv_results_는 딕셔너리 형태로 key 값과 리스트 형태의 value값을 가진다. 
#print(grid_dtree.cv_results_) 그냥 이렇게 보면 헷갈리니까 DataFrame형태로 바꿔서 봐본다. 
scores_df = pd.DataFrame(grid_dtree.cv_results_)

# 결과 보기
# scores_df.head()
scores_df[['params', 'mean_test_score', 'rank_test_score', 
           'split0_test_score', 'split1_test_score', 'split2_test_score']]
Out[2]:
  params mean_test_score rank_test_score split0_test_score split1_test_score split2_test_score
0 {'max_depth': 1, 'min_samples_split': 2} 0.700000 5 0.700 0.7 0.70
1 {'max_depth': 1, 'min_samples_split': 3} 0.700000 5 0.700 0.7 0.70
2 {'max_depth': 2, 'min_samples_split': 2} 0.958333 3 0.925 1.0 0.95
3 {'max_depth': 2, 'min_samples_split': 3} 0.958333 3 0.925 1.0 0.95
4 {'max_depth': 3, 'min_samples_split': 2} 0.975000 1 0.975 1.0 0.95
5 {'max_depth': 3, 'min_samples_split': 3} 0.975000 1 0.975 1.0 0.95
In [3]:
# .fit의 최고 성능을 나타낸 하이퍼 파라미터의 값과 그떼의 평가 결과 값이 각각 best_params, best_score_ 속성에 길록된다. 
print('GridSearchCV 최적 파라미터:', grid_dtree.best_params_)
print('GridSearchCV 최고 정확도: {0:.4f}'.format(grid_dtree.best_score_))

# refit=True로 설정된 GridSearchCV 객체가 fit()을 수행 시 학습이 완료된 Estimator를 내포하고 있으므로 predict()를 통해 예측도 가능. 
pred = grid_dtree.predict(X_test)
print('테스트 데이터 세트 정확도: {0:.4f}'.format(accuracy_score(y_test,pred)))
 
GridSearchCV 최적 파라미터: {'max_depth': 3, 'min_samples_split': 2}
GridSearchCV 최고 정확도: 0.9750
테스트 데이터 세트 정확도: 0.9667

 

 

지금까지 파이썬 머신러닝 완벽 가이드 수업을 바탕으로 

Sklearn에 대한 소개와 

Sklearn의 Model Selection 모듈에 있는 train/test data set 분리 및 Cross Validation 에 대해서 정리해 보았다. 

 

다음 포스트에서는 sklearn을 이용한 데이터 전처리 방법들에 대해서 정리해 볼 것이다. 

 

최종 모델 결정 방법

hoon427.tistory.com/53

 

Reference

파이썬 머신러닝 완벽 가이드 - 권철민 저

kongdols-room.tistory.com/112