AWS 기술 블로그
Amazon SageMaker와 Amazon MWAA를 활용한 29CM의 개인화 추천시스템 MLOps 구축여정
29CM는 스토리텔링 노하우를 기반으로 한 온라인 셀렉트샵으로, 고객의 더 나은 선택을 돕고자 공통의 미션인 “Guide To Better Choice”를 달성하기 위해 모두가 노력하고 있습니다. 이러한 노력의 일환으로, 2022년 10월부터 사용자 개개인에게 최적화 된 경험을 제공하고자 자체 추천 서비스를 구축하는 것의 준비를 시작했고, 2023년 1월부터 본격적으로 추천 서비스 팀이 신설되었습니다.
그림 1. 29CM 웹/앱 홈페이지
프로젝트 소개
추천 서비스팀의 첫번째 프로젝트로 카테고리 페이지에서 노출되는 상품을 개인화 추천 기반으로 재정렬하는 서비스를 준비했습니다. 카테고리 페이지는 29CM 서비스 사용자들이 빈번하게 방문하는 페이지중 하나 입니다. 과거에는 인기순으로 정렬할 경우 모든 사용자가 동일한 순서로 정렬된 인기상품 목록을 받아볼 수 있었습니다.
이를 사용자 개개인의 취향을 반영한 순서로 제공해 사용자들의 클릭률을 높이는 것이 이번 프로젝트의 기능적인 목표였습니다. 프로젝트의 향후 확장성이나 운영 효율화 측면을 고려 했을 때 모델을 학습하고 배포하는 과정을 자동화하기 위해 MLOps를 적용하는 것 또한 요구되는 상황이었습니다.
그림 2. 개인화 추천 적용 후 카테고리 페이지
모델 선정 및 학습
개인화된 추천 서비스를 만드는 데 있어 먼저 고려했던 내용은 패션도메인의 사용자 취향이 빠르게 변한다는 것이었습니다. 따라서 초기모델 선정에 있어 시간에 따라 변화하는 취향을 반영할 수 있는 Session-based 추천모델과 Sequence-based 추천 모델에 대한 연구조사를 진행했고, 트랜드에 민감하게 반응하는 패션 도메인의 특성과 추후 실시간 데이터 활용 가능성 측면에서 상대적으로 Short-term의 시그널을 더 잘 반영할 수 있는 Session-based 모델을 베이스 모델로 정하여 구현했습니다.
모델의 학습 데이터로는 수 주간 발생한 사용자 인터랙션 데이터 (상품 상세페이지 방문 등)를 활용했습니다. 특정 사용자에서 일정 기간 내에 발생 한 일련의 이벤트들을 같은 세션으로 묶었고, 이후 너무 적은 이벤트를 가진 세션을 버리거나, 신규 사용자나 신규 상품을 걸러내는 등의 전처리를 수행했습니다. 그렇게 수집 된 수백만 개의 세션 데이터를 시간을 기준으로 Train, Valid, Test 데이터세트로 구분했습니다. 모델의 성능은 추천 시스템에서 널리 쓰이는 Recall@k, Precision@k, MRR@k, NDCG@k 등의 지표를 사용해 정확도와 순서를 모두 고려하는 방법으로 평가했습니다.
관리형 클라우드 서비스를 활용한 학습, 배포, MLOps 효율화
사용자 취향에 맞춘 추천 서비스를 구축하는 데 있어 모델의 성능이나 실시간 배포와 같은 기능적인 측면도 중요했지만, 이를 자동화하여 운영 자원을 줄일 수 있는 MLOps 체계를 구축하는 것이 필수였습니다. 하지만 팀 구성이 된 지 얼마 되지 않아 시작 된 프로젝트였기에 인프라와 플랫폼을 준비하는 소위 밑바닥부터 구축하기 위한 시간과 인적 자원이 충분하지 않았습니다.
빠른 프로젝트 진행을 위해서는 관리형 클라우드 서비스를 이용하는 것이 효율적이라 판단했고, 여러 조사 끝에 Amazon SageMaker와 Amazon Managed Workflows for Apache Airflow(MWAA)를 사용하기로 결정했습니다.
Amazon SageMaker 선정이유
- 모델의 학습 및 개발 등 머신러닝 개발 수명주기 전반에 걸친 기능을 제공
- 컨테이너 기반 서비스로 알고리즘의 일관성이 보장되는 한편 학습데이터와 소스코드를 동적으로 적용하여 유연하게 활용 가능
- 작업이 수행 된 시간에 대한 인스턴스 비용만 지불하여 비용 효율적인 방식
- Amazon SageMaker SDK를 활용한 다양한 커스터마이징 가능
- Amazon CloudWatch를 통해 추론 엔드포인트에 대한 다양한 지표 모니터링 가능
Amazon Managed Workflows for Apache Airflow (MWAA) 선정이유
- Apache Airflow에 대한 공식문서나 장애 대처 방법 등 다양한 레퍼런스
- Amazon SageMaker를 비롯한 다양한 AWS 서비스와 연동할 수 있고, 사내 정책에 의한 인증 및 권한설정에 대한 부분도 IAM을 통해 관리 가능
- Amazon MWAA의 대안으로는 Amazon SageMaker Pipelines나 AWS Step Functions와 같은 서비스도 있지만, 레퍼런스가 풍부한 이유로 Airflow를 선호함
기술스택
- Version Control System: GitHub
- Data Mart/Warehouse: Google BigQuery
- Model Building & Hosting: SageMaker Preprocessing Job, Training Job, Hyper-Parameter Optimization, Realtime Hosting, Batch-Transform Job, Amazon ECR, AWS SecretManager
- Workflow: Amazon MWAA
Github Repository는 ml-scheduler와 ml-worker를 만들었습니다. ml-scheduler에서는 Amazon MWAA에서 활용 될 DAG(Directed Acyclic Graph)으로 구성 된 워크플로우와 필요한 Plugin들에 대한 소스코드를 관리합니다. DAG의 각 스텝에서는 필요한 SageMaker Job을 호출합니다. ml-worker 에서는 DAG 내 각각의 스텝에서는 호출되는 Amazon SageMaker Job에 대한 구체적인 로직이 들어있는 소스코드를 관리합니다.
MLOps 아키텍처
아래 그림은 위 기술 스택을 이용해서 하나의 추천 서비스가 준비되는 과정을 보여줍니다.
그림 3. 29CM의 추천서비스 아키텍처
Amazon SageMaker를 활용한 모델 학습과 서빙 구현
Amazon SageMaker는 기계학습 라이프사이클에 필요한 모든 기능을 제공합니다. 여러 가지 매력적인 기능들이 있지만, 이번 프로젝트에 서는 데이터 전처리, 모델 학습, 실시간 추론, 모니터링에 대한 기능만을 사용했습니다. 각 단계별 설명에 들어가기에 앞서 Amazon SageMaker의 동작 원리에 대해서 간략하게 설명하겠습니다.
- 데이터의 전처리와 모델 학습은 Amazon SageMaker의 Processing job, Training job을 통해 수행합니다.
- Amazon SageMaker SDK를 활용 해 각각의 job을 선언합니다. 컨테이너 이미지와 소스코드, 데이터세트에 대한 정보를 지정해 주면 해당 job들은 각각의 컨테이너 환경에서 독립적으로 실행됩니다.
- job이 실행되면 사전에 정의 한 사양의 인스턴스가 먼저 띄워지고, 컨테이너 이미지와 소스코드, 데이터세트를 다운로드합니다. 그리고 컨테이너 환경에서 소스코드가 실행 된 후 처리결과(데이터세트, 모델 아티팩트 등)를 Amazon S3에 업로드 합니다. 그리고 job이 끝나게 되면 인스턴스는 자동으로 종료됩니다.
- 모델배포는 Amazon SageMaker의 Model과 Endpoint를 활용 해 실시간 추천을 위한 API 엔드포인트를 생성하고 업데이트합니다.
- Amazon MWAA에서는 앞서 이야기 한 데이터 전처리, 모델학습, 모델배포의 과정을 각각 Task 단위로 지정 해 DAG 형태의 Workflow로 정의합니다.
- DAG를 정의한 Python 파일이 Amazon S3 버킷에 업로드 되면 해당 워크플로우가 Amazon MWAA에 자동으로 반영됩니다.
- 각 Task에서 공유가 필요 한 데이터나 아티팩트는 Amazon S3의 Uri를 매개변수로 하여 공유 할 수 있도록 합니다.
그러면 각 단계별로 세부적인 구현 방식은 다음과 같습니다.
데이터 전처리 (Processing Job)
모델 학습에 필요한 데이터를 가공해서 데이터세트로 만드는 단계입니다. 아래는 Amazon SageMaker SDK를 사용한 Processing job을 정의하는 예시를 보여줍니다. 보다 정확한 사용방법은 Amazon SageMaker Python SDK에서 확인하실 수 있습니다.
import os
from sagemaker import get_execution_role
from sagemaker.processing import ProcessingOutput
from sagemaker.pytorch.processing import PyTorchProcessor
from sagemaker.network import NetworkConfig
processor = PyTorchProcessor( role=get_execution_role(),
image_uri=PROCESS_IMAGE_URI, # 코드 개발 환경이 갖춰진 도커 이미지 주소(CPU)
instance_count=1, # 인스턴스 개수
instance_type=PROCESS_INSTANCE_TYPE, # 인스턴스 타입
volume_size_in_gb=100, # 인스턴스 디스크 크기
env={'ENV1': 'VALUE1', 'ENV2': 'VALUE2'}, # 컨테이너 실행시 추가할 환경변수
network_config=NetworkConfig(security_group_ids=[SECURITY_GROUP_ID], subnets=[SUBNET_ID_AZ1]), # 네트워크 설정
)
processor.run(
job_name=PROCESS_JOB_NAME,
source_dir=SOURCE_PATH_S3, # 소스코드가 저장된 S3 path
code='PATH/TO/PROCESS/process.py', # 소스코드에서 실행할 파일
outputs=[ # processing job의 결과를 S3에 저장
ProcessingOutput(
source='/opt/ml/processing/input/data/datasets', # 컨테이너 환경에서 dataset이 저장되는 위치,"/opt/ml/processing/input/data"
destination=os.path.join(DATA_PATH_S3, f'{PROCESS_JOB_NAME}/datasets/'), # dataset을 저장할 S3 dir
),
],
arguments=['--ARG1', 'VALUE1', '--ARG2', 'VALUE2'], # 파일 실행시 동적으로 argument 할당
)
processor.run 함수의 code 값으로 넘겨진 process.py 에서는 아래의 로직을 수행합니다
- Google BigQuery에 쿼리를 보내 학습에 필요한 데이터 수집
- 29CM의 이벤트, 운영 데이터는 Google BigQuery에 적재되고 있습니다.
- 접속을 위한 Credential정보를 AWS Secret Manager에 등록해서 사용합니다.
- 쿼리를 통해 가져온 데이터를 모델 학습에 필요한 데이터세트의 형태로 가공
- 가공한 데이터세트를 /opt/ml/processing/input/data/datasets 에 저장
모델 학습 (Training Job)
이전 단계인 Processing job에서 만든 데이터세트로 모델을 학습하는 단계입니다. 아래는 SageMaker SDK를 사용한 Training job 을 정의하는 예시를 보여줍니다. Processing job의 정의와 비슷하지만, 매개변수의 이름이나 값을 받는 방식이 다른 부분이 있습니다.
import os
from sagemaker import get_execution_role
from sagemaker.pytorch.estimator import PyTorch
estimator = PyTorch(
role=get_execution_role(),
entry_point='PATH/TO/TRAIN/train.py',
source_dir=SOURCE_PATH_S3,
image_uri=TRAIN_IMAGE_URI, # 모델 학습을 위한 환경이 갖춰진 도커 이미지 주소(GPU)
instance_count=1,
instance_type=TRAIN_INSTANCE_TYPE,
volume_size=100,
subnets=[SUBNET_ID_AZ1],
security_group_ids=[SECURITY_GROUP_ID],
output_path=os.path.join(DATA_PATH_S3, f'{TRAIN_JOB_NAME}/'), # inference에 필요한 모델 및 아티팩트
checkpoint_s3_uri=os.path.join(DATA_PATH_S3, f'{TRAIN_JOB_NAME}/checkpoints/'), # 학습 도중 생성되는 체크포인트 저장
)
estimator.fit(
job_name=TRAIN_JOB_NAME,
inputs={
'datasets': os.path.join(DATA_PATH_S3, f'{PROCESS_JOB_NAME}/datasets'),
},
)
모델 학습 시에는 GPU를 활용해서 학습하기 때문에, job을 정의할 때 선언하는 컨테이너 이미지와 인스턴스 타입도 맞추어서 바꿔주었습니다. estimator.fit 함수의 inputs 파라미터를 보시면 processing job에서 가공한 학습용 데이터세트를 datasets라는 채널로 전달 해 주는 것을 확인할 수 있습니다. fit함수를 호출해 생성된 컨테이너에서는 /opt/ml/inputs/datasets폴더에 해당 학습데이터가 올라가게 되고, train.py에서는 os.environ[‘SM_CHANNEL_TRAIN’])을 호출해 해당 경로를 받아올 수 있습니다.
Tips
- 실제 서비스를 위해서는 모델 파일 외에도 id-index mapper, 상품 메타 데이터 등의 파일이 필요할 수 있습니다. 이런 경우 모델 파일과 같은 위치 ( output_path 매개변수)에 필요한 파일을 위치 시키면, 다음 단계인 Deploy 단계에서 그 파일들을 함께 패키징해서 사용할 수 있습니다
모델 배포 (Real-time inference)
대망의 모델 배포 단계입니다. 이전 Training Job에서 만든 모델을 배포해서 추천 서비스를 제공하는 엔드포인트를 생성합니 다. 카테고리 개인화 정렬 프로젝트의 엔드포인트는 그림3에 있는Rec. API Server와 통신해서 추천 결과를 제공합니다. 엔드포인트는 고객 ID, 카테고리 ID, 그리고 추천 candidate들을 요청으로 받고, 이를 재정렬 후 응답으로 내려줍니다.
모델 배포는 2가지 방법을 사용합니다. 엔드포인트를 생성하는 create_endpoint 와, 기존 엔드포인트에 배포된 모델 을 교체하는 update_endpoint 가 있습니다. create_endpoint 방법을 사용하면 새로운 엔드포인트가 생기고, 이 엔드포인트의 이름을 따서 접근 URL이 생성됩니다.
워크플로우가 실행 될 때 마다 매번 create_endpoint를 호출하게 되면 엔드포인트의 URL도 바뀌어서 서비스에 활용하기 어렵기 때문에, 한 번 엔드포인트를 생성하고 나면 엔드 포인트 URL를 유지하면서 연결되어 있는 모델만 교체하는 update_endpoint 방법을 이용하는게 좋습니다. 아래는 create_endpoint 에 대한 예시입니다. update_endpoint는 AWS SDK for Python 문서의 update_endpoint을 참고하세요.
import os
from sagemaker import get_execution_role
from sagemaker.deserializers import JSONDeserializer
from sagemaker.pytorch.estimator import PyTorchModel
from sagemaker.serializers import JSONSerializer
model: PyTorchModel = PyTorchModel(
role=get_execution_role(),
name=MODEL_NAME,
model_data=os.path.join(
DATA_PATH_S3,
f'{TRAIN_JOB_NAME}/output/model.tar.gz',
),
image_uri=INFERENCE_IMAGE_URI,
entry_point='inference.py',
source_dir=INFERENCE_PATH_S3,
vpc_config={
'Subnets': [SUBNET_ID_AZ1, SUBNET_ID_AZ2, SUBNET_ID_AZ3],
'SecurityGroupIds': [SECURITY_GROUP_ID],
},
env={'SAGEMAKER_MODEL_SERVER_TIMEOUT': '600'},
)
model.deploy(
endpoint_name=ENDPOINT_NAME,
initial_instance_count=1,
instance_type=INFERENCE_INSTANCE_TYPE,
serializer=JSONSerializer(), # 응답 serialize
deserializer=JSONDeserializer(), # 요청 deserialize
)
return str(sm_boto.describe_endpoint(EndpointName=ENDPOINT_NAME))
PyTorchModel 을 선언 할때 env 값으로 설정한 SAGEMAKER_MODEL_SERVER_TIMEOUT 매개변수는 엔드포인트 인스턴스의 응답 시간 (초) 제한값입니다. 처음 엔드포인트를 배포하면 모델 및 아티팩트를 로드 하는데 시간이 걸리는데, 그 시간을 고려해서 넉넉하게 설정했습니다.
모니터링 (Amazon CloudWatch)
엔드포인트가 생성되고 나면, Amazon SageMaker에서는 다양한 모니터링 지표를 기본으로 제공해 줍니다. 지표를 세부적으로 확인하고 싶은 경우 호출지표, 추론 인스턴스 지표, 로그 보기와 같은 링크를 통해 Amazon CloudWatch에서 확인할 수도 있습니다.
그림 4. 엔드포인트 기본 모니터링 지표 예시
Tips
- 별도의 모니터링 하고 싶은 값이 있는 경우 Amazon CloudWatch의 대시보드로 커스텀한 대시보드를 만들 수 있습니다
- 로그 분석을 더욱 편리하게 하고싶은 경우 Amazon CloudWatch Log Insights 를 활용한다면 쿼리를 통해 로그를 분석하고 시각화 할 수도 있습니다.
공통 Tips
- Amazon SageMaker의 Processing Job, Training Job, Endpoint와 같은 기능은 컨테이너 기반으로 작동하기 때문에 인스턴스를 띄울 때 컨테이너 이미지를 다운로드 받고, 사용 할 데이터세트 또는 모델 아티팩트를 다운로드 하는 과정이 필요합니다. 따라서 여러 실험을 진행하는 경우 몇 줄의 코드변경내역을 확인하기위해 매번 인스턴스를 띄우는 시간과 비용을 소비할 수 있습니다. Amazon SageMaker의 로컬모드를 활용하신다면 Amazon SageMaker 노트북 인스턴스 내부에 컨테이너를 구동해서 여러분의 코드변경사항을 테스트 할 수 있어 시간과 비용을 절감할 수 있습니다. Amazon SageMaker 클러스터 환경으로 사용하시기 전에 로컬모드에서 충분히 테스트를 진행하시는 것을 추천합니다.
- SageMaker Deep Learning Container를 활용하세요. Amazon SageMaker에서 제공하는 컨테이너 이미지를 사용하고 필요한 의존성은 requirements.txt를 통해 설치하여 적은 노력으로 학습환경을 만들 수 있습니다.
Amazon MWAA를 사용한 워크플로우 DAG 만들기 & 스케쥴링하기
이제 각 job을 정의하고 테스트가 끝났다면, 이 일련의 job들을 주기적으로, 순서대로 실행시켜주기 위해, Amazon MWAA를 사용할 차례입니다.
Amazon MWAA는 파이프라이닝 및 스케쥴링에 필요한 DAG 파일, plugin, requirements만 지정된 S3에 업로드 해주면 자동으로 Airflow 환경을 구축해주고, Amazon S3에 변경사항이 생기면 이를 바로 환경에 반영해 줍니다.
따라서 29CM 추천 서비스팀은 DAG 파일, plugin, requirements을 정의한 ml-scheduler repository를 만들고, git-action을 통해 업데이트가 있을 때마다 지정된 Amazon S3에 변경된 파일을 업로드하고 있습니다. Amazon MWAA를 사용하면 위의 데이터 전처리, 모델 학습, 모델 배포와 같은 코드를 그대로 사용해서 손쉽게 파이프라인을 만들고, 스케쥴링할 수 있습니다. 위의 각 job을 수행하는 코드를 함수로 감싸주고, DAG 파일을 만들때는 기본 오퍼레이터인 PythonOperator의 python_callable 로 각 job 함수를 지정하면 됩니다. 아래는 DAG 파일의 예시 입니다, callables 패키지의 각 함수가 위에서 설명한 코드들을 감싼 함수들입니다.
from datetime import datetime
from airflow import DAG
from airflow.operators.empty import EmptyOperator
from airflow.operators.python import BranchPythonOperator, PythonOperator
from callables import (
create_endpoint,
create_model,
process,
select_endpoint_deploy_method,
train,
update_endpoint,
)
from dependencies import default_dag_args
job_name = '{{ execution_date.in_timezone("Asia/Seoul").strftime("%Y-%m-%d-%H-%M-%S") }}'
with DAG(
default_args=default_dag_args(retry_delay=1, dagrun_timeout=180),
start_date=datetime(2023, 2, 10),
schedule_interval='0 18 * * *', # 예시) 매일 새벽 3시에 dag 실행
dag_id='example',
catchup=False,
max_active_runs=10,
) as dag:
start_task = EmptyOperator(task_id='start')
end_task = EmptyOperator(task_id='end')
process_task = PythonOperator(
task_id='process', python_callable=process, op_kwargs={'job_name': job_name}
)
train_task = PythonOperator(
task_id='train', python_callable=train, op_kwargs={'job_name': job_name}
)
select_deploy_method_task = BranchPythonOperator(
task_id='select-endpoint-deploy-method',
python_callable=select_endpoint_deploy_method,
)
create_endpoint_task = PythonOperator(
task_id='create-endpoint',
python_callable=create_endpoint,
op_kwargs={'job_name': job_name},
)
create_model_task = PythonOperator(
task_id='create-model',
python_callable=create_model,
op_kwargs={'job_name': job_name},
)
update_endpoint_task = PythonOperator(
task_id='update-endpoint',
python_callable=update_endpoint,
op_kwargs={'job_name': job_name},
)
(start_task >> process_task >> train_task >> select_deploy_method_task >> [create_endpoint_task, create_model_task])
(create_model_task >> update_endpoint_task >> end_task) # endpoint가 존재하는 경우
(create_endpoint_task >> end_task) # endpoint가 존재하지 않는 경우
DAG 파일을 Amazon S3에 업로드 하면 Amazon MWAA 웹 콘솔에서 아래와 같이 워크플로우에 반영됩니다.
그림 5. 예시)카테고리 개인화 정렬 MWAA 워크플로우 DAG
결과
Amazon SageMaker의 Processing Job과 Training Job이용해 반복작업을 줄여나가면서 성공적으로 모델을 학습시킬 수 있었고, Amazon MWAA을 활용해 MLOps를 구축하며 전체 워크플로우를 자동화하며 성공적으로 프로젝트를 완수할 수 있었습니다.
기존에 카테고리 화면에서 사용중이었던 인기도 기반 로직과 추천서비스를 활용한 추천항목을 가지고 A/B 테스트를 수행한 결과, 카테고리 화면 전체에서 CTR (Click-Through Rate)향상이 있었고, 특히 ATF(Above the fold, 스크롤 없이 눈에 보이는 영역) 영역인 카테고리별 상위 2개 상품에 대해서는 80% 이상의 CTR 향상이 있었습니다. 개인화 추천 품질에 대한 A/B 테스트를 성공적으로 마친 후, 현재는 서비스 운영환경에 배포되어 29CM의 카테고리 화면에서 개인화된 정렬 결과를 보여주고 있습니다.
무엇보다 엔지니어 경험이 없는 데이터과학자임에도 불구하고 Amazon SageMaker와 Amazon MWAA라는 관리형 클라우드 서비스를 통해 모델개발에서부터 배포환경구성과 MLOps 시스템을 손쉽게 구현할 수 있었다는 점에서 의미있는 결과라고 생각합니다.
회고 및 29CM의 향후 계획
카테고리 개인화 정렬 모델 개발부터 MLOps 인프라 구축을 진행했고, 1차 A/B 테스트에서 수집된 피드백을 반영하고, 2차 A/B테스트를 거친 뒤 전수 적용하는데 까지 약 6개월 정도의 시간이 걸렸습니다. Amazon SageMaker와 Amazon MWAA라는 관리형 서비스를 이용해 빠른 속도로 추천 시스템을 만들 수 있었습니다. 29CM 추천 서비스팀의 다음 목표는 카테고리 개인화 정렬모델의 개선과 MLOps의 고도화입니다. 그리고 이번에 구축 한 MLOps를 활용해 브랜드 홈 화면에서도 개인화 추천을 제공하는 것입니다. 이외 여러 가지 추천 서비스로 확장을 계획하고 있습니다.
마무리
머신러닝을 활용하는 많은 기업에서 MLOps는 어려운 분야라는 인식을 가지고 있습니다. ML 모델을 만들고 학습하는 것 뿐만 데이터 전처리와 배포환경을 구축하는 상호 의존적인 워크플로우를 수립하는 것이 우선적으로 되어야 합니다. 그리고 CI/CD 체계를 구축 해 Ops를 자동화 하는 것 까지 구성되어야 합니다. 이렇게 방대한 업무의 범위와 난이도를 생각했을 때, 관리형 서비스를 사용하는 것으로 그 도입시기를 앞당길 수 있었습니다.
MLOps은 넓은 범위의 개념이기 때문에, 그 도입에는 기술역량 뿐만 아니라 강력한 스폰서십과 관계부서의 협조가 필수적인 부분입니다. 29CM에서 또한 인프라 보안팀, 데이터 그로스팀, 추천서비스팀과 발견스쿼드 팀 까지 다양한 관계부서의 협업이 있었고, 이들의 긴밀한 협조가 기술 뒤에 숨어있는 성공 포인트였습니다.
이 프로젝트는 AWS에서 고객사를 대상으로 지원하는 AI/ML Concierge Program의 도움을 받아 진행되었습니다. 이 프로젝트를 진행하신 29CM와 AWS의 많은 분들께 감사의 말씀을 드립니다.