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

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

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

מה לומדים

נסביר לכם איך:

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

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

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

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

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

עוברים לקטע 'מודלים של AI Platform במסוף Cloud ולוחצים על 'הפעלה' אם הוא עדיין לא מופעל.

d0d38662851c6af3.png

שלב 2: מפעילים את Compute Engine API

עוברים אל Compute Engine ובוחרים באפשרות Enable (הפעלה) אם היא לא מופעלת עדיין. תצטרכו את הקישור הזה כדי ליצור מכונה של ה-notebook.

שלב 3: יצירת מכונה של AI Platform Notebooks

עוברים אל הקטע 'notebooks של AI Platform' במסוף Cloud ולוחצים על New Instance. לאחר מכן בוחרים את סוג המכונה האחרון של PyTorch (ללא מעבדי GPU):

892b7588f940d145.png

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

63d2cf44801c2df5.png

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

de4c86c6c7f9438f.png

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

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

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

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

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

3. יצירת חבילה למשימת האימון

כדי להריץ את משימת האימון על הדרכת AI Platform, אנחנו זקוקים לקוד האימון שלנו שנארז באופן מקומי במכונה של Notebooks, וקטגוריה של 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 שלנו. אנחנו משתמשים בקסם %%writefile של iPython כדי לשמור את הקובץ במכונה שלנו. ברשימה הזו מפורטות 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.'
)

השלב הבא הוא ליצור את הספרייה/ של המדריך שלנו ואת הקובץ 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 בספריית המשנה/ המדריך שיצרנו קודם. משימת האימון שלנו תפעל באימון AI Platform, ותתבסס גם על שירות כוונון ההיפר-פרמטרים של 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 Tensor ואז מגדירים את המודל שלנו. כדי לבנות את המודל אנחנו משתמשים ב-API של PyTorch nn.Sequential, שמאפשר להגדיר את המודל שלנו כערימה של שכבות:
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

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

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