Del prototipo a la producción: Ajuste de hiperparámetros

1. Descripción general

En este lab, usarás Vertex AI para ejecutar un trabajo de ajuste de hiperparámetros en Vertex AI Training.

Este lab forma parte de la serie de videos Del prototipo a la producción. Asegúrate de completar los labs anteriores antes de realizar este. Puedes mirar la serie de videos complementaria para obtener más información:

.

Qué aprenderás

Aprenderás a hacer lo siguiente:

  • Modificar el código de la aplicación de entrenamiento para el ajuste automatizado de hiperparámetros
  • Configurar y, luego, iniciar un trabajo de ajuste de hiperparámetros con el SDK de Vertex AI para Python

El costo total de la ejecución de este lab en Google Cloud es de aproximadamente $1.

2. Introducción a Vertex AI

En este lab, se utiliza la oferta de productos de IA más reciente de Google Cloud. Vertex AI integra las ofertas de AA de Google Cloud en una experiencia de desarrollo fluida. Anteriormente, se podía acceder a los modelos personalizados y a los entrenados con AutoML mediante servicios independientes. La nueva oferta combina ambos en una sola API, junto con otros productos nuevos. También puedes migrar proyectos existentes a Vertex AI.

Vertex AI incluye muchos productos distintos para respaldar flujos de trabajo de AA de extremo a extremo. Este lab se enfocará en los productos que se destacan a continuación: Training y Workbench

Descripción general del producto Vertex

3. Configura tu entorno

Completa los pasos en el lab Entrenamiento de modelos personalizados con Vertex AI para configurar tu entorno.

4. Aloja en un contenedor el código de la aplicación de entrenamiento

Para enviar este trabajo de entrenamiento a Vertex AI, deberás colocar el código de la aplicación de entrenamiento en un contenedor de Docker y enviar el contenedor a Google Artifact Registry. Mediante este enfoque, puedes entrenar y ajustar un modelo compilado con cualquier framework.

Para comenzar, en el menú Selector (Launcher) del notebook de Workbench que creaste en los labs anteriores, abre una ventana de terminal.

Abrir terminal en el notebook

Paso 1: Escribe el código de entrenamiento

Crea un directorio nuevo llamado flowers-hptune y ábrelo con el comando cd:

mkdir flowers-hptune
cd flowers-hptune

Ejecuta el siguiente comando para crear un directorio para el código de entrenamiento y un archivo de Python en el que agregarás el siguiente código.

mkdir trainer
touch trainer/task.py

Ahora, deberías tener lo siguiente en el directorio flowers-hptune/:

+ trainer/
    + task.py

A continuación, abre el archivo task.py que acabas de crear y copia el código que aparece más abajo.

Deberás reemplazar {your-gcs-bucket} en BUCKET_ROOT por el bucket de Cloud Storage en el que almacenaste el conjunto de datos de flores en el 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()

Antes de crear el contenedor, analicemos el código en detalle. Hay algunos componentes que son específicos para el uso del servicio de ajuste de hiperparámetros.

  1. La secuencia de comandos importa la biblioteca hypertune.
  2. La función get_args() define un argumento de la línea de comandos para cada hiperparámetro que deseas ajustar. En este ejemplo, los hiperparámetros que se ajustarán son la tasa de aprendizaje, el valor de impulso en el optimizador y la cantidad de unidades en la última capa oculta del modelo, pero puedes experimentar con otros. El valor que se pasa en dichos argumentos luego se usa para configurar el hiperparámetro correspondiente en el código.
  3. Al final de la función main(), se usa la biblioteca hypertune para definir la métrica que quieres optimizar. En TensorFlow, el método model.fit de Keras muestra un objeto History. El atributo History.history es un registro de los valores de pérdida de entrenamiento y de valores de métricas en ciclos sucesivos. Si pasas datos de validación a model.fit, el atributo History.history también incluirá valores de métricas y pérdida de validación. Por ejemplo, si entrenaste un modelo durante tres ciclos con datos de validación y proporcionaste accuracy como métrica, el atributo History.history se verá como el siguiente diccionario.
{
 "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
 ]

Si quieres que el servicio de ajuste de hiperparámetros descubra qué valores maximizan la exactitud de la validación del modelo, debe definir la métrica como la última entrada (o NUM_EPOCS - 1) de la lista val_accuracy. Luego, pasa esta métrica a una instancia de HyperTune. Puedes elegir la cadena que desees para hyperparameter_metric_tag, pero deberás volver a usarla más adelante cuando inicies el trabajo de ajuste de hiperparámetros.

Paso 2: Crea un Dockerfile

Para alojar tu código en contenedores, deberás crear un Dockerfile. En él, incluirás todos los comandos necesarios para ejecutar la imagen. Se instalarán todas las bibliotecas necesarias y se configurará el punto de entrada para el código de entrenamiento.

En la terminal, crea un Dockerfile vacío en la raíz del directorio flowers-hptune:

touch Dockerfile

Ahora, deberías tener lo siguiente en el directorio flowers-hptune/:

+ Dockerfile
+ trainer/
    + task.py

Abre el Dockerfile y copia lo siguiente. Notarás que esto es casi idéntico al Dockerfile que usamos en el primer lab, con la excepción de que ahora instalaremos la biblioteca de 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"]

Paso 3: Compila el contenedor

Desde tu terminal, ejecuta lo siguiente para definir una variable de entorno para tu proyecto y asegúrate de reemplazar your-cloud-project con el ID de tu proyecto:

PROJECT_ID='your-cloud-project'

Define un repositorio en Artifact Registry. Usaremos el repositorio que creamos en el primer lab.

REPO_NAME='flower-app'

Define una variable con el URI de la imagen de contenedor en Google Artifact Registry:

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

Configura el Docker.

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

Luego, ejecuta el siguiente comando para compilar el contenedor desde la raíz de tu directorio flower-hptune:

docker build ./ -t $IMAGE_URI

Por último, envíalo a Artifact Registry:

docker push $IMAGE_URI

Ahora que ya enviaste el contenedor a Artifact Registry, puedes iniciar el trabajo de entrenamiento.

5. Ejecuta un trabajo de ajuste de hiperparámetros con el SDK

En esta sección, aprenderás a configurar y enviar el trabajo de ajuste de hiperparámetros mediante la API de Vertex Python.

En el Selector (Launcher), crea un notebook de TensorFlow 2.

new_notebook

Importa el SDK de Vertex AI.

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

Para iniciar el trabajo de ajuste de hiperparámetros, primero debes definir el worker_pool_specs, que especifica el tipo de máquina y la imagen de Docker. En las siguientes especificaciones, se define una máquina con dos GPU NVIDIA Tesla V100.

Deberás reemplazar {PROJECT_ID} en el image_uri con tu proyecto.

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

A continuación, define parameter_spec, que es un diccionario que especifica los parámetros que deseas optimizar. La clave del diccionario es la string que asignaste al argumento de la línea de comandos para cada hiperparámetro, y el valor del diccionario es la especificación del parámetro.

En cada hiperparámetro, debes definir el tipo y los límites de los valores que intentará el servicio de ajuste. Los hiperparámetros pueden ser de tipo doble, de número entero, categóricos o discretos. Si seleccionas el tipo doble o número entero, deberás proporcionar un valor mínimo y uno máximo. Además, si seleccionas categórico o discreto, debes proporcionar los valores. Para los tipos doble y número entero, también deberás proporcionar el valor de escalamiento. Puedes obtener más información acerca de cómo elegir la mejor escala en este 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 especificación final que se debe definir es metric_spec, que es un diccionario que representa la métrica que se debe optimizar. La clave del diccionario es el hyperparameter_metric_tag que estableces en el código de tu aplicación de entrenamiento y el valor es el objetivo de optimización.

# 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 vez que se definan las especificaciones, crearás un CustomJob, que es la especificación común que se usará para ejecutar el trabajo en cada una de las pruebas de ajuste de hiperparámetros.

Deberás reemplazar {YOUR_BUCKET} por el bucket que creaste anteriormente.

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

Luego, crea y ejecuta 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()

Debes tener en cuenta los siguientes argumentos:

  • max_trial_count: Deberás establecer un límite superior para la cantidad de pruebas que se ejecutarán en el servicio. Si se realizan más pruebas, se obtendrán mejores resultados, pero habrá un punto en el que los beneficios disminuirán, y las pruebas adicionales a partir de ese punto tendrán un efecto nulo o muy limitado en la métrica que intentas optimizar. Como práctica recomendada, comienza con una cantidad más pequeña de pruebas para tener una idea del impacto que generan los hiperparámetros elegidos antes de escalar verticalmente.
  • paralelo_trial_count: Si usas pruebas paralelas, el servicio aprovisiona varios clústeres de procesamiento de entrenamiento. Si las aumentas, se reducirá el tiempo que el trabajo de ajuste de hiperparámetros tarda en ejecutarse; sin embargo, esto puede reducir su eficacia general. Esto se debe a que la estrategia de ajuste predeterminada utiliza los resultados de pruebas anteriores para asignar los valores en las pruebas posteriores.
  • search_algorithm: Puedes configurar el algoritmo de búsqueda como cuadrícula, aleatorio o predeterminado (ninguno). La opción predeterminada aplica la optimización bayesiana para buscar el espacio de valores de hiperparámetros posibles y es el algoritmo recomendado. Puedes obtener más información sobre este algoritmo aquí.

En la consola, podrás ver el progreso del trabajo.

hp_job

Cuando esté listo, podrás ver los resultados de cada prueba y los conjuntos de valores con el mejor rendimiento.

hp_results

🎉 ¡Felicitaciones! 🎉

Aprendiste a usar Vertex AI para hacer lo siguiente:

  • Ejecutar un trabajo automatizado de ajuste de hiperparámetros

Para obtener más información sobre las distintas partes de Vertex, consulta la documentación.

6. Realiza una limpieza

Debido a que configuramos el notebook para que se agote el tiempo de espera después de 60 minutos de inactividad, no tenemos que preocuparnos por cerrar la instancia. Si quieres cerrar la instancia de forma manual, haz clic en el botón Detener (Stop) en la sección Vertex AI Workbench de la consola. Si quieres borrar el notebook por completo, haz clic en el botón Borrar (Delete).

Detener instancias

Para borrar el bucket de almacenamiento, en el menú de navegación de la consola de Cloud, navega a Almacenamiento, selecciona tu bucket y haz clic en Borrar (Delete):

Borrar almacenamiento