1. סקירה כללית
בשיעור ה-Lab הזה תלמדו איך ליצור, לאמן ולכוונן רשתות נוירונים קונבולוציוניות משלכם מהתחלה באמצעות Keras ו-Tensorflow 2. עכשיו אפשר לעשות זאת תוך דקות ספורות באמצעות TPU. תלמדו גם על מספר גישות, החל מלמידה פשוטה מאוד בהעברה ועד לארכיטקטורות קונבולוציה מודרניות כמו סקוזנט. הסדנה הזו כוללת הסברים תיאורטיים על רשתות נוירונים, והיא נקודת התחלה טובה למפתחים שמתחילים ללמוד על למידת עומק.
קריאת מאמרים בנושא למידה עמוקה יכולה להיות קשה ומבלבלת. נבחן ארכיטקטורות מודרניות של רשתות נוירונים קונבולוציוניות.
מה תלמדו
- כדי להשתמש ב-Keras וביחידות עיבוד נתונים טילר (TPU) כדי ליצור מודלים מותאמים אישית מהר יותר.
- כדי להשתמש ב-tf.data.Dataset API ובפורמט TFRecord כדי לטעון נתוני אימון ביעילות.
- כדי לרמות 😈, אפשר להשתמש בלמידה בהעברה במקום לבנות מודלים משלך.
- כדי להשתמש בסגנונות של מודלים פונקציונליים ושל מודלים רצופיים ב-Keras.
- כדי ליצור סיווג משלכם ב-Keras עם שכבת softmax ואובדן cross-entropy.
- כדי לכוונן את המודל באמצעות בחירה טובה של שכבות קונבולוציה.
- כדי לבחון רעיונות מודרניים לארכיטקטורה של רשתות נוירוניות קונבולוציוניות (CNN), כמו מודולים, צבירה גלובלית ממוצעת וכו'.
- כדי ליצור רשת נוירונים קונבולוציונית (CNN) מודרנית ופשוטה באמצעות ארכיטקטורת Squeezenet.
משוב
אם תיתקלו בבעיה במעבדת הקוד הזו, נשמח לשמוע על כך. אפשר לשלוח משוב דרך 'בעיות ב-GitHub' [feedback link].
2. התחלה מהירה של Google Colaboratory
בשיעור ה-Lab הזה נעשה שימוש ב'שיתוף פעולה עם Google' ולא נדרשת הגדרה כלשהי מצדך. אפשר להריץ אותו מ-Chromebook. כדי להכיר את קובצי ה-notebook של Colab, צריך לפתוח את הקובץ שלמטה ולהפעיל את התאים.
בחירת קצה עורפי של TPU
בתפריט של Colab, בוחרים באפשרות Runtime > Change runtime type (סביבת זמן ריצה > שינוי הסוג של סביבת זמן הריצה) ואז בוחרים באפשרות TPU. בשיעור ה-Lab הזה תלמדו להשתמש ב-TPU (יחידת עיבוד Tensor) חזקה, לגיבוי לצורך אימון עם האצת חומרה. החיבור לסביבת זמן הריצה יתבצע באופן אוטומטי בביצוע הראשון. אפשר גם ללחוץ על הלחצן Connect (התחברות) בפינה הימנית העליונה.
ביצוע של Notebook
כדי להפעיל תאים בכל פעם, לוחצים על תא באמצעות מקש Shift-ENTER. אפשר גם להריץ את כל ה-notebook באמצעות סביבת זמן הריצה > הרצת הכול
תוכן העניינים
בכל המחברות יש תוכן עניינים. אפשר לפתוח אותו באמצעות החץ השחור שמימין.
תאים מוסתרים
בחלק מהתאים יוצג רק השם שלהם. זוהי תכונת notebook ספציפית ל-Colab. אפשר ללחוץ עליהם לחיצה כפולה כדי לראות את הקוד שבתוכם, אבל בדרך כלל הוא לא מעניין במיוחד. בדרך כלל פונקציות תמיכה או פונקציות של תצוגה חזותית. עדיין צריך להריץ את התאים האלה כדי להגדיר את הפונקציות שבתוכה.
אימות
ל-Colab יש אפשרות לגשת לקטגוריות הפרטיות שלך ב-Google Cloud Storage בתנאי שביצעת אימות באמצעות חשבון מורשה. קטע הקוד שלמעלה יפעיל תהליך אימות.
3. [INFO] מהם מעבדי Tensor Processing Unit (TPU)?
בקצרה
הקוד לאימון מודל ב-TPU ב-Keras (והחזרה ל-GPU או ל-CPU אם 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 כדי ליצור מסווג פרחים ולבצע אופטימיזציה שלו במהירויות אינטראקטיביות (דקות בכל ריצה של אימון).
למה כדאי להשתמש ב-TPU?
מעבדי GPU מודרניים מאורגנים לפי 'ליבות' שניתנות לתכנות, ארכיטקטורה גמישה מאוד שמאפשרת להם להתמודד עם מגוון משימות כמו רינדור בתלת-ממד, למידה עמוקה (Deep Learning), סימולציות פיזיות וכו'. מצד שני, יחידות TPU יוצרות התאמה בין מעבד וקטורי קלאסי עם יחידת הכפלה ייעודית של מטריצה, ומתבלטות בכל משימה שבה הכפולות של המטריצה הגדולה שולטות, כמו רשתות נוירונים.
איור: שכבה של רשת נוירונים צפופה ככפל של מטריצה, עם אצווה של שמונה תמונות שמעובדות דרך רשת הנוירונים בבת אחת. עליך לבצע כפל של שורה אחת x עמודה אחת כדי לוודא שבאמת מתבצעת סכימה משוקללת של כל ערכי הפיקסלים בתמונה. אפשר לייצג שכבות מתקפלות בתור הכפלות של מטריצות, אבל התהליך קצת יותר מורכב ( הסבר כאן, בסעיף 1).
החומרה
MXU ו-VPU
ליבת TPU v2 מורכבת מיחידת הכפלת מטריצות (MXU) שמפעילה הכפלת מטריצות ויחידה לעיבוד וקטורים (VPU) לכל המשימות האחרות כמו הפעלות, softmax וכו'. ה-VPU מטפל בחישובים של float32 ו-int32. מצד שני, MXU פועל בפורמט נקודה צפה (floating-point) ברמת דיוק מעורב של 16-32 ביט.
נקודה צפה (floating-point) מעורבת ו-bfloat16
ה-MXU מחשב את הכפלת המטריצות באמצעות קלט bfloat16 ופלט float32. הצטברות ביניים מבוצעות ברמת דיוק של float32.
האימון של רשת נוירונים עמיד בדרך כלל לרעש שנוצר על ידי דיוק מופחת של נקודה צפה (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 יכול להטמיע 16,000 יחידות כאלה ב-MXU בגודל 128x128 ולעבד את הכפלת המטריצות הזו בבת אחת.
איור: מערך הלחץ הסיסטולי של MXU. רכיבי המחשוב הם מכפילים-מצברים. הערכים של מטריצה אחת נטענים למערך (נקודות אדומות). ערכי המטריצה השנייה עוברים דרך המערך (נקודות אפורות). קווים אנכיים מעבירים את הערכים למעלה. קווים אופקיים מפיצים סכומים חלקיים. המשתמשים יכולים לבדוק בעצמם שבזמן שהנתונים עוברים דרך המערך, מתקבלת תוצאת כפל המטריצות בצד שמאל.
בנוסף, בזמן שמתבצע חישוב של המכפלות הסקלריות ב-MXU, סכומים ביניים פשוט עוברים בין יחידות מחשוב סמוכות. אין צורך לאחסן אותם ולאחזר אותם מהזיכרון או אפילו מקובץ רישום. התוצאה הסופית היא שלארכיטקטורת מערך סיסטולי של TPU יש יתרון משמעותי בדחיסות ויתרון כוח, וגם יתרון מהירות לא זניח על פני GPU, במהלך חישוב הכפלות של מטריצות.
Cloud TPU
כשמבקשים Cloud TPU v2 אחד בפלטפורמת Google Cloud, מקבלים מכונה וירטואלית (VM) עם לוח TPU שמחובר ל-PCI. ללוח ה-TPU יש ארבעה צ'יפים של TPU עם ליבה כפולה. כל ליבת TPU כוללת VPU (יחידת עיבוד וקטורי) ו-MXU 128x128 (יחידת הכפלה של MatriX). לאחר מכן, בדרך כלל ה-Cloud TPU מחובר דרך הרשת ל-VM שהזמין אותו. התמונה המלאה נראית כך:
איור: המכונה הווירטואלית עם מואץ Cloud TPU שמחובר לרשת. "Cloud TPU" עצמו עשוי מ-VM עם לוח TPU מחובר PCI ועליו ארבעה שבבי TPU עם שתי ליבות.
מארזי TPU
במרכזי הנתונים של Google, מערכות ה-TPU מחוברות לחיבור מחשוב בעל ביצועים גבוהים (HPC), שיכול לגרום להן להופיע כמאיץ אחד גדול מאוד. Google מכנה אותם pods, והם יכולים לכלול עד 512 ליבות TPU v2 או 2,048 ליבות TPU v3.
איור: pod TPU v3. לוחות ומדפים של TPU שמחוברים דרך חיבור HPC.
במהלך האימון, יש החלפה של שיפועים בין ליבות ה-TPU באמצעות אלגוריתם all-reduce (כאן מוסבר היטב על all-reduce). המודל שמאמנים יכול לנצל את היתרונות של החומרה על ידי אימון בקבוצות גדולות.
איור: סנכרון הדרגה במהלך אימון באמצעות אלגוריתם כל ההפחתה ברשת ה-HPC של רשת ה-HPC הדו-כיוונית של Google TPU.
התוכנה
הדרכה לקבוצות גדולות
גודל האצווה האידיאלי ל-TPU הוא 128 פריטים של נתונים לכל ליבה של TPU, אבל כבר אפשר לראות ניצול יעיל של החומרה עם 8 פריטים של נתונים לכל ליבה של TPU. חשוב לזכור של-Cloud TPU אחד יש 8 ליבות.
בשיעור ה-Lab הזה נשתמש ב-Keras API. ב-Keras, האצווה שציינתם היא גודל האצווה הגלובלי של כל ה-TPU. האצוות יפוצלו אוטומטית ל-8 ויפעלו על 8 ליבות ה-TPU.
טיפים נוספים לשיפור הביצועים מפורטים במדריך לביצועים של TPU. אם מדובר בכמויות גדולות מאוד של אצווה, ייתכן שיידרש טיפול מיוחד במודלים מסוימים, ראו LARSOptimizer לפרטים נוספים.
טיפול יסודי: XLA
תוכנות Tensorflow מגדירות גרפים של מחשוב. ה-TPU לא מפעיל קוד Python ישירות, אלא מפעיל את תרשים החישובים שמוגדר על ידי תוכנית Tensorflow. מתחת לפני השטח, מהדר שנקרא XLA (מהדר אלגברה לינארית מואצת) ממיר את הגרף של Tensorflow של צמתים לחישוב לקוד מכונה של TPU. בנוסף, המהדר מבצע פעולות אופטימיזציה מתקדמות רבות בקוד ובפריסה של הזיכרון. ה-compilation מתבצע באופן אוטומטי כשהעבודה נשלחת ל-TPU. אין צורך לכלול XLA באופן מפורש בשרשרת ה-build.
איור: כדי לרוץ ב-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 ב-VM שבו משתמשים בו, או להפעיל אתTPUClusterResolver
עם פרמטרים מפורשים:TPUClusterResolver(tp_uname, zone, project)
TPUStrategy
הוא החלק שמיישם את ההתפלגות ואת האלגוריתם לסנכרון הדרגתי של "all-reduce".- האסטרטגיה חלה באמצעות היקף. צריך להגדיר את המודל בתוך ה-scope() של האסטרטגיה.
- הפונקציה
tpu_model.fit
מצפה לאובייקט tf.data.Dataset לקלט לצורך אימון TPU.
משימות נפוצות של ניוד TPU
- יש הרבה דרכים לטעון נתונים במודל Tensorflow, אבל ב-TPUs צריך להשתמש ב-
tf.data.Dataset
API. - TPUs הם מהירים מאוד, ולרוב צוואר הבקבוק בזמן ההרצה שלהם הוא הטמעת הנתונים. במדריך לביצועים של TPU מפורטים כלים שאפשר להשתמש בהם כדי לזהות צווארי בקבוק בנתונים וטיפים נוספים לשיפור הביצועים.
- מספרים מסוג int8 או int16 נחשבים כ-int32. ל-TPU אין חומרת מספרים שלמים שפועלת על פחות מ-32 ביטים.
- אין תמיכה בחלק מהפעולות של Tensorflow. הרשימה מופיעה כאן. החדשות הטובות הן שההגבלה הזו חלה רק על קוד אימון, כלומר המעבר קדימה ואחורה במודל שלכם. עדיין תוכלו להשתמש בכל הפעולות של Tensorflow בצינור עיבוד הנתונים להזנת נתונים, כי הפעולות האלה יבוצעו ב-CPU.
tf.py_func
לא נתמך ב-TPU.
4. טעינת נתונים
נעבוד עם מערך נתונים של תמונות פרחים. המטרה היא ללמוד לסווג אותם ל-5 סוגי פרחים. טעינת הנתונים מתבצעת באמצעות ה-API של tf.data.Dataset
. קודם כל נכיר את ה-API.
מעשי
פותחים את ה-notebook הבא, מריצים את התאים (Shift-ENTER) ופועלים לפי ההוראות בכל מקום שבו מופיעה התווית 'נדרשת עבודה'.
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?
אפשר להשתמש ב-Datasets בכל הפונקציות של 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)
מערכי נתונים של צמדי מידע (tuples)
בלמידה מונחית, מערך נתונים לאימון עשוי בדרך כלל מזוגות של נתוני אימון ותשובות נכונות. כדי לאפשר זאת, פונקציית הפענוח יכולה להחזיר צמדי מידע (tuples). לאחר מכן תקבלו מערך נתונים של צמדים וצמדים מוחזרים כשתחזרו אליו. הערכים שמוחזרים הם טינסורים של 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 תמונות בשנייה. זה איטי מדי! מואצי החומרה שבהם נשתמש לאימון יכולים להגיע למהירות גבוהה בהרבה. בקטע הבא מוסבר איך אנחנו עושים את זה.
פתרון
כאן מופיע מסמך הפתרון. אפשר להשתמש בו אם נתקעתם.
Fun with tf.data.Dataset (solution).ipynb
אילו נושאים דיברנו?
- 🤔 tf.data.Dataset.list_files
- 🤔 tf.data.Dataset.map
- 🤔 מערכי נתונים של צמדי מידע (tuples)
- 👋 חזרה באמצעות מערכי נתונים
כדאי להקדיש כמה רגעים כדי לעבור בראש את רשימת המשימות הזו.
5. טעינת נתונים במהירות
מאיצי החומרה של יחידות עיבוד ה-Tensor (TPU) שבהם נשתמש במעבדה הזו הם מהירים מאוד. האתגר בדרך כלל הוא להזין להם נתונים מהר מספיק כדי להעסיק אותם. Google Cloud Storage (GCS) מסוגל לשמור על תפוקה גבוהה מאוד, אבל כמו בכל מערכות האחסון בענן, התחלת החיבור כרוכה בקצת תנועה קדימה ואחורה ברשת. לכן, אחסון הנתונים שלנו כאלפי קבצים נפרדים לא אידיאלי. נקבץ אותם במספר קטן יותר של קבצים ונשתמש ביכולות של tf.data.Dataset כדי לקרוא ממספר קבצים במקביל.
קריאה
הקוד שמטעין קובצי תמונות, משנה את הגודל שלהם לגודל נפוץ ולאחר מכן מאחסן אותם ב-16 קובצי TFRecord מופיע במסמך הבא. עליך לקרוא אותו במהירות. אין צורך להריץ אותו, כי נתונים בפורמט TFRecord תקין יסופקו בשאר הקוד למעבדה.
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
הפונקציה VarLenFeature מחזירה וקטור דל, ונדרש שלב נוסף אחרי פענוח ה-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, ... )
רשת נוירונים צפופה
זוהי רשת העצבים הפשוטה ביותר לסיווג תמונות. הוא מורכב מ'נוירונים' שמאורגנים בשכבות. השכבה הראשונה מעבדת את נתוני הקלט ומעבירה את הפלט שלה לשכבות אחרות. היא נקראת 'דחוס' כי כל נוירונים מחובר לכל נוירונים בשכבה הקודמת.
כדי להזין תמונה לרשת כזו, צריך לשטח את ערכי ה-RGB של כל הפיקסלים שלה וקטור ארוך ולהשתמש בו כקלט. זו לא השיטה הטובה ביותר לזיהוי תמונות, אבל נשפר אותה בהמשך.
נוירונים, הפעלות, RELU
'נוירון' מחשב סכום משוקלל של כל הקלט שלו, מוסיף ערך שנקרא 'הטיה' ומעביר את התוצאה דרך 'פונקציית הפעלה'. המשקולות וההטיה לא ידועים בהתחלה. הם יופעלו באופן אקראי ו"ילמדו" על ידי אימון של רשת הנוירונים על כמות גדולה של נתונים ידועים.
פונקציית ההפעלה הפופולרית ביותר נקראת RELU (יחידה לינארית מתוקנת). זוהי פונקציה פשוטה מאוד כפי שניתן לראות בתרשים שלמעלה.
הפעלת Softmax
הרשת שלמעלה מסתיימת בשכבה של 5 נוירונים מכיוון שאנחנו מסווגים את הפרחים ל-5 קטגוריות (ורד, צבעוני, שן הארי, חיננית, חמנית). נוירונים בשכבות ביניים מופעלים באמצעות פונקציית ההפעלה הקלאסית של RELU. עם זאת, בשכבה האחרונה אנחנו רוצים לחשב מספרים בין 0 ל-1 שמייצגים את ההסתברות שהפרח הזה הוא ורד, צבעוני וכו'. לשם כך נשתמש בפונקציית הפעלה שנקראת "softmax".
כדי להחיל פונקציית softmax על וקטור, מחשבים את החזקה של כל רכיב ואז מבצעים נורמליזציה של הווקטור, בדרך כלל באמצעות נורמלי L1 (סכום הערכים המוחלטים) כך שהערכים יתווספו ל-1 וניתן יהיה לפרש אותם כprobabilities.
Cross-entropy loss
עכשיו, כשרשת הנוירונים שלנו מייצרת חיזויים מתמונות קלט, אנחנו צריכים למדוד עד כמה הן טובות, כלומר, המרחק בין מה שהרשת אומרת לנו לבין התשובות הנכונות, שנקראות גם 'תוויות'. חשוב לזכור שיש לנו תוויות נכונות לכל התמונות במערך הנתונים.
כל מרחק יכול להתאים, אבל במקרה של בעיות סיווג, "מרחק באנטרופיה חוצה" הוא היעיל ביותר. נקרא לפונקציה הזו פונקציית השגיאה או פונקציית 'הפסד':
ירידה הדרגתית
"אימון" של רשת הנוירונים פירושו שימוש בתמונות ובתוויות אימון כדי להתאים משקולות והטיות ולצמצם את פונקציית האיבוד החוצה-אנטרופיה. כך זה עובד.
ה-cross-entropy הוא פונקציה של משקולות, הטיות, פיקסלים של תמונת האימון והסיווג הידוע שלה.
אם מחושבים הנגזרות הפרטיות של האנטרופיה הצולבת ביחס לכל המשקלים וכל הטיות, מתקבל 'שיפוע' שמחושב עבור תמונה, תווית וערך נוכחי נתונים של משקלים וטיות. זכרו שיש לנו מיליוני משקולות והטיות, ולכן חישוב ההדרגתיות נשמע כמו עבודה רבה. למרבה המזל, Tensorflow עושה את זה בשבילנו. המאפיין המתמטי של שיפוע הוא שהוא מצביע "מעלה". מכיוון שאנחנו רוצים להגיע למקום שבו ה-cross-entropy הוא נמוך, אנחנו הולכים בכיוון ההפוך. אנחנו מעדכנים את המשקל וההטיות לפי חלק מההדרגתיות. לאחר מכן אנחנו מבצעים את אותו הדבר שוב ושוב באמצעות הקבוצות הבאות של תמונות ותוויות לאימון, בלולאת אימון. יש לקוות שהיא תגיע לנקודה שבה האנטרופיה ההצטלבותית תהיה מינימלית, אבל אין ערובה שהמינימום הזה יהיה ייחודי.
מיני-אצווה ומומנטום
אפשר לחשב את שיפוע הגרדיאנטים בתמונה לדוגמה אחת בלבד ולעדכן את המשקלים וההטיות באופן מיידי, אבל ביצוע הפעולה הזו על קבוצה של, למשל, 128 תמונות נותן שיפוע שמייצג טוב יותר את האילוצים שהוטלו על ידי תמונות לדוגמה שונות, ולכן סביר להניח שהוא יגיע לפתרון מהר יותר. גודל המיני-אצווה הוא פרמטר שניתן לשנות.
לשיטה הזו, שנקראת לפעמים "ירידה הדרגתית סטוכסטית", יש יתרון נוסף, פרגמטי יותר: עבודה עם אצוות פירושה גם עבודה עם מטריצות גדולות יותר, ובדרך כלל קל יותר לבצע אופטימיזציה שלהן במעבדי GPU ובמעבדי TPU.
עם זאת, ההתכנסות עדיין יכולה להיות קצת כאוטית והיא יכולה אפילו לעצור אם הווקטור ההדרגתי הוא אפסים. האם המשמעות היא שמצאנו ערך מינימלי? לא תמיד. רכיב שיפוע יכול להיות אפס בערכים מינימלי או מקסימלי. אם וקטור שיפוע מכיל מיליוני רכיבים, והם כולם אפס, הסבירות שכל אפס תואם לנקודת מינימום ואף אחת מהן לא תואמת לנקודת מקסימום היא קטנה למדי. במרחב עם הרבה מאפיינים, נקודות רכס הן נפוצות למדי, ואנחנו לא רוצים לעצור בהן.
איור: נקודת אוכף. ההדרגתיות היא 0 אך הוא לא ערך מינימלי בכל הכיוונים. (קרדיט תמונה Wikimedia: By Nicoguaro - Own work, CC BY 3.0)
הפתרון הוא להוסיף קצת מומנטום לאלגוריתם האופטימיזציה כדי שיוכל לעבור נקודות רכס בלי לעצור.
מילון מונחים
batch או mini-batch: תמיד מתבצע אימון על קבוצות של נתוני אימון ותוויתות. כך אפשר לעזור לאלגוריתם להגיע להסכמה. המאפיין 'קבוצה' הוא בדרך כלל המאפיין הראשון של טינסורים של נתונים. לדוגמה, Tensor של צורה [100, 192, 192, 3] מכיל 100 תמונות של 192x192 פיקסלים עם שלושה ערכים לכל פיקסל (RGB).
הפסד באנטרופיה אחת: פונקציית הפסד מיוחדת שמשמשת לעיתים קרובות במסווגים.
שכבה צפופה: שכבה של נוירונים שבה כל נוירונים מחובר לכל נוירונים בשכבה הקודמת.
מאפיינים: הקלט של רשת נוירונים נקרא לפעמים 'מאפיינים'. היכולת להבין אילו חלקים של מערך נתונים (או שילובי חלקים) להזין ברשת נוירונים כדי לקבל תחזיות טובות נקראת 'הנדסת תכונות'.
labels: שם נוסף ל'כיתות' או לתשובות נכונות בבעיה של סיווג בפיקוח
שיעור הלמידה: חלק מהמדריגנט שבו משקולות ונטיות מועדכנים בכל חזרה של לולאת האימון.
logits: הפלט של שכבת נוירונים לפני הפעלת פונקציית ההפעלה נקראות 'logits'. המונח נגזר מ'פונקציה לוגיסטית', שנקראת גם 'פונקציית סיגמואיד', שהייתה בעבר פונקציית ההפעלה הפופולרית ביותר. השם 'פלטות של נוירונים לפני פונקציה לוגיסטית' קוצר ל'לוגיט'.
loss: פונקציית השגיאה שמשויכת בין הפלט של רשת העצבים לתשובות הנכונות
neuron: מחשב את הסכום המשוקלל של הקלט, מוסיף הטיה ומזין את התוצאה באמצעות פונקציית הפעלה.
קידוד one-hot: הכיתה 3 מתוך 5 מקודדת כוקטור של 5 רכיבים, כולם אפס מלבד הרכיב השלישי שהוא 1.
relu: יחידה לינארית מתוקנת. פונקציית הפעלה פופולרית לנוירונים.
sigmoid: פונקציית הפעלה נוספת שהייתה פופולרית בעבר ועדיין שימושית במקרים מיוחדים.
softmax: פונקציית הפעלה מיוחדת שפועלת על וקטור, מגדילה את ההפרש בין הרכיב הגדול ביותר לבין כל האחרים, וגם מנרמלת את הווקטור לסכום של 1 כדי שאפשר יהיה לפרש אותו כווקטור של הסתברויות. משמש כשלב האחרון במסווגים.
tensor: 'tensor' דומה למטריצה, אבל עם מספר שרירותי של מימדים. טנזור חד-ממדי הוא וקטור. מפריד דו-ממדי הוא מטריצה. לאחר מכן אפשר ליצור טינסורים עם 3, 4, 5 או יותר מאפיינים.
7. למידת העברה
לבעיה בסיווג תמונות, סביר להניח ששכבות צפופות לא יספיקו. עלינו ללמוד על שכבות קונבולוציה ועל הדרכים הרבות שבהן ניתן לארגן אותן.
אבל אנחנו יכולים גם ליצור קיצור דרך. יש רשתות נוירונים מלאכותיות (CNN) שהוכשרו במלואן וזמינות להורדה. אפשר לחתוך את השכבה האחרונה שלהם, 'ראש הסיווג של softmax', ולהחליף אותה בשכבה משלכם. כל ההטיות והמשקלים שאומנו נשארים כפי שהם, רק מאמנים מחדש את שכבת ה-softmax שמוסיפים. הטכניקה הזו נקראת 'למידת העברה', ומדהים לגלות שהיא פועלת כל עוד מערך הנתונים שבו רשת העצבים הוכשרה מראש הוא 'קרוב מספיק' למערך הנתונים שלכם.
שיחה קולית
פותחים את ה-notebook הבא, מריצים את התאים (Shift-ENTER) ופועלים לפי ההוראות בכל מקום שבו מופיעה התווית 'נדרשת עבודה'.
Keras Flowers transfer learning (playground).ipynb
מידע נוסף
למידה של ההעברה מאפשרת לכם להפיק תועלת מארכיטקטורות מתקדמות של רשתות נוירונים מלאכותיות שפותחו על ידי חוקרים מובילים, וגם מאימון מראש של מערך נתונים עצום של תמונות. במקרה שלנו נעביר את הלמידה מרשת שאמנתה ב-ImageNet, מסד נתונים של תמונות שמכילות הרבה צמחים וסצנות מבחוץ, שקרובות מספיק לפרחים.
איור: שימוש ברשת נוירונים קוונטית מורכבת שכבר הוכשרה, כקופסה שחורה, והכשרה מחדש של 'ראש' הסיווג בלבד. זאת למידת העברה. בהמשך נראה איך פועלות ההתאמות המורכבות האלה של שכבות נוירונים קונבולוציוניים. בינתיים, זו בעיה של מישהו אחר.
העברת הלמידה ב-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%.
פתרון
כאן מופיע מסמך הפתרון. אפשר להשתמש בו אם נתקעתם.
Keras Flowers transfer learning (solution).ipynb
אילו נושאים דיברנו?
- 🤔 איך לכתוב סיווג ב-Keras
- 🤓 שהוגדרו עם שכבה אחרונה של softmax, ואובדן ב-Cross-entropy
- 😈 להעביר את הלמידה
- 🤔 אימון המודל הראשון
- 🧐 מעקב אחרי ההפסד והדיוק שלו במהלך האימון
כדאי להקדיש כמה רגעים כדי לעבור על רשימת המשימות הבאה בראש שקט.
8. [מידע] רשתות נוירונים מלאכותיות
בקצרה
אם כל המונחים המודגשים בפסקה הבאה כבר מוכרים לכם, אפשר לעבור לתרגיל הבא. אם אתם רק מתחילים להשתמש ברשתות נוירונים קונבולוציוניות, כדאי להמשיך לקרוא.
איור: סינון תמונה באמצעות שני מסננים רצופים שמכילים 48 משקלים ללמידה בכל אחד מהם (4x4x3=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'])
מבוא לרשתות נוירונים מלאכותיות (CNN)
בשכבה של רשת קונבולוציה, "נוירו" אחד מבצע סכום משוקלל של הפיקסלים שנמצאים מעליו, באזור קטן בלבד של התמונה. הוא מוסיף הטיה ומזין את הסכום באמצעות פונקציית הפעלה, בדיוק כמו שנירון בשכבה רגילה וצפיפות. לאחר מכן הפעולה הזו חוזרת על עצמה בכל התמונה תוך שימוש באותם משקלים. חשוב לזכור שבשכבות צפופות, לכל נוירון יש משקלים משלו. כאן, "תיקון" אחד של משקולות מחליק על התמונה בשני הכיוונים ("קובולציה"). הפלט מכיל מספר ערכים שזהה למספר הפיקסלים בתמונה (עם זאת, נדרש תוספת קצת מרווח בשוליים). זוהי פעולת סינון, שמשתמשת במסנן של 48 משקולות (4x4x3).
עם זאת, 48 משקולות לא מספיקות. כדי להוסיף עוד דרגות חופש, חוזרים על אותה פעולה עם קבוצה חדשה של משקלים. כך נוצרת קבוצה חדשה של תוצאות מסנן. נקרא לזה 'ערוץ' של פלטים באמצעות אנלוגיה עם ערוצי R,G,B בתמונת הקלט.
אפשר לסכם את שתי קבוצות המשקלים (או יותר) כטנסור אחד על ידי הוספת מאפיין חדש. מתקבלת הצורה הגנרית של חיישן המשקולות לשכבה קונבולוציה. מאחר שמספר ערוצי הקלט והפלט הם פרמטרים, אנחנו יכולים להתחיל ליצור מקבצים ולשרשר שכבות של שכבות קונבולוציה.
איור: רשת נוירונים מלאכותית (CNN) ממירה 'קוביות' של נתונים ל'קוביות' אחרות של נתונים.
קיפולים (קונבולציות) עם צעד, יצירת מאגרים מקסימליים
ביצוע המצטברים עם צעד של 2 או 3 מאפשר גם לכווץ את קוביית הנתונים שמתקבלת בממדים האופקיים שלה. אפשר לעשות זאת ב-2 דרכים נפוצות:
- עיבוד נתונים סטריידי (Strided convolution): מסנן הזזה כמו למעלה, אבל עם צעד (stride) גדול מ-1
- קיבוץ מקסימלי: חלון הזזה שמבצע את הפעולה MAX (בדרך כלל בתיקונים בגודל 2x2, שחוזר על עצמו כל 2 פיקסלים)
איור: החלקה של חלון החישוב ב-3 פיקסלים מובילה לפחות ערכי פלט. קונבולוציות חדות או צבירה מקסימלית (מקסימום בחלון של 2x2 עם החלקה בצעד של 2) הן דרך לכווץ את קוביית הנתונים במידות האופקיות.
מסווג מסתובב
לבסוף, אנחנו מחברים ראש סיווג על ידי שטחת את קוביית הנתונים האחרונה ומעבירים אותה דרך שכבה צפופה שמופעלת על ידי softmax. סיווג קונבולוציוני טיפוסי יכול להיראות כך:
איור: סיווג תמונות באמצעות שכבות convolutional ו-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. ה-CNN בהתאמה אישית
הדרכה מעשית
נלמד איך יוצרים ומאמנים רשת נוירונים מלאכותית (CNN) מאפס. השימוש ב-TPU יאפשר לנו לבצע איטרציה במהירות רבה. פותחים את ה-notebook הבא, מריצים את התאים (Shift-ENTER) ופועלים לפי ההוראות בכל מקום שבו מופיעה התווית 'נדרשת עבודה'.
Keras_Flowers_TPU (playground).ipynb
המטרה היא לשפר את רמת הדיוק של 75% של מודל למידת ההעברה. למודל הזה היה יתרון, כי הוא עבר אימון מראש על מערך נתונים של מיליוני תמונות, בעוד שיש לנו כאן רק 3,670 תמונות. יש לך אפשרות להתאים אותו לפחות?
מידע נוסף
כמה שכבות, מה הגודל?
בחירת גודל השכבות היא יותר אומנות מאשר מדע. צריך למצוא את האיזון הנכון בין מעט מדי פרמטרים לבין יותר מדי פרמטרים (משקלים ונטיות). אם יש מעט מדי משקולות, רשת העצבים לא יכולה לייצג את המורכבות של צורות הפרחים. אם יש יותר מדי תמונות, המערכת עלולה להיכנס למצב של 'התאמה יתר', כלומר התמקדות בתמונות האימון ואי יכולת להכליל. יש הרבה פרמטרים, לכן גם אימון המודל יהיה איטי. ב-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. הדרך הזו לבניית 'הראש' של הסיווג לא כוללת עלות של משקולות. ב-Keras, התחביר הוא tf.keras.layers.GlobalAveragePooling2D().
פתרון
כאן מופיע מסמך הפתרון. אפשר להשתמש בו אם נתקעתם.
Keras_Flowers_TPU (solution).ipynb
אילו נושאים דיברנו?
- 🤔 הפעלה עם שכבות קונבולוציה
- 🤓 ניסיתי max pooling, strides, global average pooling ועוד…
- 😀 ביצענו חזרה על מודלים בעולם האמיתי במהירות, באמצעות TPU
כדאי להקדיש כמה רגעים כדי לעבור על רשימת המשימות הבאה בראש שקט.
10. [INFO] ארכיטקטורות נוירוניות קונבולוציוניות מודרניות
בקצרה
איור: 'מודול' קונבולוציה. מה הכי טוב בשלב זה? שכבת max-pool ואחריה שכבת convolutin 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)
טריקים זולים נוספים
פילטרים קטנים ביחס גובה-רוחב של 3x3
באיור הזה מוצגת התוצאה של שני מסננים רצופים בגודל 3x3. נסו לבדוק אילו נקודות נתונים תרמו לתוצאה: שני המסננים הבאים ברצף בגודל 3x3 מחשבים שילוב כלשהו של אזור בגודל 5x5. זה לא בדיוק אותו השילוב שמסנן בגודל 5x5 יחושב, אבל כדאי לנסות כי שני מסננים עוקבים מסוג 3x3 זולים יותר ממסנן יחיד בגודל 5x5.
קונבולוציות 1x1?
במונחים מתמטיים, קונבולציה של 1x1 היא כפל בקבוע קבוע, ולא קונספט שימושי במיוחד. עם זאת, חשוב לזכור שברשתות נוירונליות קונבולוציוניות, המסנן מיושם על קוביית נתונים ולא רק על תמונה דו-ממדית. לכן, מסנן '1x1' מחשב סכום משוקלל של עמודת נתונים בגודל 1x1 (ראו איור), וככל שגוררים אותו על פני הנתונים, מתקבל שילוב לינארי של הערוצים של הקלט. זה באמת שימושי. אם נחשוב על הערוצים כתוצאות של פעולות סינון נפרדות, לדוגמה: מסנן ל"אוזניים מחודדות", מסנן נוסף ל"שפמים" ומסנן נוסף ל"עיניים חסומות", השכבה "1x1" תחשב שילובים ליניאריים אפשריים בין התכונות האלה. סינון כזה יכול להיות שימושי לחיפוש "חתול". בנוסף, בשכבות בגודל 1x1 נעשה שימוש בפחות משקולות.
11. סקווֶנט
דרך פשוטה לשלב את הרעיונות האלה הוצגה במאמר 'Squeezenet'. המחברים מציעים תכנון פשוט מאוד של מודול קונבולוציה, באמצעות שכבות קונבולוציה בגודל 1x1 ו-3x3 בלבד.
איור: ארכיטקטורת מעיכה שמבוססת על 'מודולים של אש'. הם משתמשים בשכבה 1x1 שמצמצמת את הנתונים הנכנסים במאפיין האנכי, ואחריה בשתי שכבות עיבוד מקבילות של 1x1 ו-3x3 שמרחיבות שוב את עומק הנתונים.
שיחה קולית
ממשיכים ביומן הקודם ויוצרים רשת נוירונים מלאכותית מבוססת-קונטור בהשראת squeezenet. תצטרכו לשנות את קוד המודל ל'סגנון פונקציונלי' של Keras.
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%.
דברים שכדאי לנסות
מתחילים בשכבת convolve אחת, ואז ממשיכים עם "fire_modules
", לסירוגין עם שכבות MaxPooling2D(pool_size=2)
. אפשר להתנסות עם 2 עד 4 שכבות max pooling ברשת, וגם עם 1, 2 או 3 מודולים של הפעלה רצופות בין שכבות ה-max pooling.
במודולים של אש, הפרמטר 'squeeze' צריך להיות בדרך כלל קטן מהפרמטר '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 כי מערך הנתונים שלנו קטן. בינתיים, אפשר להתעלם מהפרט הזה.
הגדלת נתונים
תקבל עוד כמה נקודות אחוזים על ידי הרחבת הנתונים באמצעות טרנספורמציות קלות כמו שינויי רוויה לשמאליים:
קל מאוד לעשות זאת ב-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 תקופות של זמן מערכת.
פתרון
כאן מופיע מסמך הפתרון. אפשר להשתמש בו אם נתקעתם.
Keras_Flowers_TPU_squeezenet.ipynb
מה עסקנו בו
- 🤔 מודלים מסוג 'סגנון פונקציונלי' של Keras
- 🤓 ארכיטקטורת מעיכה
- 🤓 הגדלת נתונים באמצעות tf.data.datset
כדאי להקדיש כמה רגעים כדי לעבור על רשימת המשימות הבאה בראש שקט.
12. Xception עם כוונון עדין
קיפולים (קונבולציות) שניתנות להפרדה
דרך אחרת ליישום שכבות קונבולוציה צברה לאחרונה פופולריות: קונבולוציות עם הפרדה בעומק. אני יודע, זה שם ארוך, אבל הרעיון פשוט למדי. הם מוטמעים ב-Tensorflow וב-Keras בתור tf.keras.layers.SeparableConv2D
.
קונבולציה ניתנת להפרדה מפעילה גם מסנן על התמונה, אבל היא משתמשת בקבוצה נפרדת של משקלים לכל ערוץ של תמונת הקלט. לאחר מכן, מתבצעת 'ערבול (convolution) של 1x1', סדרה של מכפלות סקלריות שמניבות סכום משוקלל של הערוצים המסוננים. בכל פעם מחושב שילוב משוקלל חדש של הערוצים, לפי הצורך.
איור: קונבולציות נפרדות. שלב 1: קונבולציות עם מסנן נפרד לכל ערוץ. שלב 2: שילובים לינאריים של ערוצים. חוזרים על הפעולה עם קבוצת משקולות חדשה עד שמגיעים למספר הרצוי של ערוצי הפלט. אפשר לחזור על שלב 1 גם על שלב 1, עם משקלים חדשים בכל פעם, אבל בפועל זה קורה רק לעיתים רחוקות.
קונבולציות הניתנות להפרדה משמשות בארכיטקטורות הרשתות הקונבולוציות האחרונות: MobileNetV2, Xception, EfficientNet. דרך אגב, MobileNetV2 היא השיטה שבה השתמשתם בעבר כדי להעביר את הלמידה.
הם זולים יותר מקונבולוציות רגילות, והם הוכיחו שהם יעילים באותה מידה בפועל. זהו ספירת המשקלים של הדוגמה שמוצגת למעלה:
שכבה מתקפלת: 4 x 4 x 3 x 5 = 240
שכבת convolutin נפרדת: 4 x 4 x 3 + 3 x 5 = 48 + 15 = 63
נותר לקורא לחשב את מספר המכפלות הנדרשות כדי להחיל כל סגנון של שכבות נוירונים קונבולוציוניים באותו אופן. עיבוד נתונים באמצעות convolve נפרד קטן יותר ויעיל יותר מבחינה חישובית.
שיחה קולית
מתחילים מחדש מהמחברות של 'transfer learning', אבל הפעם בוחרים ב-Xception בתור המודל שעבר אימון מראש. ב-Xception נעשה שימוש רק בהתנזקות ניתנות להפרדה. צריך לאפשר אימון של כל המשקולות. אנחנו נכוונן את המשקולות לפני אימון על הנתונים שלנו במקום להשתמש ככה בשכבות שאומנו מראש.
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
כדי לקבל תוצאות טובות בזמן כוונון של המודל, צריך לשים לב לקצב הלמידה ולהשתמש בלוח זמנים של קצב הלמידה עם תקופת הרצה. כך:
התחלה עם קצב למידה רגיל תפריע למשקלים המאומנים מראש של המודל. כשמתחילים בהדרגה, הנתונים נשמרים עד שהמודל יתבסס עליהם ויוכל לשנות אותם בצורה הגיונית. אחרי התקופה של העלייה, אפשר להמשיך בקצב למידה קבוע או בקצב למידה שפתאום יורד באופן מעריכי.
ב-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])
פתרון
כאן מופיע מסמך הפתרון. אפשר להשתמש בו אם נתקעתם.
07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb
מה עסקנו בו
- 🤔 הסרה בקישורי עומק
- 🤓 לוחות זמנים לקצב הלמידה
- 😈. כוונון עדין של מודל שעבר אימון מראש.
כדאי להקדיש כמה רגעים כדי לעבור על רשימת המשימות הבאה בראש שקט.
13. מעולה!
יצרתם את רשת העצבים הקוונטית המודרנית הראשונה שלכם ואומנתם אותה לרמת דיוק של יותר מ-90%, תוך ביצוע חזרות על פעולות אימון עוקבות תוך דקות ספורות בלבד, בזכות TPUs.
מעבדי TPU בפועל
מעבדי TPU ומעבדי GPU זמינים ב-Vertex AI של Google Cloud:
לסיום, אנחנו אוהבים לקבל משוב. נשמח לדעת אם משהו השתבש בשיעור ה-Lab הזה או אם לדעתכם צריך לשפר אותו. אפשר לשלוח משוב דרך 'בעיות ב-GitHub' [feedback link].
|