Post

2.4 교차 검증

2.4 교차 검증


일반적으로 훈련 데이터로 모델을 훈련하고, 테스트 데이터로 예측해 모델 성능을 측정한다. 모델을 훈련만 하고, 성능을 검증해보지 않으면 두 가지 문제가 발생할 수 있다.

  1. 모델이 과대적합될 가능성이 있다.
    • 고정된 훈련 데이터만을 활용해 반복해서 훈련하면 모델이 훈련 데이터에만 과대적합될 가능성이 있다.
  2. 모델 성능을 확인하기 어렵다.
    • 전체 데이터를 훈련 데이터와 검증 데이터로 나눈 뒤, 검증 데이터로 성능을 가늠해볼 수 있지만 그럴 경우 검증 데이터는 훈련에 사용하지 못해 손해이다. 실무에서도 마찬가지로 아직 주어지지 않은 미래 데이터로 미리 테스트해볼 수 없다. 즉 실제 서비스에 적용하기 전에는 모델 성능을 가늠해볼 수 없다.


이상의 두 문제를 개선하기 위한 방법이 교차 검증이다. 교차 검증은 훈련 데이터를 여러 그룹으로 나누어 일부는 훈련 시 사용하고, 일부는 검증 시 사용해서 모델 성능을 측정하는 기법이다. 가장 일반적인 교차 검증 기법은 K 폴드 교차 검증이다.




1. K 폴드 교차 검증 (K-Fold Cross Validation)


K 폴드 교차 검증 절차는 다음과 같다.

  1. 전체 훈련 데이터를 K개 그룹으로 나눈다.

  2. 그룹 하나는 검증 데이터로, 나머지 K - 1개는 훈련 데이터로 지정한다.

  3. 훈련 데이터로 모델을 훈련하고, 검증 데이터로 평가한다.

  4. 평가점수를 기록한다.

  5. 검증 데이터를 다른 그룹으로 바꿔가며 2~4 절차를 K번 반복한다.

  6. K개 검증 평가점수의 평균을 구한다.


K개 검증 평가점수의 평균이 최종 평가점수이다.


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

data = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

folds = KFold(n_split = 5, shuffle = False)

for train_idx, valid_idx in folds.split(data):
	print(f'훈련 데이터: {data[train_idx]}, 검증 데이터: {data[valid_idx]}')


'''
훈련 데이터: [2 3 4 5 6 7 8 9], 검증 데이터: [0 1]
훈련 데이터: [0 1 4 5 6 7 8 9], 검증 데이터: [2 3]
훈련 데이터: [0 1 2 3 6 7 8 9], 검증 데이터: [4 5]
훈련 데이터: [0 1 2 3 4 5 8 9], 검증 데이터: [6 7]
훈련 데이터: [0 1 2 3 4 5 6 7], 검증 데이터: [8 9]
'''
  • KFold() 는 데이터를 K 폴드로 나누는 함수이다. n_splits 파라미터에 전달하는 값이 K값이다.

  • 여기서는 5로 설정하여 데이터가 총 5개로 나뉜다. 폴드가 5개이므로 검증 데이터는 [0, 1], [2, 3], [4, 5], [6, 7], [8, 9]가 된다.


데이터가 편향되게 분포되어 있을 수도 있기 떄문에 폴드로 나누기 전에 데이터를 섞어주는게 좋다. shuffle 파라미터에 True를 전달하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
folds = KFold(n_split = 5, shuffle = True)

for train_idx, valid_idx in folds.split(data):
	print(f'훈련 데이터: {data[train_idx]}, 검증 데이터: {data[valid_idx]}')


'''
훈련 데이터: [0 3 4 5 6 7 8 9], 검증 데이터: [1 2]
훈련 데이터: [0 1 2 3 5 6 7 9], 검증 데이터: [4 8]
훈련 데이터: [1 2 3 4 5 6 7 8], 검증 데이터: [0 9]
훈련 데이터: [0 1 2 3 4 7 8 9], 검증 데이터: [5 6]
훈련 데이터: [0 1 2 4 5 6 8 9], 검증 데이터: [3 7]
'''





2. 층화 K 폴드 교차 검증 (Stratified K-Fold Cross Validation)

층화 K 폴드 교차 검증은 타겟값이 골고루 분포되게 폴드를 나누는 K폴드 교차 검증 방법이다. 타겟값이 불균형하게 분포되어 있는 경우 층화 K 폴드를 사용하는게 좋다.

일반 메일과 스팸 메일을 분류하는 문제를 생각해보자. 받은 메일이 1,000개인데, 그중 스팸은 단 10개라고 가정해보면 스팸이 10개밖에 안되므로 K 폴드 교차 검증을 해도 특정 폴드에는 스팸이 아예 없을 수 있다. 스팸 데이터 없이 모델을 훈련하면 스팸 예측을 제대로 하지 못하게 된다.

이처럼 특정 타겟값이 다른 타겟값 보다 굉장히 작은 경우 주로 층화 K 폴드 교차 검증을 사용한다. 이 방식은 스팸 데이터를 모든 폴드에 균일하게 나누어 준다. 즉, 폴드가 5개면 각 폴드에 스팸 데이터를 2개씩 골고루 분배해서 교차 검증을 수행한다.


층화 K 폴드 교차 검증은 분류 문제에만 쓰인다. 회귀 문제의 타겟값은 연속된 값이기 때문에 폴드마다 균등한 비율로 나누는 게 불가능하기 때문이다.


아래 코드는 일반 메일 45개 스팸 메일 5인 데이터를 K 폴드로 나눈 코드이다.

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
import numpy as np
from sklearn.model_selection import KFlod

y = np.array(['스팸'] * 5 + ['일반'] * 45)

folds = KFold(n_splits = 5, shuffle = True)

for idx, (train_idx, valid_idx) in enumerate(folds.split(y)):
	print(f'Fold {idx + 1} 검증 데이터 타겟값: ')
	print(y[valid_idx], '\n')

'''
Fold 1 검증 데이터 타겟값: 
['스팸' '스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 2 검증 데이터 타겟값: 
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 3 검증 데이터 타겟값: 
['스팸' '스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 4 검증 데이터 타겟값: 
['일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 5 검증 데이터 타겟값: 
['일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 
'''


결과를 보면 검증 데이터 타겟값에 스팸이 없는 폴드가 있다. 이러면 학습이 제대로 되었는지 판단하기 어렵다. 따라서 모든 폴드에 스팸 데이터가 있게 하려면 층화 K 폴드를 사용하면 된다.

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
from sklearn.model_selection import StratifiedKFold

X = np.array(range(50))
y = np.array(['스팸'] * 5 + ['일반'] * 45)

folds = StratifiedKFold(n_splits = 5)

for idx, (train_idx, val_idx) in enumerate(folds.split(X, y)):
	print(f'Fold {idx + 1} 검증 데이터 타겟값: ')
	print(y[val_idx], '\n')

'''
Fold 1 검증 데이터 타겟값: 
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 2 검증 데이터 타겟값: 
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 3 검증 데이터 타겟값: 
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 4 검증 데이터 타겟값: 
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 5 검증 데이터 타겟값: 
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 
'''

층화 K 폴드 방식은 모든 폴드에 스팸이 1개씩 포함되어 있는것을 확인할 수 있다.

[!] KFold의 split()에는 데이터 하나만 전달해도 된다. 데이터 불균형 여부와 상관없이 임의로 K개로 분할하기 때문이다. 반면 StratifiedKFold의 split() 함수에는 피처와 타겟값 모두를 전달해야 한다.

This post is licensed under CC BY 4.0 by the author.