דלג לתוכן
3/30פרק 3 מתוך 30

במורד: Gradient Descent, ושני הצעדים שכולם מדלגים עליהם

חשבו את התקרה המדויקת לקצב הלמידה, ואז ראו חיפוש brute-force על 3,600 כיוונים מגלה מחדש את ה-gradient בלי שאמרו לו.

בעמוד הזה

הפרק הקודם הסתיים בעמק.

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

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

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

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

ההגדרה, ולמה אי אפשר פשוט לחפש

קישור למקטע: ההגדרה, ולמה אי אפשר פשוט לחפש

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

belt.pyPYTHON
import numpy as np

WIDTH  = np.array([18.0, 19.5, 20.2, 21.0, 24.0, 25.5, 23.0, 26.0])
WEIGHT = np.array([47.0, 52.0, 49.0, 55.0, 61.0, 66.0, 70.0, 58.0])

x = WIDTH  - WIDTH.mean()      # 22.15 mm
y = WEIGHT - WEIGHT.mean()     # 57.25 g

המדידות ממורכזות, בדיוק כמו בפרק 1 ומסיבה שתחזור עם ריבית לפני סוף הפרק הזה. המודל הוא קו, y^=ax+b\hat{y} = a x + b, וההפסד הוא השגיאה הריבועית הממוצעת שהפרק הקודם גזר:

L(a,b)=1ni=1n(axi+byi)2L(a, b) = \frac{1}{n} \sum_{i=1}^{n} \left(a x_i + b - y_i\right)^2

שני פרמטרים. למה לא פשוט לנסות הרבה ערכים? בואו באמת נעשה את זה — grid מ-a=0a = 0 עד 55 ומ-b=5b = -5 עד 55, בצעדים של 0.010.01:

TEXT
grid 501 x 1001 = 501,501 evaluations in 3.67 s
  best found: a = 2.1000, b = -0.0000, L = 24.592450

חצי מיליון הערכות כדי לנעול שני מספרים לשתי ספרות אחרי הנקודה — והשנייה הזאת היא זמן שעון על מכונה אחת, כך שהרצה חוזרת יכולה לנחות בכל מקום בין שלוש לשש; מספר ההערכות והמינימום הם החלקים שמשתחזרים. Gradient descent, בסוף הפרק הזה, מגיע לארבע ספרות אחרי הנקודה בשמונה צעדים ולתשובת float64 המלאה בשלושים ושישה.

אבל מהירות היא לא הטיעון, וזו הנקודה שמכריעה את כל הקורס. Grid search עולה kPk^P הערכות עבור PP פרמטרים ב-kk ערכים לכל אחד. עם אלף ערכים לכל ציר:

מודלפרמטריםהערכות grid
הקו הזה210610^{6}
רשת ה-XOR של פרק 59102710^{27}
רשת רב-שכבתית קטנה20,0001060,00010^{60{,}000}

השורה השלישית היא לא מספר גדול, היא מספר חסר משמעות — יש בערך 108010^{80} אטומים ביקום הנצפה. חיפוש לא נעשה איטי יותר כשהמודלים גדלים; הוא מפסיק להתקיים. כל מה שבא עכשיו קיים בגלל הטבלה הזאת.

נגזרת היא מדידה שאפשר לקחת

קישור למקטע: נגזרת היא מדידה שאפשר לקחת

קבעו את b=0b = 0 לרגע כך שיהיה פרמטר אחד ועקומה אחת, שזה הציור שהפרק האחרון השאיר לכם. קחו נקודה עליה, a=1a = 1, ושאלו: אם אזיז את aa בכמות קטנה hh, כמה ההפסד זז, לכל יחידת הזזה?

L(a+h)L(a)h\frac{L(a + h) - L(a)}{h}

היחס הזה הוא עלייה חלקי ריצה — השיפוע של הקו הישר שעובר דרך שתי נקודות על העקומה. ככל ש-hh מתכווץ, שתי הנקודות מחליקות זו אל זו והקו נעשה המשיק. השיפוע שלו הוא הנגזרת L(a)L'(a): הקצב שבו ההפסד משתנה לכל יחידת שינוי ב-aa. לא קירוב של משהו, ולא כמות אינסופית קטנה. גבול של יחסים רגילים.

שווה להריץ את זה, כי המספרים אומרים משהו שההגדרה לא אומרת:

slope.pyPYTHON
def loss1(a):
    return np.mean((a * x - y) ** 2)

for h in [1.0, 1e-2, 1e-4, 1e-6, 1e-8, 1e-10, 1e-12, 1e-14]:
    q = (loss1(1.0 + h) - loss1(1.0)) / h
    print(f"h = {h:<8.0e}   slope estimate = {q:.10f}   error = {abs(q + 16.385):.3e}")
TEXT
h = 1e+00      slope estimate = -8.9400000000    error = 7.445e+00
h = 1e-02      slope estimate = -16.3105500000   error = 7.445e-02
h = 1e-04      slope estimate = -16.3842555001   error = 7.445e-04
h = 1e-06      slope estimate = -16.3849925556   error = 7.444e-06
h = 1e-08      slope estimate = -16.3850003787   error = 3.787e-07
h = 1e-10      slope estimate = -16.3850444324   error = 4.443e-05
h = 1e-12      slope estimate = -16.3851154866   error = 1.155e-04
h = 1e-14      slope estimate = -17.0530256582   error = 6.680e-01

שני דברים קורים כאן, ושניהם נושאים משקל.

השגיאה לא פרופורציונלית באופן מעורפל ל-hh — היא בדיוק 7.445h7.445\,h. חלקו את hh במאה, והשגיאה מתחלקת במאה, לארבע ספרות משמעותיות בכל פעם. הקבוע הזה אינו קישוט: הוא חצי הנגזרת השנייה של ההפסד, והוא ההופעה הראשונה של רעיון שיגיע בעוד שני סעיפים — שעקומה ליד נקודה נראית כמו קו ועוד תיקון פרופורציונלי ל-h2h^2.

ואז התבנית נשברת. מתחת ל-h=108h = 10^{-8} האומדן נעשה גרוע יותר, וב-101410^{-14} הוא שגוי כבר בספרה השנייה. לא קרה שום דבר מתמטי; קופסת הנקודה הצפה מהפרק הקודם קרתה. L(a+h)L(a+h) ו-L(a)L(a) מסכימים בעשר הספרות הראשונות שלהם, החיסור שלהם הורס את הספרות האלה, וחלוקה של ההריסות במספר זעיר מגבירה את מה שנשאר. יש hh מיטבי — כאן סביב 10810^{-8}, בערך השורש הריבועי של אפסילון המכונה — ולהקטין עוד זה לא זהיר יותר, זה פחות. זכרו את זה; פונקציה בסוף הפרק הזה תלויה בזה.

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

הנה הרעיון שעליו בנוי שאר הקורס, נאמר פעם אחת, בפשטות.

להרכיב שתי פונקציות פירושו להזין אחת לתוך השנייה: (fg)(x)=f(g(x))(f \circ g)(x) = f(g(x)). לא יותר.

רשת עמוקה אינה כמו הרכבה. היא הרכבה. שכבה היא פונקציה; לערום שכבות פירושו להרכיב אותן; ״עומק״ הוא מספר הפונקציות בשרשרת. כשפרק 5 בונה רשת, הוא בונה את f4f3f2f1f_4 \circ f_3 \circ f_2 \circ f_1 ושום דבר אחר. כלומר, כלל החשבון החשוב ביותר לענייננו הוא הכלל שגוזר הרכבה:

ddxf(g(x))=f(g(x))g(x)\frac{d}{dx} f(g(x)) = f'(g(x)) \cdot g'(x)

קצבים מוכפלים. אם gg משתנה פי שלושה מהר יותר מ-xx, ו-ff משתנה פי שניים מהר יותר מ-gg, אז ff משתנה פי שישה מהר יותר מ-xx. זה כל התוכן, וזו הסיבה שאות שעובר אחורה דרך עשר שכבות מוכפל בעשרה מספרים — ולכן פרק 6 מקדיש סעיף למה שקורה כשהמספרים האלה כולם קצת פחות מאחד.

השתמשו בזה על ההפסד שלנו. כתבו את השארית ri=axi+byir_i = a x_i + b - y_i, כך ש-L=1nri2L = \frac{1}{n}\sum r_i^2. כל rir_i תלוי ב-aa דרך הפונקציה הפנימית axia x_i, שהנגזרת שלה היא xix_i. כלל השרשרת, איבר אחר איבר:

La=1ni2rixi,Lb=1ni2ri1\frac{\partial L}{\partial a} = \frac{1}{n}\sum_i 2 r_i \cdot x_i, \qquad \frac{\partial L}{\partial b} = \frac{1}{n}\sum_i 2 r_i \cdot 1

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

L=(La, Lb)\nabla L = \left( \frac{\partial L}{\partial a},\ \frac{\partial L}{\partial b} \right)

בנקודה (a,b)=(1,4)(a, b) = (1, 4) הווקטור הזה הוא (16.385, 8.0)(-16.385,\ 8.0). שני מספרים. השאלה היא מה הם אומרים, וזה הצעד הראשון שכולם מדלגים עליו.

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

אז נגדיר את הדבר שאנחנו באמת רוצים. בחרו וקטור יחידה u\mathbf{u}, כיוון. הנגזרת הכיוונית היא הקצב שבו ההפסד משתנה כשצועדים בכיוון הזה:

DuL=limh0L(θ+hu)L(θ)hD_{\mathbf{u}} L = \lim_{h \to 0} \frac{L(\boldsymbol{\theta} + h\mathbf{u}) - L(\boldsymbol{\theta})}{h}

כלל השרשרת הופך את זה למשהו שאפשר לחשב. הליכה לאורך u\mathbf{u} משנה את aa בקצב u1u_1 ואת bb בקצב u2u_2, והתרומות מסתכמות:

DuL=Lau1+Lbu2=LuD_{\mathbf{u}} L = \frac{\partial L}{\partial a} u_1 + \frac{\partial L}{\partial b} u_2 = \nabla L \cdot \mathbf{u}

קצב השינוי בכל כיוון הוא המכפלה הסקלרית של ה-gradient עם הכיוון הזה. ועכשיו הפאנץ׳, שהוא שורת גאומטריה אחת. כשכותבים את המכפלה הסקלרית עם הזווית ϕ\phi בין הווקטורים,

Lu=Lucosϕ=Lcosϕ\nabla L \cdot \mathbf{u} = \lVert \nabla L \rVert \, \lVert \mathbf{u} \rVert \cos\phi = \lVert \nabla L \rVert \cos\phi

כי ל-u\mathbf{u} יש אורך 1. הדבר היחיד שאתם שולטים בו הוא cosϕ\cos\phi, שהוא מקסימלי ב-ϕ=0\phi = 0 ומינימלי בחצי סיבוב, ϕ=180\phi = 180 מעלות. לכן:

  • העלייה התלולה ביותר היא לאורך L\nabla L עצמו, והשיפוע שם הוא בדיוק L\lVert \nabla L \rVert.
  • הירידה התלולה ביותר היא לאורך L-\nabla L, והשיפוע שם הוא L-\lVert \nabla L \rVert.
  • בניצב ל-gradient, ההפסד לא משתנה בכלל. זו הסיבה שקווי מפה טופוגרפית חוצים את ה-gradient בזוויות ישרות.

זה סימן המינוס. לא מוסכמה, לא היפוך סימן שמישהו בחר: כיוון הירידה המהירה ביותר הוא ה-gradient השלילי כי cosϕ\cos\phi מינימלי בחצי סיבוב, ולא משום סיבה אחרת.

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

directions.pyPYTHON
theta = np.array([1.0, 4.0])
g = grad(theta)
print("gradient      ", g)
print("its length    ", np.linalg.norm(g))
print("its angle     ", np.degrees(np.arctan2(g[1], g[0])) % 360, "degrees")

best = max(
    ((loss(theta + 1e-6 * u) - loss(theta - 1e-6 * u)) / 2e-6, np.degrees(ang))
    for ang, u in (
        (a, np.array([np.cos(a), np.sin(a)])) for a in np.arange(3600) * 2 * np.pi / 3600
    )
)
print("steepest slope", best[0], "at", best[1], "degrees")
TEXT
gradient       [-16.385   8.   ]
its length     18.23371122399386
its angle      153.97598928042032 degrees
steepest slope 18.233709624837502 at 154.0 degrees

חיפוש שלא יודע שום דבר על gradients, על פני 3,600 כיוונים, מוצא את הטיפוס התלול ביותר שלו ב-154.0 מעלות — הכיוון של ה-gradient עצמו, עד רזולוציית 0.1 המעלה של החיפוש. והשיפוע שהוא מוצא שם, 18.2337, הוא אורך ה-gradient לשש ספרות. המשפט אינו סיפור על המשמעות של gradients; הוא עובדה שאפשר למדוד, וזו המדידה.

למה צעד קטן במורד באמת עוזר

קישור למקטע: למה צעד קטן במורד באמת עוזר

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

הגשר הוא לינאריזציה. ליד נקודה, פונקציה חלקה היא המשיק שלה ועוד תיקון:

L(θ+δ)=L(θ)+Lδ+O(δ2)L(\boldsymbol{\theta} + \boldsymbol{\delta}) = L(\boldsymbol{\theta}) + \nabla L \cdot \boldsymbol{\delta} + O(\lVert\boldsymbol{\delta}\rVert^2)

זו התפשטות טיילור מסדר ראשון. ה-O(δ2)O(\lVert\boldsymbol{\delta}\rVert^2) שהושלך הוא העקמומיות — אותו איבר שגרם לאומדן בטבלת השיפועים לטעות בדיוק ב-7.445h7.445\,h. נציב את הצעד שאנחנו מתכוונים לקחת, δ=ηL\boldsymbol{\delta} = -\eta \nabla L:

L(θηL)L(θ)ηL2L(\boldsymbol{\theta} - \eta \nabla L) \approx L(\boldsymbol{\theta}) - \eta \lVert \nabla L \rVert^2

ההפסד יורד ב-ηL2\eta \lVert \nabla L \rVert^2. כל חלק בזה אינו שלילי, כך שההבטחה אמיתית — עבור η\eta קטן מספיק, כי האיבר שהוזנח גדל כמו η2\eta^2 ובסוף אוכל אותה. זו כל התיאוריה. הנה ההבטחה מתקיימת, ואז נשברת:

TEXT
eta = 0.2       promised    66.49364500   delivered   -16.01619240   ratio -0.240868
eta = 0.1       promised    33.24682250   delivered    12.61936315   ratio  0.379566
eta = 0.01      promised     3.32468225   delivered     3.11840766   ratio  0.937957
eta = 0.001     promised     0.33246822   delivered     0.33040548   ratio  0.993796
eta = 0.0001    promised     0.03324682   delivered     0.03322620   ratio  0.999380
eta = 1e-05     promised     0.00332468   delivered     0.00332448   ratio  0.999938

קראו מלמטה. ככל ש-η\eta מתכווץ, הירידה שהתקבלה מתכנסת לזו שהובטחה — יחס 0.99938, ואז 0.99994 — וזה משפט טיילור עובד. קראו מלמעלה וב-η=0.2\eta = 0.2 ה״ירידה״ שהתקבלה היא מינוס שש-עשרה. הצעד הלך במורד וההפסד עלה.

אז כלל העדכון הוא

θθηL(θ)\boldsymbol{\theta} \leftarrow \boldsymbol{\theta} - \eta \nabla L(\boldsymbol{\theta})

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

לקצב הלמידה יש תקרה, ואפשר לחשב אותה

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

נתחיל בעמק הפשוט ביותר שיש, f(x)=x2f(x) = x^2, שבו f(x)=2xf'(x) = 2x. צעד אחד של gradient descent הוא

xxη2x=x(12η)x \leftarrow x - \eta \cdot 2x = x\,(1 - 2\eta)

המיקום מוכפל ב-(12η)(1 - 2\eta) בכל צעד. זו סדרה הנדסית, ולסדרות הנדסיות יש בדיוק כלל אחד: הן מתכווצות כשהכופל קטן מ-1 בערך מוחלט וגדלות אחרת. לכן 12η<1\lvert 1 - 2\eta \rvert < 1, כלומר 0<η<10 < \eta < 1.

הגבול נמצא בדיוק ב-η=1\eta = 1. לא ״בערך 1״, לא ״1 בדרך כלל גדול מדי״. ב-η=1\eta = 1 הכופל הוא 1-1 והנקודה קופצת בין xx ל-x-x לנצח, לא מתקרבת ולא בורחת. מתחת לזה, מתכנסת; מעל לזה, מתבדרת. המרווח מתפצל שוב ב-η=0.5\eta = 0.5, שם הכופל מחליף סימן: מתחת לזה ההתקרבות מונוטונית, מעל לזה הנקודה מפספסת מעבר למינימום ומחליפה צדדים, ובדיוק ב-0.50.5 הכופל הוא 0 וצעד יחיד נוחת על המינימום.

ארבעה משטרים, מארבע שורות אלגברה. לכו וחצו את הגבולות בעצמכם:

מספר צעדים: 14, סיום ב־x = -0.0836.

הצגת הנתונים כטבלה
צעדxf(x)
0⁨-1.9000⁩⁨3.6100⁩
1⁨-1.5200⁩⁨2.3104⁩
2⁨-1.2160⁩⁨1.4787⁩
3⁨-0.9728⁩⁨0.9463⁩
4⁨-0.7782⁩⁨0.6057⁩
5⁨-0.6226⁩⁨0.3876⁩
6⁨-0.4981⁩⁨0.2481⁩
7⁨-0.3985⁩⁨0.1588⁩
8⁨-0.3188⁩⁨0.1016⁩
9⁨-0.2550⁩⁨0.0650⁩
10⁨-0.2040⁩⁨0.0416⁩
11⁨-0.1632⁩⁨0.0266⁩
12⁨-0.1306⁩⁨0.0170⁩
13⁨-0.1045⁩⁨0.0109⁩
14⁨-0.0836⁩⁨0.0070⁩
ירידת גרדיאנט אינטראקטיבית

ארבעה-עשר צעדים בקצב 0.1, מ-x=1.9x = -1.9, מסתיימים ב-0.0836-0.0836. דחפו את הקצב ל-0.5 והצעד הראשון ממש נוחת בתחתית. דחפו אותו ל-0.9 והוא מסתיים באותו 0.0836-0.0836 כמו 0.1 — אותו מרחק, סגנון הפוך, כי 12η\lvert 1 - 2\eta \rvert הוא 0.8 בשניהם — אבל הוא מגיע לשם בזיגזוג דרך העמק במקום ללכת למטה בצד אחד.

ועכשיו המקרה המעניין:

מספר צעדים: 14, סיום ב־x = -1.9000.

הצגת הנתונים כטבלה
צעדxf(x)
0⁨-1.9000⁩⁨3.6100⁩
1⁨1.9000⁩⁨3.6100⁩
2⁨-1.9000⁩⁨3.6100⁩
3⁨1.9000⁩⁨3.6100⁩
4⁨-1.9000⁩⁨3.6100⁩
5⁨1.9000⁩⁨3.6100⁩
6⁨-1.9000⁩⁨3.6100⁩
7⁨1.9000⁩⁨3.6100⁩
8⁨-1.9000⁩⁨3.6100⁩
9⁨1.9000⁩⁨3.6100⁩
10⁨-1.9000⁩⁨3.6100⁩
11⁨1.9000⁩⁨3.6100⁩
12⁨-1.9000⁩⁨3.6100⁩
13⁨1.9000⁩⁨3.6100⁩
14⁨-1.9000⁩⁨3.6100⁩
ירידת גרדיאנט אינטראקטיבית

בדיוק על הגבול. ארבעה-עשר צעדים בקצב 1, והוא מסיים ב-1.9-1.9: בדיוק איפה שהתחיל, אחרי שלא עשה דבר מלבד לקפוץ. הזזה קטנה למעלה והקפיצות גדלות במקום להישאר; ב-1.2 הוא מחוץ לתרשים בארבעה צעדים. קצב גדול מדי לא מתכנס לאט. הוא לא מתכנס.

עכשיו הכלל הכללי, שנופל מאותו טיעון. הכופל 12η1 - 2\eta היה בעצם 1ηf1 - \eta f'', וליד מינימום להפסד רב-פרמטרי יש מספר כזה לכל כיוון — הערכים העצמיים של מטריצת הנגזרות השניות. כל הכיוונים צריכים להיות יציבים בבת אחת, לכן התקרה נקבעת על ידי הגדול ביותר:

η<2λmax\eta < \frac{2}{\lambda_{\max}}

עבור f(x)=x2f(x) = x^2, f=2f'' = 2, התקרה 1, וזה מה שגזרנו עכשיו. עבור המסוע שלנו, מטריצת הנגזרות השניות היא 2nAA\frac{2}{n} A^{\top} A כאשר AA היא מטריצת הקלטים בעלת שתי העמודות, והערכים העצמיים שלה הם 2 ו-14.89, כך שהתקרה היא 2/14.89=0.134322 / 14.89 = 0.13432. זו תחזית עם חמש ספרות משמעותיות. בדקו אותה:

TEXT
lr=0.1343    -> L =      24.5924
lr=0.13431   -> L =      24.5924
lr=0.13432   -> L =       4707.8       BLEW UP
lr=0.13433   -> L =  4.00452e+16       BLEW UP
lr=0.1344    -> L = 1.18229e+107       BLEW UP

חמש ספרות אחרי הנקודה של התאמה בין שורת אלגברה ליניארית למאה אלף איטרציות של לולאת for.

וכאן פרק 1 חוזר. כל מה שמעל השתמש במדידות הממורכזות. הריצו את אותו קוד בדיוק על מילימטרים וגרמים גולמיים והערכים העצמיים הם 0.0298 ו-998.1 במקום 2 ו-14.89. התקרה קורסת מ-0.134 ל-0.002004 — באותה מידה של דיוק, מתכנסת ב-lr=0.002003 ומתפוצצת ב-lr=0.002004.

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

featuresמספר תנאיהקצב הטוב ביותרצעדים עד מרחק 1% מהאופטימום
ממורכז7.440.118410
מילימטרים וגרמים גולמיים33,4520.002003779,513

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

שום דבר ממה שהיה למעלה לא דרש ספרייה. הנה כל האופטימייזר.

descent.pyPYTHON
def loss(theta):
    a, b = theta
    return np.mean((a * x + b - y) ** 2)


def grad(theta):
    a, b = theta
    residual = a * x + b - y
    return np.array([np.mean(2 * residual * x), np.mean(2 * residual)])


def descend(theta, lr, steps):
    theta = np.array(theta, dtype=float)
    for _ in range(steps):
        theta = theta - lr * grad(theta)   
    return theta


theta = descend([0.0, 0.0], lr=0.05, steps=60)
print(theta, loss(theta))
TEXT
[ 2.10040296e+00 -2.76445533e-15] 24.592448791134984

תשובת least-squares הסגורה עבור שמונה הנקודות האלה היא a=2.100403a = 2.100403, b=0b = 0, עם הפסד של 24.59244924.592449. הלולאה מצאה אותה לשמונה ספרות משמעותיות בלי לדעת שקיימת צורה סגורה — וזה חשוב, כי מפרק 5 ואילך לא תהיה אחת.

המסלול, כי לראות אותו זו הנקודה:

TEXT
   0 a=0.000000 b=0.000000 L=57.437500
   1 a=1.563750 b=0.000000 L=26.736582
   2 a=1.963288 b=-0.000000 L=24.732418
   5 a=2.098116 b=-0.000000 L=24.592488
  10 a=2.100400 b=-0.000000 L=24.592449
  60 a=2.100403 b=-0.000000 L=24.592449

רוב המרחק נחתך בשני הצעדים הראשונים, כי ה-gradient הוא הגדול ביותר כשאתם הכי רחוקים מהתחתית ומתכווץ כשמתקרבים. Gradient descent מאט אוטומטית ליד מינימום. זו תכונה, ובפרק 6 זו גם בעיה.

בטיעון עד כאן יש חור. הצעד נעצר כש-L=0\nabla L = \mathbf{0}, וקראנו לזה ״המינימום״. נקודה עם gradient אפס היא נקודה קריטית, ולהיות מינימום היא רק אחת הדרכים להיות כזו:

  • מינימום מקומי: עלייה בכל כיוון, אבל ייתכן שאינו הנקודה הנמוכה ביותר מסוגה בכלל;
  • מקסימום מקומי: ירידה בכל כיוון;
  • נקודת אוכף: עלייה בכמה כיוונים וירידה באחרים. למשטח f(x,y)=x2y2f(x,y) = x^2 - y^2 יש f=(2x,2y)\nabla f = (2x, -2y), שהוא אפס בראשית, שם הפונקציה היא מינימום לאורך ציר ה-xx ומקסימום לאורך ציר ה-yy בו-זמנית.

Gradient descent לא יכול להבדיל ביניהם, כי הוא מסתכל רק על ה-gradient, וה-gradient הוא אפס בשלושתם.

לקו שלנו יש נקודה קריטית אחת והיא התשובה — הפסד של שגיאה ריבועית על מודל ליניארי הוא קמור, קערה יחידה, וירידה עליו לא יכולה להיכשל במציאת המינימום הגלובלי. התכונה הזאת לא שורדת מגע עם הקורס הזה. ההפסד של רשת נוירונים אינו קמור, ומפרק 5 ואילך ״המינימום״ הוא לא דבר שקיים: יש רבים, בעומקים שונים, ואיזה מהם תקבלו תלוי מאיפה התחלתם. זה משפט אחד ונשאר משפט אחד, כי התיאוריה גדולה והמשמעות המעשית קטנה.

אפשר לראות את כל התוצאה על עקומה אחת. קחו את f(x)=x44x22+x10f(x) = \tfrac{x^4}{4} - \tfrac{x^2}{2} + \tfrac{x}{10}, שיש לה שני עמקים בעומקים שונים:

TEXT
   x =  -1.046681   f(x) =  -0.352386   minimum
   x =   0.101031   f(x) =   0.005026   maximum
   x =   0.945649   f(x) =  -0.152639   minimum

מספר צעדים: 40, סיום ב־x = 0.9456.

הצגת הנתונים כטבלה
צעדxf(x)
0⁨0.1100⁩⁨0.0050⁩
1⁨0.1122⁩⁨0.0050⁩
2⁨0.1149⁩⁨0.0049⁩
3⁨0.1182⁩⁨0.0049⁩
4⁨0.1223⁩⁨0.0048⁩
5⁨0.1275⁩⁨0.0047⁩
6⁨0.1338⁩⁨0.0045⁩
7⁨0.1416⁩⁨0.0042⁩
8⁨0.1513⁩⁨0.0038⁩
9⁨0.1633⁩⁨0.0032⁩
10⁨0.1781⁩⁨0.0022⁩
11⁨0.1962⁩⁨0.0007⁩
12⁨0.2183⁩⁨-0.0014⁩
13⁨0.2453⁩⁨-0.0046⁩
14⁨0.2779⁩⁨-0.0093⁩
15⁨0.3170⁩⁨-0.0160⁩
16⁨0.3633⁩⁨-0.0253⁩
17⁨0.4172⁩⁨-0.0377⁩
18⁨0.4783⁩⁨-0.0535⁩
19⁨0.5455⁩⁨-0.0721⁩
20⁨0.6163⁩⁨-0.0922⁩
21⁨0.6869⁩⁨-0.1116⁩
22⁨0.7526⁩⁨-0.1277⁩
23⁨0.8092⁩⁨-0.1393⁩
24⁨0.8540⁩⁨-0.1463⁩
25⁨0.8868⁩⁨-0.1499⁩
26⁨0.9091⁩⁨-0.1516⁩
27⁨0.9236⁩⁨-0.1522⁩
28⁨0.9325⁩⁨-0.1525⁩
29⁨0.9379⁩⁨-0.1526⁩
30⁨0.9411⁩⁨-0.1526⁩
31⁨0.9430⁩⁨-0.1526⁩
32⁨0.9441⁩⁨-0.1526⁩
33⁨0.9448⁩⁨-0.1526⁩
34⁨0.9451⁩⁨-0.1526⁩
35⁨0.9454⁩⁨-0.1526⁩
36⁨0.9455⁩⁨-0.1526⁩
37⁨0.9455⁩⁨-0.1526⁩
38⁨0.9456⁩⁨-0.1526⁩
39⁨0.9456⁩⁨-0.1526⁩
40⁨0.9456⁩⁨-0.1526⁩
ירידת גרדיאנט אינטראקטיבית

ארבעים צעדים מ-x=0.11x = 0.11, מתייצבים ב-0.94560.9456 — הרדוד מבין שני העמקים. עכשיו הזיזו את נקודת ההתחלה מדרגה אחת שמאלה, ל-0.100.10. אותו קצב, אותם ארבעים צעדים, והיא מתייצבת במקום ב-1.0461-1.0461, שם ההפסד נמוך יותר ב-0.199747. קו פרשת המים הוא הגבעה ב-0.1010310.101031, וכל ההבדל בין שתי התשובות הוא באיזה צד שלה במקרה התחלתם.

נחיתה בעמק הרדוד גרועה ב-56.7% מבחינת ההפסד, ולאלגוריתם אין דרך לדעת, כי מתוך עמק כל כיוון הוא עלייה. אין לזה תיקון ב-gradient descent ולא מגיע אחד. מה שכן יש, בפועל, הוא הממצא שזה חשוב הרבה פחות ממה שהתמונה הזאת מרמזת — בממדים הגבוהים מאוד של רשת אמיתית רוב הנקודות הקריטיות מתבררות כאוכפים ולא כמלכודות,2 ופרק 5 מודד באיזו תדירות רשת קטנה באמת נתקעת.

צעדים זולים יותר: סטוכסטי, minibatch, מומנטום

קישור למקטע: צעדים זולים יותר: סטוכסטי, minibatch, מומנטום

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

הדרך החוצה היא שה-gradient הוא ממוצע, וממוצע אפשר לאמוד מתוך מדגם. חשבו אותו על קומץ אקראי — minibatch — וצעדו על זה. האומדן רועש; הוא גם חסר הטיה, ומאות צעדים רועשים זולים מנצחים צעד מדויק יקר אחד. על מאה אלף חלקים סינתטיים, כשסופרים gradients פר-דוגמה ולא צעדים:

שיטהצעדים עד מרחק 0.1% מהאופטימוםgradients פר-דוגמה
full batch7700,000
minibatch של 321003,200
דוגמה אחת בכל פעם17,58017,580

פי מאתיים ותשע-עשרה פחות חשבון כדי להגיע לאותו מקום. והקיצון — דוגמה אחת בכל פעם, הקירוב הסטוכסטי המקורי של Robbins ו-Monro3 — הוא לא המנצח: הוא גרוע פי חמישה מאצוות של 32, כי 32 דוגמאות כמעט לא עולות יותר מאחת על חומרה שמכפילה מטריצות, בזמן שהרעש יורד עם השורש הריבועי של גודל האצווה. הטרייד-אוף הזה הוא הסיבה שבכל סקריפט אימון שתקראו אי פעם יש batch_size.

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

vβv+L(θ),θθηv\mathbf{v} \leftarrow \beta \mathbf{v} + \nabla L(\boldsymbol{\theta}), \qquad \boldsymbol{\theta} \leftarrow \boldsymbol{\theta} - \eta \mathbf{v}

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

TEXT
momentum beta=0.0   ->    79,513 steps to 1%
momentum beta=0.9   ->     1,609 steps to 1%
momentum beta=0.99  ->       461 steps to 1%

פקטור של 172 עבור שתי שורות קוד. פרק 6 הופך את זה ל-Adam; המנגנון כבר כאן.

כל gradient בפרק הזה נגזר ביד ולכן יכול להיות שגוי. התיקון הוא טבלת השיפועים מההתחלה: למדוד את הנגזרת נומרית ולהשוות. השתמשו בהפרש המרכזי, L(θ+h)L(θh)2h\frac{L(\theta+h) - L(\theta-h)}{2h}, שמבטל את איבר השגיאה המוביל ומדויק הרבה יותר עבור אותו hh.

gradcheck.pyPYTHON
def numeric_grad(f, theta, h=1e-5):
    theta = np.asarray(theta, dtype=float)
    out = np.zeros_like(theta)
    for i in range(theta.size):
        bump = np.zeros_like(theta)
        bump[i] = h
        out[i] = (f(theta + bump) - f(theta - bump)) / (2 * h)     
    return out


def gradcheck(f, df, theta, h=1e-5):
    analytic = np.asarray(df(theta), dtype=float)
    numeric = numeric_grad(f, theta, h)
    return np.max(np.abs(analytic - numeric) / np.maximum(1e-8, np.abs(analytic) + np.abs(numeric)))

הצורה היחסית של ההשוואה חשובה: הפרש מוחלט של 10410^{-4} הוא אסון על gradient בגודל 10310^{-3} וחסר משמעות על אחד בגודל 10610^{6}.

TEXT
relative error: 1.8929136036763527e-11
with 2 dropped: 0.33333333331650744

השורה הראשונה היא ה-gradient שנגזר ביד למעלה. השנייה היא אותה פונקציה עם הפקטור 2 שנשאר מחוץ לרכיב אחד — טעות הקלדה של תו יחיד — והבדיקה תופסת אותה מיד. כל דבר מתחת בערך ל-10710^{-7} הוא הסכמה; כל דבר מעל 10410^{-4} הוא באג. שמרו את הפונקציה הזאת: פרק 5 משתמש בה כדי לנפות באגים במנוע גזירה אוטומטית, והיא הסיבה היחידה שאפשר בכלל למצוא gradient שגוי.

כל מה שבפרק הזה נשען על הנחה אחת שמעולם לא נאמרה: שאפשר לכתוב את L/θ\partial L / \partial \theta.

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

רשתפעולות בנגזרת חלקית אחת
ארבע יחידות חבויות, שכבה אחת40
ארבע יחידות חבויות, שתי שכבות301
ארבע יחידות חבויות, שלוש שכבות1,717

השורה השלישית היא רשת עם 57 פרמטרים — רשת כל כך קטנה שהיא הייתה הערת שוליים בפרק 6 — ולכתוב את ה-gradient שלה ביד פירושו בערך 97,869 פעולות עבור דוגמת אימון אחת. אין סימון שמציל מזה. מה שמציל הוא ההבחנה שלכלל השרשרת כשהוא מיושם על הרכבה יש מבנה עצום, שאותן כמויות ביניים מופיעות שוב ושוב, ושחישוב שלהן בסדר הנכון נותן את כל הנגזרות בערך במחיר של מעבר קדימה אחד. זה פרק 5.

אבל יש קודם בעיה קטנה יותר, והיא מחכה מיד.

עכשיו יש לנו מכונה שתתגלגל במורד על כל הפסד גזיר. כוונו אותה לשאלה המקורית של המסוע — לקבל או לדחות, יעד שהוא 1 או 0 — שימו sigmoid על הפלט כדי שיחזה הסתברות, ומזערו שגיאה ריבועית. היא תרוץ. היא גם בקושי תזוז כשהיא הכי טועה, וה-gradient מסביר למה:

פלט zzחיזויאמתgradient עם שגיאה ריבועיתgradient עם cross-entropy
000.500012.5×1012.5 \times 10^{-1}5.0×1015.0 \times 10^{-1}
2-20.119211.850×1011.850 \times 10^{-1}8.808×1018.808 \times 10^{-1}
6-60.002514.921×1034.921 \times 10^{-3}9.975×1019.975 \times 10^{-1}
10-104.54×1054.54 \times 10^{-5}19.079×1059.079 \times 10^{-5}1.0001.000

מודל שטועה בביטחון, באופן קטסטרופלי — חוזה 0.0000454 כשהתשובה היא 1 — מייצר gradient של שגיאה ריבועית בגודל 9×1059 \times 10^{-5}. אין לו מושג שהוא בצרות. העמודה השנייה, מהפסד שעדיין לא גזרנו, מדווחת 1.0: דחיפות מקסימלית, בדיוק איפה שמגיע.

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


השיטה עתיקה יותר מכל אלה: Cauchy תיאר אותה בהערה ל-Académie des Sciences ב-1847, כדרך לפתור מערכות משוואות באמצעות הליכה במורד על סכום השאריות הריבועיות שלהן. כדאי לקרוא לצד הפרק הזה גם את An overview of gradient descent optimization algorithms של Sebastian Ruder (arXiv:1609.04747), שמכסה מומנטום עד Adam בארבעה-עשר עמודים קריאים; פרק 3 ב-Numerical Optimization של Nocedal ו-Wright (מהדורה 2, Springer, 2006), שמשפט 3.3 שלו נותן את קצב ההתכנסות של steepest descent על ריבועית במונחי מספר התנאי — זו התיאוריה שמאחורי הסיבה ש-conditioning קובע את מספר הצעדים, אף שהוא מטפל ב-line search ולא בתקרת 2/λmax2/\lambda_{\max} בצעד קבוע שנמדדה למעלה, או §5.8 ו-§7.1 ב-Mathematics for Machine Learning של Deisenroth, Faisal ו-Ong לאותה קרקע עם פחות מכונות; §6.1 ב-Understanding Deep Learning של Prince ו-§4.3 ב-Deep Learning של Goodfellow, Bengio ו-Courville; Dive into Deep Learning §12.1–12.3, שיש בו את ניתוח ה-minibatch עם יותר מדידות ממה שיש כאן מקום; ופרק 4 ב-Hands-On Machine Learning של Géron (מהדורה 3), הטיפול המעשי ביותר בקצב הלמידה כדבר שמכוונים ולא גוזרים. הערות MIT 6.390 שמות את gradient descent לפני סיווג, כמו שהקורס הזה עושה ומאותה סיבה.

  1. LeCun, Y., Bottou, L., Orr, G. B. and Müller, K.-R. Efficient BackProp, in Neural Networks: Tricks of the Trade (Springer, 1998), pp. 9–50. סעיף 4.3 נותן את ההמלצה וסעיף 5.1 את הטיעון ששימש בתיבת הפירוט למעלה: מירכוז וסקיילינג של קלטים משנים את הערכים העצמיים של מטריצת הנגזרות השניות, ולכן את מספר הצעדים, ולא רק את הנוחות הנומרית.

  2. Dauphin, Y. N., Pascanu, R., Gulcehre, C., Cho, K., Ganguli, S. and Bengio, Y. Identifying and attacking the saddle point problem in high-dimensional non-convex optimization, arXiv:1406.2572 (2014). הטיעון שבממדים גבוהים נקודות קריטיות הן ברובן המכריע אוכפים ולא מינימות מקומיות, כי מינימום דורש שכל אחד מאלפי הכיוונים יתעקם כלפי מעלה בו-זמנית.

  3. Robbins, H. and Monro, S. A Stochastic Approximation Method. Annals of Mathematical Statistics 22(3), pp. 400–407 (1951). המאמר שקבע שאומדן רועש של gradient מספיק, בהינתן גודל צעד שמתכווץ בדרך הנכונה.

  4. Polyak, B. T. Some methods of speeding up the convergence of iteration methods. USSR Computational Mathematics and Mathematical Physics 4(5), pp. 1–17 (1964). שיטת heavy-ball, שהיא עדכון המומנטום שלמעלה, עשרים ושתיים שנה לפני ש-backpropagation הגיע לתחום הזה.


נוצר על ידי

David Vicente Campos

מייסד NeuraLIA Labs ושותף-מייסד MyRealFood

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

עוד על המחבר

פורסם על ידי NeuraLIA Labs.

פוסטים חדשים ישירות לתיבת הדואר

חדשות AI, מדריכים ועדכוני מוצר — מייל קצר כשאנחנו מפרסמים משהו ששווה את הזמן שלך.

תוכן הקורס

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev10 דקות קריאה

מודל ה-AI Jev נבנה להחלטות, לא לפרוזה

Jev של TypeSafe AI מושך תשומת לב כי הוא מתייחס לאינטליגנציית תוכנה כאל בעיית הסתברות: לבחור את ההסתעפות הנכונה, להצמיד ביטחון, ולהימנע מתשלום ל-LLM כדי שיכתוב טקסט כשהקוד צריך החלטה.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering10 דקות קריאה

הנדסת הקשר לסוכני AI ארוכי־טווח

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

מוכנים לתת ל-LIA לבחור?

בנו עם כל מודלי ה-AI במקום אחד — התחילו בחינם עוד היום.