דף זה תורגם על ידי Cloud Translation API.
Switch to English

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 הנתונים הוא ניתן להפעלה של פיתון. זה מאפשר לצרוך את האלמנטים שלו באמצעות לולאה עבור:

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 .

המאפיין 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() , 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())
[1 3 6 2 4 3 7 5 9 5]
[8 9 5 3 6 4 1 6 6 3]
[4 8 2 3 3 6 9 8 5 5]
[7 8 7 9 5 5 6 4 8 8]

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.1241  0.5308  0.3018]
1 : []
2 : [ 0.5769 -0.8721  2.0072 -1.7862  0.8289  0.59  ]
3 : [-1.5209 -1.5252  0.2506 -0.526   1.2647 -1.2677 -1.4078]
4 : [ 1.6039  0.2602  0.2278  1.205  -0.8033  0.3032]
5 : [ 0.5982  1.5779  0.0248  1.3666 -1.9277  1.3854 -0.4739]
6 : [ 1.0598 -0.2546  0.5908  1.3619  1.1141 -0.6058  0.8438 -2.4862]

הפלט הראשון הוא 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())
[ 3  7  6 22  0 13  4 23  5 17]

[[ 9.5867e-01 -2.5104e-01  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00]
 [-1.4743e-01  4.1422e-02  4.8626e-01 -4.4328e-01 -3.0196e+00  3.0172e-01
   0.0000e+00]
 [ 2.9469e-01 -2.8750e-01 -1.2391e-01 -7.7315e-01  7.5218e-01  7.9246e-01
   0.0000e+00]
 [ 1.5680e+00 -6.4869e-01 -7.5440e-01  3.3234e-01 -1.0759e+00  0.0000e+00
   0.0000e+00]
 [ 4.0357e-01 -7.8729e-01  2.1975e-02  2.4870e-02 -9.1991e-01 -2.1324e+00
   0.0000e+00]
 [-8.2417e-02  1.0919e+00 -6.6252e-01 -4.2764e-01  7.9078e-01  1.9829e-03
  -9.5911e-01]
 [-2.7661e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00]
 [ 0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00]
 [ 1.7720e-01  1.1324e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00]
 [-4.8885e-01  0.0000e+00  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 [==============================] - 2s 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': [0 0 1 1]
features:
  'sex'               : [b'male' b'male' b'female' b'female']
  'age'               : [38. 16. 28. 28.]
  'n_siblings_spouses': [0 0 0 0]
  'parch'             : [1 0 0 0]
  'fare'              : [153.4625  10.5      7.8792   7.7333]
  'class'             : [b'First' b'Second' b'Third' b'Third']
  'deck'              : [b'C' b'unknown' b'unknown' b'unknown']
  'embark_town'       : [b'Southampton' b'Southampton' b'Queenstown' b'Queenstown']
  'alone'             : [b'n' b'y' b'y' 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'              : [25.4667  7.8958 13.      7.8958]
  'class'             : [b'Third' b'Third' b'Second' b'Third']

יש גם מחלקה 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/daisy/9489270024_1b05f08492_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/11023214096_b5b39fab08.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/14928117202_139d2142cc_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/2215318403_06eb99176a.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/7481215720_73e40f178f_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\xe2\x0cXICC_PROFILE\x00\x01\x01\x00\x00\x0cHLino\x02\x10\x00\x00mntrRGB XYZ \x07\xce\x00\x02\x00\t\x00\x06\x001\x00\x00acspMSFT\x00\x00\x00\x00IEC sRGB\x00\x00\x00\x00\x00\x00'

b'daisy'

אצוות רכיבי מערך נתונים

אצווה פשוט

הצורה הפשוטה מינון ערימות 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())
[ 94  56  75  71  27  35  99  14  20  33  60   4  87  47  32  19  55  93
 112 103]

כמו ב- 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:

[371 576 469 293 598 618 559 512 491 524]
[527 613 566 625 573 621 608 375 568 587]
[600 578 617 580 496  18 541 601]
[16 54 62 59 98 18 82 61 91 99]
[50 86 75 90 40 92 63 94 51 80]

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 0x7f4ce55901d0>

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:

[549 311 612 441 614 521   8 434 606  12]
[623  31 622  22 509 435 447 448 479   6]
[487  19 358  28 545  36 593 588 502 527]
[ 45 586 596   7 154  16 559 402  43  27]
[432  25  38 417  29 570 582  46  14  11]
[594 572   2 554 563   5 556 444  21  30]
[537   0 530  33 483 608  68  18  75  10]
[ 81  84  58 626 542  86  87 610  85  41]
[  4 627 584 397 534  66  99 603 105  44]
[ 65 106  23  13 100 599 428 595  74 104]
[ 62  24  92 558 121  95 451  50 587  47]
[ 48 362  17  78  89 323  57  73 130  77]
[ 63 617  69 619 109 115   9  10  76 122]
[144  96  79 140 368 101 552 117 113 158]
[153 150  54 562 148 149 141 168 142  98]

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 0x7f4cd03727b8>

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],   # Take the first 5 steps
          batch[-5:])   # take the remainder

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 = 5

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

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

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]
[10 11 12 13 14 15 16 17 18 19]  =>  [20 21 22 23 24]
[20 21 22 23 24 25 26 27 28 29]  =>  [30 31 32 33 34]

באמצעות window

בזמן השימוש ב- Dataset.batch עובד, ישנם מצבים שבהם ייתכן שתזדקק לשליטה טובה יותר. שיטת 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=' ')
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7f4cd044e510> and will run it as-is.
Cause: could not parse the source code:

for x in windows.flat_map(lambda x: x).take(30):

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function <lambda> at 0x7f4cd044e510> and will run it as-is.
Cause: could not parse the source code:

for x in windows.flat_map(lambda x: x).take(30):

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
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 [==============================] - 3s 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.9957 0.0043]

גישה נפוצה לאימונים עם מערך נתונים לא מאוזן היא לאזן אותו. 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())
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7f4d44598488> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==0)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function <lambda> at 0x7f4d44598488> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==0)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7f4d445988c8> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==1)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function <lambda> at 0x7f4d445988c8> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==1)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert

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 0 1 1 0 1 0 1 0]
[0 0 1 0 0 0 0 1 1 1]
[0 0 0 0 0 0 0 1 0 1]
[0 0 0 0 1 0 0 0 0 0]
[1 1 0 0 1 0 1 1 0 1]
[0 1 1 0 0 0 1 0 1 0]
[0 1 0 0 1 1 0 0 1 0]
[1 0 0 0 1 0 1 0 1 0]
[1 1 1 0 1 1 1 0 1 0]
[1 0 1 1 1 0 1 1 0 1]

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

בעיה אחת בגישה 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.6/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())
[0 0 0 1 1 1 0 0 0 0]
[1 0 1 0 1 1 0 1 1 1]
[1 0 1 0 0 0 0 1 1 1]
[0 0 0 1 0 1 0 1 0 1]
[1 1 1 1 0 1 1 0 0 0]
[1 0 0 0 1 0 1 0 0 1]
[0 1 1 1 0 1 0 0 0 0]
[1 0 1 1 1 0 1 1 1 1]
[0 0 0 0 0 0 1 0 1 1]
[1 1 1 1 1 0 0 0 0 0]

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

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
WARNING:tensorflow:Layer flatten is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2.  The layer has dtype float32 because its dtype defaults to floatx.

If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.

To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.

1875/1875 [==============================] - 3s 2ms/step - loss: 0.5997 - accuracy: 0.7981
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4631 - accuracy: 0.8411

<tensorflow.python.keras.callbacks.History at 0x7f4cd016bc18>

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

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2
20/20 [==============================] - 0s 2ms/step - loss: 0.3873 - accuracy: 0.8734
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4442 - accuracy: 0.8313

<tensorflow.python.keras.callbacks.History at 0x7f4d4c10edd8>

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

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4445 - accuracy: 0.8447
Loss : 0.44445285201072693
Accuracy : 0.8446999788284302

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

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.4147 - accuracy: 0.8500
Loss : 0.4146668314933777
Accuracy : 0.8500000238418579

התוויות אינן נדרשות כאשר מתקשרים ל- 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)