Cross Validation

    이번 시간에는 cross validation에 대해서 알아보도록 하겠습니다!✨ cross validation은 평상시 데이터가 부족할 때 사용한다고만 알고 있었는데, 이번에 제대로 알아보고자 공부해보았습니다😎

What is Cross validation?

    보통 큰 데이터 셋이 있다면 실제 모델을 평가하기 위해서 데이터를 훈련용, 검증용, 테스트용 이렇게 세 가지로 분리하는 것이 일반적입니다. 훈련용 데이터로는 데이터 훈련을, 검증용 데이터로는 모델의 성능을 조절하게 됩니다. 모델이 과적합이 되고 있는지 판단하거나 훈련용 데이터로 훈련을 시킨 모델의 정확도검증용 데이터를 사용하여 검증하면서 하이퍼 파라미터를 조정(hyper parameter tuning)을 하게 됩니다.

    이런 과정이 끝나게 된다면 검증용 데이터에 대해서 일정 부분 최적화가 진행되었기 때문에 검증용 데이터로 모델을 평가하는 것이 적합하지 않습니다. 이에 테스트 데이터를 통해 모델의 진짜 성능을 평가하게 됩니다.

    평상시 대회에서는, test data의 label은 모르는 상태에서 data의 label을 맞춰야 하므로 주어진 training dataset을 training set과 validation set을 나눠야합니다. 하지만 training dataset이 작아서 traning set과 validation set으로 나누지 못하는 경우 cross validation을 사용합니다. 오늘 알아볼 cross validation에는 K-Fold cross validation, Startified-K-Fold cross validation이 있습니다.

K-Fold cross validation

    K-Fold cross validation은 전체 데이터 셋을 training set과 test set으로 나눈 뒤, training set을 다시 training set과 validation set으로 나누는데 이 때, k개의 fold로 나누게 됩니다. k개 의 fold가 k번 동안 차례대로 validation set이 되고 나머지 fold는 training set이 됩니다. 아래의 그림은 방금한 설명을 도식화 한 것입니다.

< How to implement cross validation? >

assets 2021 08 26 1

    해당 그림을 보게되면 ALL Data(모든 데이터)를 Training data와 Test data로 나눈 것을 볼 수 있습니다. 이후 5-Fold cross validation을 사용했는데 5-Fold 이기 때문에 5번 동안 5개의 Fold가 한번씩 validation set이 되며 진행됩니다(나머지 4개의 fold는 학습데이터가 됩니다.). 이렇게 학습하면서 검증을 하게 되며 학습이 끝난 이후에 Test data를 통해 마지막 evaluation을 거치게 됩니다.

Code

    사이킷런을 사용하면 K-Fold cross validation을 손쉽게 구현할 수 있습니다. 아래의 코드와 같이 사용할 수 있습니다.

from sklearn.model_selection import KFold

X=np.array([
    [ 1, 2, 3, 4],
    [11,12,13,14],
    [21,22,23,24],
    [31,32,33,34],
    [41,42,43,44],
    [51,52,53,54],
    [61,62,63,64],
    [71,72,73,74]
])

y=np.array([0,0,0,0,0,0,0,0])

splits = KFold(n_splits=5, random_state=0, shuffle=False)

for fold, (train_idx,val_idx) in enumerate(splits.split(np.arange(len(X)))):
	X_train, X_validate = X[train_idx], X[val_idx]
  y_train, y_validate = y[train_idx], y[val_idx]

    X라는 독립 변수이며, y는 종속변수 입니다. Xyn_splits개의 fold로 나누어 n_splits번 만큼 training과 validation이 진행 됩니다. 그림과 같은 방법으로 K-Fold를 구현할 수 있습니다.

    K-Fold cross validation의 기본 코드을 살펴보았으니 K-Fold cross validation을 실제로 pytorch에서 어떤 구조로 구현되는지 살펴 보겠습니다.

# k-fold
for fold, (train_ids, test_ids) in enumerate(kfold.split(dataset)):
# epoch
	for epoch in range(0, num_epochs):
# trainloader
		for i, data in enumerate(trainloader, 0):

    kfold를 먼저 사용하여 첫 for문을 돌린 이후, 해당 kfold를 통해 정해진 training dataset을 통해 epoch를 돌립니다. 또한, dataloader를 사용하여 정해진 batch-size대로 data를 받아와서 정해진 epoch 만큼 k번 돌아가게 됩니다. 1개의 fold 당 num_epochs만큼 돌기 때문에 전체 epoch는 num_epochs* fold가 됩니다.

    아래는 classification task에 cross validation을 적용한 code입니다.

link: Deep MNIST CNN using cross validation

    다음은 SubsetRandomSampler를 이해하고자 pytorch documentation에 있는 source를 가져온 것입니다. 아래의 코드에 대해 살펴보겠습니다.

[docs]class SubsetRandomSampler(Sampler[int]):
    r"""Samples elements randomly from a given list of indices, without replacement.

    Args:
        indices (sequence): a sequence of indices
        generator (Generator): Generator used in sampling.
    """
    indices: Sequence[int]

    def __init__(self, indices: Sequence[int], generator=None) -> None:
        self.indices = indices
        self.generator = generator

    def __iter__(self) -> Iterator[int]:
        return (self.indices[i] for i in torch.randperm(len(self.indices), generator=self.generator))

    def __len__(self) -> int:
        return len(self.indices)

    indices parameter에 a set of index가 들어 가므로 __iter__len(self.indices)에 a set of index의 갯수가 들어가게 됩니다. 이후, torch.randperm()의 parameter로 들어가게 되어 len(indices) 크기의 배열에 len(indices)미만의 숫자들이 중복 없이 정렬되게 됩니다. 이를 통해 0번째 index부터 len(indices)-1까지 차례대로 for문을 통해 가져와서 indices안에 있는 값들을 뽑아서 return하게 됩니다. 즉, random하게 a set of index안에 있는 index들을 뽑아내기 위해 SubsetRandomSampler를 사용하는 것입니다. 이 SubsetRandomSampler 덕분에 torch.utils.data.Dataloader를 사용할 때 shuffle = True를 사용하지 않아도 됩니다.

Startified-K-Fold cross validation

    Startified-K-Fold cross validation하나의 fold안으로 들어갈 label들의 종류가 골고루 들어갈 수 있도록 조정해주는 역할까지 수행합니다. StartifiedKFold의 사용방법은 아래와 같습니다.

import numpy as np
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import KFold

X=np.array([
    [ 1, 2, 3, 4],
    [11,12,13,14],
    [21,22,23,24],
    [31,32,33,34],
    [41,42,43,44],
    [51,52,53,54],
    [61,62,63,64],
    [71,72,73,74]
])

y=np.array([3,1,1,0,2,3,2,0])

stratifiedkfold = StratifiedKFold(n_splits=2,random_state=0,shuffle=False)
print(X.shape, y.shape)

print("\nStratifiedKFold**************")
for train_index, validate_index in stratifiedkfold.split(X,y):
    print("train index:", train_index, "validate index:", validate_index)
    X_train, X_validate = X[train_index], X[validate_index]
    y_train, y_validate = y[train_index], y[validate_index]
    print("train data")
    print(X_train, y_train)
    print("validate data")
    print(X_validate, y_validate)

다음의 코드의 결과는 아래와 같습니다.

< Result >

assets 2021 08 26 2

    위의 결과를 보게되면 label 값이 골고루 들어갈 수 있도록 분배 된 것을 볼 수 있습니다.

Summary

  • torch.utils.data.Dataloader를 사용해도 모든 데이터를 batchsize로 나눠서 minibatch로 만들어 사용하므로 한 epoch안에서 반드시 모든 데이터를 사용하게 됩니다.
  • k-Fold cross validationk번 동한 for문을 돌며 dataset을 k fold개 만큼 나누게 됩니다. 또한, k-1개의 fold를 사용하여 training set를 만들고 1개의 fold를 사용하여 validation set을 만듭니다. 이 과정을 k번 동안 반복하며 학습과 검증을 k번 진행합니다.
  • SubsetRandomSampler를 사용하면 torch.utils.data.Dataloadershuffle=True를 사용하지 않아도 됩니다.
  • shuffle = True를 사용해서 KFold를 사용할 경우, k-Fold로 split하기 전에 먼저 전체 데이터를 한 번(!) shuffle을 수행합니다. random_staterandom seed와 같은 개념입니다.

이것으로 이번 포스트를 마무리하겠습니다.

감사합니다😉👍

Reference



Written by@[Gunu]
AI, 수학에 관심이 많은 대학생입니다😊

GitHub