Passer du prototype à la mise en production : réglage des hyperparamètres

1. Présentation

Dans cet atelier, vous allez utiliser Vertex AI afin d'exécuter un job de réglage des hyperparamètres sur Vertex AI Training.

Cet atelier fait partie d'une série de vidéos appelée Passer du prototype à la mise en production. Assurez-vous d'avoir terminé l'atelier précédent avant de commencer celui-ci. Vous pouvez consulter la série de vidéos associée pour approfondir vos connaissances :

.

Objectifs de l'atelier

Vous allez apprendre à effectuer les opérations suivantes :

  • Modifier le code de l'application d'entraînement pour le réglage automatique des hyperparamètres
  • Configurer et lancer un job de réglage des hyperparamètres avec le SDK Vertex AI pour Python

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 via 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 : Training et Workbench.

Présentation des produits Vertex

3. Configurer votre environnement

Suivez les étapes de l'atelier Entraîner des modèles personnalisés avec Vertex AI pour configurer votre environnement.

4. Conteneuriser le code de l'application d'entraînement

Vous allez soumettre ce job d'entraînement à Vertex AI en plaçant votre code d'application d'entraînement dans un conteneur Docker et en déployant ce conteneur dans Google Artifact Registry. Cette approche vous permet d'entraîner et de régler un modèle créé avec n'importe quel framework.

Pour commencer, ouvrez une fenêtre de terminal à partir du menu de lancement du notebook Workbench que vous avez créé lors des ateliers précédents.

Ouvrir le terminal dans le notebook

Étape 1 : Rédigez le code d'entraînement

Créez un répertoire appelé flowers-hptune et utilisez la commande cd pour y accéder :

mkdir flowers-hptune
cd flowers-hptune

Exécutez la commande suivante afin de créer un répertoire pour le code d'entraînement et un fichier Python dans lequel vous pourrez ajouter le code ci-dessous :

mkdir trainer
touch trainer/task.py

Votre répertoire flowers-hptune/ doit maintenant contenir les éléments suivants :

+ trainer/
    + task.py

Ouvrez ensuite le fichier task.py que vous venez de créer et copiez-y le code ci-dessous.

Dans BUCKET_ROOT, vous devez remplacer {your-gcs-bucket} par le bucket Cloud Storage où vous avez stocké l'ensemble de données "Flowers" dans l'atelier 1.

import tensorflow as tf
import numpy as np
import os
import hypertune
import argparse

## Replace {your-gcs-bucket} !!
BUCKET_ROOT='/gcs/{your-gcs-bucket}'

# Define variables
NUM_CLASSES = 5
EPOCHS=10
BATCH_SIZE = 32

IMG_HEIGHT = 180
IMG_WIDTH = 180

DATA_DIR = f'{BUCKET_ROOT}/flower_photos'

def get_args():
  '''Parses args. Must include all hyperparameters you want to tune.'''

  parser = argparse.ArgumentParser()
  parser.add_argument(
      '--learning_rate',
      required=True,
      type=float,
      help='learning rate')
  parser.add_argument(
      '--momentum',
      required=True,
      type=float,
      help='SGD momentum value')
  parser.add_argument(
      '--num_units',
      required=True,
      type=int,
      help='number of units in last hidden layer')
  args = parser.parse_args()
  return args

def create_datasets(data_dir, batch_size):
  '''Creates train and validation datasets.'''

  train_dataset = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="training",
    seed=123,
    image_size=(IMG_HEIGHT, IMG_WIDTH),
    batch_size=batch_size)

  validation_dataset = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="validation",
    seed=123,
    image_size=(IMG_HEIGHT, IMG_WIDTH),
    batch_size=batch_size)

  train_dataset = train_dataset.cache().shuffle(1000).prefetch(buffer_size=tf.data.AUTOTUNE)
  validation_dataset = validation_dataset.cache().prefetch(buffer_size=tf.data.AUTOTUNE)

  return train_dataset, validation_dataset

def create_model(num_units, learning_rate, momentum):
  '''Creates model.'''

  model = tf.keras.Sequential([
    tf.keras.layers.Resizing(IMG_HEIGHT, IMG_WIDTH),
    tf.keras.layers.Rescaling(1./255, input_shape=(IMG_HEIGHT, IMG_WIDTH, 3)),
    tf.keras.layers.Conv2D(16, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Conv2D(64, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(num_units, activation='relu'),
    tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')
  ])

  model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=learning_rate, momentum=momentum),
              loss=tf.keras.losses.SparseCategoricalCrossentropy(),
              metrics=['accuracy'])

  return model

def main():
  args = get_args()
  train_dataset, validation_dataset = create_datasets(DATA_DIR, BATCH_SIZE)
  model = create_model(args.num_units, args.learning_rate, args.momentum)
  history = model.fit(train_dataset, validation_data=validation_dataset, epochs=EPOCHS)

  # DEFINE METRIC
  hp_metric = history.history['val_accuracy'][-1]

  hpt = hypertune.HyperTune()
  hpt.report_hyperparameter_tuning_metric(
      hyperparameter_metric_tag='accuracy',
      metric_value=hp_metric,
      global_step=EPOCHS)

if __name__ == "__main__":
    main()

Avant de créer le conteneur, examinons le code de plus près. Certains composants sont utilisés spécifiquement pour le service de réglage des hyperparamètres.

  1. Le script importe la bibliothèque hypertune.
  2. La fonction get_args() définit un argument de ligne de commande pour chaque hyperparamètre que vous souhaitez régler. Dans cet exemple, les hyperparamètres qui seront réglés sont le taux d'apprentissage, la valeur du momentum dans l'optimiseur et le nombre d'unités dans la dernière couche cachée du modèle, mais n'hésitez pas à effectuer des tests avec d'autres hyperparamètres. La valeur transmise dans ces arguments est ensuite utilisée pour définir l'hyperparamètre correspondant dans le code.
  3. À la fin de la fonction main(), la bibliothèque hypertune permet de définir la métrique que vous souhaitez optimiser. Dans TensorFlow, la méthode Keras model.fit renvoie un objet History. L'attribut History.history est un enregistrement des valeurs de perte d'entraînement et des métriques à des époques successives. Si vous transmettez des données de validation à model.fit, l'attribut History.history inclut également les valeurs de perte de validation et celles des métriques. Par exemple, si vous aviez entraîné un modèle pendant trois époques avec des données de validation et que vous aviez fourni accuracy comme métrique, l'attribut History.history ressemblerait au dictionnaire suivant.
{
 "accuracy": [
   0.7795261740684509,
   0.9471358060836792,
   0.9870933294296265
 ],
 "loss": [
   0.6340447664260864,
   0.16712145507335663,
   0.04546636343002319
 ],
 "val_accuracy": [
   0.3795261740684509,
   0.4471358060836792,
   0.4870933294296265
 ],
 "val_loss": [
   2.044623374938965,
   4.100203514099121,
   3.0728273391723633
 ]

Si vous souhaitez que le service de réglage des hyperparamètres découvre les valeurs qui maximisent la justesse de la validation du modèle, vous devez définir la métrique en tant que dernière entrée (ou NUM_EPOCS - 1) de la liste val_accuracy. Transmettez ensuite cette métrique à une instance de HyperTune. Vous pouvez choisir la chaîne que vous voulez pour hyperparameter_metric_tag, mais vous devrez la réutiliser ultérieurement lorsque vous lancerez le job de réglage des hyperparamètres.

Étape 2 : Créez un Dockerfile

Pour conteneuriser votre code, vous devrez créer un Dockerfile. Vous allez placer dans ce Dockerfile toutes les commandes nécessaires à l'exécution de l'image. Il installera toutes les bibliothèques nécessaires et configurera le point d'entrée du code d'entraînement.

Depuis votre terminal, créez un Dockerfile vide à la racine de votre répertoire flowers-hptune :

touch Dockerfile

Votre répertoire flowers-hptune/ doit maintenant contenir les éléments suivants :

+ Dockerfile
+ trainer/
    + task.py

Ouvrez le Dockerfile et copiez-y ce qui suit. Vous remarquerez que ce code est presque identique au Dockerfile utilisé dans le premier atelier, l'installation de la bibliothèque cloudml-hypertune en plus.

FROM gcr.io/deeplearning-platform-release/tf2-gpu.2-8

WORKDIR /

# Installs hypertune library
RUN pip install cloudml-hypertune

# Copies the trainer code to the docker image.
COPY trainer /trainer

# Sets up the entry point to invoke the trainer.
ENTRYPOINT ["python", "-m", "trainer.task"]

Étape 3 : Créez le conteneur

Depuis votre terminal, exécutez la commande suivante afin de définir une variable d'environnement pour votre projet, en veillant à remplacer your-cloud-project par l'ID de votre projet :

PROJECT_ID='your-cloud-project'

Définissez un dépôt dans Artifact Registry. Vous allez utiliser le dépôt créé dans le premier atelier.

REPO_NAME='flower-app'

Définissez une variable avec l'URI de votre image de conteneur dans Google Artifact Registry :

IMAGE_URI=us-central1-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/flower_image_hptune:latest

Configurer Docker

gcloud auth configure-docker \
    us-central1-docker.pkg.dev

Ensuite, créez le conteneur en exécutant la commande suivante à partir de la racine de votre répertoire flower-hptune :

docker build ./ -t $IMAGE_URI

Enfin, déployez-le dans Artifact Registry :

docker push $IMAGE_URI

Maintenant que le conteneur a été transféré vers Artifact Registry, vous êtes prêt à lancer un job d'entraînement.

5. Exécuter un job de réglage des hyperparamètres avec le SDK

Dans cette section, vous allez apprendre à configurer et à soumettre le job de réglage des hyperparamètres à l'aide de l'API Vertex pour Python.

Créez un notebook TensorFlow 2 via le lanceur.

new_notebook

Importez le SDK Vertex AI.

from google.cloud import aiplatform
from google.cloud.aiplatform import hyperparameter_tuning as hpt

Pour lancer le job de réglage des hyperparamètres, vous devez d'abord définir la valeur de worker_pool_specs, qui spécifie le type de machine et l'image Docker. La spécification suivante définit une machine comportant deux GPU NVIDIA Tesla V100.

Vous devrez remplacer {PROJECT_ID} par l'ID de votre projet dans image_uri.

# The spec of the worker pools including machine type and Docker image
# Be sure to replace PROJECT_ID in the `image_uri` with your project.

worker_pool_specs = [{
    "machine_spec": {
        "machine_type": "n1-standard-4",
        "accelerator_type": "NVIDIA_TESLA_V100",
        "accelerator_count": 1
    },
    "replica_count": 1,
    "container_spec": {
        "image_uri": "us-central1-docker.pkg.dev/{PROJECT_ID}/flower-app/flower_image_hptune:latest"
    }
}]

Ensuite, définissez le parameter_spec, qui est un dictionnaire spécifiant les paramètres à optimiser. La clé du dictionnaire correspond à la chaîne que vous avez attribuée à l'argument de ligne de commande pour chaque hyperparamètre, et la valeur du dictionnaire correspond à la spécification du paramètre.

Pour chaque hyperparamètre, vous devez définir le type ainsi que les limites pour les valeurs que le service de réglage testera. Les hyperparamètres peuvent être de type Double, Entier, Catégoriel ou Discret. Si vous sélectionnez le type "Double" ou "Entier", vous devez indiquer des valeurs minimale et maximale. Si vous sélectionnez un type "Catégoriel" ou "Discret", vous devez indiquer les valeurs. Pour les types "Double" et "Entier", vous devez également fournir la valeur de scaling. Pour savoir comment choisir la meilleure valeur de scaling, regardez cette vidéo.

# Dictionary representing parameters to optimize.
# The dictionary key is the parameter_id, which is passed into your training
# job as a command line argument,
# And the dictionary value is the parameter specification of the metric.
parameter_spec = {
    "learning_rate": hpt.DoubleParameterSpec(min=0.001, max=1, scale="log"),
    "momentum": hpt.DoubleParameterSpec(min=0, max=1, scale="linear"),
    "num_units": hpt.DiscreteParameterSpec(values=[64, 128, 512], scale=None)
}

La dernière spécification à définir est metric_spec, le dictionnaire qui représente la métrique à optimiser. La clé du dictionnaire est l'option hyperparameter_metric_tag, que vous avez définie dans le code de votre application d'entraînement, et sa valeur correspond à l'objectif d'optimisation.

# Dictionary representing metric to optimize.
# The dictionary key is the metric_id, which is reported by your training job,
# And the dictionary value is the optimization goal of the metric.
metric_spec={'accuracy':'maximize'}

Une fois toutes les spécifications définies, vous allez créer un CustomJob, c'est-à-dire la spécification commune qui sera utilisée pour exécuter votre job lors de chacun des tests de réglage des hyperparamètres.

Vous devez remplacer {YOUR_BUCKET} par le bucket que vous avez créé précédemment.

# Replace YOUR_BUCKET
my_custom_job = aiplatform.CustomJob(display_name='flowers-hptune-job',
                              worker_pool_specs=worker_pool_specs,
                              staging_bucket='gs://{YOUR_BUCKET}')

Ensuite, créez et exécutez HyperparameterTuningJob.

hp_job = aiplatform.HyperparameterTuningJob(
    display_name='flowers-hptune-job',
    custom_job=my_custom_job,
    metric_spec=metric_spec,
    parameter_spec=parameter_spec,
    max_trial_count=15,
    parallel_trial_count=3)

hp_job.run()

Voici quelques arguments à prendre en compte :

  • max_trial_count : vous devez définir le nombre maximal d'essais que le service va réaliser. Un plus grand nombre d'essais permet généralement d'obtenir de meilleurs résultats, mais il existe un point de retours décroissants au-delà duquel les essais supplémentaires n'ont que peu ou pas d'effet sur la métrique que vous essayez d'optimiser. Il est recommandé de commencer avec un petit nombre d'essais et d'attendre d'avoir une idée de l'impact des hyperparamètres choisis avant d'augmenter le nombre d'essais.
  • parallel_trial_count : si vous utilisez des essais en parallèle, le service provisionne plusieurs clusters de traitement pour l'entraînement. L'augmentation du nombre d'essais parallèles a pour effet de réduire le temps nécessaire à l'exécution du job de réglage des hyperparamètres. Toutefois, elle peut réduire l'efficacité globale du job. En effet, la stratégie de réglage par défaut utilise les résultats des essais précédents pour ajuster les valeurs affectées lors des essais suivants.
  • search_algorithm : vous pouvez définir l'algorithme de recherche sur "Grille", "Aléatoire" ou utiliser la valeur par défaut (Aucun). L'option par défaut applique l'optimisation bayésienne pour rechercher l'espace des valeurs possibles pour les hyperparamètres. Il s'agit de l'algorithme recommandé. Obtenez plus d'informations sur cet algorithme.

La console affiche des informations sur la progression du job.

hp_job

Une fois le job terminé, la console affiche également les résultats de chaque essai et indique quel ensemble de valeurs a donné les meilleurs résultats.

hp_results

🎉 Félicitations ! 🎉

Vous savez désormais utiliser Vertex AI pour :

  • exécuter un job de réglage automatique des hyperparamètres

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

6. Nettoyage

Comme le notebook est configuré pour expirer au bout de 60 minutes d'inactivité, il n'est pas nécessaire d'arrêter 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