Protótipo para produção: ajuste de hiperparâmetro

1. Visão geral

Neste laboratório, você vai usar a Vertex AI para executar um ajuste de hiperparâmetro no Vertex AI Training.

Este laboratório é parte da série de vídeos Protótipo para produção. Finalize o laboratório anterior antes de tentar este. Para saber mais, confira a série de vídeos complementar:

.

Conteúdo do laboratório

Você vai aprender a:

  • modificar o código do aplicativo de treinamento para ajuste do hiperparâmetro automatizado;
  • configurar e iniciar um job de ajuste de hiperparâmetro com o SDK da Vertex AI para Python.

O custo total da execução deste laboratório no Google Cloud é de aproximadamente US$ 1.

2. Introdução à Vertex AI

Este laboratório usa a mais nova oferta de produtos de IA disponível no Google Cloud. A Vertex AI integra as ofertas de ML do Google Cloud em uma experiência de desenvolvimento intuitiva. Anteriormente, modelos treinados com o AutoML e modelos personalizados eram acessíveis por serviços separados. A nova oferta combina ambos em uma única API, com outros novos produtos. Você também pode migrar projetos para a Vertex AI.

A Vertex AI inclui vários produtos diferentes para dar suporte a fluxos de trabalho integrais de ML. Os produtos destacados abaixo são o foco deste laboratório: Treinamentos e Workbench.

Visão geral do produto Vertex

3. Configurar o ambiente

Finalize as etapas no laboratório Como treinar modelos personalizados com a Vertex AI para configurar seu ambiente:

4. Faça a conteinerização do código do aplicativo de treinamento

Você vai enviar este job de treinamento para a Vertex AI adicionado o código do aplicativo de treinamento a um contêiner do Docker e enviando esse contêiner por push para o Google Artifact Registry. Com esta abordagem, você pode treinar e ajustar um modelo criado com qualquer framework.

Para começar, no menu de acesso rápido do notebook do Workbench que você criou nos laboratórios anteriores, abra uma janela do terminal.

Abrir o terminal no notebook

Etapa 1: escrever código de treinamento

Crie um novo diretório chamado flowers-hptune e coloque cd nele:

mkdir flowers-hptune
cd flowers-hptune

Execute o comando a seguir para criar um diretório destinado ao código de treinamento e a um arquivo Python em que você vai adicionar o código abaixo:

mkdir trainer
touch trainer/task.py

Agora você deve ter o seguinte no diretório flowers-hptune/:

+ trainer/
    + task.py

Depois abra o arquivo task.py que você acabou de criar e copie o código abaixo.

Você vai precisar substituir {your-gcs-bucket} em BUCKET_ROOT pelo bucket do Cloud Storage em que o conjunto de dados de flores está armazenado no laboratório 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 criar o contêiner, vamos analisar o código mais a fundo. Existem alguns componentes específicos para usar o serviço de ajuste de hiperparâmetros.

  1. O script importa a biblioteca hypertune.
  2. A função get_args() define um argumento de linha de comando para cada hiperparâmetro a ser ajustado. No exemplo, os hiperparâmetros que serão ajustados são a taxa de aprendizado, o valor do momentum no optimizer e o número de unidades na última camada escondida do modelo, mas fique à vontade para testar outros. O valor transferido nesses argumentos é usado para definir o hiperparâmetro correspondente no código.
  3. Ao final da função main(), a biblioteca hypertune é usada para definir a métrica a ser otimizada. No TensorFlow, o método model.fit da Keras retorna um objeto History. O atributo History.history é um registro de valores de perda de treinamento e de valores de métricas em épocas sucessivas. Se você transmitir os dados de validação para model.fit, o atributo History.history vai incluir também valores de perda de validação e de métricas. Por exemplo, se você treinar um modelo para três épocas com dados de validação e informar accuracy como a métrica, o atributo History.history será semelhante ao dicionário a seguir.
{
 "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 você quiser que o serviço de ajuste de hiperparâmetros descubra os valores que maximizam a acurácia da validação do modelo, defina a métrica como a última entrada (ou NUM_EPOCS - 1) da lista val_accuracy. Em seguida, transfira essa métrica para uma instância do HyperTune. É possível escolher qualquer string para o argumento hyperparameter_metric_tag, mas será preciso usar a string novamente, quando você iniciar o job de ajuste de hiperparâmetros.

Etapa 2: criar um Dockerfile

Para conteinerizar seu código, você precisa criar um Dockerfile. No Dockerfile, você vai incluir todos os comandos necessários para executar a imagem. Ele vai instalar todas as bibliotecas necessárias e configurar o ponto de entrada do código de treinamento.

No seu terminal, crie um Dockerfile vazio na raiz do diretório de flowers-hptune.

touch Dockerfile

Agora você deve ter o seguinte no diretório flowers-hptune/:

+ Dockerfile
+ trainer/
    + task.py

Abra o Dockerfile e copie o seguinte código nele. Você vai perceber que o Dockerfile é idêntico ao usado no primeiro laboratório, só que agora estamos instalando a biblioteca 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"]

Etapa 3: criar o contêiner

No Terminal, execute o comando a seguir e defina uma variável env para o projeto. Lembre-se de substituir your-cloud-project pelo ID do projeto.

PROJECT_ID='your-cloud-project'

Defina um repositório no Artifact Registry. Vamos usar o que criamos no primeiro laboratório.

REPO_NAME='flower-app'

Defina uma variável com o URI da imagem do seu contêiner no Google Artifact Registry.

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

Configure o Docker.

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

Agora execute o comando a seguir na raiz do diretório flower-hptune para criar o diretório:

docker build ./ -t $IMAGE_URI

Por fim, envie por push para o Artifact Registry:

docker push $IMAGE_URI

Com o contêiner enviado por push para o Artifact Registry, agora está tudo pronto para você iniciar o job de treinamento.

5. Executar o job de ajuste de hiperparâmetro com o SDK

Nesta seção, você vai aprender a configurar e enviar o job de ajuste de hiperparâmetro usando a API Vertex Python.

Na tela de início, crie um notebook do TensorFlow 2.

new_notebook

Importe o SDK da Vertex AI.

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

Para iniciar o job de ajuste de hiperparâmetros, primeiro defina o worker_pool_specs, que especifica o tipo de máquina e a imagem do Docker. A especificação a seguir define uma máquina com duas GPUs NVIDIA Tesla V100.

Será necessário substituir {PROJECT_ID} no image_uri pelo seu projeto.

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

Em seguida, defina o parameter_spec, que é um dicionário que especifica os parâmetros que você quer otimizar. A chave de dicionário é a string atribuída ao argumento de linha de comando para cada hiperparâmetro e o valor do dicionário é a especificação de parâmetros.

Para cada hiperparâmetro, você precisa definir o tipo e os limites dos valores que o serviço de ajuste vai tentar. Os hiperparâmetros podem ser do tipo duplo, inteiro, categórico ou discreto. Se você selecionar o tipo "Duplo" ou "Inteiro", vai precisar fornecer um valor mínimo e máximo. E, se você selecionar "Categórico" ou "Discreto", vai ter que fornecer os valores. Para os tipos duplo e inteiro, você também precisa fornecer o valor de escalonamento. Saiba como escolher o melhor escalonamento neste vídeo

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

A especificação final a ser definida é metric_spec, que é um dicionário que representa a métrica a ser otimizada. A chave de dicionário é o hyperparameter_metric_tag que você definiu no código do aplicativo de treinamento, e o valor é a meta de otimização.

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

Depois de definir as especificações, crie um CustomJob, que é a especificação comum a ser usada para executar o job em cada um dos testes de ajuste de hiperparâmetro.

Será necessário substituir {YOUR_BUCKET} pelo bucket que você criou 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}')

Em seguida, crie e execute o 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()

Há alguns argumentos a serem observados:

  • max_trial_count: será necessário estabelecer um limite para o número de testes que o serviço vai executar. Um número maior de testes geralmente leva a melhores resultados, mas haverá um ponto com retornos decrescentes. Depois dele, a execução de mais testes terá pouco ou nenhum efeito na métrica a ser otimizada. Uma prática recomendada é começar com um número menor de testes para ter noção do impacto dos hiperparâmetros escolhidos, antes de escalonar verticalmente.
  • parallel_trial_count: se você usa testes em paralelo, o serviço provisiona vários clusters de processamento de treinamento. Aumentar o número de testes paralelos reduz o tempo necessário para a execução do job de ajuste de hiperparâmetros, mas isso pode reduzir a eficácia do job em geral. Isso ocorre devido à estratégia de ajuste padrão que usa os resultados de testes anteriores para informar a atribuição de valores em testes seguintes.
  • search_algorithm: você pode definir o algoritmo de pesquisa como grade, aleatório ou padrão (nenhum). A opção padrão aplica a otimização bayesiana para pesquisar o espaço de possíveis valores de hiperparâmetros e é o algoritmo recomendado. Saiba mais sobre esse algoritmo aqui.

No console, você pode acompanhar o progresso do job.

hp_job

Quando ele termina, você pode acessar os resultados de cada teste e saber qual conjunto de valores teve o melhor desempenho.

hp_results

Parabéns! 🎉

Você aprendeu a usar a Vertex AI para:

  • Executar um job de ajuste de hiperparâmetro automatizado.

Para saber mais sobre as diferentes partes da Vertex, consulte a documentação.

6. Limpeza

Como configuramos o notebook para expirar após 60 minutos de inatividade, não precisamos nos preocupar em desligar a instância. Para encerrar a instância manualmente, clique no botão "Parar" na seção "Vertex AI Workbench" do console. Se quiser excluir o notebook completamente, clique no botão "Excluir".

Interromper instância

Para excluir o bucket do Storage, use o menu de navegação do console do Cloud, acesse o Storage, selecione o bucket e clique em "Excluir":

Excluir armazenamento