在 Cloud AI Platform 上训练和超参数调节 PyTorch 模型

1. 概览

在本实验中,您将使用 PyTorch 构建您的模型,从而了解 Google Cloud 上的完整机器学习训练工作流。在 Cloud AI Platform Notebooks 环境中,您将了解如何打包训练作业,以便通过超参数调节在 AI Platform Training 上运行该作业。

学习内容

您将了解如何:

  • 创建 AI Platform Notebooks 实例
  • 创建 PyTorch 模型
  • 在 AI Platform Training 上通过超参数调节来训练模型

在 Google Cloud 上运行本实验的总费用约为 $1

2. 设置您的环境

您需要一个启用了结算功能的 Google Cloud Platform 项目才能运行此 Codelab。如需创建项目,请按照此处的说明操作。

第 1 步:启用 Cloud AI Platform Models API

转到 Cloud 控制台的 AI Platform 模型部分,然后点击“启用”(如果尚未启用)。

d0d38662851c6af3.png

第 2 步:启用 Compute Engine API

前往 Compute Engine,然后选择启用(如果尚未启用)。您需要用它来创建笔记本实例。

第 3 步:创建 AI Platform Notebooks 实例

前往 Cloud 控制台的 AI Platform Notebooks 部分,然后点击新建实例。然后选择最新的 PyTorch 实例类型(不带 GPU):

892b7588f940d145

使用默认选项或根据需要自行指定名称,然后点击创建。创建实例后,选择打开 JupyterLab

63d2cf44801c2df5.png

接下来,从启动器中打开 Python 3 笔记本实例:

de4c86c6c7f9438f.png

您可以开始使用了!

第 5 步:导入 Python 软件包

在笔记本的第一个单元中,添加以下导入并运行该单元。您可以按顶部菜单中的向右箭头按钮或按 Command+Enter 键来运行它:

import datetime
import numpy as np
import os
import pandas as pd
import time

您会发现,此处并未导入 PyTorch。这是因为我们在 AI Platform Training 上运行训练作业,而不是从笔记本实例运行。

3. 为训练作业创建软件包

如需在 AI Platform Training 上运行训练作业,我们需要将训练代码打包到 Notebooks 实例中,并需要一个 Cloud Storage 存储分区来存储作业的资源。首先,我们创建一个存储分区。如果您已有账号,可以跳过此步骤。

第 1 步:为模型创建 Cloud Storage 存储分区

我们首先定义一些环境变量,我们将在 Codelab 的其余部分使用它们。在下面的值中填写您的 Google Cloud 项目名称和您要创建的 Cloud Storage 存储分区的名称(必须是全局唯一的):

# Update these to your own GCP project, model, and version names
GCP_PROJECT = 'your-gcp-project'
BOCKET_URL = 'gs://storage_bucket_name'

现在,我们可以创建存储分区了,我们将在开始训练作业时指向该存储分区。

在笔记本中运行以下 gsutil 命令以创建存储分区:

!gsutil mb $BUCKET_URL

第 2 步:为 Python 软件包创建初始文件

如需在 AI Platform 上运行训练作业,我们需要将代码配置为 Python 软件包。这包含根目录中用于指定所有外部软件包依赖项的 setup.py 文件、以软件包名称命名的子目录(此处我们将其称为 trainer/),以及该子目录中的空 __init__.py 文件。

首先,编写 setup.py 文件。我们使用 iPython %%writefile 魔法命令将文件保存到实例。在这里,我们指定了将在训练代码中使用的 3 个外部库:PyTorch、Scikit-learn 和 Pandas:

%%writefile setup.py
from setuptools import find_packages
from setuptools import setup

REQUIRED_PACKAGES = ['torch>=1.5', 'scikit-learn>=0.20', 'pandas>=1.0']

setup(
    name='trainer',
    version='0.1',
    install_requires=REQUIRED_PACKAGES,
    packages=find_packages(),
    include_package_data=True,
    description='My training application package.'
)

接下来,让我们创建 trainer/ 目录,并在其中创建空的 init.py 文件。Python 使用此文件来识别这是一个软件包:

!mkdir trainer
!touch trainer/__init__.py

现在,我们可以开始创建训练作业了。

4. 预览数据集

本实验的重点是训练模型的工具,但我们来快速了解一下用于训练模型理解的数据集。我们将使用 BigQuery 中提供的出生率数据集。其中包含美国几十年来的出生数据。我们将使用该数据集中的几列来预测婴儿的出生体重。原始数据集非常大,我们将使用 Cloud Storage 存储分区中为您提供的部分数据。

第 1 步:下载 BigQuery 出生率数据集

我们将 Cloud Storage 中提供的数据集版本下载到 Pandas DataFrame 并预览。

natality = pd.read_csv('https://storage.googleapis.com/ml-design-patterns/natality.csv')
natality.head()

此数据集的行数刚刚超过 10 万行。我们将使用 5 个特征来预测婴儿的出生体重:母亲和父亲的年龄、妊娠周数、母亲的体重增加(以磅为单位)以及婴儿的性别(以布尔值表示)。

5. 通过超参数调节定义训练作业

我们将训练脚本写入到之前创建的 trainer/ 子目录中名为 model.py 的文件中。我们的训练作业将在 AI Platform Training 上运行,并且还会利用 AI Platform 的超参数调节服务,利用贝叶斯优化为模型找到最佳超参数。

第 1 步:创建训练脚本

首先,使用训练脚本创建 Python 文件。然后,我们分析一下到底发生了什么。运行此 %%writefile 命令会将模型代码写入本地 Python 文件:

%%writefile trainer/model.py
import argparse
import hypertune
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim

from sklearn.utils import shuffle
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import normalize

def get_args():
    """Argument parser.
    Returns:
        Dictionary of arguments.
    """
    parser = argparse.ArgumentParser(description='PyTorch MNIST')
    parser.add_argument('--job-dir',  # handled automatically by AI Platform
                        help='GCS location to write checkpoints and export ' \
                             'models')
    parser.add_argument('--lr',  # Specified in the config file
                        type=float,
                        default=0.01,
                        help='learning rate (default: 0.01)')
    parser.add_argument('--momentum',  # Specified in the config file
                        type=float,
                        default=0.5,
                        help='SGD momentum (default: 0.5)')
    parser.add_argument('--hidden-layer-size',  # Specified in the config file
                        type=int,
                        default=8,
                        help='hidden layer size')
    args = parser.parse_args()
    return args

def train_model(args):
    # Get the data
    natality = pd.read_csv('https://storage.googleapis.com/ml-design-patterns/natality.csv')
    natality = natality.dropna()
    natality = shuffle(natality, random_state = 2)
    natality.head()

    natality_labels = natality['weight_pounds']
    natality = natality.drop(columns=['weight_pounds'])


    train_size = int(len(natality) * 0.8)
    traindata_natality = natality[:train_size]
    trainlabels_natality = natality_labels[:train_size]

    testdata_natality = natality[train_size:]
    testlabels_natality = natality_labels[train_size:]

    # Normalize and convert to PT tensors
    normalized_train = normalize(np.array(traindata_natality.values), axis=0)
    normalized_test = normalize(np.array(testdata_natality.values), axis=0)

    train_x = torch.Tensor(normalized_train)
    train_y = torch.Tensor(np.array(trainlabels_natality))

    test_x = torch.Tensor(normalized_test)
    test_y = torch.Tensor(np.array(testlabels_natality))

    # Define our data loaders
    train_dataset = torch.utils.data.TensorDataset(train_x, train_y)
    train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=128, shuffle=True)

    test_dataset = torch.utils.data.TensorDataset(test_x, test_y)
    test_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size=128, shuffle=False)

    # Define the model, while tuning the size of our hidden layer
    model = nn.Sequential(nn.Linear(len(train_x[0]), args.hidden_layer_size),
                          nn.ReLU(),
                          nn.Linear(args.hidden_layer_size, 1))
    criterion = nn.MSELoss()

    # Tune hyperparameters in our optimizer
    optimizer = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum)
    epochs = 20
    for e in range(epochs):
        for batch_id, (data, label) in enumerate(train_dataloader):
            optimizer.zero_grad()
            y_pred = model(data)
            label = label.view(-1,1)
            loss = criterion(y_pred, label)
            
            loss.backward()
            optimizer.step()


    val_mse = 0
    num_batches = 0
    # Evaluate accuracy on our test set
    with torch.no_grad():
        for i, (data, label) in enumerate(test_dataloader):
            num_batches += 1
            y_pred = model(data)
            mse = criterion(y_pred, label.view(-1,1))
            val_mse += mse.item()


    avg_val_mse = (val_mse / num_batches)

    # Report the metric we're optimizing for to AI Platform's HyperTune service
    # In this example, we're mimizing error on our test set
    hpt = hypertune.HyperTune()
    hpt.report_hyperparameter_tuning_metric(
        hyperparameter_metric_tag='val_mse',
        metric_value=avg_val_mse,
        global_step=epochs        
    )

def main():
    args = get_args()
    print('in main', args)
    train_model(args)

if __name__ == '__main__':
    main()

训练作业包含两个函数,其中大部分工作都发生在这些函数中。

  • get_args():这会解析我们将在创建训练作业时传递的命令行参数,以及我们希望 AI Platform 优化的超参数。在此示例中,我们的参数列表仅包含将要优化的超参数:模型的学习速率、动量和隐藏层中的神经元数量。
  • train_model():在这里,我们将数据下载到 Pandas DataFrame,对其进行归一化,将其转换为 PyTorch 张量,然后定义模型。为了构建模型,我们使用 PyTorch nn.Sequential API,它允许我们将模型定义为层的堆栈:
model = nn.Sequential(nn.Linear(len(train_x[0]), args.hidden_layer_size),
                      nn.ReLU(),
                      nn.Linear(args.hidden_layer_size, 1))

请注意,我们没有对模型隐藏层的大小进行硬编码,而是将其设为 AI Platform 将为我们调优的超参数。有关详情,请参阅下一部分。

第 2 步:使用 AI Platform 的超参数调节服务

我们将使用 Cloud AI Platform 的超参数优化服务,而不是每次手动尝试不同的超参数值并重新训练模型。如果使用超参数参数设置训练作业,AI Platform 将使用贝叶斯优化为我们指定的超参数查找理想值。

在超参数调节中,一次试验由模型的一次训练运行组成,其中包含超参数值的特定组合。根据运行的试验次数,AI Platform 将使用已完成试验的结果来优化为未来的试验选择的超参数。为了配置超参数调节,我们需要在启动训练作业时传递一个配置文件,其中包含要优化的每个超参数的一些数据。

接下来,在本地创建该配置文件:

%%writefile config.yaml
trainingInput:
  hyperparameters:
    goal: MINIMIZE
    maxTrials: 10
    maxParallelTrials: 5
    hyperparameterMetricTag: val_mse
    enableTrialEarlyStopping: TRUE
    params:
    - parameterName: lr
      type: DOUBLE
      minValue: 0.0001
      maxValue: 0.1
      scaleType: UNIT_LINEAR_SCALE
    - parameterName: momentum
      type: DOUBLE
      minValue: 0.0
      maxValue: 1.0
      scaleType: UNIT_LINEAR_SCALE
    - parameterName: hidden-layer-size
      type: INTEGER
      minValue: 8
      maxValue: 32
      scaleType: UNIT_LINEAR_SCALE

对于每个超参数,我们指定了类型、要搜索的值的范围,以及在不同试验中增加值的规模。

在作业开头,我们还指定了要优化的指标。请注意,在上面的 train_model() 函数末尾,每次试验完成时,我们都会向 AI Platform 报告此指标。在这里,我们要将模型的均方误差降至最低,因此希望使用会使模型均方误差最小的超参数。此指标的名称 (val_mse) 与我们用来报告该指标的名称(我们在试验结束时调用 report_hyperparameter_tuning_metric() 时)一致。

6. 在 AI Platform 上运行训练作业

在本部分中,我们将使用 AI Platform 上的超参数调节开始模型训练作业。

第 1 步:定义一些环境变量

我们首先定义一些环境变量,供我们用于开始训练作业。如果您希望在其他区域运行作业,请更新下面的 REGION 变量:

MAIN_TRAINER_MODULE = "trainer.model"
TRAIN_DIR = os.getcwd() + '/trainer'
JOB_DIR = BUCKET_URL + '/output'
REGION = "us-central1"

AI Platform 上的每个训练作业都应具有唯一的名称。运行以下命令,使用时间戳为作业名称定义变量:

timestamp = str(datetime.datetime.now().time())
JOB_NAME = 'caip_training_' + str(int(time.time()))

第 2 步:启动训练作业

我们将使用 Google Cloud CLI gcloud 创建训练作业。我们可以直接在笔记本中运行此命令,并引用上面定义的变量:

!gcloud ai-platform jobs submit training $JOB_NAME \
        --scale-tier basic \
        --package-path $TRAIN_DIR \
        --module-name $MAIN_TRAINER_MODULE \
        --job-dir $JOB_DIR \
        --region $REGION \
        --runtime-version 2.1 \
        --python-version 3.7 \
        --config config.yaml

如果作业创建正确,请前往 AI Platform 控制台的作业部分以监控日志。

第 3 步:监控作业

进入控制台的“作业”部分后,点击刚刚启动的作业以查看详情:

c184167641bb7ed7.png

第一轮试验开始时,您将能够看到为每次试验选择的超参数值:

787c053ef9110e6b

试验完成后,优化指标的结果值(在本例中为 val_mse)将记录在此处。运行作业需要 15-20 分钟,作业完成后,信息中心将如下所示(确切值会有所不同):

47ef6b9b4ecb532c

如需调试潜在问题并更详细地监控作业,请点击作业详情页面中的查看日志

18c32dcd36351930

模型训练代码中的每个 print() 语句都将显示在此处。如果您遇到问题,请尝试添加更多输出语句并开始新的训练作业。

训练作业完成后,找出产生最低 val_mse 的超参数。您可以使用它们来训练和导出模型的最终版本,也可以将其用作指导,通过额外的超参数调优试验启动另一项训练作业。

7. 清理

如果您想继续使用该笔记本,建议您在不使用时将其关闭。在 Cloud 控制台的 Notebooks 界面中,选择相应笔记本,然后选择停止

879147427150b6c7

如果您想删除在本实验中创建的所有资源,只需删除该笔记本实例,而无需停止该实例。

使用 Cloud 控制台中的导航菜单,浏览到“存储空间”,然后删除您为存储模型资产而创建的两个存储分区。