אב טיפוס לסביבת ייצור: כוונון היפר-פרמטר

1. סקירה כללית

בשיעור ה-Lab הזה תשתמשו ב-Vertex AI כדי להריץ משימת כוונון היפר-פרמטרים ב-Vertex AI Training.

שיעור ה-Lab הזה הוא חלק מסדרת הסרטונים Prototype to Production. חשוב להשלים את שיעור ה-Lab הקודם לפני שמנסים את שיעור ה-Lab הזה. כדאי לצפות בסדרת הסרטונים הנלווית כדי לקבל מידע נוסף:

.

מה לומדים

במאמר הזה נסביר איך:

  • שינוי קוד אפליקציה של האימון לצורך כוונון אוטומטי של היפרפרמטרים
  • הגדרת משימת כוונון של היפר-פרמטר והפעלתה באמצעות Vertex AI Python SDK

העלות הכוללת להרצת שיעור ה-Lab הזה ב-Google Cloud היא בערך 1$‎.

2. מבוא ל-Vertex AI

בשיעור ה-Lab הזה נעשה שימוש במוצר ה-AI החדש ביותר שזמין ב-Google Cloud. ‫Vertex AI משלב את מוצרי ה-ML ב-Google Cloud לחוויית פיתוח חלקה. בעבר, היה אפשר לגשת למודלים שאומנו באמצעות AutoML ולמודלים בהתאמה אישית דרך שירותים נפרדים. המוצר החדש משלב את שניהם ב-API אחד, יחד עם מוצרים חדשים אחרים. אפשר גם להעביר פרויקטים קיימים אל Vertex AI.

‫Vertex AI כולל מוצרים רבים ושונים לתמיכה בתהליכי עבודה של למידת מכונה מקצה לקצה. בשיעור ה-Lab הזה נתמקד במוצרים שמודגשים בהמשך: Training ו-Workbench

סקירה כללית על מוצר Vertex

3. הגדרת הסביבה

כדי להגדיר את הסביבה, צריך לבצע את השלבים בשיעור ה-Lab בנושא אימון מודלים בהתאמה אישית באמצעות Vertex AI.

4. יצירת קונטיינר לקוד אפליקציה של אימון

כדי לשלוח את משימת האימון הזו אל Vertex AI, צריך להכניס את קוד אפליקציית האימון אל קונטיינר Docker ולדחוף את הקונטיינר הזה אל Google Artifact Registry. באמצעות הגישה הזו, אפשר לאמן ולכוונן מודל שנבנה עם כל מסגרת.

כדי להתחיל, בתפריט Launcher של מחברת Workbench שיצרתם במעבדות הקודמות, פותחים חלון טרמינל.

פתיחת טרמינל ב-notebook

שלב 1: כותבים קוד לאימון

יוצרים ספרייה חדשה בשם flowers-hptune ועוברים אליה באמצעות הפקודה cd:

mkdir flowers-hptune
cd flowers-hptune

מריצים את הפקודה הבאה כדי ליצור ספרייה לקוד ההדרכה וקובץ Python שבו מוסיפים את הקוד שבהמשך.

mkdir trainer
touch trainer/task.py

עכשיו אמורים להיות לכם הקבצים הבאים בספרייה flowers-hptune/:

+ trainer/
    + task.py

לאחר מכן, פותחים את הקובץ task.py שיצרתם ומעתיקים את הקוד שבהמשך.

תצטרכו להחליף את {your-gcs-bucket} ב-BUCKET_ROOT בקטגוריה של Cloud Storage שבה שמרתם את קבוצת הנתונים של הפרחים במעבדה 1.

import tensorflow as tf
import numpy as np
import os
import hypertune
import argparse

## Replace {your-gcs-bucket} !!
BUCKET_ROOT='/gcs/{your-gcs-bucket}'

# Define variables
NUM_CLASSES = 5
EPOCHS=10
BATCH_SIZE = 32

IMG_HEIGHT = 180
IMG_WIDTH = 180

DATA_DIR = f'{BUCKET_ROOT}/flower_photos'

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 create_datasets(data_dir, batch_size):
  '''Creates train and validation datasets.'''

  train_dataset = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="training",
    seed=123,
    image_size=(IMG_HEIGHT, IMG_WIDTH),
    batch_size=batch_size)

  validation_dataset = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="validation",
    seed=123,
    image_size=(IMG_HEIGHT, IMG_WIDTH),
    batch_size=batch_size)

  train_dataset = train_dataset.cache().shuffle(1000).prefetch(buffer_size=tf.data.AUTOTUNE)
  validation_dataset = validation_dataset.cache().prefetch(buffer_size=tf.data.AUTOTUNE)

  return train_dataset, validation_dataset


def create_model(num_units, learning_rate, momentum):
  '''Creates model.'''

  model = tf.keras.Sequential([
    tf.keras.layers.Resizing(IMG_HEIGHT, IMG_WIDTH),
    tf.keras.layers.Rescaling(1./255, input_shape=(IMG_HEIGHT, IMG_WIDTH, 3)),
    tf.keras.layers.Conv2D(16, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Conv2D(64, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(num_units, activation='relu'),
    tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')
  ])

  model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=learning_rate, momentum=momentum),
              loss=tf.keras.losses.SparseCategoricalCrossentropy(),
              metrics=['accuracy'])
  
  return model

def main():
  args = get_args()
  train_dataset, validation_dataset = create_datasets(DATA_DIR, BATCH_SIZE)
  model = create_model(args.num_units, args.learning_rate, args.momentum)
  history = model.fit(train_dataset, validation_data=validation_dataset, epochs=EPOCHS)

  # 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=EPOCHS)


if __name__ == "__main__":
    main()

לפני שיוצרים את הקונטיינר, כדאי לעיין בקוד. יש כמה רכיבים שספציפיים לשימוש בשירות להתאמת היפרפרמטרים.

  1. הסקריפט מייבא את הספרייה hypertune.
  2. הפונקציה get_args() מגדירה ארגומנט בשורת הפקודה לכל היפרפרמטר שרוצים לכוונן. בדוגמה הזו, ההיפרפרמטרים שיעברו אופטימיזציה הם קצב הלמידה, ערך המומנטום באופטימיזציה ומספר היחידות בשכבה הנסתרת האחרונה של המודל, אבל אתם יכולים להתנסות גם עם פרמטרים אחרים. הערך שמועבר בארגומנטים האלה משמש להגדרת ההיפרפרמטר המתאים בקוד.
  3. בסוף הפונקציה main(), נעשה שימוש בספרייה hypertune כדי להגדיר את המדד שרוצים לבצע אופטימיזציה שלו. ב-TensorFlow, השיטה model.fit של keras מחזירה אובייקט History. המאפיין History.history הוא רשומה של ערכי הפסד באימון וערכי מדדים בתקופות עוקבות. אם מעבירים נתוני אימות אל model.fit, מאפיין History.history יכלול גם את ערכי המדדים ואת הפסד האימות. לדוגמה, אם אימנתם מודל ל-3 תקופות עם נתוני אימות וסיפקתם את 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
 ]

אם רוצים ששירות אופטימיזציית ההיפרפרמטרים יגלה את הערכים שממקסמים את דיוק האימות של המודל, צריך להגדיר את המדד כערך האחרון (או NUM_EPOCS - 1) ברשימה val_accuracy. לאחר מכן, מעבירים את המדד הזה למופע של HyperTune. אתם יכולים לבחור איזו מחרוזת שתרצו בשביל hyperparameter_metric_tag, אבל תצטרכו להשתמש במחרוזת שוב בהמשך כשמפעילים את משימת ההתאמה של ההיפרפרמטרים.

שלב 2: יצירת Dockerfile

כדי להוסיף את הקוד למאגר, צריך ליצור קובץ Dockerfile. ב-Dockerfile, צריך לכלול את כל הפקודות שנדרשות להרצת האימג'. הסקריפט יתקין את כל הספריות הנדרשות ויגדיר את נקודת הכניסה לקוד האימון.

בטרמינל, יוצרים קובץ Dockerfile ריק בתיקיית הבסיס של flowers-hptune:

touch Dockerfile

עכשיו אמורים להיות לכם הקבצים הבאים בספרייה flowers-hptune/:

+ Dockerfile
+ trainer/
    + task.py

פותחים את Dockerfile ומעתיקים לתוכו את הקוד הבא. אפשר לראות שהקובץ הזה כמעט זהה לקובץ Dockerfile שבו השתמשנו בשיעור ה-Lab הראשון, רק שעכשיו אנחנו מתקינים את הספרייה cloudml-hypertune.

FROM gcr.io/deeplearning-platform-release/tf2-gpu.2-8

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

שלב 3: בניית מאגר התגים

במסוף, מריצים את הפקודה הבאה כדי להגדיר משתנה סביבה לפרויקט. חשוב להחליף את your-cloud-project במזהה הפרויקט:

PROJECT_ID='your-cloud-project'

מגדירים מאגר ב-Artifact Registry. נשתמש במאגר שיצרנו במעבדה הראשונה.

REPO_NAME='flower-app'

מגדירים משתנה עם ה-URI של קובץ אימג' של קונטיינר ב-Google Artifact Registry:

IMAGE_URI=us-central1-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/flower_image_hptune:latest

הגדרת Docker

gcloud auth configure-docker \
    us-central1-docker.pkg.dev

לאחר מכן, בונים את הקונטיינר על ידי הרצת הפקודה הבאה מתיקיית השורש של flower-hptune:

docker build ./ -t $IMAGE_URI

לבסוף, מעלים אותו ל-Artifact Registry:

docker push $IMAGE_URI

אחרי שמעבירים את הקונטיינר אל Artifact Registry, אפשר להתחיל את משימת האימון.

5. הפעלת משימת כוונון של היפר-פרמטר באמצעות ה-SDK

בקטע הזה תלמדו איך להגדיר את משימת כוונון ההיפר-פרמטרים ולשלוח אותה באמצעות API בשפת Python של Vertex.

במרכז האפליקציות, יוצרים מחברת TensorFlow 2.

new_notebook

מייבאים את Vertex AI SDK.

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

כדי להפעיל את משימת האופטימיזציה של ההיפרפרמטרים, צריך קודם להגדיר את worker_pool_specs, שמציין את סוג המכונה ואת קובץ האימג' של Docker. המפרט הבא מגדיר מכונה אחת עם שני מעבדי NVIDIA Tesla V100 GPU.

צריך להחליף את {PROJECT_ID} ב-image_uri בפרויקט שלכם.

# 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": 1
    },
    "replica_count": 1,
    "container_spec": {
        "image_uri": "us-central1-docker.pkg.dev/{PROJECT_ID}/flower-app/flower_image_hptune:latest"
    }
}]

בשלב הבא מגדירים את parameter_spec, שהוא מילון שמציין את הפרמטרים שרוצים לבצע להם אופטימיזציה. מפתח המילון הוא המחרוזת שהקציתם לארגומנט של שורת הפקודה לכל היפרפרמטר, וערך המילון הוא הגדרת הפרמטר.

לכל היפרפרמטר צריך להגדיר את הסוג ואת הגבולות של הערכים ששירות האופטימיזציה ינסה. היפרפרמטרים יכולים להיות מסוג Double,‏ Integer,‏ Categorical או Discrete. אם בוחרים את הסוג Double או Integer, צריך לציין ערך מינימלי ומקסימלי. אם בוחרים באפשרות 'קטגורי' או 'דיסקרטי', צריך לספק את הערכים. עבור הסוגים Double ו-Integer, תצטרכו לציין גם את ערך ההתאמה. בסרטון הזה מוסבר איך לבחור את הסולם המתאים ביותר.

# 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 שהגדרתם בקוד אפליקציה של האימון, והערך הוא יעד האופטימיזציה.

# Dictionary representing metric 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='flowers-hptune-job',
                              worker_pool_specs=worker_pool_specs,
                              staging_bucket='gs://{YOUR_BUCKET}')

לאחר מכן, יוצרים ומריצים את HyperparameterTuningJob.

hp_job = aiplatform.HyperparameterTuningJob(
    display_name='flowers-hptune-job',
    custom_job=my_custom_job,
    metric_spec=metric_spec,
    parameter_spec=parameter_spec,
    max_trial_count=15,
    parallel_trial_count=3)

hp_job.run()

יש כמה טיעונים שכדאי לשים לב אליהם:

  • max_trial_count: צריך להגדיר גבול עליון למספר הניסיונות שהשירות יריץ. בדרך כלל, ככל שמבצעים יותר ניסויים התוצאות טובות יותר, אבל מגיע שלב שבו התועלת פוחתת, ואחריו לניסויים נוספים יש השפעה מועטה או בכלל לא על המדד שמנסים לבצע בו אופטימיזציה. מומלץ להתחיל עם מספר קטן יותר של ניסויים כדי להבין את ההשפעה של ההיפרפרמטרים שבחרתם לפני שמרחיבים את היקף הניסויים.
  • parallel_trial_count: אם משתמשים בניסויים מקבילים, השירות מקצה כמה אשכולות לעיבוד נתוני האימון. הגדלת מספר הניסויים המקבילים מקצרת את משך הזמן שנדרש להרצת משימת אופטימיזציה של היפרפרמטרים, אבל היא עלולה לפגוע ביעילות הכוללת של המשימה. הסיבה לכך היא שאסטרטגיית הכוונון שמוגדרת כברירת מחדל משתמשת בתוצאות של ניסיונות קודמים כדי להקצות ערכים בניסיונות הבאים.
  • search_algorithm: אפשר להגדיר את אלגוריתם החיפוש לרשת, אקראי או ברירת מחדל (None). אפשרות ברירת המחדל היא להחיל אופטימיזציה בייסיאנית כדי לחפש את מרחב הערכים האפשריים של היפרפרמטרים, וזהו האלגוריתם המומלץ. מידע נוסף על האלגוריתם הזה

במסוף תוכלו לראות את התקדמות העבודה.

hp_job

בסיום הניסוי, תוכלו לראות את התוצאות של כל ניסוי ואת קבוצת הערכים שהניבה את הביצועים הכי טובים.

hp_results

‫🎉 איזה כיף! 🎉

למדתם איך להשתמש ב-Vertex AI כדי:

  • הפעלת משימת כוונון אוטומטי של היפר-פרמטרים

מידע נוסף על החלקים השונים של Vertex זמין בתיעוד.

6. הסרת המשאבים

הגדרנו את מחברת ה-Jupyter כך שתפסיק לפעול אחרי 60 דקות של חוסר פעילות, ולכן אין צורך לדאוג להשבתת המופע. כדי לכבות את המופע באופן ידני, לוחצים על הלחצן Stop (עצירה) בקטע Vertex AI Workbench במסוף. כדי למחוק את ה-Notebook לגמרי, לוחצים על לחצן המחיקה.

עצירת מכונה

כדי למחוק את קטגוריית האחסון, בתפריט הניווט ב-Cloud Console, עוברים אל Storage, בוחרים את הקטגוריה ולוחצים על Delete:

מחיקת האחסון