본문 바로가기

Study/Machine Learning 개론

1. 교차 검증

ML 에는 Under-fitting 문제와 Over-fitting 문제가 존재한다.

 

그 중 학습 데이터에 과도하게 맞춰진 나머지,

테스트 데이터에 대한 예측 정확도가 떨어지는 문제인 Overfitting 문제를 해결하기 위해

교차 검증 , Cross Validation 을 수행한다.

 

그 중 K 폴드 교차 검증과 Stratified K 폴드 교차 검증을 정리하고자 한다.

 

K 폴드 교차 검증은 전체 데이터 셋을 N 등분하여 데이터 셋 N개로 소분한 다음,

학습 데이터 세트 N-1 개와 테스트 데이터 세트 1개를 가지고 검증을 반복한다.

검증을 반복할 때마다 테스트 데이터 세트와 학습 데이터 세트 구성을 변경한다.

 

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

 

Stratified K 폴드 교차 검증

데이터 셋의 분포가 고르지 못한 경우,

데이터 셋을 N 등분 하면 어떤 하위 데이터 셋은 특정 데이터가 편중되어 있을 가능성이 높다.

편중된 데이터 셋을 가지고 학습 혹은 테스트가 이뤄지기 때문에 좋지 않다.

따라서 이 문제를 해결하기 위해 데이터 셋에 특정 데이터가 편중되지 않고 고르게 분포되도록 N등분하여

하위 데이터 셋으로 분할하여 K 폴드 교차 검증을 수행해야 한다.

그리고 이 방법을 Stratified K 폴드 교차 검증이라고 한다.


 

sklearn.datasets 에서 제공하는 데이터인 붓꽃(iris)의 데이터를 예시로 가져온다.

붓꽃의 품종: setosa, versicolor, virginica 마다 

각 품종별 꽃받침 길이&넓이  꽃잎 길이&넓이를 저장한 데이터셋이다.

from sklearn.datasets import load_iris
from sklearn.model_selection import KFold, StratifiedKFold
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

import numpy as np
import pandas as pd

 

iris = load_iris()

iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df['label'] = iris.target

def get_label_names(label):
    label_names=''
    if label == 0:
        label_names = iris.target_names[0]
    elif label == 1:
        label_names = iris.target_names[1]
    else:
        label_names = iris.target_names[2]
        
    return label_names
    
# 'label' 의 값을 x 로 하여 get_label_names(x) 의 리턴값을 'label_names'로 한다.
iris_df['label_names'] = iris_df['label'].apply(lambda x : get_label_names(x))

iris_df.head(3)
# iris_df.describe()

iris_df.head(3) 에 대한 출력값

 

일반적으로 KFold 로 데이터 셋 분할

kfold = KFold(n_splits=3)
iter=0;

for train_index, test_index in kfold.split(iris_df):
    iter += 1
    # DataFrame.iloc[index] 의 index를 리스트로 넘겨줄 수도 있다.
    label_train = iris_df['label'].iloc[train_index]
    label_test = iris_df['label'].iloc[test_index]
    
    print("\n######")
    print("{0} 번째 교차 검증".format(iter))
    print(label_train)
    print(label_test)
    print("###")
    print("학습 레이블 데이터 분포: ", label_train.value_counts())
    print("검증 (테스트) 레이블 데이터 분포: ", label_test.value_counts())

가져온 iris 데이터셋은 행이 총 150개 (iris_df.describe() 를 하면 행이 몇 개인지 등 알 수 있다.)인데,

0 ~ 49번 행까지 는 0 ('setosa')

50 ~ 99번 행까지는 1 ('versicolor')

100 ~ 149번 행까지는 2('virginica')이다.

 

즉, 데이터 분포가 불규칙적인데,

150 개의 데이터를 n_splits = 3, 앞에서 부터 50개씩 묶어서 3개의 하위 데이터셋으로 분할했으므로

분할한 데이터셋도 값이 치우치게 된다.

 


 

Stratified_KFold 적용

stratified_kfd = StratifiedKFold(n_splits=3)
iter = 0

for train_index, test_index in stratified_kfd.split(iris_df, iris_df['label']):
    iter += 1
    
    label_train = iris_df['label'].iloc[train_index]
    label_test = iris_df['label'].iloc[test_index]
    
    print("\n######")
    print("{0} 번째 교차 검증".format(iter))
#     print(label_train)
#     print(label_test)
    print("###")
    print("학습 레이블 데이터 분포: ", label_train.value_counts())
    print("검증 (테스트) 레이블 데이터 분포: ", label_test.value_counts())

StratifiedKFold.split 의 파라미터는 분할할 데이터 셋 이외에도,

어떤 값을 기준으로 균등하게 데이터셋을 분할할지도 파라미터로 받는다.

-> 위의 예제에서는 iris_df['label']의 분포가 고르도록 데이터셋을 분할하도록 한다. 

 

지도 학습은 크게 분류(classifier)와 회귀(regressor)로 나눌 수 있으며,

Classifier class 중 하나인 DecisionTreeClassifier 를 통해 모델을 학습시킨 후 ( fit() )

검증 데이터 셋에 대한 예측을 실시 ( prediction(X_test) )

예측값들에 대한 평균을 구한다.

random_state 는 랜덤시드값이다.

classifier = DecisionTreeClassifier(random_state=255)

s_KFold = StratifiedKFold(n_splits=3) # 교차 검증도 3 번 진행
iter=0;
cv_accuracy=[]

for train_index, test_index in s_KFold.split(iris_df, iris_df['label']):
    X_train, X_test = iris.data[train_index], iris.data[test_index]
    Y_train, Y_test = iris_df['label'].iloc[train_index], iris_df['label'].iloc[test_index]
    
    # 모델 학습 및 예측
    classifier.fit(X_train, Y_train)
    prediction = classifier.predict(X_test)
    
    iter += 1
    # from sklearn.metrics import accuracy_score
    accuracy = np.round(accuracy_score(Y_test, prediction), 4)
    train_size = X_train.shape[0] # X_train 행갯수
    test_size = X_test.shape[0] # X_test 행갯수
    
    print("\n###")
    print("{0} 번째 교차검증(CV) 정확도: {1}".format(iter, accuracy))
    print("학습 데이터 크기 : {0}, 검증 데이터 크기 : {0}".format(train_size, test_size))
    
    cv_accuracy.append(accuracy)
    
print("\n\n 평균 CV 정확도: ", np.mean(cv_accuracy))

 

근데 사실 교차 검증을 알아서 해주는 API 가 있다

# from sklearn.model_selection import train_test_split
#iris = load_iris()
from sklearn.model_selection import GridSearchCV

X_train, X_test, Y_train, Y_test = train_test_split(iris.data, iris.target, test_size = 0.2
                                                   ,random_state=121)

classifier = DecisionTreeClassifier(random_state=255)

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

grid_dtree = GridSearchCV(classifier, param_grid = parameters, cv=3, refit=True)
grid_dtree.fit(X_train, Y_train)

scores_df = pd.DataFrame(grid_dtree.cv_results_)
scores_df.head(5)

그 중 하나가 cross_val_score()

from sklearn.model_selection import cross_val_score

iris = load_iris()
classifier = DecisionTreeClassifier(random_state=255)

scores = cross_val_score(classifier, iris.data, iris.target, scoring='accuracy', cv=3)
print(scores)

 

교차 검증과 하이퍼 파라미터 튜닝까지 같이 하는

GridSearchCV 도 있다.

 

# from sklearn.model_selection import train_test_split
#iris = load_iris()
from sklearn.model_selection import GridSearchCV

X_train, X_test, Y_train, Y_test = train_test_split(iris.data, iris.target, test_size = 0.2,random_state=121)

classifier = DecisionTreeClassifier(random_state=255)

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

grid_dtree = GridSearchCV(classifier, param_grid = parameters, cv=3, refit=True)
grid_dtree.fit(X_train, Y_train)

scores_df = pd.DataFrame(grid_dtree.cv_results_)
scores_df.head(5)

scores_df.head(5) 출력값

estimator = grid_dtree.best_estimator_

pred = estimator.predict(X_test)

print("accuracy of the best estimator: ", np.round(accuracy_score(Y_test, pred), 4))