머신러닝 · 수요 예측과 재고 최적화 · 식료품 소매

빈 진열대 뒤의 수요

신선 상품이 품절되면 판매량은 더 이상 진실을 말하지 않습니다. 빈 진열대 뒤에 숨은 수요를 구간과 함께 추정하고, 이를 다음 날 주문으로 바꿉니다.

직접 실행해 보기

01

개요

FreshRetailNet-50K는 신선식품 판매에 관한 공개 데이터셋으로, 매장 898곳의 상품 시계열 50,000개에 시간별 판매량과 해당 상품의 재고 여부를 나타내는 시간별 플래그가 담겨 있습니다. 데이터에서 판매 시간의 19.7 %가 품절 상태이며, 상품-일의 44 %는 적어도 한 번 품절됩니다. 품절 시간 동안 데이터에는 판매가 거의 기록되지 않습니다. 따라서 기록된 판매량으로 학습한 예측은 빈 진열대를 한산한 시간으로 취급하고, 예측값이 낮게 나옵니다.

저희는 각 품절이 가린 수요를 80 % 구간을 가진 수치로 추정합니다. 모델은 정답을 아는 시간에서만 학습합니다. 품절 없이 지나간 날을 골라 실제 품절된 날의 품절 패턴을 그대로 옮겨 씌우고, 그 시간을 가린 뒤 이를 복원하도록 학습합니다. 이때 그날의 나머지 시간, 해당 상품의 과거 이력, 할인, 달력, 날씨, 카테고리를 사용합니다. 모델이 본 적 없는 매장 167곳에서 구간은 80.0 %의 경우 실제 값을 포함했습니다. 첫 버전은 실제로 품절된 날에 71 %만 포함했는데, 그런 날은 재고가 있던 시간으로 짐작되는 것보다 더 붐비기 때문입니다. 품절된 날의 재고가 있던 시간으로 학습하자 이 시험의 결과가 79.5 %로 돌아왔습니다. 두 시험 모두 판매가 실제로 관측된 시간을 가립니다. 품절 이후의 수요는 결코 관측할 수 없으므로, 그 구간은 한 가지 가정, 즉 시험할 수 있는 시간처럼 움직인다는 가정에 기대고 있습니다. 데이터셋 전체에서 저희의 중앙 추정치는 기록된 판매량에 17 %를 더합니다.

다음으로, 확률적 예측이 복원한 수요를 주문으로 바꿉니다. 예측은 데이터셋의 홀드아웃 주에서 시험했습니다. 이어서 보충 정책이 매일 저녁, 아직 팔 수 있는 재고를 뺀 예측 분위수 수준까지 주문합니다. 재고는 선입선출로 소진되며, 유통기한은 사용자가 정하는 1일, 2일 또는 3일이라는 가정입니다. 홀드아웃 주를 시뮬레이션으로 재생해, 기록된 판매량으로 주문했을 때와 나란히 폐기량과 판매 손실을 보여 줍니다. 이 재생에서 판매 손실 하나의 비용은 폐기 단위 하나의 두 배이며, 수요에는 저희의 추정치가 포함됩니다. 이 조건에서 저희 추정치로 주문하면 폐기와 판매 손실을 합친 비용이 유통기한 1일, 2일, 3일에서 각각 4.6 %, 5.6 %, 10.9 % 줄었습니다. 품절이 수요를 전혀 가리지 않았다면 두 정책의 차이는 1.3 % 이내였을 것입니다. 이 개선은 판매 손실이 폐기보다 비용이 크다는 점에도 달려 있습니다. 판매 손실 비용이 폐기 단위 하나와 같거나 더 작고 품절이 수요를 가리지 않았다면, 저희 추정치로 주문할 때 비용이 1~5 % 더 듭니다. 판매 손실 비용이 1일 때, 3일 수치는 -10.9 %에서 -0.2 %로 줄어듭니다. 유통기한이 2일이나 3일이면 두 정책 모두 예측 범위의 최상단(P95)에서 주문하므로, 이 두 수치가 가장 불확실합니다. 판매량은 데이터셋 고유의 스케일링된 단위입니다. 복원한 모든 수치는 저희의 추정치이며, 결코 관측값이 아닙니다. 영상 속 진열대는 데이터에서 한 상품의 하루를 재생하는 3D 렌더입니다.

02

구현 내용

  • 중도절단된 수요의 복원. 재고가 있던 시간을 가려 학습한 P10~P90 LightGBM 분위수 모델입니다. 별도 매장에서의 split-conformal 검사로 보정하고, 홀드아웃 매장 167곳에서 검증했습니다. 그 검사 중 하나는 실제 품절된 날만 사용합니다.
  • 홀드아웃 주에 대한 확률적 예측으로, 하루 앞서 만듭니다. 기록된 판매량과 복원한 수요로 각각 학습하며, CRPS, pinball loss, WAPE, 편향으로 단순 기준 모델과 비교해 평가합니다.
  • 재고의 경과 일수를 추적하는 목표 재고(order-up-to) 정책. 가정한 유통기한 1~3일에 걸쳐 선입선출로 판매합니다. 주문 분위수는 비용 비율마다 백테스트 두 주로 조정한 뒤, 홀드아웃 주에는 고정합니다.
  • 빈 진열대에서 시작하는 시뮬레이션 재생. 결과의 범위를 두 가지로 확인합니다. 한 번은 저희 추정치를 포함한 수요로, 한 번은 기록된 판매량만으로 실행합니다.
  • 모두 홀드아웃 매장에서 고른 시계열 2,000개의 인터랙티브 재생. 브라우저에서 매장과 상품을 고르고, 기록된 수요나 추정 수요를 표시하고, 판매 손실 하나의 비용을 폐기 단위 하나와 비교해 조절하고, 유통기한을 바꿀 수 있습니다. 정책은 실시간으로 다시 조정되고 재생됩니다.
03

기술 스택

Python, pandas, NumPy, LightGBM(분위수 회귀), split-conformal 보정 · three.js(물리 기반 렌더링, 누적 피사계 심도와 소프트 섀도) · 브라우저 내 최적화기용 순수(vanilla) JavaScript

04

주요 수치

모든 수치는 FreshRetailNet-50K(Dingdong-Inc, CC BY 4.0)에서 저희가 직접 실행해 얻었습니다. 판매량은 데이터셋의 스케일링된 단위입니다.

수치측정 방법
판매 시간의 19.7 %가 품절

시간별 재고 플래그(06:00–22:00), 전체 시계열 50,000개와 97일 기준입니다. 상품-일의 44.0 %에서 상품이 적어도 한 번 품절되었고, 4.0 %에서는 하루 종일 품절이었습니다.

구간 포함률 80.0 %(목표 80 %)

홀드아웃 매장 167곳의 상품-일 444,393개입니다. 실제 품절 패턴을 재고가 있던 시간에 옮겨 씌우고 그 시간을 가린 뒤, 실제 합계가 P10–P90 구간 안에 드는지 확인했습니다.

실제 품절일의 포함률 79.5 %

12:00 이후에 품절된 홀드아웃 날 222,549개입니다. 품절 2시간 전에 끝나는, 재고가 있던 3시간을 가렸습니다. 품절일로 학습하지 않았을 때는 포함률이 71.4 %였고 추정치가 39 % 낮았습니다.

가린 시간의 오차 40 %(P50의 WAPE)

같은 444,393일 기준입니다. 최근 재고가 있던 날의 같은 시간을 쓰면 오차가 49 %이고, 재고가 있던 시간을 일반적인 시간대별 패턴으로 확대하면 60 %입니다.

품절이 가린 수요 +17 %

품절이 있었던 상품-일 210만 개 전체에 대한 P50 추정치의 합을 기록된 총판매량과 비교한 값입니다. 중앙값의 합은 총량을 과소평가합니다. 대신 일별 P10과 P90 값을 합하면 8 %와 30 %입니다.

홀드아웃 주에서 CRPS 2.4 % 감소

복원한 수요로 학습한 예측과, 같은 모델을 기록된 판매량으로 학습한 예측의 비교: 0.192 vs 0.197(스케일링된 CRPS, 낮을수록 좋음). 기록된 판매량이 곧 수요인, 품절 없이 지나간 홀드아웃 상품-일 206,562개에서 평가했습니다. 28일 경험적 분위수 기준 모델은 0.240입니다.

품절일의 과소 예측 42 % vs 56 %

홀드아웃 품절일 가운데 예측 중앙값이 실제로 기록된 판매량, 즉 그날 수요의 하한보다 낮은 날의 비율입니다. 복원 수요 예측 vs 기록 판매량 예측입니다. 이 검사는 높게 예측할수록 유리하므로, 재고가 있던 날의 편향(+3.1 % vs -6.4 %)과 함께 읽어야 합니다.

유통기한 1 / 2 / 3일에서의 재생 비용 -4.6 % / -5.6 % / -10.9 %

비용 = 수요 단위당 (2 × 판매 손실 + 폐기 단위)이며, 빈 진열대에서 시작해 전체 시계열 50,000개에 대해 홀드아웃 주 동안 계산했습니다. 재생 수요 = 기록된 판매량 + 품절 시간에 대한 P50 추정치입니다. 저희 추정치로 주문한 경우와 기록된 판매량으로 주문한 경우를 비교합니다. 각 정책의 주문 분위수는 직전 두 주로 조정했습니다. 가려진 수요를 대신 저희 구간에서 추출하면(200회 추출) -4.0 % / -7.2 % / -12.6 %입니다.

가려진 수요가 없다면 1.3 % 이내

기록된 판매량만을 수요로 한 같은 재생: +1.3 % / -0.3 % / +0.8 %.

-5 % ~ +5 %(판매 손실 비용이 폐기 0.5~1단위일 때)

같은 재생, 다른 비용 비율입니다. 저희 추정치를 수요로 하면 -5.5 % ~ -0.2 %입니다. 기록된 판매량만 쓰면 +0.8 % ~ +5.4 %이므로, 가려진 수요가 없고 판매 손실 비용이 낮을 때는 저희 추정치로 주문하는 쪽이 비용이 더 듭니다. 유통기한 2일과 3일에서는 조정된 두 정책 모두 예측 범위의 상한인 P95에 머뭅니다.

유통기한 1일에서의 폐기율 20.4 % vs 23.0 %

위와 같은 재생입니다. 저희 추정치로 주문하면 수요의 20.4 %를 폐기하고 7.2 %의 판매를 놓칩니다. 기록된 판매량으로 주문하면 23.0 %를 폐기하고 6.7 %를 놓칩니다. 전날 판매량에 조정 계수를 곱해 주문하면 수요 단위당 비용이 0.600으로, 저희 방식의 0.347보다 높습니다.

브라우저에서 재조정·재생에 42 ms

저희 M1 Pro 노트북의 헤드리스 Chromium에서 10회 실행한 중앙값입니다. 각 실행은 시계열 2,000개에 대해 백테스트 14일로 주문 분위수 37개 × 정책 2개를 조정한 뒤, 7일간의 한 주를 재생합니다. 휴대폰에서는 더 느립니다.

05

스틸 이미지

06

직접 실행해 보기

브라우저에서 실행되는 인터랙티브 뷰입니다. 열기 전에는 아무것도 불러오지 않습니다.

직접 실행해 보기 3.3 MB 다운로드

이와 비슷한 과제가 있으신가요?

계산하거나 시뮬레이션하거나 결정해야 할 내용을 알려 주시면, 저희가 어떻게 접근할지 말씀드리겠습니다.

과제 문의하기 AI·머신러닝