본문 바로가기
파이썬(Python), 머신러닝, 딥러닝

(머신러닝 with 파이썬) Model Selection / train_test_split, K-fold CV, Startifield K-fold CV, GridSearch CV

by 굳세라(goodsarah) 2023. 8. 16.
728x90
반응형

이번에는 Model Selection에 대해서 알아보겠습니다.

 

1. Model Selection이란?

 Model Selection(모형 선택)은 머신 러닝이나 통계적 모델링에서 가장 적합한 모델을 선택하는 과정을 말합니다. 

 이는 데이터 분석의 중요한 단계 중 하나로,

 1) 어떤 종류의 모델을 사용할 것인지,

 2) 그 모델의 하이퍼파라미터를 어떻게 설정하 것인지를 결정하는 과정을 포함합니다.

 

 Model Selection이 중요한 이유는 아래와 같습니다.

 a) Over-fitting 또는 Under-fitting 을 방지

    : 너무 복잡한 모델을 선택하여 train 데이터에 딱 맞게 만들어진 모델은 새로운 데이터를 예측하는데에는 적합하지 않는 문제인 Over-fitting 문제를 야기합니다. 반대로, 너무 단순한 모델은 데이터의 패턴을 제대로 학습하지 못할 수 있어 이 또한 새로운 데이터를 잘 예측하지 못하는 Under-fitting 문제를 야기합니다.

 b) 자원 및 시간 최적화

    : 모델 선택은 자원(메모리, 연산 속도)과 시간을 최적화 할 수 있는 모델을 선택하는데 도움을 줍니다. 더 간단한 모델은 더 빠르게 예측을 수행하거나 더 작은 자원을 사용할 수 있기 때문입니다. 

 

 

2. 파이썬으로 Model Selection 구현하기 (데이터 : iris / 모델 : Decision Tree Classifier)

1) 기초 모델 만들기 

 

먼저, iris 데이터와  Decision Tree Classifier를 활용해서 예측모델을 만들어보자

1
2
3
4
5
6
7
8
9
10
11
12
13
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
 
iris = load_iris()
dt_clf = DecisionTreeClassifier()
train_data = iris.data
train_label = iris.target
dt_clf.fit(train_data, train_label)
 
# 학습 데이터 셋으로 예측 수행
pred = dt_clf.predict(train_data)
print('예측 정확도:',accuracy_score(train_label,pred))
cs

 학습데이터(train data)로 결정나무 분류기(Decision Tree Classifier) 모델을 만들었고, 동일하게 학습데이터로 검증해본 결과는 100%가 나왔다.

 

 이는, decision tree classifier가 train 데이터에 딱 맞게 만들어졌다(정확히 예측하였다)라고 할 수 있다. 그렇다면 정말 정확한 모델을 만들었다고 할 수 있을까?

 

 아직 속단하기는 이르다. 학습 데이터로 만든 모델이 학습 데이터를 100%로 분류해낸다는 것은 곧 Over-fitting 문제가 있을 가능성이 매우 높다고 할 수 있다. 

 

 그렇다면, 먼저 전체 데이터를 train 데이터와 test 데이터로 나누어서, 모델을 구축하고(train 데이터 활용) 테스트(test 데이터 활용)를 해보자.

 

2) train_test_split (학습 / 테스테 데이터 셋 분리)

 

이번에는 데이터를 train_test_split 함수를 사용해서 train 용 데이터와 test 용 데이터로 분리해서 예측을 해보자

1
2
3
4
5
6
7
8
9
10
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)
cs

*  train_test_split 함수에서 "iris_data.data" 는 features로 구성된 Matrix (: X) 를 의미하고, "iris_data.target"은 iris 데이터의 3가지 품종을 뜻하는 response(: y) 이다.

* test_size를 0.3(=30%)로 선정했으므로, 전체 150개의 데이터 중 45개 데이터는 test용으로, 105개는 train 용으로 사용했음을 의미한다. 

 

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

 

이번에는 train 데이터(105개)로 모델을 구성하고, test 데이터(45개)로 예측을 해봤더니 총 95.56%의 정확도를 나타내었다.

 

이 결과를 Confusion Matrix 를 통해 확인해보자.

아래는 train 데이터로 구성된 모델을 활용하여 45개의 관측치로 구성된 test 데이터에 대한 분류 결과를 나타내는 코드와 그 결과를 Confusion Matrix(혼동 행렬)로 만든 결과이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
 
# 예측 결과와 실제 레이블 비교
y_pred = dt_clf.predict(X_test)
cm = confusion_matrix(y_test, y_pred)
 
# 클래스 이름 가져오기
class_names = iris.target_names
 
# 혼동 행렬 시각화
plt.figure(figsize=(86))
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", xticklabels=class_names, yticklabels=class_names)
plt.xlabel("Predicted Labels")
plt.ylabel("True Labels")
plt.title("Confusion Matrix")
plt.show()
cs

총 45개의 데이터 중 2개를 제외한 43개의 데이터는 옳게 분류되었음을 의미한다.

 

 

3) k-fold Cross Validation (k겹 교차 검증)

 

이번에는 k-fold Cross Validation(k겹 교차검증 / k-fold CV)에 대해서 알아보겠습니다.

 k-fold Cross-Validation (k-겹 교차 검증)은 모델의 성능을 평가하기 위한 통계적 방법 중 하나입니다. 머신 러닝 모델의 일반화 성능을 평가하고 하이퍼파라미터 튜닝에 사용하는 데에 유용합니다. k-fold 교차 검증은 데이터를 k개의 서브셋(또는 폴드)으로 나누고, 각 폴드를 순차적으로 검증 세트로 사용하고 나머지 폴드를 훈련 세트로 사용하여 모델을 훈련하는 방식으로 진행됩니다.

 일반적인 k-fold 교차 검증의 단계는 다음과 같습니다:

a) 데이터를 k개의 동일한 크기의 서브셋(폴드)으로 나눕니다.
b) 모델을 k번 훈련하고 검증합니다. 각 반복에서 하나의 폴드를 검증 세트로 사용하고, 나머지 k-1개의 폴드를 훈련 세트로 사용합니다.
c) 각 반복에서 얻은 검증 성능을 기록하여 최종 성능을 평균하거나 평가합니다.

 

 k-fold 교차 검증은 다음과 같은 이점을 가지고 있습니다:

a) 데이터를 효율적으로 활용하며, 일반화 성능의 신뢰성을 높일 수 있습니다.
b) 모델의 성능을 평가하는 데에 과적합 문제를 방지합니다.
c) 하이퍼파라미터 튜닝 시에도 신뢰성 있는 모델 평가를 제공합니다.

 

그렇다면 k-fold CV에 대해서 파이썬 코드를 통해 구현해보도록 하겠습니다.

 

우선 동일하게 데이터 셋(iris)과 모델(Decision Tree Classifier)를 불러옵니다.

이때 추가적으로, KFold 함수를 불러와서 전체 데이터를 k값에 따라 등분해줍니다. 

이번에 사용할 k는 가장 보편적으로 활용하는 5(k=5) 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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])
cs

 

 

이후 각 폴드 중 1개를 test 데이터로 선정하고, 나머지를 train 데이터로 선정하여 모델을 훈련시키고 평가를 진행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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)) 
cs

 

위 결과 중 #1은 1번부터 30번까지의 데이터(첫번째 겹)를 test 데이터로

위 결과 중 #2는 31번부터 60번까지의 데이터(첫번째 겹)를 test 데이터로

위 결과 중 #3은 61번부터 90번까지의 데이터(첫번째 겹)를 test 데이터로

위 결과 중 #4은 91번부터 120번까지의 데이터(첫번째 겹)를 test 데이터로

위 결과 중 #5은 121번부터 150번까지의 데이터(첫번째 겹)를 test 데이터로

 

선정하고 나머지 데이터를 train 데이터로 선정하여 도출해낸 결과를 의미한다.

 

#1의 경우 정확도는 100% / #2의 경우 정확도는 96.67% / #3의 경우 정확도는 86.67% / #4의 경우 정확도는 93.33% / #5의 경우 정확도는 73.33%이며 

 

 전체 정확도의 평균은 90%를 의미합니다.(각 샘플의 크기가 동일하기에, 전체 확률을 더해서 5로 나누어주면 됩니다.)

 

 이를 통해 도출된 결과는, train_test_split을 통해 단 한번의 검증을 거친 경우보다는 더 공정한 결과라고 할 수 있겠습니다.

 

4) Startifiled K-fold Cross Validation


 StratifiedKFold는 일반적인 KFold와 달리, 각 폴드 안의 클래스 분포가 원본 데이터셋의 클래스 분포와 비슷하게 유지되도록 데이터를 분할하는 것을 의미합니다.

 이는, 불균형한 클래스 분포를 가진 데이터셋에서 모델의 성능을 평가할 때 유용합니다. 예를 들어, 어떤 클래스가 다른 클래스보다 훨씬 많거나 적은 경우, 일반적인 KFold로 데이터를 나눴을 때 특정 클래스가 훈련 세트 또는 검증 세트에 대표적으로 포함되지 않을 수 있습니다. 이는 모델의 평가에 편향을 일으킬 수 있습니다.

 StratifiedKFold는 각 폴드 안의 클래스 비율을 원본 데이터셋과 비슷하게 맞추는 데 도움을 주어 이러한 문제를 완화합니다. 각 폴드는 원본 데이터셋의 클래스 비율을 유지하며 훈련 세트와 검증 세트를 구성합니다. 이로써 각 폴드에서의 모델 성능 평가가 전체 데이터셋에서의 성능을 더 잘 대변하게 됩니다.

 

그렇다면 Startifield K-fold CV를 대해서 파이썬 코드를 통해 구현해보도록 하겠습니다.

 

먼저, 데이터를 불러오고 label(iris의 품종)의 분포에 대해서 알아봅시다

1
2
3
4
5
6
7
import pandas as pd
 
iris = load_iris()
 
iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df['label']=iris.target
iris_df['label'].value_counts()
cs

결과는 알고 있는 것처럼 50:50:50 으로 나누어져있음을 알 수 있습니다.

 

 

먼저 k-fold를 활용하여 (k=3) 각 폴드 별 레이블의 데이터 분포를 확인해봅시다.

1
2
3
4
5
6
7
8
9
10
kfold = KFold(n_splits=3)
# kfold.split(X)는 폴드 세트를 3번 반복할 때마다 달라지는 학습/테스트 용 데이터 로우 인덱스 번호 반환. 
n_iter =0
for train_index, test_index  in kfold.split(iris_df):
    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())
cs

 

 다음은 Startifield KFold를 활용해봅시다. 

 

 사이킷런에서 StartifieldKFold를 활용해, 각 겹(fold)를 만들 때 전체 데이터에서 target(iris 품종)의 분포에 따라 분할 해보면 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
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())
cs

 

이렇게 나뉘어진 데이터를 활용해서 검증을 해본 결과는 아래와 같습니다.

 

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
dt_clf = DecisionTreeClassifier(random_state=156)
 
skfold = StratifiedKFold(n_splits=3)
n_iter=0
cv_accuracy=[]
 
# StratifiedKFold의 split( ) 호출시 반드시 레이블 데이터 셋도 추가 입력 필요  
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]
    #학습 및 예측 
    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)
    
# 교차 검증별 정확도 및 평균 정확도 계산 
print('\n## 교차 검증별 정확도:', np.round(cv_accuracy, 4))
print('## 평균 검증 정확도:', np.mean(cv_accuracy)) 
cs

 

 

 전체 평균의 검증 정확도는 96.66%가 나오게 되었습니다.

 

 전체 데이터의 분포에 따라 k-fold의 비중을 구성하는 Startifield k-fold를 활용한 경우, 일반적인 k-fold를 활용한 경우보다 더 높은 검증 정확도를 보여줌을 알 수 있습니다.

 

 

5) Cross_val_score

 

이번에는 Cross_val score 함수를 통해  보다 간편하게 k-fold CV를 하는 코드를 만들어 봅시다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score , cross_validate
from sklearn.datasets import load_iris
 
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))
cs

 

 

6) Grid Search CV

 

 마지막으로 Grid Search CV에 대해서 알아보겠습니다.

 

 Grid Search Cross-Validation (Grid Search CV)는 머신 러닝 모델의 하이퍼파라미터를 튜닝하고 최적의 조합을 찾기 위한 방법 중 하나입니다. 하이퍼파라미터는 모델의 구조나 학습 과정에 영향을 주는 조절 가능한 매개변수들을 말하며, 이들의 최적값을 찾는 것이 모델의 성능 향상에 중요한 역할을 합니다.

 Grid Search CV는 다음과 같은 방법으로 동작합니다:

 a) 튜닝하고자 하는 하이퍼파라미터들과 그들의 가능한 값을 사전에 정의합니다. 이들 조합으로 모든 가능한 조합을 만들어 그리드(Grid) 형태로 구성합니다.
 b) 이 그리드에서 각 조합마다 교차 검증(Cross-Validation)을 사용하여 모델을 훈련하고 검증합니다. 교차 검증을 통해 모델의 성능을 평가합니다.
 c) 각 조합에 대한 교차 검증 결과를 비교하여 가장 좋은 조합을 선택합니다.

 

 Grid Search CV의 장점은 가능한 모든 조합을 검증할 수 있다는 것입니다. 하지만 이로 인해 계산 비용이 높아질 수 있습다는 단점이 존재합니다.

 

다음은 파이썬 코드를 통해 Grid Search CV를 구현해보겠습니다.

 

 Grid Search CV를 통해 최적화 시킬 하이퍼 파라미터는 Decision Tree Classifier의 max_depth(나무의 깊이)와 min_samples_split(샘플의 나눌때 최소 개수) 입니다.

 max_depth는 1,2,3을 후보로 / min_samples_split은 2,3을 후보로 하여 

 총 6개의 모델 ( 6 = 3 x 2 )를 만들어 결과를 비교해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV
 
# 데이터를 로딩하고 학습데이타와 테스트 데이터 분리
iris = 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)
dtree = DecisionTreeClassifier()
 
### parameter 들을 dictionary 형태로 설정
parameters = {'max_depth':[1,2,3], 'min_samples_split':[2,3]}
cs

 

 이때 3겹의 CV(3-fold Cross Validation)을 활용해 각 하이퍼 파라미터의 조합들이 도출해내는 결과값의 평균값을 종합해보는 코드를 만들었고, 이는 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
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']]
cs

 

그 결과는 아래와 같습니다.

 

첫번째 행을 해석해보면,

 max_depth가 1이고 min_samples_split의 개수가 2일때  3겹으로 데이터를 나누어 교차검증하는 3-fold CV를 진행했을때  평균 테스트 정확도(mean_test_score)는 70% 이며

이는 전체 테스트 스코어 중 5등(공동 5등 / max_depth가 1이고 min_samples_split가 3일때와 동일)이 되었음을 의미합니다.

 

 전체 결과를 해석해보면 rank가 1인 조합이 가장 좋고 해당 조합은

 max_depth가 3이고, min_samples_split이 3일때 97.5%의 테스트 데이터에 대한 평균 예측 정확도를 도출했음을 알 수 있습니다.

 

 

728x90
반응형

댓글