Vertex AI: rozproszone dostrajanie hiperparametrów

1. Przegląd

W tym laboratorium dowiesz się, jak używać Vertex AI do dostrajania hiperparametrów i trenowania rozproszonego. W tym laboratorium używamy TensorFlow do tworzenia kodu modelu, ale te same koncepcje mają zastosowanie również do innych platform ML.

Czego się dowiesz

Poznasz takie zagadnienia jak:

  • Trenowanie modelu z użyciem trenowania rozproszonego w kontenerze niestandardowym
  • Uruchamianie wielu prób kodu trenującego na potrzeby automatycznego dostrajania hiperparametrów

Całkowity koszt przeprowadzenia tego laboratorium w Google Cloud wynosi około 6 USD.

2. Wprowadzenie do Vertex AI

W tym module wykorzystujemy najnowszą ofertę produktów AI dostępną w Google Cloud. Vertex AI integruje oferty ML w Google Cloud, zapewniając płynne środowisko programistyczne. Wcześniej modele wytrenowane za pomocą AutoML i modele niestandardowe były dostępne w ramach osobnych usług. Nowa oferta łączy je w jeden interfejs API wraz z innymi nowymi usługami. Możesz też przeprowadzić migrację istniejących projektów do Vertex AI. Jeśli masz jakieś uwagi, odwiedź stronę pomocy.

Vertex AI obejmuje wiele różnych usług, które obsługują kompleksowe przepływy pracy związane z uczeniem maszynowym. Ten moduł skupia się na trenowaniuWorkbench.

Omówienie usługi Vertex

3. Omówienie przypadku użycia

W tym module użyjesz dostrajania hiperparametrów, aby znaleźć optymalne parametry modelu klasyfikacji obrazów wytrenowanego na zbiorze danych konie lub ludzieTensorFlow Datasets.

Dostrajanie hiperparametrów

Dostrajanie hiperparametrów za pomocą Vertex AI Training polega na uruchamianiu wielu prób aplikacji treningowej z wartościami wybranych hiperparametrów ustawionymi w określonych przez Ciebie granicach. Vertex AI śledzi wyniki każdego testu i wprowadza korekty w przypadku kolejnych testów.

Aby używać optymalizacji hiperparametrów w usłudze Vertex AI Training, musisz wprowadzić w kodzie trenowania 2 zmiany:

  1. W głównym module trenowania zdefiniuj argument wiersza poleceń dla każdego hiperparametru, który chcesz dostroić.
  2. Użyj wartości przekazanej w tych argumentach, aby ustawić odpowiedni hiperparametr w kodzie aplikacji.

Trenowanie rozproszone

Jeśli masz pojedynczy procesor GPU, TensorFlow użyje tego akceleratora, aby przyspieszyć trenowanie modelu bez dodatkowej pracy z Twojej strony. Jeśli jednak chcesz uzyskać dodatkowe korzyści z używania wielu procesorów GPU, musisz użyć modułu tf.distribute TensorFlow, który umożliwia uruchamianie obliczeń na wielu urządzeniach.

W tym module użyjemy tf.distribute.MirroredStrategy, które możesz dodać do aplikacji szkoleniowych, wprowadzając tylko kilka zmian w kodzie. Ta strategia tworzy kopię modelu na każdym procesorze GPU na Twoim komputerze. Kolejne aktualizacje gradientu będą przeprowadzane synchronicznie. Oznacza to, że każdy procesor GPU oblicza przejścia w przód i w tył przez model na innym wycinku danych wejściowych. Obliczone gradienty z każdego z tych wycinków są następnie agregowane na wszystkich procesorach GPU i uśredniane w procesie zwanym all-reduce. Parametry modelu są aktualizowane za pomocą tych uśrednionych gradientów.

Aby ukończyć ten moduł, nie musisz znać szczegółów, ale jeśli chcesz dowiedzieć się więcej o tym, jak działa trenowanie rozproszone w TensorFlow, obejrzyj poniższy film:

4. 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 Compute Engine API

Przejdź do Compute Engine i kliknij Włącz, jeśli nie jest jeszcze włączona.

Krok 2. Włącz interfejs Container Registry API

Otwórz Container Registry i kliknij Włącz, jeśli nie jest jeszcze włączona. Użyjesz go do utworzenia kontenera na potrzeby niestandardowego zadania trenowania.

Krok 3. Włącz interfejs Vertex AI API

Otwórz sekcję Vertex AI w konsoli Cloud i kliknij Włącz interfejs Vertex AI API.

Panel Vertex AI

Krok 4. Tworzenie instancji Vertex AI Workbench

W sekcji Vertex AI w konsoli Cloud kliknij Workbench:

Menu Vertex AI

Włącz interfejs Notebooks API, jeśli nie jest jeszcze włączony.

Notebook_api

Po włączeniu kliknij ZARZĄDZANE NOTATNIKI:

Notebooks_UI

Następnie wybierz NOWY NOTEBOOK.

new_notebook

Nadaj notatnikowi nazwę, a potem kliknij Ustawienia zaawansowane.

create_notebook

W sekcji Ustawienia zaawansowane włącz wyłączanie w przypadku bezczynności i ustaw liczbę minut na 60. Oznacza to, że Twój notebook będzie się automatycznie wyłączać, gdy nie jest używany, dzięki czemu nie poniesiesz niepotrzebnych kosztów.

idle_timeout

W sekcji Zabezpieczenia wybierz „Włącz terminal”, jeśli nie jest jeszcze włączony.

enable-terminal

Wszystkie pozostałe ustawienia zaawansowane możesz pozostawić bez zmian.

Następnie kliknij Utwórz. Udostępnienie instancji zajmie kilka minut.

Po utworzeniu instancji wybierz Otwórz JupyterLab.

open_jupyterlab

Przy pierwszym użyciu nowej instancji pojawi się prośba o uwierzytelnienie. Aby to zrobić, postępuj zgodnie z instrukcjami wyświetlanymi w interfejsie.

uwierzytelnij

5. Pisanie kodu szkoleniowego

Aby rozpocząć, w menu Launchera otwórz okno terminala w instancji notatnika:

launcher_terminal

Utwórz nowy katalog o nazwie vertex-codelab i przejdź do niego.

mkdir vertex-codelab
cd vertex-codelab

Aby utworzyć katalog z kodem trenowania i plik Pythona, w którym dodasz kod, uruchom to polecenie:

mkdir trainer
touch trainer/task.py

W katalogu vertex-codelab powinny się teraz znajdować te pliki:

+ trainer/
    + task.py

Następnie otwórz utworzony plik task.py i wklej do niego cały poniższy kod.

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

Przyjrzyjmy się dokładniej kodowi i zbadajmy komponenty związane z trenowaniem rozproszonym i dostrajaniem hiperparametrów.

Trenowanie rozproszone

  1. W funkcji main() tworzony jest obiekt MirroredStrategy. Następnie umieść tworzenie zmiennych modelu w zakresie strategii. Ten krok informuje TensorFlow, które zmienne powinny być odzwierciedlane na wszystkich procesorach GPU.
  2. Wielkość wsadu jest zwiększana o num_replicas_in_sync. Skalowanie wielkości wsadu jest sprawdzoną metodą w przypadku korzystania w TensorFlow ze strategii synchronicznego równoległego przetwarzania danych. Więcej informacji znajdziesz tutaj

Dostrajanie hiperparametrów

  1. Skrypt importuje bibliotekę hypertune. Gdy później będziemy tworzyć obraz kontenera, musimy pamiętać o zainstalowaniu tej biblioteki.
  2. Funkcja get_args() definiuje argument wiersza poleceń dla każdego hiperparametru, który chcesz dostroić. W tym przykładzie dostrajane hiperparametry to tempo uczenia się, wartość momentum w optymalizatorze i liczba jednostek w ostatniej warstwie ukrytej modelu, ale możesz eksperymentować z innymi. Wartość przekazana w tych argumentach jest następnie używana do ustawienia odpowiedniego hiperparametru w kodzie (np. ustawienia learning_rate = args.learning_rate).
  3. Na końcu funkcji main() biblioteka hypertune służy do zdefiniowania danych, które chcesz zoptymalizować. W TensorFlow metoda Keras model.fit zwraca obiekt History. Atrybut History.history to zapis wartości funkcji straty i wartości danych w kolejnych epokach. Jeśli przekażesz dane weryfikacyjne do model.fit, atrybut History.history będzie zawierać również wartości utraty i wartości wskaźników weryfikacji. Jeśli na przykład wytrenujesz model w 3 epokach z danymi do weryfikacji i podasz accuracy jako wskaźnik, atrybut History.history będzie wyglądać podobnie do tego słownika.
{
 "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
 ]

Jeśli chcesz, aby usługa dostrajania hiperparametrów odkryła wartości, które maksymalizują dokładność weryfikacji modelu, zdefiniuj dane jako ostatni wpis (lub NUM_EPOCS - 1) na liście val_accuracy. Następnie przekaż te dane do instancji elementu HyperTune. W przypadku parametru hyperparameter_metric_tag możesz wybrać dowolny ciąg znaków, ale musisz go użyć ponownie później, gdy uruchomisz zadanie dostrajania hiperparametrów.

6. Konteneryzacja kodu

Pierwszym krokiem w procesie kontenerowania kodu jest utworzenie pliku Dockerfile. W pliku Dockerfile umieścisz wszystkie polecenia potrzebne do uruchomienia obrazu. Zainstaluje wszystkie niezbędne biblioteki i skonfiguruje punkt wejścia dla kodu trenowania.

Krok 1. Napisz plik Dockerfile

W terminalu sprawdź, czy jesteś w katalogu vertex-codelab, i utwórz pusty plik Dockerfile:

touch Dockerfile

W katalogu vertex-codelab powinny się teraz znajdować te pliki:

+ Dockerfile
+ trainer/
    + task.py

Otwórz plik Dockerfile i skopiuj do niego ten kod:

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"]

Ten plik Dockerfile używa obrazu Dockera kontenera Deep Learning Container TensorFlow Enterprise 2.7 z GPU. Kontenery do deep learningu w Google Cloud mają fabrycznie zainstalowanych wiele popularnych platform ML i narzędzi do nauki o danych. Po pobraniu tego obrazu ten plik Dockerfile konfiguruje punkt wejścia dla kodu trenującego.

Krok 2. Utwórz kontener

W terminalu uruchom to polecenie, aby zdefiniować zmienną środowiskową dla projektu. Pamiętaj, aby zastąpić your-cloud-project identyfikatorem projektu:

PROJECT_ID='your-cloud-project'

Zdefiniuj zmienną z identyfikatorem URI obrazu kontenera w Google Container Registry:

IMAGE_URI="gcr.io/$PROJECT_ID/horse-human-codelab:latest"

Konfigurowanie Dockera

gcloud auth configure-docker

Następnie utwórz kontener, uruchamiając to polecenie w katalogu głównym vertex-codelab:

docker build ./ -t $IMAGE_URI

Na koniec przenieś go do Google Container Registry:

docker push $IMAGE_URI

Krok 3. Utwórz zasobnik Cloud Storage

W naszym zadaniu trenowania przekażemy ścieżkę do zasobnika tymczasowego.

Aby utworzyć nowy zasobnik w projekcie, uruchom w terminalu to polecenie:

BUCKET_NAME="gs://${PROJECT_ID}-hptune-bucket"
gsutil mb -l us-central1 $BUCKET_NAME

7. Uruchamianie zadania dostrajania hiperparametrów

Krok 1. Utwórz niestandardowe zadanie trenowania z dostrajaniem hiperparametrów

W programie uruchamiającym otwórz nowy notatnik TensorFlow 2.

new_notebook

Zaimportuj pakiet Vertex AI Python SDK.

from google.cloud import aiplatform
from google.cloud.aiplatform import hyperparameter_tuning as hpt

Aby uruchomić zadanie dostrajania hiperparametrów, musisz najpierw zdefiniować worker_pool_specs, który określa typ maszyny i obraz Dockera. Poniższa specyfikacja określa 1 maszynę z 2 procesorami graficznymi NVIDIA Tesla V100.

image_uri musisz zastąpić {PROJECT_ID} nazwą swojego projektu.

# 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"
    }
}]

Następnie zdefiniuj parameter_spec, czyli słownik określający parametry, które chcesz optymalizować. Kluczem słownika jest ciąg tekstowy przypisany do argumentu wiersza poleceń dla każdego hiperparametru, a wartością słownika jest specyfikacja parametru.

W przypadku każdego hiperparametru musisz określić typ oraz zakres wartości, które będzie testować usługa optymalizacji. Hiperparametry mogą być typu Double, Integer, Categorical lub Discrete. Jeśli wybierzesz typ Double lub Integer, musisz podać wartość minimalną i maksymalną. Jeśli wybierzesz opcję Kategorialne lub Dyskretne, musisz podać wartości. W przypadku typów Double i Integer musisz też podać wartość skalowania. Więcej informacji o tym, jak wybrać najlepszą skalę, znajdziesz w tym filmie.

# 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)
}

Ostatnią specyfikacją do zdefiniowania jest metric_spec, czyli słownik reprezentujący wskaźnik, który ma zostać zoptymalizowany. Kluczem słownika jest hyperparameter_metric_tag ustawiony w kodzie aplikacji do trenowania, a wartością jest cel optymalizacji.

# 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'}

Po zdefiniowaniu specyfikacji utworzysz CustomJob, czyli wspólną specyfikację, która będzie używana do uruchamiania zadania w każdej próbie dostrajania hiperparametrów.

Zastąp {YOUR_BUCKET} utworzonym wcześniej zasobnikiem.

# Replace YOUR_BUCKET
my_custom_job = aiplatform.CustomJob(display_name='horses-humans',
                              worker_pool_specs=worker_pool_specs,
                              staging_bucket='gs://{YOUR_BUCKET}')

Następnie utwórz i uruchom 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()

Warto zwrócić uwagę na kilka argumentów:

  • max_trial_count: musisz podać górną granicę liczby prób, które usługa przeprowadzi. Większa liczba prób zwykle pozwala uzyskać lepsze wyniki, ale w pewnym momencie dodatkowe próby przestają przynosić korzyści i mają niewielki lub żaden wpływ na wskaźnik, który próbujesz zoptymalizować. Zalecamy rozpoczęcie od mniejszej liczby prób i sprawdzenie, jak duże znaczenie mają wybrane hiperparametry, zanim zwiększysz skalę.
  • parallel_trial_count: jeśli używasz prób równoległych, usługa udostępnia wiele klastrów przetwarzania treningowego. Zwiększenie liczby równoległych prób skraca czas działania zadania dostrajania hiperparametrów, ale może zmniejszyć ogólną skuteczność zadania. Dzieje się tak, ponieważ domyślna strategia dostrajania wykorzystuje wyniki poprzednich prób do określania wartości w kolejnych próbach.
  • search_algorithm: możesz ustawić algorytm wyszukiwania na siatkę, losowy lub domyślny (brak). Opcja domyślna stosuje optymalizację bayesowską do przeszukiwania przestrzeni możliwych wartości hiperparametrów i jest zalecanym algorytmem. Więcej informacji o tym algorytmie znajdziesz tutaj.

Po rozpoczęciu zadania możesz śledzić jego stan w interfejsie na karcie ZADANIA DOSTRAJANIA HIPERPARAMETRÓW.

HP_job

Po zakończeniu zadania możesz wyświetlić i posortować wyniki prób, aby znaleźć najlepszą kombinację wartości hiperparametrów.

HP_results

🎉 Gratulacje! 🎉

Dowiedziałeś się, jak używać Vertex AI do:

  • Uruchamianie zadania dostrajania hiperparametrów z rozproszonym trenowaniem

Więcej informacji o poszczególnych częściach Vertex AI znajdziesz w dokumentacji.

8. Czyszczenie

Skonfigurowaliśmy notatnik tak, aby po 60 minutach bezczynności przekraczał limit czasu, więc nie musimy się martwić o zamykanie instancji. Jeśli chcesz ręcznie wyłączyć instancję, kliknij przycisk Zatrzymaj w sekcji Vertex AI Workbench w konsoli. Jeśli chcesz całkowicie usunąć notatnik, kliknij przycisk Usuń.

usuń

Aby usunąć zasobnik Storage, w menu nawigacyjnym w konsoli Cloud otwórz Storage, wybierz zasobnik i kliknij Usuń:

Usuń miejsce na dane