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

1. Présentation

Dans cet atelier, vous allez suivre un workflow complet d'entraînement de ML sur Google Cloud à l'aide de PyTorch pour créer votre modèle. À partir d'un environnement Cloud AI Platform Notebooks, vous apprendrez à 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. 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 les instructions ici.

Étape 1 : Activer l'API Cloud AI Platform Models

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

d0d38662851c6af3.png

Étape 2 : Activer l'API Compute Engine

Accédez à Compute Engine, puis sélectionnez Activer si elle n'est pas déjà activée. Vous en aurez besoin pour créer votre instance de notebook.

Étape 3 : Créer une instance AI Platform Notebooks

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

892b7588f940d145.png

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

63d2cf44801c2df5.png

Ouvrez ensuite une instance de notebook Python 3 à partir du lanceur :

de4c86c6c7f9438f.png

Vous êtes prêt à commencer !

Étape 5 : Importer des packages Python

Dans la première cellule de votre notebook, ajoutez les importations suivantes, puis exécutez la cellule. Pour l'exécuter, appuyez sur le bouton flèche vers la droite dans le menu supérieur ou sur Commande+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 de notebook.

3. Créer un package pour la tâche d'entraînement

Pour exécuter notre tâche d'entraînement sur AI Platform Training, nous devons empaqueter notre code d'entraînement localement dans notre instance Notebooks et disposer d'un bucket Cloud Storage pour stocker les ressources de notre tâche. 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éer un bucket Cloud Storage pour notre modèle

Commençons par définir des variables d'environnement que nous utiliserons tout au long de cet 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 globalement unique) :

# 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, que nous allons pointer lorsque nous lancerons notre tâche d'entraînement.

Exécutez cette commande gsutil à partir de votre notebook pour créer un bucket :

!gsutil mb $BUCKET_URL

Étape 2 : Créer 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 toutes 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 iPython %%writefile 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.'
)

Créons ensuite notre répertoire trainer/ et le fichier init.py vide qu'il contient. 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 à commencer à créer notre tâche d'entraînement.

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

Cet atelier est axé sur les outils d'entraînement des modèles, mais examinons rapidement l'ensemble de données que nous allons utiliser 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 en 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, les semaines de gestation, la prise de poids de la mère en livres et le sexe du bébé représenté sous forme de valeur booléenne.

5. Définir la tâche d'entraînement avec le 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éer le script d'entraînement

Commençons par créer le fichier Python avec notre script d'entraînement. Nous allons ensuite analyser 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()

La tâche d'entraînement se compose de deux fonctions dans lesquelles la majeure partie du travail est effectuée.

  • get_args() : cette fonction analyse les arguments de ligne de commande que nous allons transmettre lors de la création de notre tâche d'entraînement, ainsi que les hyperparamètres qu'AI Platform doit optimiser. 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 masquée.
  • train_model() : ici, nous téléchargeons les données dans un DataFrame Pandas, nous les normalisons, nous les convertissons en tenseurs PyTorch, puis nous définissons notre modèle. Pour créer notre modèle, nous utilisons l'API PyTorch nn.Sequential, qui nous permet de définir notre modèle en tant que 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 masquée de notre modèle, nous en faisons un hyperparamètre qu'AI Platform va régler 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 d'entraîner à nouveau 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 des hyperparamètres que nous spécifions.

Dans le réglage des hyperparamètres, un essai unique 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 utilisera les résultats des essais terminés pour optimiser les hyperparamètres qu'il sélectionne pour les essais futurs. Pour configurer le réglage des 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.

Créez ensuite 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 de la tâche, 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 au nom que nous utilisons pour la signaler lorsque nous appelons report_hyperparameter_tuning_metric() à la fin d'un essai.

6. Exécuter une tâche d'entraînement sur AI Platform

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

Étape 1 : Définir 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 tâche dans une autre région, mettez à jour 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 tâche à l'aide d'un code temporel :

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

Étape 2 : Lancer la tâche d'entraînement

Nous allons créer notre tâche d'entraînement à l'aide de gcloud, la CLI Google Cloud. Nous pouvons exécuter cette commande directement dans notre notebook, en référençant les 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 Tâches section de votre console AI Platform pour surveiller les journaux.

Étape 3 : Surveiller 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

Au début de votre première série d'essais, vous pourrez voir les valeurs d'hyperparamètres sélectionnées pour chaque essai :

787c053ef9110e6b.png

Une fois les essais terminés, la valeur résultante de votre métrique d'optimisation (dans ce cas, val_mse) sera enregistrée ici. L'exécution de la tâche devrait prendre entre 15 et 20 minutes. Le tableau de bord se présentera comme suit une fois la tâche terminée (les valeurs exactes varieront) :

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 de détails des tâches :

18c32dcd36351930.png

Chaque instruction print() dans votre code d'entraînement de 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 les utiliser comme guide pour lancer une autre tâche 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, puis sélectionnez 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.

Dans le menu de navigation de Cloud Console, accédez à "Stockage" et supprimez les deux buckets que vous avez créés pour stocker les ressources de votre modèle.