Keras וסנפירות מודרניות, במעבדי TPU

1. סקירה כללית

בשיעור ה-Lab הזה תלמדו איך לפתח, לאמן ולכוונן רשתות נוירונים קונבולוציה משלכם באמצעות Keras ו-Tensorflow 2. עכשיו אפשר לעשות זאת תוך דקות באמצעות העוצמה של מעבדי TPU. תלמדו גם על מספר גישות, החל מלמידה פשוטה מאוד בהעברה ועד לארכיטקטורות קונבולוציה מודרניות כמו סקוזנט. שיעור ה-Lab הזה כולל הסברים תיאורטיים על רשתות נוירונים, ונקודת התחלה טובה למפתחים שלומדים על למידה עמוקה (Deep Learning).

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

ca8cc21f6838eccc.png

מה תלמדו

  • כדי להשתמש ביחידות עיבוד של Keras ו-Tensor (TPU) כדי לבנות מודלים מותאמים אישית מהר יותר.
  • להשתמש ב-API tf.data.Dataset ובפורמט TFRecord כדי לטעון נתוני אימון ביעילות.
  • כדי לרמות 😈, אפשר להשתמש בלמידה בהעברה במקום לבנות מודלים משלך.
  • כדי להשתמש בסגנונות מודלים רציפים ופונקציונליים של Keras.
  • כדי ליצור מסווג Keras משלכם עם שכבת softmax ואובדן חוצה-אנטרופיה.
  • כדי לכוונן את המודל באמצעות בחירה טובה של שכבות קונבולוציה.
  • לחקור רעיונות לארכיטקטורה מודרנית של קונספירציה כמו מודולים, מאגרי ממוצעים גלובליים וכו'.
  • לבנות מנזר מודרני ופשוט באמצעות ארכיטקטורת סקזנט.

משוב

אם תיתקלו בבעיה במעבדת הקוד הזו, נשמח לשמוע על כך. אפשר לשלוח משוב דרך 'בעיות ב-GitHub' [feedback link].

2. התחלה מהירה של Google Colaboratory

בשיעור ה-Lab הזה נעשה שימוש ב'שיתוף פעולה עם Google' ולא נדרשת הגדרה כלשהי מצדך. אפשר להריץ אותו מ-Chromebook. כדי להכיר את קובצי ה-notebook של Colab, צריך לפתוח את הקובץ שלמטה ולהפעיל את התאים.

c3df49e90e5a654f.png Welcome to Colab.ipynb

בחירת קצה עורפי של TPU

8832c6208c99687d.png

בתפריט Colab, בוחרים באפשרות סביבת זמן הריצה > משנים את הסוג של סביבת זמן הריצה ואז בוחרים באפשרות TPU. בשיעור ה-Lab הזה תלמדו להשתמש ב-TPU (יחידת עיבוד Tensor) חזקה, לגיבוי לצורך אימון עם האצת חומרה. החיבור לסביבת זמן הריצה יתבצע באופן אוטומטי בהפעלה הראשונה, או שאפשר להשתמש באפשרות Connect (התחברות) בפינה הימנית העליונה.

ביצוע Notebook

76d05caa8b4db6da.png

כדי להפעיל תאים בכל פעם, לוחצים על תא באמצעות מקש Shift-ENTER. אפשר גם להריץ את כל ה-notebook באמצעות סביבת זמן הריצה > הפעלה של הכול

תוכן העניינים

429f106990037ec4.png

בכל המחברות יש תוכן עניינים. אפשר לפתוח אותו באמצעות החץ השחור שמימין.

תאים מוסתרים

edc3dba45d26f12a.png

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

אימות

cdd4b41413100543.png

ל-Colab יש אפשרות לגשת לקטגוריות הפרטיות שלך ב-Google Cloud Storage בתנאי שביצעת אימות באמצעות חשבון מורשה. קטע הקוד שלמעלה יפעיל תהליך אימות.

3. [מידע] מהן יחידות עיבוד של Tensor (TPU)?

בקצרה

f88cf6facfc70166.png

הקוד לאימון מודל על TPU ב-Keras (ומפסיקים להשתמש ב-GPU או במעבד אם ה-TPU לא זמין):

try: # detect TPUs
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
    strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines

# use TPUStrategy scope to define model
with strategy.scope():
  model = tf.keras.Sequential( ... )
  model.compile( ... )

# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)

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

688858c21e3beff2.png

למה דווקא מעבדי TPU?

מעבדי GPU מודרניים מסודרים לפי 'ליבות' שניתנות לתכנות, ארכיטקטורה גמישה מאוד שמאפשרת להם להתמודד עם מגוון משימות כמו רינדור בתלת-ממד, למידה עמוקה (Deep Learning), סימולציות פיזיות וכו'. מצד שני, יחידות TPU יוצרות התאמה בין מעבד וקטורי קלאסי עם יחידת הכפלה ייעודית של מטריצה, ומתבלטות בכל משימה שבה הכפולות של המטריצה הגדולה שולטות, כמו רשתות נוירונים.

8eb3e718b8e2ed08.png

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

החומרה

MXU ו-VPU

ליבת TPU v2 מורכבת מיחידת הכפלת מטריצות (MXU) שמפעילה הכפלת מטריצות ויחידה לעיבוד וקטורים (VPU) לכל המשימות האחרות כמו הפעלות, softmax וכו'. ה-VPU מטפל בחישובים של float32 ו-int32. מצד שני, MXU פועל בפורמט נקודה צפה (floating-point) ברמת דיוק מעורב של 16-32 ביט.

7d68944718f76b18.png

נקודה צפה (floating-point) מעורבת ו-bfloat16

ה-MXU מחשב את הכפלת המטריצות באמצעות קלט bfloat16 ופלט float32. הצטברות ביניים מבוצעות ברמת דיוק של float32.

19c5fc432840c714.png

האימון של רשת נוירונים עמיד בדרך כלל לרעש שנוצר על ידי דיוק מופחת של נקודה צפה (floating-point). יש מקרים שבהם רעש אפילו עוזר לכלי האופטימיזציה להתכנס. בעבר נעשה שימוש בדיוק של נקודה צפה (floating-point) של 16 ביט כדי להאיץ את החישובים, אבל לפורמטים float16 ו-float32 יש טווחים שונים מאוד. הפחתת רמת הדיוק מ-float32 ל-float16 בדרך כלל מובילה לזרימה חוזרת ונשנית. קיימים פתרונות, אבל בדרך כלל נדרשת עבודה נוספת כדי שהמערכת תזהה את התכונה float16.

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

מערך סיסטולי

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

הרכיב הבסיסי של כפל מטריצות הוא מכפלה של קו בין קו ממטריצה אחת ועמודה מהמטריצה השנייה (ראו איור בחלק העליון של קטע זה). בהכפלה של מטריצה Y=X*W, רכיב אחד של התוצאה יהיה:

Y[2,0] = X[2,0]*W[0,0] + X[2,1]*W[1,0] + X[2,2]*W[2,0] + ... + X[2,n]*W[n,0]

ב-GPU, משתמש יתכנת את מוצר הנקודה הזה ל'ליבה' של GPU. ואז לבצע אותו בכמה "ליבות" מכיוון שהם זמינים במקביל כדי לנסות ולחשב כל ערך של המטריצה שמתקבלת בבת אחת. אם המטריצה שמתקבלת היא 128x128 גדולה, יידרשו 128x128=16K "ליבות" להיות זמינים, ובדרך כלל זה לא אפשרי. למעבדי ה-GPU הגדולים ביותר יש כ-4,000 ליבות. מצד שני, TPU משתמש בחומרה המינימלית הנדרשת עבור יחידות המחשוב ב-MXU: רק bfloat16 x bfloat16 => float32 מכפילים, שום דבר אחר. הם כל כך קטנים ש-TPU יכול ליישם 16K מהם ב-MXU של 128x128 ולעבד את הכפלת המטריצה הזו בפעם אחת.

f1b283fc45966717.gif

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

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

Cloud TPU

כאשר תבקשו " Cloud TPU גרסה 2 ב-Google Cloud Platform, מקבלים מכונה וירטואלית (VM) עם לוח TPU שמחובר ל-PCI. בלוח ה-TPU יש ארבעה שבבי TPU עם שתי ליבות. כל ליבת TPU כוללת VPU (יחידת עיבוד וקטורי) ו-MXU 128x128 (יחידת הכפלה של MatriX). ה-"Cloud TPU" הזה בדרך כלל מחובר למכונה הווירטואלית שביקשה אותה דרך הרשת. התמונה המלאה נראית כך:

dfce5522ed644ece.png

איור: מכונה וירטואלית עם "Cloud TPU" שמחובר לרשת מאיץ. "The Cloud TPU" עצמה מורכבת ממכונה וירטואלית עם לוח TPU מחובר PCI עם ארבעה שבבי TPU עם שתי ליבות.

מארזי TPU

במרכזי הנתונים של Google, מערכות ה-TPU מחוברות לחיבור מחשוב בעל ביצועים גבוהים (HPC), שיכול לגרום להן להופיע כמאיץ אחד גדול מאוד. Google מכנה אותם Pods והם יכולים לכלול עד 512 ליבות TPU v2 או 2048 ליבות TPU v3.

2ec1e0d341e7fc34.jpeg

איור: pod TPU v3. לוחות ומדפים של TPU שמחוברים דרך חיבור HPC.

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

d97b9cc5d40fdb1d.gif

איור: סנכרון הדרגתיות במהלך אימון באמצעות אלגוריתם הצמצום הכולל ברשת HPC של רשת טורואידים דו-ממדית של Google TPU.

התוכנה

הדרכה לקבוצות גדולות

גודל אצווה אידיאלי למעבדי TPU הוא 128 פריטי נתונים לכל ליבת TPU, אבל החומרה כבר יכולה להראות ניצול טוב מ-8 פריטי נתונים לכל ליבת TPU. חשוב לזכור של-Cloud TPU אחד יש 8 ליבות.

במעבדת הקוד הזו נשתמש ב-Keras API. ב-Keras, האצווה שתגדירו היא הגודל הגלובלי של אצווה של כל ה-TPU. האצוות יפוצלו אוטומטית ל-8 ויפעלו על 8 ליבות ה-TPU.

da534407825f01e3.png

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

טיפול יסודי: XLA

תוכנות Tensorflow מגדירות גרפים של מחשוב. ה-TPU לא מריץ ישירות קוד Python, אלא מריץ את תרשים החישוב שהוגדר על ידי תוכנת Tensorflow. אבל בקצרה, מהדר בשם XLA (מהדר (compiler לינארי) מואץ' ממיר את תרשים Tensorflow של צומתי החישוב לקוד של מכונת TPU. המהדר הזה מבצע גם הרבה אופטימיזציות מתקדמות בקוד שלכם ובפריסת הזיכרון שלכם. האיסוף מתבצע באופן אוטומטי כשהעבודה נשלחת ל-TPU. אין צורך לכלול XLA באופן מפורש בשרשרת ה-build.

edce61112cd57972.png

איור: כדי לרוץ ב-TPU, תרשים החישוב שהוגדר על ידי תוכנית Tensorflow מתורגם קודם לייצוג XLA (מהידר אלגברה מואצת) ואז הידור באמצעות XLA לקוד מכונה של TPU.

שימוש במעבדי TPU ב-Keras

החל מ-Tensorflow 2.1 יש תמיכה במעבדי TPU דרך Keras API. התמיכה ב-Keras פועלת עם מעבדי TPU ושקעי TPU. דוגמה שעובדת ב-TPU, ב-GPU ובמעבדים:

try: # detect TPUs
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
    strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines

# use TPUStrategy scope to define model
with strategy.scope():
  model = tf.keras.Sequential( ... )
  model.compile( ... )

# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)

בקטע הקוד הזה:

  • TPUClusterResolver().connect() מוצא את ה-TPU ברשת. התכונה פועלת ללא פרמטרים ברוב מערכות Google Cloud (משימות AI Platform, Colaboratory, Kubeflow, מכונות וירטואליות של למידה עמוקה שנוצרו באמצעות הכלי ctpu up). המערכות האלה יודעות איפה נמצא ה-TPU שלהן בזכות משתנה הסביבה TPU_NAME. אם יוצרים TPU באופן ידני, צריך להגדיר את סביבת TPU_NAME. משתנה במכונה הווירטואלית שממנה משתמשים בו, או קוראים לפונקציה TPUClusterResolver עם פרמטרים מפורשים: TPUClusterResolver(tp_uname, zone, project)
  • TPUStrategy הוא החלק שבו מיישם את ההתפלגות ואת "all-reduce" אלגוריתם לסנכרון הדרגתי.
  • האסטרטגיה מיושמת באמצעות היקף. המודל חייב להיות מוגדר בתוך ההיקף() של האסטרטגיה.
  • הפונקציה tpu_model.fit מצפה לאובייקט tf.data.Dataset לקלט לצורך אימון TPU.

משימות נפוצות של ניוד TPU

  • במודל Tensorflow יש הרבה דרכים לטעון נתונים, אבל עבור מעבדי TPU חובה להשתמש ב-API של tf.data.Dataset.
  • מעבדי TPU מהירים מאוד ומטמיעים נתונים בדרך כלל הופכים לצוואר בקבוק כשהם פועלים עליהם. במדריך הביצועים של TPU יש כלים שבהם אפשר להשתמש כדי לזהות צווארי בקבוק בנתונים וטיפים נוספים לשיפור הביצועים.
  • מספרי int8 או int16 נחשבים כ-int32. ל-TPU אין חומרת מספרים שלמים שפועלת על פחות מ-32 ביטים.
  • חלק מהפעולות של Tensorflow לא נתמכות. הרשימה כבר כאן. החדשות הטובות הן שההגבלה הזו חלה רק על קוד אימון, כלומר המעבר קדימה ואחורה במודל שלכם. עדיין אפשר להשתמש בכל פעולות Tensorflow בצינור עיבוד הנתונים של קלט הנתונים כי הן יבוצעו במעבד (CPU).
  • tf.py_func לא נתמך ב-TPU.

4. טעינת נתונים

c0ecb860e4cad0a9.jpeg cc4781a7739c49ae.jpeg 81236b00f8bbf39e.jpeg 961e2228974076bb.jpeg 7517dc163bdffcd5.jpeg 96392df4767f566d.png

נעבוד עם מערך נתונים של תמונות פרחים. המטרה היא ללמוד לסווג אותם ל-5 סוגי פרחים. טעינת הנתונים מתבצעת באמצעות ה-API של tf.data.Dataset. קודם נכיר את ה-API.

הפעלה קולית

פותחים את ה-notebook הבא, מריצים את התאים (Shift-ENTER) ופועלים לפי ההוראות בכל מקום שבו מופיע הכיתוב 'נדרשת עבודה' התיוג.

c3df49e90e5a654f.png Fun with tf.data.Dataset (playground).ipynb

מידע נוסף

מידע על "פרחים" מערך נתונים

מערך הנתונים מסודר ב-5 תיקיות. כל תיקייה מכילה פרחים מסוג מסוים. השמות של התיקיות הם 'חמניות', 'חנית', 'שן הארי', 'צבעונים' ו'ורדים'. הנתונים מתארחים בקטגוריה ציבורית ב-Google Cloud Storage. קטע:

gs://flowers-public/sunflowers/5139971615_434ff8ed8b_n.jpg
gs://flowers-public/daisy/8094774544_35465c1c64.jpg
gs://flowers-public/sunflowers/9309473873_9d62b9082e.jpg
gs://flowers-public/dandelion/19551343954_83bb52f310_m.jpg
gs://flowers-public/dandelion/14199664556_188b37e51e.jpg
gs://flowers-public/tulips/4290566894_c7f061583d_m.jpg
gs://flowers-public/roses/3065719996_c16ecd5551.jpg
gs://flowers-public/dandelion/8168031302_6e36f39d87.jpg
gs://flowers-public/sunflowers/9564240106_0577e919da_n.jpg
gs://flowers-public/daisy/14167543177_cd36b54ac6_n.jpg

למה להשתמש ב-tf.data.Dataset?

Keras ו-Tensorflow מקבלים מערכי נתונים בכל פונקציות האימון וההערכה שלהם. אחרי שטוענים נתונים במערך נתונים, ה-API מציע את כל הפונקציות הנפוצות שמועילות לנתוני אימון של רשת נוירונים:

dataset = ... # load something (see below)
dataset = dataset.shuffle(1000) # shuffle the dataset with a buffer of 1000
dataset = dataset.cache() # cache the dataset in RAM or on disk
dataset = dataset.repeat() # repeat the dataset indefinitely
dataset = dataset.batch(128) # batch data elements together in batches of 128
AUTOTUNE = tf.data.AUTOTUNE
dataset = dataset.prefetch(AUTOTUNE) # prefetch next batch(es) while training

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

מידע בסיסי על tf.data.Dataset

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

filenames_dataset = tf.data.Dataset.list_files('gs://flowers-public/*/*.jpg')
# The parameter is a "glob" pattern that supports the * and ? wildcards.

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

def decode_jpeg(filename):
  bits = tf.io.read_file(filename)
  image = tf.io.decode_jpeg(bits)
  return image

image_dataset = filenames_dataset.map(decode_jpeg)
# this is now a dataset of decoded images (uint8 RGB format)

כדי לבצע איטרציה במערך נתונים:

for data in my_dataset:
  print(data)

מערכי נתונים של צמדים

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

def decode_jpeg_and_label(filename):
  bits = tf.read_file(filename)
  image = tf.io.decode_jpeg(bits)
  label = ... # extract flower name from folder name
  return image, label

image_dataset = filenames_dataset.map(decode_jpeg_and_label)
# this is now a dataset of (image, label) pairs 

for image, label in dataset:
  print(image.numpy().shape, label.numpy())

מסקנות:טעינת תמונות אחת אחרי השנייה היא איטית!

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

פתרון

הנה ה-notebook של הפתרון. אפשר להשתמש בו אם נתקעת.

c3df49e90e5a654f.png Fun with tf.data.Dataset (solution).ipynb

אילו נושאים דיברנו?

  • 🤔 tf.data.Dataset.list_files
  • 🤔 tf.data.Dataset.map
  • 🤔 מערכי נתונים של צמדים
  • 👋 חזרה באמצעות מערכי נתונים

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

5. הנתונים נטענים במהירות

מאיצי החומרה של Tensor לעיבוד נתונים (TPU) שבהם נשתמש בשיעור ה-Lab הזה מהירים מאוד. האתגר בדרך כלל הוא להזין להם נתונים מהר מספיק כדי להעסיק אותם. Google Cloud Storage (GCS) יכול לשמור על תפוקה גבוהה מאוד, אבל כמו בכל מערכות האחסון בענן, תהליך החיבור כרוך בעלויות מסוימות של הרשת הלוך ושוב. לכן, אחסון הנתונים שלנו כאלפי קבצים נפרדים לא אידיאלי. אנחנו מתכננים לקבץ אותם במספר קטן יותר של קבצים ולהשתמש בעוצמה של tf.data.Dataset כדי לקרוא מכמה קבצים במקביל.

שיעור קריאה

הקוד שטוען קובצי תמונה, משנה את הגודל שלהם לגודל משותף ולאחר מכן מאחסן אותם ב-16 קובצי TFRecord ב-notebook הבא. מומלץ לקרוא אותו במהירות. אין צורך להפעיל אותו כי נתונים בפורמט TFRecord כראוי יסופקו לשאר ה-Codelab.

c3df49e90e5a654f.png Flower pictures to TFRecords.ipynb

פריסת נתונים אידיאלית לתפוקה אופטימלית של GCS

פורמט הקובץ TFRecord

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

filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...) # do the TFRecord decoding here - see below

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

AUTOTUNE = tf.data.AUTOTUNE
ignore_order = tf.data.Options()
ignore_order.experimental_deterministic = False

filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE)
dataset = dataset.with_options(ignore_order)
dataset = dataset.map(...) # do the TFRecord decoding here - see below

תקציר של TFRecord

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

כתיבת מחרוזות של בייטים

# warning, the input is a list of byte strings, which are themselves lists of bytes
def _bytestring_feature(list_of_bytestrings):
  return tf.train.Feature(bytes_list=tf.train.BytesList(value=list_of_bytestrings))

כתיבת מספרים שלמים

def _int_feature(list_of_ints): # int64
  return tf.train.Feature(int64_list=tf.train.Int64List(value=list_of_ints))

כתיבת צפים

def _float_feature(list_of_floats): # float32
  return tf.train.Feature(float_list=tf.train.FloatList(value=list_of_floats))

כתיבת TFRecord, באמצעות כלי העזר שלמעלה

# input data in my_img_bytes, my_class, my_height, my_width, my_floats
with tf.python_io.TFRecordWriter(filename) as out_file:
  feature = {
    "image": _bytestring_feature([my_img_bytes]), # one image in the list
    "class": _int_feature([my_class]),            # one class in the list
    "size": _int_feature([my_height, my_width]),  # fixed length (2) list of ints
    "float_data": _float_feature(my_floats)       # variable length  list of floats
  }
  tf_record = tf.train.Example(features=tf.train.Features(feature=feature))
  out_file.write(tf_record.SerializeToString())

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

קריאה מ-TFRecords

def read_tfrecord(data):
  features = {
    # tf.string = byte string (not text string)
    "image": tf.io.FixedLenFeature([], tf.string), # shape [] means scalar, here, a single byte string
    "class": tf.io.FixedLenFeature([], tf.int64),  # shape [] means scalar, i.e. a single item
    "size": tf.io.FixedLenFeature([2], tf.int64),  # two integers
    "float_data": tf.io.VarLenFeature(tf.float32)  # a variable number of floats
  }

  # decode the TFRecord
  tf_record = tf.io.parse_single_example(data, features)

  # FixedLenFeature fields are now ready to use
  sz = tf_record['size']

  # Typical code for decoding compressed images
  image = tf.io.decode_jpeg(tf_record['image'], channels=3)

  # VarLenFeature fields require additional sparse.to_dense decoding
  float_data = tf.sparse.to_dense(tf_record['float_data'])

  return image, sz, float_data

# decoding a tf.data.TFRecordDataset
dataset = dataset.map(read_tfrecord)
# now a dataset of triplets (image, sz, float_data)

קטעי קוד שימושיים:

קריאת רכיבים בודדים של נתונים

tf.io.FixedLenFeature([], tf.string)   # for one byte string
tf.io.FixedLenFeature([], tf.int64)    # for one int
tf.io.FixedLenFeature([], tf.float32)  # for one float

קריאת רשימות בגודל קבוע של רכיבים

tf.io.FixedLenFeature([N], tf.string)   # list of N byte strings
tf.io.FixedLenFeature([N], tf.int64)    # list of N ints
tf.io.FixedLenFeature([N], tf.float32)  # list of N floats

קריאת מספר משתנה של פריטי נתונים

tf.io.VarLenFeature(tf.string)   # list of byte strings
tf.io.VarLenFeature(tf.int64)    # list of ints
tf.io.VarLenFeature(tf.float32)  # list of floats

הפונקציה VerLenFeature מחזירה וקטור sparse ונדרש שלב נוסף אחרי פענוח ה-TFRecord:

dense_data = tf.sparse.to_dense(tf_record['my_var_len_feature'])

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

tf.io.FixedLenFeature([], tf.int64, default_value=0) # this field is optional

אילו נושאים דיברנו?

  • 🤔 פיצול של קובצי נתונים כדי לקבל גישה מהירה מ-GCS
  • 😓 איך לכתוב רשומות TFRecords. (כבר שכחת את התחביר? זה בסדר, כדאי להוסיף את הדף הזה לסימניות כתקציר)
  • 🤔 טעינת מערך נתונים מ-TFRecords באמצעות הפונקציה TFRecordDataset

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

6. [INFO] מסווג רשת נוירונים 101

בקצרה

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

עבור מודלים שנוצרו כרצף של שכבות, Keras מציעה את Sequential API. לדוגמה, אפשר לכתוב ב-Keras מסווג תמונות שמשתמש בשלוש שכבות צפופות כך:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[192, 192, 3]),
    tf.keras.layers.Dense(500, activation="relu"),
    tf.keras.layers.Dense(50, activation="relu"),
    tf.keras.layers.Dense(5, activation='softmax') # classifying into 5 classes
])

# this configures the training of the model. Keras calls it "compiling" the model.
model.compile(
  optimizer='adam',
  loss= 'categorical_crossentropy',
  metrics=['accuracy']) # % of correct answers

# train the model
model.fit(dataset, ... )

688858c21e3beff2.png

רשת נוירונים צפופה

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

c21bae6dade487bc.png

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

נוירונים, הפעלות, RELU

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

644f4213a4ee70e5.png

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

הפעלת Softmax

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

אפשר להחיל softmax על וקטור

ef0d98c0952c262d.png d51252f75894479e.gif

הפסד חוצה-אנטרופיה

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

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

7bdf8753d20617fb.png

ירידה הדרגתית

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

ה-cross-entropy הוא פונקציה של משקולות, הטיות, פיקסלים של תמונת האימון והסיווג הידוע שלה.

אם נחשב את הנגזרות החלקיות של ה-Cross-entropy ביחס לכל המשקולות ולכל ההטיות, נקבל 'הדרגתיות', המחושב עבור תמונה, תווית וערך נוכחי של משקולות והטיות. זכרו שיש לנו מיליוני משקולות והטיות, ולכן חישוב ההדרגתיות נשמע כמו עבודה רבה. למרבה המזל, Tensorflow עושה את זה בשבילנו. התכונה המתמטית של שיפוע הוא שהוא מצביע "למעלה". מכיוון שאנחנו רוצים להגיע למקום שבו ה-cross-entropy הוא נמוך, אנחנו הולכים בכיוון ההפוך. אנחנו מעדכנים את המשקל וההטיות לפי חלק מההדרגתיות. לאחר מכן אנחנו מבצעים את אותו הדבר שוב ושוב באמצעות הקבוצות הבאות של תמונות ותוויות לאימון, בלולאת אימון. אנו מקווים שזה מתכנס למקום שבו ה-cross-entropy הוא מינימלי, אם כי שום דבר לא מבטיח שהמינימום הזה ייחודי.

הדרגתיות של ירידה2.png

מיני-אצווה ומומנטום

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

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

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

52e824fe4716c4a0.png

איור: נקודת אוכף. ההדרגתיות היא 0 אך הוא לא ערך מינימלי בכל הכיוונים. (קרדיט תמונה Wikimedia: By Nicoguaro – Own work, CC BY 3.0)

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

מילון מונחים

אצווה או מיני-אצווה: האימון תמיד מתבצע על קבוצות של נתוני אימון ותוויות. הפעולה הזו עוזרת לאלגוריתם להתמזג. "האצווה" הוא בדרך כלל המימד הראשון של מזיני הנתונים. לדוגמה, Tensor של צורה [100, 192, 192, 3] מכיל 100 תמונות של 192x192 פיקסלים עם שלושה ערכים לכל פיקסל (RGB).

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

שכבה צפופה: שכבה של נוירונים שבה כל נוירונים מחובר לכל נוירונים בשכבה הקודמת.

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

labels: שם אחר ל-"classes" או נכונות תשובות בעייתיות בסיווג בפיקוח

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

logits: הפלט של שכבת נוירונים לפני הפעלת פונקציית ההפעלה נקראים 'logits'. המונח מגיע מהפונקציה הלוגיסטית. נקרא גם "פונקציית sigmoid" שהיתה בעבר פונקציית ההפעלה הפופולרית ביותר. "פלט נוירונים לפני פונקציה לוגיסטית" קוצר ל-"logit".

loss (הפסד): פונקציית השגיאה שמשווה בין פלטי רשת נוירונים לתשובות הנכונות

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

קידוד חם אחד: מחלקה 3 מתוך 5 מקודדת בתור וקטור של 5 רכיבים, כל אפסים חוץ מהאפס השלישי שהוא 1.

relu: יחידה לינארית מתוקנת. פונקציית הפעלה פופולרית לנוירונים.

sigmoid: פונקציית הפעלה נוספת שבעבר הייתה פופולרית ועדיין שימושית במקרים מיוחדים.

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

tensor: "tensor" דומה למטריצה, אבל עם מספר שרירותי של מאפיינים. טנזור חד-ממדי הוא וקטור. מפריד דו-ממדי הוא מטריצה. לאחר מכן יכולים להיות רכיבי Tensor עם 3, 4, 5 או יותר מאפיינים.

7. העברת הלמידה

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

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

שיחה קולית

פותחים את ה-notebook הבא, מריצים את התאים (Shift-ENTER) ופועלים לפי ההוראות בכל מקום שבו מופיע הכיתוב 'נדרשת עבודה' התיוג.

c3df49e90e5a654f.png Keras Flowers transfer learning (playground).ipynb

מידע נוסף

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

b8fc1efd2001f072.png

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

העברת הלמידה ב-Keras

ב-Keras אפשר ליצור מודל שעבר אימון מראש מהאוסף tf.keras.applications.*. לדוגמה, MobileNet V2 היא ארכיטקטורה מתקפלת טובה מאוד שנשארת סבירה בגודלה. אם בוחרים באפשרות include_top=False, מקבלים את המודל שעבר אימון מראש בלי שכבת ה-softmax הסופית שלו, ואז אפשר להוסיף לו:

pretrained_model = tf.keras.applications.MobileNetV2(input_shape=[*IMAGE_SIZE, 3], include_top=False)
pretrained_model.trainable = False

model = tf.keras.Sequential([
    pretrained_model,
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(5, activation='softmax')
])

כדאי לשים לב גם להגדרה pretrained_model.trainable = False. המודל מקפיא את המשקולות ואת ההטיות של המודל שעבר אימון מראש, כדי לאמן את שכבת ה-softmax בלבד. בדרך כלל יש בכך מעט משקולות וניתן לבצע אותה במהירות וללא צורך במערך נתונים גדול מאוד. עם זאת, אם יש לכם הרבה נתונים, תוכלו לשפר את הלמידה של ההעברה באמצעות pretrained_model.trainable = True. המשקולות שעברו אימון מראש מספקות ערכים ראשוניים מצוינים, ועדיין אפשר לשנות אותן באימון כך שיתאימו טוב יותר לבעיה שלכם.

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

רמת הדיוק של השיטה אמורה להגיע ל-75%.

פתרון

הנה ה-notebook של הפתרון. אפשר להשתמש בו אם נתקעת.

c3df49e90e5a654f.png Keras Flowers transfer learning (solution).ipynb

אילו נושאים דיברנו?

  • 🤔 איך לכתוב סיווג ב-Keras
  • 🤓 שהוגדרו עם שכבה אחרונה של softmax, ואובדן ב-Cross-entropy
  • 😈 להעביר את הלמידה
  • 🤔 אימון המודל הראשון
  • 🧐 בעקבות ההפסד והדיוק שלהן במהלך האימון

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

8. [מידע] רשתות נוירונים מלאכותיות

בקצרה

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

convolutional.gif

איור: סינון תמונה עם שני פילטרים עוקבים שמורכבים מ-48 x 4 x 48 משקולות לכל אחד מהם.

כך נראית רשת עצבית מתקפלת פשוטה ב-Keras:

model = tf.keras.Sequential([
  # input: images of size 192x192x3 pixels (the three stands for RGB channels)
  tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu', input_shape=[192, 192, 3]),
  tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=6, padding='same', activation='relu'),
  tf.keras.layers.Flatten(),
  # classifying into 5 categories
  tf.keras.layers.Dense(5, activation='softmax')
])

model.compile(
  optimizer='adam',
  loss= 'categorical_crossentropy',
  metrics=['accuracy'])

688858c21e3beff2.png

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

בשכבה של רשת קונבולוציונית, "נוירון" אחד מציג סכום משוקלל של הפיקסלים שנמצאים בדיוק מעליו, באזור קטן בלבד של התמונה. הוא מוסיף הטיה ומזין את הסכום באמצעות פונקציית הפעלה, בדיוק כמו שנירון בשכבה רגילה וצפיפות. לאחר מכן הפעולה הזו חוזרת על עצמה בכל התמונה תוך שימוש באותם משקלים. זכרו שבשכבות צפופות, לכל נוירון יש משקולות משלו. כאן יש "תיקון" אחד המשקולות מחליקות על פני התמונה בשני הכיוונים ("קובולציה"). הפלט כולל ערכים רבים ככל האפשר בתמונה (מספר הפיקסלים בתמונה (עם זאת, יש צורך במרווח פנימי מסוים בקצוות). זוהי פעולת סינון שבה נעשה שימוש במסנן של משקולות 4x4x3=48.

עם זאת, 48 משקולות לא מספיקות. כדי להוסיף עוד דרגות חופש, אנחנו חוזרים על אותה פעולה עם קבוצת משקולות חדשה. הפעולה הזו תפיק קבוצה חדשה של פלט מסננים. שנקרא 'ערוץ' של פלטים באנלוגיה עם הערוצים R,G,B בתמונת הקלט.

צילום מסך מתאריך 2016-07-29 בשעה 16.02.37.png

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

d1b557707bcd1cb9.png

איור: רשת עצבית מתקפלת משנה 'קוביות' של נתונים ל"קוביות" אחרות של נתונים.

קיפולים (קונבולציות) ברצף, צבירה מקסימלית

אם מבצעים את הקונבולוציות בקצב של 2 או 3, אפשר גם לכווץ את קוביית הנתונים שמתקבלת בממדים האופקיים שלה. אפשר לעשות זאת ב-2 דרכים נפוצות:

  • קונבולציה זזה: מסנן הזזה כמו למעלה, אבל עם צעדים >1
  • קיבוץ מקסימלי: חלון הזזה שמבצע את הפעולה MAX (בדרך כלל בתיקונים בגודל 2x2, שחוזר על עצמו כל 2 פיקסלים)

2b2d4263bb8470b.gif

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

מסווג מסתובב

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

4a61aaffb6cba3d1.png

איור: מסווג תמונות באמצעות שכבות קונבולוציה ושכבות softmax. היא משתמשת במסננים ביחס של 3x3 ו-1x1. שכבות ה-maxpool צריכות לכלול עד 2x2 נקודות נתונים. ראש הסיווג מוטמע בשכבה צפופה עם הפעלה softmax.

ב-Keras

אפשר לכתוב ב-Keras את הערימה המתקפלת שמתוארת למעלה כך:

model = tf.keras.Sequential([
  # input: images of size 192x192x3 pixels (the three stands for RGB channels)    
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu', input_shape=[192, 192, 3]),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=16, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=8, padding='same', activation='relu'),
  tf.keras.layers.Flatten(),
  # classifying into 5 categories
  tf.keras.layers.Dense(5, activation='softmax')
])

model.compile(
  optimizer='adam',
  loss= 'categorical_crossentropy',
  metrics=['accuracy'])

9. המרה מותאמת אישית

שיחה קולית

בואו לבנות ולאמן מחדש רשת נוירונים קונבולוציה. השימוש ב-TPU יאפשר לנו לבצע איטרציה במהירות רבה. פותחים את ה-notebook הבא, מריצים את התאים (Shift-ENTER) ופועלים לפי ההוראות בכל מקום שבו מופיע הכיתוב 'נדרשת עבודה' התיוג.

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

המטרה היא לגבור על רמת הדיוק של 75% מהמודל של למידת ההעברה. למודל הזה יש יתרון: הוא עבר אימון מראש על מערך נתונים של מיליוני תמונות, ויש לנו כאן רק תמונות ב-3670. האם תוכלו לפחות להתאים אותה?

מידע נוסף

כמה שכבות, מה הגודל?

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

Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 192, 192, 16)      448       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 192, 192, 30)      4350      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 96, 96, 30)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 96, 96, 60)        16260     
_________________________________________________________________
 ... 
_________________________________________________________________
global_average_pooling2d (Gl (None, 130)               0         
_________________________________________________________________
dense (Dense)                (None, 90)                11790     
_________________________________________________________________
dense_1 (Dense)              (None, 5)                 455       
=================================================================
Total params: 300,033
Trainable params: 300,033
Non-trainable params: 0
_________________________________________________________________

כמה טיפים:

  • שכבות מרובות הן מה שהופך את רשתות נוירונים יעילות. לפתרון הבעיה הפשוטה הזו של זיהוי פרחים, מומלץ להוסיף 5 עד 10 שכבות.
  • להשתמש במסננים קטנים. בדרך כלל, מסננים בגודל 3x3 מתאימים בכל מקום.
  • אפשר להשתמש גם בפילטרים ביחס 1x1, והם זולים. הם לא ממש "מסננים" הכול חוץ מלחשב שילובים ליניאריים של ערוצים. כדאי להחליף אותם בפילטרים אמיתיים. (מידע נוסף על "קונבולציות של 1x1" מופיע בקטע הבא).
  • במקרה של בעיית סיווג כזו, מומלץ להקטין את הדגימה בתדירות גבוהה באמצעות שכבות מאגר מקסימלי (או קונבולוציות עם צעדים >1). לא משנה לך איפה הפרח נמצא, אלא רק שהוא ורד או שן הארי, כך שאיבוד המידע על ה-x ו-y אינו חשוב, והסינון של אזורים קטנים יותר הוא זול יותר.
  • מספר המסננים בדרך כלל דומה למספר המחלקות בסוף הרשת (למה? ראה למטה הטריק "מאגר ממוצע גלובלי"). אם מסווגים למאות מחלקות, מגדילים את מספר המסננים בהדרגה בשכבות עוקבות. למערך נתוני הפרחים שמורכב מ-5 מחלקות, סינון עם 5 מסננים בלבד לא יספיק. אפשר להשתמש באותו מספר מסננים ברוב השכבות, לדוגמה 32 ולהקטין אותו לקראת הסוף.
  • השכבות הדחוסות הסופיות יקרות. יכולים להיות להם יותר משקלים מאשר כל השכבות הקונבולוציה ביחד. לדוגמה, גם עם פלט סביר מאוד מקוביית הנתונים האחרונה של 24x24x10 נקודות נתונים, שכבה של 100 צפופה נוירונים בעלות של 24x24x10x100=576,000 משקולות !!! מומלץ לחשוב פעמיים או לנסות מאגר נתונים ממוצע גלובלי (ראו בהמשך).

מאגר ממוצע גלובלי

במקום להשתמש בשכבה צפופה יקרה בקצה של רשת עצבית מתקפלת, אפשר לפצל את ה'קובייה' של הנתונים הנכנסים בכמה חלקים ככל האפשר, צריך לחשב את הממוצע של הערכים שלהם ולהזין אותם באמצעות פונקציית הפעלה Softmax. בשיטה הזו של בניית ראש הסיווג, עולה 0 משקלים. ב-Keras, התחביר הוא tf.keras.layers.GlobalAveragePooling2D().

93240029f59df7c2.png

פתרון

הנה ה-notebook של הפתרון. אפשר להשתמש בו אם נתקעת.

c3df49e90e5a654f.png Keras_Flowers_TPU (solution).ipynb

אילו נושאים דיברנו?

  • 🤔 הפעלה עם שכבות קונבולוציה
  • 🤓 התנסות באיסוף מקסימום, צעדים, קיבוץ ממוצע עולמי, ...
  • 👋 עברת במהירות על מודל בעולם האמיתי, ב-TPU

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

10. [מידע] ארכיטקטורות קונבולוציה מודרניות

בקצרה

7968830b57b708c0.png

איור: 'מודול' קונבולוציה. מה הכי טוב בשלב זה? האם שכבת המאגר המקסימלי ולאחר מכן שכבה קונבולוטית של 1x1 או שילוב אחר של שכבות? מנסים את כולם, משרשרים את התוצאות ונותנים לרשת להחליט. בצד שמאל: " inception" של הארכיטקטורה המורכבת באמצעות מודולים כאלה.

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

l = tf.keras.layers # syntax shortcut

y = l.Conv2D(filters=32, kernel_size=3, padding='same',
             activation='relu', input_shape=[192, 192, 3])(x) # x=input image

# module start: branch out
y1 = l.Conv2D(filters=32, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=32, kernel_size=3, padding='same', activation='relu')(y)
y = l.concatenate([y1, y3]) # output now has 64 channels
# module end: concatenation

# many more layers ...

# Create the model by specifying the input and output tensors.
# Keras layers track their connections automatically so that's all that's needed.
z = l.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, z)

688858c21e3beff2.png

טריקים זולים אחרים

פילטרים קטנים ביחס גובה-רוחב של 3x3

40a7b15fb7dbe75c.png

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

קונבולוציות 1x1?

fd7cac16f8ecb423.png

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

11. סקווֶנט

אחת הדרכים הפשוטות ביותר להרכיב את הרעיונות האלה מוצגת ברשימה של "Squeezenet" נייר. המחברים מציעים תכנון פשוט מאוד של מודול קונבולוציה, באמצעות שכבות קונבולוציה בגודל 1x1 ו-3x3 בלבד.

1730ac375379269b.png

איור: ארכיטקטורת מעיכה שמבוססת על 'מודולים של אש'. הן מחליפים ביניהן שכבה של 1x1 ש"כווצת" את הנתונים הנכנסים במימד האנכי ולאחר מכן שתי שכבות מקבילות של 1x1 ו-3x3 ש"מתרחבות". את עומק הנתונים שוב.

שיחה קולית

ממשיכים ב-notebook הקודם ויוצרים רשת עצבית מתקפלת בהשראת סקוץ'. תצטרכו לשנות את קוד המודל ל"סגנון פונקציונלי" של Keras.

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

מידע נוסף

בתרגיל הזה כדאי להגדיר פונקציה מסייעת למודול מעיכה:

def fire(x, squeeze, expand):
  y = l.Conv2D(filters=squeeze, kernel_size=1, padding='same', activation='relu')(x)
  y1 = l.Conv2D(filters=expand//2, kernel_size=1, padding='same', activation='relu')(y)
  y3 = l.Conv2D(filters=expand//2, kernel_size=3, padding='same', activation='relu')(y)
  return tf.keras.layers.concatenate([y1, y3])

# this is to make it behave similarly to other Keras layers
def fire_module(squeeze, expand):
  return lambda x: fire(x, squeeze, expand)

# usage:
x = l.Input(shape=[192, 192, 3])
y = fire_module(squeeze=24, expand=48)(x) # typically, squeeze is less than expand
y = fire_module(squeeze=32, expand=64)(y)
...
model = tf.keras.Model(x, y)

המטרה הפעם היא להגיע לרמת דיוק של 80%.

דברים שכדאי לנסות

מתחילים עם שכבה מתקפלת אחת ולאחר מכן ממשיכים עם "fire_modules", לסירוגין עם MaxPooling2D(pool_size=2) שכבות. אפשר להתנסות במספר מקסימלי של 2 עד 4 שכבות מאגר ברשת, וגם עם 1, 2 או 3 מודולי אש רצופים בין שכבות המאגר המקסימליות.

במודולים של Fire, "smoeze" בדרך כלל צריך להיות קטן יותר מהפרמטר "expand" הפרמטר. הפרמטרים האלה הם למעשה מספרים של מסננים. בדרך כלל הם נע בין 8 ל-196. אתם יכולים להתנסות בארכיטקטורות שבהן מספר המסננים גדל בהדרגה דרך הרשת או בארכיטקטורות פשוטות שבהן לכל המודולים של אש יש אותו מספר מסננים.

לדוגמה:

x = tf.keras.layers.Input(shape=[*IMAGE_SIZE, 3]) # input is 192x192 pixels RGB

y = tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu')(x)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.GlobalAveragePooling2D()(y)
y = tf.keras.layers.Dense(5, activation='softmax')(y)

model = tf.keras.Model(x, y)

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

נירמול באצווה

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

y = tf.keras.layers.BatchNormalization(momentum=0.9)(y)
# please adapt the input and output "y"s to whatever is appropriate in your context

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

הגדלת נתונים

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

4ed2958e09b487ca.png

ad795b70334e0d6b.png

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

def data_augment(image, label):
    image = tf.image.random_flip_left_right(image)
    image = tf.image.random_saturation(image, lower=0, upper=2)
    return image, label

לאחר מכן משתמשים בו בטרנספורמציה הסופית של הנתונים ("מערכי נתונים של אימון ואימות" של התא, פונקציה "get_batched_dataset"):

dataset = dataset.repeat() # existing line
# insert this
if augment_data:
  dataset = dataset.map(data_augment, num_parallel_calls=AUTO)
dataset = dataset.shuffle(2048) # existing line

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

עכשיו אפשר להגיע לרמת דיוק של 80% ב-35 תקופות של זמן מערכת.

פתרון

הנה ה-notebook של הפתרון. אפשר להשתמש בו אם נתקעת.

c3df49e90e5a654f.png Keras_Flowers_TPU_squeezenet.ipynb

אילו נושאים דיברנו?

  • 🤔 Keras "סגנון פונקציונלי" דגמים
  • 🤓 ארכיטקטורת מעיכה
  • 🤓 נתונים באמצעות tf.data.datset

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

12. כוונון עדין של Xception

קיפולים (קונבולציות) שניתנות להפרדה

דרך אחרת ליישום שכבות קונבולוציה צברה לאחרונה פופולריות: קונבולוציות עם הפרדה בעומק. אני יודעת, שפע של פה, אבל העיקרון די פשוט. הם מוטמעים ב-Tensorflow וב-Keras בתור tf.keras.layers.SeparableConv2D.

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

615720b803bf8dda.gif

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

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

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

שכבה מתקפלת: 4 x 4 x 3 x 5 = 240

שכבה מתקפלת שניתנת להפרדה: 4 x 4 x 3 + 3 x 5 = 48 + 15 = 63

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

שיחה קולית

התחלה מחדש מהקטע 'העברת הלמידה' notebook של Playground, אבל הפעם בוחרים ב-Xception כמודל שעבר אימון מראש. Xception משתמש בקיפולים נפרדים בלבד. צריך לאפשר אימון של כל המשקולות. אנחנו נבצע כוונון עדין של המשקולות לפני אימון על הנתונים שלנו במקום להשתמש ככה בשכבות שאומנו מראש.

c3df49e90e5a654f.png Keras Flowers transfer learning (playground).ipynb

יעד: דיוק > 95% (לא, ברצינות, זה אפשרי!)

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

מידע נוסף על כוונון עדין

Xception זמין במודלים הרגילים שעברו אימון מראש באפליקציה tf.keras.application.* לא לשכוח להשאיר את כל המשקולות בשלב הזה לאימון.

pretrained_model = tf.keras.applications.Xception(input_shape=[*IMAGE_SIZE, 3],
                                                  include_top=False)
pretrained_model.trainable = True

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

9b1af213b2b36d47.png

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

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

def lr_fn(epoch):
  lr = ...
  return lr

lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_fn, verbose=True)

model.fit(..., callbacks=[lr_callback])

פתרון

הנה ה-notebook של הפתרון. אפשר להשתמש בו אם נתקעת.

c3df49e90e5a654f.png 07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb

אילו נושאים דיברנו?

  • 🤔 הסרה בקישורי עומק
  • 🤓 לוח זמנים לקצב הלמידה
  • 😈. כוונון עדין של מודל שעבר אימון מראש.

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

13. מעולה!

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

מעבדי TPU בפועל

מעבדי TPU ומעבדי GPU זמינים ב-Vertex AI של Google Cloud:

לבסוף, אנחנו אוהבים לקבל משוב. נשמח לשמוע אם משהו השתבש בשיעור ה-Lab הזה או אם לדעתכם צריך לשפר אותו. אפשר לשלוח משוב דרך 'בעיות ב-GitHub' [feedback link].

HR.png

Martin Görner ID Small.jpg
המחבר: מרטין גורנר
Twitter: @martin_gorner