You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ML-For-Beginners/translations/he/8-Reinforcement/2-Gym
localizeflow[bot] 71dfe6b060
[nl,he,vi] chore(i18n): sync translations
1 month ago
..
solution chore(i18n): sync translations with latest source changes (chunk 1/1, 300 changes) 7 months ago
README.md [nl,he,vi] chore(i18n): sync translations 1 month ago
assignment.md chore(i18n): sync translations with latest source changes (chunk 1/1, 300 changes) 7 months ago
notebook.ipynb 🌐 Update translations via Co-op Translator 12 months ago

README.md

גלישת CartPole

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

חידון לפני ההרצאה

מבוא

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

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

הבריחה הגדולה!

פיטר וחבריו מגלים יצירתיות כדי להימלט מהזאב! תמונה מאת Jen Looper

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

cartpole

דרישות מוקדמות

בשיעור זה נשתמש בספרייה בשם OpenAI Gym כדי לסמלץ סביבות שונות. ניתן להריץ את קוד השיעור מקומית (למשל מתוך Visual Studio Code), ובמקרה זה הסימולציה תיפתח בחלון חדש. כאשר מריצים את הקוד אונליין, ייתכן שתצטרכו לבצע כמה התאמות בקוד, כפי שמתואר כאן.

OpenAI Gym

בשיעור הקודם, חוקי המשחק והסטייט נקבעו על ידי מחלקת Board שהגדרנו בעצמנו. כאן נשתמש בסביבת סימולציה מיוחדת, שתחקה את הפיזיקה שמאחורי הקורה המשמרת איזון. אחת מסביבות הסימולציה הפופולריות ביותר לאימון אלגוריתמים של למידה מחוזקת נקראת Gym, שמתוחזקת על ידי OpenAI. באמצעות Gym זה, נוכל ליצור סביבות שונות, מסימולציית cartpole ועד למשחקי אטארי.

הערה: ניתן לראות סביבות נוספות זמינות מתוך OpenAI Gym כאן.

ראשית, נתקין את gym ונייבא את הספריות הנדרשות (קוד בלוק 1):

import sys
!{sys.executable} -m pip install gym 

import gym
import matplotlib.pyplot as plt
import numpy as np
import random

תרגיל - אתחול סביבה ל-cartpole

כדי לעבוד עם בעיית שמירת האיזון ב-cartpole, יש לאתחל את הסביבה המתאימה. כל סביבה מקושרת ל:

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

  • מרחב פעולה שמגדיר פעולות אפשריות. במקרה שלנו מרחב הפעולה הוא בדידי, וכולל שתי פעולות - שמאל ו-ימין. (קוד בלוק 2)

  1. לאתחל, הקלד את הקוד הבא:

    env = gym.make("CartPole-v1")
    print(env.action_space)
    print(env.observation_space)
    print(env.action_space.sample())
    

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

  1. הרץ את הקוד למטה וראה לאן זה מוביל.

    זכור שמומלץ להריץ קוד זה בהתקנת פייתון מקומית! (קוד בלוק 3)

    env.reset()
    
    for i in range(100):
       env.render()
       env.step(env.action_space.sample())
    env.close()
    

    תראה משהו דומה לתמונה הזו:

    cartpole לא מאזין

  2. במהלך הסימולציה, אנו זקוקים לתצפיות כדי להחליט איך לפעול. בפועל, הפונקציה step מחזירה את התצפיות הנוכחיות, את פונקציית התגמול, ואת הדגל done שמצביע האם יש טעם להמשיך בסימולציה או לא: (קוד בלוק 4)

    env.reset()
    
    done = False
    while not done:
       env.render()
       obs, rew, done, info = env.step(env.action_space.sample())
       print(f"{obs} -> {rew}")
    env.close()
    

    תראה משהו כזה ביציאת המחברת:

    [ 0.03403272 -0.24301182  0.02669811  0.2895829 ] -> 1.0
    [ 0.02917248 -0.04828055  0.03248977  0.00543839] -> 1.0
    [ 0.02820687  0.14636075  0.03259854 -0.27681916] -> 1.0
    [ 0.03113408  0.34100283  0.02706215 -0.55904489] -> 1.0
    [ 0.03795414  0.53573468  0.01588125 -0.84308041] -> 1.0
    ...
    [ 0.17299878  0.15868546 -0.20754175 -0.55975453] -> 1.0
    [ 0.17617249  0.35602306 -0.21873684 -0.90998894] -> 1.0
    

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

    • מיקום העגלה
    • מהירות העגלה
    • זווית הקורה
    • קצב סיבוב הקורה
  3. קבל ערך מינימום ומקסימום של המספרים האלה: (קוד בלוק 5)

    print(env.observation_space.low)
    print(env.observation_space.high)
    

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

    למעשה, סימולציית CartPole נחשבת לפתרונה אם נצליח לקבל תגמול ממוצע של 195 על 100 ניסיונות עוקבים.

בדידת סטייט

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

יש כמה דרכים לעשות זאת:

  • חלוקה לתאים (bins). אם אנו יודעים את התחום של ערך מסוים, נוכל לחלק תחום זה למספר תאים, ואז להחליף את הערך במספר התא שאליו הוא שייך. ניתן לעשות זאת באמצעות השיטה digitize של numpy. במקרה זה, נדע בדיוק את גודל הסטייט, כי הוא תלוי במספר התאים שנבחר.

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

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

  1. הנה הפונקציה שלוקחת תצפית מהמודל ומחזירה זוג זוגות של 4 ערכים שלמים: (קוד בלוק 6)

    def discretize(x):
        return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))
    
  2. בוא נבדוק גם שיטת בדידה אחרת באמצעות תאים: (קוד בלוק 7)

    def create_bins(i,num):
        return np.arange(num+1)*(i[1]-i[0])/num+i[0]
    
    print("Sample bins for interval (-5,5) with 10 bins\n",create_bins((-5,5),10))
    
    ints = [(-5,5),(-2,2),(-0.5,0.5),(-2,2)] # טווחי ערכים עבור כל פרמטר
    nbins = [20,20,10,10] # מספר תיבות עבור כל פרמטר
    bins = [create_bins(ints[i],nbins[i]) for i in range(4)]
    
    def discretize_bins(x):
        return tuple(np.digitize(x[i],bins[i]) for i in range(4))
    
  3. עכשיו נריץ סימולציה קצרה ונבחן את הערכים הבדידים הללו מהסביבה. הרגש חופשי לנסות גם את discretize וגם את discretize_bins ולראות אם יש הבדל.

    discretize_bins מחזיר את מספר התא, שמתחיל ב-0. לכן, עבור ערכי משתנה קלט סביב 0 הוא מחזיר את המספר מהאמצע התחום (10). ב-discretize לא התחשבנו בטווח ערכי הפלט, ואיפשרנו להם להיות שליליים, ולכן ערכי הסטייט לא מוזזים, ו-0 מתייחס ל-0. (קוד בלוק 8)

    env.reset()
    
    done = False
    while not done:
       #הסביבה מציגה()
       obs, rew, done, info = env.step(env.action_space.sample())
       #הדפס את תוצאות החלוקה למרחבים בדידים (obs)
       print(discretize(obs))
    env.close()
    

    בטל את ההערה בשורה שמתחילה ב-env.render אם ברצונך לראות כיצד הסביבה מתבצעת. אחרת אפשר להריץ אותה ברקע, וזה מהיר יותר. נשתמש בהרצה "בלתי נראית" זו במהלך תהליך ה-Q-Learning.

מבנה טבלת Q

בשיעור הקודם, היה הסטייט זוג פשוט של מספרים מ-0 עד 8, ולכן היה נוח לייצג את טבלת Q בטנסור numpy עם צורה 8x8x2. אם נשתמש בבידוד תאים, גודל וקטור הסטייט שלנו ידוע גם כן, ולכן נוכל להשתמש בגישה דומה ולייצג את הסטייט במערך בצורה 20x20x10x10x2 (כאן 2 היא מימד מרחב הפעולה, והמימדים הראשונים מתאימים למספר התאים שבחרנו לכל אחד מהפרמטרים במרחב התצפית).

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

  1. השתמש בזוג (state,action) כמפתח מילון, והערך יתאים לערך טבלת Q. (קוד בלוק 9)

    Q = {}
    actions = (0,1)
    
    def qvalues(state):
        return [Q.get((state,a),0) for a in actions]
    

    כאן גם מגדירים פונקציה qvalues(), שמחזירה רשימת ערכי טבלת Q עבור סטייט נתון התואמת לכל הפעולות האפשריות. אם הכניסה אינה קיימת בטבלה, נחזיר 0 כברירת מחדל.

נתחיל ב-Q-Learning

עכשיו אנו מוכנים ללמד את פיטר לשמור על איזון!

  1. ראשית, נגדיר כמה היפר-פרמטרים: (קוד בלוק 10)

    # היפר-פרמטרים
    alpha = 0.3
    gamma = 0.9
    epsilon = 0.90
    

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

    gamma הוא מקדם הנחה שמראה באיזו מידה יש להעדיף תגמול עתידי על פני תגמול נוכחי.

    epsilon הוא מקדם חקירה/ניצול שמכתיב האם להעדיף חקירה או ניצול. באלגוריתם שלנו נבחר באחוז epsilon מהרצות את הפעולה על פי ערכי טבלת Q, ובשאר המקרים נבחר פעולה אקראית. זה יאפשר לנו לחקור אזורים במרחב החיפוש שמעולם לא ראינו.

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

שיפור האלגוריתם

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

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

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

  1. אסוף את כל תגמולי המצטברים בכל סימולציה במערך rewards לצורך הצגה גרפית מאוחרת יותר. (קוד בלוק 11)

    def probs(v,eps=1e-4):
        v = v-v.min()+eps
        v = v/v.sum()
        return v
    
    Qmax = 0
    cum_rewards = []
    rewards = []
    for epoch in range(100000):
        obs = env.reset()
        done = False
        cum_reward=0
        # == בצע את הסימולציה ==
        while not done:
            s = discretize(obs)
            if random.random()<epsilon:
                # ניצול - בחר את הפעולה בהתאם להסתברויות בטבלת Q
                v = probs(np.array(qvalues(s)))
                a = random.choices(actions,weights=v)[0]
            else:
                # חקר - בחר באקראי את הפעולה
                a = np.random.randint(env.action_space.n)
    
            obs, rew, done, info = env.step(a)
            cum_reward+=rew
            ns = discretize(obs)
            Q[(s,a)] = (1 - alpha) * Q.get((s,a),0) + alpha * (rew + gamma * max(qvalues(ns)))
        cum_rewards.append(cum_reward)
        rewards.append(cum_reward)
        # == הדפס תוצאות מחזוריות וחשב את התגמול הממוצע ==
        if epoch%5000==0:
            print(f"{epoch}: {np.average(cum_rewards)}, alpha={alpha}, epsilon={epsilon}")
            if np.average(cum_rewards) > Qmax:
                Qmax = np.average(cum_rewards)
                Qbest = Q
            cum_rewards=[]
    

מה שאולי תבחין מהתוצאות האלה:

  • קרוב למטרה שלנו. אנו מאוד קרובים להשיג את המטרה של 195 תגמולים מצטברים על פני 100+ הרצות רצופות של הסימולציה, ואולי אפילו השגנו זאת! אפילו אם נקבל מספרים נמוכים יותר, לא נדע בוודאות, כי אנו מחשבים ממוצע על פני 5000 הרצות, ובקריטריון הרשמי נדרשות רק 100 הרצות.

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

תופעה זו נראית בבהירות גדולה יותר אם נציג גרף של תהליך האימון.

הצגת התקדמות האימון

במהלך האימון אספנו את ערך התגמול המצטבר בכל איטרציה לתוך מערך rewards. כך זה נראה כאשר מציגים את הגרף כפונקציה של מספר האיטרציה:

plt.plot(rewards)

התקדמות גולמית

מהגרף הזה אי אפשר להסיק דבר, כי בשל אופיו הסטוכסטי של תהליך האימון, אורך סשני האימון משתנה במידה רבה. כדי להבין טוב יותר את הגרף, נחשב את הממוצע הנע על פני סדרה של ניסויים, נניח 100. זה נעשה בנוחות באמצעות np.convolve: (קוד בלוק 12)

def running_average(x,window):
    return np.convolve(x,np.ones(window)/window,mode='valid')

plt.plot(running_average(rewards,100))

התקדמות האימון

שינוי היפר-פרמטרים

כדי להפוך את הלמידה ליציבה יותר, כדאי להתאים חלק מההיפר-פרמטרים במהלך האימון. במיוחד:

  • לקצב הלמידה, alpha, כדאי להתחיל בערכים קרובים ל-1, ואז להמשיך ולהוריד את הפרמטר. עם הזמן, נקבל ערכי הסתברות טובים בטבלת Q, ולכן נרצה להתאים אותם במעט ולא להחליף לגמרי בערכים חדשים.

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

משימה 1: שחק עם ערכי ההיפר-פרמטרים וראה אם אתה יכול להשיג תגמול מצטבר גבוה יותר. האם אתה מגיע מעל 195?

משימה 2: כדי לפתור את הבעיה באופן פורמלי, עליך לקבל ממוצע תגמולים של 195 לאורך 100 הרצות רצופות. מדוד זאת במהלך האימון וודא שפתרת את הבעיה באופן פורמלי!

לראות את התוצאה בפעולה

יהיה מעניין לראות בפועל כיצד המודל המאומן מתנהג. בואו נפעיל את הסימולציה ונעקוב אחרי אסטרטגיית בחירת הפעולה כפי שבוצעה באימון, על ידי דגימה לפי התפלגות ההסתברות בטבלת Q: (בלוק קוד 13)

obs = env.reset()
done = False
while not done:
   s = discretize(obs)
   env.render()
   v = probs(np.array(qvalues(s)))
   a = random.choices(actions,weights=v)[0]
   obs,_,done,_ = env.step(a)
env.close()

אמור לראות משהו כזה:

עגלת איזון פנדולום


🚀אתגר

משימה 3: כאן השתמשנו בגירסה הסופית של טבלת Q, שייתכן ואינה הטובה ביותר. זכור ששמרנו את טבלת Q הביצועית הטובה ביותר במשתנה Qbest! נסה את אותו הדוגמה עם טבלת Q הטובה ביותר על ידי העתקת Qbest ל-Q וראה אם אתה מבחין בהבדל.

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

חידון לאחר ההרצאה

משימה

אמן רכב הרים

סיכום

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

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


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