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.
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:
- Defina um argumento de linha de comando no módulo de treinamento principal para cada hiperparâmetro que você quer ajustar.
- 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.
Etapa 4: criar uma instância do Vertex AI Workbench
Na seção Vertex AI do Console do Cloud, clique em "Workbench":
Ative a API Notebooks, se ela ainda não tiver sido ativada.
Após a ativação, clique em NOTEBOOK GERENCIADO:
Em seguida, selecione NOVO NOTEBOOK.
Dê um nome ao notebook e clique em Configurações avançadas.
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.
Em "Segurança", selecione "Ativar terminal", se essa opção ainda não estiver ativada.
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.
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.
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:
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
- Na função
main()
, o objetoMirroredStrategy
é 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. - 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
- O script importa a biblioteca
hypertune
. Mais tarde, quando criarmos a imagem do contêiner, vamos garantir que essa biblioteca seja instalada. - 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, definalearning_rate = args.learning_rate
). - Ao final da função
main()
, a bibliotecahypertune
é usada para definir a métrica a ser otimizada. No TensorFlow, o métodomodel.fit
da Keras retorna um objetoHistory
. O atributoHistory.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 paramodel.fit
, o atributoHistory.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 informaraccuracy
como a métrica, o atributoHistory.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.
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.
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.
🎉 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".
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":