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 模型部分,然后点击“启用”(如果尚未启用)。

第 2 步:启用 Compute Engine API
前往 Compute Engine,然后选择启用(如果尚未启用)。您需要此权限才能创建笔记本实例。
第 3 步:创建 AI Platform Notebooks 实例
前往 Cloud 控制台的 AI Platform Notebooks 部分,然后点击新建实例。然后,选择最新的 PyTorch 实例类型(不带 GPU):

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

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

您已准备就绪,可以开始使用了!
第 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 上运行训练作业,我们需要在笔记本实例中本地打包训练代码,并使用 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 magic 将文件保存到实例中。在此示例中,我们指定了将在训练代码中使用的 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 张量,然后定义模型。为了构建模型,我们使用了 PyTorchnn.SequentialAPI,该 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 步:监控作业
进入控制台的“作业”部分后,点击刚刚启动的作业以查看详细信息:

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

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

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

模型训练代码中的每个 print() 语句都会显示在此处。如果您遇到问题,请尝试添加更多 print 语句并启动新的训练作业。
训练作业完成后,找到产生最低 val_mse 的超参数。您可以使用这些结果来训练和导出模型的最终版本,也可以将它们作为指南,启动另一项训练作业,并进行额外的超参数调节试验。
7. 清理
如果您想继续使用此笔记本电脑,建议您在不使用时将其关闭。在 Cloud 控制台的笔记本界面中,选择笔记本,然后选择停止:

如果您想删除在本实验中创建的所有资源,只需删除笔记本实例,而不是停止它。
使用 Cloud 控制台中的导航菜单,浏览到“存储空间”,然后删除您创建的用于存储模型资产的两个存储分区。