Python

Docker기반의 RESTful api로 pytorch model 배포하기[1-5]

DoDo&ToTo 2020. 11. 19. 16:18

백엔드에 대해서는 따로 배운적은 없지만..

예전부터 model을 만드는 것부터 배포하는 것까지의 과정을 한번 해보고 싶긴 했었다.

 

해당 과정에 대한 기록을 남겨두기 위해서 오랜만에 블로그를 열었다 :)

 

pytorch로 model 학습을 완료한 상태라고 가정을 하고, 해당 model을 restful 형태로 service하는 과정을 남겨본다.

대략적인 순서는 아래와 같음.

  1. Flask RESTful을 이용하여, HTTP로 호출할 수 있는 RESTful api 만들기
  2. 학습한 모델의 weight와 graph를 하나의 파일로 만들기(torchscript)
  3. 보안을 위해, 2번에서 만든 torchscript파일을 encryption하기
  4. 보안을 위해, cython을 이용하여 python script를 library형태로 만들기
  5. Docker image 만들고 배포하기

편의를 위해서, 하나의 순서 당 하나의 글을 쓸 예정

이번 글에서는, 1번에 대해 정리해본다.

 

 

  • Flask는 python library로 웹 개발을 손쉽게 해주는 tool이다.
    • 내부의 자세한 구조나 형태는 웹 개발을 안해봐서 아직 지식이 부족하다.
  • Flask를 기반으로 RESTful api를 더 손쉽게 만들 수 있도록 해주는 tool로 Flask-RESTful이 있다.
  • pip를 이용해서 간단하게 설치 가능
pip install Flask-RESTful
  • 자, 이제 간단한 RESTful web server를 10줄 이내의 코드로 만들 수 있다.
import argparse
from flask import Flask
from flask_restful import Api

from resources.MyAPI import MyAPI
from resources.custom_errors import errors

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--port', type=int, required=True, help='port number')
    parser.add_argument('--debug', default=False, action='store_true', help='debug mode')
    parser.add_argument('--thread', default=False, action='store_true', help='multi-thread mode')
    parser.add_argument('--processes', type=int, default=1, help='num. of processes')
    opt = parser.parse_args()

    app = Flask(__name__)
    api = Api(app, errors=errors)

    api.add_resource(MyAPI, '/predict', resource_class_kwargs={'debug': opt.debug})

    app.run(port=opt.port, debug=opt.debug, threaded=opt.thread, processes=opt.processes)
  • web server 완성! args 관련 부분을 제외하면 4줄이면 web server를 만들 수 있다.
  • Flask를 초기화하고, 이를 restful api로 감싸준다.
  • restful api를 이용해서 resource를 추가할 수 있다. 여기서 resource는 url 하나로 이해하면 쉽다.
    • 하나의 resource의 복수의 url을 지정할 수도 있다.
  • 즉, server_address/predict를 web에서 호출하면 MyAPI에 정의된 method가 실행되도록 routing 해준다. (편하다..)
  • resource에 등록하는 것은 Resource class를 상속한 class로 정의된다.
  • class의 init function에 넘겨줄 argument는 resource_class_kwargs에 dictionary 형태로 넘겨줄 수 있다.
  • api에 resource를 추가하고 나면, app.run으로 server를 올릴 수 있다. 이 때 port 번호나 debuging mode 등을 parameter로 줄 수 있다.
from flask_restful import Resource, reqparse
import werkzeug
import numpy as np
import cv2

from resources.MyModel import MyModel
from resources.custom_errors import WrongMethodError, NoneArgumentError, NotExistResultError

model = MyModel()

class MyAPI(Resource):
    def __init__(self, **kwargs):
        # Create a request parser
        parser = reqparse.RequestParser()
        parser.add_argument('image', dest='image', type=werkzeug.datastructures.FileStorage, location='files')
        self.args = parser.parse_args(strict=True)
        self.debug = kwargs['debug']

    def get(self):
    	raise WrongMethodError

    def delete(self):
        raise WrongMethodError
        
    def put(self):
    	raise WrongMethodError

    def post(self):
        ''' prediction '''
        image_file = self.args.get('image', None)
        if image_file is None:
            raise NoneArgumentError

        image_bytes = image_file.read()
        image = cv2.imdecode(np.frombuffer(image_bytes, dtype=np.uint8), cv2.IMREAD_COLOR)

        preds = model.prediction(image=image, debug=self.debug)

        return preds


  • 앞서 작성한 server에서 사용할 resource에 대한 간단한 예제이다.
  • 앞서 말한 것과 같이 Resource class를 상속하였다.
  • 그리고 HTTP method에서 image file을 argument로 받아야 하는데, 이 부분도 RequestParser를 이용하면 간단하게 처리가 가능하다. (놀랍다..)
  • dest는 해당 argument를 parsing했을 때 dictionary에 저장될 key name이며, type은 file의 type을 의미한다.
  • location은 HTTP packet에서 data의 위치를 의미하며, image는 file로 넘겨줄 것이므로 files가 된다. file이 아닐 경우 form, data, header 등등 원하는 위치로 정하면 된다.
  • HTTP method인 GET, PUT, DELETE, POST에 맞게 class 함수를 정의해주면 된다. 함수명은 위와 같이 소문자로 정하면 별도의 decorator가 없어도 알아서 routing이 된다.
  • 위의 예제에서는 사용하지 않는 method의 함수도 다 정의를 했지만, 꼭 재정의할 필요는 없다. 안하면 알아서 exception 처리해준다.
  • 우리는 image를 POST method로 받아서 결과를 return할 것이므로, post 함수를 위와 같이 간단하게 정의할 수 있다.
  • argument를 parsing해서 image의 binary값을 얻고, 이를 decoding 시켜서 image형태로 만들어 준다.
  • 그리고 image를 model에 넘겨줘서 prediction하고 결과를 return한다.

 

다음 번에는 위에서 나온 model을 torchscript와 file encryption을 이용해서 어떻게 저장하고 불러 올지에 대해 쓸 예정이다.