본문 바로가기
Programming/Time series forecasting

시계열 모델의 교차검증 (cross-validation) 전략 (파이썬 코드 포함)

by a voyager 2021. 5. 4.
728x90
반응형

두 가지 time series 교차검증 (cross-validation) 방법 

 

교차검증 cross validataion (CV) 은 데이터 모델링에 있어 모델의 정확도를 확인할 수 있는 효율적인 방법이다. 이것은 데이터를 훈련용과 테스트용을 순서 없이 나누는 과정을 포함한다. 하지만, 전후 데이터 사이의 상관관계가 존재하는 시계열 데이터(time series data)를 모델링할 때는 기존의 사용하는 CV를 적용할 수 없다. 이때는 시간순으로 나열된 데이터를 보존하면서 훈련용과 테스트용으로 데이터를 나눠야 한다. 다음의 두 가지 방법이 대표적으로 사용된다. 

 

Time series cross validation /  blocked time cross validation 

 

Helper function: visualizing cross-validation indices for many CV objects

우선 교차검증 시 데이터 분할이 어떻게 이루어지는지 그려 볼 수 있게 해주는 helper function을 가져오자. 이 함수는 scikit learn의 홈페이지에 있는 예제 함수를 가져와 약간 편집한 것이다. 

 

from sklearn.model_selection import TimeSeriesSplit 
from matplotlib.patches import Patch
import matplotlib.pyplot as plt 

cmap_data = plt.cm.Paired
cmap_cv = plt.cm.coolwarm
plt.style.use('fivethirtyeight')


def plot_cv_indices(cv, X, n_splits, lw=10):
    
    fig, ax = plt.subplots()
    """Create a sample plot for indices of a cross-validation object."""

    # Generate the training/testing visualizations for each CV split
    for ii, (tr, tt) in enumerate(cv.split(X=X)):
        # Fill in indices with the training/test groups
        indices = np.array([np.nan] * len(X))
        indices[tt] = 1
        indices[tr] = 0

        # Visualize the results
        ax.scatter(range(len(indices)), [ii + .5] * len(indices),
                   c=indices, marker='_', lw=lw, cmap=cmap_cv,
                   vmin=-.2, vmax=1.2)

    # Formatting
    yticklabels = list(range(n_splits))
    ax.set(yticks=np.arange(n_splits) + .5, yticklabels=yticklabels,
           xlabel='Sample index', ylabel="CV iteration",
           ylim=[n_splits+0.1, -.1], xlim=[0, len(X)])
    ax.set_title('{}'.format(type(cv).__name__), fontsize=15)
    
    ax.legend([Patch(color=cmap_cv(.8)), Patch(color=cmap_cv(.02))],
          ['Testing set', 'Training set'], loc=(1.02, .8))

 

 

두 가지 cross-validation 방법 

1. time series cross-validation 

우선 np.array으로 0~99까지 데이터 셋을 만들어보자. 포스팅의 목적상, 이것을 시간순으로 기록된 시계열 데이터라고 가정해보자. 

 

TimeSeriesSplit이라는 sklearn 라이브러리 함수를 사용한다. 입력 변수는 몇 번의 반복으로 교차검즘을 할 것인지 정하는 것이다. n_split으로 표기하고 5를 주었다. 이것은 5개의 훈련용+검증용 데이터셋을 만든다는 것을 의미한다. 

 

# plotting with a simple array data 

XX = np.arange(100)

n_split = 5

tscv = TimeSeriesSplit(n_splits=n_split)

plot_cv_indices(tscv, XX, n_splits=n_split)

 

결과는 다음과 같다. x축은 sample index 즉, 시간이 될 것이다. y축은 교차검증의 횟수이다. n_split에서 입력한 대로 다섯 번의 교차검증을 위한 준비가 된 것을 볼 수 있다.

 

여기에서 주목할 점은 훈련용 데이터는 검증용보다 항상 앞선 시간으로 할당되었다는 것이다. 즉, 모델링을 통해 미래의 데이타를 예측하려는 것이기 때문에 과거 시간을 훈련용으로 쓰는 것이다. 

 

 

 

2. blocked cross-validation 

두번째 방법은 훈련용과 검증용 데이터의 크기를 모든 교차검증 횟수에서 고정시키는 것이다. 이것을 지원하는 라이브러리가 없기 때문에 다음과 같이 generator 클래스를 정의한다. 

 

class BlockingTimeSeriesSplit():
    def __init__(self, n_splits):
        self.n_splits = n_splits
    
    def get_n_splits(self, groups):
        return self.n_splits
    
    def split(self, X, y=None, groups=None):
        n_samples = len(X)
        k_fold_size = n_samples // self.n_splits
        indices = np.arange(n_samples)
    
        margin = 0
        for i in range(self.n_splits):
            start = i * k_fold_size
            stop = start + k_fold_size
            mid = int(0.8 * (stop - start)) + start
            yield indices[start: mid], indices[mid + margin: stop]

 

위의 클래스를 이용해 데이터를 나누고 교차검증을 위한 분할이 어떻게 이루어지는지 확인한다. 

 

btscv = BlockingTimeSeriesSplit(n_splits=n_split)
plot_cv_indices(btscv, XX, n_splits=n_split)

 

결과에서 볼 수 있듯이 각 교차검증에서 사용된 데이터의 크기가 동일하게 나누어진 것을 볼 수 있다. 여전히 훈련용 데이터는 검증용 보다 앞선 시간에 위치해 있다. 

 

 

References

728x90
반응형

댓글