אופטימיזציה של מדיניות פרוקסימלית (PPO) היא אחת האלגוריתמים המועדפים לפתרון בעיות למידת חיזוק (RL). היא פותחה בשנת 2017 על ידי ג'ון שומאן, אחד ממייסדי OpenAI.
PPO שימשה באופן נרחב ב-OpenAI לאימון מודלים כדי לחקות התנהגות אנושית. היא משפרת שיטות קודמות כמו אופטימיזציה של מדיניות באזור אמון (TRPO) והפכה לפופולרית מכיוון שהיא אלגוריתם חזק ויעיל.
בטutorial הזה, נבחן את PPO לעומק. נסקור את התיאוריה ונראה כיצד ליישם אותה באמצעות PyTorch.
הבנת אופטימיזציה של מדיניות פרוקסימלית (PPO)
אלגוריתמים קונבנציונליים של למידה מפוקחת מעדכנים את הפרמטרים בכיוון של הגרדיאנט התלול ביותר. אם העדכון הזה מתברר כגדול מדי, הוא מתוקן במהלך דוגמאות אימון הבאות שאינן תלויות זו בזו.
עם זאת, דוגמאות האימון בלמידת חיזוק כוללות את פעולותיו של הסוכן ותשואותיו. לכן, דוגמאות האימון תלויות זו בזו. הסוכן חוקר את הסביבה כדי לגלות את המדיניות האופטימלית. לפיכך, שינויים גדולים בגרדיאנט יכולים לגרום למדיניות להיתקע באיזור רע עם תגמולים לא אופטימליים. מכיוון שהסוכן צריך לחקור את הסביבה, שינויים גדולים במדיניות מקנים לתהליך האימון חוסר יציבות.
שיטות המבוססות על אזור האמון נועדו למנוע את בעיה זו על ידי הבטיחות כי עדכוני מדיניות נמצאים בתוך אזור אמון. אזור האמון הזה הוא אזור מוגבל באופן מלאכותי בתוך מרחב המדיניות שבתוךו מותרים עדכונים. המדיניות המעודכנת יכולה להיות רק בתוך אזור האמון של המדיניות הישנה. הבטיחות שעדכוני מדיניות הם גורמיים מונים לאי יציבות.
עדכוני מדיניות של אזור האמון (TRPO)
אלגוריתם עדכוני מדיניות של אזור האמון (TRPO) הוצע בשנת 2015 על ידי ג'ון שולמן (שגם הציע את PPO בשנת 2017). כדי למדוד את ההבדל בין המדיניות הישנה והמדיניות המעודכנת, TRPO משתמש בפיצול קולבק-לייבלר (KL). פיצול KL משמש למדידת ההבדל בין שתי פיזורי סיכויים. TRPO הוכיח להיות יעיל ביישום אזורי אמון.
הבעיה עם TRPO היא המורכבות החישובית הקשורה לפיצול KL. יישום פיצול KL צריך להיות מורחב לסדר שני באמצעות שיטות מספריות כמו הרחבת טיילור. זה עולה בעלות חישובית. PPO הוצע כאלטרנטיבה פשוטה ויעילה יותר ל-TRPO. PPO מקטין את היחס בין המדיניות כדי לחישב את אזור האמון ללא צורך בחישובים מורכבים הכוללים פיצול KL.
זו הסיבה שבגללה PPO הפכה להיות מועדפת על TRPO בפתרון בעיות של למידה חישובית. עקב שיטת הערכת אזורי האמון היעילה יותר, PPO מאזנת ביעילות בין ביצועים ויציבות.
הערכת מדיני פוליטיקה מקרבת (PPO)
PPO לעתים קרובות מובנת כתת-מחלקה של שיטות שחקן-ביקורת, שמעדכנות את הגרדיאנטים של המדיניות על סמך פונקציית הערך. שיטות שחקן-ביקורת בית המערכת (A2C) משתמשות בפרמטר הנקרא היתרון. זה מודד את ההבדל בין ההחזרים שניבאו על ידי הביקורת וההחזרים הממומשים על ידי ביצוע המדיניות.
כדי להבין את PPO, עליך להבין את הרכיבים שלה:
- השחקן מבצע את המדיניות. זה מיושם כרשת עצבים. בהתחשב במצב כקלט, זה מייצא את הפעולה לביצוע.
- הביקורת היא רשת עצבים נוספת. זה קולט את המצב כקלט ומעביר את הערך הצפוי של אותו מצב. לכן, הביקורת מבטאת את פונקציית ערך המצב.
- שיטות מבוססות גרדיינטי על פוליסי מסוגלות לבחור להשתמש בפונקציות מטרה שונות. במיוחד, PPO משתמשת בפונקציית היתרון. פונקציית היתרון מודדת את הכמות שבה הפרסום הסכום (המבוסס על המדיניות שיושמת על ידי השחקן) עולה על הפרס הבסיסי הצפוי (כפי שניבא על ידי הביקורת). המטרה של PPO היא להגביר את הסיכוי לבחירת פעולות עם יתרון גבוה. המטרה לייעול של PPO משתמשת בפונקציות ההפסד המבוססות על פונקציית היתרון הזו.
- הפונקצית המטרה המגוזזת היא המהפכה העיקרית ב-PPO.היא מונעת עדכונים גדולים במדיניות באיטרציה אחת של אימון. היא מגבילה כמה המדיניות מתעדכנת באיטרציה אחת. כדי למדוד עדכוני מדיניות בהתבסס על מדיניות, שיטות המבוססות על מדיניות משתמשות ביחס ההסתברות של המדיניות החדשה למדיניות הישנה.
- ההפסד השולי הוא הפונקצית המטרה ב-PPO והוא לוקח בחשבון את המהפכות שצוינו לעיל.הוא מחושב באופן הבא:
- חישוב היחס האמיתי (כפי שנסבר קודם) וכפלו ביתרון.
- לחתוך את היחס כך שישאר בטווח הרצוי. לכפול את היחס המקוטע ליתרון.
- לקחת את הערך המינימלי של שני הכמויות הנ"ל.
- בפועל, מוסיפים תוספת של אנטרופיה להפסד השיקול החליפי. כך נקרא את תוספת האנטרופיה. היא מבוססת על ההפצה המתמטית של סבירויות פעולה. הרעיון שמאחורי תוספת האנטרופיה הוא להכניס קצת ריבויות נוספות באופן שליטה. בעשות זאת, זה מעודד את תהליך האופטימיזציה לחקור את מרחב הפעולות. תוספת אנטרופיה גבוהה מקדמת חקירה על פני ניצול.
הבנת מנגנון החיתוך
נניח כי על פי המדיניות הישנה πישנה, הסתברות לביצוע פעולה a במצב s היא πישנה(a|s). בהתאם למדיניות החדשה, הסתברות לביצוע אותה פעולה a מאותו מצב s מתעדכנת ל πחדשה(a|s). היחס בין הסתברויות הללו, כפונקציה של פרמטרי המדיניות θ, הוא r(θ). כאשר המדיניות החדשה מגבירה את הסתברות הפעולה (באותו מצב), היחס גדול מ-1 ולהיפך.
המנגנון לחיתוך מגביל את יחס ההסתברות הזה כך שסבירויות הפעולה החדשות חייבות להיות בתוך אחוז מסוים מסבירויות הפעולה הישנות. לדוגמה, r(θ) ניתן להגביל כך שיהיה בין 0.8 ל-1.2. זה מונע קפיצות גדולות, שבתורן מבטיח שלתהליך האימון יהיה יציב.
בשאר המאמר תלמד כיצד להרכיב את הרכיבים עבור מימוש פשוט של PPO באמצעות PyTorch.
1. הגדרת הסביבה
לפני המימוש של PPO, אנו צריכים להתקין את ספריות התוכנה הדרושות ולבחור סביבה מתאימה ליישום המדיניות.
התקנת PyTorch וספריות הדרושות
אנו צריכים להתקין את התוכנה הבאה:
- PyTorch וספריות תוכנה נוספות, כגון
numpy
(לפונקציות מתמטיות וסטטיסטיות) ו־matplotlib
(לשרטוט גרפים). - החבילה הפתוחה Gym מ-OpenAI, ספריית Python שמדמה סביבות ומשחקים שונים, שניתן לפתור באמצעות למידת ה ref="https://he.wikipedia.org/wiki/Reinforcement_learning">רינפורסמנט. ניתן להשתמש ב- API של Gym כדי לאפשר לאלגוריתמים שלך להתקשר עם הסביבה. מאחר שהפונקציונליות של
gym
יכולה לשנות במהלך תהליך השדרוג, בדוגמה זו אנו מקפיאים את הגרסה של0.25.2
.
כדי להתקין על שרת או מחשב מקומי, יש להריץ:
$ pip install torch numpy matplotlib gym==0.25.2
כדי להתקין באמצעות מחשב נייד דומה ל־Google Colab או DataLab, יש להשתמש:
!pip install torch numpy matplotlib gym==0.25.2
ליצירת סביבת ה־CartPole
יש להשתמש ב־OpenAI Gym כדי ליצור שני מופעים (אחד לאימון והשני לבדיקה) של סביבת ה־CartPole:
env_train = gym.make('CartPole-v1') env_test = gym.make('CartPole-v1')
2. מימוש של PPO ב־PyTorch
עכשיו, בואו נממש את PPO באמצעות PyTorch.
הגדרת רשת המדיניות
כפי שנסביר בקטע הקודם, PPO מיושם כמודל של שחקן־צופה. השחקן מיישם את המדיניות, והצופה חוזה את הערך המשוער שלה. הרשתות העצביות של השחקן והצופה מקבלות את אותו קלט—המצב בכל צעד זמן. לכן, רשת השחקן ורשת הצופה יכולות לשתף רשת עצבית משותפת, שנקראת ארכיטקטורת השדרה. השחקן והצופה יכולים להרחיב את ארכיטקטורת השדרה עם שכבות נוספות.
הגדרת רשת הארכיטקטורה
השלבים הבאים מתארים את רשת הארכיטקטורה:
- מימוש רשת עם 3 שכבות – קלט, נסתר ופלט.
- אחרי השכבות של הקלט והנסתר, אנו משתמשים בפונקציית ההפעלה. במדריך זה, בחרנו ב־ReLU משום שהוא יעיל חישובית.
- אנו גם מחייבים פונקציית נפילה לאחר השכבות הקלט והמוסתרות כדי לקבל רשת חזקה. פונקציית הנפילה מאפסת אקראית חלק מנוירונים. זה מפחית התמקדות בנוירונים ספציפיים ומונע עובדות מיות, מה שהופך את הרשת לחזקה יותר.
הקוד למטה מיישם את השלד:
class BackboneNetwork(nn.Module): def __init__(self, in_features, hidden_dimensions, out_features, dropout): super().__init__() self.layer1 = nn.Linear(in_features, hidden_dimensions) self.layer2 = nn.Linear(hidden_dimensions, hidden_dimensions) self.layer3 = nn.Linear(hidden_dimensions, out_features) self.dropout = nn.Dropout(dropout) def forward(self, x): x = self.layer1(x) x = f.relu(x) x = self.dropout(x) x = self.layer2(x) x = f.relu(x) x = self.dropout(x) x = self.layer3(x) return x
הגדר את רשת השחקן-קריטיק
עכשיו, נוכל להשתמש ברשת זו כדי להגדיר את מחלקת השחקן-קריטיק, ActorCritic
. השחקן מבצע תכנית ומנבא את הפעולה. הקריטיק מבצע תפקיד של פונקציית הערך ומנבא את הערך. הם שניהם מקבלים את המצב כקלט.
class ActorCritic(nn.Module): def __init__(self, actor, critic): super().__init__() self.actor = actor self.critic = critic def forward(self, state): action_pred = self.actor(state) value_pred = self.critic(state) return action_pred, value_pred
יצירת מופעים של רשתות השחקן והקריטיק
נשתמש ברשתות המוגדרות למעלה כדי ליצור שחקן וקריטיק. לאחר מכן, ניצור סוכן, כולל את השחקן והקריטיק.
לפני יצירת הסוכן, נאתחל את הפרמטרים של הרשת:
- ממדי שכבת המוסתר, H, שהוא פרמטר ניתן להגדרה. גודל ומספר השכבות המוסתרות תלויים במורכבות של הבעיה. נשתמש בשכבה מוסתרת בממדים 64 X 64.
- מאפייני הקלט, N, שבו N הוא גודל מערך המצב. שכבת הקלט כוללת N X H ממדים. בסביבת CartPole, המצב הוא מערך בעל 4 איברים. לכן N הוא 4.
- מאפייני הפלט של רשת השחקן, O, שבו O הוא מספר הפעולות בסביבה. שכבת הפלט של השחקן כוללת ממדים H x O. בסביבת CartPole יש 2 פעולות.
- מאפייני הפלט של רשת הקריטיק. מאחר שרשת הקריטיק מנבאת רק את הערך הצפוי (נתון מצב קלט), מספר המאפיינים המאוחזרים הוא 1.
- התיקול כשבר.
הקוד הבא מציג כיצד להצהיר על רשתות השחקן והביקורת בהתבסס על רשת הגב.
def create_agent(hidden_dimensions, dropout): INPUT_FEATURES = env_train.observation_space.shape[0] HIDDEN_DIMENSIONS = hidden_dimensions ACTOR_OUTPUT_FEATURES = env_train.action_space.n CRITIC_OUTPUT_FEATURES = 1 DROPOUT = dropout actor = BackboneNetwork( INPUT_FEATURES, HIDDEN_DIMENSIONS, ACTOR_OUTPUT_FEATURES, DROPOUT) critic = BackboneNetwork( INPUT_FEATURES, HIDDEN_DIMENSIONS, CRITIC_OUTPUT_FEATURES, DROPOUT) agent = ActorCritic(actor, critic) return agent
חישוב ההחזרים
הסביבה נותנת פרס בהתאם לפעולת הסוכן מכל שלב לשלב הבא. הפרס, R, מובא כ:
ההחזר מוגדר כערך הצפוי המתקבצ של פרסים עתידיים. פרסים משלבי זמן הרחוקים יותר בעתיד פחות ערכיים מפרסים מיידיים. לכן, ההחזר מחושב כהחזר מושלם, G, המוגדר כ:
במדריך זה (ובהפניות רבות אחרות), ההחזר מתייחס להחזר מושלם.
כדי לחשב את ההחזר:
- מתחילים עם הפרסים הצפויים מכל המדינות העתידיות.
- מכפילים כל פרס עתידי בחזקת גורם ההנחה, . לדוגמה, הפרס הצפוי אחרי 2 שלבי זמן (מהווה) מוכפל ב-2.
- מסכמים את כל הפרסים העתידיים בהנחה כדי לחשב את ההחזר.
- מכוונים את ערך ההחזר.
הפונקציה calculate_returns()
מבצעת את החישובים הללו, כפי שמוצג להלן:
def calculate_returns(rewards, discount_factor): returns = [] cumulative_reward = 0 for r in reversed(rewards): cumulative_reward = r + cumulative_reward * discount_factor returns.insert(0, cumulative_reward) returns = torch.tensor(returns) # תיקול ההחזר returns = (returns - returns.mean()) / returns.std() return returns
מיושם של פונקציית היתרון
היתרון מחושב כהפרש בין הערך המיוחס על ידי הביקורת וההחזר הצפוי מהפעולות שנבחרו על ידי השחקן לפי המדיניות. עבור פעולה נתון, היתרון מבטא את התועלת של לקחת את הפעולה הספציפית הזו על פני פעולה רשאית (ממוצעת).
במאמר המקורי PPO (משוואה 10), היתרון, כשמסתכלים קדימה עד לאותו צעד T, מתבטא כך:
בעת קידוד האלגוריתם, ההגבלה של הסתכלות קדימה עד למספר מוגדר של צעדים מאכפת דרך גודל המערך. כך, המשוואה למעלה יכולה להתפשט כהפרש בין הערך לבין התשואות הצפויות. התשואות הצפויות מתוארות בפונקציית הערך של מצב-פעולה, Q.
לכן, הנוסחה הפשוטה למטה מתארת את היתרון של בחירת:
- פעולה מסוימת
- במצב נתון
- במדיניות מסוימת
- בצעד מסוים
זה מתואר כך:
OpenAI משתמשת גם בנוסחה זו ליישום RL. הפונקציה calculate_advantages()
המוצגת למטה מחשבת את היתרון:
def calculate_advantages(returns, values): advantages = returns - values # נורמליזציה של היתרון advantages = (advantages - advantages.mean()) / advantages.std() return advantages
מנגנון אובדן חליפי וחיתוך
ההפסד במדיניות יהיה ההפסד הסטנדרטי לגרדיאנט המדיניות ללא טכניקות מיוחדות כמו PPO. ההפסד הסטנדרטי לגרדיאנט המדיניות נחשב כמכפלה של:
- ההסתברויות של פעולת המדיניות
- פונקציית היתרון, שנחשבת כהבדל בין:
- ההחזר של המדיניות
- הערך הצפוי
ההפסד הסטנדרטי לגרדיאנט המדיניות לא יכול לבצע תיקונים עבור שינויים פתאומיים במדיניות. ההפסד החילופי משנה את ההפסד הסטנדרטי כדי להגביל את כמות השינוי שהמדיניות יכולה לעשות בכל איטרציה. זהו המינימום של שני סכומים:
- המוצר של:
- יחס המדיניות. יחס זה מבטא את ההבדל בין ההסתברויות הישנות והחדשות.
- פונקציית היתרון
- המוצר של:
- ערך היחס של יחס המדיניות. יחס זה מוגבל כך שהמדיניות המעודכנת תהיה בתוך אחוז מסוים מהמדיניות הישנה.
- פונקציית היתרון
לתהליך האופטימיזציה, ההפסד המחליף משמש כפרוקסי להפסד האמיתי.
מנגנון הקליפינג
יחס המדיניות, R, הוא ההבדל בין מדיניות החדשה והישנה ומתקבל כיחס הלוג של הסיכויים של המדיניות תחת הפרמטרים החדשים לעומת הישנים:
יחס המדיניות המקוצץ, R', מוגבל כך ש:
נתונה היתרון, At, כפי שנראה בקטע הקודם, ויחס המדיניות, כפי שנראה למעלה, ההפסד המחליף נחשב כ:
הקוד למטה מציג איך ליישם את מנגנון הקיטום ואת ההפסד המחליף.
def calculate_surrogate_loss( actions_log_probability_old, actions_log_probability_new, epsilon, advantages): advantages = advantages.detach() policy_ratio = ( actions_log_probability_new - actions_log_probability_old ).exp() surrogate_loss_1 = policy_ratio * advantages surrogate_loss_2 = torch.clamp( policy_ratio, min=1.0-epsilon, max=1.0+epsilon ) * advantages surrogate_loss = torch.min(surrogate_loss_1, surrogate_loss_2) return surrogate_loss
3. אימון הסוכן
כעת, בואו נאמן את הסוכן.
חישוב ההפסדים במדיניות ובערך
אנו מוכנים כעת לחשב את ההפסדים במדיניות ובערך:
- ההפסד במדיניות הוא סכום ההפסד המחליף ובונוס האנטרופיה.
- ההפסד בערך מבוסס על ההבדל בין הערך שניבא על ידי הבודק לבין ההחזרים (פרס כולל) שנוצרים על ידי המדיניות. חישוב ההפסד בערך משתמש בפונקציית ההפסד Smooth L1 Loss. זה עוזר להחליק את פונקציית ההפסד ומרכז אותה פחות לשוטרים.
שני ההפסדים, כפי שחושבו לעיל, הם טנזורים. ירידת הגרדיאנט מבוססת על ערכים סקלריים. כדי לקבל ערך סקלרי יחיד המייצג את ההפסד, עליך להשתמש בפונקציית .sum() כדי לסכם את רכיבי הטנזור. הפונקציה להלן מציגה כיצד לעשות זאת:
def calculate_losses( surrogate_loss, entropy, entropy_coefficient, returns, value_pred): entropy_bonus = entropy_coefficient * entropy policy_loss = -(surrogate_loss + entropy_bonus).sum() value_loss = f.smooth_l1_loss(returns, value_pred).sum() return policy_loss, value_loss
הגדרת לולאת האימון
לפני התחלת תהליך האימון, עליך ליצור מערכת של באפרים כמערכות ריקות. אלגוריתם האימון ישתמש בבאפרים אלו כדי לאחסן מידע על פעולות הסוכן, מצבי הסביבה, והפרסוות בכל שלב זמן. הפונקציה למטה מאתחלת את הבאפרים הללו:
def init_training(): states = [] actions = [] actions_log_probability = [] values = [] rewards = [] done = False episode_reward = 0 return states, actions, actions_log_probability, values, rewards, done, episode_reward
כל איטרציה באימון מריצה את הסוכן עם פרמטרי המדיניות לאיטרציה זו. הסוכן מתקשר עם הסביבה בצעדי זמן בלולאה עד שהוא מגיע לתנאי סופי.
לאחר כל צעד זמן, פעולת הסוכן, הפרס והערך מתווספים לבאפרים המתאימים. כאשר הפרק מסתיים, הפונקציה מחזירה את מערכת הבאפרים המעודכנת, שמסכמת את תוצאות הפרק.
לפני הפעלת לולאת האימון:
- קבע את המודל למצב אימון באמצעות
agent.train()
. - אפס את הסביבה למצב אקראי באמצעות
env.reset()
. זהו המצב ההתחלתי לסיבוב האימון הזה.
השלבים הבאים מסבירים מה קורה בכל צעד זמן בלולאת האימון:
- העבר את המצב לאוגן.
- האוגן מחזיר:
- הפעולה המיוחזקת בהתבסס על המדינה, בהתאם למדיניות (שחקן). העבר את טנזור הפעולה המיוחזקת הזה דרך פונקציית הסופטמקס כדי לקבל את קבוצת ההסתברויות של הפעולות.
- הערך המתוחזק של המצב, מבוסס על הביקורת.
- הסוכן בוחר את הפעולה לביצוע:
- השתמש בהסתברויות הפעולה כדי להעריך את חלוקת ההסתברות.
- בחירת פעולה באופן אקראי על ידי בחירת דוגמה מתוך החלוקה הזו. פונקציית
dist.sample()
עושה זאת. - השתמש בפונקציית
env.step()
כדי להעביר את הפעולה הזו לסביבה כדי לדמות את תגובת הסביבה עבור צעד זה. בהתבסס על הפעולה של הסוכן, הסביבה יוצרת: - המצב החדש
- הפרס
- ערך החזרת הבוליאני
done
(מציין האם הסביבה הגיעה למצב סופי) - מחבר לבאפרים המתאימים את ערכי פעולת הסוכן, הפרסים, הערכים המתוחזקים והמצב החדש.
הפרק האימון מסתיים כאשר פונקציית env.step()
מחזירה true
עבור ערך ההחזרה הבוליאני של done
.
לאחר שהפרק הסתיים, יש להשתמש בערכים המצטברים מכל צעד זמן כדי לחשב את ההחזרים הצפויים מהפרק הזה על ידי חיבור הפרסויות מכל צעד זמן. אנו משתמשים בפונקצית calculate_returns()
המתוארת מראש לעשות זאת. הקלטים של פונקציה זו הם מגדל ההנחה והבאפר שמכיל את הפרסויות מכל צעד זמן. אנו משתמשים בהחזרים אלו ובערכים המצטברים מכל צעד זמן כדי לחשב את היתרונות באמצעות הפונקצית calculate_advantages()
.
הפונקציה ב-Python הבאה מראה איך ליישם את השלבים הללו:
def forward_pass(env, agent, optimizer, discount_factor): states, actions, actions_log_probability, values, rewards, done, episode_reward = init_training() state = env.reset() agent.train() while not done: state = torch.FloatTensor(state).unsqueeze(0) states.append(state) action_pred, value_pred = agent(state) action_prob = f.softmax(action_pred, dim=-1) dist = distributions.Categorical(action_prob) action = dist.sample() log_prob_action = dist.log_prob(action) state, reward, done, _ = env.step(action.item()) actions.append(action) actions_log_probability.append(log_prob_action) values.append(value_pred) rewards.append(reward) episode_reward += reward states = torch.cat(states) actions = torch.cat(actions) actions_log_probability = torch.cat(actions_log_probability) values = torch.cat(values).squeeze(-1) returns = calculate_returns(rewards, discount_factor) advantages = calculate_advantages(returns, values) return episode_reward, states, actions, actions_log_probability, advantages, returns
עדכון פרמטרי המודל
בכל איטרציה של האימון מריצים את המודל דרך פרק מלא הכולל מספר רב של צעדי זמן (עד שהוא מגיע לתנאי סופי). בכל צעד זמן, אנו שומרים את פרמטרי המדיניות, את הפעולה של הסוכן, את ההחזרים ואת היתרונות. לאחר כל איטרציה, אנו מעדכנים את המודל על סמך ביצועי המדיניות בכל הצעדים באותה איטרציה.
מספר הצעדים המרבי בסביבת CartPole הוא 500. בסביבות מורכבות יותר, קיימים יותר צעדים, אף מיליונים. במקרים כאלה, עליך לחלק את קובץ נתוני תוצאות האימון לסדרות. מספר הצעדים בכל סדרה נקרא גודל הסדרה לאופטימיזציה.
לכן, השלבים לעדכון פרמטרי המודל הם:
- לחלק את קובץ נתוני תוצאות האימון לסדרות.
- לכל סדרה:
- לקבל את פעולת הסוכן ואת הערך המנובא לכל מצב.
- להשתמש בפעולות המנובאות אלו כדי לאפס את ההתפלגות החדשה של ההסתברות של פעולה.
- השתמש בהפצה זו כדי לחשב את האנטרופיה.
- השתמש בהפצה זו כדי לקבל את הלוג ההסתברות של הפעולות בסט התוצאות של האימון. זהו סט הלוג ההסתברות החדש של הפעולות בסט התוצאות של האימון. הסט הישן של הלוג ההסתברות של אותן הפעולות נחשב בלולאת האימון שנתונה בסעיף הקודם.
- חשב את ההפסד המחליף באמצעות ההפצות הישנות והחדשות של ההסתברויות.
- חשב את ההפסד במדיניות ואת ההפסד בערך באמצעות ההפסד המחליף, האנטרופיה והיתרונות.
- הרץ
.backward()
בנפרד על ההפסדים במדיניות ובערך. זה מעדכן את הגרדיאנטים על פונקציות ההפסד. - הרץ
.step()
על האופטימיזר כדי לעדכן את פרמטרי המדיניות. במקרה זה, אנו משתמשים באופטימיזר Adam כדי לאזן בין מהירות ובין עמידות. - אסוף את ההפסדים של המדיניות והערך.
- חזור על המעבר לאחור (הפעולות לעיל) על כל אצווה מספר פעמים, בהתאם לערך של הפרמטר
PPO_STEPS
. החזרת המעבר לאחור על כל אצווה היא יעילה מבחינה חישובית מכיוון שהיא מגדילה ביעילות את גודל סט הנתונים לאימון מבלי להריץ מעברים לפנים נוספים. מספר השלבים בסביבה בכל תחלופה בין דגימה ואופטימיזציה נקרא גודל האצווה בכל איטרציה. - החזר את ממוצע ההפסדים של המדיניות והערך.
הקוד למטה מיישם את השלבים הללו:
def update_policy( agent, states, actions, actions_log_probability_old, advantages, returns, optimizer, ppo_steps, epsilon, entropy_coefficient): BATCH_SIZE = 128 total_policy_loss = 0 total_value_loss = 0 actions_log_probability_old = actions_log_probability_old.detach() actions = actions.detach() training_results_dataset = TensorDataset( states, actions, actions_log_probability_old, advantages, returns) batch_dataset = DataLoader( training_results_dataset, batch_size=BATCH_SIZE, shuffle=False) for _ in range(ppo_steps): for batch_idx, (states, actions, actions_log_probability_old, advantages, returns) in enumerate(batch_dataset): # get new log prob of actions for all input states action_pred, value_pred = agent(states) value_pred = value_pred.squeeze(-1) action_prob = f.softmax(action_pred, dim=-1) probability_distribution_new = distributions.Categorical( action_prob) entropy = probability_distribution_new.entropy() # estimate new log probabilities using old actions actions_log_probability_new = probability_distribution_new.log_prob(actions) surrogate_loss = calculate_surrogate_loss( actions_log_probability_old, actions_log_probability_new, epsilon, advantages) policy_loss, value_loss = calculate_losses( surrogate_loss, entropy, entropy_coefficient, returns, value_pred) optimizer.zero_grad() policy_loss.backward() value_loss.backward() optimizer.step() total_policy_loss += policy_loss.item() total_value_loss += value_loss.item() return total_policy_loss / ppo_steps, total_value_loss / ppo_steps
4. ביצוע ה-Agent PPO
ונפעיל את ה-Agent PPO.
הערכת הביצועים
כדי להעריך את ביצועי ה-Agent, ניצור סביבה חדשה ונחשב את התקן המצטבר מהרצת ה-Agent בסביבה חדשה זו. עליך להגדיר את ה-Agent למצב הערכתי באמצעות הפונקציה .eval()
. השלבים דומים לשלבי האימון. קטע הקוד למטה מיישם את פונקציית ההערכה:
def evaluate(env, agent): agent.eval() rewards = [] done = False episode_reward = 0 state = env.reset() while not done: state = torch.FloatTensor(state).unsqueeze(0) with torch.no_grad(): action_pred, _ = agent(state) action_prob = f.softmax(action_pred, dim=-1) action = torch.argmax(action_prob, dim=-1) state, reward, done, _ = env.step(action.item()) episode_reward += reward return episode_reward
הצגת תוצאות האימון
נשתמש בספריית Matplotlib כדי להמחיש את ההתקדמות של תהליך האימון. הפונקציה למטה מראה איך לבצע עיגול על התקן משני לולאות:
def plot_train_rewards(train_rewards, reward_threshold): plt.figure(figsize=(12, 8)) plt.plot(train_rewards, label='Training Reward') plt.xlabel('Episode', fontsize=20) plt.ylabel('Training Reward', fontsize=20) plt.hlines(reward_threshold, 0, len(train_rewards), color='y') plt.legend(loc='lower right') plt.grid() plt.show()
def plot_test_rewards(test_rewards, reward_threshold): plt.figure(figsize=(12, 8)) plt.plot(test_rewards, label='Testing Reward') plt.xlabel('Episode', fontsize=20) plt.ylabel('Testing Reward', fontsize=20) plt.hlines(reward_threshold, 0, len(test_rewards), color='y') plt.legend(loc='lower right') plt.grid() plt.show()
בדוגמאות התרשימים למטה, אנו מראים את הפרסויות של האימון והבדיקה, שנרכשות על ידי החלפת המדיניות בסביבות האימון והבדיקה בהתאמה. שים לב שצורת התרשימים הללו תראה שונה בכל פעם שתריץ את הקוד. זה בגלל האקראיות הטבעית לתהליך האימון.
פרסויות האימון (נרכשות על ידי החלפת המדיניות בסביבת האימון). תמונה על ידי המחבר.
פרסויות הבדיקה (נרכשות על ידי החלפת המדיניות בסביבת הבדיקה). תמונה על ידי המחבר.
בתרשימי הפלט שמוצגים למעלה, שים לב להתקדמות תהליך האימון:
- הפרס התחיל מערכים נמוכים. ככל שהאימון מתקדם, הפרסים מתרבים.
- הפרסים מתנדנדים באופן אקראי במהלך ההתקדמות. זה קשור לכך שהסוכן חוקר את אזור מדיניות הפעולה.
- האימון מסתיים, והפרסים בבדיקה יציבים סביב הרמת הסף (475) למשך מספר רב של איטרציות.
- הפרסים מוגבלים ב-500. אלה אילוצים שהוטלו על ידי הסביבה (Gym CartPole v1).
באותה מידה, ניתן לשרטט את הערכים וההפסדים של מדיניות דרך האיטרציות:
def plot_losses(policy_losses, value_losses): plt.figure(figsize=(12, 8)) plt.plot(value_losses, label='Value Losses') plt.plot(policy_losses, label='Policy Losses') plt.xlabel('Episode', fontsize=20) plt.ylabel('Loss', fontsize=20) plt.legend(loc='lower right') plt.grid() plt.show()
הגרף המופיע למטה מציג את ההפסדים שנמצאים במעקב במהלך פרקי האימון:
ערכים ואובדנים במדיניות דרך תהליך האימון. תמונה מאת המחבר
שקוף את המקטע ושים לב:
- האובדנים נראים כאילו מיושבים באופן אקראי ואינם עוקבים אחר כל דפוס.
- זה טיפוסי לאימון ב-RL, שבו המטרה אינה למזער את האובדן אלא למקסם את הפרסומים.
הפעל את אלגוריתם PPO
כעת יש לך את כל הרכיבים לאימון הסוכן באמצעות PPO. כדי לשלב את הכל, עליך:
- להכריז על היפרפרמטרים כמו גורם הנחה, גודל מצע, קצב למידה, וכו'.
- יצור מערכי buffets כמערכים ריקים כדי לאחסן את הפרסים וההפסדים מכל טיוטה.
- צור מופע Agent באמצעות פונקציית
create_agent()
. - הרץ פונקציות forward_pass() ו- update_policy() בצורה רצופה.
- בדוק את ביצועי המדיניות באמצעות פונקציית
evaluate()
. - הוסף את המדיניות, ההפסדים בערך והפרסים מהפונקציות להמסיפים המתאימים.
- חשבו את הממוצע של הפרסים וההפסדים במהלך הצעדים האחרונים. הדוגמה למטה מחשבת את הפרסים וההפסדים במהלך 40 צעדי זמן.
- הדפיסו את תוצאות ההערכה מדי מספר צעדים. הדוגמה למטה מדפיסה כל 10 צעדים.
- סיימו את התהליך כאשר הפרס הממוצע חוצה את אחד מהסף.
הקוד למטה מראה איך להכריז על פונקציה שעושה זאת בפייתון:
def run_ppo(): MAX_EPISODES = 500 DISCOUNT_FACTOR = 0.99 REWARD_THRESHOLD = 475 PRINT_INTERVAL = 10 PPO_STEPS = 8 N_TRIALS = 100 EPSILON = 0.2 ENTROPY_COEFFICIENT = 0.01 HIDDEN_DIMENSIONS = 64 DROPOUT = 0.2 LEARNING_RATE = 0.001 train_rewards = [] test_rewards = [] policy_losses = [] value_losses = [] agent = create_agent(HIDDEN_DIMENSIONS, DROPOUT) optimizer = optim.Adam(agent.parameters(), lr=LEARNING_RATE) for episode in range(1, MAX_EPISODES+1): train_reward, states, actions, actions_log_probability, advantages, returns = forward_pass( env_train, agent, optimizer, DISCOUNT_FACTOR) policy_loss, value_loss = update_policy( agent, states, actions, actions_log_probability, advantages, returns, optimizer, PPO_STEPS, EPSILON, ENTROPY_COEFFICIENT) test_reward = evaluate(env_test, agent) policy_losses.append(policy_loss) value_losses.append(value_loss) train_rewards.append(train_reward) test_rewards.append(test_reward) mean_train_rewards = np.mean(train_rewards[-N_TRIALS:]) mean_test_rewards = np.mean(test_rewards[-N_TRIALS:]) mean_abs_policy_loss = np.mean(np.abs(policy_losses[-N_TRIALS:])) mean_abs_value_loss = np.mean(np.abs(value_losses[-N_TRIALS:])) if episode % PRINT_INTERVAL == 0: print(f'Episode: {episode:3} | \ Mean Train Rewards: {mean_train_rewards:3.1f} \ | Mean Test Rewards: {mean_test_rewards:3.1f} \ | Mean Abs Policy Loss: {mean_abs_policy_loss:2.2f} \ | Mean Abs Value Loss: {mean_abs_value_loss:2.2f}') if mean_test_rewards >= REWARD_THRESHOLD: print(f'Reached reward threshold in {episode} episodes') break plot_train_rewards(train_rewards, REWARD_THRESHOLD) plot_test_rewards(test_rewards, REWARD_THRESHOLD) plot_losses(policy_losses, value_losses)
הריצו את התוכנית:
run_ppo()
הפלט צריך לדמות את הדוגמה למטה:
Episode: 10 | Mean Train Rewards: 22.3 | Mean Test Rewards: 30.4 | Mean Abs Policy Loss: 0.37 | Mean Abs Value Loss: 0.39 Episode: 20 | Mean Train Rewards: 38.6 | Mean Test Rewards: 69.8 | Mean Abs Policy Loss: 0.46 | Mean Abs Value Loss: 0.37 . . . Episode: 100 | Mean Train Rewards: 289.5 | Mean Test Rewards: 427.3 | Mean Abs Policy Loss: 1.73 | Mean Abs Value Loss: 0.21 Episode: 110 | Mean Train Rewards: 357.7 | Mean Test Rewards: 461.4 | Mean Abs Policy Loss: 1.86 | Mean Abs Value Loss: 0.22 Reached reward threshold in 116 episodes
ניתן לצפות ולהריץ את התוכנית על מחברת DataLab זו!
5. כיוון ואופטימיזצית פרמטרים
בלמידת מכונה, פרמטרים כמותיים שולטים בתהליך האימון. להלן, אני מסביר על חלק מהפרמטרים הכמותיים החשובים ב-PPO:
- שיעור למידה: השיעור למידה קובע כמה פרמטרי המדיניות יכולים להשתנות בכל טווח. בגריד סטנט סטוכסטי, הכמות שבה פרמטרי המדיניות מתעדכנים בכל טווח מסודרת על ידי כפל השיעור למידה והגריד.
- פרמטר החיתוך: נקרא גם אפסילון, ε. זה קובע את המידה בה היחס בין המדיניות נחתך. יחס בין המדיניות החדשה והישנה ניתן להשתנות בטווח [1-ε, 1+ε]. כאשר הוא חורג מהטווח הזה, הוא נחתך באופן מלאכותי כדי להישאר בטווח.
- גודל הצטברות: מתייחס למספר השלבים לשקול עבור כל עדכון של הגרדיאנט. ב-PPO, גודל הצטברות היא מספר השלבים הדרושים כדי להחיל את המדיניות ולחשב את ההפסד המחליף כדי לעדכן את פרמטרי המדיניות. במאמר זה, השתמשנו בגודל הצטברות של 64.
- שלבי איטרציה: זהו מספר הפעמים שבהן כל צטברות משמשת להרצת החזרה. הקוד במאמר זה מתייחס אל זה כ-
PPO_STEPS
. בסביבות מורכבות, הרצת החישוב הקדימה פעמים רבות היא יקרה מבחינה חישובית. אלטרנטיבה יעילה יותר היא להריץ שוב כל צטברות מספר פעמים. נהוג להמליץ להשתמש בערך בין 5 ל-10. - גורם הנחה: נקרא גם גמא, γ. מבטא באופן כללי את הערך של פרסים מיידיים מעל פרסים בעתיד. דומה למושג של ריבית בחישוב ערך הזמן של כסף. כאשר קרוב ל-0, זה אומר שפרסים בעתיד פחות חשובים והסוכן צריך לתת עדיפות לפרסים מיידיים. כאשר קרוב ל-1, זה אומר שפרסים בעתיד חשובים.
- מקדם אנטרופיה:המקדם אנטרופיה קובע את בונוס האנטרופיה, שמחושב כמכפלת של המקדם אנטרופיה ושל האנטרופיה של ההפצה. תפקיד בונוס האנטרופיה הוא להכניס יותר אקראיות למדיניות. זה מעודד את הסוכן לחקור את מרחב המדיניות. אך האימון נכשל להתכנס למדיניות אופטימלית כאשר האקראיות הזו גבוהה מדי.
- קריטריונים להצלחת האימונים: עליך לקבוע את הקריטריונים להחלטה מתי האימון הוא מוצלח. דרך נפוצה לעשות זאת היא לקבוע תנאי שהפרסויות הממוצעות מעל ה-N ניסיונות האחרונים (פרקים) גבוהים מעל אחזקת מסוימת. בקוד הדוגמה לעיל, זה מובא באמצעות המשתנה
N_TRIALS
. כאשר זה מוגדר בערך גבוה יותר, האימון מותן יותר זמן מאחר שיש למדיניות להשיג את הפרסות האחזקה על מספר גדול יותר של פרקים. זה גם תוביל למדיניות יותר עמידה בעוד שהיא יקרה מבחינה חישובית. שימו לב כי PPO הוא מדיניות סטוכסטית, ויכול להיות שישנם פרקים בהם הסוכן לא יחצה את האחזקה. לכן, אם ערך המשתנהN_TRIALS
גבוה מדי, האימון שלך עשוי שלא להסתיים.
אסטרטגיות לשיפור ביצועי PPO
למקסם ביצועי אלגוריתמים PPO לאימון משתמשים בניסוי וטעות ובניסוי עם ערכי היפרפרמטרים השונים. עם זאת, קיימים מדיניות כלליות:
- גורם הנחה: כאשר פרסומים לטווח ארוך חשובים, כמו בסביבת CartPole, שבה הקוטר צריך להישאר יציב לאורך זמן, מתחילים עם ערך גאמה בינוני, כמו 0.99.
- בונוס אנטרופיה: בסביבות מורכבות, הסוכן צריך לחקור את מרחב הפעולה כדי למצוא את המדיניות האופטימלית. בונוס האנטרופיה עוזר לקידום החקירה. בונוס האנטרופיה מתווסף להפסד השיקול החליפי. בדקו את גודל ההפסד השיקול החליפי ואת אנטרופיית ההפצה לפני קביעת קואפיציית האנטרופיה. במאמר זה, השתמשנו בקואפיציית אנטרופיה של 0.01.
- פרמטר הקליפינג: פרמטר הקליפינג קובע כמה שונה יכולה להיות המדיניות המעודכנת מהמדיניות הנוכחית. ערך גבוה של פרמטר הקליפינג מעודד יותר גילוי ביקורת של הסביבה, אך יש בו סיכון לשחיקת האימון. רצוי לבחור פרמטר הקליפינג שמאפשר גילוי ביקורת באופן בהדרגה תוך מניעת עדכונים שמסוכנים. במאמר זה, השתמשנו בפרמטר הקליפינג של 0.2.
- שיעור למידה: כאשר השיעור למידה גבוה מדי, המדיניות מתעדכנת בצעדים גדולים בכל איטרציה והתהליך האימוני עשוי להיות בלתי יציב. כאשר השיעור למידה נמוך מדי, האימון מתמשך מדי. במדריך זה השתמשנו בשיעור למידה של 0.001, שעובד טוב עבור הסביבה. במקרים רבים, מומלץ להשתמש בשיעור למידה של 1e-5.
אתגרים ושיטות מובילות ב-PPO
לאחר הסבר על מושגי PPO ופרטי יישום, בואו נדבר על האתגרים והפרקטיקות הטובות.
אתגרים נפוצים באימון של PPO
אף על פי ש-PPO נמצא בשימוש נרחב, עליכם להיות מודעים לאתגרים אפשריים כדי לפתור ביעילות בעיות בעולם האמיתי באמצעות טכניקה זו. מהם אלה:
- ספירה איטית: בסביבות מורכבות, PPO יכול להיות לא יעיל ביותר בשימוש בדוגמאות וצריך הרבה אינטראקציות עם הסביבה כדי להתכנס על המדיניות האופטימלית. זה עשוי להאט ולהקרין את תהליך האימון.
- רגישות לפרמטרים זעירים: PPO תלוי בחקירה יעילה של מרחב המדיניות. יציבות התהליך האימון ומהירות הסגירה רגישים לערכי הפרמטרים הזעירים. ערכים האופטימליים של מספר זהירים אלו לרוב ניתן לקבוע רק באמצעות ניסוי וטעייה.
- על-זיקה: סביבות RL נאתחלות בדרך כלל עם פרמטרים אקראיים. אימון PPO מבוסס על מציאת המדיניות האופטימלית בהתבסס על סביבת הסוכן. לפעמים, התהליך האימון מתכנס לקבוצת פרמטרים אופטימליים לסביבה מסוימת אך לא לסביבה אקראית. פתרון נפוץ לכך הוא יש כמה איטרציות, כל אחת עם סביבת אימון אקראית אחרת.
- סביבות דינמיות: סביבות הלמידה החיפושיות הפשוטות, כמו סביבת CartPole, הן סטטיות – החוקים נשארים זהים לאורך הזמן. רבות מהסביבות האחרות, כגון רובוט שלומד ללכת על משטח נע ולא יציב, הן דינמיות – החוקים של הסביבה משתנים עם הזמן. כדי להצליח בסביבות כאלה, PPO צריך לעיתים תיקונים נוספים.
- חקירה נגד ניצול: מנגנון הקליפינג של PPO מבטיח שעדכוני המדיניות נמצאים בגבולות אמינות. אך, זה גם מונע מהסוכן לחקור את תחום הפעולה. זה עשוי להוביל להתכנסות אל נקודות קיצון מקומיות, במיוחד בסביבות מורכבות. מצד שני, הרשאה לסוכן לחקור יתר יכולה למנוע ממנו להתכנס למדיניות אופטימלית.
שיטות מומלצות לאימון מודלי PPO
כדי לקבל תוצאות טובות בשימוש ב-PPO, אני ממליץ על כמה שיטות מומלצות, כגון:
- לתקן מאפייני קלט: התאמת מידע מאפיינים עוזרת להפחתת השוני בנתונים ומובילה לעדכוני גרדיינט יציבים. התאמת המידע מביאה את כל הערכים לטווח נומרי עקבי. זה עוזר בהפחתת השפעת ערכי חריגים וערכים קיצוניים, שיוכלו לעקוף את עדכוני הגרדיינט ולהאט מהירות הכלים.
- השתמשו בגדלי צמתים גדולים במידה ראויה: צמתים קטנים מאפשרים עדכונים ואימון מהירים, אך עשויים להוביל לקונברגנציה לאופטימום מקומי ולאיטיות בתהליך האימון. גדלי צמתים גדולים מאפשרים לסוכן ללמוד מדיניות עמידה, מובילים לתהליך אימון יציב. עם זאת, גדלי צמתים שהם רבים מדי גם אינם אופטימליים. בנוסף לעליית עלויות החישוב, הם גורמים לעדכוני מדיניות שאינם רגישים מספיק לפונקציית הערך מאחר ועדכוני הגרדיינט מבוצעים על ידי ממוצעים שנחשבים על גבי צמתים גדולים. בנוסף, זה עשוי לגרום למותאמות מידיות של העדכונים לצמתים אלו.
- שלבי איטרציה: כללית נמלץ להשתמש באותו צומת במשך 5-10 איטרציות. זה עושה את תהליך האימון יעיל יותר. השימוש באותו צומת מדי פעם מוביל למותאמות מידיות. הקוד מתייחס אל היפרפרמטר הזה כ-
PPO_STEPS
. - בצע דירוג רגיל: כדי לזהות יתר-הקישור, חשוב לעקוב מדי פעם אחר היעילות של המדיניות. אם תתברר כי המדיניות אינה יעילה בתרחישים מסוימים, עשוי להיות נחוץ אימון נוסף או עיקול קטן.
- כוון את הפרמטרים היפר: כפי שנבאר למעלה, אימון PPO רגיש לערכי הפרמטרים היפר. ניתן לנסח ניסויים עם ערכי פרמטרים היפר שונים כדי לקבוע את סט הערכים הנכון לבעיה הספציפית שלך.
- רשת גב המשותפת: כפי שמודגם במאמר זה, שימוש ברשת גב משותפת מונע עיוותים בין רשתות השחקן והצופה. שיתוף רשת גב בין השחקן והצופה מסייע בחילוץ מאפיינים משותפים ובהבנה משותפת של הסביבה. זה מפשט את תהליך הלמידה והופך אותו ליעיל ויציב יותר. זה גם עוזר להפחית את המרחב החישובי ואת פריקות זמן הריצה של האלגוריתם.
- מספר וגודל שכבות נסתרות: עליה במספר השכבות הנסתרות והממדים למצבים מורכבים יותר. בעיות פשוטות יותר כמו CartPole ניתן לפתור עם שכבה נסתרת אחת. השכבה הנסתרת שנמצאת במאמר זה מכילה 64 ממדים. בניית הרשת גדולה יותר ממה שנדרש היא פוגעת ביעילות חישובית ויכולה לגרום לאירוס.
- עצירה מוקדמת: עצירה של האימון כאשר מדדי ההערכה מתקיימים מסייעת למנוע אימון יתר ומונעת בזבוז משאבים. מדד הערכה נפוץ הוא כאשר הסוכן חוצה את תגמולי הסף על פני N האירועים האחרונים.
סיכום
במאמר זה, דנו ב-PPO כדרך לפתור בעיות RL. לאחר מכן, פירטנו את הצעדים ליישום PPO באמצעות PyTorch. לבסוף, הצגנו כמה טיפים לביצועים ושיטות עבודה מומלצות ל-PPO.
הדרך הטובה ביותר ללמוד היא ליישם את הקוד בעצמך. אתה יכול גם לשנות את הקוד כדי לעבוד עם סביבות בקרות קלאסיות אחרות ב-Gym. כדי ללמוד כיצד ליישם סוכני RL באמצעות Python ו-Gymnasium של OpenAI, עקוב אחרי הקורס למידת חיזוק עם Gymnasium ב-Python!
Source:
https://www.datacamp.com/tutorial/proximal-policy-optimization