Entraîner un modèle PyTorch sur Cloud AI Platform et régler ses hyperparamètres

1. Présentation

Dans cet atelier, vous allez découvrir un workflow d'entraînement de ML complet sur Google Cloud, en utilisant PyTorch pour créer votre modèle. Dans un environnement Cloud AI Platform Notebooks, vous allez apprendre à empaqueter votre tâche d'entraînement pour l'exécuter sur AI Platform Training avec le réglage des hyperparamètres.

Objectifs de l'atelier

Vous allez apprendre à effectuer les opérations suivantes :

  • Créer une instance AI Platform Notebooks
  • Créer un modèle PyTorch
  • Entraîner votre modèle avec le réglage des hyperparamètres sur AI Platform Training

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

2. Configurez 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 Cloud AI Platform Models

Accédez à la section "Modèles AI Platform" de la console Cloud, puis cliquez sur "Activer" si elle n'est pas déjà activée.

d0d38662851c6af3.png

Étape 2 : Activez l'API Compute Engine

Accédez à Compute Engine et cliquez sur Activer si ce n'est pas déjà fait. Vous en aurez besoin pour créer votre instance de notebook.

Étape 3 : Créez une instance AI Platform Notebooks.

Accédez à la section AI Platform Notebooks de la console Cloud, puis cliquez sur Nouvelle instance. Sélectionnez ensuite le type d'instance PyTorch le plus récent (sans GPU) :

892b7588f940d145.png

Utilisez les options par défaut ou attribuez-lui un nom personnalisé si vous le souhaitez, puis cliquez sur Créer. Une fois l'instance créée, sélectionnez Ouvrir JupyterLab :

63d2cf44801c2df5.png

Ensuite, ouvrez une instance de notebook Python 3 à partir du lanceur :

de4c86c6c7f9438f.png

Vous êtes prêt à vous lancer !

Étape 5 : Importer des packages Python

Dans la première cellule de votre notebook, ajoutez les importations suivantes et exécutez la cellule. Pour l'exécuter, appuyez sur le bouton en forme de flèche vers la droite dans le menu du haut ou appuyez sur Cmd+Entrée :

import datetime
import numpy as np
import os
import pandas as pd
import time

Vous remarquerez que nous n'importons pas PyTorch ici. En effet, nous exécutons la tâche d'entraînement sur AI Platform Training, et non à partir de notre instance Notebook.

3. Créer un package pour le job d'entraînement

Pour exécuter notre job d'entraînement sur AI Platform Training, nous avons besoin de notre code d'entraînement empaqueté localement dans notre instance Notebooks et d'un bucket Cloud Storage pour stocker les ressources de notre job. Nous allons d'abord créer un bucket de stockage. Vous pouvez ignorer cette étape si vous en avez déjà un.

Étape 1 : Créez un bucket Cloud Storage pour notre modèle

Commençons par définir des variables d'environnement que nous utiliserons tout au long de l'atelier de programmation. Renseignez les valeurs ci-dessous avec le nom de votre projet Google Cloud et le nom du bucket Cloud Storage que vous souhaitez créer (il doit être unique au niveau mondial) :

# Update these to your own GCP project, model, and version names
GCP_PROJECT = 'your-gcp-project'
BOCKET_URL = 'gs://storage_bucket_name'

Nous sommes maintenant prêts à créer un bucket de stockage, vers lequel nous allons pointer lorsque nous lancerons notre job d'entraînement.

Exécutez cette commande gsutil depuis votre notebook pour créer un bucket :

!gsutil mb $BUCKET_URL

Étape 2 : Créez les fichiers initiaux pour notre package Python

Pour exécuter une tâche d'entraînement sur AI Platform, nous devons configurer notre code en tant que package Python. Il s'agit d'un fichier setup.py dans notre répertoire racine qui spécifie les dépendances de package externes, d'un sous-répertoire portant le nom de notre package (ici, nous l'appellerons trainer/) et d'un fichier __init__.py vide dans ce sous-répertoire.

Commençons par écrire notre fichier setup.py. Nous utilisons les commandes magiques %%writefile d'iPython pour enregistrer le fichier dans notre instance. Ici, nous avons spécifié trois bibliothèques externes que nous utiliserons dans notre code d'entraînement : PyTorch, Scikit-learn et Pandas.

%%writefile setup.py
from setuptools import find_packages
from setuptools import setup

REQUIRED_PACKAGES = ['torch>=1.5', 'scikit-learn>=0.20', 'pandas>=1.0']

setup(
    name='trainer',
    version='0.1',
    install_requires=REQUIRED_PACKAGES,
    packages=find_packages(),
    include_package_data=True,
    description='My training application package.'
)

Ensuite, créons notre répertoire "trainer/" et le fichier vide init.py à l'intérieur. Python utilise ce fichier pour reconnaître qu'il s'agit d'un package :

!mkdir trainer
!touch trainer/__init__.py

Nous sommes maintenant prêts à créer notre job d'entraînement.

4. Prévisualiser l'ensemble de données

Cet atelier se concentre sur les outils d'entraînement des modèles. Toutefois, examinons rapidement l'ensemble de données que nous utiliserons pour entraîner notre modèle. Nous allons utiliser l'ensemble de données sur la natalité disponible dans BigQuery. Il contient des données sur les naissances aux États-Unis sur plusieurs décennies. Nous allons utiliser quelques colonnes de l'ensemble de données pour prédire le poids de naissance d'un bébé. L'ensemble de données d'origine est assez volumineux. Nous allons utiliser un sous-ensemble que nous avons mis à votre disposition dans un bucket Cloud Storage.

Étape 1 : Télécharger l'ensemble de données BigQuery sur la natalité

Téléchargeons la version de l'ensemble de données que nous avons mise à votre disposition dans Cloud Storage dans un DataFrame Pandas et prévisualisons-la.

natality = pd.read_csv('https://storage.googleapis.com/ml-design-patterns/natality.csv')
natality.head()

Cet ensemble de données comporte un peu moins de 100 000 lignes. Nous allons utiliser cinq caractéristiques pour prédire le poids de naissance d'un bébé : l'âge de la mère et du père, le nombre de semaines de gestation, la prise de poids de la mère en livres et le genre du bébé représenté sous la forme d'une valeur booléenne.

5. Définir le job d'entraînement avec réglage des hyperparamètres

Nous allons écrire notre script d'entraînement dans un fichier nommé model.py dans le sous-répertoire trainer/ que nous avons créé précédemment. Notre tâche d'entraînement s'exécutera sur AI Platform Training et utilisera également le service de réglage des hyperparamètres d'AI Platform pour trouver les hyperparamètres optimaux de notre modèle à l'aide de l'optimisation bayésienne.

Étape 1 : Créez le script d'entraînement

Commençons par créer le fichier Python avec notre script d'entraînement. Ensuite, nous allons décortiquer ce qui s'y passe. L'exécution de cette commande %%writefile écrira le code du modèle dans un fichier Python local :

%%writefile trainer/model.py
import argparse
import hypertune
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim

from sklearn.utils import shuffle
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import normalize

def get_args():
    """Argument parser.
    Returns:
        Dictionary of arguments.
    """
    parser = argparse.ArgumentParser(description='PyTorch MNIST')
    parser.add_argument('--job-dir',  # handled automatically by AI Platform
                        help='GCS location to write checkpoints and export ' \
                             'models')
    parser.add_argument('--lr',  # Specified in the config file
                        type=float,
                        default=0.01,
                        help='learning rate (default: 0.01)')
    parser.add_argument('--momentum',  # Specified in the config file
                        type=float,
                        default=0.5,
                        help='SGD momentum (default: 0.5)')
    parser.add_argument('--hidden-layer-size',  # Specified in the config file
                        type=int,
                        default=8,
                        help='hidden layer size')
    args = parser.parse_args()
    return args

def train_model(args):
    # Get the data
    natality = pd.read_csv('https://storage.googleapis.com/ml-design-patterns/natality.csv')
    natality = natality.dropna()
    natality = shuffle(natality, random_state = 2)
    natality.head()

    natality_labels = natality['weight_pounds']
    natality = natality.drop(columns=['weight_pounds'])


    train_size = int(len(natality) * 0.8)
    traindata_natality = natality[:train_size]
    trainlabels_natality = natality_labels[:train_size]

    testdata_natality = natality[train_size:]
    testlabels_natality = natality_labels[train_size:]

    # Normalize and convert to PT tensors
    normalized_train = normalize(np.array(traindata_natality.values), axis=0)
    normalized_test = normalize(np.array(testdata_natality.values), axis=0)

    train_x = torch.Tensor(normalized_train)
    train_y = torch.Tensor(np.array(trainlabels_natality))

    test_x = torch.Tensor(normalized_test)
    test_y = torch.Tensor(np.array(testlabels_natality))

    # Define our data loaders
    train_dataset = torch.utils.data.TensorDataset(train_x, train_y)
    train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=128, shuffle=True)

    test_dataset = torch.utils.data.TensorDataset(test_x, test_y)
    test_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size=128, shuffle=False)

    # Define the model, while tuning the size of our hidden layer
    model = nn.Sequential(nn.Linear(len(train_x[0]), args.hidden_layer_size),
                          nn.ReLU(),
                          nn.Linear(args.hidden_layer_size, 1))
    criterion = nn.MSELoss()

    # Tune hyperparameters in our optimizer
    optimizer = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum)
    epochs = 20
    for e in range(epochs):
        for batch_id, (data, label) in enumerate(train_dataloader):
            optimizer.zero_grad()
            y_pred = model(data)
            label = label.view(-1,1)
            loss = criterion(y_pred, label)
            
            loss.backward()
            optimizer.step()


    val_mse = 0
    num_batches = 0
    # Evaluate accuracy on our test set
    with torch.no_grad():
        for i, (data, label) in enumerate(test_dataloader):
            num_batches += 1
            y_pred = model(data)
            mse = criterion(y_pred, label.view(-1,1))
            val_mse += mse.item()


    avg_val_mse = (val_mse / num_batches)

    # Report the metric we're optimizing for to AI Platform's HyperTune service
    # In this example, we're mimizing error on our test set
    hpt = hypertune.HyperTune()
    hpt.report_hyperparameter_tuning_metric(
        hyperparameter_metric_tag='val_mse',
        metric_value=avg_val_mse,
        global_step=epochs        
    )

def main():
    args = get_args()
    print('in main', args)
    train_model(args)

if __name__ == '__main__':
    main()

Le job d'entraînement se compose de deux fonctions dans lesquelles la majeure partie du travail est effectuée.

  • get_args() : analyse les arguments de ligne de commande que nous transmettrons lors de la création de notre tâche d'entraînement, ainsi que les hyperparamètres que nous souhaitons qu'AI Platform optimise. Dans cet exemple, notre liste d'arguments n'inclut que les hyperparamètres que nous allons optimiser : le taux d'apprentissage, le momentum et le nombre de neurones de notre couche cachée.
  • train_model() : ici, nous téléchargeons les données dans un DataFrame Pandas, les normalisons, les convertissons en Tensors PyTorch, puis définissons notre modèle. Pour créer notre modèle, nous utilisons l'API nn.Sequential de PyTorch, qui nous permet de définir notre modèle comme une pile de couches :
model = nn.Sequential(nn.Linear(len(train_x[0]), args.hidden_layer_size),
                      nn.ReLU(),
                      nn.Linear(args.hidden_layer_size, 1))

Notez qu'au lieu de coder en dur la taille de la couche cachée de notre modèle, nous en faisons un hyperparamètre qu'AI Platform ajustera pour nous. Nous y reviendrons dans la section suivante.

Étape 2 : Utiliser le service de réglage des hyperparamètres d'AI Platform

Au lieu d'essayer manuellement différentes valeurs d'hyperparamètres et de réentraîner notre modèle à chaque fois, nous allons utiliser le service d'optimisation des hyperparamètres de Cloud AI Platform. Si nous configurons notre tâche d'entraînement avec des arguments d'hyperparamètres, AI Platform utilisera l'optimisation bayésienne pour trouver les valeurs idéales pour les hyperparamètres que nous spécifions.

Dans le réglage des hyperparamètres, un seul essai consiste en une exécution d'entraînement de notre modèle avec une combinaison spécifique de valeurs d'hyperparamètres. En fonction du nombre d'essais que nous exécutons, AI Platform utilise les résultats des essais terminés pour optimiser les hyperparamètres qu'il sélectionne pour les futurs essais. Pour configurer le réglage d'hyperparamètres, nous devons transmettre un fichier de configuration lorsque nous lançons notre tâche d'entraînement avec des données sur chacun des hyperparamètres que nous optimisons.

Ensuite, créez ce fichier de configuration localement :

%%writefile config.yaml
trainingInput:
  hyperparameters:
    goal: MINIMIZE
    maxTrials: 10
    maxParallelTrials: 5
    hyperparameterMetricTag: val_mse
    enableTrialEarlyStopping: TRUE
    params:
    - parameterName: lr
      type: DOUBLE
      minValue: 0.0001
      maxValue: 0.1
      scaleType: UNIT_LINEAR_SCALE
    - parameterName: momentum
      type: DOUBLE
      minValue: 0.0
      maxValue: 1.0
      scaleType: UNIT_LINEAR_SCALE
    - parameterName: hidden-layer-size
      type: INTEGER
      minValue: 8
      maxValue: 32
      scaleType: UNIT_LINEAR_SCALE

Pour chaque hyperparamètre, nous spécifions le type, la plage de valeurs que nous souhaitons rechercher et l'échelle sur laquelle augmenter la valeur au cours des différents essais.

Au début du job, nous spécifions également la métrique pour laquelle nous optimisons. Notez qu'à la fin de notre fonction train_model() ci-dessus, nous signalons cette métrique à AI Platform chaque fois qu'un essai est terminé. Ici, nous minimisons l'erreur quadratique moyenne de notre modèle. Nous voulons donc utiliser les hyperparamètres qui entraînent l'erreur quadratique moyenne la plus faible pour notre modèle. Le nom de cette métrique (val_mse) correspond à celui que nous utilisons pour la signaler lorsque nous appelons report_hyperparameter_tuning_metric() à la fin d'un essai.

6. Exécuter un job d'entraînement sur AI Platform

Dans cette section, nous allons lancer notre job d'entraînement du modèle avec réglage des hyperparamètres sur AI Platform.

Étape 1 : Définissez des variables d'environnement

Commençons par définir des variables d'environnement que nous utiliserons pour lancer notre tâche d'entraînement. Si vous souhaitez exécuter votre job dans une autre région, modifiez la variable REGION ci-dessous :

MAIN_TRAINER_MODULE = "trainer.model"
TRAIN_DIR = os.getcwd() + '/trainer'
JOB_DIR = BUCKET_URL + '/output'
REGION = "us-central1"

Chaque tâche d'entraînement sur AI Platform doit avoir un nom unique. Exécutez la commande suivante pour définir une variable pour le nom de votre job à l'aide d'un code temporel :

timestamp = str(datetime.datetime.now().time())
JOB_NAME = 'caip_training_' + str(int(time.time()))

Étape 2 : Démarrer le job d'entraînement

Nous allons créer notre job d'entraînement à l'aide de gcloud, la Google Cloud CLI. Nous pouvons exécuter cette commande directement dans notre notebook, en faisant référence aux variables que nous avons définies ci-dessus :

!gcloud ai-platform jobs submit training $JOB_NAME \
        --scale-tier basic \
        --package-path $TRAIN_DIR \
        --module-name $MAIN_TRAINER_MODULE \
        --job-dir $JOB_DIR \
        --region $REGION \
        --runtime-version 2.1 \
        --python-version 3.7 \
        --config config.yaml

Si votre tâche a été créée correctement, accédez à la section Tâches de votre console AI Platform pour surveiller les journaux.

Étape 3 : Surveillez votre tâche

Une fois dans la section "Tâches" de la console, cliquez sur la tâche que vous venez de démarrer pour afficher les détails :

c184167641bb7ed7.png

Lorsque votre première série d'essais commence, vous pouvez voir les valeurs d'hyperparamètres sélectionnées pour chaque essai :

787c053ef9110e6b.png

À la fin des tests, la valeur obtenue pour votre métrique d'optimisation (val_mse dans ce cas) sera consignée ici. L'exécution du job devrait prendre entre 15 et 20 minutes. Une fois le job terminé, le tableau de bord devrait se présenter comme suit (les valeurs exactes peuvent varier) :

47ef6b9b4ecb532c.png

Pour déboguer les problèmes potentiels et surveiller votre tâche plus en détail, cliquez sur Afficher les journaux sur la page d'informations sur la tâche :

18c32dcd36351930.png

Chaque instruction print() dans le code d'entraînement de votre modèle s'affichera ici. Si vous rencontrez des problèmes, essayez d'ajouter d'autres instructions d'impression et de démarrer une nouvelle tâche d'entraînement.

Une fois votre tâche d'entraînement terminée, recherchez les hyperparamètres qui ont généré la valeur val_mse la plus faible. Vous pouvez les utiliser pour entraîner et exporter une version finale de votre modèle, ou vous en servir comme point de départ pour lancer un autre job d'entraînement avec des essais de réglage d'hyperparamètres supplémentaires.

7. Nettoyage

Si vous souhaitez continuer à utiliser ce notebook, nous vous recommandons de le désactiver quand vous ne vous en servez pas. À partir de l'interface utilisateur de Notebooks dans la console Cloud, sélectionnez le notebook et cliquez sur Arrêter :

879147427150b6c7.png

Si vous souhaitez supprimer toutes les ressources que vous avez créées dans cet atelier, supprimez simplement l'instance de notebook au lieu de l'arrêter.

À l'aide du menu de navigation de la console Cloud, accédez à "Stockage" et supprimez les deux buckets que vous avez créés pour stocker les composants de votre modèle.