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 en utilisant PyTorch pour créer votre modèle. Dans un environnement Cloud AI Platform Notebooks, vous allez apprendre à empaqueter votre job d'entraînement pour l'exécuter sur AI Platform Training avec des réglages d'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 ces instructions.

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

Accédez à la section Modèles AI Platform de Cloud Console, 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 dernier type d'instance PyTorch (sans GPU):

892b7588f940d145.png

Utilisez les options par défaut ou donnez-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 à commencer !

Étape 5: Importez les packages Python

Dans la première cellule de votre notebook, ajoutez les importations suivantes et exécutez la cellule. Vous pouvez l'exécuter en appuyant sur la flèche vers la droite dans le menu supérieur ou en appuyant sur Commande+Entrée:

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

Vous remarquerez que PyTorch n'est pas importé ici. En effet, nous exécutons le job d'entraînement sur AI Platform Training, et non à partir de notre instance de 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 du code d'entraînement empaqueté localement dans notre instance Notebooks et d'un bucket Cloud Storage pour stocker les éléments de notre job. Commençons par créer un bucket de stockage. Vous pouvez ignorer cette étape si vous en avez déjà une.

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

Commençons par définir certaines 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 (ils doivent être uniques):

# 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 indiquerons lorsque nous lancerons le job d'entraînement.

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

!gsutil mb $BUCKET_URL

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

Pour exécuter un job d'entraînement sur AI Platform, nous devons configurer notre code en tant que package Python. Il se compose 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 du package (nous l'appellerons ici trainer/) et d'un fichier __init__.py vide dans ce sous-répertoire.

Tout d'abord, écrivons notre fichier setup.py. Nous utilisons les commandes magiques %%writefile d'iPython pour enregistrer le fichier dans notre instance. Dans cet exemple, nous avons indiqué trois bibliothèques externes que nous allons utiliser 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 à créer le job d'entraînement.

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

Cet atelier se concentre sur les outils permettant d'entraîner des modèles, mais examinons rapidement l'ensemble de données qui nous permettra d'entraîner le modèle à comprendre. Nous allons utiliser l'ensemble de données sur la natalité disponible dans BigQuery. Il contient des données sur la naissance provenant des É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, et nous allons en utiliser un sous-ensemble que nous avons mis à votre disposition dans un bucket Cloud Storage.

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

Nous allons télécharger la version de l'ensemble de données que nous avons mis à votre disposition dans Cloud Storage dans un DataFrame Pandas et la prévisualiser.

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

Cet ensemble de données contient 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 grossesse, la prise de poids de la mère en livres et le sexe du bébé représenté sous forme d'une valeur booléenne.

5. Définir le job d'entraînement avec des réglages d'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 job d'entraînement sera exécuté sur AI Platform Training et utilisera également le service de réglage des hyperparamètres d'AI Platform pour trouver les hyperparamètres optimaux pour 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 analyserons ce qu'il s'y passe. L'exécution de cette commande %%writefile permet d'écrire 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 comprend deux fonctions où l'essentiel du travail est effectué.

  • get_args(): analyse les arguments de ligne de commande que nous transmettrons lors de la création du job d'entraînement, ainsi que les hyperparamètres que nous voulons 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 de notre modèle, le momentum et le nombre de neurones dans notre couche cachée.
  • train_model(): ici, nous téléchargeons les données dans un DataFrame Pandas, nous les normalisons, les convertissons en Tensors 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 sous la forme d'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 réglera pour nous. Vous trouverez plus d'informations à ce sujet 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 le modèle à chaque fois, nous allons utiliser le service d'optimisation des hyperparamètres de Cloud AI Platform. Si nous configurons notre job 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 spécifiés.

Dans le réglage des hyperparamètres, un seul essai consiste en un cycle d'entraînement du 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 des hyperparamètres, nous devons transmettre un fichier de configuration lorsque nous lançons le job 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 à rechercher et l'échelle sur laquelle augmenter la valeur sur différents essais.

Au début de la tâche, nous spécifions également la métrique sur laquelle porte l'optimisation. Notez qu'à la fin de la fonction train_model() ci-dessus, nous signalons cette métrique à AI Platform chaque fois qu'un essai se termine. Ici, nous minimisons l'erreur quadratique moyenne de notre modèle. Nous voulons donc utiliser les hyperparamètres qui entraînent la plus faible erreur quadratique moyenne 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 démarrer le job d'entraînement de modèle avec le réglage des hyperparamètres sur AI Platform.

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

Commençons par définir certaines variables d'environnement que nous utiliserons pour lancer le job d'entraînement. Si vous souhaitez exécuter votre job 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 job 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 horodatage:

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

Étape 2: Démarrez 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 job a été créé correctement, accédez à la section Tâches de la console AI Platform pour surveiller les journaux.

Étape 3: Surveiller votre tâche

Une fois dans la section "Jobs" (Tâches) de la console, cliquez sur le job que vous venez de démarrer pour en afficher les détails:

c184167641bb7ed7.png

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

787c053ef9110e6b.png

Une fois les essais terminés, la valeur obtenue de votre métrique d'optimisation (dans le cas présent, val_mse) est enregistrée ici. L'exécution du job devrait prendre entre 15 et 20 minutes. Une fois le job terminé, le tableau de bord ressemblera à ceci (les valeurs exactes pourront varier):

47ef6b9b4ecb532c.png

Pour déboguer les problèmes potentiels et surveiller votre job plus en détail, cliquez sur Afficher les journaux sur la page de détails des jobs:

18c32dcd36351930.png

Chaque instruction print() de 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 un nouveau job d'entraînement.

Une fois votre job d'entraînement terminé, recherchez les hyperparamètres ayant généré la valeur val_mse la plus basse. Vous pouvez les utiliser pour entraîner et exporter une version finale de votre modèle, ou les utiliser pour lancer un autre job d'entraînement avec d'autres essais de réglage des hyperparamètres.

7. Nettoyage

Si vous souhaitez continuer à utiliser cet ordinateur portable, nous vous recommandons de le désactiver lorsqu'il n'est pas utilisé. À partir de l'interface utilisateur de Notebooks dans Cloud Console, sélectionnez le notebook, puis cliquez sur Arrêter:

879147427150b6c7.png

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

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