对生产环境进行原型设计:超参数调节

1. 概览

在本实验中,您将使用 Vertex AI 在 Vertex AI Training 上运行超参数调节作业。

本实验是对生产环境进行原型设计视频系列的一部分。请务必先完成上一个实验,然后再尝试此实验。您可以观看随附的视频系列以了解详情:

学习内容

您将了解如何:

  • 修改训练应用代码以自动进行超参数调节
  • 使用 Vertex AI Python SDK 配置并启动超参数调节作业

在 Google Cloud 上运行此实验的总费用约为 1 美元

2. Vertex AI 简介

本实验使用的是 Google Cloud 上提供的最新 AI 产品。Vertex AI 将整个 Google Cloud 的机器学习产品集成到无缝的开发体验中。以前,使用 AutoML 训练的模型和自定义模型是通过不同的服务访问的。现在,该新产品与其他新产品一起将这两种模型合并到一个 API 中。您还可以将现有项目迁移到 Vertex AI。

Vertex AI 包含许多不同的产品,可支持端到端机器学习工作流。本实验将重点介绍下面突出显示的产品:TrainingWorkbench

Vertex 产品概览

3. 设置您的环境

完成使用 Vertex AI 训练自定义模型实验中的步骤以设置您的环境。

4. 容器化训练应用代码

如需将此训练作业提交到 Vertex AI,您可以将训练应用代码放在 Docker 容器中,然后将此容器推送到 Google Artifact Registry。使用此方法,您可以训练和调整使用任何框架构建的模型。

首先,通过您在之前的实验中创建的 Workbench 笔记本的“启动器”菜单,打开一个终端窗口。

在笔记本中打开终端

第 1 步:编写训练代码

创建一个名为 flowers-hptune 的新目录并通过 cd 命令进入该目录:

mkdir flowers-hptune
cd flowers-hptune

运行以下命令,为训练代码创建一个目录和一个 Python 文件(您将在其中添加下面的代码)。

mkdir trainer
touch trainer/task.py

您的 flowers-hptune/ 目录中现在应包含以下内容:

+ trainer/
    + task.py

接下来,打开您刚刚创建的 task.py 文件并复制下面的代码。

您需要将 BUCKET_ROOT 中的 {your-gcs-bucket} 替换为您在实验 1 中存储花卉数据集的 Cloud Storage 存储桶。

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

在构建容器之前,我们先来深入了解一下代码。有一些组件专用于使用超参数调节服务。

  1. 该脚本会导入 hypertune 库。
  2. get_args() 函数为您要调节的每个超参数定义命令行参数。在此示例中,将要调节的超参数是学习速率、优化器中的动量值以及模型最后一个隐藏层中的单元数,但您可以随意尝试其他超参数。然后,在这些参数中传递的值将用于在代码中设置相应的超参数。
  3. main() 函数的末尾,hypertune 库用于定义您要优化的指标。在 TensorFlow 中,Keras model.fit 方法会返回 History 对象。History.history 特性是连续周期的训练损失值和指标值的记录。如果您将验证数据传递给 model.fit,则 History.history 特性也将包含验证损失和指标值。例如,如果您使用验证数据将模型训练三个周期并提供 accuracy 作为指标,则 History.history 特性将类似于以下字典。
{
 "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
 ]

如果您希望超参数调节服务发现使模型的验证准确率最大化的值,则可以将指标定义为 val_accuracy 列表的最后一个条目(或 NUM_EPOCS - 1)。然后,将此指标传递给 HyperTune 的实例。您可以为 hyperparameter_metric_tag 选择任何字符串,但稍后在启动超参数调节作业时,您需要再次使用该字符串。

第 2 步:创建 Dockerfile

如需将代码容器化,您需要创建一个 Dockerfile。在 Dockerfile 中,您将添加运行映像所需的所有命令。它将安装所有必要的库并为训练代码设置入口点。

在您的终端中,在 flowers-hptune 目录的根目录中创建一个空的 Dockerfile:

touch Dockerfile

您的 flowers-hptune/ 目录中现在应包含以下内容:

+ Dockerfile
+ trainer/
    + task.py

打开 Dockerfile 并将以下代码复制到其中。您会发现,这与我们在第一个实验中使用的 Dockerfile 几乎完全相同,只是现在我们要安装 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"]

第 3 步:构建容器

在终端中,运行以下命令为项目定义一个环境变量,务必注意将 your-cloud-project 替换为您的项目 ID:

PROJECT_ID='your-cloud-project'

在 Artifact Registry 中定义代码库。我们将使用在第一个实验中创建的代码库。

REPO_NAME='flower-app'

使用 Google Artifact Registry 中容器映像的 URI 定义变量:

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

配置 Docker

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

然后,从 flower-hptune 目录的根目录运行以下命令来构建容器:

docker build ./ -t $IMAGE_URI

最后,将其推送到 Artifact Registry:

docker push $IMAGE_URI

在将容器推送到 Artifact Registry 后,您就可以启动训练作业了。

5. 使用 SDK 运行超参数调节作业

在本部分中,您将了解如何使用 Vertex Python API 配置和提交超参数调节作业。

在启动器中,创建一个 TensorFlow 2 笔记本。

new_notebook

导入 Vertex AI SDK。

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

如需启动超参数调节作业,您需要先定义用于指定机器类型和 Docker 映像的 worker_pool_specs。以下规范定义了一台具有两个 NVIDIA Tesla V100 GPU 的机器。

您需要将 image_uri 中的 {PROJECT_ID} 替换为您的项目。

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

接下来,定义 parameter_spec,这是一个字典,用于指定您要优化的参数。字典键是您分配给每个超参数的命令行参数的字符串,字典值是参数规范。

对于每个超参数,您需要定义类型以及调节服务将尝试的值的边界。超参数可以是双精度、整数、分类或离散类型。如果您选择双精度或整数类型,则需要提供最小值和最大值。如果您选择分类或离散类型,则需要提供值。对于双精度和整数类型,您还需要提供调节值。您可以通过此视频详细了解如何选择最佳调节值。

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

要定义的最终规范是 metric_spec,它是表示要优化的指标的字典。字典键是您在训练应用代码中设置的 hyperparameter_metric_tag,值是优化目标。

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

定义这些规范后,您将创建一个 CustomJob,这是将要用于在每次超参数调节试验中运行作业的常见规范。

您需要将 {YOUR_BUCKET} 替换为之前创建的存储桶。

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

然后,创建并运行 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()

请注意以下几个参数:

  • max_trial_count:您需要对服务将运行的试验次数设置上限。更多的试验通常会带来更好的结果,但会有一个收益递减点,在该点之后,额外的试验对您尝试优化的指标几乎没有什么效果。最佳实践是从少量试验开始,并在扩容之前了解您所选超参数的影响力。
  • parallel_trial_count:如果您使用并行试验,则服务会预配多个训练处理集群。增加并行试验的次数会减少超参数调节作业运行所需的时间,但可能降低作业的整体效率。这是因为默认调节策略使用先前试验的结果来告知后续试验中的赋值情况。
  • search_algorithm:您可以将搜索算法设置为网格、随机或默认(无)。默认选项会应用贝叶斯优化来搜索可能的超参数值空间,并且是推荐的算法。如需详细了解此算法,请点击此处。

在控制台中,您可以查看作业的进度。

hp_job

完成后,您可以看到每个试验的结果以及哪组值效果最佳。

hp_results

🎉 恭喜!🎉

您学习了如何使用 Vertex AI 执行以下操作:

  • 运行自动超参数调节作业

如需详细了解 Vertex 的不同部分,请参阅相关文档

6. 清理

因为我们将笔记本配置为在空闲 60 分钟后超时,所以不必担心关停实例。如果您要手动关停实例,请点击控制台的 Vertex AI Workbench 部分中的“停止”按钮。如果您想完全删除该笔记本,请点击“删除”按钮。

可停止实例

如需删除存储桶,请使用 Cloud Console 中的导航菜单,浏览到“存储空间”,选择您的存储桶,然后点击“删除”:

删除存储空间