intermediate/flask_rest_api_tutorial Show Run in Google Colab Colab Download Notebook Notebook View on GitHub GitHub Note Click here to download the full example code Author: Avinash Sajjanshetty 번역: 박정환 이 튜토리얼에서는 Flask를 사용하여 PyTorch 모델을 배포하고 모델 추론(inference)을 할 수 있는 REST API를 만들어보겠습니다. 미리 훈련된 DenseNet 121 모델을 배포하여 이미지를 인식해보겠습니다. Tip 여기서 사용한 모든 코드는 MIT 라이선스로 배포되며, GitHub 에서 확인하실 수 있습니다. 이것은 PyTorch 모델을 상용(production)으로 배포하는 튜토리얼 시리즈의 첫번째 편입니다. Flask를 여기에 소개된 것처럼 사용하는 것이 PyTorch 모델을 제공하는 가장 쉬운 방법이지만, 고성능을 요구하는 때에는 적합하지 않습니다. 그에 대해서는:
API 정의¶먼저 API 엔드포인트(endpoint)의 요청(request)와 응답(response)을 정의하는 것부터 시작해보겠습니다. 새로 만들 API 엔드포인트는 이미지가 포함된 {"class_id": "n02124075", "class_name": "Egyptian_cat"} 의존성(Dependencies)¶아래 명령어를 실행하여 필요한 패키지들을 설치합니다: $ pip install Flask==2.0.1 torchvision==0.10.0 간단한 웹 서버¶Flask의 문서를 참고하여 아래와 같은 코드로 간단한 웹 서버를 구성합니다. from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'Hello World!' 위 코드를 $ FLASK_ENV=development FLASK_APP=app.py flask run 웹 브라우저로 API 정의에 맞게 위 코드를 조금 수정해보겠습니다. 먼저, 메소드의 이름을 @app.route('/predict', methods=['POST']) def predict(): return 'Hello World!' 또한, ImageNet 분류 ID와 이름을 포함하는 JSON을 회신하도록 응답 형식을 변경하겠습니다. 이제 from flask import Flask, jsonify app = Flask(__name__) @app.route('/predict', methods=['POST']) def predict(): return jsonify({'class_id': 'IMAGE_NET_XXX', 'class_name': 'Cat'}) 추론(Inference)¶다음 섹션에서는 추론 코드 작성에 집중하겠습니다. 먼저 이미지를 DenseNet에 공급(feed)할 수 있도록 준비하는 방법을 살펴본 뒤, 모델로부터 예측 결과를 얻는 방법을 살펴보겠습니다. 이미지 준비하기¶DenseNet 모델은 224 x 224의 3채널 RGB 이미지를 필요로 합니다. 또한 이미지 텐서를 평균 및 표준편차 값으로 정규화합니다. 자세한 내용은 여기 를 참고하세요.
import io import torchvision.transforms as transforms from PIL import Image def transform_image(image_bytes): my_transforms = transforms.Compose([transforms.Resize(255), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize( [0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]) image = Image.open(io.BytesIO(image_bytes)) return my_transforms(image).unsqueeze(0) 위 메소드는 이미지를 byte 단위로 읽은 후, 일련의 변환을 적용하고 Tensor를 반환합니다. 위 메소드를 테스트하기 위해서는 이미지를 byte 모드로 읽은 후 Tensor를 반환하는지 확인하면 됩니다. (먼저 ../_static/img/sample_file.jpeg 을 컴퓨터 상의 실제 경로로 바꿔야 합니다.) with open("../_static/img/sample_file.jpeg", 'rb') as f: image_bytes = f.read() tensor = transform_image(image_bytes=image_bytes) print(tensor) Out: tensor([[[[ 0.4508, 0.4166, 0.3994, ..., -1.3473, -1.3473, -1.3473], [ 0.5364, 0.4851, 0.4508, ..., -1.2959, -1.3130, -1.3302], [ 0.7248, 0.6392, 0.6049, ..., -1.2959, -1.3302, -1.3644], ..., [ 1.3584, 1.3755, 1.4098, ..., 1.1700, 1.3584, 1.6667], [ 1.8893, 1.7694, 1.4440, ..., 1.2899, 1.4783, 1.5468], [ 1.6324, 1.8379, 1.8379, ..., 1.4783, 1.7352, 1.4612]], [[ 0.5728, 0.5378, 0.5203, ..., -1.3529, -1.3529, -1.3529], [ 0.6604, 0.6078, 0.5728, ..., -1.3004, -1.3354, -1.3529], [ 0.8529, 0.7654, 0.7304, ..., -1.3004, -1.3354, -1.3704], ..., [ 1.4657, 1.4657, 1.4832, ..., 1.3256, 1.5357, 1.8508], [ 2.0084, 1.8683, 1.5182, ..., 1.4657, 1.6583, 1.7283], [ 1.7458, 1.9384, 1.9209, ..., 1.6583, 1.9209, 1.6408]], [[ 0.7228, 0.6879, 0.6531, ..., -1.6476, -1.6302, -1.6302], [ 0.8099, 0.7576, 0.7228, ..., -1.6302, -1.6302, -1.6476], [ 1.0017, 0.9145, 0.8797, ..., -1.6650, -1.6650, -1.6999], ..., [ 1.6291, 1.6291, 1.6465, ..., 1.6291, 1.8208, 2.1346], [ 2.1694, 2.0300, 1.6814, ..., 1.7685, 1.9428, 2.0125], [ 1.9254, 2.0997, 2.0823, ..., 1.9428, 2.2043, 1.9254]]]]) 예측(Prediction)¶미리 학습된 DenseNet 121 모델을 사용하여 이미지 분류(class)를 예측합니다. from torchvision import models # 이미 학습된 가중치를 사용하기 위해 `pretrained` 에 `True` 값을 전달합니다: model = models.densenet121(pretrained=True) # 모델을 추론에만 사용할 것이므로, `eval` 모드로 변경합니다: model.eval() def get_prediction(image_bytes): tensor = transform_image(image_bytes=image_bytes) outputs = model.forward(tensor) _, y_hat = outputs.max(1) return y_hat
import json imagenet_class_index = json.load(open('../_static/imagenet_class_index.json')) def get_prediction(image_bytes): tensor = transform_image(image_bytes=image_bytes) outputs = model.forward(tensor) _, y_hat = outputs.max(1) predicted_idx = str(y_hat.item()) return imagenet_class_index[predicted_idx]
with open("../_static/img/sample_file.jpeg", 'rb') as f: image_bytes = f.read() print(get_prediction(image_bytes=image_bytes)) Out: ['n02124075', 'Egyptian_cat'] 다음과 같은 응답을 받게 될 것입니다: ['n02124075', 'Egyptian_cat'] 배열의 첫번째 항목은 ImageNet 분류 ID이고, 두번째 항목은 사람이 읽을 수 있는 이름입니다. Note
모델을 API 서버에 통합하기¶마지막으로 위에서 만든 Flask API 서버에 모델을
추가하겠습니다. API 서버는 이미지 파일을 받는 것을 가정하고 있으므로, 요청으로부터 파일을 읽도록 from flask import request @app.route('/predict', methods=['POST']) def predict(): if request.method == 'POST': # we will get the file from the request file = request.files['file'] # convert that to bytes img_bytes = file.read() class_id, class_name = get_prediction(image_bytes=img_bytes) return jsonify({'class_id': class_id, 'class_name': class_name})
import io import json from torchvision import models import torchvision.transforms as transforms from PIL import Image from flask import Flask, jsonify, request app = Flask(__name__) imagenet_class_index = json.load(open('<PATH/TO/.json/FILE>/imagenet_class_index.json')) model = models.densenet121(pretrained=True) model.eval() def transform_image(image_bytes): my_transforms = transforms.Compose([transforms.Resize(255), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize( [0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]) image = Image.open(io.BytesIO(image_bytes)) return my_transforms(image).unsqueeze(0) def get_prediction(image_bytes): tensor = transform_image(image_bytes=image_bytes) outputs = model.forward(tensor) _, y_hat = outputs.max(1) predicted_idx = str(y_hat.item()) return imagenet_class_index[predicted_idx] @app.route('/predict', methods=['POST']) def predict(): if request.method == 'POST': file = request.files['file'] img_bytes = file.read() class_id, class_name = get_prediction(image_bytes=img_bytes) return jsonify({'class_id': class_id, 'class_name': class_name}) if __name__ == '__main__': app.run() 이제 웹 서버를 테스트해보겠습니다! 다음과 같이 실행해보세요: $ FLASK_ENV=development FLASK_APP=app.py flask run requests 라이브러리를 사용하여 POST 요청을 만들어보겠습니다: import requests resp = requests.post("http://localhost:5000/predict", files={"file": open('<PATH/TO/.jpg/FILE>/cat.jpg','rb')}) resp.json() 을 호출하면 다음과 같은 결과를 출력합니다: {"class_id": "n02124075", "class_name": "Egyptian_cat"} 다음 단계¶지금까지 만든 서버는 매우 간단하여 상용 프로그램(production application)으로써 갖춰야할 것들을 모두 갖추지 못했습니다. 따라서, 다음과 같이 개선해볼 수 있습니다:
Total running time of the script: ( 0 minutes 1.130 seconds) Gallery generated by Sphinx-Gallery |