איתור באגים מספריים בתוכניות TensorFlow באמצעות TensorBoard Debugger V2

אירועים קטסטרופליים הכוללים NaN s יכולים להתרחש לפעמים במהלך תוכנית TensorFlow, ומשתקים את תהליכי אימון המודל. הסיבה העיקרית לאירועים כאלה היא לעתים קרובות מעורפלת, במיוחד עבור מודלים בגודל ומורכבות לא טריוויאליים. כדי להקל על ניפוי באגים מסוג זה של באגים בדגם, TensorBoard 2.3+ (יחד עם TensorFlow 2.3+) מספק לוח מחוונים מיוחד בשם Debugger V2. כאן אנו מדגימים כיצד להשתמש בכלי זה על ידי עבודה דרך באג אמיתי הכולל NaNs ברשת עצבית הכתובה ב- TensorFlow.

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

התבוננות בבאג

קוד המקור של תוכנית TF2 שאנו נפתור באגים זמין ב-GitHub . התוכנית לדוגמה ארוזה גם בחבילת tensorflow pip (גרסה 2.3+) וניתן להפעיל אותה על ידי:

python -m tensorflow.python.debug.examples.v2.debug_mnist_v2

תוכנית TF2 זו יוצרת תפיסה רב-שכבתית (MLP) ומאמנת אותה לזהות תמונות MNIST . דוגמה זו משתמשת בכוונה ב-API ברמה נמוכה של TF2 כדי להגדיר בניית שכבות מותאמות אישית, פונקציית אובדן ולולאת אימון, מכיוון שהסבירות לבאגים של NaN גבוהה יותר כאשר אנו משתמשים ב-API הגמיש יותר אך נוטה יותר לשגיאות מאשר כאשר אנו משתמשים ב-API הקל יותר. ממשקי API ברמה גבוהה לשימוש אך מעט פחות גמישים כגון tf.keras .

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

Accuracy at step 0: 0.216
Accuracy at step 1: 0.098
Accuracy at step 2: 0.098
Accuracy at step 3: 0.098
...

ניחוש מושכל הוא שבעיה זו נגרמת מחוסר יציבות מספרית, כגון NaN או אינסוף. עם זאת, כיצד אנו מאשרים שזה באמת המקרה וכיצד אנו מוצאים את פעולת TensorFlow (op) האחראית ליצירת אי היציבות המספרית? כדי לענות על שאלות אלו, הבה נחבר את תוכנית הבאגי עם Debugger V2.

מכשור קוד TensorFlow עם Debugger V2

tf.debugging.experimental.enable_dump_debug_info() היא נקודת הכניסה ל-API של Debugger V2. הוא מכשיר תוכנית TF2 עם שורת קוד אחת. לדוגמה, הוספת השורה הבאה ליד תחילת התוכנית תגרום למידע על ניפוי באגים להיכתב לספריית היומן (logdir) ב-/tmp/tfdbg2_logdir. המידע על ניפוי הבאגים מכסה היבטים שונים של זמן הריצה של TensorFlow. ב-TF2, הוא כולל את ההיסטוריה המלאה של ביצוע להוט, בניית גרפים שבוצעה על ידי @tf.function , ביצוע הגרפים, ערכי הטנזור שנוצרו על ידי אירועי הביצוע, כמו גם את מיקום הקוד (Python stack traces) של אותם אירועים . העושר של מידע ניפוי הבאגים מאפשר למשתמשים לצמצם באגים לא ברורים.

tf.debugging.experimental.enable_dump_debug_info(
    "/tmp/tfdbg2_logdir",
    tensor_debug_mode="FULL_HEALTH",
    circular_buffer_size=-1)

הארגומנט tensor_debug_mode שולט באיזה מידע Debugger V2 מחלץ מכל טנסור להוט או בתוך הגרף. "FULL_HEALTH" הוא מצב הלוכד את המידע הבא על כל טנזור מסוג צף (למשל, ה-float32 הנפוץ וה-dtype bfloat16 הפחות נפוץ):

  • DType
  • דַרגָה
  • מספר כולל של אלמנטים
  • פירוט של האלמנטים מסוג צף לקטגוריות הבאות: סופי שלילי ( - ), אפס ( 0 ), סופי חיובי ( + ), אינסוף שלילי ( -∞ ), אינסוף חיובי ( +∞ ), ו- NaN .

מצב "FULL_HEALTH" מתאים לאיתור באגים הכוללים NaN ואינסוף. ראה למטה עבור s tensor_debug_mode נתמכים אחרים.

הארגומנט circular_buffer_size שולט בכמה אירועי טנסור נשמרים ב-logdir. ברירת המחדל היא 1000, מה שגורם רק ל-1000 הטנזורים האחרונים לפני סיום תוכנית ה-TF2 המאובזרת להישמר בדיסק. התנהגות ברירת מחדל זו מפחיתה את תקורה של ניפוי באגים על ידי הקרבת שלמות נתוני ניפוי באגים. אם העדפת השלמות, כמו במקרה זה, נוכל להשבית את המאגר העגול על ידי הגדרת הארגומנט לערך שלילי (למשל, -1 כאן).

הדוגמה של debug_mnist_v2 מפעילה enable_dump_debug_info() על ידי העברת דגלי שורת הפקודה אליה. כדי להפעיל שוב את תוכנית ה-TF2 הבעייתית שלנו כשמכשור איתור באגים מופעל, בצע:

python -m tensorflow.python.debug.examples.v2.debug_mnist_v2 \
    --dump_dir /tmp/tfdbg2_logdir --dump_tensor_debug_mode FULL_HEALTH

הפעלת ה-Debugger V2 GUI ב-TensorBoard

הפעלת התוכנית עם מכשור ניפוי הבאגים יוצרת logdir ב- /tmp/tfdbg2_logdir. נוכל להפעיל את TensorBoard ולכוון אותו ל-logdir עם:

tensorboard --logdir /tmp/tfdbg2_logdir

בדפדפן האינטרנט, נווט לדף של TensorBoard בכתובת http://localhost:6006. הפלאגין "Debugger V2" לא יהיה פעיל כברירת מחדל, אז בחר אותו מתפריט "פלאגינים לא פעילים" בפינה השמאלית העליונה. לאחר הבחירה, זה אמור להיראות כך:

Debugger V2 צילום מסך מלא

שימוש ב-Debugger V2 GUI כדי למצוא את סיבת השורש של NaNs

ה-Debugger V2 GUI ב-TensorBoard מאורגן בשישה חלקים:

  • התראות : חלק זה משמאל למעלה מכיל רשימה של אירועי "התראה" שזוהו על ידי מאפר הבאגים בנתוני ניפוי הבאגים מתוכנית TensorFlow המכוננת. כל התראה מצביעה על חריגה מסוימת שמחייבת תשומת לב. במקרה שלנו, סעיף זה מדגיש 499 אירועי NaN/∞ עם צבע ורוד-אדום בולט. זה מאשש את החשד שלנו שהמודל לא מצליח ללמוד בגלל נוכחותם של NaNs ו/או אינסוף בערכי הטנסור הפנימיים שלו. נעמיק בהתראות אלו בקרוב.
  • ציר זמן לביצוע Python : זהו החצי העליון של החלק העליון-אמצעי. הוא מציג את ההיסטוריה המלאה של הביצוע הנלהב של פעולות וגרפים. כל תיבה של ציר הזמן מסומנת באות הראשונית של שם ה-op או הגרף (למשל, "T" עבור ה- "TensorSliceDataset", "m" עבור "מודל" tf.function ). נוכל לנווט בציר הזמן הזה על ידי שימוש בכפתורי הניווט ובסרגל הגלילה שמעל לציר הזמן.
  • ביצוע גרף : ממוקם בפינה השמאלית העליונה של ממשק המשתמש, חלק זה יהיה מרכזי במשימת איתור הבאגים שלנו. הוא מכיל היסטוריה של כל הטנזורים הצפים מסוג dtype המחושבים בתוך גרפים (כלומר, הידור על ידי @tf-function s).
  • מבנה הגרף (החצי התחתון של החלק העליון-אמצעי העליון), קוד המקור (החלק השמאלי התחתון) ו- Stack Trace (החלק הימני התחתון) ריקים בתחילה. התוכן שלהם יאוכלס כאשר אנו מתקשרים עם ה-GUI. שלושת הסעיפים הללו ישחקו גם תפקידים חשובים במשימת איתור הבאגים שלנו.

לאחר שכיוונו את עצמנו לארגון של ממשק המשתמש, בואו ניקח את הצעדים הבאים כדי לרדת לתחתית מדוע ה-NaNs הופיעו. ראשית, לחץ על ההתראה NaN/∞ בקטע התראות. פעולה זו גוללת אוטומטית את רשימת 600 טנסור הגרפים בקטע ביצוע גרפים ומתמקדת ב-#88, שהוא טנסור בשם Log:0 שנוצר על ידי הפעלה Log (לוגריתם טבעי). צבע ורוד-אדום בולט מדגיש אלמנט -∞ בין 1000 האלמנטים של טנסור 2D float32. זהו הטנזור הראשון בהיסטוריית זמן הריצה של תוכנית TF2 שהכיל כל NaN או אינסוף: טנסורים שחושבו לפניו אינם מכילים NaN או ∞; רבים (למעשה, רוב) הטנזורים המחושבים לאחר מכן מכילים NaNs. נוכל לאשר זאת על ידי גלילה למעלה ולמטה ברשימת ביצוע הגרפים. תצפית זו מספקת רמז חזק לכך שה- Log op הוא המקור לאי היציבות המספרית בתוכנית TF2 זו.

Debugger V2: התראות Nan / Infinity ורשימת ביצוע גרפים

מדוע Log זה יורק -∞? תשובה לשאלה זו דורשת בחינת הקלט לאופ. לחיצה על שם הטנסור ( Log:0 ) מעלה הדמיה פשוטה אך אינפורמטיבית של הסביבה של Log op בגרף ה- TensorFlow שלו בקטע Graph Structure. שימו לב לכיוון מלמעלה למטה של ​​זרימת המידע. האופ עצמו מוצג במודגש באמצע. מיד מעליו, אנו יכולים לראות אופציה של Placeholder מספקת את הקלט האחד והיחיד ל- Log op. היכן הטנזור שנוצר על ידי מציין מיקום probs זה ברשימת ביצוע הגרפים? על ידי שימוש בצבע הרקע הצהוב כעזר חזותי, אנו יכולים לראות שהטנסור probs:0 נמצא שלוש שורות מעל הטנסור Log:0 , כלומר בשורה 85.

Debugger V2: תצוגת מבנה גרף ומעקב לטנסור קלט

מבט זהיר יותר בחלוקה המספרי של הטנזור probs:0 בשורה 85 מגלה מדוע הצרכן שלו Log:0 מייצר -∞: בין 1000 האלמנטים של probs:0 , לאלמנט אחד יש ערך של 0. ה- -∞ הוא תוצאה של חישוב הלוגריתם הטבעי של 0! אם נוכל איכשהו להבטיח שה- Log op ייחשף לכניסות חיוביות בלבד, נוכל למנוע מה-NaN/∞ להתרחש. ניתן להשיג זאת על ידי החלת חיתוך (למשל, על ידי שימוש ב- tf.clip_by_value() ) על טנסור ה-Placeholder probs .

אנחנו מתקרבים לפתרון הבאג, אבל עדיין לא ממש סיימנו. על מנת להחיל את התיקון, עלינו לדעת מאיפה בקוד המקור של Python מקור ה- Log op והקלט ה-Placeholder שלו. Debugger V2 מספק תמיכה מהשורה הראשונה למעקב אחר פעולות הגרף ואירועי הביצוע למקור שלהם. כאשר לחצנו על הטנסור Log:0 ב-Graph Executions, החלק של Stack Trace היה מאוכלס ב-Stack Trace המקורי של יצירת Log op. מעקב המחסנית הוא מעט גדול מכיוון שהוא כולל מסגרות רבות מהקוד הפנימי של TensorFlow (למשל, gen_math_ops.py ו-dumping_callback.py), מהם אנו יכולים להתעלם בבטחה עבור רוב משימות ניפוי הבאגים. מסגרת העניין היא שורה 216 של debug_mnist_v2.py (כלומר, קובץ Python שאנו למעשה מנסים לנפות באגים). לחיצה על "שורה 216" מעלה תצוגה של שורת הקוד המתאימה בקטע קוד המקור.

Debugger V2: קוד מקור ומעקב מחסנית

זה סוף סוף מביא אותנו לקוד המקור שיצר את ה- Log op הבעייתי מקלט probs שלו. זוהי פונקציית אובדן חוצה אנטרופיה מותאמת אישית שלנו המעוטרת ב- @tf.function ומכאן שהומרה לגרף TensorFlow. ה-Placeholder op probs תואם את ארגומנט הקלט הראשון לפונקציית ההפסד. ה- Log op נוצר עם הקריאה tf.math.log() API.

תיקון חיתוך הערך לבאג זה ייראה בערך כמו:

  diff = -(labels *
           tf.math.log(tf.clip_by_value(probs), 1e-6, 1.))

זה יפתור את חוסר היציבות המספרית בתוכנית TF2 זו ויגרום ל-MLP להתאמן בהצלחה. גישה אפשרית נוספת לתיקון אי היציבות המספרית היא להשתמש ב- tf.keras.losses.CategoricalCrossentropy .

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

תאימות חומרה של Debugger V2

Debugger V2 תומך בחומרת אימון מיינסטרים כולל מעבד ו-GPU. אימון ריבוי GPU עם tf.distributed.MirroredStrategy נתמך גם כן. התמיכה ב- TPU עדיין בשלב מוקדם ודורשת התקשרות

tf.config.set_soft_device_placement(True)

לפני קריאה ל- enable_dump_debug_info() . עשויות להיות לו מגבלות אחרות גם על TPUs. אם אתה נתקל בבעיות בשימוש ב-Debugger V2, אנא דווח על באגים בדף בעיות GitHub שלנו.

תאימות API של Debugger V2

Debugger V2 מיושם ברמה נמוכה יחסית של מחסנית התוכנה של TensorFlow, ומכאן שהוא תואם ל- tf.keras , tf.data וממשקי API אחרים שנבנו על גבי הרמות הנמוכות של TensorFlow. Debugger V2 גם תואם לאחור עם TF1, אם כי ציר הזמן של Eager Execution יהיה ריק עבור יומני ניפוי הבאגים שנוצרו על ידי תוכניות TF1.

טיפים לשימוש ב-API

שאלה נפוצה לגבי ממשק API זה לניפוי באגים היא היכן בקוד TensorFlow יש להכניס את הקריאה ל- enable_dump_debug_info() . בדרך כלל, יש לקרוא ל-API מוקדם ככל האפשר בתוכנית TF2 שלך, רצוי לאחר שורות הייבוא ​​של Python ולפני תחילת בניית הגרפים והביצוע. זה יבטיח כיסוי מלא של כל האופציות והגרפים שמניעים את המודל שלך ואת ההכשרה שלו.

ה-tensor_debug_modes הנתמכים כעת הם: NO_TENSOR , CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH ו- SHAPE . הם משתנים בכמות המידע המופקת מכל טנזור ובתקורת הביצועים לתוכנית שנוגדה באגים. אנא עיין בסעיף args בתיעוד של enable_dump_debug_info() .

ביצועים תקורה

ממשק ה-API לניפוי באגים מציג תקורה של ביצועים לתוכנית TensorFlow המוכשרת. התקורה משתנה לפי tensor_debug_mode , סוג החומרה והאופי של תוכנית TensorFlow המוכשרת. כנקודת ייחוס, ב-GPU, מצב NO_TENSOR מוסיף תקורה של 15% במהלך האימון של דגם שנאי בגודל אצווה 64. אחוז התקורה עבור מצבי tensor_debug_אחרים גבוהים יותר: כ-50% עבור CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH ו- SHAPE מצבים. במעבדים, התקורה מעט נמוכה יותר. ב-TPU, התקורה כרגע גבוהה יותר.

קשר לממשקי API אחרים לניפוי באגים של TensorFlow

שימו לב כי TensorFlow מציעה כלים וממשקי API אחרים לניפוי באגים. אתה יכול לעיין בממשקי API כאלה תחת מרחב השמות tf.debugging.* בדף מסמכי ה-API. בין ממשקי ה-API הללו, השימוש השכיח ביותר הוא tf.print() . מתי צריך להשתמש ב-Debugger V2 ומתי צריך להשתמש tf.print() במקום זאת? tf.print() נוח למקרה שבו

  1. אנחנו יודעים בדיוק אילו טנסורים להדפיס,
  2. אנו יודעים היכן בדיוק בקוד המקור להכניס את הצהרות tf.print() האלה,
  3. מספר הטנסורים הללו אינו גדול מדי.

עבור מקרים אחרים (למשל, בחינת ערכי טנסור רבים, בחינת ערכי טנסור שנוצרו על ידי הקוד הפנימי של TensorFlow וחיפוש אחר המקור של חוסר יציבות מספרית כפי שהראינו לעיל), Debugger V2 מספק דרך מהירה יותר לניפוי באגים. בנוסף, Debugger V2 מספק גישה מאוחדת לבדיקת טנסור להוט וגרף. הוא מספק בנוסף מידע על מבנה הגרף ומיקומי קוד, שהם מעבר ליכולת של tf.print() .

API נוסף שניתן להשתמש בו כדי לנפות בעיות הקשורות ל-∞ ו-NaN הוא tf.debugging.enable_check_numerics() . בניגוד ל- enable_dump_debug_info() , enable_check_numerics() אינו שומר מידע על ניפוי באגים בדיסק. במקום זאת, הוא רק עוקב אחר ∞ ו-NaN במהלך זמן הריצה של TensorFlow ומוציא שגיאות במיקום קוד המקור ברגע שכל פעולה מייצרת ערכים מספריים גרועים כל כך. יש לו תקורה נמוכה יותר של ביצועים בהשוואה ל- enable_dump_debug_info() , אך אינו מאפשר מעקב מלא של היסטוריית הביצוע של התוכנית ואינו מגיע עם ממשק משתמש גרפי כמו Debugger V2.