אימון וכוונון היפר-פרמטרים של מודל 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 Notebooks

עוברים אל 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 ובשם הקטגוריה ב-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

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

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

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