tl;dr

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

מבוא

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

משימות NLP נפוצות

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

סיווג

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

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

תיוג רצפים

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

ארכיטקטורת תיוג רצפים עם LSTM דו-כיווני

יצירת רצפים

אפשר לטעון שהתוצאות המרשימות ביותר ב‑NLP בזמן האחרון היו בתרגום. תרגום הוא מיפוי מרצף אחד לאחר, בלי הבטחה לגבי אורך משפט הפלט. למשל, תרגום המילים הראשונות של התנ״ך מעברית לאנגלית הוא בראשית = “In the Beginning”.

בלב ההצלחה הזו נמצא מסגרת Sequence to Sequence (המכונה גם encoder decoder), מתודולוגיה ל״דחוס״ רצף לקוד ואז לפענח אותו לרצף אחר. דוגמאות בולטות כוללות תרגום (מקודדים עברית ומפענחים לאנגלית), תיאור תמונות (מקודדים תמונה ומפענחים תיאור טקסטואלי של התוכן שלה)

צינור תיאור תמונות עם מאפייני CNN שמזינים מפענח LSTM עם קשב

שלב ה‑Encoder הבסיסי דומה לסכמה שתיארנו עבור סיווג. מה שמדהים הוא שאפשר לבנות מפענח שלומד לייצר פלטים באורך שרירותי.

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

ביקורות אמזון שנוצרו עם סנטימנט שמוגבל לחיובי או שלילי

עוד אהובה אישית היא המאמר Generating Sentences from a Continuous Space. במאמר הזה הם אימנו אוטואנקודר וריאציוני על טקסט, מה שהוביל ליכולת לאינטרפולציה בין שני משפטים ולקבל תוצאות קוהרנטיות.

דוגמאות לאינטרפולציה בין משפטים מאוטואנקודר וריאציוני

דרישות מארכיטקטורת NLP

מה שמשותף לכל המימושים שבדקנו הוא שהם משתמשים בארכיטקטורה חוזרת, בדרך כלל LSTM (אם אתם לא בטוחים מה זה, כאן יש מבוא מצוין). ראוי לציין שלאף אחת מהמשימות אין ״חוזר״ בשם שלה, ושאף אחת לא הזכירה LSTM‑ים. עם זה בראש, בואו נעצור רגע לחשוב מה RNN‑ים ובמיוחד LSTM‑ים מספקים שהופך אותם לכל כך נפוצים ב‑NLP.

גודל קלט שרירותי

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

  1. זה מגביל את גודל הקלט שאפשר לטפל בו. לרשת שלנו יהיה מספר סופי של נודים קלט, והיא לא תוכל לגדול מעבר לכך.
  2. אנחנו מאבדים הרבה מידע משותף. קחו למשל את המשפטים “I like to drink beer a lot” ו‑“I like to drink a lot of beer”. רשת feed forward הייתה צריכה ללמוד על המושג “a lot” פעמיים, כי הוא מופיע בכל פעם בנוד קלט אחר.

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

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

תלות ארוכת טווח

ההבטחה של RNN‑ים היא היכולת שלהם למדל תלות ארוכת טווח באופן מובלע. התמונה למטה לקוחה מ‑OpenAI. הם אימנו מודל שבסופו של דבר זיהה סנטימנט וצבע את הטקסט, תו אחר תו, לפי פלט המודל. שימו לב איך המודל רואה את המילה “best” ומפעיל סנטימנט חיובי שהוא שומר עליו לאורך יותר מ‑100 תווים. זה לכידת תלות ארוכת טווח.

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

התיאוריה של RNN‑ים מבטיחה לנו תלות ארוכת טווח out of the box. בפועל זה קצת יותר קשה. כשאנחנו לומדים באמצעות backpropagation, אנחנו צריכים להפיץ את האות לאורך כל יחס הרקורסיה. העניין הוא שבכל צעד אנחנו בסוף מכפילים במספר. אם המספרים האלה בדרך כלל קטנים מ‑1, האות שלנו יגיע מהר מאוד ל‑0. אם הם גדולים מ‑1, האות יתפוצץ.

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

יתרונות של קונבולוציות

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

תשובה אחת היא: “Because we can”.

אבל יש עוד שתי סיבות משכנעות להשתמש בקונבולוציות: מהירות והקשר.

הקבלה

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

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

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

לראות את כל הקלט בבת אחת

LSTM‑ים קוראים את הקלט משמאל לימין (או מימין לשמאל), אבל לפעמים נרצה שההקשר מסוף המשפט ישפיע על מחשבות הרשת לגבי תחילתו. למשל, יכול להיות לנו משפט כמו “I’d love to buy your product. Not!” ונרצה שהשלילה בסוף תשפיע על כל המשפט.

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

קונבולוציות, לעומת זאת, מגדילות ״שדה קליטה״ (receptive field) כשאנחנו מערימים עוד ועוד שכבות. המשמעות היא שכברירת מחדל, כל ״צעד״ בייצוג של הקונבולוציה רואה את כל הקלט שבשדה הקליטה שלו — מה שלפניו ומה שאחריו. אני לא מכיר טיעון מכריע שזה בהכרח טוב יותר מ‑LSTM, אבל זה נותן לנו את האפקט הרצוי בצורה נשלטת ובעלות חישובית נמוכה.

עד עכשיו הגדרנו את תחום הבעיה ודיברנו קצת על היתרונות המושגיים של קונבולוציות ל‑NLP. מכאן והלאה הייתי רוצה לתרגם את המושגים הללו לשיטות מעשיות שנוכל להשתמש בהן כדי לנתח ולבנות את הרשתות שלנו.

קונבולוציות מעשיות לטקסט

הדמיה מונפשת של קרנל קונבולוציה שמחליק על תמונה

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

הכול נראה הגיוני, חוץ מזה שהקלט בתמונה הוא תמונה עם שני ממדים מרחביים (גובה ורוחב). אנחנו מדברים על טקסט, שיש לו רק ממד אחד, והוא זמני ולא מרחבי.

לכל צורך מעשי, זה לא משנה. אנחנו רק צריכים לחשוב על הטקסט כתמונה ברוחב n ובגובה 1. Tensorflow מספקת פונקציית conv1d שעושה זאת עבורנו, אבל היא לא חושפת פעולות קונבולוציה אחרות בגרסת ה‑1d שלה.

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

אז מה שאנחנו עושים כאן הוא לשנות את הצורה של הקלט עם tf.expand_dims כך שהוא יהפוך ל״תמונה בגובה 1״. אחרי הרצת אופרטור הקונבולוציה הדו‑ממדי אנחנו מסירים (squeeze) את הממד העודף.

היררכיה ושדות קליטה

היררכיה של פילטרים ב-CNN שמתקדמת מקצוות לפרצופים

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

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

עץ שדה קליטה שמראה אגירה שכבתית על פני קלטים

הגדלת שדה הקליטה

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

אפליקציית מסווג נקניקייה חמה שמשווה תמונות של נקניקייה ונעל

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

פילטרים גדולים יותר

הדרך הראשונה והברורה ביותר היא להגדיל את גודל הפילטר, כלומר לבצע קונבולוציית [1x5] במקום [1x3]. בעבודה שלי עם טקסט לא קיבלתי תוצאות טובות עם זה, ואציע את ההשערות שלי למה.

בדומיין שלי אני בעיקר מתמודד עם קלט ברמת תו ועם טקסטים עשירים מאוד מורפולוגית. אני חושב על (לפחות השכבות הראשונות) של קונבולוציה כעל למידת n‑grams, כך שרוחב הפילטר תואם ביגרמים, טריגרמים וכו׳. כשגורמים לרשת ללמוד n‑grams גדולים יותר מוקדם, חושפים אותה לפחות דוגמאות, כי יש יותר מופעים של “ab” בטקסט מאשר של “abb”.

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

הוספת שכבות

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

להוספת שכבות יש שני אפקטים מובחנים אבל קשורים. הראשון, שמוזכר הרבה, הוא שהמודל ילמד לבצע הפשטות ברמה גבוהה יותר על הקלטים שהוא מקבל (Pixels =>Edges => Eyes => Face). השני הוא ששדה הקליטה גדל בכל צעד.

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

הפשרה בין גרדיאנט לשדה קליטה

רשתות נוירונים הן רשתות שדרכן מידע זורם. ב‑forward pass הקלט זורם ומשתנה, בתקווה שיהפוך לייצוג שמתאים יותר למשימה שלנו. בשלב ה‑back אנחנו מפיצים אות — הגרדיאנט — חזרה דרך הרשת. בדיוק כמו ב‑RNN‑ים וניליים, האות הזה מוכפל לעיתים תכופות, ואם הוא עובר דרך סדרה של מספרים קטנים מ‑1 הוא ידעך ל‑0. זה אומר שלרשת שלנו יהיה מעט מאוד אות ללמוד ממנו.

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

שתי פתרונות לבעיית היעלמות הגרדיאנט

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

חיבורים שיוריים

2016 הייתה עוד שנה נהדרת עבור ״אנשי הראייה״, עם לפחות שתי ארכיטקטורות פופולריות מאוד שצמחו: ResNets ו‑DenseNets (המאמר של DenseNet, במיוחד, כתוב באופן יוצא מן הכלל וממש שווה קריאה). שתיהן מתמודדות עם אותה בעיה: ״איך אני עושה את הרשת שלי מאוד עמוקה בלי לאבד את אות הגרדיאנט?״

Arthur Juliani כתב סקירה נהדרת של Resnet, DenseNets and Highway networks עבור מי שמחפש פרטים והשוואה. אני אתעכב בקצרה על DenseNets, שמביאות את הרעיון לליטרליות קיצונית.

בלוק DenseNet עם שכבות קונבולוציה מחוברות בצפיפות

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

DenseNets עושות זאת בדרך מעניינת במיוחד. הן מצרפות (concatenate) את הפלט של כל שכבה אל הקלט שלה כך ש:

  1. מתחילים באמבדינג של הקלטים שלנו, נניח בממד 10.
  2. השכבה הראשונה מחשבת 10 מפות מאפיינים. היא מוציאה את 10 מפות המאפיינים מצורפות לאמבדינג המקורי.
  3. השכבה השנייה מקבלת כקלט וקטורים בממד 20 (10 מהקלט ו‑10 מהשכבה הקודמת) ומחשבת עוד 10 מפות מאפיינים. כך היא מוציאה וקטורים בממד 30.

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

יש עוד שני דברים שהייתי רוצה להדגיש.

  1. קודם הזכרתי ששכבות עליונות רואות את הקלט המקורי אולי דרך שכבות של הפשטה. אחד היתרונות של צירוף הפלטים של כל שכבה הוא שהאות המקורי מגיע לשכבות הבאות ללא פגיעה, כך שלכל השכבות יש ראייה ישירה של מאפיינים ברמה נמוכה — למעשה זה מסיר חלק מה״ערפל״.
  2. טריק החיבור השיורי דורש שלכל השכבות תהיה אותה צורה. זה אומר שאנחנו צריכים לרפד (pad) כל שכבה כך שלקלט ולפלט יהיו אותם ממדים מרחביים [1Xwidth]. זה אומר שכשלעצמו, סוג כזה של ארכיטקטורה יעבוד למשימות תיוג רצפים (שבהן לקלט ולפלט יש אותם ממדים מרחביים) אבל ידרוש יותר עבודה עבור משימות קידוד וסיווג (שבהן צריך לצמצם את הקלט לווקטור בגודל קבוע או סט וקטורים). המאמר של DenseNet למעשה מטפל בזה כי המטרה שלהם היא סיווג, ונרחיב על הנקודה הזו בהמשך.

קונבולוציות מדוללות

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

אפשר למצוא הסבר כמעט נגיש לקונבולוציות מדוללות במאמר Multi scale context aggregation by dilated convolutions, שמשתמש בהן לראייה. אף שהוא פשוט מושגית, לקח לי זמן להבין בדיוק מה הן עושות, וייתכן שאני עדיין לא לגמרי מדייק.

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

דיברנו הרבה תיאוריה עד עכשיו, אבל סוף סוף אנחנו בנקודה שבה אפשר לראות את הדברים האלה בפעולה!

מאמר אהוב במיוחד עליי הוא Neural Machine Translation in Linear Time. הוא עוקב אחרי מבנה ה‑encoder decoder שעליו דיברנו בתחילת הדרך. עדיין אין לנו את כל הכלים לדבר על המפענח, אבל אפשר לראות את המקודד בפעולה.

מקודד קונבולוציה מדוללת עם שדות קליטה מתרחבים על פני רצף

והנה קלט באנגלית

Director Jon Favreau, who is currently working on Disney’s forthcoming Jungle Book film, told the website Hollywood Reporter: “I think times are changing.”

והתרגום שלו, באדיבות קונבולוציות מדוללות

Regisseur Jon Favreau, der zur Zeit an Disneys kommendem Jungle Book Film arbeitet, hat der Website Hollywood Reporter gesagt: “Ich denke, die Zeiten andern sich”.

וכבונוס, זכרו שצליל הוא בדיוק כמו טקסט, במובן שיש לו רק ממד מרחבי/זמני אחד. בדקו את Wavenet של DeepMind, שמשתמש בקונבולוציות מדוללות (והרבה קסם נוסף) כדי לייצר דיבור שנשמע אנושי ו‑מוזיקת פסנתר.

להוציא דברים מהרשת שלך

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

  • בתִיוג חלקי דיבר, שבו כל מילה היא חלק דיבר.
  • בזיהוי ישויות, שבו אולי נתייג Person, Company, ו‑Other עבור כל השאר

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

  • נרצה לתייג אימייל כספאם בהתבסס על התוכן שלו ו/או הנושא
  • לחזות אם משפט מסוים סרקסטי או לא

במקרים האלה אפשר ללכת בעקבות הגישות המסורתיות של ״אנשי הראייה״ ולהוסיף לראש הרשת שכבות קונבולוציה ללא ריפוד ו/או להשתמש בפעולות pooling.

אבל לפעמים נרצה לעקוב אחרי פרדיגמת Seq2Seq, מה ש‑Matthew Honnibal קרא בקצרה Embed, encode, attend, predict. במקרה הזה אנחנו מצמצמים את הקלט לייצוג וקטורי כלשהו, אבל אז צריכים איכשהו לבצע upsample לוקטור הזה חזרה לרצף באורך הנכון.

המשימה הזו כוללת שתי בעיות

  • איך עושים upsampling עם קונבולוציות?
  • איך עושים בדיוק את כמות ה‑up sampling הנכונה?

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

Upsampling עם דקונבולוציות

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

דיאגרמת קונבולוציה עם stride שמראה את הקרנל מכסה את הקלטים

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

אם תעצרו לחשוב על זה לרגע, פעולת ה״מלמעלה למטה״ הזו כבר מתרחשת ברשתות הקונבולוציה שלכם כשאתם עושים back propagation, כי אותות הגרדיאנט צריכים להתפשט בדיוק בדרך שמוצגת בתמונה. אפילו טוב יותר: מסתבר שהפעולה הזו היא פשוט ה‑transpose של פעולת הקונבולוציה, ומכאן השם הנפוץ האחר (והטכני הנכון) לפעולה הזו: transposed convolution.

וכאן זה נעשה כיף. אנחנו יכולים לבצע stride לקונבולוציות כדי לכווץ את הקלט. לכן אנחנו יכולים לבצע stride גם ל״דקונבולוציות״ כדי להגדיל את הקלט. אני חושב שהדרך הקלה ביותר להבין איך strides עובדים עם דקונבולוציות היא להסתכל על התמונות הבאות.

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

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

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

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

קונבולוציה טרנספוזית שמגדילה את אורך הרצף

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

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

כמה דברים שכדאי לחשוב עליהם

  1. אם מסתכלים על השרטוטים האלה מלמטה למעלה, הם בסוף קונבולוציות עם stride רגיל, שבהן פשוט הוספנו חורים דמיוניים לשכבות הפלט (הבלוקים הלבנים)
  2. בפועל, כל ״קלט״ אינו מספר יחיד אלא וקטור. בעולם התמונות זה יכול להיות ערך RGB תלת‑ממדי. בטקסט זה יכול להיות אמבדינג מילה בממד 300. אם אתם (de)convolving באמצע הרשת, כל נקודה תהיה וקטור בגודל כלשהו שיצא מהשכבה הקודמת.
  3. אני מציין זאת כדי לשכנע אתכם שיש מספיק מידע בשכבת הקלט של דקונבולוציה כדי להתפרס על פני כמה נקודות בפלט.
  4. בפועל, הייתה לי הצלחה בהרצה של כמה קונבולוציות עם padding שמשמר אורך אחרי דקונבולוציה. אני מדמיין (אם כי לא הוכחתי) שזה פועל כמו redistributing מידע. אני חושב על זה כמו לתת לסטייק לנוח אחרי הצלייה כדי לתת למיצים להתפזר מחדש.

השוואה בין סטייק שלא נח לבין סטייק שנח כדי להמחיש הפצה מחדש של מידע

סיכום

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

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

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

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