Prototipazione alla produzione: ottimizzazione degli iperparametri

1. Panoramica

In questo lab utilizzerai Vertex AI per eseguire un job di ottimizzazione degli iperparametri su Vertex AI Training.

Questo lab fa parte della serie di video Prototype to Production (Dal prototipo alla produzione). Assicurati di completare il lab precedente prima di provare questo. Per saperne di più, puoi guardare la serie di video di accompagnamento:

.

Cosa imparerai

Al termine del corso sarai in grado di:

  • Modificare il codice dell'applicazione di addestramento per l'ottimizzazione automatica degli iperparametri
  • Configura e avvia un job di ottimizzazione degli iperparametri con l'SDK Vertex AI Python

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

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.

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

Panoramica dei prodotti Vertex

3. Configura l'ambiente

Completa i passaggi nel lab Addestramento di modelli personalizzati con Vertex AI per configurare il tuo ambiente.

4. containerizza il codice dell'applicazione di addestramento

Invierai questo job di addestramento a Vertex AI inserendo il codice dell'applicazione di addestramento in un container Docker ed eseguendo il push di questo container in Google Artifact Registry. Utilizzando questo approccio, puoi addestrare e ottimizzare un modello creato con qualsiasi framework.

Per iniziare, apri una finestra del terminale dal menu Avvio app del blocco note di Workbench creato nei lab precedenti.

Aprire il terminale nel notebook

Passaggio 1: scrivi il codice di addestramento

Crea una nuova directory denominata flowers-hptune e accedi tramite cd:

mkdir flowers-hptune
cd flowers-hptune

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

mkdir trainer
touch trainer/task.py

Ora dovresti avere quanto segue nella tua directory flowers-hptune/:

+ trainer/
    + task.py

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

Dovrai sostituire {your-gcs-bucket} in BUCKET_ROOT con il bucket Cloud Storage in cui hai archiviato il set di dati sui fiori nel lab 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()

Prima di creare il container, diamo uno sguardo più approfondito al codice. Esistono alcuni componenti specifici dell'utilizzo del servizio di ottimizzazione degli iperparametri.

  1. Lo script importa la libreria hypertune.
  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.
  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.

Passaggio 2: crea un Dockerfile

Per containerizzare il codice, devi 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.

Dal terminale, crea un Dockerfile vuoto nella directory principale flowers-hptune:

touch Dockerfile

Ora dovresti avere quanto segue nella tua directory flowers-hptune/:

+ Dockerfile
+ trainer/
    + task.py

Apri il Dockerfile e copia al suo interno quanto segue. Noterai che è quasi identico al Dockerfile utilizzato nel primo lab, tranne per il fatto che ora stiamo installando la libreria cloudml-hypertune.

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

Passaggio 3: crea il contenitore

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 un repository in Artifact Registry. Utilizzeremo il repository creato nel primo lab.

REPO_NAME='flower-app'

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

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

Configura Docker

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

Quindi, crea il container eseguendo quanto segue dalla radice della directory flower-hptune:

docker build ./ -t $IMAGE_URI

Infine, esegui il push ad Artifact Registry:

docker push $IMAGE_URI

Dopo il push del container in Artifact Registry, ora puoi avviare un job di addestramento.

5. Esegui il job di ottimizzazione degli iperparametri con l'SDK

In questa sezione imparerai a configurare e inviare il job di ottimizzazione degli iperparametri utilizzando l'API Python di Vertex.

Da Avvio app, crea un notebook TensorFlow 2.

new_notebook

Importa l'SDK Vertex AI.

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 seguente specifica 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": 1
    },
    "replica_count": 1,
    "container_spec": {
        "image_uri": "us-central1-docker.pkg.dev/{PROJECT_ID}/flower-app/flower_image_hptune: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 Categorial o Discrete, dovrai fornire i valori. Per i tipi Double e Integer, dovrai anche fornire il valore Scaling. Scopri 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, ovvero 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.

# 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'}

Una volta definite le specifiche, dovrai creare un CustomJob, ovvero la specifica comune che verrà utilizzata per eseguire il job in ciascuna delle prove 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='flowers-hptune-job',
                              worker_pool_specs=worker_pool_specs,
                              staging_bucket='gs://{YOUR_BUCKET}')

Quindi, crea ed esegui il 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()

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.
  • search_algorithm: 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 scoprire di più su questo algoritmo qui.

Nella console potrai visualizzare l'avanzamento del job.

hp_job

Al termine, puoi visualizzare i risultati di ogni prova e l'insieme di valori con il rendimento migliore.

hp_results

🎉 Complimenti! 🎉

Hai imparato come utilizzare Vertex AI per:

  • Esegui un job di ottimizzazione iperparametri automatica

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

6. Esegui la pulizia

Poiché abbiamo configurato il timeout del blocco note 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.

Arresta istanza

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