주요 컨텐츠로 이동
Engineering blog

대규모의 세분화된 시계열 예측을 위한 Facebook Prophet과 Apache Spark: Spark 3 업데이트

Bilal Obeidat
Bryan Smith
브레너 하인츠
Kelly O'Malley
이 포스트 공유하기

Translated by HaUn Kim - Original Blog Post

시계열 예측의 발전 덕분에 리테일러들은 더욱 신뢰할 수 있는 수요 예측을 만들어낼 수 있게 되었습니다. 이제 남은 과제는 이러한 예측을 적시에, 그리고 기업이 제품 재고를 정밀하게 조절할 수 있을 만큼 세밀하게 분류하여 제작하는 것입니다. 이 과제에 직면한 점점 더 많은 기업들이 Apache Spark™Facebook Prophet을 활용함으로써 과거의 솔루션이 가진 확장성과 정확도의 한계를 극복할 수 있다는 것을 알아차리고 있습니다.

이 게시물에서 언급된 예측 액셀러레이터에 바로 접근하실 수 있습니다. Spark 2.0에 대한 이 솔루션을 확인하고 싶으시다면, 여기에서 원본 블로그 게시물을 읽어보세요.

이 포스트에서는 시계열 예측의 중요성을 논의하고, 몇 가지 예시 시계열 데이터를 시각화한 후, 간단한 모델을 구축하여 Facebook Prophet의 사용 방법을 설명해 드리겠습니다. 단일 모델 구축에 익숙해지면, Facebook Prophet과 Spark의 결합을 통해 한 번에 수백 개의 모델을 훈련시키고, 이전에는 거의 달성할 수 없었던 세밀한 수준에서 각각의 제품-매장 조합에 대한 정확한 예측을 만드는 방법을 보여드리겠습니다.

정확하고 시기 적절한 예측은 그 어느 때보다 중요해졌습니다.

제품 및 서비스의 수요를 더 정확히 예측하기 위해서는 시계열 분석의 속도와 정확도를 향상시키는 것이 리테일러의 성공에 매우 중요합니다. 매장에 제품을 과다하게 진열하면 진열대와 창고 공간이 부족해지고, 유통기한이 지난 제품이 판매될 위험이 있으며, 또한 소매업체의 재정 자원이 재고에 묶여 제조업체가 제공하는 새로운 기회나 소비자 트렌드의 변화를 활용하지 못할 수 있습니다. 반면, 매장에 제품을 너무 적게 배치하면 고객이 필요한 제품을 구매하지 못하는 상황이 발생할 수 있습니다. 이러한 예측의 오류는 리테일러에게 즉각적인 매출 손실을 일으킬 뿐만 아니라, 시간이 지나면서 소비자의 불만이 증가하여 경쟁업체로의 고객 이탈을 초래할 수도 있습니다.

New expectations require more precise time series models and forecasting methods

오랫동안 ERP(Enterprise Resource Planning, 전사적 자원 관리) 시스템과 타사 솔루션은 기본적인 시계열 모델에 기반한 수요 예측 기능을 소매업체에 제공해 왔습니다. 그러나 기술의 발전과 업계의 압박이 커짐에 따라 많은 리테일러들이 기존에 사용하던 선형 모델과 전통적인 알고리즘을 넘어서는 새로운 접근 방식을 찾고 있습니다.

데이터 사이언스 커뮤니티에서는 Facebook의 Prophet과 같이 새로운 기능을 제공하는 머신러닝 모델이 등장하고 있으며, 기업들은 이러한 모델을 시계열 예측에 유연하게 적용할 수 있는 방법을 탐색하고 있습니다.

전통적인 예측 솔루션을 넘어서기 위해서는 소매업체들이 수요 예측의 복잡성을 이해하고 수십만 또는 수백만 개의 ML 모델을 적시에 생성하는 데 필요한 작업을 효율적으로 분배할 수 있는 내부 전문 지식을 개발해야 합니다. 다행히도 Spark를 활용하면 이러한 모델의 학습을 분산 처리할 수 있어, 제품 및 서비스의 수요뿐만 아니라 각 지역별 각 제품에 대한 독특한 수요도 예측할 수 있습니다.

시계열 데이터의 수요 계절성 시각화

개별 매장 및 제품에 대한 세분화된 수요 예측을 생성하는 데 Facebook Prophet을 사용하는 방법을 알려드리기 위해 Kaggle에서 공개적으로 제공되는 데이터 세트를 활용해 보겠습니다. 이 데이터는 10개 매장의 50개 개별 품목에 대한 5년간의 일일 판매 데이터로 구성되어 있습니다.

Sample Kaggle retail data used to demonstrate the combined fine-grained demand forecasting capabilities of Facebook Prophet and Apache Spark

다음으로, 동일한 데이터를 월별로 살펴보면 전년 대비 상승 추세가 매월 꾸준히 진행되지 않는다는 것을 알 수 있습니다. 대신 여름철에 정점을 찍고 겨울철에 최저점을 찍는 뚜렷한 계절적 패턴이 있습니다. 데이터브릭스 협업 노트북에 내장된 데이터 시각화 기능을 사용해 차트를 마우스로 가리키면 월별 데이터의 가치를 확인할 수 있습니다.

 

평일을 기준으로 할 때, 일요일(주중 0)에 매출이 정점을 찍고 월요일(주중 1)에 급격하게 하락한 후, 나머지 주 동안 꾸준히 회복하는 경향을 보입니다.

Demonstrating the difficulty of accounting for seasonal patterns with traditional time series forecasting methods

Facebook Prophet에서 간단한 시계열 예측 모델 시작하기

위에서 볼 수 있듯이, 데이터는 연간 및 주간의 계절별 패턴과 함께 매출의 뚜렷한 전년 대비 상승 추세를 보여줍니다. 이러한 데이터의 중첩된 패턴은 바로 Facebook Prophet이 해결하기 위해 설계된 문제입니다.

Facebook Prophet은 scikit-learn API를 따르므로, scikit-learn 사용 경험이 있는 분이라면 누구나 쉽게 익힐 수 있습니다. 모델에 입력으로 전달해야 하는 것은 첫 번째 열이 날짜, 두 번째 열이 예측할 값(이 경우 매출)으로 구성된 두 열로 이루어진 Pandas 데이터 프레임입니다. 데이터가 적절한 형식으로 제공되면, 모델을 구축하는 과정은 간단합니다:

import pandas as pd
from fbprophet import Prophet
 
# instantiate the model and set parameters
model = Prophet(
    interval_width=0.95,
    growth='linear',
    daily_seasonality=False,
    weekly_seasonality=True,
    yearly_seasonality=True,
    seasonality_mode='multiplicative'
)
 
# fit the model to historical data
model.fit(history_pd)

이제 데이터에 모델을 적용했으므로 이를 사용하여 90일 예측을 작성해 보겠습니다:

# define a dataset including both historical dates & 90-days beyond the last available date, using Prophet's built-in make_future_dataframe method
future_pd = model.make_future_dataframe(
    periods=90, 
    freq='d', 
    include_history=True
)
 
# predict over the dataset
forecast_pd = model.predict(future_pd)

끝났습니다! 이제 Facebook Prophet 모델에 내장된 .plot 메서드를 활용해 실제 데이터와 예측 데이터의 일치 여부를 시각화하고, 미래에 대한 예측도 수행할 수 있습니다. 앞서 언급된 주간 및 계절별 수요 패턴이 예측 결과에 반영된 것을 볼 수 있습니다.

predict_fig = model.plot(forecast_pd, xlabel='date', ylabel='sales')
display(fig)

Comparing the actual demand to the time-series forecast generated by Facebook Prophet leveraging Apache Spark

이 시각화 방법은 다소 복잡할 수 있습니다. Bartosz Mikulski가 이에 대해 상세한 분석을 제공하고 있으니 확인해보시는 것이 좋습니다. 간단히 설명하자면, 검은색 점은 실제 값을 나타내고, 진한 파란색 선은 예측 값을, 연한 파란색 띠는 95% 불확실성 구간을 나타냅니다.

수백 개의 시계열 예측 모델을 Facebook Prophet 및 Spark와 병행하여 학습하기

이제 단일 모델 구축 방법을 시연했으니, Spark의 강력한 기능을 활용해 작업을 확장할 수 있습니다. 우리의 목표는 전체 데이터 세트에 대한 단일 예측이 아니라, 각 제품-매장 조합에 대해 수백 개의 모델과 예측을 생성하는 것입니다. 이는 순차적으로 수행하면 시간이 많이 소요될 수 있는 작업입니다.

이 방법으로 모델을 구축하면, 예를 들어 식료품점 체인에서 각 지점별 수요에 따라 샌더스키 매장과 클리블랜드 매장에 필요한 우유의 양이 다르다는 정확한 예측을 할 수 있습니다.

Spark 데이터프레임을 사용하여 시계열 데이터의 처리를 분산하는 방법

데이터 사이언티스트들은 많은 모델을 훈련해야 할 때 종종 Spark와 같은 분산 데이터 처리 엔진을 사용합니다. Spark 클러스터를 활용하면 클러스터 내 개별 작업자 노드들이 다른 작업자 노드들과 병렬로 모델의 하위 집합을 훈련할 수 있어, 전체 시계열 모델 컬렉션을 훈련하는 데 필요한 시간을 크게 줄일 수 있습니다.

물론, 워커 노드(컴퓨터) 클러스터에서 모델을 훈련하려면 더 많은 클라우드 인프라가 필요하고, 이에는 비용이 발생합니다. 하지만 온디맨드 클라우드 리소스를 쉽게 사용할 수 있기 때문에, 기업은 필요한 리소스를 신속하게 프로비저닝하고, 모델을 훈련한 후 해당 리소스를 신속하게 해제함으로써 물리적 자산에 대한 장기적인 약정 없이도 대규모 확장성을 달성할 수 있습니다.

Spark에서 분산 데이터 처리를 달성하는 핵심 메커니즘은 DataFrame입니다. Spark 데이터프레임에 데이터를 로드하면, 클러스터 전체의 작업자들에게 데이터가 분산되어, 이들이 데이터의 하위 집합을 병렬로 처리할 수 있게 함으로써, 작업을 수행하는 데 필요한 전체 시간이 단축됩니다.

물론, 각 작업자는 자신이 담당하는 작업을 수행하기 위해 필요한 데이터의 일부에 접근할 수 있어야 합니다. 이를 위해, 키 값(이 경우는 매장과 상품의 조합)에 따라 데이터를 그룹화하여, 해당 키 값에 속하는 모든 시계열 데이터를 특정 작업 노드에 집중시킬 수 있습니다.

다음 섹션에서는 사용자 정의 Pandas 함수를 설정하고 데이터에 적용하는 과정을 다루지만, 실제로 그 단계에 이르기 전까지는 실행되지 않습니다. 여기서는, 그룹별로 코드를 공유함으로써 여러 모델을 효과적으로 병렬로 훈련시킬 수 있는 방법을 강조하고자 합니다.

store_item_history
    .groupBy('store', 'item')
    . . .

다음 섹션에서는 사용자 정의 판다스 함수를 설정하고 데이터에 적용하는 과정을 다루겠지만, 실제로 그 과정이 완료되기 전까지는 작동하지 않습니다. 여기서의 목적은 그룹별 코드를 공유함으로써 여러 모델을 효율적으로 병렬로 훈련할 수 있는 방법을 강조하는 것입니다.

Pandas의 강력한 기능 활용하기

시계열 데이터를 매장과 제품별로 적절히 그룹화한 뒤, 이제 각 그룹마다 개별 모델을 학습시켜야 합니다. 이를 위해서는 데이터 프레임의 각 데이터 그룹에 사용자 정의 함수를 적용할 수 있는 Pandas 함수를 활용할 수 있습니다.

이 함수는 각 그룹에 대한 모델을 학습할 뿐만 아니라, 해당 모델의 예측 결과도 생성합니다. 비록 이 함수가 다른 그룹과는 독립적으로 각 데이터 그룹을 학습하고 예측한다 하더라도, 각 그룹에서 반환된 결과들은 하나의 결과 데이터 프레임으로 편리하게 모아집니다. 이를 통해 매장과 제품 수준에서의 예측을 생성할 수 있으며, 분석가와 관리자는 이 결과들을 단일 출력 데이터 집합으로 받아볼 수 있습니다.

아래의 간략화된 코드 예시에서 볼 수 있듯이, 함수를 구축하는 과정은 상대적으로 단순합니다. 이전 버전의 Spark와는 다르게, 입력과 반환될 판다스 객체의 유형, 즉 Python 유형 힌트를 지정함으로써 함수 선언을 상당히 간소화할 수 있습니다.

함수 정의 내에서는 모델을 인스턴스화하고, 구성하며, 전달받은 데이터에 맞게 조정합니다. 그리고 모델이 예측을 수행하면, 해당 데이터가 함수의 출력으로 반환됩니다.

def forecast_store_item(history_pd: pd.DataFrame) -> pd.DataFrame: 
    
    # instantiate the model, configure the parameters
    model = Prophet(
        interval_width=0.95,
        growth='linear',
        daily_seasonality=False,
        weekly_seasonality=True,
        yearly_seasonality=True,
        seasonality_mode='multiplicative'
    )
    
    # fit the model
    model.fit(history_pd)
    
    # configure predictions
    future_pd = model.make_future_dataframe(
        periods=90, 
        freq='d',
        include_history=True
    )
    
    # make predictions
    results_pd = model.predict(future_pd)
    
    # . . .
    
    # return predictions
    return results_pd

이제, 앞서 언급한 groupBy 명령을 사용해 데이터 세트가 특정 매장과 제품 조합을 나타내는 그룹으로 적절히 나누어졌는지 확인합니다. 그 다음으로는 데이터 프레임에 함수를 적용하여 모델을 학습시키고, 각 데이터 그룹에 대한 예측을 수행할 수 있도록 합니다.

함수를 각 그룹에 적용하여 반환된 데이터 세트는 예측이 생성된 날짜를 반영하도록 업데이트됩니다. 이 과정을 통해, 최종적으로 기능을 실제 환경에 적용할 때, 다양한 모델 실행 중에 생성된 데이터를 추적하는 데 유용하게 사용됩니다.

from pyspark.sql.functions import current_date
 
results = (
    store_item_history
        .groupBy('store', 'item')
          .applyInPandas(forecast_store_item, schema=result_schema)
        .withColumn('training_date', current_date())
    )

넥스트 스텝

이제 매장-제품 조합별 예측을 완성했습니다. 분석가는 SQL 쿼리를 사용해 각 제품에 대한 맞춤 예측을 확인할 수 있습니다. 아래 차트에서는 10개 매장의 1번 제품에 대한 예상 수요를 그래프로 나타냈습니다. 보다시피, 수요 예측은 매장마다 다를 수 있지만, 일반적인 패턴은 모든 매장에서 일관되게 나타납니다.

Sample time series visualization generated via a SQL query

새로운 판매 데이터가 도착하면, 이를 바탕으로 새로운 예측을 효율적으로 생성하여 기존 테이블 구조에 추가함으로써 분석가들이 상황 변화에 따라 비즈니스의 기대치를 업데이트할 수 있습니다.

데이터브릭스 환경에서 이러한 예측을 생성하기 위해서는 수요 예측을 위한 솔루션 액셀러레이터를 확인해 보세요.

Spark 2.0을 위해 제작된 이 노트북의 이전 버전에 접근하고 싶으시다면, 이 링크를 클릭해 주세요.

Databricks 무료로 시작하기

관련 포스트

Engineering blog

대규모의 세분화된 시계열 예측을 위한 Facebook Prophet과 Apache Spark: Spark 3 업데이트

Translated by HaUn Kim - Original Blog Post 시계열 예측의 발전 덕분에 리테일러들은 더욱 신뢰할 수 있는 수요 예측을 만들어낼 수 있게 되었습니다...
모든 엔지니어링 블로그 포스트 보기