צפה בהערות מרכזיות, הפעלות מוצר, סדנאות ועוד מ- Google I / O ראה רשימת השמעה

tf.data: בנה צינורות קלט של TensorFlow

צפה ב- TensorFlow.org הפעל בגוגל קולאב צפה במקור ב- GitHub הורד מחברת

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

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

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

  • מקור נתונים בונה Dataset נתונים מנתונים המאוחסנים בזיכרון או בקובץ אחד או יותר.

  • שינוי נתונים בונה מערך נתונים מאובייקטtf.data.Dataset אחד או יותר.

import tensorflow as tf
import pathlib
import os
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

np.set_printoptions(precision=4)

מכניקה בסיסית

כדי ליצור צינור קלט, עליך להתחיל עם מקור נתונים. לדוגמה, כדי לבנות Dataset נתונים מנתונים בזיכרון, אתה יכול להשתמש ב- tf.data.Dataset.from_tensors() או tf.data.Dataset.from_tensor_slices() . לחלופין, אם נתוני הקלט שלך מאוחסנים בקובץ בפורמט TFRecord המומלץ, תוכל להשתמש ב- tf.data.TFRecordDataset() .

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

אובייקט Dataset הנתונים הוא ניתן להפעלה של פיתון. זה מאפשר לצרוך את האלמנטים שלו באמצעות לולאה for:

dataset = tf.data.Dataset.from_tensor_slices([8, 3, 0, 8, 2, 1])
dataset
<TensorSliceDataset shapes: (), types: tf.int32>
for elem in dataset:
  print(elem.numpy())
8
3
0
8
2
1

או על ידי יצירת מפורש איטרטור של פייתון באמצעות iter וצריכת האלמנטים שלו באמצעות next :

it = iter(dataset)

print(next(it).numpy())
8

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

print(dataset.reduce(0, lambda state, value: state + value).numpy())
22

מבנה מערך הנתונים

מערך נתונים מייצר רצף של אלמנטים , כאשר כל אלמנט הוא אותו מבנה (מקונן) של רכיבים . מרכיבים בודדים של המבנה יכולים להיות מכל סוג המיוצגים על ידי tf.TypeSpec , כולל tf.Tensor , tf.sparse.SparseTensor ,tf.RaggedTensor , tf.TensorArray אוtf.data.Dataset .

מבני הפיתון שבהם ניתן להשתמש כדי לבטא את המבנה (המקונן) של אלמנטים כוללים tuple , dict , NamedTuple ו- OrderedDict . בפרט, list אינה מבנה חוקי לביטוי המבנה של רכיבי מערך הנתונים. הסיבה היא שמשתמשי tf.data מוקדם הרגישו מאוד על list תשומות (למשל עבר tf.data.Dataset.from_tensors ) להיות ארוז באופן אוטומטי כפי tensors ואת list פלטים (ערכים חוזרים למשל של פונקציות המוגדרות על ידי משתמש) להיות כפוי לתוך tuple . כתוצאה מכך, אם תרצה שקלט list יטופל כמבנה, עליך להמיר אותו tuple , ואם תרצה tf.stack list יהיה רכיב יחיד, עליך לארוז אותו במפורש באמצעות tf.stack .

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

dataset1 = tf.data.Dataset.from_tensor_slices(tf.random.uniform([4, 10]))

dataset1.element_spec
TensorSpec(shape=(10,), dtype=tf.float32, name=None)
dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2.element_spec
(TensorSpec(shape=(), dtype=tf.float32, name=None),
 TensorSpec(shape=(100,), dtype=tf.int32, name=None))
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3.element_spec
(TensorSpec(shape=(10,), dtype=tf.float32, name=None),
 (TensorSpec(shape=(), dtype=tf.float32, name=None),
  TensorSpec(shape=(100,), dtype=tf.int32, name=None)))
# Dataset containing a sparse tensor.
dataset4 = tf.data.Dataset.from_tensors(tf.SparseTensor(indices=[[0, 0], [1, 2]], values=[1, 2], dense_shape=[3, 4]))

dataset4.element_spec
SparseTensorSpec(TensorShape([3, 4]), tf.int32)
# Use value_type to see the type of value represented by the element spec
dataset4.element_spec.value_type
tensorflow.python.framework.sparse_tensor.SparseTensor

טרנספורמציות Dataset הנתונים תומכות במערכי נתונים בכל מבנה. בעת שימוש Dataset.map() ו- Dataset.filter() , החלות פונקציה על כל אלמנט, מבנה האלמנט קובע את הארגומנטים של הפונקציה:

dataset1 = tf.data.Dataset.from_tensor_slices(
    tf.random.uniform([4, 10], minval=1, maxval=10, dtype=tf.int32))

dataset1
<TensorSliceDataset shapes: (10,), types: tf.int32>
for z in dataset1:
  print(z.numpy())
[2 7 7 7 5 9 3 3 5 2]
[9 9 7 3 6 2 3 8 7 1]
[2 9 7 1 8 7 8 3 8 2]
[7 5 2 6 9 4 4 1 6 4]
dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2
<TensorSliceDataset shapes: ((), (100,)), types: (tf.float32, tf.int32)>
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3
<ZipDataset shapes: ((10,), ((), (100,))), types: (tf.int32, (tf.float32, tf.int32))>
for a, (b,c) in dataset3:
  print('shapes: {a.shape}, {b.shape}, {c.shape}'.format(a=a, b=b, c=c))
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)

קריאת נתוני קלט

צורכים מערכי NumPy

ראה דוגמאות נוספות לטעינת מערכי NumPy .

אם כל ההתקפים נתוני הקלט שלך בזיכרון, הדרך הפשוטה ביותר ליצור Dataset מהם היא להמיר אותם tf.Tensor חפצים ולהשתמש Dataset.from_tensor_slices() .

train, test = tf.keras.datasets.fashion_mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
32768/29515 [=================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
8192/5148 [===============================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step
images, labels = train
images = images/255

dataset = tf.data.Dataset.from_tensor_slices((images, labels))
dataset
<TensorSliceDataset shapes: ((28, 28), ()), types: (tf.float64, tf.uint8)>

צורכים מחוללי פיתון

מקור נתונים נפוץ נוסף שניתן לבלוע בקלות כ-tf.data.Dataset הוא מחולל הפיתון.

def count(stop):
  i = 0
  while i<stop:
    yield i
    i += 1
for n in count(5):
  print(n)
0
1
2
3
4

Dataset.from_generator ממיר את מחולל הפיתון ל-tf.data.Dataset פונקציונלי לחלוטין.

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

הארגומנט output_types נדרש מכיוון tf.data בונה tf.Graph פנימי, וקצוות הגרפים דורשים tf.dtype .

ds_counter = tf.data.Dataset.from_generator(count, args=[25], output_types=tf.int32, output_shapes = (), )
for count_batch in ds_counter.repeat().batch(10).take(10):
  print(count_batch.numpy())
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]

הטיעון output_shapes אינו נדרש אך מומלץ מאוד מכיוון שפעולות זרימת טנסור רבות אינן תומכות בטנורים בעלי דירוג לא ידוע. אם אורך ציר מסוים אינו ידוע או משתנה, להגדיר את זה בתור None של output_shapes .

חשוב גם לציין כי output_shapes ו- output_types פועלים לפי כללי קינון כמו שיטות מערך אחרות.

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

def gen_series():
  i = 0
  while True:
    size = np.random.randint(0, 10)
    yield i, np.random.normal(size=(size,))
    i += 1
for i, series in gen_series():
  print(i, ":", str(series))
  if i > 5:
    break
0 : [0.2357]
1 : [-0.4635  0.0882 -0.7401 -1.2436 -0.1392  1.8694 -2.2567  1.5493 -1.0368]
2 : []
3 : []
4 : [1.1482 1.0136]
5 : [ 0.7923 -2.2942  0.4162  1.5056  1.6008  0.1861]
6 : [ 0.7311  0.9217  1.3697 -1.0795  1.0586 -1.0768]

הפלט הראשון הוא int32 והשני הוא float32 .

הפריט הראשון הוא סקלר, צורה () , והשני הוא וקטור באורך, צורה לא ידוע (None,)

ds_series = tf.data.Dataset.from_generator(
    gen_series, 
    output_types=(tf.int32, tf.float32), 
    output_shapes=((), (None,)))

ds_series
<FlatMapDataset shapes: ((), (None,)), types: (tf.int32, tf.float32)>

עכשיו זה יכול לשמש כמוtf.data.Dataset רגיל. שים לב שכאשר אצווה מערך נתונים עם צורה משתנה, עליך להשתמש ב- Dataset.padded_batch .

ds_series_batch = ds_series.shuffle(20).padded_batch(10)

ids, sequence_batch = next(iter(ds_series_batch))
print(ids.numpy())
print()
print(sequence_batch.numpy())
[17 11 18  8 23  2  6 13 26 28]

[[ 1.0784e+00  6.2397e-01  3.3750e-01 -2.1123e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [ 1.3756e-01 -1.5717e+00 -8.0335e-01  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-9.1093e-01  6.3951e-01  4.9384e-04  2.0273e+00 -3.6473e-01 -3.6264e-02
  -7.3862e-01 -5.3504e-01]
 [ 1.6893e-01  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-1.1584e+00 -7.9125e-01  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-1.7383e-01 -2.5935e-01  4.8755e-01  1.5578e+00 -4.7534e-01  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-4.0072e-01 -7.4969e-01 -1.1954e+00  9.0354e-02  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-8.6638e-02 -1.4680e+00  1.3155e+00  1.1772e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-1.1352e-01 -1.1264e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-1.2858e+00  7.7001e-02 -1.7588e-01  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]]

לדוגמא מציאותית יותר, נסה לעטוף את preprocessing.image.ImageDataGenerator כ-tf.data.Dataset .

הורד תחילה את הנתונים:

flowers = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz
228818944/228813984 [==============================] - 1s 0us/step

צור את התמונה. image.ImageDataGenerator

img_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255, rotation_range=20)
images, labels = next(img_gen.flow_from_directory(flowers))
Found 3670 images belonging to 5 classes.
print(images.dtype, images.shape)
print(labels.dtype, labels.shape)
float32 (32, 256, 256, 3)
float32 (32, 5)
ds = tf.data.Dataset.from_generator(
    lambda: img_gen.flow_from_directory(flowers), 
    output_types=(tf.float32, tf.float32), 
    output_shapes=([32,256,256,3], [32,5])
)

ds.element_spec
(TensorSpec(shape=(32, 256, 256, 3), dtype=tf.float32, name=None),
 TensorSpec(shape=(32, 5), dtype=tf.float32, name=None))
for images, label in ds.take(1):
  print('images.shape: ', images.shape)
  print('labels.shape: ', labels.shape)
Found 3670 images belonging to 5 classes.
images.shape:  (32, 256, 256, 3)
labels.shape:  (32, 5)

צריכת נתוני TFRecord

ראה טעינת TFRecords לדוגמא מקצה לקצה.

ממשק ה- API של tf.data תומך במגוון פורמטים של קבצים, כך שתוכלו לעבד מערכי נתונים גדולים שאינם מתאימים לזיכרון. לדוגמא, פורמט הקובץ TFRecord הוא פורמט בינארי פשוט המכוון לרשומות בו משתמשים ביישומי TensorFlow רבים לצורך נתוני אימון. המחלקה tf.data.TFRecordDataset מאפשרת לך להזרים את התוכן של קובץ TFRecord אחד או יותר כחלק מצינור קלט.

הנה דוגמה לשימוש בקובץ הבדיקה מתמרורי הרחוב הצרפתיים (FSNS).

# Creates a dataset that reads all of the examples from two files.
fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001
7905280/7904079 [==============================] - 0s 0us/step

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

dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>

פרויקטים רבים של TensorFlow משתמשים ב- tf.train.Example בסידרה. רשומות לדוגמא בקבצי TFRecord שלהם. צריך לפענח את אלה לפני שניתן יהיה לבדוק אותם:

raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())

parsed.features.feature['image/text']
bytes_list {
  value: "Rue Perreyon"
}

צריכת נתוני טקסט

ראה טעינת טקסט לקבלת דוגמה מקצה לקצה.

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

directory_url = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
file_names = ['cowper.txt', 'derby.txt', 'butler.txt']

file_paths = [
    tf.keras.utils.get_file(file_name, directory_url + file_name)
    for file_name in file_names
]
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/cowper.txt
819200/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step
dataset = tf.data.TextLineDataset(file_paths)

להלן השורות הראשונות בקובץ הראשון:

for line in dataset.take(5):
  print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b'His wrath pernicious, who ten thousand woes'
b"Caused to Achaia's host, sent many a soul"
b'Illustrious into Ades premature,'
b'And Heroes gave (so stood the will of Jove)'

כדי להחליף קווים בין קבצים השתמש ב- Dataset.interleave . זה מקל על דשדוש קבצים יחד. להלן השורה הראשונה, השנייה והשלישית מכל תרגום:

files_ds = tf.data.Dataset.from_tensor_slices(file_paths)
lines_ds = files_ds.interleave(tf.data.TextLineDataset, cycle_length=3)

for i, line in enumerate(lines_ds.take(9)):
  if i % 3 == 0:
    print()
  print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b"\xef\xbb\xbfOf Peleus' son, Achilles, sing, O Muse,"
b'\xef\xbb\xbfSing, O goddess, the anger of Achilles son of Peleus, that brought'

b'His wrath pernicious, who ten thousand woes'
b'The vengeance, deep and deadly; whence to Greece'
b'countless ills upon the Achaeans. Many a brave soul did it send'

b"Caused to Achaia's host, sent many a soul"
b'Unnumbered ills arose; which many a soul'
b'hurrying down to Hades, and many a hero did it yield a prey to dogs and'

כברירת מחדל, TextLineDataset מניב כל שורה של כל קובץ, מה שלא יכול להיות רצוי, למשל, אם הקובץ מתחיל בשורת כותרת, או מכיל הערות. ניתן להסיר שורות אלה באמצעות Dataset.skip() או Dataset.filter() . כאן, אתה מדלג על השורה הראשונה, ואז מסנן כדי למצוא רק ניצולים.

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/train.csv
32768/30874 [===============================] - 0s 0us/step
for line in titanic_lines.take(10):
  print(line.numpy())
b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone'
b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n'
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y'
b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
def survived(line):
  return tf.not_equal(tf.strings.substr(line, 0, 1), "0")

survivors = titanic_lines.skip(1).filter(survived)
for line in survivors.take(10):
  print(line.numpy())
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
b'1,male,28.0,0,0,13.0,Second,unknown,Southampton,y'
b'1,female,28.0,0,0,7.225,Third,unknown,Cherbourg,y'
b'1,male,28.0,0,0,35.5,First,A,Southampton,y'
b'1,female,38.0,1,5,31.3875,Third,unknown,Southampton,n'

צורכת נתוני CSV

ראה טעינת קבצי CSV וטענת Pandas DataFrames לקבלת דוגמאות נוספות.

פורמט קובץ CSV הוא פורמט פופולרי לאחסון נתונים טבלאיים בטקסט רגיל.

לדוגמה:

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
df = pd.read_csv(titanic_file)
df.head()

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

titanic_slices = tf.data.Dataset.from_tensor_slices(dict(df))

for feature_batch in titanic_slices.take(1):
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived'          : 0
  'sex'               : b'male'
  'age'               : 22.0
  'n_siblings_spouses': 1
  'parch'             : 0
  'fare'              : 7.25
  'class'             : b'Third'
  'deck'              : b'unknown'
  'embark_town'       : b'Southampton'
  'alone'             : b'n'

גישה מדרגית יותר היא לטעון מהדיסק לפי הצורך.

מודול tf.data מספק שיטות לחילוץ רשומות מקובץ CSV אחד או יותר העומדים בתקן RFC 4180 .

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

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived")
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  print("features:")
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [1 1 1 1]
features:
  'sex'               : [b'female' b'female' b'male' b'female']
  'age'               : [40. 30. 17. 19.]
  'n_siblings_spouses': [0 0 0 0]
  'parch'             : [0 0 2 0]
  'fare'              : [ 13.      12.475  110.8833  26.    ]
  'class'             : [b'Second' b'Third' b'First' b'Second']
  'deck'              : [b'unknown' b'unknown' b'C' b'unknown']
  'embark_town'       : [b'Southampton' b'Southampton' b'Cherbourg' b'Southampton']
  'alone'             : [b'y' b'y' b'n' b'y']

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

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived", select_columns=['class', 'fare', 'survived'])
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [0 0 0 0]
  'fare'              : [ 15.5   55.9    8.05 108.9 ]
  'class'             : [b'Third' b'First' b'Third' b'First']

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

titanic_types  = [tf.int32, tf.string, tf.float32, tf.int32, tf.int32, tf.float32, tf.string, tf.string, tf.string, tf.string] 
dataset = tf.data.experimental.CsvDataset(titanic_file, titanic_types , header=True)

for line in dataset.take(10):
  print([item.numpy() for item in line])
[0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 38.0, 1, 0, 71.2833, b'First', b'C', b'Cherbourg', b'n']
[1, b'female', 26.0, 0, 0, 7.925, b'Third', b'unknown', b'Southampton', b'y']
[1, b'female', 35.0, 1, 0, 53.1, b'First', b'C', b'Southampton', b'n']
[0, b'male', 28.0, 0, 0, 8.4583, b'Third', b'unknown', b'Queenstown', b'y']
[0, b'male', 2.0, 3, 1, 21.075, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 27.0, 0, 2, 11.1333, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 14.0, 1, 0, 30.0708, b'Second', b'unknown', b'Cherbourg', b'n']
[1, b'female', 4.0, 1, 1, 16.7, b'Third', b'G', b'Southampton', b'n']
[0, b'male', 20.0, 0, 0, 8.05, b'Third', b'unknown', b'Southampton', b'y']

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

%%writefile missing.csv
1,2,3,4
,2,3,4
1,,3,4
1,2,,4
1,2,3,
,,,
Writing missing.csv
# Creates a dataset that reads all of the records from two CSV files, each with
# four float columns which may have missing values.

record_defaults = [999,999,999,999]
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults)
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (4,), types: tf.int32>
for line in dataset:
  print(line.numpy())
[1 2 3 4]
[999   2   3   4]
[  1 999   3   4]
[  1   2 999   4]
[  1   2   3 999]
[999 999 999 999]

כברירת מחדל, CsvDataset מניב כל עמודה של כל שורה בקובץ, מה שלא יכול להיות רצוי, למשל אם הקובץ מתחיל בשורת כותרת שיש להתעלם ממנה, או אם חלק מהעמודות אינן נדרשות בקלט. שורות ושדות אלה ניתנים להסרה בעזרת header ו- select_cols בהתאמה.

# Creates a dataset that reads all of the records from two CSV files with
# headers, extracting float data from columns 2 and 4.
record_defaults = [999, 999] # Only provide defaults for the selected columns
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults, select_cols=[1, 3])
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (2,), types: tf.int32>
for line in dataset:
  print(line.numpy())
[2 4]
[2 4]
[999   4]
[2 4]
[  2 999]
[999 999]

צורכת קבוצות קבצים

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

flowers_root = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
flowers_root = pathlib.Path(flowers_root)

ספריית הבסיס מכילה ספריה לכל מחלקה:

for item in flowers_root.glob("*"):
  print(item.name)
sunflowers
daisy
LICENSE.txt
roses
tulips
dandelion

הקבצים בכל ספריית מחלקה הם דוגמאות:

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

for f in list_ds.take(5):
  print(f.numpy())
b'/home/kbuilder/.keras/datasets/flower_photos/roses/3871586333_5a708d5cf4_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/19551343954_83bb52f310_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/13289268363_b9337d751e.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/3253243865_435c1f2c2b_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/22244161124_53e457bb66_n.jpg'

קרא את הנתונים באמצעות הפונקציהtf.io.read_file וחלץ את התווית מהנתיב,tf.io.read_file זוגות (image, label) :

def process_path(file_path):
  label = tf.strings.split(file_path, os.sep)[-2]
  return tf.io.read_file(file_path), label

labeled_ds = list_ds.map(process_path)
for image_raw, label_text in labeled_ds.take(1):
  print(repr(image_raw.numpy()[:100]))
  print()
  print(label_text.numpy())
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xfe\x00\x0cAppleMark\n\xff\xe2\x05XICC_PROFILE\x00\x01\x01\x00\x00\x05Happl\x02 \x00\x00scnrRGB XYZ \x07\xd3\x00\x07\x00\x01\x00\x00\x00\x00\x00\x00acspAPPL\x00\x00\x00\x00'

b'roses'

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

אצווה פשוט

הצורה הפשוטה ביותר של ערימת מערך n אלמנטים עוקבים של מערך נתונים לאלמנט יחיד. טרנספורמציית Dataset.batch() עושה בדיוק את זה, עם אילוצים זהים tf.stack() , המופעל על כל רכיב האלמנטים: כלומר עבור כל רכיב i , על כל האלמנטים להיות בעלי טנסור של אותה צורה בדיוק.

inc_dataset = tf.data.Dataset.range(100)
dec_dataset = tf.data.Dataset.range(0, -100, -1)
dataset = tf.data.Dataset.zip((inc_dataset, dec_dataset))
batched_dataset = dataset.batch(4)

for batch in batched_dataset.take(4):
  print([arr.numpy() for arr in batch])
[array([0, 1, 2, 3]), array([ 0, -1, -2, -3])]
[array([4, 5, 6, 7]), array([-4, -5, -6, -7])]
[array([ 8,  9, 10, 11]), array([ -8,  -9, -10, -11])]
[array([12, 13, 14, 15]), array([-12, -13, -14, -15])]

בעוד ש tf.data מנסה להפיץ מידע על צורות, הגדרות ברירת המחדל של Dataset.batch גורמות לגודל אצווה לא ידוע מכיוון Dataset.batch האחרונה עשויה לא להיות מלאה. שימו לב ל- None s בצורה:

batched_dataset
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.int64)>

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

batched_dataset = dataset.batch(7, drop_remainder=True)
batched_dataset
<BatchDataset shapes: ((7,), (7,)), types: (tf.int64, tf.int64)>

אצוות טנזרים עם ריפוד

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

dataset = tf.data.Dataset.range(100)
dataset = dataset.map(lambda x: tf.fill([tf.cast(x, tf.int32)], x))
dataset = dataset.padded_batch(4, padded_shapes=(None,))

for batch in dataset.take(2):
  print(batch.numpy())
  print()
[[0 0 0]
 [1 0 0]
 [2 2 0]
 [3 3 3]]

[[4 4 4 4 0 0 0]
 [5 5 5 5 5 0 0]
 [6 6 6 6 6 6 0]
 [7 7 7 7 7 7 7]]

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

הכשרת תהליכי עבודה

עיבוד תקופות מרובות

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

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

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
def plot_batch_sizes(ds):
  batch_sizes = [batch.shape[0] for batch in ds]
  plt.bar(range(len(batch_sizes)), batch_sizes)
  plt.xlabel('Batch number')
  plt.ylabel('Batch size')

החלת Dataset.repeat() ללא ארגומנטים תחזור על הקלט ללא הגבלת זמן.

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

titanic_batches = titanic_lines.repeat(3).batch(128)
plot_batch_sizes(titanic_batches)

png

אם אתה זקוק להפרדה בין עידן ברור, שים את Dataset.batch לפני החזרה:

titanic_batches = titanic_lines.batch(128).repeat(3)

plot_batch_sizes(titanic_batches)

png

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

epochs = 3
dataset = titanic_lines.batch(128)

for epoch in range(epochs):
  for batch in dataset:
    print(batch.shape)
  print("End of epoch: ", epoch)
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  0
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  1
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  2

דשדוש נתוני קלט באופן אקראי

Dataset.shuffle() שומרת על מאגר בגודל קבוע ובוחרת את האלמנט הבא באופן אחיד באופן אקראי מאותו מאגר.

הוסף אינדקס למערך הנתונים כדי שתוכל לראות את ההשפעה:

lines = tf.data.TextLineDataset(titanic_file)
counter = tf.data.experimental.Counter()

dataset = tf.data.Dataset.zip((counter, lines))
dataset = dataset.shuffle(buffer_size=100)
dataset = dataset.batch(20)
dataset
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

מכיוון buffer_size הוא 100, וגודל האצווה הוא 20, האצווה הראשונה אינה מכילה אלמנטים עם אינדקס מעל 120.

n,line_batch = next(iter(dataset))
print(n.numpy())
[ 39   5  30  77  45 104 102  54  31  89  93  29  38  85  60   9  78  76
   1  41]

כמו ב- Dataset.batch , הסדר ביחס ל- Dataset.repeat . Dataset.repeat העניינים.

Dataset.shuffle אינו מסמן את סיומה של תקופה עד Dataset.shuffle הדשדוש ריק. אז דשדוש שהונח לפני חזרה יראה כל אלמנט של תקופה אחת לפני שעבר למשנהו:

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.shuffle(buffer_size=100).batch(10).repeat(2)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(60).take(5):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[618 522 447 528 576 514 626 610 502 404]
[601 577 586 560 490 469 604 275 551 561]
[567 550 423 486 544 457 578 448]
[85 78 32 73 45 99 37 94 13 54]
[101  55   4 109  41  25  80 106   9 113]
shuffle_repeat = [n.numpy().mean() for n, line_batch in shuffled]
plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7fe00c013690>

png

אך חזרה לפני דשדוש מערבבת את גבולות העידן יחד:

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.repeat(2).shuffle(buffer_size=100).batch(10)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(55).take(15):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[611 401 577 625   2 557 585   6 618 502]
[ 31 607 588  24   0 627 455 613 547 605]
[537 544 240  15  43 555 621  34 590   8]
[ 27 475 528 546 560  48  53 489  54  37]
[599 561  30 570  21 499 586  10   5  12]
[367  60 568 525   1 619 589  23 548  35]
[ 17 470 616  42 569  83  70 405  46 463]
[ 50  72 612 623  28 522 581  86  77  76]
[474 598 609  25  65 491 543  97 536  93]
[ 16 101  58  90  19  38 111 615 119  49]
[ 39 110  75  95 122  94   4  67  64  51]
[ 22 606 610  99 601 526 116 571  80 109]
[ 29  32 125 138 608  33 139 106 147 127]
[130 114 117  18  59  79  66 123 124 155]
[103 579 100 107 165 115  92  84   3  52]
repeat_shuffle = [n.numpy().mean() for n, line_batch in shuffled]

plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.plot(repeat_shuffle, label="repeat().shuffle()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7fe004095b50>

png

עיבוד נתונים מראש

Dataset.map(f) מייצרת מערך נתונים חדש על ידי החלת פונקציה נתונה f על כל רכיב במערך הקלט. הוא מבוסס על פונקציית map() המופעלת לרוב על רשימות (ומבנים אחרים) בשפות תכנות פונקציונליות. הפונקציה f לוקחת את אובייקטים tf.Tensor המייצגים אלמנט יחיד בקלט ומחזירה את אובייקטים tf.Tensor שייצגו אלמנט יחיד במערך הנתונים החדש. היישום שלה משתמש בפעולות TensorFlow סטנדרטיות כדי להפוך אלמנט אחד לאחר.

חלק זה מכסה דוגמאות נפוצות לשימוש ב- Dataset.map() .

פענוח נתוני תמונה ושינוי גודל

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

בנה מחדש את מערך שמות הקבצים של הפרחים:

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

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

# Reads an image from a file, decodes it into a dense tensor, and resizes it
# to a fixed shape.
def parse_image(filename):
  parts = tf.strings.split(filename, os.sep)
  label = parts[-2]

  image = tf.io.read_file(filename)
  image = tf.image.decode_jpeg(image)
  image = tf.image.convert_image_dtype(image, tf.float32)
  image = tf.image.resize(image, [128, 128])
  return image, label

בדוק שזה עובד.

file_path = next(iter(list_ds))
image, label = parse_image(file_path)

def show(image, label):
  plt.figure()
  plt.imshow(image)
  plt.title(label.numpy().decode('utf-8'))
  plt.axis('off')

show(image, label)

png

ממפה אותו דרך מערך הנתונים.

images_ds = list_ds.map(parse_image)

for image, label in images_ds.take(2):
  show(image, label)

png

png

החלת הגיון פיתון שרירותי

מסיבות ביצוע, השתמש בפעולות TensorFlow לעיבוד מקדים של הנתונים שלך במידת האפשר. עם זאת, לפעמים זה שימושי להתקשר לספריות פייתון חיצוניות בעת ניתוח נתוני הקלט שלך. אתה יכול להשתמש tf.py_function() הפעולה בתוך Dataset.map() טרנספורמציה.

לדוגמה, אם אתה רוצה להחיל רוטציה אקראית, tf.image מודול יש רק tf.image.rot90 , אשר אינו שימושי מאוד עבור הגדלת התמונה.

כדי להדגים את tf.py_function , נסה להשתמש בפונקציה scipy.ndimage.rotate במקום זאת:

import scipy.ndimage as ndimage

def random_rotate_image(image):
  image = ndimage.rotate(image, np.random.uniform(-30, 30), reshape=False)
  return image
image, label = next(iter(images_ds))
image = random_rotate_image(image)
show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

png

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

def tf_random_rotate_image(image, label):
  im_shape = image.shape
  [image,] = tf.py_function(random_rotate_image, [image], [tf.float32])
  image.set_shape(im_shape)
  return image, label
rot_ds = images_ds.map(tf_random_rotate_image)

for image, label in rot_ds.take(2):
  show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

png

png

ניתוח tf.Example הודעות tf.Example מאגר פרוטוקולים

צינורות קלט רבים מחלצים את tf.train.Example להודעות מאגר פרוטוקולים מתבנית TFRecord. כל רשומת tf.train.Example מכילה "תכונות" אחת או יותר, וצינור הקלט ממיר בדרך כלל תכונות אלה לטנזור.

fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>

אתה יכול לעבוד עם tf.train.Example מחוץ ל-tf.data.Dataset כדי להבין את הנתונים:

raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())

feature = parsed.features.feature
raw_img = feature['image/encoded'].bytes_list.value[0]
img = tf.image.decode_png(raw_img)
plt.imshow(img)
plt.axis('off')
_ = plt.title(feature["image/text"].bytes_list.value[0])

png

raw_example = next(iter(dataset))
def tf_parse(eg):
  example = tf.io.parse_example(
      eg[tf.newaxis], {
          'image/encoded': tf.io.FixedLenFeature(shape=(), dtype=tf.string),
          'image/text': tf.io.FixedLenFeature(shape=(), dtype=tf.string)
      })
  return example['image/encoded'][0], example['image/text'][0]
img, txt = tf_parse(raw_example)
print(txt.numpy())
print(repr(img.numpy()[:20]), "...")
b'Rue Perreyon'
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02X' ...
decoded = dataset.map(tf_parse)
decoded
<MapDataset shapes: ((), ()), types: (tf.string, tf.string)>
image_batch, text_batch = next(iter(decoded.batch(10)))
image_batch.shape
TensorShape([10])

חלונות סדרות זמן

לקבלת דוגמה לסדרת זמן מקצה לסיום ראה: חיזוי סדרות זמן .

נתוני סדרות זמן מסודרים לרוב עם ציר הזמן שלם.

השתמש פשוט Dataset.range להפגין:

range_ds = tf.data.Dataset.range(100000)

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

הגישה הפשוטה ביותר תהיה קבוצה של הנתונים:

באמצעות batch

batches = range_ds.batch(10, drop_remainder=True)

for batch in batches.take(5):
  print(batch.numpy())
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]
[40 41 42 43 44 45 46 47 48 49]

לחלופין, כדי לחזות צפיפות צעד אחד לעתיד, תוכל לשנות את התכונות והתוויות צעד אחד ביחס לשני:

def dense_1_step(batch):
  # Shift features and labels one step relative to each other.
  return batch[:-1], batch[1:]

predict_dense_1_step = batches.map(dense_1_step)

for features, label in predict_dense_1_step.take(3):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8]  =>  [1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18]  =>  [11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28]  =>  [21 22 23 24 25 26 27 28 29]

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

batches = range_ds.batch(15, drop_remainder=True)

def label_next_5_steps(batch):
  return (batch[:-5],   # Inputs: All except the last 5 steps
          batch[-5:])   # Labels: The last 5 steps

predict_5_steps = batches.map(label_next_5_steps)

for features, label in predict_5_steps.take(3):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9]  =>  [10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]  =>  [25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]  =>  [40 41 42 43 44]

כדי לאפשר חפיפה מסוימת בין התכונות של אצווה אחת לתוויות של אחר, השתמש ב- Dataset.zip :

feature_length = 10
label_length = 3

features = range_ds.batch(feature_length, drop_remainder=True)
labels = range_ds.batch(feature_length).skip(1).map(lambda labels: labels[:label_length])

predicted_steps = tf.data.Dataset.zip((features, labels))

for features, label in predicted_steps.take(5):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9]  =>  [10 11 12]
[10 11 12 13 14 15 16 17 18 19]  =>  [20 21 22]
[20 21 22 23 24 25 26 27 28 29]  =>  [30 31 32]
[30 31 32 33 34 35 36 37 38 39]  =>  [40 41 42]
[40 41 42 43 44 45 46 47 48 49]  =>  [50 51 52]

באמצעות window

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

window_size = 5

windows = range_ds.window(window_size, shift=1)
for sub_ds in windows.take(5):
  print(sub_ds)
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>

השיטה Dataset.flat_map יכולה לקחת מערך נתונים של מערכי נתונים Dataset.flat_map אותו למערך נתונים יחיד:

for x in windows.flat_map(lambda x: x).take(30):
   print(x.numpy(), end=' ')
0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9

כמעט בכל המקרים, תרצה .batch תחילה את מערך הנתונים:

def sub_to_batch(sub):
  return sub.batch(window_size, drop_remainder=True)

for example in windows.flat_map(sub_to_batch).take(5):
  print(example.numpy())
[0 1 2 3 4]
[1 2 3 4 5]
[2 3 4 5 6]
[3 4 5 6 7]
[4 5 6 7 8]

כעת אתה יכול לראות שארגומנט shift שולט כמה כל חלון עובר.

אם מרכיבים את זה, תוכלו לכתוב את הפונקציה הזו:

def make_window_dataset(ds, window_size=5, shift=1, stride=1):
  windows = ds.window(window_size, shift=shift, stride=stride)

  def sub_to_batch(sub):
    return sub.batch(window_size, drop_remainder=True)

  windows = windows.flat_map(sub_to_batch)
  return windows
ds = make_window_dataset(range_ds, window_size=10, shift = 5, stride=3)

for example in ds.take(10):
  print(example.numpy())
[ 0  3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34 37]
[15 18 21 24 27 30 33 36 39 42]
[20 23 26 29 32 35 38 41 44 47]
[25 28 31 34 37 40 43 46 49 52]
[30 33 36 39 42 45 48 51 54 57]
[35 38 41 44 47 50 53 56 59 62]
[40 43 46 49 52 55 58 61 64 67]
[45 48 51 54 57 60 63 66 69 72]

אז קל לחלץ תוויות, כמו קודם:

dense_labels_ds = ds.map(dense_1_step)

for inputs,labels in dense_labels_ds.take(3):
  print(inputs.numpy(), "=>", labels.numpy())
[ 0  3  6  9 12 15 18 21 24] => [ 3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29] => [ 8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34] => [13 16 19 22 25 28 31 34 37]

דגימה מחדש

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

zip_path = tf.keras.utils.get_file(
    origin='https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip',
    fname='creditcard.zip',
    extract=True)

csv_path = zip_path.replace('.zip', '.csv')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip
69156864/69155632 [==============================] - 2s 0us/step
creditcard_ds = tf.data.experimental.make_csv_dataset(
    csv_path, batch_size=1024, label_name="Class",
    # Set the column types: 30 floats and an int.
    column_defaults=[float()]*30+[int()])

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

def count(counts, batch):
  features, labels = batch
  class_1 = labels == 1
  class_1 = tf.cast(class_1, tf.int32)

  class_0 = labels == 0
  class_0 = tf.cast(class_0, tf.int32)

  counts['class_0'] += tf.reduce_sum(class_0)
  counts['class_1'] += tf.reduce_sum(class_1)

  return counts
counts = creditcard_ds.take(10).reduce(
    initial_state={'class_0': 0, 'class_1': 0},
    reduce_func = count)

counts = np.array([counts['class_0'].numpy(),
                   counts['class_1'].numpy()]).astype(np.float32)

fractions = counts/counts.sum()
print(fractions)
[0.9961 0.0039]

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

דגימת מערכי נתונים

גישה אחת sample_from_datasets מחדש של מערך נתונים היא להשתמש ב- sample_from_datasets . זה ישים יותר כאשר יש לך נפרדותdata.Dataset עבור כל מחלקה.

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

negative_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==0)
    .repeat())
positive_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==1)
    .repeat())
for features, label in positive_ds.batch(10).take(1):
  print(label.numpy())
[1 1 1 1 1 1 1 1 1 1]

כדי להשתמש ב- tf.data.experimental.sample_from_datasets מעבירים את מערכי הנתונים ואת המשקל לכל אחד מהם:

balanced_ds = tf.data.experimental.sample_from_datasets(
    [negative_ds, positive_ds], [0.5, 0.5]).batch(10)

כעת מערך הנתונים מייצר דוגמאות לכל מחלקה בהסתברות 50/50:

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
[1 1 1 0 1 0 1 1 0 1]
[1 1 1 0 0 1 0 0 0 0]
[0 1 1 1 1 1 1 1 0 0]
[0 0 1 0 0 0 1 0 0 0]
[0 1 0 0 1 0 1 1 0 1]
[1 0 0 0 0 1 1 1 0 1]
[0 0 0 1 1 0 0 1 0 1]
[1 0 0 1 1 0 0 1 1 0]
[0 0 1 0 0 0 0 1 0 0]
[1 1 1 1 0 0 1 1 1 0]

דגימה מחדש של דחייה

בעיה אחת בגישה experimental.sample_from_datasets לעיל היא שהיא זקוקה לtf.data.Dataset נפרד לכל מחלקה. השימוש ב- Dataset.filter עובד, אך מביא לכך שכל הנתונים נטענים פעמיים.

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

data.experimental.rejection_resample לוקח ארגומנט class_func . class_func זה מוחל על כל רכיב מערך נתונים ומשמש לקביעת איזו מחלקה דוגמה שייכת למטרות איזון.

האלמנטים של creditcard_ds הם כבר (features, label) זוגות. אז class_func רק צריך להחזיר את התוויות האלה:

def class_func(features, label):
  return label

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

resampler = tf.data.experimental.rejection_resample(
    class_func, target_dist=[0.5, 0.5], initial_dist=fractions)

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

resample_ds = creditcard_ds.unbatch().apply(resampler).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/resampling.py:156: Print (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2018-08-20.
Instructions for updating:
Use tf.print instead of tf.Print. Note that tf.print returns a no-output operator that directly prints the output. Outside of defuns or eager mode, this operator will not be executed unless it is directly specified in session.run or used as a control dependency for other operators. This is only a concern in graph mode. Below is an example of how to ensure tf.print executes in graph mode:

מחזיר הדגימה מחדש יוצר (class, example) זוגות מהפלט של class_func . במקרה זה, example כבר הייתה זוג (feature, label) , לכן השתמש map כדי להפיל את העותק הנוסף של התוויות:

balanced_ds = resample_ds.map(lambda extra_label, features_and_label: features_and_label)

כעת מערך הנתונים מייצר דוגמאות לכל מחלקה בהסתברות 50/50:

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
[1 0 0 0 1 0 1 1 0 0]
[1 1 0 0 0 1 0 0 0 0]
[1 1 1 1 0 0 0 0 0 1]
[1 0 0 1 0 1 1 1 1 1]
[1 0 1 0 1 1 0 0 1 1]
[1 1 0 1 0 1 0 0 0 0]
[0 0 0 1 1 1 0 1 1 1]
[1 0 0 0 1 0 0 0 0 1]
[1 1 0 1 0 0 1 0 1 0]
[0 1 1 1 0 1 0 1 1 1]

מחסום איטרטור

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

כדי לכלול את האיטרטור שלך במחסום, העביר את tf.train.Checkpoint .

range_ds = tf.data.Dataset.range(20)

iterator = iter(range_ds)
ckpt = tf.train.Checkpoint(step=tf.Variable(0), iterator=iterator)
manager = tf.train.CheckpointManager(ckpt, '/tmp/my_ckpt', max_to_keep=3)

print([next(iterator).numpy() for _ in range(5)])

save_path = manager.save()

print([next(iterator).numpy() for _ in range(5)])

ckpt.restore(manager.latest_checkpoint)

print([next(iterator).numpy() for _ in range(5)])
[0, 1, 2, 3, 4]
[5, 6, 7, 8, 9]
[5, 6, 7, 8, 9]

שימוש ב- tf.data עם tf.keras

ממשק ה- API של tf.keras מפשט היבטים רבים של יצירה וביצוע של מודלים של למידת מכונה. ממשקי ה- API של .fit() ו- .evaluate() ו- .predict() תומכים .predict() נתונים .predict() . הנה מערך נתונים מהיר והתקנת מודל:

train, test = tf.keras.datasets.fashion_mnist.load_data()

images, labels = train
images = images/255.0
labels = labels.astype(np.int32)
fmnist_train_ds = tf.data.Dataset.from_tensor_slices((images, labels))
fmnist_train_ds = fmnist_train_ds.shuffle(5000).batch(32)

model = tf.keras.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(10)
])

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), 
              metrics=['accuracy'])

העברת מערך נתונים של זוגות (feature, label) היא כל מה שנדרש עבור Model.fit ו- Model.evaluate :

model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2
1875/1875 [==============================] - 3s 1ms/step - loss: 0.5999 - accuracy: 0.7962
Epoch 2/2
1875/1875 [==============================] - 3s 1ms/step - loss: 0.4618 - accuracy: 0.8428
<tensorflow.python.keras.callbacks.History at 0x7fdffe98bd10>

אם אתה מעביר מערך אינסופי, למשל על ידי קריאה ל- Dataset.repeat() , עליך רק להעביר את הארגומנט steps_per_epoch :

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2
20/20 [==============================] - 0s 1ms/step - loss: 0.3764 - accuracy: 0.8656
Epoch 2/2
20/20 [==============================] - 0s 1ms/step - loss: 0.4980 - accuracy: 0.8406
<tensorflow.python.keras.callbacks.History at 0x7fe00c129050>

להערכה תוכלו לעבור את מספר שלבי ההערכה:

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 2s 951us/step - loss: 0.4384 - accuracy: 0.8501
Loss : 0.4384005665779114
Accuracy : 0.8500999808311462

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

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 1ms/step - loss: 0.4299 - accuracy: 0.8625
Loss : 0.4299231171607971
Accuracy : 0.862500011920929

התוויות אינן נדרשות כאשר מתקשרים ל- Model.predict .

predict_ds = tf.data.Dataset.from_tensor_slices(images).batch(32)
result = model.predict(predict_ds, steps = 10)
print(result.shape)
(320, 10)

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

result = model.predict(fmnist_train_ds, steps = 10)
print(result.shape)
(320, 10)