Obtenir des prédictions à partir d'un modèle d'images TensorFlow pré-entraîné sur Vertex AI

1. Présentation

Dans cet atelier, vous allez utiliser Vertex AI pour obtenir des prédictions à partir d'un modèle de classification d'images pré-entraîné.

Objectifs

Vous allez apprendre à effectuer les opérations suivantes :

  • Importer un modèle TensorFlow dans Vertex AI Model Registry
  • Obtenir des prédictions en ligne
  • Mettre à jour une fonction TensorFlow Serving

Le coût total d'exécution de cet atelier sur Google Cloud est d'environ 1 $.

2. Présentation de Vertex AI

Cet atelier utilise la toute dernière offre de produits d'IA de Google Cloud. Vertex AI simplifie l'expérience de développement en intégrant toutes les offres de ML de Google Cloud. Auparavant, les modèles entraînés avec AutoML et les modèles personnalisés étaient accessibles depuis des services distincts. La nouvelle offre regroupe ces deux types de modèles mais aussi d'autres nouveaux produits en une seule API. Vous pouvez également migrer des projets existants vers Vertex AI.

Vertex AI comprend de nombreux produits différents qui permettent de gérer les workflows de ML de bout en bout. Cet atelier se concentre sur les produits mis en évidence ci-dessous : Predictions et Workbench.

Présentation des produits Vertex

3. Présentation du cas d'utilisation

Dans cet atelier, vous allez apprendre à prendre un modèle pré-entraîné à partir de TensorFlow Hub et à le déployer sur Vertex AI. TensorFlow Hub est un dépôt de modèles entraînés pour divers domaines problématiques, tels que les embeddings, la génération de texte, la conversion de la parole en texte, la segmentation d'images, etc.

L'exemple utilisé dans cet atelier est un modèle de classification d'images MobileNet V1 pré-entraîné sur l'ensemble de données ImageNet. En utilisant des modèles prêts à l'emploi de TensorFlow Hub ou d'autres dépôts de deep learning similaires, vous pouvez déployer des modèles de ML de haute qualité pour un certain nombre de tâches de prédiction sans avoir à vous soucier de l'entraînement des modèles.

4. Configurer votre environnement

Pour suivre cet atelier de programmation, vous aurez besoin d'un projet Google Cloud Platform dans lequel la facturation est activée. Pour créer un projet, suivez ces instructions.

Étape 1 : Activez l'API Compute Engine

Accédez à Compute Engine et cliquez sur Activer si ce n'est pas déjà fait.

Étape 2: Activez l'API Vertex AI

Accédez à la section Vertex AI de Cloud Console, puis cliquez sur Activer l'API Vertex AI.

Tableau de bord Vertex AI

Étape 3: Créez une instance Vertex AI Workbench

Dans la section Vertex AI de Cloud Console, cliquez sur Workbench :

Menu Vertex AI

Activez l'API Notebooks si ce n'est pas déjà fait.

Notebook_api

Une fois l'API activée, cliquez sur NOTEBOOKS GÉRÉS :

Notebooks_UI

Sélectionnez ensuite NOUVEAU NOTEBOOK.

new_notebook

Attribuez un nom à votre notebook, puis sous Autorisation, sélectionnez Compte de service.

create_notebook

Sélectionnez Paramètres avancés.

Dans la section Sécurité, sélectionnez "Activer le terminal" si ce n'est pas déjà fait.

enable_terminal

Vous pouvez conserver tous les autres paramètres avancés tels quels.

Cliquez ensuite sur Créer. Le provisionnement de l'instance prend quelques minutes.

Une fois l'instance créée, sélectionnez OUVRIR JUPYTERLAB :

open_jupyterlab

5. Enregistrer le modèle

Étape 1: Importer le modèle dans Cloud Storage

Cliquez sur ce lien pour accéder à la page TensorFlow Hub du modèle MobileNet V1 entraîné sur l'ensemble de données ImageNet.

Sélectionnez Télécharger pour télécharger les artefacts de modèle enregistrés.

download_model

Dans la section Cloud Storage de la console Google Cloud, sélectionnez CRÉER.

create_bucket

Attribuez un nom à votre bucket et sélectionnez "us-central1" comme région. Cliquez ensuite sur CRÉER.

specify_bucket

Importez le modèle TensorFlow Hub que vous avez téléchargé dans le bucket. Assurez-vous d'abord de décompresser le fichier.

gcs_model

Votre bucket doit se présenter comme suit:

imagenet_mobilenet_v1_050_128_classification_5/
  saved_model.pb
  variables/
    variables.data-00000-of-00001
    variables.index

Étape 2: Importez le modèle dans le registre

Accédez à la section Registre de modèles de Vertex AI dans la console Cloud.

model_registry

Sélectionnez IMPORTER.

Sélectionnez Importer en tant que nouveau modèle, puis attribuez un nom à votre modèle.

name_and_region

Dans la section Paramètres du modèle, spécifiez le conteneur TensorFlow prédéfini le plus récent. Sélectionnez ensuite le chemin d'accès Cloud Storage dans lequel vous avez stocké les artefacts de modèle.

select_container

Vous pouvez ignorer la section Explicabilité.

Sélectionnez ensuite IMPORTER.

Une fois importé, votre modèle s'affiche dans le registre de modèles.

imported_model

6. Déployer un modèle

Dans le Registre des modèles, sélectionnez les trois points situés à droite du modèle, puis cliquez sur Déployer sur un point de terminaison.

deploy_model

Sous Définir votre point de terminaison, sélectionnez Créer un point de terminaison, puis nommez votre point de terminaison.

Sous Paramètres du modèle, définissez le nombre maximal de nœuds de calcul sur 1, le type de machine sur n1-standard-2 et laissez tous les autres paramètres inchangés. Cliquez ensuite sur DÉPLOYER.

endpoint_settings

Une fois le déploiement effectué, l'état du déploiement passe à Déployé sur Vertex AI.

deploy_status

7. Obtenir des prédictions

Ouvrez le notebook Workbench que vous avez créé lors de la configuration. Créez un notebook TensorFlow 2 via le lanceur.

tf_nb

Exécutez la cellule suivante pour importer les bibliothèques nécessaires

from google.cloud import aiplatform

import tensorflow as tf
import numpy as np
from PIL import Image

Le modèle MobileNet que vous avez téléchargé à partir de TensorFlow Hub a été entraîné sur l'ensemble de données ImageNet. Le résultat du modèle MobileNet est un nombre correspondant à une étiquette de classe dans l'ensemble de données ImageNet. Pour traduire ce nombre en libellé de chaîne, vous devez télécharger les libellés des images.

# Download image labels

labels_path = tf.keras.utils.get_file('ImageNetLabels.txt','https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')
imagenet_labels = np.array(open(labels_path).read().splitlines())

Pour accéder au point de terminaison, vous devez définir la ressource de point de terminaison. Veillez à remplacer {PROJECT_NUMBER} et {ENDPOINT_ID}.

PROJECT_NUMBER = "{PROJECT_NUMBER}"
ENDPOINT_ID = "{ENDPOINT_ID}"

endpoint = aiplatform.Endpoint(
    endpoint_name=f"projects/{PROJECT_NUMBER}/locations/us-central1/endpoints/{ENDPOINT_ID}")

Vous trouverez le numéro de votre projet sur la page d'accueil de la console.

project_number

L'ID du point de terminaison figure dans la section Points de terminaison de Vertex AI.

endpoint_id

Vous allez maintenant tester votre point de terminaison.

Commencez par télécharger l'image suivante et importez-la dans votre instance.

test_image

Ouvrez l'image avec PIL. Redimensionnez ensuite l'image et définissez la valeur de mise à l'échelle sur 255. Notez que la taille d'image attendue par le modèle se trouve sur la page TensorFlow Hub du modèle.

IMAGE_PATH = "test-image.jpg"
IMAGE_SIZE = (128, 128)

im = Image.open(IMAGE_PATH)
im = im.resize(IMAGE_SIZE
im = np.array(im)/255.0

Convertissez ensuite les données NumPy en liste afin qu'elles puissent être envoyées dans le corps de la requête HTTP.

x_test = im.astype(np.float32).tolist()

Enfin, effectuez un appel de prédiction au point de terminaison, puis recherchez l'étiquette de chaîne correspondante.

# make prediction request
result = endpoint.predict(instances=[x_test]).predictions

# post process result
predicted_class = tf.math.argmax(result[0], axis=-1)
string_label = imagenet_labels[predicted_class]

print(f"label ID: {predicted_class}")
print(f"string label: {string_label}")

8. [Facultatif] Utiliser TF Serving pour optimiser les prédictions

Pour des exemples plus réalistes, vous devrez probablement envoyer l'image directement au point de terminaison, au lieu de l'importer dans Numpy au préalable. Cette méthode est plus efficace, mais vous devrez modifier la fonction de diffusion du modèle TensorFlow. Cette modification est nécessaire pour convertir les données d'entrée au format attendu par votre modèle.

Étape 1: Modifier la fonction de diffusion

Ouvrez un nouveau notebook TensorFlow et importez les bibliothèques nécessaires.

from google.cloud import aiplatform

import tensorflow as tf

Au lieu de télécharger les artefacts du modèle enregistré, vous allez cette fois charger le modèle dans TensorFlow à l'aide de hub.KerasLayer, qui encapsule un modèle SavedModel TensorFlow en tant que couche Keras. Pour créer le modèle, vous pouvez utiliser l'API Keras Sequential avec le modèle TF Hub téléchargé en tant que couche, puis spécifier la forme d'entrée pour le modèle.

tfhub_model = tf.keras.Sequential(
    [hub.KerasLayer("https://tfhub.dev/google/imagenet/mobilenet_v1_050_128/classification/5")]
)
tfhub_model.build([None, 128, 128, 3])

Définissez l'URI du bucket que vous avez créé précédemment.

BUCKET_URI = "gs://{YOUR_BUCKET}"
MODEL_DIR = BUCKET_URI + "/bytes_model"

Lorsque vous envoyez une requête à un serveur de prédiction en ligne, elle est reçue par un serveur HTTP. Le serveur HTTP extrait la requête de prédiction du corps du contenu de la requête HTTP. La requête de prédiction extraite est transmise à la fonction de diffusion. Pour les conteneurs de prédiction prédéfinis Vertex AI, le contenu de la requête est transmis à la fonction de diffusion en tant que tf.string.

Pour transmettre des images au service de prédiction, vous devez encoder les octets d'image compressés en base64, ce qui protège le contenu contre toute modification lors de la transmission de données binaires sur le réseau.

Étant donné que le modèle déployé attend des données d'entrée sous forme d'octets bruts (non compressés), vous devez vous assurer que les données encodées en base64 sont converties en octets bruts (par exemple, au format JPEG), puis prétraitées pour répondre aux exigences d'entrée du modèle, avant d'être transmises en entrée au modèle déployé.

Pour résoudre ce problème, vous devez définir une fonction de diffusion (serving_fn) et l'associer au modèle en tant qu'étape de prétraitement. Vous ajoutez un décorateur @tf.function pour que la fonction de diffusion soit fusionnée avec le modèle sous-jacent (au lieu d'être en amont sur un processeur).

CONCRETE_INPUT = "numpy_inputs"


def _preprocess(bytes_input):
    decoded = tf.io.decode_jpeg(bytes_input, channels=3)
    decoded = tf.image.convert_image_dtype(decoded, tf.float32)
    resized = tf.image.resize(decoded, size=(128, 128))
    return resized


@tf.function(input_signature=[tf.TensorSpec([None], tf.string)])
def preprocess_fn(bytes_inputs):
    decoded_images = tf.map_fn(
        _preprocess, bytes_inputs, dtype=tf.float32, back_prop=False
    )
    return {
        CONCRETE_INPUT: decoded_images
    }  # User needs to make sure the key matches model's input


@tf.function(input_signature=[tf.TensorSpec([None], tf.string)])
def serving_fn(bytes_inputs):
    images = preprocess_fn(bytes_inputs)
    prob = m_call(**images)
    return prob


m_call = tf.function(tfhub_model.call).get_concrete_function(
    [tf.TensorSpec(shape=[None, 128, 128, 3], dtype=tf.float32, name=CONCRETE_INPUT)]
)

tf.saved_model.save(tfhub_model, MODEL_DIR, signatures={"serving_default": serving_fn})

Lorsque vous envoyez des données pour la prédiction en tant que paquet de requête HTTP, les données d'image sont encodées en base64, mais le modèle TensorFlow accepte une entrée numpy. Votre fonction d'inférence effectue la conversion de base64 en tableau Numpy.

Lorsque vous effectuez une requête de prédiction, vous devez acheminer la requête vers la fonction de diffusion plutôt que vers le modèle. Vous devez donc connaître le nom de la couche d'entrée de la fonction de diffusion. Nous pouvons obtenir ce nom à partir de la signature de la fonction de diffusion.

loaded = tf.saved_model.load(MODEL_DIR)

serving_input = list(
    loaded.signatures["serving_default"].structured_input_signature[1].keys()
)[0]
print("Serving function input name:", serving_input)

Étape 2: Importer dans le registre et déployer

Dans les sections précédentes, vous avez vu comment importer un modèle dans Vertex AI Model Registry via l'interface utilisateur. Dans cette section, vous allez voir une autre façon d'utiliser le SDK. Notez que vous pouvez toujours utiliser l'UI à la place si vous préférez.

model = aiplatform.Model.upload(
    display_name="optimized-model",
    artifact_uri=MODEL_DIR,
    serving_container_image_uri="us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-8:latest",
)

print(model)

Vous pouvez également déployer le modèle à l'aide du SDK, au lieu de l'UI.

endpoint = model.deploy(
     deployed_model_display_name='my-bytes-endpoint',
     traffic_split={"0": 100},
     machine_type="n1-standard-4",
     accelerator_count=0,
     min_replica_count=1,
     max_replica_count=1,
   )

Étape 3: Tester le modèle

Vous pouvez maintenant tester le point de terminaison. Comme nous avons modifié la fonction de diffusion, vous pouvez cette fois envoyer l'image directement (encodée en base64) dans la requête au lieu de la charger d'abord dans NumPy. Vous pourrez ainsi envoyer des images plus grandes sans dépasser la limite de taille des prédictions Vertex AI.

Télécharger à nouveau les libellés des images

import numpy as np
labels_path = tf.keras.utils.get_file('ImageNetLabels.txt','https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')
imagenet_labels = np.array(open(labels_path).read().splitlines())

Encodez l'image en base64.

import base64

with open("test-image.jpg", "rb") as f:
    data = f.read()
b64str = base64.b64encode(data).decode("utf-8")

Appelez une prédiction en spécifiant le nom de la couche d'entrée de la fonction de diffusion que nous avons définie dans la variable serving_input précédemment.

instances = [{serving_input: {"b64": b64str}}]

# Make request
result = endpoint.predict(instances=instances).predictions

# Convert image class to string label
predicted_class = tf.math.argmax(result[0], axis=-1)
string_label = imagenet_labels[predicted_class]

print(f"label ID: {predicted_class}")
print(f"string label: {string_label}")

🎉 Félicitations ! 🎉

Vous savez désormais utiliser Vertex AI pour :

  • Héberger et déployer un modèle pré-entraîné

Pour en savoir plus sur les différents composants de Vertex, consultez la documentation.

9. Nettoyage

Étant donné que les notebooks gérés par Vertex AI Workbench disposent d'une fonctionnalité d'arrêt en cas d'inactivité, vous n'avez pas à vous soucier de l'arrêt de l'instance. Si vous souhaitez arrêter l'instance manuellement, cliquez sur le bouton "Arrêter" dans la section "Vertex AI Workbench" de la console. Si vous souhaitez supprimer le notebook définitivement, cliquez sur le bouton "Supprimer".

Arrêter l'instance

Pour supprimer le bucket de stockage, utilisez le menu de navigation de la console Cloud pour accéder à Stockage, sélectionnez votre bucket puis cliquez sur "Supprimer" :

Supprimer l'espace de stockage