[ML_3] cross_validation

2025. 7. 12. 23:40python/ML

1. Cross validation이란 말 그대로 교차 검증을 뜻한다. 기존에는 dataset을 train과 test로 한 번만 나누고, train 데이터로 학습한 뒤 test 데이터로 평가를 진행했다. 그러나 이 방법은 데이터 분할에 따라 성능 평가가 달라질 수 있고, 모델이 학습 데이터에 과도하게 적합되어(overfitting) 새로운 데이터에 대한 성능이 떨어질 위험이 있다. 이러한 문제를 완화하기 위해, 데이터를 여러 번 분할해 반복적으로 학습과 평가를 수행하는 방식이 필요하게 되었고, 이를 Cross validation이라 부른다. 교차 검증은 전체 데이터를 여러 fold로 나누어 매번 다른 부분을 검증 데이터로 사용하고, 나머지를 학습 데이터로 활용해 평가를 반복하는 방식이다. 이렇게 하면 데이터 분할의 운에 따른 성능 편차를 줄일 수 있다.

보통 별도의 test 데이터셋을 따로 남겨두고, 교차 검증은 train 데이터 안에서만 이루어지며, 최종 성능 평가는 test 데이터로 수행하는 것이 일반적이다. 

 

 

2.  Cross validation의 종류 1 : K-fold cross validation 

 K-fold cross validation은 데이터를 K개의 fold로 나누어 진행하는 교차 검증 기법이다. 예를 들어 K=5일 경우, 데이터셋을 5개의 fold로 나누고, 매번 서로 다른 fold를 검증용 데이터로 사용하며 나머지 fold를 학습용 데이터로 활용한다. 이렇게 총 5번의 학습과 검증을 반복하게 되며, 검증에 사용되는 데이터는 매번 달라진다. 이를 통해 데이터 분할에 따른 편향을 줄이고, 모델의 일반화 성능을 보다 안정적으로 평가할 수 있다. 또한, 각 fold에서 얻은 성능 지표를 평균 내어 모델의 성능을 종합적으로 판단하게 된다.

첫 번째 학습/ 검증 학습 학습 학습 학습 검증
두 번째 학습 학습 학습 검증 학습
세 번째 학습 학습 검증 학습 학습
네 번째 학습 검증 학습 학습 학습
다섯 번째 검증 학습 학습 학습 학습

 

# K-fold cv
from sklearn.tree import DecisionTreeClassifier 
from sklearn.metrics import accuracy_score 
from sklearn.model_selection import train_test_split 
from sklearn.datasets import load_iris 
from sklearn.model_selection import KFold
import numpy as np 

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

kfold = KFold(n_splits = 5) 
cv_accuracy = [] 
features.shape # (150,4) 

n_iter = 0 
for train_idx , test_idx in kfold.split(features):
    X_train, X_test = features[train_idx], features[test_idx] 
    y_train, y_test = label[train_idx], label[test_idx] 

    model.fit(X_train, y_train) 
    pred = model.predict(X_test) 
    n_iter += 1 

    accuracy = np.round(accuracy_score(y_test,pred),5) 
    train_size = X_train.shape[0] 
    test_size = X_test.shape[0] 
    print(f"n_iter : {n_iter} , accuracy : {accuracy} , train,test size : {train_size}, {test_size}") 
    print(f"n_iter : {n_iter}, test_idx : {test_idx}") 
    cv_accuracy.append(accuracy) 

print(f"mean of accuracy : {np.mean(cv_accuracy)}")

 

3. Cross validation의 종류 2 : Stratified K fold 

 Stratified K-Fold는 불균형한 분포를 가진 레이블 데이터셋에 적합한 K-Fold 방식이다. 예를 들어, 1년 동안 복권을 구매한 데이터가 있다고 가정하자. 이 데이터의 레이블 값은 복권에 당첨되었는지 여부이다. 대부분의 사람들은 당첨되지 않았기 때문에, 데이터의 레이블은 대다수가 0으로 구성되어 있다. 일반적인 K-Fold 방식으로 데이터를 분할하면, 각 fold의 레이블 분포가 크게 달라질 수 있다. 예를 들어 어떤 fold에는 0만 포함될 수 있고, 다른 fold에는 1과 0이 섞여 있을 수 있다. 이처럼 일부 fold에서 특정 클래스가 아예 빠져버리면, 모델은 해당 클래스에 대해 학습하지 못해 성능이 저하될 수 있다. Stratified K-Fold는 이러한 문제를 해결하기 위해, 각 fold의 레이블 분포가 전체 데이터셋의 분포와 유사하도록 데이터를 분할한다. 이를 통해 클래스 비율이 균일하게 유지되어, 불균형 데이터셋에서도 안정적인 학습과 평가가 가능하다.

# Stratified K-fold 
from sklearn.datasets import load_iris 
from sklearn.model_selection import StratifiedKFold
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
import pandas as pd 
import numpy as np 

iris = load_iris() 
iris_df = pd.DataFrame(data = iris.data, columns= iris.feature_names) 
iris_df['label'] = iris.target 
features = iris_df.iloc[:,:-1] 
labels = iris_df.iloc[:,-1] 


skf = StratifiedKFold(n_splits=3) 
n_iter = 0 
accuracy_cv = []
for train_idx, test_idx in skf.split(features, labels): 
    n_iter += 1 
    model = DecisionTreeClassifier() 
    X_train, X_test = features.iloc[train_idx], features.iloc[test_idx] 
    y_train, y_test = labels.iloc[train_idx], labels.iloc[test_idx] 
    model.fit(X_train, y_train) 
    y_pred = model.predict(X_test) 

    score = np.round(accuracy_score(y_test, y_pred),4) 
    accuracy_cv.append(score) 
print(accuracy_cv)

 

실제 stratified K fold를 적용한 결과, y_test label 값을 보면 50개를 기준으로 했을 때, 0,1 그리고 2가 fold에 골고루 들어간다는 것을 볼 수 있다. 

0      0
1      0
2      0
3      0
4      0
5      0
6      0
7      0
8      0
9      0
10     0
11     0
12     0
13     0
14     0
15     0
16     0
50     1
51     1
52     1
53     1
54     1
55     1
56     1
57     1
58     1
59     1
60     1
61     1
62     1
63     1
64     1
65     1
66     1
100    2
101    2
102    2
103    2
104    2
105    2
106    2
107    2
108    2
109    2
110    2
111    2
112    2
113    2
114    2
115    2


0    17
1    17
2    16

 

 

 

4. Cross validation을 보다 간편하게 하는 법 - cross_val_score() 

 scikit-learn에서는 교차 검증을 보다 편리하게 수행할 수 있도록 다양한 API를 제공한다. 그중 대표적인 것이 cross_val_score() 함수이다. 이 함수는 모델, feature 데이터, label 데이터, 성능 평가 지표(scoring), 교차 검증 fold 수(cv)를 인자로 받는다. 실행하면 내부에서 모델을 반복적으로 학습하고 예측한 뒤, 지정한 평가 지표로 성능을 계산해 준다. 반환 값으로 각 fold별 성능 점수가 리스트 형태로 제공된다. 예를 들어 [0.98, 0.94, 0.98]과 같이 fold별 정확도가 반환되며, 평균 점수는 np.mean() 등을 사용해 따로 구할 수 있다.

# cross_val_score() 
from sklearn.metrics import accuracy_score 
from sklearn.datasets import load_iris 
from sklearn.model_selection import cross_val_score, cross_validate 
from sklearn.tree import DecisionTreeClassifier 
import numpy as np 

iris = load_iris() 
models = DecisionTreeClassifier() 

features = iris.data 
labels = iris.target 

scores = cross_val_score(estimator = models,X=features, y = labels, scoring = 'accuracy', cv = 3) 
# return values : [score1, score2, score3] 
print(np.round(scores,4)) 
print(np.round(np.mean(scores),4))

 

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

 특정 알고리즘, 예를 들어 Decision Tree를 사용하는 모델이 있다고 가정하자. 이 알고리즘은 max_depth, min_samples_split과 같은 여러 하이퍼파라미터에 의해 성능이 크게 영향을 받는다. GridSearchCV는 이러한 하이퍼파라미터 후보 값을 조합해 가능한 모든 경우의 수를 생성한다. 그리고 각 조합에 대해 지정된 cv 횟수만큼 교차 검증을 수행하여 성능을 평가한다. 이 과정을 통해 어떤 하이퍼파라미터 조합이 가장 좋은 평균 성능을 내는지를 찾을 수 있다. 최종적으로 GridSearchCV는 선택된 최적의 하이퍼파라미터로 전체 학습 데이터를 사용해 모델을 다시 학습시킨다. 또한, scoring 옵션을 통해 평가 지표를 자유롭게 지정할 수 있으며, 결과로 각 조합의 성능 점수와 최적 파라미터 조합을 함께 제공한다.

# GridSearchCV 
from sklearn.model_selection import GridSearchCV 
from sklearn.datasets import load_iris 
from sklearn.tree import DecisionTreeClassifier 
from sklearn.model_selection import train_test_split 
import pandas as pd 
import numpy as np 

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) 
model = DecisionTreeClassifier() 

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

grid_model = GridSearchCV(model, param_grid=parameters, cv = 3, refit = True) 
grid_model.fit(X_train, y_train) 

scores_df = pd.DataFrame(grid_model.cv_results_) 
scores_df[['params','mean_test_score', 'rank_test_score', 'split0_test_score', 'split1_test_score', 'split2_test_score']] 
print(grid_model.best_params_) 
display(grid_model.best_score_)

estimator = grid_model.best_estimator_ 

y_pred = estimator.predict(X_test) 
print(accuracy_score(y_test, y_pred))

 

output : 

 

 

 

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