צינורות נתונים במהירות TPU: tf.data.Dataset ו-TFRecords

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

TPUs הם מהירים מאוד. מקור הנתונים לאימון צריך לעמוד בקצב האימון. בשיעור ה-Lab הזה תלמדו איך לטעון נתונים מ-GCS באמצעות tf.data.Dataset API כדי להזין את ה-TPU.

שיעור ה-Lab הזה הוא חלק 1 בסדרה "Keras on TPU". אפשר לעשות את זה בסדר הבא או בנפרד.

ca8cc21f6838eccc.png

מה תלמדו

  • להשתמש ב-API של tf.data.Dataset כדי לטעון נתוני אימון.
  • להשתמש בפורמט TFRecord כדי לטעון נתוני אימון ביעילות מ-GCS

משוב

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

2. מדריך למתחילים ב-Google Colaboratory

הסדנה הזו מתבססת על Google Collaboratory ואין צורך לבצע הגדרות כלשהן. Colaboratory היא פלטפורמת notebook אונליין למטרות חינוכיות. התוכנית כוללת הכשרה בחינם לגבי מעבדים (CPU), מעבדי GPU ו-TPU.

688858c21e3beff2.png

אתם יכולים לפתוח את מסמך ה-notebook לדוגמה הזה ולעיין בכמה תאים כדי להכיר את Colaboratory.

c3df49e90e5a654f.png Welcome to Colab.ipynb

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

8832c6208c99687d.png

בתפריט Colab, בוחרים באפשרות סביבת זמן הריצה > שינוי הסוג של סביבת זמן הריצה ואז בוחרים באפשרות TPU. בשיעור ה-Lab הזה תלמדו איך להשתמש ב-TPU (יחידת עיבוד טרנספורמציות טילר) חזקה עם תמיכה באימון שמואץ בחומרה. החיבור לסביבת זמן הריצה יתבצע באופן אוטומטי בביצוע הראשון. אפשר גם ללחוץ על הלחצן 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), סימולציות פיזיות וכו'. לעומת זאת, ב-TPUs משולב מעבד וקטור קלאסי עם יחידה ייעודית להכפלת מטריצות, והם מצטיינים בכל משימה שבה עיקר העבודה היא הכפלות מטריצות גדולות, כמו רשתות נוירונליות.

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 ב-TPUs.‏ bfloat16 הוא float32 קטועה עם אותם ביטויים של מעריך ורשת כמו float32. העובדה הזו, בשילוב עם העובדה ש-TPUs מחשבים כפל מטריצות ברמת דיוק משולבת עם קלט 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, יהיה צורך ב-16,000 "ליבות" (128x128=16,000) שיהיו זמינות, וזה בדרך כלל לא אפשרי. למעבדי ה-GPU הגדולים ביותר יש כ-4,000 ליבות. מצד שני, TPU משתמש במינימום הנדרש של החומרה עבור יחידות המחשוב ב-MXU: רק bfloat16 x bfloat16 => float32 מכפילים, שום דבר אחר. הם כל כך קטנים ש-TPU יכול ליישם 16K מהם ב-MXU של 128x128 ולעבד את הכפלת המטריצה הזו בפעם אחת.

f1b283fc45966717.gif

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

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

Cloud TPU

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

dfce5522ed644ece.png

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

מארזי TPU

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

2ec1e0d341e7fc34.jpeg

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

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

d97b9cc5d40fdb1d.gif

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

התוכנה

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

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

במעבדת הקוד הזו נשתמש ב-Keras API. ב-Keras, האצווה שציינתם היא גודל האצווה הגלובלי של כל ה-TPU. הקבוצות (batches) יתחלקו באופן אוטומטי ל-8 וייבצעו ב-8 הליבות של ה-TPU.

da534407825f01e3.png

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

מה קורה מאחורי הקלעים: XLA

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

edce61112cd57972.png

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

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

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

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 env. var ב-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.
  • מעבדי 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?

אפשר להשתמש ב-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

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

מידע בסיסי על 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())

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

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

הפתרון

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

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

מה עסקנו בו

  • 🤔 tf.data.Dataset.list_files
  • 🤔 tf.data.Dataset.map
  • 🤔 מערכי נתונים של צמדי מידע (tuples)
  • 😀 חזרה על מערכי נתונים

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

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

מאיצי החומרה של יחידות עיבוד הנתונים (TPU) שבהם נשתמש במעבדה הזו הם מהירים מאוד. בדרך כלל האתגר הוא לספק להם נתונים מהר מספיק כדי שהם יהיו עסוקים. 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. מעולה!

עכשיו אפשר להזין נתונים ב-TPU. אפשר להמשיך לשיעור ה-Lab הבא

מעבדי TPU בפועל

מעבדי TPU ו-GPU זמינים ב-Cloud AI Platform:

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

HR.png

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