在 Cloud AI 平台上訓練和超參數調整 PyTorch 模型

1. 總覽

本研究室將逐步說明 Google Cloud 中完整的機器學習訓練工作流程,並使用 PyTorch 建立模型。在 Cloud AI Platform Notebooks 環境中,您將學習如何封裝訓練工作,並透過超參數調整程序在 AI 平台訓練中執行。

課程內容

學習重點:

  • 建立 AI Platform Notebooks 執行個體
  • 建立 PyTorch 模型
  • 在 AI 平台訓練中使用超參數調整訓練模型

在 Google Cloud 中執行這個研究室的總費用約為 $1 美元。

2. 設定環境

您需要已啟用計費功能的 Google Cloud Platform 專案,才能執行這個程式碼研究室。如要建立專案,請按照這裡的操作說明進行。

步驟 1:啟用 Cloud AI Platform Models API

前往 Cloud 控制台的 AI Platform 模型專區,然後按一下「啟用」(如果尚未啟用)。

d0d38662851c6af3.png

步驟 2:啟用 Compute Engine API

前往「Compute Engine」,並選取「啟用」 (如果尚未啟用)。建立筆記本執行個體時會用到。

步驟 3:建立 AI 平台筆記本執行個體

前往 Cloud 控制台的 AI 平台筆記本專區,然後按一下「新增執行個體」。接著,選取最新的 PyTorch 執行個體類型 (不含 GPU):

892b7588f940d145.png

使用預設選項或視需要設定自訂名稱,然後按一下「建立」。執行個體建立完成後,請選取「Open JupyterLab」

63d2cf44801c2df5.png

接著,從啟動器開啟 Python 3 筆記本執行個體:

de4c86c6c7f9438f.png

你可以開始使用了!

步驟 5:匯入 Python 套件

在筆記本的第一個儲存格中新增下列匯入項目,然後執行儲存格。按下頂端選單中的向右箭頭按鈕,或按下 Command 鍵,即可開啟模式:

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

您會發現我們不是在此匯入 PyTorch。這是因為訓練工作是在 AI 平台訓練,而非筆記本執行個體。

3. 為訓練工作建立套件

為了使用 AI 平台訓練執行訓練工作,我們需要將訓練程式碼封裝在 Notebooks 執行個體中,並且需要一個 Cloud Storage 值區,用來儲存工作所需的資產。首先來建立 Storage 值區如果您已有步驟,可以略過這個步驟。

步驟 1:為模型建立 Cloud Storage 值區

首先,定義一些在程式碼研究室的其他部分會用到的環境變數。將下列值填入您的 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'

現在,我們已準備好建立 Storage 值區,也就是開始執行訓練工作時指向的值區。

在筆記本中執行這個 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 natality 資料集

讓我們將 Cloud Storage 中的資料集版本下載至 Pandas DataFrame 並預覽。

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

這個資料集的資料列不超過 100,000 列。我們會使用 5 項功能來預測寶寶的出生體重:母親和父親的年齡、懷孕週、母親的體重增加 (磅) 以及以布林值表示的嬰兒性別。

5. 使用超參數調整定義訓練工作

我們會在先前建立的 trainer/ 子目錄中,將訓練指令碼寫入名為 model.py 的檔案。我們的訓練工作會在 AI 平台訓練中執行,我們也會使用 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 Tensor,然後定義模型。為建構模型,我們使用 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 平台的超參數調整服務

我們會使用 Cloud AI Platform 的超參數最佳化服務,而非每次都手動嘗試不同的超參數值,也未每次都重新訓練模型。使用超參數引數設定訓練工作時,AI 平台會使用貝葉斯最佳化功能,針對指定的超參數找出理想值。

在超參數調整中,單一「試驗」是由模型及其特定超參數值組合構成的一次訓練執行作業。視我們執行的測試數量而定,AI 平台會根據已完成的試驗結果,改善為日後設計的超參數。為了設定超參數調整,我們需要在啟動訓練工作時,傳遞設定檔,其中包含每個要最佳化的超參數的部分資料。

接著,在本機建立該設定檔:

%%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 平台上進行超參數調整模型訓練工作。

步驟 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:開始執行訓練工作

我們將使用 gcloud (Google Cloud CLI) 建立訓練工作。我們可以直接在筆記本中執行這個指令,參照我們定義的變數:

!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 Console 的「Jobs」(工作) 部分監控記錄檔。

步驟 3:監控工作

進入控制台的「工作」部分後,按一下您剛剛開始的工作即可查看詳細資料:

c184167641bb7ed7.png

第一次測試開始時,您可以查看每個試驗的超參數值:

787c053ef9110e6b.png

系統會在測試完成後記錄最佳化指標的結果值 (在本例中為 val_mse)。這項工作需要 15 至 20 分鐘才能執行,工作完成後,資訊主頁會如下所示 (實際值可能不同):

47ef6b9b4ecb532c.png

如要偵錯潛在問題並進一步監控工作,請按一下工作詳細資料頁面中的「查看記錄」

18c32dcd36351930.png

模型訓練程式碼中的所有 print() 陳述式都會顯示在這裡。如果您遇到問題,請嘗試新增更多輸出陳述式,然後開始新的訓練工作。

訓練工作完成後,請找出產生最低 val_mse 的超參數。您可以使用這些 API 訓練並匯出模型的最終版本,或是用這些內容做為指引,透過其他超參數調整試驗開始執行另一項訓練工作。

7. 清除

如要繼續使用這個筆記本,建議您在未使用時將其關閉。在 Cloud 控制台的「Notebooks」(筆記本) UI 中,依序選取筆記本和「Stop」(停止)

879147427150b6c7.png

如要刪除在本研究室中建立的所有資源,只要刪除筆記本執行個體即可,不必停止執行個體。

使用 Cloud 控制台中的導覽選單前往「儲存空間」,然後刪除您為了儲存模型資產而建立的兩個值區。