case study

머신러닝 모델 API화 - 상품 카테고리 분류기

이 글에서는 홈쇼핑모아에서 사용하는 여러 머신러닝 모델 중 하나인 상품 카테고리 분류기를 API화 했던 과정을 소개한다.

2021.2.5(금)
머신러닝 모델 API화 - 상품 카테고리 분류기

이 글에서는 홈쇼핑모아에서 사용하는 여러 머신러닝 모델 중 하나인 상품 카테고리 분류기를 API화 했던 과정을 소개한다.


개요

기존에 홈쇼핑모아에 적용된 상품 카테고리 분류기는 on-premise로 구성된 서버에 배포돼 있었다. on-premise에 구축된 서버에 배포를 진행하다 보니 예기치 못한 서버의 장애나, 다른 프로세스에 의해 서버의 리소스가 점유돼 버리거나 하는 이슈들이 생기는 경우가 많았다.


때문에 Kubernetes를 활용해 자동화된 리소스 관리가 필요했고, Kubernetes를 활용하게 되면 기존 모델 API를 배포하는 과정을 변경해야 할 필요가 있었다. 


이 글에서는 어떻게 카테고리 분류를 수행하는 모델이 어떤 형태로 API화 돼 있으며, GKE(Google Kubernetes Engine)에 어떤 과정을 통해 배포를 진행했는 지에 대해 설명할 것이다.


홈쇼핑모아에서 사용 중인 상품 카테고리 분류기 API는 다음 그림과 같이 크게 두 개의 부분으로 구성돼 있다.



사용자가 Client API를 통해 카테고리 분류를 요청하면, Client API는 사용자로부터 입력받은 상품명과 이미지 주소를 벡터화해, Model API에 해당 값을 전달한 후, 분류 결과를 반환받는다.


위 구조에서 Model API, Client API가 각각 어떻게 구성돼 있고, 어떻게 각 API를 실행시키는지, 그 과정에 대해 상세히 설명할 것이다.


Model API

실제 카테고리 분류를 수행하는 모델인 Model API는 tensorflow-serving을 이용해 구성돼 있다.


Tensorflow Serving은 Tensorflow 모델을 효과적으로 배포 및 서비스화 하기 위해 개발된 TFX의 일부분이며, 학습된 모델을 적은 자원으로도 빠른 속도를 갖는 Serving API를 띄울 수 있게 해준다.


이 Model API를 GKE(Google Kubernetes Engine) 위에 배포해 자동으로 Scale-out이 가능하도록 할 것이다.


준비 과정

준비 과정은 크게 세 가지가 필요하다. 학습한 모델을 Tensorflow Serving에서 사용할 수 있는 형태로 준비하는 것과 준비된 모델을 Kubernete 에서 접근 가능할 수 있도록 GCS로 업로드 하는 것, 그리고 적절하게 Tensorflow Serving 설정 파일을 생성하는 것이다.


1. SavedModel Format으로 학습된 모델 준비



Tensorflow Serving을 활용하기 위해서는 SavedModel 포맷으로 학습된 모델이 필요하다. 모델을 학습시키는 시점에서 SavedModel 포맷으로 저장하지 않았더라도, 다음과 같이 SavedModel 포맷으로 변경이 가능하다.


혹은, tensorflow 1.x 에서 저장한 Keras 모델도 다음과 같은 방식으로 손쉽게 SavedModel 포맷으로 변경 가능하다.



정상적으로 SavedModel 포맷으로 저장이 됐다면, saved_model.pb 파일과 함께 인퍼런스에 필요한 리소스들이 폴더로 존재하게 될 것이다.

 

2. 학습된 모델 GCS로 업로드

최종적으로 Model API를 GKE에 업로드 해야 하기 때문에, GKE에서 접근할 수 있도록 변환된 SavedModel을 GCS로 업로드 해야 한다. 현재 모델의 저장은 GCS에 다음과 같은 형태로 업로드 돼 있다.



cate_dict 폴더는 모델에서 예측한 결과를 사람이 읽을 수 있는 형태의 카테고리명으로 변환하기 위한 정보를 갖고 있는 파일들이 들어 있는 폴더이며, tokenizer 폴더는 상품명을 전처리해 띄어쓰기를 수행하는 모듈이 들어있는 폴더이다.


hybrid_classifier 폴더는 텍스트와 이미지 정보를 모두 활용해 상품의 카테고리를 예측하는 모델이 저장된 폴더이며, text_classifier 폴더는 텍스트 정보만 활용해 상품의 카테고리를 예측하는 모델이 저장된 폴더이다. 


두 모델을 전부 사용해 최종적으로 앙상블을 하기 때문에 두 모델 모두 SavedModel 형식으로 저장해 GCS에 업로드 했다.


각 모델 폴더에는 다음과 같이 숫자를 폴더명으로 갖는 구조로 모델이 업로드 돼 있어야 한다. 이후에 Tensorflow Serving을 이용해 모델을 서빙할 때, Tensorflow Serving에서는 기본적으로 가장 최신 버전의 모델 번호를 불러온다(설정을 통해 변경 가능하며, 동시에 여러 버전의 모델을 서빙하거나, 특정 버전의 모델만 서빙하는 것이 가능하다).


이 숫자들이 각 모델의 id가 되는 구조이기 때문에 반드시 숫자로 이뤄진 폴더로 모델을 저장해야 한다.



3. models.config 파일 설정

tensorflow serving을 이용해 Model API를 수행하고자 할 때에는, 설정파일(여기서는 models.config 파일)을 작성해야 한다.


카테고리 분류 모델 API를 띄울 때에는 다음과 같은 형태로 설정 파일을 작성했다.



Tensorflow Serving을 위와 같은 설정 파일을 이용해 수행한다면, 한 번에 두 종류의 모델(name key로 명시된 text_classifier, hybrid_classifier)을 API화 할 수 있다.


text_classifier, hybrid_classifier 모두 각 base_path에 존재하는 모델들을 로드해 API화 하게 되며, 이때 각 base_path (/models/text_classifier, /models/hybrid_classifier)에는 위 과정에서 GCS에 저장했던 것 처럼 번호를 폴더명으로 하는 SavedModel 들이 저장돼 있어야 한다. 


그래야 Tensorflow Serving이 모델을 로드 할 때, 가장 번호가 큰 모델을 자동으로 불러오게 된다. 그리고 가급적이면 base_path의 가장 하위 폴더 명과 name은 매칭시키도록한다.


실행 과정

실행 과정은 크게 세 부분으로 나눠져 있다. 하나는 Dockerfile을 이용해 docker image 빌드 & GKE deployment 배포 과정, 다른 하나는 GKE Ingressroute Service 배포 과정, 그리고 traefik 의 middleware 설정이다.


1. Docker image 빌드 & GKE deployment 배포

Model API에서 사용되는 Dockerfile과 Dockerfile 내부에서 사용되는 serving_api_entrypoint.sh, GKE에 배포하기 위한 deployment.yaml은 여기에서 확인할 수 있다.


이 Dockefile을 이용해 GKE 상에서 tensorflow serving API를 수행해야 하기 때문에, tensorflow serving Dockerfile을 기반으로 생성했으며, serving_api_entrypoint.sh 파일을 수행하는데 필요한 준비 작업을 진행하고, 최종적으로 serving_api_entrypoint.sh를 수행하는 구조다.


serving_api_entirypoint.sh 파일에서는 GCS에 업로드한 SavedModel 파일을 container 내부로 다운로드하고, 미리 작성한 models.config 파일을 이용해 tensorflow-serving API를 실행하는 역할을 한다.


prepare_serving.py 스크립트에서는 모델을 서빙하는데 필요한 리소스들을 GCS에서 컨테이너 내부로 다운로드하는 과정을 수행한다.


보통 Dockerfile을 빌드해 Docker image repository로 업로드하지만, Github CI를 활용해 자동으로 이미지를 빌드 후 GCS 내부 이미지 레포지토리에 업로드 하도록 했다. 따라서 생성한 Dockerfile을 직접 빌드하는 과정은 이 문서에서 제외됐다.


– Dockerfile에 대한 설명

expose 8500, 8501을 이용해 두 포트를 열어줬는데, 이 두 포트가 tensorflow serving에서 사용되는 REST API와, gRPC 호출에 사용되는 포트이다. 따라서 두 포트를 열어줬다.


deployment.yaml에서는 asia.gcr.io/storage-273502/buzzni-ai-category-api-serving에 저장된 이미지를 이용해 GKE에 컨테이너를 배포하는 것을 확인할 수 있다.

buzzni-ai-category-api-serving 이미지가 정상적으로 빌드되고, 이것이 이미지 레포지토리에 정상적으로 업로드 됐다면, 로컬 터미널에서는

kubectl apply -f deployment.yaml

명령어만 수행해 Model API를 배포할 수 있다.


이 명령어가 수행되면 다음과 같이 GKE에 정상적으로 카테고리 분류를 위한 Model API를 위한 deployment pod이 생성된다.



2. GKE Ingressroute Service 배포

위 과정을 진행했다고, 바로 API Endpoint가 생성되고, URL을 통해 접근할 수 있는 것은 아니다. AI Lab의 GKE는 Ingressroute과 Traefik을 이용해 외부 접속을 GKE 내부의 Pod과 연결시켜 주고 있다. 


이를 위해서는 별도의 Service Pod를 띄워야 하며, 해당 service의 설정 파일은 deployment_service.yaml 파일이다.


이 설정 파일에서 복잡한 역할을 담당하는 것은 아니고, 기존에 정의된 vpn-web entrypoint를 활용해, 내부 IP 망으로부터 들어오는 요청을 설정한 내부 deployment pod에(selector: app: category-api-serving 설정) 전달해 주겠다는 의미다.


그리고, URL이 /category-serving/과 매칭되는 요청에 대해서 api-strip이라는 middle-ware를 통하겠다는 설정을 줬는데, 이 부분에 대한 설명은 이어서 하도록 할 것이다.


때문에 Ingressroute Service 를 배포하기 위해서는 로컬에서 다음 명령어만 수행해 주면 된다.


kubectl apply -f buzzni-ai-category-api/kubernetes/deployment_service.yaml


3. traefik-middleware 설정

앞서 설정한 middleware를 통해 API 요청 주소를 변경해주지 않는다면, 우리가 원하는 형태의 접근 URL이 생성되지 않는다. 따라서 이에 맞게 설정을 추가해야 하는데, 다음과 같은 kubectl 명령어를 이용해 기존에 정의된 설정값에 원하는 주소를 추가해주면 된다.


kubectl edit middlewares.traefik.containo.us api-strip


위 명령어를 이용해 traefik 설정에서 api-strip 주소에 deployment_service에서 설정된 category-serving 키워드를 추가해 주면 된다.


결과 확인

여기까지 문제 없이 수행이 됐다면, 다음 URL 을 통해 정상적으로 Model API가 실행되고 있는지 확인할 수 있다.


http://IngressRoute주소/category-serving/v1/models/hybrid_classifier


요청 결과는 다음과 같다.



꼭 위와 같은 메시지가 정상적으로 출력이 되는지 확인하고, Client API를 배포하도록 하자.


Client API

Client API는 사용자가 실제 요청을 보내는 API이며, Swagger 기반의 Flask App으로 개발돼 있으며, Model API와 마찬가지로 GKE에 배포돼 있다.


준비 과정

Model API와 마찬가지로, Client API 역시 Client API에서 사용되는 몇몇 자원(여기에서는 Text Tokenizer, idx → plain text dict)을 미리 GCS에 업로드 하는 과정이 필요하다.


1. 필요한 파일 GCS 업로드

Client API에서 필요한 리소스는 두 가지가 있다. 하나는 텍스트 정보를 인코딩 하기 위한 tokenizer 이며, 다른 하나는 분류 결과의 index 값을 plain text로 변환하기 위한 python dictionary(텍스트, 하이브리드 두 개)이다.

Model API에서와 마찬가지로, 비슷한 코드에서 해당 작업을 진행해 주고 있으며, 배포 전에 미리 실행해 GCS에 필요한 파일들을 업로드 해야 한다.


실행 과정

실행 과정 역시 Model API와 마찬가지로, Dockerfile을 이용해 docker image 빌드 & GKE deployment 배포 과정, 다른 하나는 GKE Ingressroute Service 배포 과정, 그리고 traefik의 middleware 설정 세 가지 단계로 이뤄져 있다.


1. docker image 빌드 & GKE deployment 배포

Client API에서 사용되는 Dockerfile과, Dockerfile 내부에서 사용되는 client_api_entrypoint.sh, GKE에 배포하기 위한 deployment.yaml은 여기에서 확인할 수 있다.


Client API 에서 사용되는 Dockerfile은 Model API 에서 사용되는 Dockerfile과 같은 구조를 갖고 있으며, client_api_entrypoint.sh 파일을 실행하는데 필요한 준비 작업을 진행하고, 최종적으로 client_api_entrypoint.sh 를 수행하는 구조이다.


client_api_entrypoint.sh 에서는 GCS에 업로드한 여러 파일들을 docker container 내부로 다운로드 하고, client api 프로세스를 수행시키는 역할을 한다.


하지만 Model API 에서와 마찬가지로, Github CI 에서 자동으로 이미지를 빌드하도록 설정하였기 때문에 직접 Dockerfile을 빌드할 필요가 없으며, 배포 역시 쿠버네티스 yaml 파일인 deployment.yaml 파일을 이용해 손쉽게 배포할 수 있다.


Github CI 에서 정상적으로 이미지가 빌드되었다면, 로컬 터미널에서 다음 명령어를 통해 Client API를 배포할 수 있다.


kubectl apply -f deployment.yaml 


위 명령어가 수행되면, 다음과 같이 deployment pod이 생성된 것을 확인할 수 있다.



2. GKE Ingressroute Service 배포

Client API 역시 마찬가지로 Ingressroute Service를 추가해야 한다.


Ingressroute Service를 위한 설정파일은 deployment_service.yaml 파일이며, 다음 명령어를 이용해 서비스 배포가 가능하다.


kubectl apply -f deployment_service.yaml

 

3. traefik-middleware 설정

그리고 traefik의 middleware 역시 설정해줘야 한다.


Model API에서 설정하였던 것과 마찬가지로 다음 명령어를 통해 api-strip 쪽을 수정해 주면 된다.


kubectl edit middlewares.traefik.containo.us api-strip


결과 확인

여기까지 문제가 없이 실행이 됐다면, URL 을 통해 정상적으로 Client API가 실행되고 있는지 확인할 수 있다.


 

개선점

지금 크게 두 가지 부분에 필요한 개선점이 있다고 생각한다(모델 자체에 대한 개선점을 제외한).


첫 번째는 Client API를 단순 Flask만 이용했는데, gunicorn으로 한번 더 wrapping해 multi-processing을 적용하거나,  fast-api, sanic 등을 활용해 비동기로 동시에 여러 요청을 처리할 수 있도록 하는 것이다. 


현재 배포돼 사용중인 API에는 적용돼 있지만, 이 문서를 작성했던 목적은 다른 AI 리서치 엔지니어가 개발한 모델을 직접 서빙하기 위한 가이드 작성이었기 때문에 제외했었다.


두 번째는, Tensorflow Serving을 활용한 Model API와, Client API의 레포지토리를 분리하는 것이다.  현재 두 가지 API가 한 레포지토리에 있기 때문에 조금 불편한 부분들이 있는데, 구분지어 별도의 레포지토리로 하는 것이 어떨까 한다.

버즈니
홈쇼핑모아
스타트업
머신러닝
API
상품 카테고리 분류
buzzni
pr@buzzni.com