Vertex AI: ottimizzazione degli iperparametri distribuita

1. Panoramica

In questo lab imparerai a utilizzare Vertex AI per l'ottimizzazione degli iperparametri e l'addestramento distribuito. Sebbene questo lab utilizzi TensorFlow per il codice del modello, i concetti sono applicabili anche ad altri framework ML.

Cosa imparerai

Al termine del corso sarai in grado di:

  • Addestra un modello utilizzando l'addestramento distribuito su un container personalizzato
  • Lancia più prove del tuo codice di addestramento per l'ottimizzazione automatica degli iperparametri

Il costo totale per eseguire questo lab su Google Cloud è di circa 6$.

2. Introduzione a Vertex AI

Questo lab utilizza la più recente offerta di prodotti AI disponibile su Google Cloud. Vertex AI integra le offerte ML di Google Cloud in un'esperienza di sviluppo fluida. In precedenza, i modelli addestrati con AutoML e i modelli personalizzati erano accessibili tramite servizi separati. La nuova offerta combina entrambi in un'unica API, insieme ad altri nuovi prodotti. Puoi anche migrare progetti esistenti su Vertex AI. In caso di feedback, consulta la pagina di supporto.

Vertex AI include molti prodotti diversi per supportare i flussi di lavoro ML end-to-end. Questo lab si concentrerà su Training e Workbench.

Panoramica dei prodotti Vertex

3. Panoramica del caso d'uso

In questo lab utilizzerai l'ottimizzazione degli iperparametri per scoprire i parametri ottimali per un modello di classificazione delle immagini addestrato sul set di dati di cavalli o esseri umani di TensorFlow Datasets.

Ottimizzazione degli iperparametri

L'ottimizzazione degli iperparametri con Vertex AI Training funziona eseguendo più prove dell'applicazione di addestramento con i valori degli iperparametri scelti, impostati entro i limiti specificati. Vertex AI tiene traccia dei risultati di ogni prova e apporta modifiche per le prove successive.

Per utilizzare l'ottimizzazione degli iperparametri con Vertex AI Training, devi apportare due modifiche al codice di addestramento:

  1. Definisci un argomento della riga di comando nel modulo di addestramento principale per ogni iperparametro che vuoi ottimizzare.
  2. Utilizza il valore passato in questi argomenti per impostare l'iperparametro corrispondente nel codice dell'applicazione.

Addestramento distribuito

Se hai una singola GPU, TensorFlow utilizzerà questo acceleratore per velocizzare l'addestramento del modello senza alcun intervento da parte tua. Tuttavia, se vuoi ottimizzare l'uso di più GPU, devi usare tf.distribute, che è il modulo di TensorFlow per eseguire un calcolo su più dispositivi.

Questo lab utilizza tf.distribute.MirroredStrategy, che puoi aggiungere alle tue applicazioni di addestramento con poche modifiche al codice. Questa strategia crea una copia del modello su ogni GPU della tua macchina. Gli aggiornamenti successivi del gradiente verranno eseguiti in modo sincrono. Ciò significa che ogni GPU calcola i passaggi in avanti e all'indietro attraverso il modello su un segmento diverso dei dati di input. I gradienti calcolati da ciascuna di queste sezioni vengono quindi aggregati in tutte le GPU e viene calcolata la media in un processo noto come all-reduce. I parametri del modello vengono aggiornati utilizzando questi gradienti calcolati in media.

Non è necessario conoscere i dettagli per completare questo lab, ma se vuoi saperne di più sul funzionamento dell'addestramento distribuito in TensorFlow, guarda il video di seguito:

4. Configura l'ambiente

Per eseguire questo codelab, devi avere un progetto Google Cloud con la fatturazione abilitata. Per creare un progetto, segui le istruzioni riportate qui.

Passaggio 1: abilita l'API Compute Engine

Vai a Compute Engine e seleziona Attiva se non è già abilitato.

Passaggio 2: attiva l'API Container Registry

Vai a Container Registry e seleziona Attiva se non è già selezionato. Lo utilizzerai per creare un container per il tuo job di addestramento personalizzato.

Passaggio 3: attiva l'API Vertex AI

Vai alla sezione Vertex AI della console Cloud e fai clic su Abilita API Vertex AI.

Dashboard di Vertex AI

Passaggio 4: crea un'istanza di Vertex AI Workbench

Dalla sezione Vertex AI della console Cloud, fai clic su Workbench:

Menu Vertex AI

Se non l'hai ancora fatto, abilita l'API Notebooks.

Notebook_api

Una volta attivata, fai clic su NOTEBOOK GESTORI:

Notebooks_UI

Quindi seleziona NUOVO Blocco note.

new_notebook

Assegna un nome al blocco note, quindi fai clic su Impostazioni avanzate.

create_notebook

In Impostazioni avanzate, abilita l'arresto per inattività e imposta il numero di minuti su 60. Ciò significa che il notebook si spegnerà automaticamente quando non è in uso, in modo da non sostenere costi non necessari.

idle_timeout

In Sicurezza, seleziona "Abilita terminale" se non è già attivato.

terminale di abilitazione

Puoi lasciare invariate tutte le altre impostazioni avanzate.

Poi, fai clic su Crea. Il provisioning dell'istanza richiederà un paio di minuti.

Una volta creata l'istanza, seleziona Apri JupyterLab.

open_jupyterlab

La prima volta che utilizzi una nuova istanza, ti verrà chiesto di autenticarti. Segui i passaggi nell'interfaccia utente.

autenticare

5. Scrivi il codice di addestramento

Per iniziare, dal menu Avvio app, apri una finestra Terminale nell'istanza del blocco note:

launcher_terminal

Crea una nuova directory chiamata vertex-codelab e accedi con cd.

mkdir vertex-codelab
cd vertex-codelab

Esegui quanto segue per creare una directory per il codice di addestramento e un file Python in cui aggiungerai il codice:

mkdir trainer
touch trainer/task.py

Ora dovresti avere quanto segue nella tua directory vertex-codelab:

+ trainer/
    + task.py

Successivamente, apri il file task.py che hai appena creato e incolla tutto il codice riportato di seguito.

import tensorflow as tf
import tensorflow_datasets as tfds
import argparse
import hypertune
import os

NUM_EPOCHS = 10
BATCH_SIZE = 64

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 preprocess_data(image, label):
  '''Resizes and scales images.'''

  image = tf.image.resize(image, (150,150))
  return tf.cast(image, tf.float32) / 255., label


def create_dataset(batch_size):
  '''Loads Horses Or Humans dataset and preprocesses data.'''

  data, info = tfds.load(name='horses_or_humans', as_supervised=True, with_info=True)

  # Create train dataset
  train_data = data['train'].map(preprocess_data)
  train_data  = train_data.shuffle(1000)
  train_data  = train_data.batch(batch_size)

  # Create validation dataset
  validation_data = data['test'].map(preprocess_data)
  validation_data  = validation_data.batch(batch_size)

  return train_data, validation_data


def create_model(num_units, learning_rate, momentum):
  '''Defines and compiles model.'''

  inputs = tf.keras.Input(shape=(150, 150, 3))
  x = tf.keras.layers.Conv2D(16, (3, 3), activation='relu')(inputs)
  x = tf.keras.layers.MaxPooling2D((2, 2))(x)
  x = tf.keras.layers.Conv2D(32, (3, 3), activation='relu')(x)
  x = tf.keras.layers.MaxPooling2D((2, 2))(x)
  x = tf.keras.layers.Conv2D(64, (3, 3), activation='relu')(x)
  x = tf.keras.layers.MaxPooling2D((2, 2))(x)
  x = tf.keras.layers.Flatten()(x)
  x = tf.keras.layers.Dense(num_units, activation='relu')(x)
  outputs = tf.keras.layers.Dense(1, activation='sigmoid')(x)
  model = tf.keras.Model(inputs, outputs)
  model.compile(
      loss='binary_crossentropy',
      optimizer=tf.keras.optimizers.SGD(learning_rate=learning_rate, momentum=momentum),
      metrics=['accuracy'])
  return model


def main():
  args = get_args()

  # Create distribution strategy
  strategy = tf.distribute.MirroredStrategy()

  # Get data
  GLOBAL_BATCH_SIZE = BATCH_SIZE * strategy.num_replicas_in_sync
  train_data, validation_data = create_dataset(GLOBAL_BATCH_SIZE)

  # Wrap variable creation within strategy scope
  with strategy.scope():
    model = create_model(args.num_units, args.learning_rate, args.momentum)

  # Train model
  history = model.fit(train_data, epochs=NUM_EPOCHS, validation_data=validation_data)

  # 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=NUM_EPOCHS)


if __name__ == "__main__":
    main()

Diamo un'occhiata più da vicino al codice ed esaminiamo i componenti specifici per l'addestramento distribuito e l'ottimizzazione degli iperparametri.

Addestramento distribuito

  1. Nella funzione main() viene creato l'oggetto MirroredStrategy. Poi, inserisci la creazione delle variabili del modello nell'ambito della strategia. Questo passaggio indica a TensorFlow quali variabili devono essere sottoposte a mirroring tra le GPU.
  2. La dimensione del batch viene aumentata di num_replicas_in_sync. La scalabilità della dimensione del batch è una best practice quando si utilizzano strategie di parallelismo dei dati sincrono in TensorFlow. Scopri di più qui.

Ottimizzazione degli iperparametri

  1. Lo script importa la libreria hypertune. In un secondo momento, quando creeremo l'immagine del contenitore, dovremo assicurarci di installare questa libreria.
  2. La funzione get_args() definisce un argomento della riga di comando per ogni iperparametro da ottimizzare. In questo esempio, gli iperparametri che verranno ottimizzati sono il tasso di apprendimento, il valore momentum nell'ottimizzatore e il numero di unità nell'ultimo strato nascosto del modello, ma sperimenta pure con altri iperparametri. Il valore passato in questi argomenti viene quindi utilizzato per impostare l'iperparametro corrispondente nel codice (ad es. imposta learning_rate = args.learning_rate)
  3. Alla fine della funzione main(), la libreria hypertune viene utilizzata per definire la metrica che vuoi ottimizzare. In TensorFlow, il metodo Keras model.fit restituisce un oggetto History. L'attributo History.history è un record dei valori di perdita dell'addestramento e dei valori delle metriche in epoche successive. Se passi i dati di convalida a model.fit, l'attributo model.fit includerà anche la perdita di convalida e i valori delle metriche.History.history Ad esempio, se addestrassi un modello per tre epoche con dati di convalida e fornissi accuracy come metrica, l'attributo History.history sarebbe simile al seguente dizionario.
{
 "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
 ]

Se vuoi che il servizio di ottimizzazione degli iperparametri scopra i valori che massimizzano l'accuratezza di convalida del modello, definisci la metrica come l'ultima voce (o NUM_EPOCS - 1) dell'elenco val_accuracy. Quindi, passa questa metrica a un'istanza di HyperTune. Puoi scegliere la stringa che preferisci per hyperparameter_metric_tag, ma dovrai utilizzare nuovamente la stringa in seguito quando avvierai il job di ottimizzazione degli iperparametri.

6. Containerizza il codice

Il primo passaggio per containerizzare il codice è creare un Dockerfile. Nel Dockerfile includerai tutti i comandi necessari per eseguire l'immagine. Installerà tutte le librerie necessarie e configurerà il punto di accesso per il codice di addestramento.

Passaggio 1: scrivi Dockerfile

Dal tuo terminale, assicurati di essere nella directory vertex-codelab e crea un Dockerfile vuoto:

touch Dockerfile

Ora dovresti avere quanto segue nella tua directory vertex-codelab:

+ Dockerfile
+ trainer/
    + task.py

Apri il Dockerfile e copia al suo interno quanto segue:

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

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"]

Questo Dockerfile utilizza l'immagine Docker GPU TensorFlow Enterprise 2.7 del container di deep learning. Deep Learning Containers su Google Cloud è dotato di molti framework comuni di ML e data science preinstallati. Dopo aver scaricato l'immagine, questo Dockerfile configura il punto di accesso per il codice di addestramento.

Passaggio 2: crea il container

Dal tuo terminale, esegui quanto segue per definire una variabile env per il tuo progetto, assicurandoti di sostituire your-cloud-project con l'ID del tuo progetto:

PROJECT_ID='your-cloud-project'

Definisci una variabile con l'URI dell'immagine container in Google Container Registry:

IMAGE_URI="gcr.io/$PROJECT_ID/horse-human-codelab:latest"

Configura Docker

gcloud auth configure-docker

Quindi, crea il container eseguendo quanto segue dalla radice della directory vertex-codelab:

docker build ./ -t $IMAGE_URI

Infine, invialo a Google Container Registry:

docker push $IMAGE_URI

Passaggio 3: crea un bucket Cloud Storage

Nel nostro job di addestramento, passeremo il percorso a un bucket temporaneo.

Esegui questo comando nel terminale per creare un nuovo bucket nel tuo progetto.

BUCKET_NAME="gs://${PROJECT_ID}-hptune-bucket"
gsutil mb -l us-central1 $BUCKET_NAME

7. Avvia il job di ottimizzazione degli iperparametri

Passaggio 1: crea un job di addestramento personalizzato con ottimizzazione degli iperparametri

Da Avvio app, apri un nuovo blocco note TensorFlow 2.

new_notebook

Importa l'SDK Vertex AI per Python.

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

Per avviare il job di ottimizzazione degli iperparametri, devi prima definire worker_pool_specs, che specifica il tipo di macchina e l'immagine Docker. La specifica seguente definisce una macchina con due GPU NVIDIA Tesla V100.

Dovrai sostituire {PROJECT_ID} in image_uri con il tuo progetto.

# 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": 2
    },
    "replica_count": 1,
    "container_spec": {
        "image_uri": "gcr.io/{PROJECT_ID}/horse-human-codelab:latest"
    }
}]

Successivamente, definisci parameter_spec, un dizionario che specifica i parametri da ottimizzare. La chiave del dizionario è la stringa che hai assegnato all'argomento della riga di comando per ogni iperparametro e il valore del dizionario è la specifica del parametro.

Per ogni iperparametro, devi definire il tipo e i limiti per i valori che verranno provati dal servizio di ottimizzazione. Gli iperparametri possono essere di tipo Double, Integer, Categorical o Discrete. Se selezioni il tipo Doppio o Numero intero, devi fornire un valore minimo e massimo. Se selezioni Categorico o Discreto, dovrai fornire i valori. Per i tipi Doppio e Numero intero, devi specificare anche il valore di scala. Puoi scoprire di più su come scegliere la scala migliore in questo video.

# 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 specifica finale da definire è metric_spec, un dizionario che rappresenta la metrica da ottimizzare. La chiave del dizionario è hyperparameter_metric_tag impostata nel codice dell'applicazione di addestramento e il valore è l'obiettivo di ottimizzazione.

# Dicionary representing metrics 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'}

Una volta definite le specifiche, dovrai creare un file CustomJob, ovvero la specifica comune che verrà utilizzata per eseguire il job in ogni prova di ottimizzazione dell'iperparametro.

Dovrai sostituire {YOUR_BUCKET} con il bucket che hai creato in precedenza.

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

Quindi, crea ed esegui il HyperparameterTuningJob.

hp_job = aiplatform.HyperparameterTuningJob(
    display_name='horses-humans',
    custom_job=my_custom_job,
    metric_spec=metric_spec,
    parameter_spec=parameter_spec,
    max_trial_count=6,
    parallel_trial_count=2,
    search_algorithm=None)

hp_job.run()

Esistono alcuni argomenti da tenere presente:

  • max_trial_count: dovrai impostare un limite superiore al numero di prove che il servizio eseguirà. Un numero maggiore di prove generalmente porta a risultati migliori, ma ci sarà un punto in cui i risultati diminuiranno dopo il quale ulteriori prove avranno poco o nessun effetto sulla metrica che stai cercando di ottimizzare. Una best practice è iniziare con un numero inferiore di prove e farsi un'idea dell'impatto degli iperparametri scelti prima dello scale up.
  • parallel_trial_count: se utilizzi prove parallele, il servizio esegue il provisioning di più cluster di elaborazione dell'addestramento. L'aumento del numero di prove parallele riduce la quantità di tempo necessaria per l'esecuzione del job di ottimizzazione degli iperparametri; tuttavia, può ridurre l'efficacia del job nel complesso. Questo perché la strategia di ottimizzazione predefinita utilizza i risultati delle prove precedenti per informare l'assegnazione dei valori nelle prove successive.
  • algoritmo_ricerca: puoi impostare l'algoritmo di ricerca su griglia, casuale o predefinito (Nessuno). L'opzione predefinita applica l'ottimizzazione bayesiana per cercare nello spazio dei possibili valori degli iperparametri ed è l'algoritmo consigliato. Puoi trovare ulteriori informazioni su questo algoritmo qui.

Una volta avviato il job, potrai monitorarne lo stato nell'interfaccia utente nella scheda JOB DI OTTIMIZZAZIONE DEGLI IPERPARAMETRI.

HP_job

Al termine del job, puoi visualizzare e ordinare i risultati delle prove per scoprire la migliore combinazione di valori degli iperparametri.

HP_results

🎉 Complimenti! 🎉

Hai imparato come utilizzare Vertex AI per:

  • Esegui un job di ottimizzazione degli iperparametri con l'addestramento distribuito

Per scoprire di più sulle diverse parti di Vertex AI, consulta la documentazione.

8. Esegui la pulizia

Poiché abbiamo configurato il notebook in modo che termini il timeout dopo 60 minuti di inattività, non dobbiamo preoccuparci di arrestare l'istanza. Se vuoi arrestare manualmente l'istanza, fai clic sul pulsante Interrompi nella sezione Vertex AI Workbench della console. Se vuoi eliminare completamente il blocco note, fai clic sul pulsante Elimina.

elimina

Per eliminare il bucket di archiviazione, utilizza il menu di navigazione nella console Cloud, vai a Archiviazione, seleziona il bucket e fai clic su Elimina:

Eliminare lo spazio di archiviazione