חיזוי ה-token הבא: embedding, ומה באמת אומרת perplexity
מאמנים מודל תווים על 32,033 שמות, רואים את gradient descent משחזר טבלת ספירות, ומבינים למה perplexity כמעט לא תואמת.
בעמוד הזה
הנה עשרה שמות שהופקו בידי תוכנית שמעולם לא ראתה מילה:
cexze momakurailezitynn konimittain llayn ka
da moliellavo emia sade ftlspאף אחד מהם אינו שם. כמעט כולם מנסים להיות. הם ניתנים להגייה, הם מסתיימים במקום שבו שמות מסתיימים, ואחד מהם — emia — רחוק אות אחת משם אמיתי. התוכנית שהפיקה אותם מחזיקה 729 מספרים, אין לה מושג מהי מילה, הברה או אדם, והיא הותאמה במעבר יחיד של ספירת זוגות אותיות סמוכות.
עד סוף הפרק הזה רשת נוירונים תחתוך את הציון של התוכנית הזאת בשליש באותה מדידה. החלק ששווה להישאר בשבילו הוא מה שהרשת עושה קודם: היא משחזרת את טבלת הספירות עד שלוש ספרות אחרי הנקודה בכל שורה מאוכלסת היטב, בלי prompt, מפני ששני האובייקטים הם תשובות לאותה שאלה. כל מה שאחר כך הוא מה שספירה לעולם לא הייתה יכולה לעשות.
המטרה היא זהות, לא החלטת תכנון
קישור למקטע: המטרה היא זהות, לא החלטת תכנוןפרק 7 השאיר אתכם עם רצף של מספרים שלמים וללא סיבה לכך שאחד יבוא אחרי אחר. הנה הסיבה, והיא שורה אחת מתוך פרק 2.
מודל שפה הוא פונקציה שלוקחת את ה-token עד עכשיו ומחזירה התפלגות על איזה token יבוא אחר כך: מספר אחד לכל ערך באוצר המילים, לא שלילי, וסכומם אחד. שום דבר אחר. כדי להגיע מזה להסתברות של מסמך שלם, מפעילים את כלל השרשרת של הסתברות:
זו זהות, נכונה לכל רצף של כל דבר, ללא הנחות נלוות. לכן מודל שעושה את העבודה הקטנה — token הבא בהינתן הקודמים — כבר עשה את העבודה הגדולה של שיוך הסתברות לכל מסמך אפשרי, בדיוק ובחינם. המסגור הפופולרי של זה כטריק זול (״זה רק חוזה את המילה הבאה״) הופך את הלוגיקה: חיזוי ה-token הבא הוא מידול ההתפלגות המשותפת. מעולם לא היה דבר שני לעשות.
ה-loss נובע באותה מכניות. בכל מיקום המודל מפיק התפלגות והאמת היא token ידוע יחיד, ולכן ה-cross-entropy של פרק 4 חל ללא שינוי:
זהו ממוצע ה-negative log-likelihood — המתכון של פרק 2 עם התפלגות קטגורית במקום שבו ישבה הגאוסיאנית. ומכיוון שההתפלגות האמיתית היא one-hot, האנטרופיה שלה היא אפס, ולכן לפי הזהות של פרק 4 ה-cross-entropy שווה ל-KL divergence: הורדת המספר הזה ומשיכת האמונות של המודל אל עבר הנתונים הן אותה פעולה.
מסקנה אחת ראויה למשפט משלה, כי היא העובדה הכלכלית שמתחת לכל התחום. התוויות הן הנתונים, מוזזים במיקום אחד. אף אחד לא מתייג כלום. טריליון token של טקסט הם טריליון דוגמאות מתויגות מראש, ולכן קורפוס האימון של מודל מודרני הוא ״האינטרנט״ ולא ״dataset שמישהו בנה״.
קו הבסיס ההוגן: ספירה
קישור למקטע: קו הבסיס ההוגן: ספירהלפני כל רשת, קו הבסיס: 32,033 שמות, אחד בכל שורה, והמשימה היא להפיק עוד כאלה אות אחת בכל פעם.1
אוצר המילים הוא 26 אותיות ועוד סימן גבול . שמסמן גם את ההתחלה וגם את הסוף של שם, כך שהמודל צריך ללמוד היכן שמות מתחילים והיכן הם נעצרים. אלה 27 סמלים, והמודל הקטן ביותר האפשרי הוא טבלה של התדירות שבה כל סמל בא אחרי כל סמל אחר.
N = torch.zeros((27, 27), dtype=torch.int32)
for w in words:
cs = ["."] + list(w) + ["."]
for a, b in zip(cs, cs[1:]):
N[stoi[a], stoi[b]] += 1
P = N.float()
P = P / P.sum(1, keepdim=True) # one distribution per row שתי שורות של אריתמטיקה והמודל מותאם — וזה אינו heuristic: חלוקת הספירות בסכומי השורות היא אומדן maximum-likelihood להתפלגות קטגורית, כלומר המתכון של פרק 2 אחרי שהחשבון הדיפרנציאלי כבר נעשה.
names: 32033 train/val/test: 25626 / 3203 / 3204
training bigrams: 182583
the six most likely letters after 'a':
a -> '.' 0.1944 a -> 'n' 0.1600 a -> 'r' 0.0967
a -> 'l' 0.0749 a -> 'h' 0.0690 a -> 'y' 0.0606דגמו ממנו — בחרו אות מהשורה של האות הנוכחית, עברו לשורה הזאת, וחזרו עד שסימן הגבול מופיע — ותקבלו את השמות שבראש הפרק הזה. הם נכשלים בדרך מסוימת ומלמדת: מקומית סבירים, גלובלית שטויות. כל זוג אותיות סמוכות ב-momakurailezitynn הוא זוג שמופיע בשמות אמיתיים; פשוט יש שבעה-עשר כאלה ברצף. למודל יש זיכרון של אות אחת, ולכן הוא לא יכול לדעת שהוא ממשיך יותר מדי זמן.
Perplexity, ואיך לקרוא אותה
קישור למקטע: Perplexity, ואיך לקרוא אותהה-loss על שמות held-out הוא 2.4546 nats. המספר הזה לא אומר כלום בפני עצמו, ולכן קיימת perplexity:
בכתיב מלא, בלי ספרייה שעושה את העבודה:
@torch.no_grad()
def perplexity(logits, Y):
logp = F.log_softmax(logits, dim=1) # log q for every symbol
chosen = logp[torch.arange(len(Y)), Y] # log q of the one that came next
return torch.exp(-chosen.mean()) האקספוננט מחזיר את הלוגריתם לאחור ומחזיר את המספר ליחידות של ספירת דברים. הדרך הנקייה לראות מה הוא סופר היא למדוד מודל שלא יודע כלום — כזה שמקצה הסתברות לכל סמל בלי קשר ל-context:
uniform over 27 symbols loss 3.2958 nats ppl 27.000
bigram counts, add-one smoothed loss 2.4546 nats ppl 11.642בדיוק 27.000, כי . Perplexity היא מספר האפשרויות השוות-סיכוי האפקטיבי שהמודל בוחר ביניהן. Perplexity של 27 אומרת ״אין לי מושג, זה יכול להיות כל דבר״. ה-11.642 של מודל הספירה אומר ש-context של אות אחת משאיר אותו באי-ודאות כמו מישהו שבוחר בעיוורון מתוך בערך שתים-עשרה אפשרויות במקום עשרים ושבע — ולכן מצטטים perplexity ולא את ה-loss הגולמי.
שני דברים משתבשים בה, והשני משתבש גם במאמרים שפורסמו.
הסתברויות אפס הן קטלניות. מתוך 729 התאים בטבלה, 113 אינם מופיעים כלל באימון — 15.5 % ממנה ריק. זה בסדר עד שה-held-out set נוחת באחד מהם, וב-validation שבעה bigrams אכן עושים זאת, ביניהם d→q, z→j ו-q→o פעמיים. הסתברות אפס פירושה log , כלומר loss אינסופי ו-perplexity אינסופית: שם אחד מתוך שלושת אלפים הורס את המדד. התיקון המקובל הוא להוסיף 1 לכל ספירה לפני נרמול, וזה כמעט לא עולה כאן כלום (2.4546 במקום 2.4524). אבל התיקון הוא הודאה. מודל ספירה אינו יכול להכליל כלל. אין לו דרך לחשוד ש-q→o סביר מפני ש-q→u נפוץ ו-o מתנהג כמו u במקומות אחרים, כי אין לו מושג ששני סמלים יכולים לדמות זה לזה. כל תא נלמד לבדו, ותיקון הבעיה הזאת הוא מטרת שאר הפרק.
Perplexity היא מחיר לכל token, וה-token הוא פרמטר חופשי. זו הטעות שמופיעה כל הזמן כשמשווים מודלים, וקל לראות אותה ברגע שמסתכלים. קחו את אותו קורפוס של פרוזה באנגלית מפרק 7, את אותו מודל bigram עם אינטרפולציה, ושנו רק את אופן החיתוך של הטקסט:
| יחידה | אוצר מילים | token במבחן | cross-entropy | perplexity | bits לתו |
|---|---|---|---|---|---|
| תווים | 76 | 14,469 | 2.5217 | 12.45 | 3.6378 |
| BPE, 512 מיזוגים | 329 | 6,871 | 3.8547 | 47.21 | 2.6407 |
| BPE, 2,048 מיזוגים | 1,820 | 4,233 | 5.7468 | 313.20 | 2.4254 |
| מילים | 2,991 | 6,284 | 3.5627 | 35.26 | 2.2322 |
Perplexity משתנה פי 25 בין השורות האלה. שום דבר במודל לא השתנה; רק גודל הדבר שחוזים. קשה יותר לחזות מילה שלמה מאשר אות, ולכן המחיר לכל חיזוי גבוה יותר — ויש פחות חיזויים לבצע.
עכשיו קראו את העמודה האחרונה, שמחלקת את העלות הכוללת במספר התווים במקום זאת וממירה אותה ל-bits. היא משנה את סדר הטבלה. לפי perplexity הדירוג הוא תווים, מילים, BPE-512, BPE-2048; לפי bits לתו הוא מילים, BPE-2048, BPE-512, תווים. מודל התווים עובר מהמקום הראשון לאחרון. מודל 2,048 המיזוגים, שלפי perplexity נראה גרוע פי 6.6 ממודל 512 המיזוגים, הוא למעשה הטוב מבין השניים עם 2.4254 bits מול 2.6407.
לכן perplexity ניתנת להשוואה רק בין שני מודלים שחולקים tokenizer, ומודלים עם tokenizers שונים אפשר להשוות רק ב-bits לתו — הכמות ששאנון מדד ב-1951 כשנתן לנבדקים אנושיים לנחש את האות הבאה בטקסט אנגלי, וחסם בערך ב-bit אחד לתו.2 ה-bigram הטוב ביותר שלנו עומד על 2.23 bits, וזה סיכום הוגן של כמה הדרך של הפרק הזה עוד ארוכה.
אותו דבר, נלמד
קישור למקטע: אותו דבר, נלמדעכשיו נבנה את אותו מודל כרשת. היא תידרש לסדרי גודל יותר אריתמטיקה כדי להגיע לאותו מקום, וההגעה לאותו מקום היא הנקודה.
החליפו את הטבלה במטריצת משקולות אחת בצורה . הפכו את האות הנוכחית לווקטור one-hot, הכפילו, וקראו לתוצאה logits — הציונים הלא מנורמלים מפרק 4. אחר כך softmax, אחר כך cross-entropy, אחר כך gradient descent.
W = torch.randn((27, 27), requires_grad=True)
for step in range(3000):
logits = W[xs]
loss = F.cross_entropy(logits, ys)
W.grad = None
loss.backward()
W.data -= 50.0 * W.gradהשורה המודגשת מכילה הגדרה שכדאי להחזיק. הכפלת וקטור one-hot במטריצה בוחרת שורה אחת ממנה, ולכן ההכפלה היא lookup — וכל מימוש מדלג על האריתמטיקה ועושה את ה-lookup ישירות, וזה מה ש-W[xs] עושה.
זו טבלת embedding. מטריצה עם שורה אחת לכל ערך באוצר המילים, מאונדקסת לפי token id. אין גאומטריה, אין סמנטיקה, אין אלגוריתם נפרד: טבלת lookup שהתוכן שלה במקרה נלמד באמצעות gradient descent יחד עם כל השאר. כל טענה מיסטית על ״embedding space״ מסתיימת כאן.
אמנו אותה וצפו לאן היא הולכת:
step 1 train 3.7550 val 3.3882 max gap to the count table 0.757269
step 100 train 2.4732 val 2.4726 max gap to the count table 0.388354
step 1000 train 2.4557 val 2.4549 max gap to the count table 0.041862
step 3000 train 2.4547 val 2.4544 max gap to the count table 0.004048העמודה האחרונה היא ההפרש המוחלט הגדול ביותר בין תא כלשהו של softmax(W) לבין התא התואם בטבלת הספירות, והוא יורד לאפס. אחרי 3,000 צעדים, אי-ההסכמה הגדולה ביותר בכל אחד מ-729 התאים היא 0.004048 והממוצע הוא 0.000224. התא הגרוע ביותר הוא q→i, שנראה שתים-עשרה פעמים בכל קבוצת האימון; מבין 22 השורות עם יותר מאלף מופעים, אי-ההסכמה הגרועה ביותר היא 0.000562.
count table network
a -> '.' 0.1945 0.1945
a -> 'n' 0.1601 0.1601
a -> 'r' 0.0967 0.0967Gradient descent, שהתחיל ממספרים אקראיים ולא נאמר לו דבר מלבד ״הגדל את ה-log-probability של האות הבאה״, גילה מחדש את טבלת הספירות. והוא היה חייב: הספירות הן אומדן maximum-likelihood, cross-entropy הוא negative log-likelihood, ולכן שתי הפרוצדורות ממטבות את אותה מטרה ולמטרה הזאת יש optimum אחד. הרשת לא למדה משהו כמו ספירה. היא התכנסה לספירה, לאט.
וזה מעלה את השאלה ההוגנת למה שמישהו יטרח. כי לטבלת הספירות אין לאן ללכת מכאן, ולרשת יש.
Context הוא צוואר הבקבוק, לא הקיבולת
קישור למקטע: Context הוא צוואר הבקבוק, לא הקיבולתהרחיבו את המודל כך שיביט ביותר מתו קודם אחד. זו הארכיטקטורה של Bengio מ-2003, האב הישיר של כל מודל בהמשך הקורס הזה:4 לוקחים את שלושת התווים האחרונים, ממפים כל אחד דרך טבלת embedding לשורה בת 10 ממדים, מחברים את השורות ל-30 מספרים, דוחפים אותם דרך השכבה החבויה של פרק 5, ומסיימים בשכבת פלט שמפיקה logit אחד לכל ערך באוצר המילים.
C = torch.randn((27, 10)) # the embedding table
W1 = torch.randn((3 * 10, 200)) # the hidden layer from Chapter 5
W2 = torch.randn((200, 27)) # one output per vocabulary entry
emb = C[X].view(-1, 30) # three lookups, concatenated
h = torch.tanh(emb @ W1 + b1)
logits = h @ W2 + b2
loss = F.cross_entropy(logits, Y)שימו לב מה חדש ומה לא. השכבה החבויה היא זו של פרק 5, ללא שינוי; ה-loss הוא זה של פרק 4, ללא שינוי. החידושים הם טבלת ה-embedding בחזית ושכבת פלט ברוחב אוצר המילים של פרק 7 — והחלק השני הוא החלק היקר בכל מודל שפה שנבנה אי פעם, כי לאוצר מילים אמיתי יש 100,000 ערכים וכפל המטריצה הזה רץ בכל מיקום.
אותו קוד, מאומן באותו אופן, כשרק גודל ה-context window משתנה:
| context | פרמטרים | validation loss | validation perplexity |
|---|---|---|---|
| ספירה, תו אחד | 729 | 2.4546 | 11.642 |
| נוירוני, תו אחד | 7,897 | 2.4577 | 11.678 |
| נוירוני, 3 תווים | 11,897 | 2.1145 | 8.285 |
| נוירוני, 8 תווים | 21,897 | 2.0506 | 7.773 |
השורה השנייה היא המעניינת. רשת עם שכבה חבויה של 200 יחידות ופי אחד-עשר יותר פרמטרים מטבלת הספירות מתפקדת בדיוק כמו טבלת הספירות ולא טוב יותר. הקיבולת מעולם לא הייתה המגבלה. context של תו אחד מאפשר loss מסוים ושום דבר שתבריגו עליו לא יוכל לרדת מתחתיו, כי המידע לא שם.
תנו לה שלושה תווים וה-perplexity יורדת מ-11.68 ל-8.29 — ירידה של 29 %, שנקנתה ב-4,000 פרמטרים נוספים. היא מנצחת כאן את הספירה בדיוק מהסיבה שאובחנה קודם: מודל ספירה מעל contexts בני שלושה תווים צריך שורות, רובן ריקות או מחזיקות תצפית יחידה, והוא לומד כל אחת לבדה. הרשת משתפת. אם a, e ו-i מקבלים בסוף שורות embedding דומות, מה שהיא לומדת אחרי bra עובר ל-bre בלי שהיא ראתה אי פעם את bre. ההעברה הזאת היא כל הערך של טבלת ה-embedding, והיא הפער בין שורות שתיים ושלוש.
הדגימות משתפרות בהתאם:
deliah nellara joce kael quintis
salayson reety khyrmin mahnen madiaryxiaעדיין לא רשימת שמות אמיתיים. אבל deliah, nellara ו-kael לא היו נראים לא במקום ברשימה כזאת, ומפלצות הריצה-קדימה נעלמו: הארוך מבין עשרים דגימות ממודל הספירה הוא בן תשע-עשרה אותיות, הארוך מבין עשרים מהמודל הזה הוא בן שלוש-עשרה.
מה באמת נמצא בתוך טבלת ה-embedding
קישור למקטע: מה באמת נמצא בתוך טבלת ה-embeddingהטבלה היא : שורה אחת של עשרה מספרים לכל תו, כולם מאותחלים אקראית וזזים רק לפי ה-gradient של loss האות הבאה. אף אחד לא הכניס לשם דבר. אז מה הגיע לשם?
הכלי לשאול זאת הוא cosine similarity, שהוא המכפלה הסקלרית של פרק 1 אחרי שמחלקים החוצה את האורכים:
הוא מודד את הזווית בין שני וקטורים ומתעלם מהאורכים שלהם, וזה מה שאתם רוצים כשאורך של שורה משקף כמה פעמים ה-token שלה הופיע ולא מה הוא אומר. נרמלו קודם כל וקטור לאורך 1 — כפי שמערכות אמיתיות עושות, פעם אחת, בזמן אינדוקס — ו-cosine similarity הוא פשוט המכפלה הסקלרית.
הנה השכנים הקרובים ביותר של כמה תווים בטבלה המאומנת:
'c' -> 'k':+0.598 'j' -> 'z':+0.650 'i' -> 'y':+0.541
'u' -> 'e':+0.482 'a' -> 'h':+0.367 '.' -> 'q':+0.077חלק מזה הוא מה שהפולקלור מבטיח. c ו-k ניתנים להחלפה בשמות, וכך גם i ו-y; j ו-z הם שניהם עיצורים נדירים, בעיקר בתחילת שם, שמתנהגים דומה. סימן הגבול . לא קרוב כמעט לכלום — 0.077 לאות הקרובה ביותר — כי הוא הסמל היחיד שמסמן מיקום ולא צליל.
וחלק מזה לא. השכן הקרוב ביותר של a הוא h, לא תנועה אחרת. בממוצע על פני כל הזוגות:
mean cosine, vowel to vowel : +0.1889
mean cosine, consonant to consonant : +0.0765
mean cosine, vowel to consonant : -0.0042התנועות דומות יותר זו לזו מאשר לעיצורים, והאפקט אמיתי אך קטן. בבדיקה מול 2,000 קבוצות אקראיות של חמש אותיות, 58 מהקבוצות האלה נפרדות לפחות באותה ניקיון — פער מובהק בערך ברמת . אמיתי, אם כך, אבל ממש לא האי הגאומטרי החד שתיאורים פופולריים של embeddings מרמזים עליו.
זה התיאור ההוגן של טבלת embedding וכדאי להחזיק בו לשאר הקורס. זו אינה מפה של משמעות. זהו שינוי קואורדינטות, נלמד ולא מתוכנן, שתפקידו היחיד הוא להקל על העבודה של השכבה הבאה — אותו משפט שפרק 5 השתמש בו לשכבה החבויה שקיפלה את המישור כדי לפתור XOR. כל מבנה שתמצאו בו נמצא שם מפני שהוא הוריד את ה-loss, ומבנה שלא מוריד את ה-loss פשוט אינו שם.
word2vec, GloVe, והאריתמטיקה שכולם מצטטים
קישור למקטע: word2vec, GloVe, והאריתמטיקה שכולם מצטטיםאם החלק השימושי הוא הטבלה, אפשר לרדוף אחריו ישירות. זה word2vec: משאירים את ה-embedding lookup, זורקים את מודל השפה.5
מטרת skip-gram with negative sampling היא שורה אחת. עבור זוג אמיתי (מרכז, context) שנמשך מהקורפוס, דוחפים את המכפלה הסקלרית שלהם למעלה; עבור זוגות מזויפים שנמשכים מהתפלגות רעש, דוחפים אותה למטה:6
זה סיווג בינארי — ״האם שתי המילים האלה באמת הופיעו יחד?״ — והוא זול בדיוק מפני שהוא לעולם לא נוגע בכל אוצר המילים, וזה מה שהפך אימון על מיליארדי מילים למעשי ב-2013. GloVe מגיע לווקטורים דומים מהכיוון האחר, באמצעות פירוק מטריצת ספירות ה-co-occurrence הגלובליות במקום זרימה דרך דוגמאות.7 שניהם מותאמים בדיוק לסטטיסטיקה שממנה נבנתה טבלת הספירות. הם ספירה, דחוסה.
באימון על text8 — 17,005,207 מילים מוויקיפדיה האנגלית, 71,290 מהן מופיעות לפחות חמש פעמים, 100 ממדים, שלושה מעברים — הווקטורים יוצאים עם התכונה שהפכה אותם למפורסמים:
king -> charles 0.700, son 0.693, queen 0.686, henry 0.669, throne 0.667
physics -> chemistry 0.672, electromagnetism 0.661, quantum 0.654, theoretical 0.624
guitar -> bass 0.733, vocals 0.732, acoustic 0.728, guitars 0.703, drums 0.685
three -> seven 0.892, two 0.877, one 0.875, five 0.871, four 0.870אף אחד לא סיפק קטגוריה לכלי נגינה או למספרים. עכשיו החלק המפורסם: קחו את king, החסירו את man, הוסיפו את woman, ומצאו את הווקטור הקרוב ביותר לתוצאה.
king - man + woman
nothing excluded : king 0.693, elizabeth 0.657, wife 0.629, woman 0.607
a, b, c excluded : elizabeth 0.657, wife 0.629, mary 0.607 (queen is 4th, 0.604)הווקטור הקרוב ביותר ל-king - man + woman הוא king. זו לא מוזרות של דוגמה אחת. סט ההערכה של Mikolov מציב שאלות בצורה a : b :: c : ? — 8,869 סמנטיות (paris : france :: rome : italy) ו-10,675 תחביריות (walking : walked :: swimming : swam) — וב-4,103 השאלות הסמנטיות שאוצר המילים הזה יכול לענות עליהן, הזוכה הוא אחת משלוש מילות הקלט 99.8 % מהזמן. ההדגמות שפורסמו לא מזכירות זאת, כי כלל הניקוד הסטנדרטי מוחק את a, b ו-c לפני שהוא מסתכל. זה כלל לגיטימי, והוא עושה יותר עבודה מהאריתמטיקה:
| איך התשובה נבחרת | סמנטי | תחבירי |
|---|---|---|
| offset, עם הקלטים מוחרגים (סטנדרטי) | 17.0 % | 11.9 % |
| offset, בלי להחריג כלום | 0.1 % | 0.4 % |
השכן הקרוב ביותר של c בלבד, קלטים מוחרגים | 13.1 % | 9.3 % |
השכן הקרוב ביותר של b בלבד, קלטים מוחרגים | 2.3 % | 0.4 % |
השורה השלישית היא זו שכדאי לשבת איתה. זרקו את a ו-b, אל תעשו שום אריתמטיקה, החזירו את מה שהכי קרוב ל-c — ותשמרו 77 % מהציון הסמנטי. רוב מה שנראה כמו הסקה אנלוגית הוא קרבה בתוספת כלל שאוסר את התשובות הברורות, וזה מה ש-Linzen מדד על וקטורים שאומנו כראוי ומה שקווי הבסיס שלמעלה משכפלים.8 הווקטורים המסוימים האלה קטנים — 17 מיליון מילים מול המיליארדים שמאחורי המודלים שפורסמו — לכן קראו את האחוזים כצורה, לא כ-state of the art. הצורה היא מה ששורד בכל קנה מידה: האריתמטיקה אמיתית, וחלשה בהרבה מההדגמה היחידה שכולם מצטטים.
סטטי ו-contextual: וקטור אחד לכל מילה, או אחד לכל מופע
קישור למקטע: סטטי ו-contextual: וקטור אחד לכל מילה, או אחד לכל מופעלכל מה שהיה עד עכשיו יש מגבלה קשיחה שמובנית במבנה הנתונים. לטבלה יש שורה אחת לכל token. המילה bank מקבלת וקטור אחד, אותו וקטור במשפט על נהר ובמשפט על משכנתא — בהכרח, כי lookup לפי id לא יכול להיות תלוי בשום דבר אחר.
התיקון הוא להפסיק לקרוא את הווקטור מהטבלה ולהתחיל לחשב אותו מהמשפט. זהו contextual embedding, שהוצג על ידי ELMo ב-2018 והפך לסטנדרט עם BERT באותה שנה.910 במדידה על המודל האמיתי, המספרים חדים יותר מההסבר:
sentence A: "He sat on the bank of the river and watched the water go by."
sentence B: "She deposited the cheque at the bank on the corner of the street."
static vector for 'bank' (a row of the input embedding table)
cosine A vs B ........................ 1.000000
contextual vector for 'bank', layer by layer
layer | A vs B | A vs another river sentence | B vs another money sentence
0 | 0.9512 | 0.9512 | 0.9359
4 | 0.5647 | 0.8987 | 0.7716
9 | 0.4284 | 0.8699 | 0.7568
12 | 0.5278 | 0.8702 | 0.7335השורה הראשונה מדויקת, לא מקורבת: הווקטור הסטטי של bank הוא אותם 768 מספרים בשני המשפטים, ולכן ה-cosine הוא 1 מעצם הבנייה. תשע שכבות מאוחר יותר שני המופעים יושבים על 0.43, בעוד bank בשני משפטי נהר שונים נשאר ב-0.87. אף אחד לא תייג משמעות בשום מקום בתהליך הזה; המשמעויות נפרדו כי ההפרדה שלהן מקלה על סיפוק מטרת האימון — ניחוש token מוסתר מתוך שכניו.
שני פרטים מחזירים תשומת לב. שכבה 0 כבר עומדת על 0.9512 ולא על 1.0, כי position embeddings נוספו והמילה יושבת במקום שונה בכל משפט. והדמיון עולה שוב בשכבות 11 ו-12: השכבות הסופיות של מודל pretrained מתמחות במטרת האימון שלו, ולעיתים קרובות הן לא המקום הטוב ביותר לקחת ממנו representation.
הצגת פרטים
אופציונלי: weight tying.
ב-bert-base-uncased טבלת ה-embedding היא — 23,440,896 מספרים, 21.4 % מתוך 109,482,240 הפרמטרים של המודל. במודל שפה קטן החלק גדול עוד יותר, ולכן טריק אחד כמעט אוניברסלי: טבלת הקלט ושכבת הפלט שמפיקה את ה-logits הן אותה מטריצה, משמשת פעם אחת ב-row lookup ופעם אחת משוחלפת.11 שכבת הפלט כבר מקצה לכל ערך באוצר המילים וקטור — היא לוקחת מכפלה סקלרית מול כל אחד — ו-tying אומר שהווקטור שמשמש לקרוא token והווקטור שמשמש לכתוב אותו צריכים להיות אותו אובייקט. זה מקצץ פרמטרים ומשפר perplexity בבת אחת, דבר נדיר מספיק כדי לשים לב אליו.
embedding model אינו מודל שפה
קישור למקטע: embedding model אינו מודל שפהכדי לחפש קורפוס לפי משמעות צריך וקטור אחד לכל משפט. בהינתן אלה, החיפוש טריוויאלי — זה כל הסיפור של retrieval סמנטי, ו-פרק 19 עוסק בכל מה שמסביבו:
E = normalise(embed(sentences)) # (200, d), every row of length 1
q = normalise(embed([query])) # (1, d)
scores = q @ E.T # one matrix multiply
top5 = scores[0].argsort()[::-1][:5]לכן השאלה האמיתית היחידה היא מאיפה embed מגיע. המהלך המתבקש הוא לקחת מודל שפה pretrained, להריץ דרכו כל משפט ולמצע את וקטורי ה-token. הנה השיטה הזאת מול ארבע חלופות, עם שני ציונים: מתאם הדירוג בין cosine לבין שיפוטי דמיון אנושיים על 1,379 הזוגות של benchmark STS, ו-top-1 retrieval על אינדקס שנבנה מ-200 הזוגות הכי חזקים של פרפרזות מתוכם — צד אחד של כל זוג מאונדקס, השני משמש כ-query.
| איך המשפט עובר embedding | מתאם דירוג | top-1 באינדקס של 200 משפטים |
|---|---|---|
| חפיפת מילים בינארית (בלי מודל כלל) | 0.5500 | 89.0 % |
| ממוצע הווקטורים הסטטיים שאומנו למעלה | 0.5263 | 85.5 % |
BERT, ה-token [CLS] | 0.2030 | 67.0 % |
| BERT, ממוצע וקטורי token | 0.4729 | 84.0 % |
| MiniLM, מאומן contrastively | 0.8203 | 92.0 % |
קראו את שלוש השורות האמצעיות מול שתי הראשונות. transformer pretrained עם 109 מיליון פרמטרים, בשימוש בדרך המתבקשת, גרוע יותר בשיפוט דמיון בין משפטים מאשר ספירת כמה מילים שני משפטים חולקים — וגרוע יותר ממיצוע וקטורי text8 בני 100 הממדים שאומנו לפני רגע. ה-token [CLS], שמדריכים עדיין ממליצים עליו כי BERT אומן מראש עם מטרה ברמת משפט שמחוברת אליו, גרוע יותר ממחצית מזה.
זה אינו פגם ב-BERT. זו המטרה. מודל שפה מאומן כך שה-hidden states שלו יחזו token; שום דבר שם לא מבקש משתי פרפרזות להסתיים קרובות זו לזו, ושום דבר לא מתגמל גאומטריה שבה cosine פירושו ״אותה משמעות״. השורה האחרונה היא מודל בגודל חמישית (22,713,216 פרמטרים) שאומן על loss אחר לגמרי: contrastive learning, שבו הדוגמאות הן זוגות — שאלה והתשובה שלה, משפט והפרפרזה שלו — והמטרה מושכת זוגות אמיתיים יחד ודוחפת שליליים מדוגמים הרחק. זו התרומה של Sentence-BERT ומקור כל תעשיית ה-embedding model.12 Dense Passage Retrieval מיישם את אותו מתכון ישירות לחיפוש, עם encoder אחד לשאילתות ואחד לקטעים.13
לכן הכלל המעשי:
embedding model אינו מודל שפה שהסירו ממנו את השכבה האחרונה. זה מודל אחר על מטרה אחרת, בדרך כלל קטן בהרבה, שה-cosine שלו אומר את מה שאתם רוצים שיאמר מפני שהוא אומן על זוגות שבהם זו הייתה המטרה. הטבלה למעלה היא המחיר של החלפת אחד באחר.
והמשפחה נכשלת בסדר מילים. ״The dog bit the man״ ו-״the man bit the dog״ מחזיקים שקי מילים זהים, ולכן חפיפת מילים וממוצע הווקטורים הסטטיים נותנים להם cosine בדיוק 1.000000, ו-mean-pooled BERT, שכן רואה מיקום, עדיין נוחת כמעט שם — וגם MiniLM שאומן contrastively עדיין מציב אותם על 0.979. אם משימת ה-retrieval שלכם תלויה במי עשה מה למי, שום סף cosine לא יציל אתכם.
פרק 19 בונה מערכת retrieval פרודקשן על הבסיס הזה ומגיע לסף cosine קונקרטי. המדידה האחרונה בפרק הזה היא מה שהופך מספר כזה לבר-הגנה ולא לקסם.
קללת הממדיות, בטבלה אחת
קישור למקטע: קללת הממדיות, בטבלה אחתל-embeddings אמיתיים יש מאות או אלפי רכיבים, ומרחקים מתנהגים שם באופן מוזר. קחו 1,000 נקודות אקראיות בקוביית היחידה של ממדים והביטו ביחס בין המרחק הגדול ביותר והקטן ביותר בין כל שתי נקודות:
| ממדים | הזוג הקרוב ביותר | הזוג הרחוק ביותר | יחס |
|---|---|---|---|
| 2 | 0.0007 | 1.3612 | 1921.66 |
| 10 | 0.2361 | 2.3397 | 9.91 |
| 100 | 3.0047 | 5.1752 | 1.72 |
| 1,000 | 11.7809 | 14.0306 | 1.19 |
| 10,000 | 39.6152 | 42.0125 | 1.06 |
בעשרת אלפים ממדים הזוג הרחוק ביותר של נקודות רחוק רק ב-6 % יותר מהזוג הקרוב ביותר. הכול בערך שווה-מרחק מכל דבר אחר, ״השכן הקרוב ביותר״ מפסיק לשאת מידע רב, וזו קללת הממדיות — וגם סיבה אחת לכך שמסדי נתונים וקטוריים גדולים לא עושים חיפוש שכן-קרוב מדויק. הצד השני של אותו מטבע הוא מה שהופך ספי cosine למעשיים: במדידה על אלף זוגות של וקטורי יחידה אקראיים, ממוצע ה-cosine יושב על ב-100 ממדים ועל ב-768, עם סטיות תקן של 0.0968 ו-0.0357 — וב-768 ממדים רק 0.2 % מהזוגות האקראיים עוברים 0.1 בערך מוחלט. לכן דמיון נמדד של 0.4 אינו ״40 % דומה״; הוא רחוק מאוד מכל מה שמקרה מייצר, ולכן ספים בין 0.3 ל-0.7 מפרידים אות מרעש במקום לשבת באמצעו.
לאן זה ממשיך
קישור למקטע: לאן זה ממשיךהמודל בפרק הזה קורא מספר קבוע של תווים קודמים, עושה lookup לכל אחד ומדביק את התוצאות יחד לפי הסדר. לעיצוב הזה יש שתי בעיות, והן אותה בעיה.
הביטו שוב בטבלת ה-context: מעבר משלושה תווים לשמונה כמעט הכפיל את הפרמטרים וקנה 0.06 nats. העלות גדלה ליניארית עם ה-context — כל מיקום נוסף צריך לוח משלו במטריצת המשקל הראשונה — והתועלת לא. דחפו את זה לאלף token והשכבה הראשונה לבדה שוקלת יותר משאר המודל, רובה מושקעת במיקומים שאינם חשובים לשום חיזוי נתון.
וזו הבעיה השנייה: למודל אין שום דרך להחליט אילו מה-token הקודמים חשובים. מיקום שתיים מקבל משקולות משלו ומיקום שבע מקבל משלו, לצמיתות, לא משנה מה יש בהם. כשהמודל מאיית nell, התו המכריע הוא זה שמיד לפניו. כשמשפט מכיל כינוי גוף, המילה שקובעת את המושא שלו עשויה להיות ארבעים token אחורה — ואין slot קבוע שאפשר להקצות ל״ארבעים אחורה״, כי בפעם הבאה זה יהיה שש.
מה שאנחנו רוצים הוא מודל שמחשב, עבור כל חיזוי, כמה כל token מוקדם יותר צריך להיחשב — משקולות על פני ה-context שמופקות מהתוכן במקום להיקבע לפי הפריסה. כתבו זאת בזהירות וזה מתחיל כמשהו יומיומי לגמרי: ממוצע על פני ה-token הקודמים. אחר כך תנו למשקולות של הממוצע הזה להילמד, ותנו להן להיות תלויות ב-token ששואל.
זה attention, וזה פרק 9.
מקורות ושיטה
קישור למקטע: מקורות ושיטהעוד כדאי לקרוא לצד זה: פרק 3 ב-Speech and Language Processing של Jurafsky ו-Martin, שמטפל במודלי n-gram, smoothing ו-perplexity בזהירות רבה יותר מכפי שיש כאן מקום, כולל מדוע interpolation ו-back-off מנצחים הוספת אחד; הערות Stanford CS229 סעיפים §17.1–17.2 למידול שפה מהצד ההסתברותי; והמאמר של Linzen למעלה, שהוא קצר ושווה קריאה מלאה.
הפניות
קישור למקטע: הפניות-
דוגמת יצירת השמות, ה-dataset וההתקדמות מטבלת ספירות לרשת בסגנון Bengio עוקבים אחר סדרת building makemore של Andrej Karpathy, ששני חלקיה הראשונים הם הליווי הטוב ביותר לפרק הזה. ↩
-
Shannon, C. E. Prediction and Entropy of Printed English. Bell System Technical Journal 30(1), pp. 50–64 (1951). נבדקים אנושיים שמנחשים את האות הבאה באנגלית, ומדידת bits-per-character המקורית. ↩
-
Shannon, C. E. A Mathematical Theory of Communication. Bell System Technical Journal 27 (1948). משפט קידוד המקור, והזיהוי של חיזוי עם דחיסה. ↩
-
Bengio, Y., Ducharme, R., Vincent, P. and Jauvin, C. A Neural Probabilistic Language Model. Journal of Machine Learning Research 3, pp. 1137–1155 (2003). הארכיטקטורה ששימשה למעלה: embedding לכל מילה, מחובר לאורך חלון קבוע, דרך שכבה חבויה, אל softmax על פני אוצר המילים. ↩
-
Mikolov, T., Chen, K., Corrado, G. and Dean, J. Efficient Estimation of Word Representations in Vector Space. arXiv:1301.3781 (2013). CBOW ו-skip-gram, וסט האנלוגיות ששימש למעלה. ↩
-
Mikolov, T., Sutskever, I., Chen, K., Corrado, G. and Dean, J. Distributed Representations of Words and Phrases and their Compositionality. arXiv:1310.4546 (2013). Negative sampling, subsampling של מילים שכיחות, והתפלגות הרעש בחזקת 3/4 ששימשה למעלה. ↩
-
Pennington, J., Socher, R. and Manning, C. GloVe: Global Vectors for Word Representation. EMNLP 2014. וקטורי מילים מפירוק של מטריצת co-occurrence גלובלית במקום חלונות מקומיים זורמים. ↩
-
Linzen, T. Issues in evaluating semantic spaces using word analogies. RepEval 2016, arXiv:1606.07736. מקור קווי הבסיס ללא offset ששוכפלו למעלה. ↩
-
Peters, M. et al. Deep contextualized word representations. arXiv:1802.05365 (2018). ELMo: וקטור אחד לכל מופע, מחושב בידי מודל שפה דו-כיווני. ↩
-
Devlin, J., Chang, M.-W., Lee, K. and Toutanova, K. BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. arXiv:1810.04805 (2018). המודל שנמדד בניסוי bank. ↩
-
Press, O. and Wolf, L. Using the Output Embedding to Improve Language Models. arXiv:1608.05859 (2016), and Inan, H., Khosravi, K. and Socher, R. Tying Word Vectors and Word Classifiers. arXiv:1611.01462 (2016). שני טיעונים עצמאיים לאותו טריק. ↩
-
Reimers, N. and Gurevych, I. Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. arXiv:1908.10084 (2019). המדידה הפותחת שלו — mean-pooled BERT שמפגר אחרי וקטורים סטטיים ממוצעים בדמיון משפטים — היא מה שהטבלה למעלה משחזרת. ↩
-
Karpukhin, V. et al. Dense Passage Retrieval for Open-Domain Question Answering. arXiv:2004.04906 (2020). אימון contrastive של retriever עם שני encoders; האב הישיר של ערימת ה-retrieval בפרק 19. ↩