
머신러닝 모델을 구축할 때 교차 검증(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-learn과 pandas를 기반으로 작성되었습니다.
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".