Distribution Shift 대응 방법
[[model-comparison-weekly|← 주별 모델 비교 결과]]
## Distribution Shift란
모델은 과거 패턴이 미래에도 유지된다는 가정 위에서 동작한다. 비즈니스 환경이 바뀌어 데이터 분포가 달라지는 현상을 **Distribution Shift** 또는 **Concept Drift**라고 한다.
이 경우 test 기간(2025~2026) 평균 판매량이 val 기간(2024~2025)의 2.2배로 증가했고, 모델은 이 변화를 예측하지 못해 MAE가 2.6배 악화됐다.
## 대응 방향 세 가지
### 방향 1 — 최근 데이터만 학습 (Window 축소)
오래된 데이터를 버리고 최근 N년치만 학습한다. 패턴이 바뀐 시점 이전 데이터가 오히려 노이즈가 되기 때문이다.
```python
recent_cutoff = '2022-01-01'
train_recent = weekly[weekly.index >= recent_cutoff]
```
train window를 5년 → 3년 → 2년으로 줄여가며 test MAE 변화를 비교하면, distribution shift가 언제 발생했는지 역으로 파악할 수 있다.
단점: 학습 데이터가 줄어 패턴을 충분히 못 볼 수 있다.
### 방향 2 — 최근 데이터에 가중치 부여 (Sample Weight)
오래된 데이터를 버리지 않고, 최근 데이터를 더 중요하게 반영한다. sklearn 모델의 `sample_weight` 파라미터로 구현한다.
```python
import numpy as np
n = len(X_train)
weights = np.linspace(0.1, 1.0, n) # 오래된 것 0.1 → 최근 1.0
model.fit(X_train, y_train, sample_weight=weights)
```
방향 1보다 부드러운 접근. 오래된 데이터의 패턴도 약하게 유지한다.
### 방향 3 — 주기적 재학습
운영 환경에서 현실적인 방법. 새 데이터가 쌓일 때마다 모델을 다시 학습한다. 재학습 주기(월 1회, 분기 1회 등)를 정해두는 것이 일반적이다.
모델 자체를 바꾸는 게 아니라 학습 데이터를 갱신하는 방식이므로 파이프라인 자동화가 필요하다.
## 실험 방향
방향 1과 2를 같은 데이터로 실험해서 비교:
- train window를 5년/3년/2년으로 바꿔가며 test MAE 비교
- sample_weight 적용 전후 test MAE 비교
## Deep Learning과 Distribution Shift
Distribution shift는 데이터 문제이기 때문에 어떤 모델도 근본적으로 해결하지 못한다. LSTM 같은 DL 모델도 마찬가지다.
### DL이 유리한 부분
**LSTM** — 시계열 전용 구조. 어떤 시점이 중요한지를 데이터에서 스스로 학습한다. Regression은 lag feature를 수동으로 지정해야 하지만 LSTM은 이를 자동화한다.
**Online learning** — 새 데이터가 들어올 때 기존 가중치에서 조금씩 업데이트 가능. sklearn의 전체 재학습보다 shift에 빠르게 적응한다.
**Transfer learning** — 긴 기간으로 pre-train 후 최근 데이터로 fine-tuning. 과거 패턴 기반을 유지하면서 최근 변화를 반영.
### 현실적 비교
| | Regression 계열 | LSTM |
|--|--|--|
| Distribution shift 근본 해결 | ❌ | ❌ |
| 복잡한 시간 패턴 학습 | 제한적 | 유리 |
| 적은 데이터 | 유리 | 불리 |
| Online 업데이트 | 어려움 | 가능 |
| 해석 가능성 | 높음 | 낮음 |
주별 507행은 LSTM이 제대로 학습하기에 빠듯하다. 일별 데이터(2,281행)라면 시도해볼 만하다.
### 학습 순서 권장
1. Distribution shift 대응 실험 (window 축소, sample weight)
2. 월별 파이프라인 완성
3. LSTM vs Regression 직접 비교
## 실험 결과
| 실험 | train 행 수 | MAE | RMSE |
|------|------------|-----|------|
| window_전체 | 405 | 5625.8 | 7990.8 |
| window_5년 | 266 | 5160.7 | 7783.2 |
| **window_3년** | **162** | **4690.2** | **7094.6** |
| window_2년 | 109 | 5515.6 | 8151.5 |
| weight_linear | 405 | 5892.4 | 7981.4 |
| weight_exp | 405 | 5945.7 | 7952.2 |
## 해석
### window 3년이 최적인 이유
window를 줄이면 두 가지 원리가 충돌한다:
- window 줄임 → 최근 패턴 집중 (shift 대응 ↑)
- window 줄임 → 학습 데이터 감소 (패턴 학습 ↓)
3년이 이 둘의 균형점. 계절성 사이클을 3번 반복 학습 가능한 최소 단위다. 2년(109행)은 GradientBoosting이 제대로 학습하기에 부족했다.
### sample weight가 오히려 악화된 이유
shift가 발생한 이후 데이터가 학습 구간에 거의 없는 상황에서 가중치만 조정하면 효과가 없다. 오히려 오래된 안정적인 패턴까지 약화시켜 모델이 불안정해진다.
sample weight는 shift 직전/직후 데이터가 학습 구간에 충분히 있을 때 효과가 있다.
## 핵심 결론
| 결론 | 내용 |
|------|------|
| 최적 window | 3년 |
| window 한계 | 너무 줄이면 데이터 부족으로 역효과 |
| sample weight 한계 | shift 이후 데이터가 학습 구간에 없으면 효과 없음 |
| 근본 해결책 | 주기적 재학습 — shift 이후 데이터가 쌓일 때 모델 갱신 |
window/weight는 재학습 사이를 버티는 보조 수단이고, 진짜 해결책은 새 데이터로 주기적 재학습이다.