TL;DR
- AI Sales Forecasting에서 피처 기반 머신러닝(GBDT)은 “시계열을 회귀 문제로 변환”해 대량 SKU/매장 예측을 안정적으로 확장합니다.
- 핵심은 (1) 라그/롤링/캘린더/외생변수 설계, (2) 누수 방지(point-in-time), (3) 롤링 오리진 백테스트, (4) WAPE 중심 평가, (5) 분위수(quantile)로 불확실성까지 한 번에 엮는 것입니다.
- 이번 편에서는 “실무 파이프라인”을 그대로 따라 만들 수 있게 데이터 스키마, 피처 분류, 학습/검증, 운영 체크리스트를 제공합니다.
본문
TOC
- 피처 기반 ML 판매 예측의 정의와 범위
- 데이터 스키마: (store, item) 패널을 “롱 포맷”으로 고정
- 피처 설계: 라그/롤링/캘린더/외생변수(Static/Dynamic/Calendar)
- 누수 방지: point-in-time 조인과 timestamp key
- 학습 데이터 생성: horizon·전략(Recursive vs Direct)·라벨링
- 검증: 롤링 오리진 + TimeSeriesSplit + WAPE
- 불확실성: 분위수 예측(Quantile regression)
- 트러블슈팅 / 체크리스트 / FAQ
1) 피처 기반 ML 판매 예측: 정의, 포함/제외, 대표 오해
1문장 정의
AI Sales Forecasting의 피처 기반 ML 접근은 시계열을 “라그·롤링·캘린더·외생변수”로 피처화해, 회귀(GBDT 등) 모델로 판매량을 예측하는 설계입니다.
포함/제외 범위
- 포함: 라그/이동통계(rolling), 캘린더(요일·휴일), 가격·프로모션 같은 외생변수, 글로벌 모델(여러 시계열을 한 모델로 학습), 롤링 백테스트.
- 제외: 순수 통계모형(ARIMA/ETS)만으로 끝내는 구성, “미래를 알 수 없는 변수”를 미래값처럼 넣는 설계, 랜덤 셔플 CV.
대표 오해 1개
- 오해: “GBDT는 시계열에 약하니 딥러닝만 답이다”
- 사실: 시계열을 올바른 피처와 누수 방지 규칙으로 회귀 문제로 바꾸면, 대규모 실무(다수 SKU/점포)에서 GBDT가 빠르고 강하게 먹힙니다. (핵심은 피처와 검증입니다.)
Why it matters: 피처 기반 ML은 “모델 선택”보다 “데이터·피처·검증”이 성패를 갈라서, 설계 표준을 잡아두면 조직 전체에 재사용이 됩니다.
2) 데이터 스키마를 먼저 고정하세요 (롱 포맷 + 단일 진실)
판매 예측은 결국 아래 3개 컬럼이 기준입니다.
unique_id: 시계열 식별자(예:store_id + item_id)ds: 날짜/시간 (일/주 단위 권장)y: 타깃(판매량/출고량/주문량 등)
이 “롱 포맷”을 고정하면, 멀티 시리즈를 한 파이프라인으로 돌리기 쉬워집니다(글로벌 학습 포함).
Why it matters: 스키마를 고정하면 피처 생성/검증/서빙이 단순해지고, “SKU 추가”가 개발이 아니라 데이터 추가로 끝납니다.
3) 피처 설계: 판매 예측에서 반드시 나눠야 하는 3종(Static/Dynamic/Calendar)
외생변수는 “그냥 컬럼”이 아닙니다. 종류에 따라 예측 시점에 넣는 방법이 달라집니다.
Nixtla는 판매 예측에서 외생변수를 Static / Dynamic(미리 아는 미래) / Calendar(시간에서 파생)로 구분합니다.
피처 타입 표 (실무 기준)
| 피처 타입 | 예시 | 미래 제공 필요? | 누수 위험 | 예측 시 처리 |
|---|---|---|---|---|
| Static (불변) | 매장 타입, 상품 카테고리 | X | 낮음 | 예측 구간에도 그대로 복제 |
| Dynamic (미리 아는 미래) | 가격 계획, 프로모션 플래그 | O | 중간 | 예측 구간(ds+h) 값까지 입력 필요 |
| Calendar (시간 파생) | 요일, 월, 휴일 | X | 낮음 | ds에서 생성 |
| Lag/Rolling (타깃 파생) | y_{t-7}, 7일 이동평균 |
X | 높음(누수 핵심) | 반드시 “t 시점 이전”만 사용 |
Why it matters: 이 분류를 안 하면 “프로모션을 미래값처럼 넣는 누수” 또는 “미래 제공이 필요한 변수를 예측 시점에 못 넣어서 성능이 무너짐” 둘 중 하나가 반드시 터집니다.
4) 누수 방지의 정답: point-in-time(시점정합) 조인
판매 예측에서 가장 흔한 대형 사고는 라벨 시점 이후에 관측된 피처를 학습에 섞는 것(데이터 누수)입니다.
Databricks는 이를 막기 위해 point-in-time correctness와 timestamp key 기반의 "as-of 조인"을 강조합니다. 라벨이 기록된 시점 기준으로 "그 시점까지 최신 피처만" 붙여야 합니다.
구현 체크포인트 (Feature Store를 쓰든 안 쓰든 동일)
- 라벨 테이블(판매량)에는
unique_id, ds, y가 있고, - 피처 테이블(가격/재고/프로모션 등)에는
unique_id+feature_timestamp(또는ds)가 있으며, - 조인은 "feature_timestamp <= ds"에서 가장 최신 1건을 붙입니다(as-of).
Why it matters: 누수는 검증 점수를 비정상적으로 좋게 만들고, 운영에 올리면 그대로 폭락합니다. point-in-time을 표준으로 못 박으면 이 리스크가 거의 사라집니다.
5) 학습 데이터 생성: Horizon과 전략(Recursive vs Direct)을 먼저 결정
5-1. 예측 지평(horizon) 고정
예: “주간 발주”면 h=7(7일 ahead) 또는 “주 단위”면 h=4(4주 ahead)처럼 업무 의사결정 주기에 맞춰 고정합니다.
5-2. 멀티스텝 전략
- Recursive(재귀): 1-step 모델로 한 칸씩 예측하며, 예측값을 다시 피처로 써서 다음 칸을 예측
- Direct(직접):
h=1,2,3...각 스텝마다 모델을 따로 둠(“one model per step”)
mlforecast는 기본이 recursive이고, 필요하면 "one model per horizon"으로 direct 구성도 안내합니다.
sktime도 회귀로의 reduction에서 strategy(recursive 등)를 명시합니다.
실무 결론(딱 잘라서):
- h가 짧고(1~7) 모델 수를 줄이고 싶으면: Recursive
- h가 길고(14~56) 누적 오차가 문제면: Direct(또는 DirRec/멀티아웃풋)
Why it matters: 전략 선택은 “피처 생성 방식, 학습 데이터 수, 운영 복잡도”를 동시에 결정합니다. 지금 안 정하면 나중에 갈아엎습니다.
6) 검증(백테스트): 롤링 오리진 + TimeSeriesSplit + WAPE
6-1. 롤링 오리진(rolling forecasting origin)
FPP3는 시계열 검증을 "시간 원점을 앞으로 굴리며" 여러 개 테스트 구간 평균으로 평가하라고 정리합니다.
6-2. TimeSeriesSplit(시간 순서 CV)
scikit-learn은 TimeSeriesSplit이 "미래로 학습하고 과거를 평가하는 오류"를 막기 위한 분할이며, 샘플은 동일 간격(equally spaced)이어야 비교 가능한 폴드를 만든다고 명시합니다.
Azure AutoML도 예측 학습 데이터가 규칙 간격이어야 한다고 명시합니다.
6-3. 평가 지표: WAPE를 기본으로 두세요
- WAPE 정의(핵심 형태): (\text{WAPE}=\frac{\sum |y-\hat{y}|}{\sum |y|})
- AWS Forecast도 WAPE를 예측 정확도 지표로 설명합니다.
실무 팁
- "SKU가 많고 볼륨 차이가 큰" 판매 예측에서는 MAPE보다 WAPE가 관리가 쉽습니다(0에 민감한 문제도 완화).
Why it matters: 검증이 설계의 안전장치입니다. 롤링 오리진 + WAPE를 표준으로 잡으면 “모델 교체/피처 추가”가 바로 효과 검증으로 연결됩니다.
7) 구현 예시: MLForecast + LightGBM로 글로벌 판매 예측
아래 코드는 “롱 포맷”(unique_id, ds, y)을 기준으로 라그/롤링/캘린더/외생변수를 엮는 전형적인 골격입니다.
(라이브러리 선택은 고정이 아닙니다. 핵심은 피처 타입 분류 + 누수 방지 + 롤링 검증입니다.)
# pip install mlforecast lightgbm pandas
import pandas as pd
import lightgbm as lgb
from mlforecast import MLForecast
from mlforecast.lag_transforms import RollingMean, ExpandingMean
# df columns: unique_id, ds, y, (optional) static_cols..., dynamic_cols..., calendar cols...
df = df.sort_values(["unique_id", "ds"])
horizon = 14
static_cols = ["store_type", "item_category"] # 불변
dynamic_cols = ["price_plan", "promo_flag"] # 미래 제공 필요(계획값)
# calendar은 ds에서 파생 가능(요일/월/휴일 등)
fcst = MLForecast(
models=lgb.LGBMRegressor(
n_estimators=2000,
learning_rate=0.03,
subsample=0.8,
colsample_bytree=0.8,
random_state=42,
),
freq="D",
lags=[1, 7, 14, 28],
lag_transforms={
7: [RollingMean(window_size=7)],
14: [RollingMean(window_size=14)],
1: [ExpandingMean()],
},
date_features=["dayofweek", "month"],
)
# 학습
train = df[["unique_id", "ds", "y"] + static_cols + dynamic_cols].copy()
fcst.fit(train, static_features=static_cols)
# 예측 시점에 "미래에 알고 있는 dynamic"을 반드시 제공
future_X = future_df[["unique_id", "ds"] + dynamic_cols].copy() # ds는 예측 구간 날짜
pred = fcst.predict(h=horizon, X_df=future_X)
- mlforecast는 lags / lag_transforms로 라그 기반 피처를 구성하는 방법을 문서화합니다.
- 판매 예측에서 외생변수(Static/Dynamic/Calendar) 처리와 "예측 구간에 dynamic 미래값 제공(X_df)" 흐름도 예시로 설명합니다.
Why it matters: 이 골격을 표준 템플릿으로 만들면, 상품군/매장군이 늘어도 파이프라인은 그대로 재사용됩니다.
8) 불확실성까지 같이 내세요: 분위수(Quantile) 예측
판매 예측은 “점 예측”만으로 운영 의사결정을 하기 어렵습니다. P50(기준), P90(상한), P10(하한) 같이 범위를 같이 줘야 발주/재고 정책이 잡힙니다.
8-1. GBDT의 분위수 손실(Quantile loss)
- LightGBM은 objective에
quantile을 포함합니다. - XGBoost는 quantile loss를 위한
reg:quantileerror파라미터를 문서화합니다. - CatBoost도 회귀 손실 함수로 quantile 계열을 제공합니다.
실무 패턴
- P50 모델 1개 + P90 모델 1개(또는 다중 분위수)로 “안전재고/발주 상한”을 같이 제시합니다.
Why it matters: 분위수 예측은 “정확도”보다 “운영 리스크 비용(품절/과재고)”을 줄이는 데 직접 연결됩니다.
9) 트러블슈팅 (자주 터지는 3개)
(1) 검증 점수는 좋은데 운영에서 급락
- 원인: point-in-time 미준수(미래 피처 누수)
- 해결: 라벨 시점 기준 as-of 조인으로 학습셋 재구성(Feature Store면 timestamp key 강제)
Why it matters: 누수는 "운영 장애"입니다. 재현 가능한 규칙으로만 막을 수 있습니다.
(2) "프로모션/가격"을 넣었더니 예측 시점에 못 넣어서 모델이 깨짐
- 원인: Dynamic(미래에 알고 있는 값)인데 예측 구간
X_df제공이 없음 - 해결: 계획 시스템(가격/프로모션 캘린더)에서 예측 구간 값을 공급하고, 없으면 해당 변수를 제거하거나 "시나리오 예측"으로 명시
Why it matters: 피처는 "학습에서 쓰는 것"이 아니라 "예측 시점에 공급 가능한 것"이어야 합니다.
(3) TimeSeriesSplit/AutoML에서 오류 또는 비정상 결과
- 원인: 관측 간격 불규칙(누락일, 임의 집계)
- 해결: 비즈니스 단위(일/주)로 리샘플링/보정 후 모델링(규칙 간격 전제)
Why it matters: 간격이 깨진 시계열은 “검증 비교” 자체가 성립하지 않습니다.
10) 실무 체크리스트 2종
배포 전 체크리스트
-
unique_id, ds, y롱 포맷 고정 - 피처 타입 분류(Static/Dynamic/Calendar/Lag-Rolling) 완료
- point-in-time 조인 규칙(라벨 시점 기준 as-of) 자동화
- 롤링 오리진 백테스트 + WAPE 리포트
- P50/P90 같이 제공(재고·발주 정책 연결)
운영 중 체크리스트
- 예측 구간에 Dynamic 피처 공급이 끊기지 않는지 모니터링
- 데이터 갭(누락일) 감지 및 리샘플링 규칙 적용
- 주간/월간으로 롤링 백테스트 재실행(성능 드리프트 감시)
Why it matters: 운영 체크리스트가 없으면 예측 품질은 “모델”이 아니라 “데이터 사고”로 무너집니다.
FAQ (5개 이상)
Q1. 피처 기반 ML은 언제 가장 강합니까?
대량 SKU/매장 같이 시계열이 많고(패널 데이터), 프로모션·가격 같은 설명 변수가 있는 판매 예측에서 강합니다.
Q2. “미래에 모르는 변수(예: 실제 기온)”는 어떻게 넣습니까?
미래를 모르는 값은 그대로 넣지 않습니다. "예보값(미래에 아는 값)"이 있으면 Dynamic으로 넣고, 없으면 제외하거나 시나리오로 분리합니다(누수 방지).
Q3. 검증은 왜 랜덤 K-Fold가 안 됩니까?
랜덤 분할은 미래 데이터로 학습하고 과거를 평가하는 구조가 되어 시계열에서 성립하지 않습니다. TimeSeriesSplit 같은 시간 순서 분할을 써야 합니다.
Q4. WAPE를 기본 지표로 두는 이유는 뭡니까?
판매량이 0이거나 아주 작은 구간이 많으면 MAPE가 깨지거나 과도하게 튑니다. WAPE는 분모가 전체 볼륨 합이라 실무 모니터링이 쉽습니다.
Q5. Recursive와 Direct 중 뭘 선택해야 합니까?
짧은 horizon이면 Recursive로 단순화하고, 긴 horizon에서 누적 오차가 크면 Direct(스텝별 모델)로 갑니다. mlforecast는 기본 recursive, direct("one model per horizon")도 안내합니다.
Q6. 캘린더 피처는 어떻게 인코딩합니까?
요일/월 같은 주기성은 모델이 주기를 잘 학습하도록 cyclical 인코딩(예: sin/cos) 또는 주기적 변환을 씁니다.
결론 (요약 정리)
- 피처 기반 ML 판매 예측은 스키마 고정 → 피처 타입 분류 → point-in-time → 롤링 백테스트 → WAPE/분위수 순서로 설계합니다.
- GBDT( LightGBM/XGBoost/CatBoost )는 대규모 패널 판매 예측에서 강력한 기본기입니다.
- 다음 편(Part 5)에서는 딥러닝/파운데이션 모델 기반(글로벌) 예측을 “언제 쓰고, 언제 버리는지” 기준으로 정리합니다.
References
- (Point-in-time feature joins - Databricks, 2025-06-20)[https://docs.databricks.com/aws/en/machine-learning/feature-store/time-series]
- (Feature store overview and glossary - Databricks, 2025-12-10)[https://docs.databricks.com/aws/en/machine-learning/feature-store/concepts]
- (Point-in-time feature joins - Azure Databricks, 2025-06-20)[https://learn.microsoft.com/en-us/azure/databricks/machine-learning/feature-store/time-series]
- (Lag features for time-series forecasting in AutoML - Microsoft Learn, 2025-02-26)[https://learn.microsoft.com/en-us/azure/machine-learning/concept-automl-forecasting-lags?view=azureml-api-2]
- (TimeSeriesSplit - scikit-learn, 2025)[https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.TimeSeriesSplit.html]
- (Time series cross-validation - FPP3, 2021)[https://otexts.com/fpp3/tscv.html]
- (WAPE: Weighted Absolute Percentage Error - Rob J Hyndman, 2025-08-08)[https://robjhyndman.com/hyndsight/wmape.html]
- (Evaluating Predictor Accuracy - AWS Docs)[https://docs.aws.amazon.com/forecast/latest/dg/metrics.html]
- (Exogenous Variables in MLForecast - Nixtla, 2025-12-05)[https://www.nixtla.io/blog/mlforecast-exogenous-variables]
- (One model per step - Nixtla MLForecast Docs)[https://nixtlaverse.nixtla.io/mlforecast/docs/how-to-guides/one_model_per_horizon.html]
- (Lag transformations - Nixtla)[https://nixtlaverse.nixtla.io/mlforecast/docs/how-to-guides/lag_transforms_guide.html]
- (make_reduction - sktime)[https://www.sktime.net/en/v0.19.2/api_reference/auto_generated/sktime.forecasting.compose.make_reduction.html]
- (LightGBM Parameters)[https://lightgbm.readthedocs.io/en/latest/Parameters.html]
- (XGBoost Parameters)[https://xgboost.readthedocs.io/en/stable/parameter.html]
- (CatBoost regression loss functions)[https://catboost.ai/docs/en/concepts/loss-functions-regression]
- (Time-related feature engineering - scikit-learn)[https://scikit-learn.org/stable/auto_examples/applications/plot_cyclical_feature_engineering.html]
- (Frequently asked questions about forecasting in AutoML - Azure)[https://learn.microsoft.com/en-us/azure/machine-learning/how-to-automl-forecasting-faq?view=azureml-api-2]
'AI > Technical' 카테고리의 다른 글
| AI Sales Forecasting 7: 운영(MLOps) 설계—모니터링·드리프트·재학습·릴리즈 (3) | 2026.02.10 |
|---|---|
| AI Sales Forecasting 5: 딥러닝·파운데이션 모델로 판매 예측 설계 (4) | 2026.02.10 |
| AI Sales Forecasting 백테스트 설계: Rolling CV·베이스라인·리포트 (3) (0) | 2026.02.09 |
| AI Sales Forecasting 판매 예측 데이터 모델링 템플릿 (2) (4) | 2026.02.09 |
| AI Sales Forecasting: AI 기반 판매 예측 설계 로드맵 (1) (0) | 2026.02.08 |