본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 시계열 데이터 Cross Validation 시 Data Leakage를 방지하는 3가지 핵심 검증 전략

by Papa Martino V 2026. 4. 25.
728x90

Cross Validation
Cross Validation

 

머신러닝 모델을 구축할 때 교차 검증(Cross Validation)은 모델의 일반화 성능을 평가하는 표준적인 방법입니다. 하지만 일반적인 독립 항등 분포(i.i.d)를 가정하는 정적 데이터와 달리, 시계열 데이터(Time Series Data)는 시간의 흐름에 따른 순서 의존성이 존재합니다. 이 특성을 무시하고 일반적인 K-Fold를 적용하면 미래의 데이터가 과거의 학습에 관여하는 데이터 누수(Data Leakage)가 발생하여, 실전에서는 작동하지 않는 '과적합된 쓰레기 모델'을 만들게 됩니다. 본 포스팅에서는 시계열 분석의 전문성을 높이고 실무에서 즉시 활용 가능한 데이터 누수 방지 검증 전략을 심층적으로 다룹니다.


1. 왜 시계열에서는 일반 K-Fold가 위험한가?

일반적인 K-Fold 방식은 데이터를 무작위로 섞어(Shuffle) 학습셋과 검증셋을 나눕니다. 그러나 시계열 데이터에서 2024년의 데이터를 학습하고 2023년의 데이터를 예측하는 시나리오는 현실적으로 불가능합니다. 이를 'Look-ahead Bias'라고도 부르며, 모델은 이미 미래의 변동성을 학습한 상태로 과거를 예측하게 되어 성능이 비정상적으로 높게 측정되는 착시 현상을 일으킵니다.

검증 전략별 특성 비교

전략 명칭 데이터 순서 유지 Data Leakage 위험도 주요 용도
Standard K-Fold X (무작위) 매우 높음 이미지 분류, 일반 회귀
TimeSeriesSplit O (확장 창) 매우 낮음 주가, 수요 예측 기본형
Blocked CV O (고정 창) 낮음 정상성(Stationarity) 강한 데이터
Purged K-Fold O (간격 제거) 최소화 금융 시계열, 중첩된 라벨

2. 실무 적용을 위한 7가지 Python 구현 예제 (Example)

개발자가 실무 파이프라인에 바로 복사하여 사용할 수 있는 검증 코드 스니펫입니다. scikit-learnpandas를 기반으로 작성되었습니다.

Example 1: 기초적인 TimeSeriesSplit 활용

가장 표준적인 확장 창(Expanding Window) 방식입니다.

from sklearn.model_selection import TimeSeriesSplit
import numpy as np

X = np.array([[1, 2], [3, 4], [1, 2], [3, 4], [5, 6], [7, 8]])
y = np.array([1, 2, 3, 4, 5, 6])

tscv = TimeSeriesSplit(n_splits=3)
for train_index, test_index in tscv.split(X):
    print("TRAIN:", train_index, "TEST:", test_index)
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]

Example 2: Gap을 포함한 TimeSeriesSplit (최신 Sklearn 버전)

학습 데이터와 검증 데이터 사이에 시간적 공백(Gap)을 두어 정보 전이를 방지합니다.

tscv = TimeSeriesSplit(n_splits=5, gap=2)
# gap 파라미터를 통해 학습셋 마지막 지점과 테스트셋 시작 지점 사이의 데이터 유출을 차단합니다.

Example 3: Rolling Window (Sliding Window) CV 구현

과거 데이터가 너무 오래되어 현재와 연관성이 낮을 때 사용하는 고정 크기 학습 창 방식입니다.

class RollingWindowSplit:
    def __init__(self, train_size, test_size):
        self.train_size = train_size
        self.test_size = test_size

    def split(self, data):
        n_samples = len(data)
        for i in range(0, n_samples - self.train_size - self.test_size + 1, self.test_size):
            yield (np.arange(i, i + self.train_size), 
                   np.arange(i + self.train_size, i + self.train_size + self.test_size))

rw = RollingWindowSplit(train_size=100, test_size=20)

Example 4: Purged K-Fold CV (금융 데이터 특화)

라벨링된 데이터가 중첩될 때, 테스트셋 전후의 데이터를 제거(Purging)하는 고급 기법입니다.

def get_purged_indices(df, train_indices, test_indices, overlap_window):
    # 테스트 기간과 겹치는 학습 인덱스를 제거하는 로직
    purged_train = [i for i in train_indices if i < min(test_indices) - overlap_window or i > max(test_indices)]
    return np.array(purged_train)

Example 5: Walk-Forward Validation 파이프라인

실제 프로덕션 환경에서 모델을 재학습시키며 예측하는 과정을 시뮬레이션합니다.

history_x = list(X_initial)
history_y = list(y_initial)
predictions = []

for t in range(len(test_data)):
    model.fit(history_x, history_y)
    yhat = model.predict(test_data[t])
    predictions.append(yhat)
    history_x.append(test_data[t])
    history_y.append(actual_test_y[t])

Example 6: GroupTimeSeriesSplit (다변량/다중 그룹 시계열)

여러 상점이나 여러 종목의 시계열이 섞여 있을 때 그룹이 찢어지지 않게 분할합니다.

from sklearn.model_selection import GroupTimeSeriesSplit

groups = df['store_id'].values
gtscv = GroupTimeSeriesSplit(n_splits=3)
for train_idx, test_idx in gtscv.split(X, y, groups=groups):
    # 동일 상점의 데이터 순서가 유지되면서 분할됨
    pass

Example 7: 시계열 데이터 스케일링 시 Leakage 방지

가장 빈번한 실수입니다. 전체 데이터가 아닌 Train set에서만 fit해야 합니다.

from sklearn.preprocessing import StandardScaler

for train_idx, test_idx in tscv.split(X):
    scaler = StandardScaler()
    # 반드시 학습 데이터로만 기준(평균, 표준편차)을 잡음
    X_train_scaled = scaler.fit_transform(X[train_idx])
    # 테스트 데이터는 학습 데이터의 기준으로 변환만 수행
    X_test_scaled = scaler.transform(X[test_idx])

3. 데이터 누수를 방지하는 체크리스트

  • 미래 데이터 참조 금지: rolling.mean() 등을 사용할 때 shift() 처리가 되었는지 확인하십시오.
  • 전처리 시점: 결측치 대체(Imputation)나 스케일링은 반드시 각 Fold의 내부 Train 데이터 기준으로 이루어져야 합니다.
  • Target Encoding 주의: 시계열에서 타겟 인코딩을 사용할 경우, 반드시 과거 데이터의 타겟값만 사용하도록 순차적 계산을 적용해야 합니다.

4. 결론 및 요약

시계열 모델의 성패는 알고리즘 자체가 아니라 '얼마나 엄격하게 검증 체계를 구축했는가'에 달려 있습니다. TimeSeriesSplit을 기본으로 하되, 비즈니스 도메인의 특성(예: 주말 효과, 이벤트 간격)에 따라 Gap을 설정하거나 Purging 기법을 도입하는 것이 데이터 과학자의 역량입니다.

 

참고 문헌 (Sources):

  • Hyndman, R.J., & Athanasopoulos, G. (2021) Forecasting: Principles and Practice, 3rd edition.
  • Marcos Lopez de Prado (2018), Advances in Financial Machine Learning.
  • Scikit-learn Documentation: "Visualizing Cross-validation Behavior in Scikit-learn".
728x90