학습/테스트 데이터셋 분리
머신러닝을 학습시키고 테스트에 사용하는 피처들이 이미 학습데이터에 들어있는 것과 겹친다면, 예측정확도가 1.0이 나오는 과적합(Overfitting) 오류를 범할 수 있다. 마치 모의고사를 여러 번 풀면서 연습했는데, 시험에서 모의고사 문제와 완전히 똑같은 문제가 나오니까 다 맞추는 것이다.
그러므로 예측을 하기 위해서는 사전 학습된 자료가 아닌 테스트 데이터를 준비해줄 필요가 있다. 이를 위해서 사이킷런에서는 sklearn.model_selection
아래의 train_test_split
모듈로 학습데이터와 테스트 데이터를 분리해주는 기능을 제공한다.
# 분리해주는 모듈 불러오기
from sklearn.model_selection import train_test_split
# 학습데이터와 테스트 데이터를 분리.
X_train, X_test = train_test_split(iris_data, test_size=0.2, random_state=11)
y_train, y_test = train_test_split(iris_label, test_size=0.2, random_state=11)
여기서 X는 피처를 의미하고 y는 타겟을 의미한다. 그리고 각 패러미터는
test_size=0.2
: 전체 데이터 중 20%만 테스트로 만들어라.random_state
: 랜덤함수의 시드 같은 역할. 반복가능한 경우로 쓰기 위해 정해진 정수값을 넣는다.라는 의미를 가진다.
교차검증
테스트데이터를 넣기 이전에 학습데이터를 다시 한 번 더 학습용 데이터와 검증용 데이터로 나누어 자체검증을 해준다. 그러면 실제 테스트 데이터 들어가기 전에 학습데이터만으로 검증할 수 있게 된다. 마치 여러 번의 작은 모의고사를 봐서 모의고사 전체의 평균 점수와 실력을 알아내는 것과 같다.
대표적으로 쓰이는 교차검증에는 K 폴드 교차검증(K Fold Cross Validation) 과 Stratified K 폴드 교차검증이 있다.
K 폴드 교차검증이란 학습데이터를 k개만큼 균일하게 나눠서 그 중 하나를 검증용 데이터로 사용하는 방식이다. 만약 k가 5라면 5번의 검증평가를 할 수 있게 된다. k가 커지면 여러 번 검증할 수 있어서 좋지만 연산량 늘어나고 검증용 데이터 하나의 크기가 작아진다. 각각의 검증마다 평가를 해서 검증평가가 k개 나올텐데, 이 k 개의 검증평가 평균으로 전체 검증평가를 할 수 있다.
사이킷런에서는 sklearn.model_selection
패키지 안에 KFold
모듈로 존재한다. 사이킷런 iris 데이터셋으로 K=5 KFold 교차검증을 하면 다음과 같다.
# K 폴드
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 # Feature 데이터
label = iris.target # Label 데이터
dt_clf = DecisionTreeClassifier(random_state=156) # DecisionTreeClassifier 객체 생성
# 5개의 폴드 세트로 분리하는 KFold 객체와 폴드 세트별 정확도를 담을 리스트 객체 생성.
kfold = KFold(n_splits=5)
cross_validation_accuracy = [] # 교차검증 정확도를 담을 리스트 객체 생성.
print(f'붓꽃 데이터 세트 크기: {features.shape}')
# KFold 객체의 split()를 호출하면 폴드 별 학습용, 검증용 테스트의 로우 인덱스를 array로 반환.
n_iter = 0
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(f'#{n_iter} 교차 검증 정확도: {accuracy}, 학습 데이터 크기: {train_size}, 검증 데이터 크기: {test_size}')
print(f'#{n_iter} 검증 세트 인덱스: {test_index}')
cross_validation_accuracy.append(accuracy)
# 개별 iteration별 정확도를 합하여 평균 정확도 계산.
print(f'## 평균 검증 정확도: {np.mean(cross_validation_accuracy)}')
여기서 for 문을 돌 때, kfold.split(features)
부분을 실행하면 이터레이션을 통해 학습용데이터와 테스트용데이터의 인덱스를 파이썬 제너레이터 타입으로 반환한다. 예를 들어, 10개 피처데이터에 K값을 5로 스플릿을 진행하면 다음과 같은 결과를 반환한다.
(array([2, 3, 4, 5, 6, 7, 8, 9]), array([0, 1])) # 첫 번째 반복
(array([0, 1, 4, 5, 6, 7, 8, 9]), array([2, 3])) # 두 번째 반복
(array([0, 1, 2, 3, 6, 7, 8, 9]), array([4, 5])) # 세 번째 반복
(array([0, 1, 2, 3, 4, 5, 8, 9]), array([6, 7])) # 네 번째 반복
(array([0, 1, 2, 3, 4, 5, 6, 7]), array([8, 9])) # 다섯 번째 반복
따라서 위 코드에서도 iris 데이터를 5번 반복하여 교차검증할 수 있도록 인덱스를 이용하여 데이터를 나누어 준다.
층층이 나누어진 K 폴드. 만약 20,000건의 신용카드 정상/비정상 거래 레이블이 있는데, 이 중 딱 100건만 가짜거래가 있다고 생각해보자. 이렇게 불균형한 레이블 데이터 분포를 갖는 경우에는 일반 K 폴드로 균일하게 나눠버리면 학습이 잘 되지 않는다. 그래서 이러한 경우에는 학습데이터에 들어있는 레이블의 분포와 검증데이터에 들어있는 레이블의 분포가 유사하도록 검증데이터를 뽑아내주어야 한다.
사이킷런의 iris 데이터셋을 이용하면 다음과 같이 코드를 짤 수 있다.
# Stratified K Fold
import pandas as pd
import numpy as np
from sklearn.model_selection import StratifiedKFold
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
iris = load_iris()
features=iris.data
label=iris.target
iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df['label'] = iris.target
print(iris_df['label'].value_counts())
dt_clf=DecisionTreeClassifier(random_state=156)
# StratifiedKFold 객체 생성
skfold = StratifiedKFold(n_splits=3)
n_iter = 0
cv_accuracy=[]
# StratifiedKFold의 split() 호출시 반드시 레이블 데이터 셋도 추가 입력 필요.
for train_index, test_index in skfold.split(iris_df, iris_df['label']):
n_iter += 1
X_train, X_test= features[train_index], features[test_index]
y_train, y_test= label[train_index], label[test_index]
label_train = iris_df['label'].iloc[train_index]
label_test = iris_df['label'].iloc[test_index]
# print(f'#{n_iter} train index: {train_index}')
# print(f'#{n_iter} test index: {test_index}')
print(f'## 교차 검증: {n_iter}')
print(f'학습 레이블 데이터 분포:\n{label_train.value_counts()}')
print(f'검증 레이블 데이터 분포:\n{label_test.value_counts()}')
dt_clf.fit(X_train, y_train) # 모델 학습
pred = dt_clf.predict(X_test) # 예측
accuracy = np.round(accuracy_score(y_test, pred)) # 정확도 측정
cv_accuracy.append(accuracy)
print(f'#{n_iter} 교차 검증 정확도: {accuracy}, 학습 데이터 크기: {X_train.shape[0]}, 검증 데이터 크기: {X_test.shape[0]}')
print(f'## 교차 검증별 정확도: {cv_accuracy}')
그러면 학습레이블과 검증레이블의 데이터분포가 각 종류마다 동일하게 분포한다.
>>>
## 교차 검증: 1
학습 레이블 데이터 분포:
label
2 34
0 33
1 33
Name: count, dtype: int64
검증 레이블 데이터 분포:
label
0 17
1 17
2 16
Name: count, dtype: int64
#1 교차 검증 정확도: 1.0, 학습 데이터 크기: 100, 검증 데이터 크기: 50