איך מודל לומד? הסיפור המלא של אימון
הסבר מלא על training, gradient descent ו-backprop
כולם אומרים 'אימנו את המודל על מיליארדי דוגמאות'. אף אחד לא מסביר מה זה אומר בפועל. מפרמטרים, דרך loss וגרדיאנטים, ועד backprop - כל מה שקורה כשמודל לומד, כולל המתמטיקה שרוב ההסברים מדלגים עליה.
- #training
- #backpropagation
- #gradient-descent
- #neural-networks
- #LLM
תוכן עניינים
“אימנו את המודל על מיליארדי דוגמאות.” משפט שמופיע בכל כתבה על AI. אבל אף אחד לא מסביר מה זה אומר בפועל.
בפוסט הזה נחקור בדיוק את הנושא הזה, מה זה אומר אימון, איך מודל מתאמן וגם קצת מתמטיקה.
מה זה בעצם מודל?
לפני שנדבר על האימון, צריך להבין מה אנחנו בכלל מאמנים.
בניגוד לתוכנה רגילה, שבה מתכנת כתב חוקים מפורשים (“אם המשתמש שאל X, תחזיר Y”), מודל שפה הוא לא אוסף של חוקים. הוא ערימה ענקית של מספרים שקוראים להם פרמטרים.
אפשר לחשוב על כל פרמטר כמו דימר של אור. מספר שכשמזיזים אותו, זה משפיע קצת על מה שיוצא בסוף. רק שבניגוד לדימר אמיתי, הערך יכול להיות גם שלילי — לא רק “יותר” או “פחות”. מודל כמו GPT או קלוד מכילים מעל טריליון כאלה, כולם מחוברים ושינוי באחד משפיע על התוצאה הסופית.
רק שימו לב: הדימרים לא יוצרים את התשובה לבדם. הם קובעים איך הקלט שאתם נותנים הופך לפלט. אותם דימרים בדיוק, קלט שונה — תשובה שונה.
כל “הידע” שמודל צבר, על שפה, על עובדות, על איך לענות על שאלות, שמור בתוך הערכים של הפרמטרים האלה. לא בקוד. במספרים.
הקלט קבוע (מימין). אתם מזיזים רק את הדימרים — הפרמטרים שמכפילים אותו. פרמטר חיובי (ירוק) ממלא שמאלה, שלילי (אדום) ממלא ימינה, והוא יכול לרדת מתחת לאפס. לחצו −/+ כדי לקרב את הפלט למטרה ולצמצם את ה־error. עכשיו דמיינו טריליון דימרים — זה בדיוק מה שאימון מכוון, לבד.
אז מה זה אומר אימון?
אימון הוא התהליך שמוצא את הערכים הנכונים של הפרמטרים האלה.
הפרמטרים מתחילים כמספרים אקראיים ולכן מודל לא מאומן פולט ג’יבריש מוחלט. האימון הוא מה שדוחף אותם, צעד אחר צעד, מאקראיות לערכים שימושיים.
ואף אחד לא מגדיר אותם ידנית. המודל מצא אותם בעצמו, דרך לולאה אחת פשוטה שחוזרת על עצמה המון פעמים:
- מראים למודל דוגמה ומבקשים שינחש את המשך הטקסט. זה נקרא forward pass
- מודדים כמה הוא טעה. המספר הזה נקרא loss
- מעדכנים את הפרמטרים בכיוון שיקטין את הטעות
- חוזרים
מה זה forward pass?
המעבר “קדימה” ברשת: מזינים קלט, הוא עובר דרך כל הפרמטרים שכבה אחרי שכבה, ובסוף יוצא ניחוש. קוראים לזה “forward” כי המידע זורם מהקלט אל הפלט. ה-backward pass, שנגיע אליו בהמשך, זורם בדיוק בכיוון ההפוך.
דוגמה: מראים למודל דוגמה ומבקשים שינחש את ההמשך.
אותה לולאה חוזרת על עצמה מיליארדי פעמים. לחצו ▶ כדי לראות צעד שלם זורם דרך ארבעת השלבים.
Loss - המדד לכמה המודל טעה
ה-loss הוא מספר אחד שמסכם כמה המודל טעה. המטרה של כל האימון היא להוריד אותו כמה שאפשר.
אחת הדרכים הנפוצות למדוד טעות נקראת Mean Squared Error:
בשפה פשוטה: לוקחים את ההפרש בין מה שניחשנו () למה שנכון (), מעלים בריבוע, וממצעים על כל הדוגמאות.
למה בריבוע? שתי סיבות: 1. טעויות גדולות מקבלות עונש גדול יותר באופן לא לינארי, וכל הערכים יוצאים חיוביים 2. שגיאה של 3+ ושגיאה של 3- חמורות באותה מידה.
אז איך יודעים איזה פרמטר להזיז?
כאן מגיעה הבעיה האמיתית. כשיש לך טריליון פרמטרים, אי אפשר לנסות כל אחד בנפרד ולראות מה קורה.
צריך לדעת, לכל פרמטר בנפרד “אם אזיז אותך קצת, הטעות תגדל או תקטן? ובכמה?”
זה בדיוק מה שגרדיאנט אומר. הגרדיאנט של ה-loss ביחס לפרמטר הוא:
מה זה גרדיאנט?
הגרדיאנט הוא הנגזרת של ה-loss ביחס לפרמטר מסוים. במילים פשוטות, נגזרת אומרת לנו כמה שינוי קטן בדבר אחד משפיע על דבר אחר.
במקרה שלנו, השאלה היא: “אם אגדיל את הפרמטר הזה בטיפה, ה-loss יעלה או ירד, ובכמה?”
- גרדיאנט חיובי - הגדלת תגדיל את הטעות, אז כדאי להקטין אותו.
- גרדיאנט שלילי - הגדלת תקטין את הטעות, אז כדאי להגדיל אותו.
- גרדיאנט אפס - הפרמטר לא משפיע כרגע על הטעות.
ועכשיו נוצרת בעיה חדשה מכיוון שרשת עמוקה היא פונקציה של פונקציות של פונקציות. אז איך מחשבים גרדיאנט דרך כל השכבות האלה?
Backpropagation - כלל השרשרת בפעולה
הפתרון מגיע מחדו”א: כלל השרשרת.
אם תלוי ב-, וב- תלוי ב-, אז:
אפשר לפרק גרדיאנט מורכב לשרשרת של גרדיאנטים פשוטים יותר. backprop הוא בדיוק זה. מתחיל מה-loss בסוף ומגלגל אחריות אחורה, שכבה אחרי שכבה, עד שכל פרמטר יודע את הגרדיאנט שלו.
מה זה כלל השרשרת?
כלל מחדו”א לגזירת פונקציה של פונקציה: הנגזרת של היא . כשרשת היא ערימה של פונקציות זו על גבי זו, הכלל הזה מאפשר לפרק את הגרדיאנט הכולל למכפלה של גרדיאנטים מקומיים אחד לכל שכבה.
אינטואיציה: ה-loss בסוף “מגלגל אחריות” אחורה. הוא שואל את השכבה שלפניו “כמה אתה אשם בטעות?”, זו שואלת את שלפניה, וכך הלאה עד הפרמטר הראשון — וכל אחד מקבל בדיוק את חלקו בטעות.
דוגמה מספרית:
רשת זעירה עם שתי שכבות, ופרמטר אחד בכל שכבה: לוקח את הקלט ומייצר ערך ביניים . זו שכבה נסתרת. אחר כך לוקח את ומייצר את הפלט שהיא שכבת הפלט. בדיוק המבנה הרב-שכבתי הזה הוא שנותן ל-backprop “דרך” לגלגל את האחריות אחורה, שכבה אחרי שכבה:
מה זה שכבה נסתרת?
שכבה ש”יושבת באמצע”, הערכים שלה ( במקרה הזה) אינם הקלט ואינם הפלט הסופי, אלא ייצוג ביניים שהרשת בונה לעצמה בדרך. בדוגמה הזו יש שכבה נסתרת אחת בלבד; ברשתות אמיתיות מערימים עשרות ומאות כאלה וזה בדיוק מה שהופך אותן ל”עמוקות”. על שכבות, נוירונים ועומק נרחיב בפוסט נפרד.
נניח:
הקלט x עובר דרך שתי שכבות כפל (× w₁ ואז × w₂) עד לפלט ŷ. ערך הביניים z הוא השכבה הנסתרת (במסגרת המקווקוות). החץ המקווקו האדום אינו שכבה אלא חישוב ה-loss — השוואת ŷ לערך הנכון y. זה ה-forward pass שנפרק עכשיו צעד-צעד.
Forward pass:
Backward pass מגלגלים אחורה:
קודם כל, הגרדיאנט של ה-loss ביחס לפלט:
עכשיו . כלל השרשרת מכפיל שתי נגזרות מקומיות: את שכבר חישבנו, ואת (כי ). קודם כותבים את הנגזרות, ורק אז מציבים מספרים:
ובשביל צריך לעבור דרך , אז יש שלוש נגזרות מקומיות. בנוסף לקודמת: (כי ) ו- (כי ). שוב — קודם הנגזרות, ואז המספרים:
שני הגרדיאנטים שליליים, כלומר הגדלת שניהם תקטין את הטעות. הגיוני, כי המודל ניחש 3 והתשובה הייתה 6.
זו בדיוק הרשת מהדוגמה. גררו את הפרמטרים, הריצו את ה-backward pass, וראו את הגרדיאנטים מתגלגלים מה-loss אחורה לכל משקל.
Gradient Descent השימוש בגרדיאנט לכיוון הפרמטרים
לפני שנשתמש בגרדיאנט, כדאי להבין מה אנחנו בעצם מחפשים.
כל צירוף של פרמטרים נותן loss אחר. פרמטרים מסוימים יגרמו למודל לטעות הרבה, אחרים פחות. המטרה היא למצוא את הצירוף שמביא את ה-loss לכי נמוך שאפשר, כי שם המודל טועה הכי מעט. אלה בדיוק “הערכים הנכונים” שחיפשנו מתחילת הפוסט.
הבעיה: יש טריליון פרמטרים. אי אפשר לנסות כל צירוף ולבדוק איזה עובד. וכאן הגרדיאנט נכנס לתמונה. אומר בדיוק שני דברים: 1. כמה השפעה יש לפרמטר על ה-loss. גרדיאנט גדול אומר שהפרמטר הזה אחראי לחלק משמעותי מהטעות. 2. לאיזה כיוון לעדכן, גרדיאנט חיובי אומר שהגדלת תגדיל את הטעות. שלילי אומר שתקטין אותה.
אז במקום לנסות כל צירוף, שואלים בכל נקודה: לאיזה כיוון ה-loss עולה? ומזיזים בכיוון ההפוך, צעד קטן כל פעם. זה gradient descent.
כלל העדכון פשוט:
כאשר (eta) הוא ה-learning rate, כמה גדול כל צעד.
מה זה learning rate?
גודל הצעד בכל עדכון, מסומן . גדול מדי מדלגים מעבר למינימום ומתנדנדים סביבו, או מתבדרים לגמרי. קטן מדי מתקדמים למינימום בזחילה. למצוא את הערך הנכון זו חלק מהאומנות של אימון מודלים.
למה להחסיר? כי אנחנו רוצים לרדת בכיוון ההפוך לגרדיאנט. אם הגרדיאנט חיובי, גדול מדי, אז נקטין אותו.
למה לא לזוז הרבה בבת אחת? כי צעד גדול מדי יכול לדלג מעל המינימום ולהחמיר את המצב. learning rate גבוה מדי, המודל מתנדנד ולא מתכנס. נמוך מדי, האימון לוקח נצח.
אינטואיציה: דמיינו שאתם יורדים במורד הר בערפל כבד. אתם לא רואים את התחתית, אבל מרגישים את השיפוע מתחת לרגליים וצועדים בכל פעם קצת בכיוון היורד. זה בדיוק gradient descent.
כדור שמתגלגל במורד ה-loss. כל צעד: x ← x − η·שיפוע. η קטן = זוחל. η≈1 = קפיצה ישר למינימום. η בין 1 ל-2 = מדלג ומתנדנד. η≥2 = מתבדר. לחצו על הגרף כדי לבחור נקודת התחלה.
וברמת המאקרו, אחרי שמריצים את הלולאה אלפי פעמים, ה-loss פשוט יורד. ככה זה נראה לאורך אימון אמיתי:
Click any series in the legend to toggle. Hover for exact values.
Optimizer מי מחליט כמה לזוז
gradient descent בצורתו הפשוטה אומר לכל פרמטר לזוז בדיוק לפי הנוסחה שראינו. זה נקרא SGD (Stochastic Gradient Descent).
אבל בפועל, רוב המודלים משתמשים ב-optimizer חכם יותר כמו Adam. Adam לא מסתכל רק על הגרדיאנט הנוכחי. הוא מסתכל על ההיסטוריה של הגרדיאנטים ומתאים את גודל הצעד לכל פרמטר בנפרד. פרמטרים שהגרדיאנט שלהם עקבי מקבלים צעדים גדולים יותר. פרמטרים שהגרדיאנט שלהם קופץ לכל כיוון, צעדים קטנים יותר.
SGD מול Adam — מה ההבדל בקצרה?
SGD מזיז כל פרמטר בדיוק לפי הגרדיאנט הנוכחי, באותו גודל צעד לכולם. Adam שומר “זיכרון” של הגרדיאנטים האחרונים ומתאים גודל צעד נפרד לכל פרמטר’ מאיץ בכיוונים יציבים, מאט בכיוונים שמתנדנדים. בפועל הוא מתכנס מהר יותר, ולכן הוא מה שמאמן את רוב המודלים הגדולים.
כדי להרגיש את ההבדל: לחצו על המשטח כדי להפיל נקודת התחלה, והשוו איך SGD, Momentum ו-Adam מתקדמים לאותו מינימום. המשטח כאן דו-ממדי ויש בו כמה מינימומים, אז גם נקודת ההתחלה משנה:
הקישו בכל מקום על המשטח כדי להפיל נקודת התחלה, וצפו באופטימייזר יורד במורד. החץ מראה את כיוון הירידה, והטבעות הירוקות הן המינימומים — היעד. יש כמה מינימומים, אז נקודת ההתחלה קובעת לאן מגיעים.
על SGD, Adam, ומה שביניהם ארחיב בהמשך :).
אז לסיכום מה זה Pretraining ו-Finetuning
כל מה שראינו עד עכשיו, אותה לולאה בדיוק. ההבדל הוא רק בדאטה.
Pretraining הוא האימון הראשוני. מריצים את הלולאה על כמויות ענק של טקסט מהאינטרנט, ספרים, אתרים, קוד, מאמרים. המטרה פשוטה: לנחש את המילה הבאה בטקסט (ליתר דיוק, את הטוקן הבא). מיליארדי דוגמאות, שבועות של חישוב על אלפי GPU.
מה המודל לומד בשלב הזה? לא עובדות במפורש, אלא משהו עמוק יותר. איך מילים מתקשרות אחת לשנייה, מה הסמנטיקה של שפה, איך רעיונות בנויים. בסוף יש מודל שמבין שפה ועולם, אבל עוד לא ברור איך לשוחח איתו.
Finetuning הוא שלב שני. לוקחים את המודל הזה ומריצים שוב את אותה לולאה, הפעם על דאטה קטן וממוקד. ChatGPT הוא דוגמה טובה לשני סוגים של finetuning שנעשו עליו.
כאשר הראשון נקרא instruction tuning, אימון על אלפי דוגמאות של שיחות בפורמט “משתמש שואל, עוזר עונה”. זה מה שגרם לו לדעת שכשיש User הוא אמור להתנהג כעוזר ולא כמנוע השלמת טקסט.
השני הוא RLHF, שעליו הרחבתי בפוסט על mode collapse. בקצרה, במקום דוגמאות טקסט, הדאטה הוא משוב ישיר מבני אדם על איכות התשובות. זה מה שכיוון את ההתנהגות, הנימוס, הגבולות.
אותה לולאה בדיוק בכל השלבים. הדאטה הוא שמשתנה.
אותה לולאה, פעמיים. הראשונה בונה מודל בסיס שיודע שפה ועולם; השנייה מכווננת אותו למשימה. עברו בין הלשוניות כדי להתמקד.
זה הכל
אימון, pretraining, finetuning. כולם וריאציות על אותה לולאה. forward pass, מדידת loss, backprop שמחשב גרדיאנטים דרך כלל השרשרת, gradient descent שמעדכן את הפרמטרים.
כל “אימון” הוא בסוף אותה לולאה: לנחש, למדוד כמה טעית, ולזוז טיפה בכיוון הנכון. שוב, ושוב, מיליארדי פעמים.
לקח לי יותר מדי זמן לגלות שזה פשוט כל כך.