Cet article est [ici](https://aiotadiary.wp.xdomain.jp/2020/03/01/google-cloud-platform%E3%82%92%E4%BD%BF%E3%81%A3% E3% 81% A6% E8% B6% 85% E8% A7% A3% E5% 83% 8F% E5% BA% A6% E5% 8C% 96api% E3% 82% 92% E5% 85% AC% E9% 96% 8B% E3% 81% 97% E3% 81% A6% E3% 81% BF% E3% 82% 8B /) Ceci est une réécriture de l'article de blog pour qiita. Jetez également un œil à celui-ci. Cette fois, je souhaite publier l'API à l'aide de Google Cloud Platform, communément appelé GCP. J'avais l'habitude d'utiliser GCP il y a quelque temps, donc cette fois, j'aimerais garder cela à l'esprit et avoir l'expérience du déploiement du service moi-même. À propos, l'API est un modèle de super-résolution utilisant l'ESPCN précédemment implémenté. Ce que je voudrais aborder cette fois, c'est une fonction appelée Cloud Run dans GCP.
Pour vous inscrire à GCP, vous pouvez généralement vous référer au guide Google ou à d'autres articles. Il n'y a aucun problème si vous suivez le guide de démarrage. L'article ci-dessous vous sera utile. GCP (GCE) pour commencer à partir de maintenant, utilisez en toute sécurité le cadre libre En gros, inscrivez-vous dans l'ordre suivant.
L'inscription est maintenant terminée. Avec le niveau GCP gratuit, vous pouvez créer une instance GCE de f1-micro gratuitement, veuillez donc l'utiliser. En passant, dans l'essai gratuit, vous pouvez utiliser le service d'une valeur de 300 $ pendant 12 mois gratuitement, vous pouvez donc essayer diverses choses.
Cloud Run est un service qui vous permet de déployer des conteneurs Docker et déploie essentiellement le service à l'aide de l'image Docker téléchargée dans Container Registry. Pour le moment, il semble qu'il existe un moyen de créer et de déployer le conteneur déclenché en tirant sur Github, mais cette fois, cela semble un peu difficile, alors cette fois, j'aimerais prendre la forme de télécharger l'image construite localement.
Puisque j'utilise mac, l'explication suivante n'est utile pour personne d'autre que mac. Tout d'abord, téléchargez l'archive suivante.
google-cloud-sdk-245.0.0-darwin-x86_64.tar.gz
Exécutez ensuite la commande suivante.
$ ./google-cloud-sdk/install.sh
Ensuite, initialisez le SDK.
$ gcloud init
Voulez-vous vous connecter après cela? Un message apparaîtra disant cela, alors entrez Y. Après cela, sélectionnez le projet auquel vous connecter, mais soyez assuré qu'il sera sélectionné arbitrairement lorsqu'il n'y a qu'un seul projet. Après cela, vous pourrez utiliser la commande si vous la saisissez correctement. ↓ est la méthode de configuration officielle.
Démarrage rapide pour macOS Démarrage rapide pour Windows Démarrage rapide pour Linux
Ici, j'utiliserai le modèle ESPCN mis en œuvre la dernière fois et utiliserai le code qui double la résolution et le produit en tant qu'API. J'utilise Flask et Gunicorn.
En tant que flux de toute l'opération
C'est aussi simple que ça. Tout d'abord, créez un référentiel Git pour Espcn-API (Cliquez ici pour le référentiel). La structure du répertoire ressemble à ce qui suit.
ESPCN-API
├model
│ └pre_trained_mode.pth
├api.py
├requirements.txt
├Dockerfile
└networks.py
test
├test.py
└test.png
Commençons par le code de test. Il y a deux choses à vérifier dans le test.
・ Une réponse est renvoyée (status_code est 200) ・ La taille de l'image a été doublée Nous mettrons en œuvre l'opération pour les confirmer.
import sys
import requests
import json
from io import BytesIO
from PIL import Image
import base64
def image_file_to_base64(file_path):
with open(file_path, "rb") as image_file:
data = base64.b64encode(image_file.read())
return 'data:image/png;base64,' + data.decode('utf-8')
def base64_to_img(img_formdata):
img_base64 = img_formdata.split(',')[1]
input_bin = base64.b64decode(img_base64)
with BytesIO(input_bin) as b:
img = Image.open(b).copy().convert("RGB")
return img
if __name__ == "__main__":
source_image_path = "test.png "
source_image = Image.open(source_image_path)
source_width, source_height = source_image.size
print("source_width :", source_width)
print("source_height :", source_height)
host_url = "http://0.0.0.0:8000"
data = {"srcImage":image_file_to_base64(source_image_path)}
json_data = json.dumps(data)
response = requests.post(host_url, json_data, headers={'Content-Type': 'application/json'})
assert response.status_code == 200, "validation error status code should be 200"
res_json = response.json()
res_image = base64_to_img(res_json["sresoImage"])
sreso_width, sreso_height = res_image.size
print("sreso_width :", sreso_width)
print("sreso_height :", sreso_height)
assert sreso_width == source_width * 2 and sreso_height == source_height * 2 , \
"validation error image size should be 2 times of input image"
res_image.show()
print("OK")
Ensuite, nous allons implémenter le principal api.py. Tout d'abord, nous avons créé le squelette de l'ensemble de l'implémentation. Le contenu de la fonction n'est pas du tout implémenté.
from flask import Flask, request, jsonify
from networks import Espcn
import os
from io import BytesIO
import base64
from torchvision import transforms
from torch import load
import torch
import json
from PIL import Image
device = "cpu"
net = Espcn(upscale=2)
net.load_state_dict(torch.load(opt.model_path, map_location="cpu"))
net.to(device)
net.eval()
def b64_to_PILImage(b64_string):
"""
process convert base64 string to PIL Image
input: b64_string: base64 string : data:image/png;base64,{base64 string}
output: pil_img: PIL Image
"""
pass
def PILImage_to_b64(pil_img):
"""
process convert PIL Image to base64
input: pil_img: PIL Image
output: b64_string: base64 string : data:image/png;base64,{base64 string}
"""
pass
def expand(src_image, model=net, device=device):
pass
@app.route("/", methods=["POST"])
def superResolution():
pass
if __name__ == "__main__":
app.run(host="0.0.0.0", port=os.environ.get("PORT", 8000))
Je pense à une structure dans laquelle la fonction d'expansion effectue la super-résolution principale et l'exécute dans la super-résolution qui s'exécute pour la requête. Aussi, en définissant le modèle utilisé pour la super-résolution en dehors de la fonction, je pense qu'il est possible d'éviter la peine de charger le modèle lorsque le worker traite plusieurs requêtes.
Donc, tout d'abord, implémentez deux fonctions ~ to ~ qui sont entrées et sorties.
def b64_to_PILImage(b64_string):
"""
process convert base64 string to PIL Image
input: b64_string: base64 string : data:image/png;base64,{base64 string}
output: pil_img: PIL Image
"""
b64_split = b64_string.split(",")[1]
b64_bin = base64.b64decode(b64_split)
with BytesIO(b64_bin) as b:
pil_img = Image.open(b).copy().convert('RGB')
return pil_img
def PILImage_to_b64(pil_img):
"""
process convert PIL Image to base64
input: pil_img: PIL Image
output: b64_string: base64 string : data:image/png;base64,{base64 string}
"""
with BytesIO() as b:
pil_img.save(b, format='png')
b.seek(0)
img_base64 = base64.b64encode(b.read()).decode('utf-8')
img_base64 = 'data:image/png;base64,' + img_base64
return img_base64
J'ai eu du mal à utiliser BytesIO parce que c'était étonnamment difficile. Viennent ensuite la fonction d'expansion et la fonction principale de super-résolution.
def tensor_to_pil(src_tensor):
src_tensor = src_tensor.mul(255)
src_tensor = src_tensor.add_(0.5)
src_tensor = src_tensor.clamp_(0, 255)
src_tensor = src_tensor.permute(1, 2, 0)
src_tensor = src_tensor.to("cpu", torch.uint8).numpy()
return Image.fromarray(src_tensor)
def expand(src_image, model=net, device=device):
src_tensor = transforms.ToTensor()(src_image).to(device)
if src_tensor.dim() == 3:
src_tensor = src_tensor.unsqueeze(0)
srezo_tensor = model(src_tensor).squeeze()
srezo_img = tensor_to_pil(srezo_tensor)
return srezo_img
@app.route("/", methods=["POST"])
def superResolution():
req_json = json.loads(request.data)
src_b64 = req_json["srcImage"]
# main process
src_img = b64_to_PILImage(src_b64)
srezo_img = expand(src_img)
srezo_b64 = PILImage_to_b64(srezo_img)
results = {"sresoImage":srezo_b64}
return jsonify(results)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=os.environ.get("PORT", 8000))
Au début, je voulais l'adapter à deux fonctions, mais la conversion de torch.tensor en PIL Image est devenue étrange lors de l'utilisation de transforms.ToPILImage, j'ai donc décidé de la définir séparément. Fait. Aussi, concernant l'argument au moment du dernier app.run, il semble qu'il soit nécessaire de définir pour lire la variable d'environnement PORT car un autre port est automatiquement attribué à chaque conteneur docker en raison des spécifications de cloud run.
Maintenant, créez ces opérations vérifiées localement en tant qu'image docker.
$ docker build -t gcr.io/[project id]/espcn-api:0 .
Ensuite, téléchargez l'image dans le registre de conteneurs à l'aide de la commande gcloud.
$ gcloud docker -- push gcr.io/[project id]/espcn-api:0
Vous pouvez en fait vérifier l'image dans Container Registry sur GCP. Puis déployez en utilisant l'image téléchargée.
$ gcloud beta run deploy SR-API --image=gcr.io/[project id]/espcn-api:0
La partie dans laquelle vous entrez SR-API après le déploiement est le nom du service, vous pouvez donc l'ajouter comme vous le souhaitez. De plus, il semble que le composant bêta sera installé lors de sa première exécution. Si vous n'entrez pas (--platform managed) à ce moment, sur la console
[1] Cloud Run (fully managed)
[2] Cloud Run for Anthos deployed on Google Cloud
[3] Cloud Run for Anthos deployed on VMware
[4] cancel
Please enter your numeric choice: _
Vous serez invité à entrer 1, alors entrez 1. Il sera difficile de fonctionner gratuitement s'il n'est pas entièrement géré. Ensuite, on vous demandera la région.
[1] asia-east1
[2] asia-northeast1
[3] europe-north1
[4] europe-west1
[5] europe-west4
[6] us-central1
[7] us-east1
[8] us-east4
[9] us-west1
[10] cancel
Please enter your numeric choice: _
Ici, sélectionnons "us- *". Dans Cloud Run, le réseau de liaison descendante en Amérique du Nord est gratuit jusqu'à 1 Go, donc si vous appelez cela depuis Cloud Functions (depuis Cloud Functions, le réseau de liaison descendante est gratuit jusqu'à 5 Go n'importe où), vous pouvez l'utiliser presque gratuitement.
Après cela, il vous sera demandé si vous souhaitez autoriser l'accès non authentifié, mais pour l'instant, le but est de le déplacer, alors définissons-le sur oui. Si vous entrez comme ci-dessus, le service sera déployé et le message suivant sera affiché.
Deploying container to Cloud Run service [espcn-api] in project [studied-brace-261908] region [us-central1]
✓ Deploying new service... Done.
✓ Creating Revision...
✓ Routing traffic...
✓ Setting IAM Policy...
Done.
Service [espcn-api] revision [espcn-api-00001-guj] has been deployed and is serving 100 percent of traffic at https://espcn-api-~~~-uc.a.run.app
Finalement, un message indiquant où et où l'URL a été déployée apparaîtra, donc lorsque j'ai essayé de faire une demande en utilisant test.py ici, j'ai pu confirmer que la réponse a été renvoyée correctement.
Une chose est restée bloquée à ce moment-là, mais lorsque j'ai fait la demande pour la première fois, aucune réponse n'a été retournée. Donc, si vous consultez le journal Cloud Run,
Memory limit of 244M exceeded with 272M used. Consider increasing the memory limit, see https://cloud.google.com/run/docs/configuring/memory-limits
J'ai reçu un message indiquant qu'il n'y avait pas assez de mémoire. Il semble que la mémoire à utiliser soit décidée dans le cloud, et la taille de la mémoire qui peut être spécifiée est de 128 Mo ~ 2 Go, et 256 Mo sont alloués par défaut. Puisque cette erreur s'est produite avec une image de 512 x 512, il semble qu'elle puisse être résolue en allouant 512 Mo etc., mais si vous allouez trop de mémoire, il semble que le quota libre sera bientôt dépassé. Au fait, si vous souhaitez modifier la mémoire allouée à l'application, utilisez la commande suivante.
$ gcloud beta run services update [service name] --memory 512M
Nous avons créé une API et l'avons publiée sur Cloud Run. Dans l'état actuel, cette API est accessible de n'importe où et n'est pas compatible avec la sécurité ou le portefeuille.Par conséquent, après avoir limité l'accès à Cloud Run depuis GCP, utilisez Cloud Functions pour ce faire. Je voudrais passer à une structure qui envoie des demandes à. Je voudrais aborder un jour le déploiement continu.
Recommended Posts