Vertex AI: ajuste de hiperparâmetros distribuído

1. Visão geral

Neste laboratório, você vai aprender a usar a Vertex AI para realizar o ajuste de hiperparâmetros e o treinamento distribuído. Embora este laboratório use o TensorFlow para o código do modelo, os conceitos também são aplicáveis a outros frameworks de ML.

Conteúdo do laboratório

Você vai aprender como:

  • Treinar um modelo usando treinamento distribuído em um contêiner personalizado
  • Lançar vários testes do código de treinamento para o ajuste automático de hiperparâmetros

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

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 os dois produtos com produtos novos em uma API única. Também é possível migrar projetos para a Vertex AI. Se você quiser enviar um feedback, acesse a página de suporte.

A Vertex AI tem vários produtos diferentes incluídos para dar suporte a fluxos de trabalho integrais de ML. O foco deste laboratório é o Treinamento e o Workbench.

Visão geral do produto Vertex

3. Visão geral do caso de uso

Neste laboratório, você vai usar o ajuste de hiperparâmetros para descobrir parâmetros ideais para um modelo de classificação de imagens treinado no conjunto de dados de cavalos ou humanos usando os conjuntos de dados do TensorFlow.

Ajuste de hiperparâmetros

O ajuste de hiperparâmetros com treinamento da Vertex AI executa diversos testes do aplicativo de treinamento com valores para os hiperparâmetros escolhidos, definidos dentro dos limites especificados. A Vertex AI rastreia os resultados de cada teste e faz ajustes nas avaliações subsequentes.

Para usar o ajuste de hiperparâmetros com o treinamento da Vertex AI, é necessário fazer duas alterações no código de treinamento:

  1. Defina um argumento de linha de comando no módulo de treinamento principal para cada hiperparâmetro que você quer ajustar.
  2. Use o valor passado nesses argumentos para definir o hiperparâmetro correspondente no código do aplicativo.

Treinamento distribuído

Se você tiver apenas uma GPU, o TensorFlow vai usar esse acelerador no treinamento do modelo sem nenhum trabalho extra da sua parte. No entanto, se você quiser aumentar ainda mais o uso de várias GPUs, será necessário usar o tf.distribute, que é o módulo do TensorFlow para executar computação em vários dispositivos.

Este laboratório usa tf.distribute.MirroredStrategy, que você pode adicionar aos seus aplicativos de treinamento com apenas algumas mudanças no código. Essa estratégia cria uma cópia do modelo em cada GPU na máquina. As próximas atualizações de gradientes ocorrerão de maneira síncrona. Isso significa que cada GPU calcula as transmissões anteriores e posteriores do modelo em uma fração diferente dos dados de entrada. Os gradientes calculados de cada uma dessas fatias são agregados em todas as GPUs e ponderados em um processo conhecido como all-reduce. Os parâmetros do modelo são atualizados com esses gradientes médios.

Você não precisa saber os detalhes para concluir este laboratório, mas, se quiser saber mais sobre como o treinamento distribuído funciona no TensorFlow, confira o vídeo abaixo:

4. Configurar o ambiente

Para executar este codelab, você vai precisar de um projeto do Google Cloud Platform com o faturamento ativado. Para criar um projeto, siga estas instruções.

Etapa 1: ativar a API Compute Engine

Acesse o Compute Engine e selecione Ativar, caso essa opção ainda não esteja ativada.

Etapa 2: ativar a API Container Registry

Navegue até o Container Registry e selecione Ativar. Use isso para criar um contêiner para seu job de treinamento personalizado.

Etapa 3: ativar a API Vertex AI

Navegue até a seção "Vertex AI" do Console do Cloud e clique em Ativar API Vertex AI.

Painel da Vertex AI

Etapa 4: criar uma instância do Vertex AI Workbench

Na seção Vertex AI do Console do Cloud, clique em "Workbench":

Menu da Vertex AI

Ative a API Notebooks, se ela ainda não tiver sido ativada.

Notebook_api

Após a ativação, clique em NOTEBOOK GERENCIADO:

Notebooks_UI

Em seguida, selecione NOVO NOTEBOOK.

new_notebook

Dê um nome ao notebook e clique em Configurações avançadas.

create_notebook

Em "Configurações avançadas", ative o encerramento inativo e defina o número de minutos como 60. Isso significa que o notebook será desligado automaticamente quando não estiver sendo usado.

idle_timeout

Em "Segurança", selecione "Ativar terminal", se essa opção ainda não estiver ativada.

enable-terminal

Você pode manter as outras configurações avançadas como estão.

Em seguida, clique em Criar. O provisionamento da instância vai levar alguns minutos.

Quando a instância tiver sido criada, selecione Abrir o JupyterLab.

open_jupyterlab

Na primeira vez que usar uma nova instância, você vai receber uma solicitação de autenticação. Siga as etapas na IU para isso.

autenticar

5. Escrever o código de treinamento

Para começar, no menu de acesso rápido, abra uma janela de terminal na instância do notebook:

launcher_terminal

Crie um novo diretório chamado vertex-codelab e coloque cd nele.

mkdir vertex-codelab
cd vertex-codelab

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:

mkdir trainer
touch trainer/task.py

Agora você deve ter o seguinte no diretório vertex-codelab:

+ trainer/
    + task.py

Em seguida, abra o arquivo task.py que você acabou de criar e cole todo o código abaixo.

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()

Vamos analisar o código mais detalhadamente e examinar os componentes específicos do treinamento distribuído e do ajuste de hiperparâmetros.

Treinamento distribuído

  1. Na função main(), o objeto MirroredStrategy é criado. Depois, envolva a criação das variáveis de modelo no escopo da estratégia. Esta etapa informa ao TensorFlow quais variáveis precisam ser espelhadas nas GPUs.
  2. O tamanho do lote é escalonado verticalmente pelo num_replicas_in_sync. O escalonamento do tamanho do lote é uma prática recomendada ao usar estratégias de paralelismo síncrono de dados no TensorFlow. Saiba mais neste link.

Ajuste de hiperparâmetros

  1. O script importa a biblioteca hypertune. Mais tarde, quando criarmos a imagem do contêiner, vamos garantir que essa biblioteca seja instalada.
  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 (por exemplo, defina learning_rate = args.learning_rate).
  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.

6. Conteinerizar o código

A primeira etapa na conteinerização do seu código é criar um Dockerfile. Nesse Dockerfile, você vai incluir todos os comandos necessários à execução da imagem. Ele vai instalar todas as bibliotecas necessárias e configurar o ponto de entrada do código de treinamento.

Etapa 1: gravar o Dockerfile

No terminal, verifique se você está no diretório vertex-codelab e crie um Dockerfile vazio:

touch Dockerfile

Agora você deve ter o seguinte no diretório vertex-codelab:

+ Dockerfile
+ trainer/
    + task.py

Abra o Dockerfile e copie o seguinte nele:

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

Este Dockerfile usa a imagem do Docker do TensorFlow Enterprise 2.7 GPU no Deep Learning Container. O componente Deep Learning Containers no Google Cloud vem com vários frameworks de ciência de dados já instalados. Após fazer o download da imagem, este Dockerfile configura o ponto de entrada do código de treinamento.

Etapa 2: 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 a variável com o URI da imagem do seu contêiner no Google Container Registry:

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

Configurar Docker

gcloud auth configure-docker

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

docker build ./ -t $IMAGE_URI

Envio-o para o Google Container Registry:

docker push $IMAGE_URI

Etapa 3: criar um bucket do Cloud Storage

No job de treinamento, transmitiremos o caminho para um bucket de preparo.

Execute o comando a seguir no Terminal para criar um novo bucket no projeto:

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

7. Iniciar job de ajuste de hiperparâmetros

Etapa 1: criar um job de treinamento personalizado com o ajuste de hiperparâmetros

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

new_notebook

Importe o SDK da Vertex AI para Python.

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": 2
    },
    "replica_count": 1,
    "container_spec": {
        "image_uri": "gcr.io/{PROJECT_ID}/horse-human-codelab: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 mais sobre como escolher a melhor escala 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.

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

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

Será necessário substituir {YOUR_BUCKET} pelo bucket que você criou anteriormente.

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

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

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.
  • paralelo_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 para 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.

Depois que o job for iniciado, será possível acompanhar o status na IU, na guia JOBS DE AJUSTE DE HIPERPARÂMETRO.

HP_job

Depois que o job for concluído, será possível visualizar e classificar os resultados dos testes para descobrir a melhor combinação de valores de hiperparâmetros.

HP_results

🎉 Parabéns! 🎉

Você aprendeu como usar a Vertex AI para:

  • Executar um job de ajuste de hiperparâmetros com treinamento distribuído

Para saber mais sobre partes diferentes da Vertex AI, acesse a documentação.

8. 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".

excluir

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