1. 總覽
在本研究室中,您將瞭解如何使用 Vertex AI 進行超參數調整和分散式訓練。雖然本研究室使用 TensorFlow 編寫模型程式碼,但這些概念也適用於其他機器學習架構。
課程內容
學習重點:
- 在自訂容器上使用分散式訓練訓練模型
- 啟動多次測試訓練程式碼,自動執行超參數調整作業
在 Google Cloud 上執行本實驗室的總費用約為 $6 美元。
2. Vertex AI 簡介
這個研究室使用 Google Cloud 最新的 AI 產品服務。Vertex AI 將 Google Cloud 中的機器學習產品整合到流暢的開發體驗中。先前使用 AutoML 訓練的模型和自訂模型,都能透過不同的服務存取。新產品將這兩項功能與其他新產品整合至單一 API。您也可以將現有專案遷移至 Vertex AI。如有任何意見回饋,請前往支援頁面。
Vertex AI 包含許多不同產品,可支援端對端機器學習工作流程。本研究室將著重於訓練和工作台。
3. 用途簡介
在本實驗室中,您將使用超參數調整功能,找出以 TensorFlow 資料集中的馬或人類資料集訓練的圖像分類模型最佳參數。
超參數調整
使用 Vertex AI 訓練進行超參數調整的運作方式,是透過執行訓練應用程式的多次試驗,並使用您指定的限制範圍內的值,調整所選超參數。Vertex AI 會追蹤每次測試的結果,並調整後續的測試。
如要搭配使用超參數調整與 Vertex AI 訓練,您必須對訓練程式碼做出兩項變更:
- 針對要調整的每個超參數,在主要訓練模組中定義指令列引數。
- 使用這些引數中傳遞的值,在應用程式程式碼中設定對應的超參數。
分散式訓練
如果您只有一個 GPU,TensorFlow 會使用這個加速器加快模型訓練速度,而您不必額外進行任何工作。不過,如果您想透過使用多個 GPU 進一步提升效能,就必須使用 tf.distribute
,這是 TensorFlow 的模組,可在多個裝置上執行運算。
本研究室使用 tf.distribute.MirroredStrategy
。您只要稍微修改程式碼,即可將這項功能新增至訓練應用程式。這項策略會在電腦上的每個 GPU 上建立模型副本。後續的漸層更新則會以同步方式進行。也就是說,每個 GPU 會在輸入資料的不同切片上,透過模型計算正向和反向傳遞。然後,從每個切片計算的梯度會在所有 GPU 中匯總,並在稱為「all-reduce」的程序中求平均。模型參數會使用這些平均梯度更新。
您不需要具備詳細資料即可完成本研究室活動,但如果你想進一步瞭解分散式訓練在 TensorFlow 中的運作方式,請觀看以下影片:
4. 設定環境
您需要已啟用計費功能的 Google Cloud Platform 專案,才能執行這個程式碼研究室。如要建立專案,請按照這篇文章中的操作說明進行。
步驟 1:啟用 Compute Engine API
前往「Compute Engine」,然後選取「啟用」 (如果尚未啟用)。
步驟 2:啟用 Container Registry API
前往容器註冊服務,然後選取「啟用」(如果尚未啟用)。您將使用這個檔案為自訂訓練工作建立容器。
步驟 3:啟用 Vertex AI API
前往 Cloud 控制台的 Vertex AI 專區,然後按一下「啟用 Vertex AI API」。
步驟 4:建立 Vertex AI Workbench 執行個體
在 Cloud 控制台的 Vertex AI 專區中,按一下 Workbench:
啟用 Notebooks API (如果尚未啟用)。
啟用後,按一下「MANAGED NOTEBOOKS」:
然後選取「新增筆記本」。
為筆記本命名,然後按一下「進階設定」。
在「進階設定」下方,啟用閒置關機功能,並將分鐘數設為 60。也就是說,筆記型電腦會在閒置時自動關機,避免產生不必要的費用。
在「安全性」下方,選取「啟用終端機」(如果尚未啟用)。
其他進階設定皆可保持原樣。
接著,按一下「建立」。佈建執行個體需要幾分鐘的時間。
建立執行個體後,選取「Open JupyterLab」。
首次使用新執行個體時,系統會要求您進行驗證。請按照 UI 中的步驟操作。
5. 編寫訓練程式碼
首先,請在 Launcher 選單中開啟筆記本執行個體的終端機視窗:
建立名為 vertex-codelab
的新目錄,然後切換至該目錄。
mkdir vertex-codelab
cd vertex-codelab
執行下列指令,為訓練程式碼建立目錄,並建立 Python 檔案以便新增程式碼:
mkdir trainer
touch trainer/task.py
現在 vertex-codelab
目錄中應會顯示以下內容:
+ trainer/
+ task.py
接著,開啟剛才建立的 task.py
檔案,然後貼入下方的所有程式碼。
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()
讓我們進一步查看程式碼,並檢查分散式訓練和超參數調整的專屬元件。
分散式訓練
- 在
main()
函式中,系統會建立MirroredStrategy
物件。接下來,您要將模型變數建立作業納入策略範圍內。這個步驟會告訴 TensorFlow 應在 GPU 之間鏡像哪些變數。 - 批次大小會由
num_replicas_in_sync
向上調整。在 TensorFlow 中使用同步資料平行處理策略時,調整批次大小是最佳做法。如要瞭解詳情,請按這裡。
超參數調整
- 指令碼會匯入
hypertune
程式庫。稍後建構容器映像檔時,請務必安裝這個程式庫。 get_args()
函式會為您要調整的每個超參數定義指令列引數。在這個範例中,要調整的超參數是學習率、最佳化器中的動量值,以及模型最後一個隱藏層中的單位數量,但您也可以嘗試其他超參數。然後,這些引數中傳遞的值會用於在程式碼中設定對應的超參數 (例如設定learning_rate = args.learning_rate
)- 在
main()
函式的結尾,hypertune
程式庫是用來定義您要最佳化的指標。在 TensorFlow 中,Kerasmodel.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
選擇任何字串,但日後啟動超參數調整工作時,您必須再次使用該字串。
6. 將程式碼容器化
將程式碼容器化的首要步驟,就是建立 Dockerfile。而 Dockerfile 包含執行映像檔所需的所有指令。它會安裝所有必要的程式庫,並設定訓練程式碼的進入點。
步驟 1:編寫 Dockerfile
在終端機中,確認您位於 vertex-codelab
目錄中,然後建立空白 Dockerfile:
touch Dockerfile
vertex-codelab
目錄現在應該會包含以下內容:
+ Dockerfile
+ trainer/
+ task.py
開啟 Dockerfile,並將以下內容複製到檔案中:
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"]
這個 Dockerfile 會使用 深度學習容器 TensorFlow Enterprise 2.7 GPU Docker 映像檔。Google Cloud 的深度學習容器預先安裝許多常見的機器學習和數據資料學架構。下載該映像檔後,這個 Dockerfile 會設定訓練程式碼的進入點。
步驟 2:建構容器
在終端機中執行以下指令,為專案定義 env 變數,請務必將 your-cloud-project
替換為專案 ID:
PROJECT_ID='your-cloud-project'
定義變數,其中包含 Google Container Registry 中容器映像檔的 URI:
IMAGE_URI="gcr.io/$PROJECT_ID/horse-human-codelab:latest"
設定 Docker
gcloud auth configure-docker
接著,從 vertex-codelab
目錄根層級執行下列指令,建構容器:
docker build ./ -t $IMAGE_URI
最後,將映像檔推送至 Google Container Registry:
docker push $IMAGE_URI
步驟 3:建立 Cloud Storage 值區
在訓練工作中,我們會傳入前置值區的路徑。
在終端機中執行下列指令,即可在專案中建立新的值區。
BUCKET_NAME="gs://${PROJECT_ID}-hptune-bucket"
gsutil mb -l us-central1 $BUCKET_NAME
7. 啟動超參數調整工作
步驟 1:使用超參數調整功能建立自訂訓練工作
透過啟動器開啟新的 TensorFlow 2 筆記本。
匯入 Vertex AI Python SDK。
from google.cloud import aiplatform
from google.cloud.aiplatform import hyperparameter_tuning as hpt
如要啟動超參數調整工作,您必須先定義 worker_pool_specs
,指定機器類型和 Docker 映像檔。以下規格定義了搭載兩個 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": 2
},
"replica_count": 1,
"container_spec": {
"image_uri": "gcr.io/{PROJECT_ID}/horse-human-codelab:latest"
}
}]
接著,定義 parameter_spec
,這是一個字典,用來指定您要最佳化的參數。字典鍵是您為每個超參數指派給指令列引數的字串,而字典值則是參數規格。
針對每個超參數,您都需要定義類型,以及調整服務嘗試的值邊界。超參數的類型可以是 Double、Integer、Categorical 或 Discrete。如果您選取的類型是 Double 或 Integer,則必須提供最小值和最大值。如果您選取「類別」或「離散」,則需要提供值。對於 Double 和 Integer 類型,您也需要提供 Scaling 值。如要進一步瞭解如何挑選最佳比例,請觀看這部影片。
# 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
,而值則是最佳化目標。
# 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'}
定義規格後,您將建立 CustomJob
,這是用於在每個超參數調整試驗中執行工作的通用規格。
您必須將 {YOUR_BUCKET}
替換為先前建立的值區。
# Replace YOUR_BUCKET
my_custom_job = aiplatform.CustomJob(display_name='horses-humans',
worker_pool_specs=worker_pool_specs,
staging_bucket='gs://{YOUR_BUCKET}')
接著,請建立並執行 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()
請注意以下幾個引數:
- max_trial_count:您必須為服務執行的測試次數設定上限。測試量越多,成效通常就越好,但效益會因此下滑,若經過額外試驗,對要最佳化的指標幾乎或完全沒有影響。最佳做法是先從較少的試驗開始,瞭解所選超參數的影響程度,再逐步擴大規模。
- parallel_trial_count:如果使用平行試驗,服務會佈建多個訓練處理叢集。增加並行測試的數量,可縮短超參數微調工作執行的時間,但可能會降低工作整體的效益。這是因為預設調整策略會使用先前測試的結果,決定後續測試中值的分配方式。
- search_algorithm:您可以將搜尋演算法設為格狀、隨機或預設 (None)。預設選項會套用貝葉斯最佳化功能,以搜尋可能的超參數值空間 (這是建議使用的演算法)。如要進一步瞭解這項演算法,請參閱這篇文章。
工作開始進行後,您就能在使用者介面的「HYPERPARAMETER TUNING JOBS」分頁下追蹤狀態。
工作完成後,您可以查看試驗結果並加以排序,以找出超參數值的最佳組合。
🎉 恭喜!🎉
您已瞭解如何使用 Vertex AI 執行下列作業:
- 使用分散式訓練執行超參數調整工作
如要進一步瞭解 Vertex AI 的其他部分,請參閱說明文件。
8. 清除
因為我們設定了筆記本在 60 分鐘閒置後逾時,所以我們不必擔心如何關閉執行個體。如果您想手動關閉執行個體,請按一下控制台 Vertex AI Workbench 專區中的「停止」按鈕。如想完全刪除筆記本,請按一下「刪除」按鈕。
如要刪除儲存體值區,請使用 Cloud 控制台的「導覽」選單,前往「儲存體」頁面,選取所需值區,然後按一下「刪除」: