Le Blog Amazon Web Services

Utiliser AWS Lambda Container pour déployer un modèle ML PyTorch de classification d’images

PyTorch est une bibliothèque d’apprentissage automatique (Machine Learning, ML) open source largement utilisée pour développer des réseaux de neurones et des modèles ML. Ces modèles sont généralement entraînés sur plusieurs instances de GPU pour accélérer l’entraînement, ce qui entraîne des temps d’entraînement pouvant être coûteux et des tailles de modèle allant jusqu’à plusieurs gigaoctets. Une fois entraînés, ces modèles sont déployés et rendus disponibles via des points de terminaison (endpoint) en production pour effectuer des inférences en temps réel. Ces points de terminaison doivent être hautement scalables et résilients afin de traiter de traiter tout volumes de demandes. C’est sur ce point qu’AWS Lambda peut être un service adéquat pour l’inférence ML. En effet, Lambda offre des avantages tels que la mise à l’échelle automatique et la facturation à l’utilisation allant jusqu’à la milliseconde.

Cet article vous montre comment utiliser un modèle PyTorch avec Lambda pour des inférences en production allant jusqu’à 10 Go de mémoire. Cela nous permet d’utiliser des modèles ML dans les fonctions Lambda jusqu’à quelques gigaoctets. À titre d’exemple sur PyTorch, nous utilisons SqueezeNet, un réseau neuronal d’apprentissage profond (Deep learning) pour la computer vision et qui offre une précision de niveau AlexNet avec 50 fois moins de paramètres et une taille de modèle beaucoup plus petite (environ 10 Mo dans cet exemple). Le modèle que nous utilisons dans cet exemple est un modèle pré-entrainé par l’équipe de PyTorch Hub sur le dataset ImageNet, et déjà disponible sur leur portail. Si vous souhaitez utiliser vos propres labels et dataset, vous pouvez utiliser Amazon SageMaker pour le fine-tuning de ce modèle.

Aperçu de la solution

Pour cette solution, nous allons utiliser principalement trois services AWS à savoir :

  • AWS Lambda, un service permettant d’exécuter du code sans se soucier de provisionner des serveurs en amont. AWS Lambda est un service totalement serverless,
  • Amazon ECR, un registre de conteneurs entièrement géré qui facilite le stockage, la gestion, le partage et le déploiement de vos images et artefacts de conteneur,
  • Amazon API Gateway, un service entièrement opéré, qui permet aux développeurs de créer, publier, gérer, surveiller et sécuriser facilement des API à n’importe quelle échelle. Les API servent de « porte d’entrée » pour que les applications puissent accéder aux données, à la logique métier ou aux fonctionnalités de vos services back-end.

 

 

Dans cette solution, le développeur doit en amont « dockerizer » son modèle ML puis stocker l’image ainsi construite sur Amazon ECR. Ensuite, l’image pourra être récupérée par le service AWS Lambda afin de créer une fonction permettant de faire tourner le modèle ML via son image Docker. Pour finir, Amazon API permet d’utiliser une requête HTTP pour solliciter le modèle et produire des inférences qui seront retournées au format JSON.

Étape 1 : mise en place de l’environement AWS SAM CLI

Afin de créer et déployer notre image dédiée à l’inférence, nous utilisons AWS SAM CLI. L’interface de ligne de commande de SAM est une extension de l’AWS CLI qui ajoute des fonctionnalités pour créer et tester des applications serverless. Il utilise Docker pour exécuter vos fonctions dans un environnement Amazon Linux qui correspond à Lambda. Il peut également émuler l’environnement de construction et l’API de votre application en local.

Pour utiliser SAM CLI, vous avez besoin des outils suivants :

 

Étape 2 : développement de la fonction lambda permettant de solliciter le modèle ML

Nous allons dans un premier temps construire le code de la fonction Lambda. Ce dernier nous permet de charger le modèle ML que nous allons récupérer en étape 3, puis de le solliciter afin d’envoyer une image en entrée et produire une inférence en sortie. Ici, nous allons permettre à la fonction de récupérer l’image via un paramètre qui est envoyé via l’URL de l’API. La fonction va récupérer l’image via l’URL fournit en paramètre, solliciter une inférence au niveau du modèle ML, récupérer les résultats de l’inférence et les renvoyer à l’utilisateur final.

 

Voici le code de la fonction Lambda permettant d’effectuer le traitement mentionné ci-dessus :

import urllib
import json
import os
import io
import torch
from PIL import Image
from torchvision import models, transforms
import torch.nn.functional as F

model = models.squeezenet1_1()

model.load_state_dict(torch.load("model/squeezenet1_1-f364aa15.pth"))

model.eval()

normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])

snet_transform = transforms.Compose([transforms.Resize(224),transforms.CenterCrop(224),transforms.ToTensor(),normalize])

json_file = open("model/imagenet_class_index.json")

json_str = json_file.read()

labels = json.loads(json_str)

def transform_image(image):
     if image.mode != "RGB":
          image = image.convert("RGB")

def lambda_handler(event, context):
     data = {}
     url = event['queryStringParameters']['url']
     image = Image.open(urllib.request.urlopen(url))
     image = snet_transform(image)
     image = image.view(-1, 3, 224, 224)

     prediction = F.softmax(model(image)[0])
     topk_vals, topk_idxs = torch.topk(prediction, 3)
     data["predictions"] = []
     for i in range(len(topk_idxs)):
        r = {"label": labels[str(topk_idxs[i].item())][1], "probability": topk_vals[i].item()}
        data["predictions"].append(r)
 return json.dumps(data)

Dans le code ci-dessus, utilisons les paramètres suivants : (mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225]). Il s’agit ici d’utiliser la moyenne et la std d’Imagenet ce qui est une pratique courante. Elles sont calculées sur la base de millions d’images. Si vous souhaitez entraîner votre modèle à partir de zéro sur votre propre jeu de données, vous pouvez calculer les nouvelles moyenne et std. Sinon, il est recommandé d’utiliser le modèle prétrié d’Imagenet avec ses propres moyenne et std.

Étape 3 : créer l’image Docker

Afin de créer l’image Docker qui va contenir notre modèle ML, il faut définir dans un premier temps le fichier dockerfile qui embarque la configuration de notre image Docker. Ici, il est nécessaire de définir la version de Python à utiliser, les dépendances dont notre image a besoin pour fonctionner correctement (PyTorch, TorchVision, …) ainsi que le modèle lui-même.

Voici à quoi le Dockerfile ressemble une fois rédigé :

FROM public.ecr.aws/lambda/python:3.8
COPY app.py requirements.txt ./
RUN pip3 install --no-cache-dir -r requirements.txt
RUN mkdir model
RUN curl https://download.pytorch.org/models/squeezenet1_1-f364aa15.pth -o ./model/squeezenet1_1-f364aa15.pth
COPY imagenet_class_index.json ./model/imagenet_class_index.json
CMD ["app.lambda_handler"]

Une fois terminé, nous lançon la commande sam build afin de construire le projet SAM et construire l’image docker de ce projet.

Une fois la commande terminée, l’image docker est créée. Vérifions que l’image Docker est bien créée en utilisant la commande docker images comme ci-dessous :

 

Étape 4 : envoyer l’image Docker sur Amazon ECR

L’étape suivante consiste à envoyer l’image Docker sur un registre privé afin de pouvoir la déployer par la suite sur AWS Lambda. Dans un premier temps nous allons créer un registre privé sur Amazon ECR.

À partir de la console d’Amazon ECR, créer un référentiel privé et renseigner son nom.

 

 

Une fois le référentiel créé, utiliser le bouton « Afficher les commandes Push » permettant d’afficher la fenêtre suivante et qui rassemble les commandes à exécuter afin d’envoyer l’image Docker vers Amazon ECR :

 

 

Une fois les commandes exécutées dans le terminal, l’image Docker est désormais stockée sur Amazon ECR.

Étape 5 : construire l’environnement d’exécution et déployer l’image Docker contenant le modèle ML

AWS SAM CLI permet de faciliter la construction des ressources Serverless que nous utilisons. Nous allons donc utiliser AWS SAM pour déployer l’infrastructure permettant de faire fonctionner notre API à savoir :

  • Une fontion AWS Lambda avec l’image sur modèle ML récupérée depuis Amazon ECR,
  • Une API Gateway avec une méthode HTTP permettant d’invoquer la Lambda en lui fournissant un lien vers une image et de récupérer le résultat de l’inférence.

Afin de construire l’infrastructure, AWS SAM s’appuie sur AWS CloudFormation qui est le service d’infrastructure as code d’AWS. Celui-ci permet de décrire des ressources cloud AWS en utilisant des fichiers au format YAML ou JSON. Ces fichiers peuvent ensuite être déployés via AWS CloudFormation pour créer les ressources. Ici, nous allons nous baser sur le modèle CloudFormation suivant :

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
Fichier Cloudformation utilisé par SAM pour le déploiement

Globals:
   Function:
      Timeout: 3

Resources:
   pytorchEndpoint:
      Type: AWS::Serverless::Function
      Properties:
          PackageType: Image
          MemorySize: 5000
          Timeout: 300
          Events:
            ApiEndpoint:
               Type: HttpApi
               Properties:
               Path: /inference
               Method: get
               TimeoutInMillis: 29000
      Metadata:
           Dockerfile: Dockerfile
           DockerContext: ./inference
           DockerTag: image-inference
              
Outputs:
   InferenceApi:
       Description: "Point de terminaison API Gateway permettant de solliciter la fonction Lambda"
       Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/inference"
   pytorchEndpoint:
       Description: "ARN de la fonction Lambda"
       Value: !GetAtt pytorchEndpoint.Arn
   pytorchEndpointIamRole:
       Description: "Role créé pour la lambda et lui permettant d'accéder à ECR"
       Value: !GetAtt pytorchEndpointRole.Arn

Une fois le code et le modèle AWS CloudFormation édité au sein du projet AWS SAM, nous exécutons la commande : sam deploy --guided afin de déployer le projet. La commande créée le package et déploie l’application sur AWS. Une fois exécuté, il faut fournir certaines informations de configuration :

Nom de la pile (stack): nom de la pile à déployer sur CloudFormation. Cela doit être unique à votre compte et à votre région, et un bon point de départ serait quelque chose correspondant au nom de votre projet. Nous avons spécifié le nom de la pile dans l’exemple de commande ci-dessus. Vous pouvez choisir de le modifier

Région AWS: la région AWS dans laquelle vous souhaitez déployer votre application.

Confirmer les modifications avant le déploiement: si cette option est définie sur yes, tous les ensembles de modifications vous seront présentés avant l’exécution pour examen manuel. S’il est défini sur non, l’AWS SAM CLI déploiera automatiquement les modifications d’application.

Autoriser la création de rôles IAM : de nombreux modèles AWS SAM, y compris cet exemple, créent les rôles AWS IAM requis pour les fonctions AWS Lambda incluses pour accéder aux services AWS. Sélectionnez « Y  » pour cette option

Enregistrer les arguments dans samconfig.toml: si défini sur yes, vos choix seront enregistrés dans un fichier de configuration à l’intérieur du projet, de sorte qu’à l’avenir, vous puissiez simplement ré-exécuter sam deploy sans paramètres pour déployer les modifications dans votre application.

Vous pouvez trouver l’URL de votre point de terminaison API Gateway dans les valeurs de sortie affichées après le déploiement.

Étape 6 : tester l’inférence via l’API

Pour cet exemple, nous utilisons une image d’un crocodile africain afin de tester l’inférence du modèle déployé. L’image est disponible sur la page Wikipedia via cette URL.

 

Ici, nous utilisons l’application Postman pour tester l’inférence. Nous utilisons une requête HTTP en mode GET sur cette URL locale (pour les tests, l’API Gateway et la Lambda sont déployés en local) : http://127.0.0.1:3000/inference?url=https://upload.wikimedia.org/wikipedia/commons/thumb/b/bd/Nile_crocodile_head.jpg/580px-Nile_crocodile_head.jpg

La requête une fois lancée nous permet d’obtenir les résultats de l’inférence fournie par le modèle :

Nous constatons que le modèle ML retourne trois prédictions ordonnées dans l’ordre décroissant des probabilités pour chaque label identifié.

Étape 7 : penser à supprimer les ressources de votre environement AWS

Vous pouvez utiliser la commande suivante pour supprimer toutes les ressources lancées via AWS SAM : aws cloudformation delete-stack --stack-name NOM_DE_VOTRE_PILE_CLOUDFORMATION.

Conclusion

Dans cet article nous avons vu comment rapidement déployer un modèle ML contenu dans un conteneur à l’aide d’AWS SAM et d’AWS Lambda Container. L’utilisation d’AWS Lambda procurent deux avantages dans ce cas d’usage : nous n’avons pas à nous soucier de gérer une infrastructure pour l’hébergement du modèle et, d’autre part, nous utilisons un moyen d’hébergement peu coûteux car si le modèle n’est pas sollicité il n’y a pas de coût lié au service AWS Lambda (voir la facturation d’AWS Lambda).

 

Dorian Richard, Solutions Architect (EMEA) chez Amazon Web Services France, il accompagne les clients français dans leur transformation digitale et leur adoption du cloud.

Linkedin