Posts 교차검증(Kfold)
Post
Cancel

교차검증(Kfold)

1. 교차 검증

  • 과적합 : 모델이 학습 데이터에마 과도하게 최적화된 현상. 그로인해 일반화된 데이터에서는 예측 성능이 과하게 떨어지는 현상
  • 지난번 와인 맛 평가에서 훈련용 데이터의 Acc는 72.94, 테스트용 데이터는 Acc가 71.61%였는데, 누가 이결과가 정말 괜찮은 것인지 묻는다면?
  • 나에게 주어진 데이터에 적용한 모델의 성능을 정확히 표현하기 위해서도 유용하다
  • train 데이터를 다시 나누어 validation 데이터로 사용함으로써 과적합을 막는 방법
  • 검증이 끝나면 다시 train데이터로 학습 한 뒤 test로 마지막 결과를 확인한다

2. 교차검증 구현하기

2.1 simple example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import numpy as np
from sklearn.model_selection import KFold

X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
y = np.array([1, 2, 3, 4])
kf = KFold(n_splits=2)

print(kf.get_n_splits(X))
print(kf)
for train_idx, test_idx in kf.split(X):
    print('-----idx')
    print(train_idx, test_idx)
    print('-----train data')
    print(X[train_idx])
    print('-----test data')
    print(X[test_idx])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
KFold(n_splits=2, random_state=None, shuffle=False)
-----idx
[2 3] [0 1]
-----train data
[[1 2]
 [3 4]]
-----test data
[[1 2]
 [3 4]]
-----idx
[0 1] [2 3]
-----train data
[[1 2]
 [3 4]]
-----test data
[[1 2]
 [3 4]]
  • Kfold는 sklearn에 포함되어 있음.

2.2 와인맛 분류

1
2
3
4
5
6
7
8
9
10
import pandas as pd


red_wine = pd.read_csv(red_url, sep = ';')
white_wine = pd.read_csv(white_url, sep = ';')

red_wine['color'] = 1
white_wine['color'] = 0

wine = pd.concat([red_wine, white_wine])

2.3 와인맛 분류기를 위한 데이터 정리

1
2
3
4
wine['taste'] = [1. if grade > 5 else 0. for grade in wine['quality']]

X = wine.drop(['taste', 'quality'], axis = 1)
y = wine['taste']

2.4 의사결정 나무 모델

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=13)

wine_tree = DecisionTreeClassifier(max_depth=2, random_state=13)
wine_tree.fit(X_train, y_train)

y_pred_tr = wine_tree.predict(X_train)
y_pred_test = wine_tree.predict(X_test)

print('Train Acc : ', accuracy_score(y_train, y_pred_tr))
print('Test Acc : ', accuracy_score(y_test, y_pred_test))
1
2
Train Acc :  0.7294593034442948
Test Acc :  0.7161538461538461
  • Kfold를 진행하지 않고 바로 train으로 데이터를 학습 한뒤 결과를 도출해냄
  • 데이터를 train_test_split으로 랜덤하게 분리하는게 맞는것인지? 혹은 이것이 최선인건가?
  • 또한, 저 acc는 신뢰할수 있는지에 대한 검증이 필요함

2.5 KFold

1
2
3
4
from sklearn.model_selection import KFold

kfold = KFold(n_splits=5)
wine_tree_cv = DecisionTreeClassifier(max_depth=2, random_state=13)
  • Kfold를 만들고, 분류기도 다시 생성
  • cv = cross validation의 약자

2.6 KFold는 index를 반환함

1
2
for train_idx, test_idx in kfold.split(X):
    print(len(train_idx), len(test_idx))
1
2
3
4
5
5197 1300
5197 1300
5198 1299
5198 1299
5198 1299

2.7 각각의 fold에 대한 학습후 acc

1
2
3
4
5
6
7
8
9
cv_accuracy = []
for train_idx, test_idx in kfold.split(X):
    X_train, X_test = X.iloc[train_idx], X.iloc[test_idx]
    y_train, y_test = y.iloc[train_idx], y.iloc[test_idx]
    wine_tree_cv.fit(X_train, y_train)
    pred = wine_tree_cv.predict(X_test)
    cv_accuracy.append(accuracy_score(y_test, pred))
    
cv_accuracy
1
2
3
4
5
[0.6007692307692307,
 0.6884615384615385,
 0.7090069284064665,
 0.7628945342571208,
 0.7867590454195535]

2.8 각 acc의 분산이 크지 않다면 평균을 대표값으로

1
np.mean(cv_accuracy)
1
0.709578255462782

2.9 StratifiedKFold

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sklearn.model_selection import StratifiedKFold

skfold = StratifiedKFold(n_splits= 5)
wine_tree_cv = DecisionTreeClassifier(max_depth=2, random_state=13)

cv_accuracy = []

for train_idx, test_idx in skfold.split(X, y):
    X_train, X_test = X.iloc[train_idx], X.iloc[test_idx]
    y_train, y_test = y.iloc[train_idx], y.iloc[test_idx]
    wine_tree_cv.fit(X_train, y_train)
    pred = wine_tree_cv.predict(X_test)
    cv_accuracy.append(accuracy_score(y_test, pred))
    
cv_accuracy
1
2
3
4
5
[0.5523076923076923,
 0.6884615384615385,
 0.7143956889915319,
 0.7321016166281755,
 0.7567359507313318]

2.10 acc의 평균이 더 나쁨

1
np.mean(cv_accuracy)
1
0.6888004974240539
  • 인덱스를 반환하기 때문에 for문으로 각각 데이터를 validation하였다.
  • 하지만 이렇게 불편하게 코드를 하나하나 짜가면서 해야하는것일까?

2.11 cross validation을 보다 더 간편하게

1
2
3
4
5
6
from sklearn.model_selection import cross_val_score

skfold = StratifiedKFold(n_splits=5)
wine_tree_cv = DecisionTreeClassifier(max_depth=2, random_state=13)

cross_val_score(wine_tree_cv, X, y , scoring=None, cv=skfold)
1
array([0.55230769, 0.68846154, 0.71439569, 0.73210162, 0.75673595])
  • 위의 예제처럼 for문 코드를 짜서 돌리는것이 아닌 이미 Sklean에 cross_val_score로 한다
  • sklearn에 cross_val_score에 분류기, X, y, Fold객체를 넣어주면된다
  • skfold의 n_splits = 5는 데이터를 5개로 나누어 validation한다는 뜻

2.12 depth가 높다고 무조건 acc가 높은것도 아님

1
2
3
4
5
6
from sklearn.model_selection import cross_val_score

skfold = StratifiedKFold(n_splits=5)
wine_tree_cv = DecisionTreeClassifier(max_depth=5, random_state=13)

cross_val_score(wine_tree_cv, X, y, scoring=None, cv=skfold)
1
array([0.50076923, 0.62615385, 0.69745958, 0.7582756 , 0.74903772])
  • 의사결정 나무의 max_depth를 늘려서 accuracy를 보았으나, 무조건 높아지는것도 아님을 확인

2.13 train score와 함께 보고싶다면

1
2
from sklearn.model_selection import cross_validate
cross_validate(wine_tree_cv, X, y, scoring=None, cv=skfold, return_train_score= True)
1
2
3
4
{'fit_time': array([0.01485896, 0.01390481, 0.01378489, 0.01321983, 0.0129869 ]),
 'score_time': array([0.00206208, 0.00191903, 0.00187325, 0.00179911, 0.00170922]),
 'test_score': array([0.50076923, 0.62615385, 0.69745958, 0.7582756 , 0.74903772]),
 'train_score': array([0.78795459, 0.78045026, 0.77568295, 0.76356291, 0.76279338])}
  • cross_validate의 return_train_score를 쓰면됨
  • train과 test의 격차를 보니 과적합이 있는듯 하다.
This post is licensed under CC BY 4.0 by the author.

파이프라인(Pipeline)

하이퍼파라미터 튜닝(Hyperparameter Tuning)