אימון וכוונון היפר-פרמטרים של מודל PyTorch ב-Cloud AI Platform

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

בשיעור ה-Lab הזה תלמדו על תהליך עבודה מלא של אימון מודלים ללמידת מכונה ב-Google Cloud, באמצעות PyTorch לבניית המודל. בסביבת Cloud AI Platform Notebooks, תלמדו איך לארוז את משימת האימון כדי להריץ אותה ב-AI Platform Training עם כוונון של היפרפרמטרים.

מה תלמדו

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

  • יצירת מכונה של 'שירות ניסוי ופיתוח של AI Platform'
  • יצירת מודל PyTorch
  • אימון המודל באמצעות כוונון היפרפרמטרים ב-AI Platform Training

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

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

כדי להפעיל את ה-codelab הזה, צריך פרויקט ב-Google Cloud Platform שמופעל בו חיוב. כדי ליצור פרויקט, פועלים לפי ההוראות האלה.

שלב 1: הפעלת Cloud AI Platform Models API

עוברים אל AI Platform Models section במסוף Cloud ולוחצים על Enable אם הוא עדיין לא מופעל.

d0d38662851c6af3.png

שלב 2: הפעלת Compute Engine API

עוברים אל Compute Engine ובוחרים באפשרות הפעלה אם הוא עדיין לא מופעל. תצטרכו את זה כדי ליצור את מופע המחברת.

שלב 3: יצירת מופע של מחברת AI Platform

עוברים אל AI Platform Notebooks section ב-Cloud Console ולוחצים על New Instance. לאחר מכן בוחרים את סוג האירוע האחרון של PyTorch (ללא GPU):

892b7588f940d145.png

משתמשים באפשרויות ברירת המחדל או נותנים לו שם בהתאמה אישית, ואז לוחצים על יצירה. אחרי שהמופע נוצר, בוחרים באפשרות Open JupyterLab:

63d2cf44801c2df5.png

לאחר מכן, פותחים מופע של Python 3 Notebook ממרכז האפליקציות:

de4c86c6c7f9438f.png

הכול מוכן!

שלב 5: ייבוא חבילות Python

בתא הראשון של ה-Notebook, מוסיפים את הייבוא הבא ומריצים את התא. כדי להפעיל אותו, לוחצים על לחצן החץ ימינה בתפריט העליון או על Command-Enter:

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

אפשר לראות שלא מייבאים כאן את PyTorch. הסיבה לכך היא שאנחנו מריצים את משימת האימון ב-AI Platform Training, ולא ממופע Notebook שלנו.

3. יצירת חבילה למשימת ההדרכה

כדי להריץ את משימת האימון ב-AI Platform Training, נצטרך לארוז את קוד האימון באופן מקומי ב-Notebooks instance, וקטגוריה של Cloud Storage לאחסון נכסים עבור המשימה. קודם ניצור קטגוריית אחסון. אפשר לדלג על השלב הזה אם כבר יש לכם חשבון.

שלב 1: יוצרים קטגוריית Cloud Storage עבור המודל שלנו

קודם נגדיר כמה משתני סביבה שנשתמש בהם בהמשך ה-codelab. ממלאים את הערכים הבאים בשם הפרויקט בענן של Google ובשם הקטגוריה של Cloud Storage שרוצים ליצור (השם חייב להיות ייחודי באופן גלובלי):

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

עכשיו אפשר ליצור מאגר אחסון, שאליו נפנה כשנתחיל את משימת האימון.

מריצים את הפקודה gsutil מתוך ה-notebook כדי ליצור קטגוריה:

!gsutil mb $BUCKET_URL

שלב 2: יצירת הקבצים הראשוניים לחבילת Python

כדי להריץ משימת אימון ב-AI Platform, צריך להגדיר את הקוד כחבילת Python. הוא כולל קובץ setup.py בתיקיית השורש שמציין את כל יחסי התלות בחבילות חיצוניות, ספריית משנה עם שם החבילה (במקרה הזה נקרא לה trainer/) וקובץ __init__.py ריק בתוך ספריית המשנה הזו.

קודם כול, נכתוב את הקובץ setup.py. אנחנו משתמשים בפקודות הקסם של iPython ‏ %%writefile כדי לשמור את הקובץ במופע שלנו. בדוגמה הזו ציינו 3 ספריות חיצוניות שנשתמש בהן בקוד ההדרכה: PyTorch,‏ Scikit-learn ו-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.'
)

בשלב הבא, ניצור את הספרייה trainer/ ואת הקובץ הריק init.py בתוכה. קובץ Python משמש לזיהוי חבילה:

!mkdir trainer
!touch trainer/__init__.py

עכשיו אפשר להתחיל ליצור את משימת האימון.

4. תצוגה מקדימה של מערך הנתונים

המיקוד של שיעור ה-Lab הזה הוא בכלים לאימון מודלים, אבל נסתכל רגע על מערך הנתונים שבו נשתמש כדי לאמן את המודל להבין. נשתמש במערך הנתונים של לידות שזמין ב-BigQuery. המאגר הזה מכיל נתוני לידות בארה"ב במשך כמה עשורים. נשתמש בכמה עמודות ממערך הנתונים כדי לחזות את משקל הלידה של תינוק. מערך הנתונים המקורי גדול למדי, ואנחנו נשתמש בחלק ממנו שהעמדנו לרשותכם בקטגוריה של Cloud Storage.

שלב 1: הורדת מערך הנתונים של BigQuery בנושא שיעור ילודה

נוריד את הגרסה של מערך הנתונים שזמינה לכם ב-Cloud Storage ל-Pandas DataFrame ונציג אותה בתצוגה מקדימה.

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

מערך הנתונים הזה כולל קצת פחות מ-100,000 שורות. אנחנו נשתמש ב-5 מאפיינים כדי לחזות את משקל הלידה של התינוק: גיל האם והאב, שבועות ההיריון, העלייה במשקל של האם בלירות והמגדר של התינוק שמיוצג כערך בוליאני.

5. הגדרת משימת האימון עם כוונון היפר-פרמטרים

נכתוב את סקריפט האימון שלנו לקובץ בשם model.py בתוך ספריית המשנה trainer/ שיצרנו קודם. משימת האימון תפעל ב-AI Platform Training, ותשתמש גם בשירות לכוונון היפר-פרמטרים של AI Platform כדי למצוא את ההיפר-פרמטרים האופטימליים למודל באמצעות אופטימיזציה בייסיאנית.

שלב 1: יצירת תסריט ההדרכה

קודם כול, ניצור את קובץ Python עם סקריפט האימון. לאחר מכן ננתח את מה שקורה בו. הרצת הפקודה %%writefile תכתוב את קוד המודל לקובץ Python מקומי:

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

משימת האימון מורכבת משתי פונקציות שבהן מתבצע רוב העבודה.

  • get_args(): הפקודה הזו מנתחת את הארגומנטים של שורת הפקודה שנעביר כשניצור את משימת האימון, יחד עם ההיפר-פרמטרים שאנחנו רוצים ש-AI Platform יבצע אופטימיזציה שלהם. בדוגמה הזו, רשימת הארגומנטים כוללת רק את ההיפרפרמטרים שאנחנו רוצים לבצע להם אופטימיזציה – קצב הלמידה של המודל, המומנטום ומספר הנוירונים בשכבה הנסתרת.
  • train_model(): כאן אנחנו מורידים את הנתונים ל-Pandas DataFrame, מבצעים נורמליזציה, ממירים אותם ל-PyTorch Tensors ואז מגדירים את המודל. כדי ליצור את המודל, אנחנו משתמשים ב-PyTorch nn.Sequential API, שמאפשר לנו להגדיר את המודל כמערך של שכבות:
model = nn.Sequential(nn.Linear(len(train_x[0]), args.hidden_layer_size),
                      nn.ReLU(),
                      nn.Linear(args.hidden_layer_size, 1))

שימו לב שבמקום להגדיר את הגודל של השכבה הנסתרת של המודל שלנו כקבוע, אנחנו מגדירים אותו כהיפרפרמטר ש-AI Platform יכוון בשבילנו. מידע נוסף על כך מופיע בקטע הבא.

שלב 2: שימוש בשירות לכוונון היפרפרמטרים של AI Platform

במקום לנסות ידנית ערכים שונים של היפרפרמטרים ולאמן מחדש את המודל בכל פעם, נשתמש בשירות האופטימיזציה של היפרפרמטרים של Cloud AI Platform. אם נגדיר את משימת האימון עם ארגומנטים של היפר-פרמטרים, AI Platform ישתמש באופטימיזציה בייסיאנית כדי למצוא את הערכים האידיאליים להיפר-פרמטרים שציינו.

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

לאחר מכן, יוצרים את קובץ התצורה באופן מקומי:

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

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

בתחילת הגדרת המשימה מציינים גם את המדד שאליו מכוונים באופטימיזציה. שימו לב שבסוף הפונקציה train_model() שלמעלה, אנחנו מדווחים על המדד הזה ל-AI Platform בכל פעם שתקופת ניסיון מסתיימת. כאן אנחנו ממזערים את השגיאה הריבועית הממוצעת של המודל, ולכן אנחנו רוצים להשתמש בהיפרפרמטרים שמובילים לשגיאה הריבועית הממוצעת הנמוכה ביותר עבור המודל שלנו. השם של המדד הזה (val_mse) זהה לשם שבו אנחנו משתמשים כדי לדווח עליו כשאנחנו מתקשרים אל report_hyperparameter_tuning_metric() בסוף תקופת הניסיון.

6. הרצה של משימת אימון ב-AI Platform

בקטע הזה נתחיל את משימת אימון המודל עם כוונון של היפרפרמטרים ב-AI Platform.

שלב 1: הגדרה של משתני סביבה

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

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

לכל משימת אימון ב-AI Platform צריך להיות שם ייחודי. מריצים את הפקודה הבאה כדי להגדיר משתנה לשם של העבודה באמצעות חותמת זמן:

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

שלב 2: הפעלת משימת האימון

ניצור את משימת האימון באמצעות gcloud, ‏ Google Cloud CLI. אפשר להריץ את הפקודה הזו ישירות ב-notebook, עם הפניה למשתנים שהגדרנו למעלה:

!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

אם יצרתם את העבודה בצורה נכונה, תוכלו לעבור אל הקטע Jobs במסוף AI Platform כדי לעקוב אחרי היומנים.

שלב 3: מעקב אחרי העבודה

אחרי שנכנסים לקטע 'משרות' במסוף, לוחצים על המשרה שהתחלתם לפרסם כדי לראות את הפרטים:

c184167641bb7ed7.png

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

787c053ef9110e6b.png

בסיום תקופות הניסיון, הערך שמתקבל של מדד האופטימיזציה (במקרה הזה val_mse) יתועד כאן. ההרצה של המשימה אמורה להימשך 15-20 דקות, וכשהיא תסתיים לוח הבקרה ייראה בערך כך (הערכים המדויקים ישתנו):

47ef6b9b4ecb532c.png

כדי לנפות באגים בבעיות פוטנציאליות ולעקוב אחרי העבודה בפירוט רב יותר, לוחצים על הצגת יומנים בדף הפרטים של העבודות:

18c32dcd36351930.png

כל הצהרה של print() בקוד האימון של המודל תופיע כאן. אם נתקלתם בבעיות, נסו להוסיף עוד הצהרות הדפסה ולהתחיל משימת אימון חדשה.

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

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

אם רוצים להמשיך להשתמש ב-notebook הזה, מומלץ להשבית אותו כשלא משתמשים בו. בממשק המשתמש של Notebooks במסוף Cloud, בוחרים את מחברת ה-Notebook ואז בוחרים באפשרות Stop:

879147427150b6c7.png

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

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