딥러닝 추천 시스템 in production

이전 글로 딥러닝 기반 개인화 추천 시스템의 모델에 대한 부분을 소개 했었어요. 이번에는 당근마켓에서 추천 시스템을 지속적으로 업데이트하는 파이프라인과 서빙 시스템을 구축한 내용을 소개하려고 합니다.

Kubeflow Pipelines

Kubeflow Pipelines은 Kubernetes에서 동작하는 Docker 컨테이너 기반 머신러닝 워크플로우 플랫폼입니다. 구글에서 오픈소스로 만들고 있다니 더 관심이 갔어요.

문제는 회사에서 아직 Kubernetes를 사용하지 않았기에 설치/관리에 대한 부담이 있었습니다. 그래서 처음에는 많이 사용하는 Airflow로 워크플로우를 작성했는데, 기존에 작성한 코드를 DAG로 변환하는 작업량이 꽤 많아 번거로웠습니다. 그리고 웹 관리 페이지는 처음 접하기에 복잡하고 쉽게 이해되지 않은 부분이 많았습니다. 힘들게 작업했지만 앞으로 계속 쓰기에는 만족적이지 않았습니다.

그래서 Kubeflow Pipelines도 비교해보기 위해 시도해봤습니다. 일단 간편해보이는 웹으로 설치하는 방법을 따라 해봤는데 무척 간편하고 쉬웠습니다. 단순히 웹에서 몇가지 설정만 하고 클릭하면 잠시 후에 url을 통해 접속할 수 있어요!

파이프라인 만들기

다음으로는 추천 시스템 워크플로우를 Kubeflow Pipelines에서 동작하도록 파이프라인을 만들어야 합니다. 기존 추천 시스템 작업들을 컨테이너 기반으로 작성했었기 때문에 Kubeflow 파이프라인을 보다 쉽게 만들 수 있었습니다. 파이프라인 만드는 방법은 Kubeflow Pipelines SDK를 설치하여 python으로 작성하며 관련 문서는 여기를 참고해주세요.

작성된 파이프라인을 Kubeflow Pipelines에 업로드하면 아래와 같이 웹 화면에서 확인할 수 있고, ‘Create run’ 버튼으로 바로 실행해볼 수 있습니다!

위의 오른쪽 이미지처럼 하이퍼 파라미터를 테스트 해볼 때 옵션 값을 변경하여 실험을 간편히 실행할 수 있습니다.

Scheduling

추천 시스템 파이프라인에서 꼭 필요한 부분으로 파이프라인을 지속적으로 업데이트하기 위해서는 cron 작업 같이 주기적으로 자동 실행을 해야 합니다. 이 부분은 Kubeflow Pipelines에서 제공하는 Recurring runs 기능으로 간편히 설정할 수 있었습니다.

위의 화면은 하루 두번 추천시스템을 업데이트하도록 설정한 화면입니다. 참고로 trigger 시간은 아직 지역 시간이 지원되지 않아 GMT 시간 기준으로 설정했습니다.

Pipelines API

추천 시스템 파이프라인 패키지 파일을 만든 후 Pipelines 웹화면에서 설정하는 단계를 정리해보겠습니다.

  1. 패키지 파일을 업로드
  2. Experiment 만들기 (초기 한번)
  3. 새로운 파이프라인으로 반복 실행 설정
  4. 기존 반복 실행 중지/삭제
  5. 기존 파이프라인 삭제

아직 기존 파이프라인과 반복 실행을 변경할 수 있는 기능을 제공하지 않아 중지/삭제합니다.

문제는 추천 시스템을 계속 개선해나가면서 파이프라인이 변경될 때마다 웹에서 설정해야하는 단계를 거쳐야 합니다. 이 부분은 매우 번거로웠고, Kubeflow Pipelines을 다시 한번 고려해야 하는 생각까지 했었어요.

그래서 다른 더 간편한 방법을 찾던 중 공식 문서에는 문서화가 아직 안되었지만, 다행히 공식 코드에서 Pipelines API를 사용할 수 있는 예제를 찾을 수 있었습니다. 지금은 Pipelines API로 배포 설정의 모든 단계를 처리하는 스크립트로 만들어서 파이프라인을 변경하면 스크립트를 실행하여 손쉽게 파이프라인을 배포하고 있습니다.

머신러닝 파이프라인

추천 시스템의 머신러닝 워크플로우 작업들은 다음과 같은 순서로 연결되어 있고, 단계별로 더 자세히 알아보겠습니다.

  1. 데이터 수집
  2. 전처리
  3. 학습
  4. 유사 검색 학습 / 모델 저장소 복사
  5. 배포

데이터 수집

당근마켓 안드로이드/iOS 클라이언트에서 firebase를 통해 기록된 이벤트 로그는 BigQuery에 저장되고 있습니다. 추천 시스템에서 필요한 사용자가 본 글에 대한 로그는 클라이언트에서 이벤트가 발생하도록 구현했고, 이제 BigQuery에서 필요한 데이터를 수집할 수 있습니다. 최종 수집된 데이터 테이블은 BigQuery에서 제공하는 csv 추출 기능으로 Cloud Storage로 저장하도록 했습니다.

여기서 중요하게 고려해야 하는 건 BigQuery 테이블이 매우 크다는 점입니다. 모든 이벤트 로그의 많은 정보가 한 테이블에 기록되기 때문에 스캔해야하는 테이블 용량이 커서 자주 요청한다면 시간과 비용이 높아지기 때문입니다. 물론 날짜 단위로 파티션되지만 일간 방문자수가 많아져서 고려하지 않을 수 없습니다.

하루에 여러번 반복적으로 모든 데이터가 아닌 필요한 데이터만 조회하기 위해 중간 단계의 테이블을 수집하도록 했습니다. 이 단계는 외부 cron 작업으로 준비했고, 새로운 필요한 데이터만 지속적으로 수집하여 업데이트하고 있습니다.

결과로 현재의 데이터 수집 작업에서는 필요한 데이터로 축소된 테이블을 통해 수집하여 시간/비용 문제를 해결할 수 있었습니다.

전처리

수집된 csv는 실제 학습에 쓰이기 전에 다음과 같은 데이터 변환 처리가 필요합니다.

  • 글/지역 ID 사전 구축
  • x개 이하 존재하는 데이터 제외
  • ID를 index 번호로 변환
  • 빠르게 읽기 위한 tfrecords 형식으로 저장

위의 작업을 학습 모델에 포함할 수도 있지만, 반복적인 학습 처리 속도가 느려지기 때문에 미리 해야 할 필요가 있습니다.

전처리를 구현하는 방법으로는 Tensorflow Transform(tft)를 사용했습니다. tft는 초기 실험적인 머신러닝 프로젝트에서 사용할 때는 불필요할 수 있지만, 배포 시스템에서는 많은 이점을 얻을 수 있습니다.

  • 사전과 함께 전처리 작업을 최종 배포 모델에 포함
  • 전처리 과정을 tensorflow 모델에 추가
  • 사전 파일 자동 구축
  • Cloud Dataflow 환경에서 병렬로 실행하여 시간 단축
  • 쉽게 사용할 수 있는 전처리 함수 제공

전처리 과정을 tensorflow 모델에 간편히 포함시킬 수 있는 점이 매우 편리했습니다. 예측할 때 데이터를 전처리 할 필요없이 csv의 데이터 형식으로 바로 사용할 수 있기 때문입니다.

아래는 전처리 파이프라인의 과정 그래프를 볼 수 있고, Cloud Dataflow에서 병렬 처리로 14개 인스턴스 확장하여 실행된 결과 18분으로 소요시간을 단축할 수 있었습니다.

학습

모델을 개발할 때는 사내 워크스테이션에서 학습을 실행했지만, 실제 서비스에 안정적으로 학습되기 위해서 클라우드 학습 환경인 Cloud ML Engine(CMLE)을 사용하고 있습니다.

기존 다른 프로젝트의 모델도 Tensorflow Estimator API 작성하여 CMLE 환경에서 분산 학습을 보다 쉽게 사용하고 있었습니다. 이번 추천 모델도 동일한 방식으로 작성하여 사내 워크스테이션에서 클라우드 학습 환경으로 실행하기 편했습니다. 앞으로는 TF 2.0의 Keras model도 production에 강화됐다니 고려해보려고 해요.

학습된 모델은 Cloud Storage에 저장이 되며 추가 작업으로 모델에서 학습된 글 embeddings 값을 추출합니다. 추출된 데이터는 binary 파일로 Cloud Storage에 저장하고, 이후 유사 벡터 인덱스의 학습과 서빙에서 사용합니다.

Pipeline Metrics
일반적으로 학습이 완료된 후 실행하는 evaluation과 prediction 작업이 있습니다. evaluation으로 학습에 대한 metric 측정값으로 평가할 수 있고, prediction은 샘플을 뽑아서 예측을 실행해 볼 수 있습니다.

이러한 evaluation 값은 Kubeflow Pipelines의 형식에 맞게 파일로 저장하면 다음과 같이 쉽게 볼 수 있습니다. 그리고 prediction도 markdown, html 형식 등의 파일로 저장한다면 학습 완료된 결과를 Kubeflow 웹 화면에서 쉽게 확인할 수 있습니다.

왼쪽 : 각 학습에 따른 metrics 확인, 오른쪽 : 학습 후 예측 결과 확인 (이미지를 클릭하면 크게 볼 수 있어요.)

Output Viewer
Tensorboard는 이제 머신러닝 프로젝트에 빠질 수 없는 거의 필수 도구가 된 것 같습니다. Kubeflow Pipelines의 Output Viewer 기능으로 Tensorboard를 아주 쉽게 실행해볼 수 있습니다! ‘Start Tensorboard’ 버튼 클릭만으로 자동으로 Kubernetes 노드에서 Tensorboard를 실행하고 proxy를 연결하여 바로 웹으로 접속할 수 있는 환경을 만들어줍니다.

유사 검색 학습

빠르게 후보를 뽑기 위해서 실제 예측에는 벡터 인덱스를 사용합니다. 이렇게 설계한 부분에 대해 더 자세한 내용은 이전 글을 참고해주세요.

당근마켓에서는 예전부터 faiss 라이브러리를 사용해서 유사 벡터 검색 서버인 faiss-server를 개발하여 안정적으로 사용하고 있었습니다. 추천 시스템에서도 같은 목적으로 이미 개발한 서버를 활용합니다.

faiss index에 데이터가 많아지면 검색속도가 느려지고 메모리 사용량이 늘어나게 됩니다. 이러한 부분은 faiss index 학습을 통해 해결할 수 있어서 배포하기전에 학습을 하게 됩니다.

모델 저장소 복사

학습한 Tensorflow 모델은 Google Cloud Storage에 저장되는데, 추천 서버는 AWS에 배포되어 있어 AWS S3 저장소로 복사합니다. 이 작업은 유사 검색 학습과 의존성이 없어서 동시에 실행합니다.

배포

추천 서버는 AWS ECS에 배포되어 있습니다. 이 작업에서는 aws cli 명령으로 ECS 서비스를 재시작 요청하여 새롭게 학습된 모델 데이터를 추천 서버에 적용하고 있습니다.

여기까지 머신러닝 워크플로우의 모든 작업이 끝나고 새로운 추천 시스템이 업데이트가 모두 완료되었습니다! 이제 사용자에게 계속해서 최신의 글로 개인화 추천이 가능하게 되었습니다.😃


서빙

Cloud Dataflow, ML Engine을 활용하기 위해 머신러닝 워크플로우는 Google Cloud에서 실행되지만, 당근마켓 서버는 AWS seoul region에서 운영되고 있습니다. 서버 간 직접적인 빠른 통신을 위해 머신러닝 관련 서버는 당근마켓 서버와 동일한 AWS에서 동작하도록 했습니다.

서버 간 동작하는 상황을 알아보겠습니다. 당근마켓 서버에서 사용자의 정보를 입력으로 추천 후보를 요청하면 추천 서버는 해당 사용자에게 추천할 만한 글 후보 200개를 다음과 같이 처리하여 응답합니다.

추천 후보 결과를 받기 위해서 2단계를 거치지만 총 응답 시간은 0.01초 이하로 매우 빠르게 처리됩니다. 아직은 CPU만으로 충분해서 GPU를 사용하지 않고 있습니다. 그리고 접속량이 늘어도 자동으로 확장되는 Auto Scaling 기능을 적용하여 빠른 추천 처리 시간이 유지됩니다.

워크플로우 작업과 마찬가지로 머신러닝 관련 서버도 컨테이너 기반으로 개발하여 AWS ECS에 배포하고 Auto Scaling 기능을 적용하여 운영하기도 간편했습니다. 그럼 계속해서 관련 서버에 대해서 알아보겠습니다.

당근마켓 서버

접속하는 사용자마다 즉시 개인화된 추천 요청을 하기 위해 필요한 사용자 정보(최근 본 글 50개)를 빠르게 읽어야 합니다. 이 정보는 BigQuery에 저장되어 있지만 빠르게 읽을 수 없고, 기본 저장소인 RDBMS에서는 부담이 될 수 있어 Redis 메모리 저장소를 활용하여 사용자 정보를 빠르게 읽을 수 있도록 구현했습니다.

Tensorflow Serving

학습된 추천 시스템 tensorflow 모델은 tensorflow-serving Docker 이미지를 사용하여 컨테이너 기반 서버를 실행했습니다. tensorflow-serving은 tesorflow에서 공식 제공하는 Docker 이미지로 production 서빙하기에 가장 쉽고 빠른 방법인 것 같습니다.

배포된 추천 모델 서버는 네트워크 전체를 실행하지 않고, 중간 부분인 user vector까지만 생성하여 응답하는 역할을 합니다. 빠른 속도를 위해서 네트워크의 나머지 뒷 부분 연산은 유사 검색 서버로 대체하도록 설계되었습니다.

Faiss Server

faiss 라이브러리로 faiss index 기능을 서비스하는 gRPC python 서버를 만들어 사용하고 있습니다. 서버 실행 시 학습된 faiss index 데이터 파일을 로드하고, vector값으로 검색 요청을 받으면 가장 유사한 벡터의 상위 ID 목록을 응답하도록 구현하였습니다.


마치며

Kubeflow Pipelines 0.4.1 버전부터 지금은 0.5.0 버전으로 업데이트해서 사용한 경험으로 만족하고 있습니다. 아직 1.0 버전이 안되었지만 실서비스 머신러닝 파이프라인으로 큰 부족함이 없었습니다. 앞으로 새로운 프로젝트에서도 계속 사용하고, 더불어 Kubeflow의 학습/서빙 등 기능을 다양하게 활용할 예정입니다.

혹시 당근마켓 개인화 추천 기능을 사용해보고 싶으신가요? 당근마켓 앱에서 글 상세 화면 하단에 나오는 ‘…님, 이건 어때요?’ 에서 확인할 수 있습니다. 참고로 글 상세 화면 하단에는 ‘함께 봤어요’ 연관 상품 추천이 준비되지 않았을 때 ‘이건 어때요?’ 개인화 추천을 노출하고 있습니다.

그리고 첫 화면의 시간 순인 글 목록 중간에 개인화 추천 글을 노출하는 A/B 테스트 중이며, 곧 당근마켓 첫화면 피드에서 자연스럽게 개인화 추천을 접하실 수 있도록 할 예정입니다.