Trenowanie i dostrajanie hiperparametrów modelu PyTorch w Cloud AI Platform

1. Przegląd

W tym module przejdziesz cały przepływ pracy trenowania modeli ML w Google Cloud, używając PyTorch do utworzenia modelu. W środowisku notatników AI Platform dowiesz się, jak spakować zadanie trenowania, aby uruchomić je w AI Platform Training z dostrajaniem hiperparametrów.

Czego się nauczysz

Poznasz takie zagadnienia jak:

  • Tworzenie instancji notatników AI Platform
  • Tworzenie modelu PyTorch
  • Trenowanie modelu z dostrajaniem hiperparametrów w AI Platform Training

Całkowity koszt ukończenia tego modułu w Google Cloud wynosi około 1 USD.

2. Konfigurowanie środowiska

Aby wykonać to ćwiczenie, musisz mieć projekt w Google Cloud Platform z włączonymi płatnościami. Aby utworzyć projekt, postępuj zgodnie z instrukcjami.

Krok 1. Włącz interfejs Cloud AI Platform Models API

Otwórz sekcję AI Platform Models w konsoli Cloud i kliknij Włącz, jeśli nie jest jeszcze włączona.

d0d38662851c6af3.png

Krok 2. Włącz interfejs Compute Engine API

Przejdź do Compute Engine i kliknij Włącz, jeśli nie jest jeszcze włączona. Będzie Ci potrzebny do utworzenia instancji notatnika.

Krok 3. Utwórz instancję notatników AI Platform

Otwórz sekcję AI Platform Notebooks w Cloud Console i kliknij Nowa instancja. Następnie wybierz najnowszy typ instancji PyTorch (bez procesorów graficznych):

892b7588f940d145.png

Możesz użyć opcji domyślnych lub nadać mu własną nazwę, a potem kliknąć Utwórz. Po utworzeniu instancji wybierz Otwórz JupyterLab:

63d2cf44801c2df5.png

Następnie otwórz instancję notatnika w Pythonie 3 z poziomu menu z aplikacjami:

de4c86c6c7f9438f.png

Możesz zaczynać.

Krok 5. Zaimportuj pakiety Pythona

W pierwszej komórce notatnika dodaj te instrukcje importu i uruchom komórkę. Możesz go uruchomić, naciskając przycisk strzałki w prawo w menu u góry lub naciskając Command-Enter:

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

Zauważysz, że nie importujemy tutaj PyTorcha. Dzieje się tak, ponieważ zadanie trenowania uruchamiamy w AI Platform Training, a nie w instancji notatnika.

3. Tworzenie pakietu dla zadania trenowania

Aby uruchomić zadanie trenowania w AI Platform Training, musimy mieć kod trenowania spakowany lokalnie w instancji Notebooks i zasobnik Cloud Storage do przechowywania zasobów na potrzeby zadania. Najpierw utworzymy zasobnik na dane. Jeśli masz już konto, możesz pominąć ten krok.

Krok 1. Utwórz zasobnik Cloud Storage dla modelu

Najpierw zdefiniujmy zmienne środowiskowe, których będziemy używać w dalszej części tych ćwiczeń z programowania. Wpisz poniżej nazwę projektu w chmurze Google Cloud i nazwę zasobnika Cloud Storage, który chcesz utworzyć (musi być globalnie unikalna):

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

Teraz możemy utworzyć zasobnik pamięci, który wskażemy podczas uruchamiania zadania trenowania.

Aby utworzyć zasobnik, uruchom to polecenie gsutil w notatniku:

!gsutil mb $BUCKET_URL

Krok 2. Utwórz pliki początkowe pakietu Pythona

Aby uruchomić zadanie trenowania na platformie AI Platform, musimy skonfigurować nasz kod jako pakiet Pythona. Składa się z pliku setup.py w katalogu głównym, który określa wszelkie zewnętrzne zależności pakietów, podkatalogu o nazwie pakietu (w tym przypadku nazwiemy go trainer/) oraz pustego pliku __init__.py w tym podkatalogu.

Najpierw napiszemy plik setup.py. Używamy magicznych poleceń %%writefile iPython, aby zapisać plik w naszej instancji. Określiliśmy tutaj 3 biblioteki zewnętrzne, których będziemy używać w kodzie trenowania: PyTorch, Scikit-learn i 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.'
)

Następnie utwórz katalog trainer/ i pusty plik init.py w tym katalogu. Python używa tego pliku, aby rozpoznać, że jest to pakiet:

!mkdir trainer
!touch trainer/__init__.py

Teraz możemy zacząć tworzyć zadanie trenowania.

4. Wyświetlanie podglądu zbioru danych

W tym module skupimy się na narzędziach do trenowania modeli, ale najpierw przyjrzyjmy się zbiorowi danych, którego użyjemy do trenowania modelu. Będziemy używać zbioru danych natality dostępnego w BigQuery. Zawiera dane o urodzeniach w Stanach Zjednoczonych z kilku dziesięcioleci. Do przewidywania masy urodzeniowej dziecka użyjemy kilku kolumn ze zbioru danych. Oryginalny zbiór danych jest dość duży, dlatego użyjemy jego podzbioru, który udostępniliśmy w zasobniku Cloud Storage.

Krok 1. Pobieranie zbioru danych BigQuery dotyczących urodzeń

Pobierzmy wersję zbioru danych, którą udostępniliśmy w Cloud Storage, do obiektu DataFrame biblioteki Pandas i wyświetlmy jej podgląd.

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

Ten zbiór danych ma nieco poniżej 100 tys. wierszy. Do przewidywania masy urodzeniowej dziecka użyjemy 5 cech: wieku matki i ojca, liczby tygodni ciąży, przyrostu masy ciała matki w funtach oraz płci dziecka (wartość logiczna).

5. Definiowanie zadania trenowania z dostrajaniem hiperparametrów

Napiszemy skrypt trenujący w pliku o nazwie model.py w podkatalogu trainer/ utworzonym wcześniej. Zadanie trenowania będzie uruchamiane w AI Platform Training. Będzie też korzystać z usługi dostrajania hiperparametrów AI Platform, aby znaleźć optymalne hiperparametry modelu przy użyciu optymalizacji bayesowskiej.

Krok 1. Utwórz skrypt trenujący

Najpierw utwórzmy plik Pythona ze skryptem trenującym. Następnie przeanalizujemy, co się w nim dzieje. Uruchomienie tego polecenia %%writefile spowoduje zapisanie kodu modelu w lokalnym pliku Pythona:

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

Zadanie trenowania składa się z 2 funkcji, w których odbywa się większość pracy.

  • get_args(): analizuje argumenty wiersza poleceń, które przekażemy podczas tworzenia zadania trenowania, oraz hiperparametry, które AI Platform ma zoptymalizować. W tym przykładzie lista argumentów zawiera tylko hiperparametry, które będziemy optymalizować – tempo uczenia się modelu, momentum i liczbę neuronów w warstwie ukrytej.
  • train_model(): Pobieramy dane do Pandas DataFrame, normalizujemy je, przekształcamy w tensory PyTorch, a następnie definiujemy model. Do utworzenia modelu używamy interfejsu nn.Sequential API PyTorch, który umożliwia zdefiniowanie modelu jako stosu warstw:
model = nn.Sequential(nn.Linear(len(train_x[0]), args.hidden_layer_size),
                      nn.ReLU(),
                      nn.Linear(args.hidden_layer_size, 1))

Zwróć uwagę, że zamiast na stałe określać rozmiar warstwy ukrytej modelu, tworzymy hiperparametr, który AI Platform dostosuje za nas. Więcej informacji na ten temat znajdziesz w następnej sekcji.

Krok 2. Korzystanie z usługi dostrajania hiperparametrów AI Platform

Zamiast ręcznie wypróbowywać różne wartości hiperparametrów i za każdym razem ponownie trenować model, użyjemy usługi optymalizacji hiperparametrów Cloud AI Platform. Jeśli skonfigurujemy zadanie trenowania z argumentami hiperparametrów, AI Platform użyje optymalizacji bayesowskiej, aby znaleźć idealne wartości określonych przez nas hiperparametrów.

W dostrajaniu hiperparametrów pojedyncza próba to jedno trenowanie modelu z określoną kombinacją wartości hiperparametrów. W zależności od liczby przeprowadzonych prób AI Platform będzie używać wyników ukończonych prób do optymalizacji hiperparametrów wybieranych w przyszłości. Aby skonfigurować dostrajanie hiperparametrów, musimy przekazać plik konfiguracyjny podczas uruchamiania zadania trenowania z danymi dotyczącymi każdego z optymalizowanych hiperparametrów.

Następnie utwórz lokalnie ten plik konfiguracyjny:

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

W przypadku każdego hiperparametru określamy typ, zakres wartości, które chcemy przeszukać, oraz skalę, w jakiej wartość ma być zwiększana w różnych próbach.

Na początku zadania określamy też rodzaj danych, które chcemy zoptymalizować. Zwróć uwagę, że na końcu powyższej funkcji train_model() zgłaszamy ten wskaźnik do AI Platform za każdym razem, gdy kończy się próba. W tym przypadku minimalizujemy średni błąd kwadratowy modelu, więc chcemy użyć hiperparametrów, które dają najniższy średni błąd kwadratowy. Nazwa tych danych (val_mse) jest zgodna z nazwą, której używamy do raportowania tych danych, gdy na koniec okresu próbnego wywołujemy funkcję report_hyperparameter_tuning_metric().

6. Uruchamianie zadania treningowego w AI Platform

W tej sekcji rozpoczniemy zadanie trenowania modelu z dostrajaniem hiperparametrów w AI Platform.

Krok 1. Zdefiniuj zmienne środowiskowe

Najpierw zdefiniujmy zmienne środowiskowe, których użyjemy do uruchomienia zadania trenowania. Jeśli chcesz uruchomić zadanie w innym regionie, zaktualizuj zmienną REGION poniżej:

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

Każde zadanie trenowania na platformie AI Platform powinno mieć unikalną nazwę. Aby zdefiniować zmienną dla nazwy zadania, używając sygnatury czasowej, uruchom to polecenie:

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

Krok 2. Uruchom zadanie trenowania

Zadanie trenowania utworzymy za pomocą gcloud, czyli Google Cloud CLI. To polecenie możemy uruchomić bezpośrednio w notatniku, odwołując się do zdefiniowanych powyżej zmiennych:

!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

Jeśli zadanie zostało utworzone prawidłowo, przejdź do sekcji Zadania w konsoli AI Platform, aby monitorować logi.

Krok 3. Monitoruj zadanie

Gdy przejdziesz do sekcji Zadania w konsoli, kliknij zadanie, które zostało przed chwilą uruchomione, aby wyświetlić szczegóły:

c184167641bb7ed7.png

Gdy rozpocznie się pierwsza runda prób, zobaczysz wartości hiperparametrów wybrane dla każdej próby:

787c053ef9110e6b.png

Po zakończeniu okresów próbnych zostanie tu zarejestrowana uzyskana wartość danych optymalizacji (w tym przypadku val_mse). Uruchomienie zadania powinno zająć 15–20 minut. Po jego zakończeniu panel będzie wyglądać mniej więcej tak (dokładne wartości mogą się różnić):

47ef6b9b4ecb532c.png

Aby debugować potencjalne problemy i dokładniej monitorować zadanie, na stronie szczegółów zadań kliknij Wyświetl logi:

18c32dcd36351930.png

W tym miejscu pojawi się każda instrukcja print() w kodzie trenowania modelu. Jeśli napotkasz problemy, dodaj więcej instrukcji drukowania i uruchom nowe zadanie trenowania.

Po zakończeniu zadania trenowania znajdź hiperparametry, które dały najniższą wartość val_mse. Możesz ich użyć do wytrenowania i wyeksportowania ostatecznej wersji modelu lub jako wskazówek do rozpoczęcia kolejnego zadania trenowania z dodatkowymi próbami dostrajania hiperparametrów.

7. Czyszczenie

Jeśli chcesz nadal korzystać z tego notebooka, zalecamy wyłączanie go, gdy nie jest używany. W interfejsie Notebooks w konsoli Cloud wybierz notatnik, a następnie kliknij Zatrzymaj:

879147427150b6c7.png

Jeśli chcesz usunąć wszystkie zasoby utworzone w tym module, po prostu usuń instancję notatnika zamiast ją zatrzymywać.

W menu nawigacyjnym w konsoli Cloud otwórz Storage i usuń oba utworzone zasobniki do przechowywania zasobów modelu.