Đào tạo và siêu tham số điều chỉnh mô hình PyTorch trên Cloud AI Platform

1. Tổng quan

Trong bài tập thực hành này, bạn sẽ thực hiện quy trình huấn luyện mô hình học máy hoàn chỉnh trên Google Cloud bằng cách sử dụng PyTorch để xây dựng mô hình. Trong môi trường Cloud AI Platform Notebooks, bạn sẽ tìm hiểu cách đóng gói công việc huấn luyện để kích hoạt công việc đó trên AI Platform Training (Huấn luyện trên Nền tảng Trí tuệ nhân tạo) với tính năng điều chỉnh siêu tham số.

Kiến thức bạn sẽ học được

Bạn sẽ tìm hiểu cách:

  • Tạo một thực thể Nền tảng Trí tuệ nhân tạo Notebooks
  • Tạo mô hình PyTorch
  • Huấn luyện mô hình bằng tính năng điều chỉnh siêu tham số trên Nền tảng Trí tuệ nhân tạo Training

Tổng chi phí để chạy bài tập thực hành này trên Google Cloud là khoảng 1 USD.

2. Thiết lập môi trường

Bạn cần có một dự án trên Google Cloud Platform đã bật tính năng thanh toán để chạy lớp học lập trình này. Để tạo một dự án, hãy làm theo hướng dẫn tại đây.

Bước 1: Bật API Mô hình Nền tảng Trí tuệ nhân tạo Cloud

Chuyển đến phần Mô hình Nền tảng Trí tuệ nhân tạo trong Cloud Console (Bảng điều khiển Cloud) rồi nhấp vào Bật nếu chưa bật.

d0d38662851c6af3.png

Bước 2: Bật API Compute Engine

Chuyển đến Compute Engine rồi chọn Bật nếu chưa bật. Bạn cần bật API này để tạo thực thể sổ ghi chú.

Bước 3: Tạo một thực thể Nền tảng Trí tuệ nhân tạo Notebooks

Chuyển đến phần Sổ tay Nền tảng Trí tuệ nhân tạo trong bảng điều khiển Cloud rồi nhấp vào Thực thể mới. Sau đó, hãy chọn loại thực thể PyTorch mới nhất (không có GPU):

892b7588f940d145.png

Sử dụng các lựa chọn mặc định hoặc đặt tên tuỳ chỉnh nếu muốn, rồi nhấp vào Create (Tạo). Sau khi tạo thực thể, hãy chọn Open JupyterLab (Mở JupyterLab):

63d2cf44801c2df5.png

Tiếp theo, hãy mở một thực thể Python 3 Notebook từ trình chạy:

de4c86c6c7f9438f.png

Bạn đã sẵn sàng bắt đầu!

Bước 5: Nhập các gói Python

Trong ô đầu tiên của sổ ghi chú, hãy thêm các mục nhập sau rồi chạy ô. Bạn có thể kích hoạt bằng cách nhấn vào nút mũi tên phải trong trình đơn trên cùng hoặc nhấn vào lệnh-enter:

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

Bạn sẽ nhận thấy rằng chúng ta không nhập PyTorch tại đây. Lý do là vì chúng ta đang chạy công việc huấn luyện trên AI Platform Training (Huấn luyện trên Nền tảng Trí tuệ nhân tạo), chứ không phải từ thực thể Notebook.

3. Tạo một gói cho công việc huấn luyện

Để chạy công việc huấn luyện trên AI Platform Training (Huấn luyện trên Nền tảng Trí tuệ nhân tạo), chúng ta cần đóng gói mã huấn luyện cục bộ trong thực thể Notebooks và một vùng chứa Cloud Storage để lưu trữ tài sản cho công việc. Trước tiên, chúng ta sẽ tạo một bộ chứa lưu trữ. Bạn có thể bỏ qua bước này nếu đã có một vùng chứa.

Bước 1: Tạo một vùng chứa Cloud Storage cho mô hình

Trước tiên, hãy xác định một số biến môi trường mà chúng ta sẽ sử dụng trong phần còn lại của lớp học lập trình này. Điền các giá trị bên dưới bằng tên dự án trên đám mây Google Cloud và tên bộ chứa Cloud Storage mà bạn muốn tạo (phải là duy nhất trên toàn cầu):

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

Giờ đây, chúng ta đã sẵn sàng tạo một bộ chứa lưu trữ mà chúng ta sẽ trỏ đến khi bắt đầu công việc huấn luyện.

Chạy lệnh gsutil này trong sổ ghi chú để tạo một vùng chứa:

!gsutil mb $BUCKET_URL

Bước 2: Tạo các tệp ban đầu cho gói Python

Để chạy công việc huấn luyện trên Nền tảng Trí tuệ nhân tạo, chúng ta cần định cấu hình mã dưới dạng gói Python. Gói này bao gồm một tệp setup.py trong thư mục gốc chỉ định mọi phần phụ thuộc của gói bên ngoài, một thư mục con có tên gói của chúng ta (ở đây, chúng ta sẽ gọi là trainer/) và một tệp __init__.py trống trong thư mục con này.

Trước tiên, hãy viết tệp setup.py. Chúng ta đang sử dụng iPython %%writefile magics để lưu tệp vào thực thể. Ở đây, chúng ta đã chỉ định 3 thư viện bên ngoài mà chúng ta sẽ sử dụng trong mã huấn luyện: PyTorch, Scikit-learn và 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.'
)

Tiếp theo, hãy tạo thư mục trainer/ và tệp init.py trống trong thư mục đó. Python sử dụng tệp này để nhận ra rằng đây là một gói:

!mkdir trainer
!touch trainer/__init__.py

Giờ đây, chúng ta đã sẵn sàng bắt đầu tạo công việc huấn luyện.

4. Xem trước tập dữ liệu

Trọng tâm của bài tập thực hành này là các công cụ để huấn luyện mô hình tại đây, nhưng hãy xem nhanh tập dữ liệu mà chúng ta sẽ sử dụng để huấn luyện mô hình nhằm hiểu rõ hơn. Chúng ta sẽ sử dụng tập dữ liệu về tỷ lệ sinh có trong BigQuery. Tập dữ liệu này chứa dữ liệu về tỷ lệ sinh ở Hoa Kỳ trong vài thập kỷ. Chúng ta sẽ sử dụng một vài cột trong tập dữ liệu để dự đoán cân nặng của trẻ sơ sinh. Tập dữ liệu gốc khá lớn và chúng ta sẽ sử dụng một tập hợp con của tập dữ liệu đó mà chúng tôi đã cung cấp cho bạn trong một bộ chứa Cloud Storage.

Bước 1: Tải tập dữ liệu về tỷ lệ sinh BigQuery xuống

Hãy tải phiên bản tập dữ liệu mà chúng tôi đã cung cấp cho bạn trong Cloud Storage xuống một Pandas DataFrame và xem trước tập dữ liệu đó.

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

Tập dữ liệu này có chưa đến 100.000 hàng. Chúng ta sẽ sử dụng 5 đặc điểm để dự đoán cân nặng của trẻ sơ sinh: tuổi của mẹ và cha, số tuần thai, cân nặng của mẹ tăng lên tính bằng pound và giới tính của trẻ sơ sinh được biểu thị dưới dạng boolean.

5. Xác định công việc huấn luyện bằng tính năng điều chỉnh siêu tham số

Chúng ta sẽ viết tập lệnh huấn luyện vào một tệp có tên là model.py trong thư mục con trainer/ mà chúng ta đã tạo trước đó. Công việc huấn luyện của chúng ta sẽ chạy trên AI Platform Training (Huấn luyện trên Nền tảng Trí tuệ nhân tạo) và cũng sẽ sử dụng dịch vụ điều chỉnh siêu tham số của Nền tảng Trí tuệ nhân tạo để tìm các siêu tham số tối ưu cho mô hình của chúng ta bằng cách tối ưu hoá Bayesian.

Bước 1: Tạo tập lệnh huấn luyện

Trước tiên, hãy tạo tệp Python bằng tập lệnh huấn luyện. Sau đó, chúng ta sẽ phân tích những gì đang diễn ra trong đó. Việc chạy lệnh %%writefile này sẽ ghi mã mô hình vào một tệp Python cục bộ:

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

Công việc huấn luyện bao gồm 2 hàm nơi phần lớn công việc đang diễn ra.

  • get_args(): Hàm này phân tích cú pháp các đối số dòng lệnh mà chúng ta sẽ truyền khi tạo công việc huấn luyện, cùng với các siêu tham số mà chúng ta muốn Nền tảng Trí tuệ nhân tạo tối ưu hoá. Trong ví dụ này, danh sách đối số của chúng ta chỉ bao gồm các siêu tham số mà chúng ta sẽ tối ưu hoá – tốc độ học tập, động lượng của mô hình và số lượng nơ-ron trong lớp ẩn.
  • train_model(): Tại đây, chúng ta tải dữ liệu xuống một Pandas DataFrame, chuẩn hoá dữ liệu đó, chuyển đổi thành PyTorch Tensors, rồi xác định mô hình. Để xây dựng mô hình, chúng ta đang sử dụng API nn.Sequential của PyTorch, cho phép chúng ta xác định mô hình dưới dạng một ngăn xếp các lớp:
model = nn.Sequential(nn.Linear(len(train_x[0]), args.hidden_layer_size),
                      nn.ReLU(),
                      nn.Linear(args.hidden_layer_size, 1))

Lưu ý rằng thay vì mã hoá cứng kích thước của lớp ẩn của mô hình, chúng ta đang biến đây thành một siêu tham số mà Nền tảng Trí tuệ nhân tạo sẽ điều chỉnh cho chúng ta. Thông tin chi tiết hơn về vấn đề này trong phần tiếp theo.

Bước 2: Sử dụng dịch vụ điều chỉnh siêu tham số của Nền tảng Trí tuệ nhân tạo

Thay vì thử các giá trị siêu tham số khác nhau theo cách thủ công và huấn luyện lại mô hình mỗi lần, chúng ta sẽ sử dụng dịch vụ tối ưu hoá siêu tham số của Nền tảng Trí tuệ nhân tạo Cloud. Nếu chúng ta thiết lập công việc huấn luyện bằng các đối số siêu tham số, thì Nền tảng Trí tuệ nhân tạo sẽ sử dụng tính năng tối ưu hoá Bayesian để tìm các giá trị lý tưởng cho các siêu tham số mà chúng ta chỉ định.

Trong quá trình điều chỉnh siêu tham số, một lần thử duy nhất bao gồm một lần chạy huấn luyện mô hình của chúng ta với một tổ hợp cụ thể các giá trị siêu tham số. Tuỳ thuộc vào số lượng thử nghiệm mà chúng ta chạy, Nền tảng Trí tuệ nhân tạo sẽ sử dụng kết quả của các thử nghiệm đã hoàn tất để tối ưu hoá các siêu tham số mà nền tảng này chọn cho các thử nghiệm trong tương lai. Để định cấu hình tính năng điều chỉnh siêu tham số, chúng ta cần truyền một tệp cấu hình khi bắt đầu công việc huấn luyện bằng một số dữ liệu về từng siêu tham số mà chúng ta đang tối ưu hoá.

Tiếp theo, hãy tạo tệp cấu hình đó cục bộ:

%%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

Đối với mỗi siêu tham số, chúng ta chỉ định loại, phạm vi giá trị mà chúng ta muốn tìm kiếm và tỷ lệ tăng giá trị trên các thử nghiệm khác nhau.

Khi bắt đầu công việc, chúng ta cũng chỉ định chỉ số mà chúng ta đang tối ưu hoá. Lưu ý rằng ở cuối hàm train_model() ở trên, chúng ta báo cáo chỉ số này cho Nền tảng Trí tuệ nhân tạo mỗi khi một thử nghiệm hoàn tất. Ở đây, chúng ta đang giảm thiểu lỗi bình phương trung bình của mô hình, vì vậy, chúng ta muốn sử dụng các siêu tham số dẫn đến lỗi bình phương trung bình thấp nhất cho mô hình. Tên của chỉ số này (val_mse) khớp với tên mà chúng ta sử dụng để báo cáo chỉ số đó khi gọi report_hyperparameter_tuning_metric() ở cuối một thử nghiệm.

6. Chạy công việc huấn luyện trên Nền tảng Trí tuệ nhân tạo

Trong phần này, chúng ta sẽ bắt đầu công việc huấn luyện mô hình bằng tính năng điều chỉnh siêu tham số trên Nền tảng Trí tuệ nhân tạo.

Bước 1: Xác định một số biến môi trường

Trước tiên, hãy xác định một số biến môi trường mà chúng ta sẽ sử dụng để bắt đầu công việc huấn luyện. Nếu bạn muốn chạy công việc ở một khu vực khác, hãy cập nhật biến REGION bên dưới:

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

Mỗi công việc huấn luyện trên Nền tảng Trí tuệ nhân tạo phải có một tên riêng biệt. Chạy lệnh sau để xác định một biến cho tên công việc bằng cách sử dụng dấu thời gian:

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

Bước 2: Bắt đầu công việc huấn luyện

Chúng ta sẽ tạo công việc huấn luyện bằng gcloud, Google Cloud CLI. Chúng ta có thể chạy lệnh này trực tiếp trong sổ ghi chú, tham chiếu đến các biến mà chúng ta đã xác định ở trên:

!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

Nếu công việc của bạn được tạo đúng cách, hãy chuyển đến phần Jobs (Công việc) của bảng điều khiển Nền tảng Trí tuệ nhân tạo để theo dõi nhật ký.

Bước 3: Theo dõi công việc

Sau khi bạn vào phần Jobs (Công việc) của bảng điều khiển, hãy nhấp vào công việc mà bạn vừa bắt đầu để xem thông tin chi tiết:

c184167641bb7ed7.png

Khi vòng thử nghiệm đầu tiên bắt đầu, bạn sẽ có thể thấy các giá trị siêu tham số được chọn cho mỗi thử nghiệm:

787c053ef9110e6b.png

Khi các thử nghiệm hoàn tất, giá trị kết quả của chỉ số tối ưu hoá (trong trường hợp này là val_mse) sẽ được ghi lại tại đây. Công việc sẽ mất 15 – 20 phút để chạy và trang tổng quan sẽ trông giống như sau khi công việc hoàn tất (các giá trị chính xác sẽ khác nhau):

47ef6b9b4ecb532c.png

Để gỡ lỗi các vấn đề tiềm ẩn và theo dõi công việc của bạn chi tiết hơn, hãy nhấp vào View Logs (Xem nhật ký) trên trang chi tiết công việc:

18c32dcd36351930.png

Mọi câu lệnh print() trong mã huấn luyện mô hình sẽ xuất hiện tại đây. Nếu bạn gặp vấn đề, hãy thử thêm nhiều câu lệnh in hơn và bắt đầu một công việc huấn luyện mới.

Sau khi công việc huấn luyện hoàn tất, hãy tìm các siêu tham số mang lại val_mse thấp nhất. Bạn có thể sử dụng các siêu tham số này để huấn luyện và xuất phiên bản cuối cùng của mô hình hoặc sử dụng các siêu tham số này làm hướng dẫn để bắt đầu một công việc huấn luyện khác với các thử nghiệm điều chỉnh siêu tham số bổ sung.

7. Dọn dẹp

Nếu bạn muốn tiếp tục sử dụng sổ ghi chú này, thì bạn nên tắt sổ ghi chú khi không sử dụng. Trên giao diện người dùng Notebooks trong Cloud Console (Bảng điều khiển Cloud), hãy chọn sổ ghi chú rồi chọn Stop (Dừng):

879147427150b6c7.png

Nếu bạn muốn xoá tất cả các tài nguyên mà bạn đã tạo trong bài tập thực hành này, chỉ cần xoá thực thể sổ ghi chú thay vì dừng thực thể đó.

Sử dụng trình đơn Điều hướng trong Cloud Console (Bảng điều khiển Cloud), hãy duyệt đến Storage (Bộ nhớ) rồi xoá cả hai vùng chứa mà bạn đã tạo để lưu trữ tài sản mô hình.