TL;DR
- AI Sales Forecasting에서 백테스트는 "점수 뽑기"가 아니라 운영과 같은 조건으로 성능을 검증하는 절차입니다. FPP3는 진짜 예측 오차는 학습 잔차가 아니라 새 데이터에서의 genuine forecasts로 봐야 한다고 정리합니다.
- 기본은 rolling forecasting origin(rolling-origin) + expanding/rolling window + 재학습(refit) 여부 명시입니다.
- 리테일/판매 예측에서는 MAPE만 고집하지 말고 WAPE(볼륨 가중), MASE(스케일드) 같은 지표를 함께 써야 합니다.
- 베이스라인은 최소 Seasonal Naive + ETS(지수평활 계열) 2개를 두고, 모든 모델은 여기에 "상대 개선"으로 보고하세요.
- 분위수(확률) 예측을 한다면 pinball(quantile) loss까지 리포트에 넣어야 의사결정(안전재고/서비스레벨)과 연결됩니다.
본문
TOC
- 사전 요구사항: 백테스트가 “같은 게임”인지 확인
- 단계별 절차: Rolling-origin 백테스트 설계
- 베이스라인 2종(필수)과 구현 예시
- 평가 지표: WAPE/MASE/sMAPE + 분위수 지표
- 검증 방법: 리포트 템플릿(필수 표)과 게이트
- 트러블슈팅 3개
- 운영 팁: 재학습 주기·대시보드·감사/재현성
- FAQ
- 다음 글 예고(Part 4)
1) 사전 요구사항: 백테스트가 “같은 게임”인지 확인
AI Sales Forecasting 백테스트 설계의 1순위는 “운영과 같은 조건”입니다. 첫 2문장만 고정해도 사고가 크게 줄어듭니다: 예측 지평(horizon)과 리드타임/발주 주기가 같아야 합니다(예: 리드타임 7일인데 28일 점수만 좋으면 의미가 약해짐).
1-1. 백테스트 설정 체크(필수)
| 항목 | 예시 | 왜 필요한가 |
|---|---|---|
| horizon | 7/14/28 | 운영 의사결정과 동일해야 함 |
| step(재계산 주기) | 매일/매주 | 운영 배치와 동일해야 함 |
| window | expanding vs rolling | 데이터 비정상성/시즌 변화 대응 |
| refit | 매 fold마다 재학습? | 실제 운영도 재학습한다면 refit가 맞음 |
| cutoff | D+1 확정, 타임존 고정 | 누수 방지(포인트-인-타임 정합) |
FPP3는 예측 정확도 평가는 "학습 잔차"가 아니라 새 데이터에서의 genuine forecasts로 해야 한다고 강조합니다.
Why it matters: 백테스트 설정이 운영과 다르면, 점수는 좋아도 운영 성능은 그대로 무너집니다.
2) 단계별 절차: Rolling-origin 백테스트 설계
Step 1) Rolling-origin(rolling forecasting origin)으로 fold 만들기
FPP3는 이 절차를 rolling forecasting origin이라 부르며, 원점(origin)이 시간에 따라 앞으로 "굴러간다"고 설명합니다.
실무에서는 보통 두 가지 창(window)을 씁니다.
- Expanding window: 학습 구간이 계속 늘어남(장기 패턴/계절성에 유리)
- Rolling window: 학습 구간 길이를 고정하고 앞으로 이동(분포 변화에 유리) — skforecast는 고정 창을 굴리며 테스트하는 backtesting을 설명합니다.
Step 2) “재학습(refit)” 여부를 명시
- 운영이 “매일 재학습”이면 백테스트도 fold마다 refit가 맞습니다.
- 운영이 “주 1회 재학습 + 매일 추론”이면, 백테스트도 그 형태로 맞추는 것이 정직합니다.
sktime는 rolling 환경을 흉내 내는 평가를 위해 evaluate(timeseries CV 기반)를 제공한다고 안내합니다.
Why it matters: refit 여부를 숨기면 모델 비교가 무의미해집니다(특히 드리프트가 큰 판매 데이터).
3) 베이스라인 2종(필수)과 구현 예시
베이스라인은 “최소한의 정직함”입니다. 여기서는 2개만 고정합니다.
- Seasonal Naive: “작년/지난주 같은 요일 값”을 그대로 쓰는 기준
- ETS(지수평활 계열): 추세/계절성을 포함하는 대표 통계 모델(Statsmodels ETSModel)
3-1. Python 예시(단일 시계열)
아래 코드는 “구조 템플릿”입니다. 실제로는 Part 2에서 만든
ds, y뷰를 그대로 쓰면 됩니다.
import numpy as np
import pandas as pd
from statsmodels.tsa.exponential_smoothing.ets import ETSModel # ETS family
# ETSModel docs: https://www.statsmodels.org/stable/generated/statsmodels.tsa.exponential_smoothing.ets.ETSModel.html
def seasonal_naive(y: pd.Series, season_length: int, horizon: int) -> np.ndarray:
# 마지막 시즌 패턴을 반복
last_season = y.iloc[-season_length:].to_numpy()
reps = int(np.ceil(horizon / season_length))
return np.tile(last_season, reps)[:horizon]
def wape(y_true: np.ndarray, y_pred: np.ndarray) -> float:
denom = np.sum(np.abs(y_true))
return float(np.sum(np.abs(y_true - y_pred)) / denom) if denom != 0 else np.nan
def rolling_origin_backtest(
df: pd.DataFrame, # columns: ds, y
horizon: int,
step: int,
min_train_size: int,
season_length: int
):
df = df.sort_values("ds").reset_index(drop=True)
y = df["y"]
rows = []
for cutoff in range(min_train_size, len(df) - horizon + 1, step):
train = y.iloc[:cutoff]
test = y.iloc[cutoff:cutoff + horizon].to_numpy()
# Baseline 1: Seasonal Naive
pred_naive = seasonal_naive(train, season_length, horizon)
# Baseline 2: ETS (simple example; tune error/trend/seasonal per your data)
ets = ETSModel(train, error="add", trend="add", seasonal="add", seasonal_periods=season_length)
ets_fit = ets.fit(disp=False)
pred_ets = ets_fit.forecast(horizon).to_numpy()
rows.append({
"cutoff_ds": df.loc[cutoff - 1, "ds"],
"wape_naive": wape(test, pred_naive),
"wape_ets": wape(test, pred_ets),
})
return pd.DataFrame(rows)
Why it matters: ETS 같은 "강한 통계 베이스라인"을 이기지 못하면, AI 모델을 운영에 넣을 이유가 약합니다.
4) 평가 지표: WAPE/MASE/sMAPE + 분위수 지표
4-1. WAPE(볼륨 가중 MAE)
- Hyndman은 WAPE를 "가중 퍼센트 오차"로 정리하며 공식(가중치 정의 포함)을 제시합니다.
- AWS Forecast 문서도 WAPE를 지표로 설명합니다.
4-2. MASE(Scaled Error)와 sMAPE
FPP3는 "진짜 예측 오차"를 새 데이터에서 평가해야 하고, 다양한 지표군(스케일 의존/백분율/스케일드)을 목적에 맞게 써야 한다는 맥락을 제공합니다.
AutoGluon TimeSeries는 MAE/MAPE/MASE/RMSE/WAPE 등을 예측 평가 지표로 정리합니다.
4-3. 분위수(확률) 예측이면 pinball loss(quantile loss)
- pinball loss는 분위수 예측 평가 지표로 널리 알려져 있고(quantile loss), 정의와 의미가 정리돼 있습니다.
Why it matters: 판매 예측은 "작은 SKU가 많고 0도 많아지는" 순간 MAPE가 쉽게 깨집니다. WAPE/MASE를 같이 두면 KPI가 더 안정적입니다.
5) 검증 방법: 리포트 템플릿(필수 표)과 게이트
5-1. 리포트는 “전체 점수 1개”로 끝내면 실패합니다
리테일 예측은 계층(상품/카테고리/전체)과 구간(프로모션/비프로모션)에 따라 성능이 갈립니다. M5 대회는 리테일 판매 예측에서 계층을 중요하게 다루고, 평가로 WRMSSE를 사용한 것으로 널리 참고됩니다.
(A) 백테스트 요약표(필수)
| 구분 | Baseline(Seasonal Naive) | Baseline(ETS) | Candidate(AI) | Gate |
|---|---|---|---|---|
| WAPE(전체) | ETS 대비 ↓ | |||
| WAPE(프로모션) | 악화 금지 | |||
| WAPE(상위 20% 매출 SKU) | 개선 필수 | |||
| MASE(전체) | ETS 대비 ↓ | |||
| pinball(p50/p90) | 서비스레벨 기준 |
(B) “누수/정합성” 검증 체크(필수)
- fold별 cutoff 이후 데이터가 학습에 들어가지 않는지(시간 기준)
- 테스트 구간이 "동일 길이/동일 간격"인지(특히 TimeSeriesSplit을 쓸 경우)
scikit-learn의 TimeSeriesSplit은 시간 순서를 보존해 "미래로 학습"하는 실수를 피하기 위한 splitter라고 설명합니다.
Why it matters: 리포트는 "모델 자랑"이 아니라 배포 승인 문서입니다. 구간별/계층별로 깨지는 곳을 먼저 드러내야 운영 사고가 줄어듭니다.
6) 트러블슈팅(증상→원인→해결) 3개
- 증상: 오프라인 점수만 좋아지고 운영에서 붕괴
- 원인 후보: 백테스트가 rolling-origin이 아니라 “한 번만 자른 홀드아웃”이거나, cutoff 규칙이 불명확
- 해결: rolling forecasting origin으로 fold를 만들고, refit/step/horizon을 운영과 일치
- 증상: TimeSeriesSplit 사용 시 fold 점수가 들쭉날쭉/데이터가 깨짐
- 원인 후보: 샘플이 "고정 간격"이 아니거나(날짜 누락), series가 섞여 있음
- 해결: 날짜 연속성 확보 + 단일 시계열에만 적용하거나, 패널이면 series_id별로 분리 backtest(또는 sktime의 평가 워크플로 사용)
- 증상: ETS는 이기는데 프로모션 구간만 악화
- 원인 후보: 프로모션 피처가 미래에 “계획값”으로 제공되지 않거나 라벨 품질이 낮음
- 해결: 프로모션은 “미래에 알 수 있는 계획 테이블”만 추론에 사용하고, 프로모션 구간을 별도 KPI로 게이트화
Why it matters: 백테스트가 제대로면, 실패 원인이 "모델"인지 "데이터/운영 조건"인지 바로 갈라집니다.
7) 운영 팁: 재학습 주기·대시보드·감사/재현성
- 재현성(필수): fold 설정(horizon/step/window/refit)과 데이터 컷오프를 설정 파일로 고정하고, 백테스트 결과(요약표/세그먼트별 점수)를 아티팩트로 저장하세요.
- 대시보드(필수): 전체 WAPE만 보지 말고, “프로모션/상위 매출 SKU/신규 SKU” 3개를 최소로 고정하세요.
- 지표 정의(필수): WAPE는 Hyndman 정의(가중치 포함)처럼 수식/분모 처리(0일 때)를 문서로 고정하세요.
Why it matters: 운영에서 진짜 필요한 건 “점수”가 아니라 점수가 깨졌을 때 원인을 역추적할 수 있는 구조입니다.
8) FAQ
Q. 베이스라인은 왜 2개나 필요합니까?
A. Seasonal Naive는 "아무것도 안 한 기준", ETS는 "강한 통계 기준"이라서 둘 다 넘어야 모델 투입 명분이 생깁니다.Q. WAPE는 왜 판매 예측에서 많이 씁니까?
A. Hyndman이 WAPE를 볼륨 가중 퍼센트 오차로 정리했고, AWS Forecast도 WAPE를 지표로 제공합니다.Q. scikit-learn TimeSeriesSplit로 충분합니까?
A. 단일 시계열/고정 간격 조건에서는 유용하지만, 리테일처럼 "다중 series + 누락 날짜"가 흔하면 series별 backtest 또는 시계열 평가 워크플로(sktimeevaluate)가 더 안전합니다.Q. 확률예측은 언제부터 넣어야 합니까?
A. 안전재고/서비스레벨처럼 "비대칭 비용"이 명확할 때부터입니다. 그때는 pinball(quantile) loss를 같이 평가해야 합니다.Q. M5의 WRMSSE 같은 지표를 그대로 써야 합니까?
A. 그대로 쓸 필요는 없지만, 리테일 예측이 "계층/가중"을 중요하게 본다는 점을 참고해 내부 KPI(상위 매출 SKU 가중 등)를 설계하는 데 도움이 됩니다.
9) 다음 글 예고(Part 4)
Part 4에서는 피처 기반 ML(회귀/트리/부스팅)로 넘어가서, Part 2의 데이터 모델을 바탕으로 누수 없는 피처 카탈로그 + 학습/추론 파이프라인을 만듭니다.
결론 (요약 정리)
- AI Sales Forecasting 백테스트는 rolling-origin으로 "운영과 같은 조건"을 재현해야 합니다.
- 베이스라인은 Seasonal Naive + ETS 2개를 고정하고, 모든 비교는 여기서 시작해야 합니다.
- 판매 예측 KPI는 WAPE/MASE를 기본으로 두고, 확률예측이면 pinball loss를 포함하세요.
References
- (Time series cross-validation (rolling forecasting origin) - FPP3, Accessed 2026-02-09)[https://otexts.com/fpp3/tscv.html]
- (Evaluating point forecast accuracy (genuine forecasts) - FPP3, Accessed 2026-02-09)[https://otexts.com/fpp3/accuracy.html]
- (Forecasting with sktime (rolling evaluation), Accessed 2026-02-09)[https://www.sktime.net/en/latest/examples/01_forecasting.html]
- (sktime evaluate (timeseries CV), Accessed 2026-02-09)[https://www.sktime.net/en/stable/api_reference/auto_generated/sktime.forecasting.model_evaluation.evaluate.html]
- (TimeSeriesSplit - scikit-learn docs, Accessed 2026-02-09)[https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.TimeSeriesSplit.html]
- (ETSModel - statsmodels docs, Accessed 2026-02-09)[https://www.statsmodels.org/stable/generated/statsmodels.tsa.exponential_smoothing.ets.ETSModel.html]
- (WAPE (Weighted Absolute Percentage Error) - Rob Hyndman, 2025-08-08)[https://robjhyndman.com/hyndsight/wape.html]
- (Evaluating Predictor Accuracy (WAPE) - AWS Forecast docs, Accessed 2026-02-09)[https://docs.aws.amazon.com/forecast/latest/dg/metrics.html]
- (Forecasting metrics (WAPE, MASE, etc.) - AutoGluon TimeSeries, Accessed 2026-02-09)[https://auto.gluon.ai/dev/tutorials/timeseries/forecasting-metrics.html]
- (M5 Forecasting - Accuracy - Kaggle, Accessed 2026-02-09)[https://www.kaggle.com/competitions/m5-forecasting-accuracy]
- (M5 accuracy competition: Results, findings, and conclusions, 2022-01-01)[https://www.sciencedirect.com/science/article/pii/S0169207021001874]
- (Pinball loss function definition (quantile loss) - Lokad, 2012-02-01)[https://www.lokad.com/pinball-loss-function-definition/]
- (Backtesting forecaster (rolling window) - skforecast docs, Accessed 2026-02-09)[https://skforecast.org/0.15.1/user_guides/backtesting.html]
'AI > Technical' 카테고리의 다른 글
| AI Sales Forecasting 5: 딥러닝·파운데이션 모델로 판매 예측 설계 (4) | 2026.02.10 |
|---|---|
| AI Sales Forecasting 4: 피처 기반 ML로 판매 예측 설계 (1) | 2026.02.09 |
| AI Sales Forecasting 판매 예측 데이터 모델링 템플릿 (2) (4) | 2026.02.09 |
| AI Sales Forecasting: AI 기반 판매 예측 설계 로드맵 (1) (0) | 2026.02.08 |
| vibe coding과 ADHD: 생산성 올리고 사고 줄이는 운영법 (1) | 2026.02.04 |