diff --git a/intermediate_source/flask_rest_api_tutorial.py b/intermediate_source/flask_rest_api_tutorial.py index e904e55d3..f0d2341b9 100644 --- a/intermediate_source/flask_rest_api_tutorial.py +++ b/intermediate_source/flask_rest_api_tutorial.py @@ -1,36 +1,37 @@ # -*- coding: utf-8 -*- """ -Deploying PyTorch in Python via a REST API with Flask -======================================================== +Flask를 이용하여 Python에서 PyTorch를 REST API로 배포하기 +=========================================================== **Author**: `Avinash Sajjanshetty `_ + **번역**: `박정환 `_ -In this tutorial, we will deploy a PyTorch model using Flask and expose a -REST API for model inference. In particular, we will deploy a pretrained -DenseNet 121 model which detects the image. +이 튜토리얼에서는 Flask를 이용하여 PyTorch 모델을 배포하고 모델 추론(inference)을 +할 수 있는 REST API를 만들어보겠습니다. 미리 훈련된 DenseNet 121 모델을 배포하여 +이미지를 인식해보겠습니다. -.. tip:: All the code used here is released under MIT license and is available on `Github `_. +.. tip:: 여기서 사용한 모든 코드는 MIT 라이선스로 배포되며, + `GitHub `_ 에서 확인하실 수 있습니다. -This represents the first in a series of tutorials on deploying PyTorch models -in production. Using Flask in this way is by far the easiest way to start -serving your PyTorch models, but it will not work for a use case -with high performance requirements. For that: +이것은 PyTorch 모델을 상용(production)으로 배포하는 튜토리얼 시리즈의 첫번째 +편입니다. Flask를 여기에 소개된 것처럼 사용하는 것이 PyTorch 모델을 제공하는 +가장 쉬운 방법이지만, 고성능을 요구하는 때에는 적합하지 않습니다. 그에 대해서는: - - If you're already familiar with TorchScript, you can jump straight into our - `Loading a TorchScript Model in C++ `_ tutorial. + - TorchScript에 이미 익숙하다면, 바로 `Loading a TorchScript Model in C++ `_ + 를 읽어보세요. - - If you first need a refresher on TorchScript, check out our - `Intro a TorchScript `_ tutorial. + - TorchScript가 무엇인지 알아보는 것이 필요하다면 `TorchScript 소개 `_ + 부터 보시길 추천합니다. """ ###################################################################### -# API Definition +# API 정의 # -------------- # -# We will first define our API endpoints, the request and response types. Our -# API endpoint will be at ``/predict`` which takes HTTP POST requests with a -# ``file`` parameter which contains the image. The response will be of JSON -# response containing the prediction: +# 먼저 API 엔드포인트(endpoint)의 요청(request)와 응답(response)을 정의하는 것부터 +# 시작해보겠습니다. 새로 만들 API 엔드포인트는 이미지가 포함된 ``file`` 매개변수를 +# HTTP POST로 ``/predict`` 에 요청합니다. 응답은 JSON 형태이며 다음과 같은 예측 결과를 +# 포함합니다: # # :: # @@ -39,10 +40,10 @@ # ###################################################################### -# Dependencies -# ------------ +# 의존성(Dependencies) +# ------------------------- # -# Install the required dependenices by running the following command: +# 아래 명령어를 실행하여 필요한 패키지들을 설치합니다: # # :: # @@ -50,10 +51,10 @@ ###################################################################### -# Simple Web Server +# 간단한 웹 서버 # ----------------- # -# Following is a simple webserver, taken from Flask's documentaion +# Flask의 문서를 참고하여 아래와 같은 코드로 간단한 웹 서버를 구성합니다. from flask import Flask @@ -65,23 +66,22 @@ def hello(): return 'Hello World!' ############################################################################### -# Save the above snippet in a file called ``app.py`` and you can now run a -# Flask development server by typing: +# 위 코드를 ``app.py`` 라는 파일명으로 저장한 후, 아래와 같이 Flask 개발 서버를 +# 실행합니다: # # :: # # $ FLASK_ENV=development FLASK_APP=app.py flask run ############################################################################### -# When you visit ``http://localhost:5000/`` in your web browser, you will be -# greeted with ``Hello World!`` text +# 웹 브라우저로 ``http://localhost:5000/`` 에 접속하면, ``Hello World!`` 가 +# 표시됩니다. ############################################################################### -# We will make slight changes to the above snippet, so that it suits our API -# definition. First, we will rename the method to ``predict``. We will update -# the endpoint path to ``/predict``. Since the image files will be sent via -# HTTP POST requests, we will update it so that it also accepts only POST -# requests: +# API 정의에 맞게 위 코드를 조금 수정해보겠습니다. 먼저, 메소드의 이름을 +# ``predict`` 로 변경합니다. 엔드포인트의 경로(path)도 ``/predict`` 로 변경합니다. +# 이미지 파일은 HTTP POST 요청을 통해서 보내지기 때문에, POST 요청에만 허용하도록 +# 합니다: @app.route('/predict', methods=['POST']) @@ -89,9 +89,8 @@ def predict(): return 'Hello World!' ############################################################################### -# We will also change the response type, so that it returns a JSON response -# containing ImageNet class id and name. The updated ``app.py`` file will -# be now: +# 또한, ImageNet 분류 ID와 이름을 포함하는 JSON을 회신하도록 응답 형식을 변경하겠습니다. +# 이제 ``app.py`` 는 아래와 같이 변경되었습니다: from flask import Flask, jsonify app = Flask(__name__) @@ -102,25 +101,23 @@ def predict(): ###################################################################### -# Inference +# 추론(Inference) # ----------------- # -# In the next sections we will focus on writing the inference code. This will -# involve two parts, one where we prepare the image so that it can be fed -# to DenseNet and next, we will write the code to get the actual prediction -# from the model. +# 다음 섹션에서는 추론 코드 작성에 집중하겠습니다. 먼저 이미지를 DenseNet에 공급(feed)할 수 +# 있도록 준비하는 방법을 살펴본 뒤, 모델로부터 예측 결과를 얻는 방법을 살펴보겠습니다. # -# Preparing the image +# 이미지 준비하기 # ~~~~~~~~~~~~~~~~~~~ # -# DenseNet model requires the image to be of 3 channel RGB image of size -# 224 x 224. We will also normalise the image tensor with the required mean -# and standard deviation values. You can read more about it -# `here `_. +# DenseNet 모델은 224 x 224의 3채널 RGB 이미지를 필요로 합니다. +# 또한 이미지 텐서를 평규 및 표준편차 값으로 정규화합니다. 자세한 내용은 +# `여기 `_ 를 참고하세요. # -# We will use ``transforms`` from ``torchvision`` library and build a -# transform pipeline, which transforms our images as required. You -# can read more about transforms `here `_. +# ``torchvision`` 라이브러리의 ``transforms`` 을 이용하여 변환 파이프라인 +# (transform pipeline)을 구축합니다. Transforms와 관련한 더 자세한 내용은 +# `여기 `_ 에서 +# 읽어볼 수 있습니다. import io @@ -139,10 +136,10 @@ def transform_image(image_bytes): ###################################################################### -# The above method takes image data in bytes, applies the series of transforms -# and returns a tensor. To test the above method, read an image file in -# bytes mode (first replacing `../_static/img/sample_file.jpeg` with the actual -# path to the file on your computer) and see if you get a tensor back: +# 위 메소드는 이미지를 byte 단위로 읽은 후, 일련의 변환을 적용하고 Tensor를 +# 반환합니다. 위 메소드를 테스트하기 위해서는 이미지를 byte 모드로 읽은 후 +# Tensor를 반환하는지 확인하면 됩니다. (먼저 `../_static/img/sample_file.jpeg` 을 +# 컴퓨터 상의 실제 경로로 바꿔야 합니다.) with open("../_static/img/sample_file.jpeg", 'rb') as f: image_bytes = f.read() @@ -150,20 +147,20 @@ def transform_image(image_bytes): print(tensor) ###################################################################### -# Prediction +# 예측(Prediction) # ~~~~~~~~~~~~~~~~~~~ # -# Now will use a pretrained DenseNet 121 model to predict the image class. We -# will use one from ``torchvision`` library, load the model and get an -# inference. While we'll be using a pretrained model in this example, you can -# use this same approach for your own models. See more about loading your -# models in this :doc:`tutorial `. +# 미리 학습된 DenseNet 121 모델을 사용하여 이미지 분류(class)를 예측합니다. +# ``torchvision`` 라이브러리의 모델을 사용하여 모델을 읽어오고 추론을 합니다. +# 이 예제에서는 미리 학습된 모델을 사용하지만, 직접 만든 모델에 대해서도 +# 이와 동일한 방법을 사용할 수 있습니다. 모델을 읽어오는 것은 이 +# :doc:`튜토리얼 ` 을 참고하세요. from torchvision import models -# Make sure to pass `pretrained` as `True` to use the pretrained weights: +# 이미 학습된 가중치를 사용하기 위해 `pretrained` 에 `True` 값을 전달합니다: model = models.densenet121(pretrained=True) -# Since we are using our model only for inference, switch to `eval` mode: +# 모델을 추론에만 사용할 것이므로, `eval` 모드로 변경합니다: model.eval() @@ -175,15 +172,13 @@ def get_prediction(image_bytes): ###################################################################### -# The tensor ``y_hat`` will contain the index of the predicted class id. -# However, we need a human readable class name. For that we need a class id -# to name mapping. Download -# `this file `_ -# as ``imagenet_class_index.json`` and remember where you saved it (or, if you -# are following the exact steps in this tutorial, save it in -# `tutorials/_static`). This file contains the mapping of ImageNet class id to -# ImageNet class name. We will load this JSON file and get the class name of -# the predicted index. +# ``y_hat`` Tensor는 예측된 분류 ID의 인덱스를 포함합니다. 하지만 사람이 읽을 수 +# 있는 분류명이 있어야 하기 떄문에, 이를 위해 이름과 분류 ID를 매핑하는 것이 필요합니다. +# `이 파일 `_ +# 을 다운로드 받아 ``imagenet_class_index.json`` 이라는 이름으로 저장 후, 저장한 곳의 +# 위치를 기억해두세요. (또는, 이 튜토리얼과 똑같이 진행하는 경우에는 `tutorials/_static` +# 에 저장하세요.) 이 파일은 ImageNet 분류 ID와 ImageNet 분류명의 쌍을 포함하고 있습니다. +# 이제 이 JSON 파일을 불러와 예측 결과의 인덱스에 해당하는 분류명을 가져오겠습니다. import json @@ -198,10 +193,10 @@ def get_prediction(image_bytes): ###################################################################### -# Before using ``imagenet_class_index`` dictionary, first we will convert -# tensor value to a string value, since the keys in the -# ``imagenet_class_index`` dictionary are strings. -# We will test our above method: +# ``imagenet_class_index`` 사전(dictionary)을 사용하기 전에, +# ``imagenet_class_index`` 의 키 값이 문자열이므로 Tensor의 값도 +# 문자열로 변환해야 합니다. +# 위 메소드를 테스트해보겠습니다: with open("../_static/img/sample_file.jpeg", 'rb') as f: @@ -209,34 +204,32 @@ def get_prediction(image_bytes): print(get_prediction(image_bytes=image_bytes)) ###################################################################### -# You should get a response like this: +# 다음과 같은 응답을 받게 될 것입니다: ['n02124075', 'Egyptian_cat'] ###################################################################### -# The first item in array is ImageNet class id and second item is the human -# readable name. +# 배열의 첫번째 항목은 ImageNet 분류 ID이고, 두번째 항목은 사람이 읽을 수 있는 +# 이름입니다. # # .. Note :: -# Did you notice that ``model`` variable is not part of ``get_prediction`` -# method? Or why is model a global variable? Loading a model can be an -# expensive operation in terms of memory and compute. If we loaded the model in the -# ``get_prediction`` method, then it would get unnecessarily loaded every -# time the method is called. Since, we are building a web server, there -# could be thousands of requests per second, we should not waste time -# redundantly loading the model for every inference. So, we keep the model -# loaded in memory just once. In -# production systems, it's necessary to be efficient about your use of -# compute to be able to serve requests at scale, so you should generally -# load your model before serving requests. +# ``model`` 변수가 ``get_prediction`` 메소드 내부에 있지 않은 것을 눈치채셨나요? +# 왜 모델이 전역 변수일까요? 모델을 읽어오는 것은 메모리와 계산 측면에서 비싼 +# 연산일 수 있습니다. 만약 ``get_prediction`` 메소드 내부에서 모델을 불러온다면, +# 메소드가 호출될 때마다 불필요하게 불러오게 됩니다. 초당 수천번의 요청을 받을 +# 지도 모르는 웹 서버를 구축하고 있기 때문에, 매번 추론을 할 때마다 모델을 +# 중복으로 불러오는데 시간을 낭비해서는 안됩니다. 따라서, 모델은 메모리에 +# 딱 한번만 불러옵니다. 상용 시스템(production system)에서는 대량의 요청을 +# 효율적으로 처리해야 하므로, 일반적으로 요청(request)을 처리하기 전에 모델을 +# 불러와둡니다. ###################################################################### -# Integrating the model in our API Server +# 모델을 API 서버에 통합하기 # --------------------------------------- # -# In this final part we will add our model to our Flask API server. Since -# our API server is supposed to take an image file, we will update our ``predict`` -# method to read files from the requests: +# 마지막으로 위에서 만든 Flask API 서버에 모델을 추가하겠습니다. +# API 서버는 이미지 파일을 받는 것을 가정하고 있으므로, 요청으로부터 파일을 읽도록 +# ``predict`` 메소드를 수정해야 합니다: # # .. code-block:: python # @@ -253,8 +246,8 @@ def get_prediction(image_bytes): # return jsonify({'class_id': class_id, 'class_name': class_name}) ###################################################################### -# The ``app.py`` file is now complete. Following is the full version; replace -# the paths with the paths where you saved your files and it should run: +# ``app.py`` 파일은 이제 완성되었습니다. 아래가 정식 버전(full version)입니다; +# 아래 경로를 json 파일을 저장해둔 경로로 바꾸면 동작합니다: # # .. code-block:: python # @@ -305,16 +298,15 @@ def get_prediction(image_bytes): # app.run() ###################################################################### -# Let's test our web server! Run: +# 이제 웹 서버를 테스트해보겠습니다! 다음과 같이 실행해보세요: # # :: # # $ FLASK_ENV=development FLASK_APP=app.py flask run ####################################################################### -# We can use the -# `requests `_ -# library to send a POST request to our app: +# `requests `_ 라이브러리를 사용하여 +# POST 요청을 만들어보겠습니다: # # .. code-block:: python # @@ -324,7 +316,7 @@ def get_prediction(image_bytes): # files={"file": open('/cat.jpg','rb')}) ####################################################################### -# Printing `resp.json()` will now show the following: +# `resp.json()` 을 호출하면 다음과 같은 결과를 출력합니다: # # :: # @@ -332,39 +324,40 @@ def get_prediction(image_bytes): # ###################################################################### -# Next steps +# 다음 단계 # -------------- # -# The server we wrote is quite trivial and and may not do everything -# you need for your production application. So, here are some things you -# can do to make it better: +# 지금까지 만든 서버는 매우 간단하여 상용 프로그램(production application)으로써 +# 갖춰야할 것들을 모두 갖추지 못했습니다. 따라서, 다음과 같이 개선해볼 수 있습니다: # -# - The endpoint ``/predict`` assumes that always there will be a image file -# in the request. This may not hold true for all requests. Our user may -# send image with a different parameter or send no images at all. +# - ``/predict`` 엔드포인트는 요청 시에 반드시 이미지 파일이 전달되는 것을 가정하고 +# 있습니다. 하지만 모든 요청이 그렇지는 않습니다. 사용자는 다른 매개변수로 이미지를 +# 보내거나, 이미지를 아예 보내지 않을수도 있습니다. # -# - The user may send non-image type files too. Since we are not handling -# errors, this will break our server. Adding an explicit error handing -# path that will throw an exception would allow us to better handle -# the bad inputs +# - 사용자가 이미지가 아닌 유형의 파일을 보낼수도 있습니다. 여기서는 에러를 처리하지 +# 않고 있으므로, 이러한 경우에 서버는 다운(break)됩니다. 예외 전달(exception throe)을 +# 위한 명시적인 에러 핸들링 경로를 추가하면 잘못된 입력을 더 잘 처리할 수 있습니다. # -# - Even though the model can recognize a large number of classes of images, -# it may not be able to recognize all images. Enhance the implementation -# to handle cases when the model does not recognize anything in the image. +# - 모델은 많은 종류의 이미지 분류를 인식할 수 있지만, 모든 이미지를 인식할 수 있는 +# 것은 아닙니다. 모델이 이미지에서 아무것도 인식하지 못하는 경우를 처리하도록 +# 개선합니다. # -# - We run the Flask server in the development mode, which is not suitable for -# deploying in production. You can check out `this tutorial `_ -# for deploying a Flask server in production. +# - 위에서는 Flask 서버를 개발 모드에로 실행하였지만, 이는 상용으로 배포하기에는 +# 적당하지 않습니다. Flask 서버를 상용으로 배포하는 것은 +# `이 튜토리얼 `_ +# 을 참고해보세요. # -# - You can also add a UI by creating a page with a form which takes the image and -# displays the prediction. Check out the `demo `_ -# of a similar project and its `source code `_. +# - 또한 이미지를 가져오는 양식(form)과 예측 결과를 표시하는 페이지를 만들어 +# UI를 추가할 수도 있습니다. 비슷한 프로젝트의 `데모 `_ +# 와 이 데모의 `소스 코드 `_ +# 를 참고해보세요. # -# - In this tutorial, we only showed how to build a service that could return predictions for -# a single image at a time. We could modify our service to be able to return predictions for -# multiple images at once. In addition, the `service-streamer `_ -# library automatically queues requests to your service and samples them into mini-batches -# that can be fed into your model. You can check out `this tutorial `_. +# - 이 튜토리얼에서는 한 번에 하나의 이미지에 대한 예측 결과를 반환하는 서비스를 +# 만드는 방법만 살펴보았는데요, 한 번에 여러 이미지에 대한 예측 결과를 반환하도록 +# 수정해볼 수 있습니다. 추가로, `service-streamer `_ +# 라이브러리는 자동으로 요청을 큐에 넣은 뒤 모델에 공급(feed)할 수 있는 미니-배치로 +# 샘플링합니다. `이 튜토리얼 `_ +# 을 참고해보세요. # -# - Finally, we encourage you to check out our other tutorials on deploying PyTorch models -# linked-to at the top of the page. +# - 마지막으로 이 문서 상단에 링크된, PyTorch 모델을 배포하는 다른 튜토리얼들을 +# 읽어보는 것을 권장합니다.