Problem
판매량 0이 많은 수요 데이터
메뉴별 판매량이 자주 0으로 나타나 단순 회귀 모델이 실제 수요 변동을 충분히 따라가기 어려웠습니다.
ML Project
메뉴 단위 주간 수요 예측을 위한 Hurdle Model 기반 ML 프로젝트
리조트 식음업장의 메뉴 단위 주간 수요를 예측하기 위해
Zero-Inflated 데이터 특성을 분석하고,
Hurdle Model 기반 예측 구조를 설계한 팀 프로젝트입니다.
Tech Skills
판매량 0이 반복되는 운영 데이터를 분석해, 메뉴 단위 주간 수요 예측 문제를 Hurdle Model 기반 구조로 재정의했습니다.
Problem
메뉴별 판매량이 자주 0으로 나타나 단순 회귀 모델이 실제 수요 변동을 충분히 따라가기 어려웠습니다.
Approach
먼저 판매 발생 여부를 예측하고, 판매가 발생한 경우의 수량을 별도로 예측하는 Hurdle Model 구조로 설계했습니다.
Result
Weather ON Hurdle Model이 가장 낮은 SMAPE를 기록했고, AWS AI School 1기 내 1위 성과로 이어졌습니다.
판매량 0이 얼마나 자주, 얼마나 오래 반복되는지 확인하고 단일 회귀 모델의 예측 한계와 비교했습니다. 이를 바탕으로 수요 예측 문제를 판매 발생 여부와 발생 시 판매량을 나누어 예측하는 구조로 재정의했습니다.
Observation 01
평균
52.6%
중앙값
56.4%
절반 이상 0인 메뉴
109개
What I saw
메뉴별 Zero Sales Ratio의 평균과 중앙값이 모두 50%를 넘었습니다. 판매량 0은 일부 메뉴의 이상치가 아니라 데이터 전반에 반복되는 운영 패턴으로 보였습니다.
So I decided
0을 제거하거나 평균으로 메우지 않고, 판매 발생 여부 자체를 별도의 예측 대상으로 분리했습니다.
Observation 02
7일 이상 연속 무판매
1,030개
분석 관점
상태 지속성
What I saw
무판매가 하루 단위로 흩어진 것이 아니라 여러 날 연속되는 구간이 많았습니다. 이는 메뉴가 일시적으로 팔리지 않는 수준을 넘어 활성/비활성 상태가 이어질 수 있음을 보여줍니다.
So I decided
최근 연속 무판매 일수와 최근 판매 발생 비율을 Operational Feature로 추가했습니다.
Modeling Decision
관찰된 한계
평균 수렴
선택한 구조
Hurdle Model
What I saw
단일 회귀 모델은 실제 판매량의 분산을 충분히 따라가지 못하고 평균 근처로 예측이 눌렸습니다. 무판매와 고수요를 하나의 연속값으로만 설명하기 어려웠습니다.
So I decided
판매 발생 여부를 먼저 분류하고, 판매가 발생한 경우의 수량만 별도 회귀로 예측하는 2단계 구조로 문제를 재정의했습니다.
전처리의 목표는 데이터를 깨끗하게 만드는 데서 끝나지 않고, 메뉴별 판매 패턴을 모델이 읽을 수 있는 신호로 바꾸는 것이었습니다. 각 피처 그룹은 EDA에서 확인한 수요 변동, 주기성, 무판매 지속성을 반영하도록 설계했습니다.
Phase 01
날짜와 메뉴 기준이 흔들리면 Lag/Rolling 피처가 틀어지기 때문에, 먼저 예측 가능한 시계열 테이블로 정리했습니다.
Phase 02
전처리된 시계열 위에 달력, 과거 판매 이력, 날씨, 무판매 지속성을 나타내는 피처를 구성했습니다.
Features
요일, 월, 공휴일, 공휴일 전후, 성수기 여부
Why it matters
식음업장 수요는 주말, 휴일, 성수기처럼 반복되는 달력 패턴의 영향을 받기 때문에 기본 수요 리듬을 표현했습니다.
Features
Lag 1/7/14/28, Rolling Mean, Rolling Std
Why it matters
최근 판매량과 주간 반복 패턴을 반영해, 메뉴별로 평소 어느 정도 팔렸는지와 변동성이 큰지를 모델이 볼 수 있게 했습니다.
Features
평균기온, 최저기온, 최고기온, 일교차, 강수량, 강수 여부
Why it matters
날씨가 방문객 행동과 메뉴 선택에 영향을 줄 수 있다고 보고, Weather ON/OFF 실험으로 실제 설명력을 비교했습니다.
Features
연속 무판매 일수, 최근 판매 발생 비율, 장기 무판매 추정 플래그
Why it matters
EDA에서 확인한 무판매 지속성을 모델에 전달하기 위해, 메뉴가 최근에 활성 상태였는지 비활성 상태였는지를 나타내는 피처를 추가했습니다.
EDA에서 확인한 핵심은 판매량 0과 실제 판매량 규모가 서로 다른 성격의 문제라는 점이었습니다. 그래서 판매 발생 여부를 먼저 판단하고, 판매가 발생할 때의 수량만 별도로 예측하는 구조로 설계했습니다.
Structure
1단계
판매 발생 확률
특정 날짜에 해당 메뉴가 팔릴 가능성이 있는지 먼저 판단했습니다. 판매량 0이 많은 데이터에서는 수량을 바로 예측하기보다, 판매 발생 조건을 먼저 구분하는 편이 더 자연스러웠습니다.
2단계
발생 시 판매량
판매가 발생한 데이터만 따로 보고 판매량 규모를 예측했습니다. 소량 판매와 대량 판매가 섞여 있어, 판매량은 log1p 변환 후 학습하고 다시 원래 단위로 복원했습니다.
결합
Soft Gating
판매 발생 확률을 판매량 예측값에 부드럽게 반영했습니다. 확률이 낮다고 바로 0으로 자르지 않아 예측값이 급격히 흔들리는 문제를 줄였습니다.
비교 실험
Weather ON/OFF
날씨 변수를 포함한 예측과 제외한 예측을 비교했습니다. 날씨가 항상 좋은 피처라고 가정하지 않고, 영업장 성격에 따라 다르게 작동하는지 확인했습니다.
Design Point
Zero-Inflated 데이터에서는 팔릴지와 팔린다면 얼마나 팔릴지가 서로 다른 판단입니다. 이 둘을 분리해 단일 회귀 모델의 평균 수렴 문제를 줄이고, 무판매 패턴을 더 자연스럽게 반영했습니다.
학습과 검증은 점수를 한 번 확인하는 과정이 아니라, 실제 예측 상황을 최대한 비슷하게 재현하는 과정으로 설계했습니다. 시간 순서를 유지하고, 7일 예측을 하루씩 생성한 뒤 제출 가능한 형태로 정리했습니다.
01
과거로 학습하고 이후 기간으로 검증
02
판매 발생 여부와 판매량 규모를 분리 학습
03
Weighted SMAPE로 모델별 성능 비교
04
+1일부터 +7일까지 순차 생성
05
음수 제거, 정수 변환, 제출 형식 정리
검증 방식
Time-based Holdout
평가 기준
Weighted SMAPE
예측 단위
+1일부터 +7일까지
Baseline과 Hurdle Model 실험 결과를 SMAPE 기준으로 비교했습니다.
단순 시계열 기반 Baseline은 판매량 0이 많은 데이터 구조를 충분히 반영하지 못했습니다. 반면 Hurdle Model은 판매 발생 여부와 판매량 규모를 분리해 예측함으로써 더 안정적인 성능을 보였습니다.
01
단일 회귀로 예측했더니 예측 표준편차가 15.94로 실제(58.64) 대비 크게 줄었습니다. 매출 0이 56%인 데이터 구조가 원인임을 분포 분석으로 확인했고, 성능 부진 시 튜닝보다 데이터 구조를 먼저 진단해야 함을 배웠습니다.
02
SMAPE가 0을 제외하고 비율로 동작하는 점에서 출발해, log1p 변환(왜도 7.729→0.913)에 RMSE를 결합하면 로그 공간의 비율 오차를 최소화함을 정리했습니다. 평가지표 특성에 맞춰 타깃 변환과 손실함수를 설계할 수 있음을 배웠습니다.
03
2단계 Hurdle Model을 설계했으나 단순 임계값 분리 시 저빈도 메뉴에서 0 예측이 과하고 예측값이 불연속적으로 튀었습니다. Soft Gating으로 변형해 해결하며, 검증된 구조도 실제 데이터에서 깨지는 지점을 확인하고 수정해야 함을 배웠습니다.