העמקה על מצגת "ממשקל" מ-DefenseML


צפו בסרטון שלי מעביר את ההרצאה! אתם יכולים גם לפתוח את המצגת המקורית :)

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

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

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

כדי להבין את הבלוג פוסט הזה אתם צריכים להכיר את ההרצאה עצמה, המארגנים האדיבים של הכנס העלו את ההרצאה במלואה בערוץ ה-Youtube שלהם, אתם מוזמנים לראות אותה שם :)
בנוסף אתם מוזמנים לעבור על המצגת שלי בעצמכם ובקצב שלכם, בלי קשר להרצאה עצמה.

שאלות ותשובות

  1. מה המודל שלכם מוציא כפלט?
    בעת אימון למודל יש ראש קלאסיפיקציה, כלומר הפלט שלו הוא N וקטורים של לוג’יטים בגודל של כמות ה-class-ים (כמות הדוברים ב-train-set, במקרה שלנו 856).
    בעת inference אנחנו לא משתמשים בראש הקלאסיפיקציה, לכן במקום לקבל N וקטורים של לוג’יטים אנחנו מקבלים N וקטורים של Embeddings (במקרה שלנו 256 מימדים).

  2. מה המודל בכלל לומד אם “לייק” לא מייחד את הקובץ? מה ה-supervision פה?
    המודל לומד לייצר לכל קובץ סט Embeddings שייצג את ה”לייקים” שתויגו עליו.
    כלומר - המודל לומד לייצר Embeddings דומים לקבצים עליהם יש את אותו ה”לייק”, עצם זה שצריך להיות Embedding משותף זה ה-supervision.

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

  4. איך בנויות שכבות המודול “הממשקל” שלכם?
    השתמשנו במימוש של torchaudio לבלוק ה-Encoder של WavLM, בגדול בלוק חמוד עם self-attention ו-positional-embeddings וכמובן הרבה היפרפרמטרים built-in.
    אין שום דבר מתוחכם בחלק הליבתי. זה Transformer.
    אבל עשינו כמה דברים מעניינים מסביב, ראשית השתמשנו בשתי שכבות קונבולוציה כדי להוריד את כמות המימדים בערוצים פי 12 ובזמן פי 15, זה חסך המון VRAM וכמעט לא שינה ביצועים, אפילו הקל על האימון קצת ושיפר (לפחות האפוקים הראשונים).
    אז ביציאה ניסינו לעשות קונבולוציה כדי להחזיר את מימד הזמן שהוקטן, בסוף הכי טוב היה לשכפל את המספרים 15 פעמים (repeat interleave), קונבולוציות גרעו.

העמקה והעשרה

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

שכבות מ-torchaudio

אנחנו השקענו מעט מאוד בשכבות של המודל, לא ניסינו ליצור משהו מאוד מתוחכם, רצינו משהו נגיש וטוב עם הרבה היפר-פרמטרים.
הגישה הטיפוסית בימינו תהיה לקחת שכבה ממודל כלשהו ב-transformers, אבל אנחנו ראינו שיש מימוש לא רע ל-block-ים של WavLM בספרייה שגם ככה כבר השתמשנו בה - torchaudio.

הנה ה-import שלנו:

from torchaudio.models.wav2vec2.components import _get_wavlm_encoder, FeatureProjection

הפונקציה _get_wavlm_encoder מייצרת בלוק Transformer עם positional-embeddings וכדו’ built-in, מקבל sequence של וקטורים ומוציא sequence בגודל זהה עם self-attention, בדיוק מה שחיפשנו.

המחלקה FeatureProjection היא סתם טרנספורמציה פשוטה עם layer-norm ו-dropout שצריך להפעיל לפני הכנסת הקלט ל-encoder.

לנו זה חסך את ה-dependency של transformers, אולי גם לכם זה יכול לעזור :)

הסימולציה

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

הנה דוגמא שתמחיש למה מימד הזמן הוא מהותי:

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

הגרף הראשון שמעל מציג חישוב מאוד טריוויאלי לערוץ המדבר - היכן שה-envelope חזק יותר.
אבל ניתן לראות שה-Real נראה מאוד שונה מ-Pure random, אז מה שונה?

שימו לב שהחלפת ערוץ היא דבר פחות נפוץ ב-Real, ב-Pure random כל frame לערוץ יש 50% להתחלף, בעוד ב-Real הסיכויים הרבה יותר נמוכים, כלומר ב-Real יש משמעות למימד הזמן.

דרך אחת למדל אקראיות שמתייחסת למימד הזמן היא Random walk, אנחנו השתמשנו בחישוב פשוט שיוצר Random walk שתחום בין 0 ל-1 כפי שניתן לראות בגרף השני.
בגרף השלישי אנחנו משתמשים ב-Random walk כדי לבחור ערוץ באקראיות שתלויה בזמן, ניתן לראות ששני הגרפים נראים יותר דומים.

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

ה-Confidence

הגדרת ה-Confidence שלנו היא לא טריוויאלית, כפי שהוצג במצגת זו הנוסחה הבאה:

\[C(\mathbf{X}) =\frac{\sum _{j=1}^{T} X_{j}^{2}}{\sum _{i=1}^{T} X_{i}}\]

כש-C זו נוסחת חישוב ה-Confidence, ו-X זה הפלט של שכבת הממשקל, T זה אורך מימד הזמן של X, ו-X_i זה הערך בנקודת זמן i מתוך T בציר הזמן, כל ערכי X_i הם בין 0 ל-1.

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

כשהתחלנו את הפרויקט תהינו כיצד נוכל לגרום למודל להוציא פחות מ-N וקטורים באופן נלמד, חשבנו על הגישה של DETR שיצרו את ה-class ששמו no-class, שאומר שזה לא אובייקט אמיתי.
אנחנו היינו צריכים להתמודד עם הבעיה שבעת ריצה לא יהיה לנו Logits אלא Embeddings, לכן לא יכולנו לקחת את אותה הגישה.

ב-DETR גישת האימון כן דומה למה שהשתמשנו בסוף, רק שבהתחלה אנחנו החלטנו להפריד את הפלטים, פלט אחד של המודל יהיה המשקולים ופלט שני יהיה ה-Confidence-ים.
למשל אם N=3, היינו מתכננים את המודל להוציא 6 מימדים, השלושה הראשונים מנורמלים לאורך זמן הם המשקולים, והממוצעים על הזמן של שלושת האחרונים הם ה-Confidence-ים.

נקרא לפלט של המשקולים X, ולפלט של ה-Confidence-ים Y:

\(C(\mathbf{Y}) =\frac{\sum _{j=1}^{T} Y_{j}}{T}\)

\(W_{j}(\mathbf{X}) =\frac{X_{j}}{\sum _{i=1}^{T} X_{i}}\)

שימו לב שכדי להפוך את Y ל-Confidence מיצענו אותו כי אמור לצאת מספר יחיד, בעוד שכדי להוציא את המשקולים נרמלנו את X לפי סכומו, כך שייסכם לאחד כמתאים להגדרת משקולים.

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

מה שהחלטנו לעשות זה לחשב את ה-Confidence לא כממוצע של Y, אלא ממוצע ממושקל של Y על בסיס X:

\(C(\mathbf{X}, \mathbf{Y}) = \sum _{j=1}^{T} Y_{j} \cdot W_{j}(\mathbf{X})\)

\(W_{j}(\mathbf{X}) = \frac{X_{j}}{\sum _{i=1}^{T} X_{i}}\)

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

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

\(C(\mathbf{X}) = \sum _{j=1}^{T} X_{j} \cdot W_{j}(\mathbf{X})\)

\(W_{j}(\mathbf{X}) = \frac{X_{j}}{\sum _{i=1}^{T} X_{i}}\)

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

\(C(\mathbf{X}) = \sum _{j=1}^{T} X_{j} \cdot W_{j}(\mathbf{X}) = \sum _{j=1}^{T} X_{j} \cdot \frac{X_{j}}{\sum _{i=1}^{T} X_{i}} = \frac{\sum _{j=1}^{T} X_{j}^{2}}{\sum _{i=1}^{T} X_{i}}\)

אבל אז הגענו להגדרה ממש מוזרה, ה-confidence שלנו הוא X ממושקל לפי X. מה?!

האינטואיציה שלנו לזה היא כזאת, זה סוג של ממוצע שמניח שככל שהערך יותר גדול הוא יותר חשוב.
0 זה לא חשוב בכלל, 100 חשוב פי שניים מ-50.
דוגמה למקרה בו חישוב כזה נחוץ יהיה למשל בקורונה, כשניסו להעריך עבור מאומת טיפוסי בסביבת כמה אנשים הוא היה.
נניח שהוא יכל להדביק את הכל האנשים שהוא פגש בכל התקהלות בה הוא נכח, אם זו התקהלות של 100 אנשים הוא יכל להדביק 100, אם זו התקהלות של 200 אנשים הוא יכל להדביק 200 אנשים.
אבל מה הסבירות שלו להיות נוכח בהתקהלות של 100 אנשים ביחס ל-200 אנשים?
התשובה היא שפי שניים יותר סביר שהוא היה בהתקהלות של 200 אנשים מאשר 100 אנשים.
אם נספור את כל ההתקהלויות ואת הסבירות של המאומת להיות בכל אחת מהן ונכפיל בכמות האנשים בכל התקהלות, ונקראה להתפלגות הכמויות X - נקבל בדיוק X ממושקל לפי X.

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

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

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

הנוסחה של wBCE

\(wBCE(y,\hat{y})=-y⋅ln(\hat{y})+\hat{y}\)

ה-wBCE הוא תוצאה מאוד נחמדה, נוסחה מאוד פשוטה אבל לא מובנת מאליה שמשפרת תוצאות ומשמרת תכונות חשובות של BCE.

אסביר את הפיתוח שלה כעת.

ראשית אזכיר לכם את הגדרת BCE:

\(BCE(y,\hat{y})=-y⋅ln(\hat{y})-(1-y)⋅ln(1-\hat{y})\)

בפיתוח שלנו התחלנו עם השאלה - האם אנחנו יכולים להחליף את החלק שמעניש טעויות כאשר y הוא 0 במשהו אחר שישמר את נקודת המינימום של BCE במימד אחד?

\(wBCE(y,\hat{y})=-y⋅ln(\hat{y})+f(y, \hat{y})\)

\(min(wBCE(y,\hat{y}))=min(BCE(y,\hat{y}))\)

לא אפתח זאת כאן, אבל אזכיר שהמינימום של BCE הוא פשוט y:

\(min(BCE(y,\hat{y}))=y\)

\(min(wBCE(y,\hat{y}))=y\)

בואו ננסה להבין את התכונות הרצויות ל-f כדי לקיים את התנאי הזה.

\(\frac{d}{d\hat{y}} wBCE(y,\hat{y})=-\frac{y}{\hat{y}}+\frac{d}{d\hat{y}}f(y, \hat{y})\)

אנחנו יודעים שכאשר y כובע שווה ל-y, זוהי נקודת המינימום, כלומר הנגזרת שווה ל-0:

\(-\frac{y}{y}+\frac{d}{d\hat{y}}f(y, \hat{y})=0\)

\(\frac{d}{d\hat{y}}f(y, \hat{y})=1\)

כלומר, הנגזרת של f כאשר y כובע שווה ל-y היא 1.
כמובן יש אינסוף פתרונות לבעיה הזו, אבל אנחנו נזכיר שאנחנו רצינו פתרון יותר מתון מ-ln, לכן גישה פשוטה תהיה לכוון לנוסחה לינארית שכזו:

\(f(y, \hat{y})=m\hat{y}+n\)

על פי מה שאנחנו כבר יודעים ניתן להסיק ש-m חייב להיות שווה ל-1, ואנחנו רשאים לבחור את n ללא השפעה על נקודת המינימום.

הפתרון הפשוט ביותר יהיה:

\(f(y, \hat{y})=\hat{y}\)

אם נציב חזרה בנוסחה איתה התחלנו, נקבל את הנוסחה הסופית של wBCE:

\(wBCE(y,\hat{y})=-y⋅ln(\hat{y})+f(y, \hat{y})=-y⋅ln(\hat{y})+\hat{y}\)

כדי לקבל יותר אינטואיציה מה ההבדלים, אציג כמה ויזואליזציות פשוטות של BCE מול wBCE:

באוסף הגרפים הראשון, ניתן לראות שנקודת המינימום של BCE ו-wBCE נראות תואמות, בדיוק כש-y_hat שווה ל-y.

באוסף הגרפים השני אני מחשב בצורה נומרית את הנגזרת (גרדיאנט) של שניהם ומציג אותן, ניתן לראות שבאמת שתי הנגזרות חוצות את ה-0 באותו המיקום, או במילים אחרות - הסימן של הנגזרת של BCE תמיד שווה לסימן של wBCE.
בנוסף ניתן לראות ש-BCE מכיל גרדיאנטים חיוביים ושליליים גדולים בסמוך לאסימפטוטות, לעומת wBCE שמכיל אסימפטוטה אחת סמוך ל-0, כלומר הוא מכיל גרדיאנטים שליליים גדולים, אבל הגרדיאנטים החיוביים שלו הם לכל היותר 1.

כמו שניתן להבין - wBCE ממלא את תפקידו, הוא תוחם את גודל הגרדיאנט האפשרי בעת טעות תיוג כש-y שווה ל-0.

השיפור בין BCE ל-wBCE אינו דרמטי, אך הוא היה נחוץ עבורנו כדי לעמוד ביעדים.

###




נהנים מהפוסט?

הנה כמה פוסטים שאולי תרצו להמשיך אליהם:

  • A Deep Dive into my "Weighter" talk in DefenseML
  • Lessons from Launching an Open-Source
  • My Portfolio Year Starts
  • WaloViz is Out!
  • Deeper into the Portfolio