השב / י לאירוע TensorFlow Everywhere המקומי שלך היום!
דף זה תורגם על ידי 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 הנתונים הוא ניתן להפעלה של פיתון. זה מאפשר לצרוך את האלמנטים שלו באמצעות לולאת 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 .

המאפיין 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())
[4 6 7 3 1 1 6 7 3 7]
[6 6 1 7 3 8 9 8 9 4]
[2 3 2 2 7 1 8 8 5 9]
[6 6 7 8 8 9 2 3 7 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 [==============================] - 1s 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 : [-1.8423 -0.1016  0.2763  0.815   0.0137  0.1228  0.0773]
1 : [ 0.4419  0.6819 -0.576 ]
2 : [-0.8961 -0.8613 -0.5917  0.7749 -0.2283  0.4406 -2.4833  0.1952  0.9962]
3 : []
4 : [0.2609 0.854  2.96  ]
5 : []
6 : [ 1.0899 -0.377   0.4295 -1.835  -0.4915 -0.0435 -0.6999 -0.9527]

הפלט הראשון הוא 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())
[12  0 21 20 19  2 13  6 16 15]

[[ 0.6409  0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.3158 -1.1566  0.5766  0.2067  0.2566 -0.7567  0.      0.      0.    ]
 [ 1.703   0.      0.      0.      0.      0.      0.      0.      0.    ]
 [ 1.577   0.      0.      0.      0.      0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-1.1427  1.779   1.5403  0.428  -0.0309  0.8038 -0.4779  0.3646 -0.3527]
 [-1.0069  0.6749 -1.4268  0.0887  0.4798  0.769   0.5454  0.      0.    ]
 [-0.3393  0.5725 -0.8578 -3.5323 -0.9053  0.261  -1.7785  0.5377 -0.4388]
 [ 0.5343  1.609  -0.9407  1.1031  0.4216  0.      0.      0.      0.    ]
 [ 1.1637  0.6195  1.6317 -0.759  -0.4261 -3.2933  1.9672 -0.2561  1.341 ]]

לדוגמא מציאותית יותר, נסה לעטוף את 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 [==============================] - 11s 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 0 1]
features:
  'sex'               : [b'male' b'male' b'male' b'female']
  'age'               : [11. 16. 28. 19.]
  'n_siblings_spouses': [5 4 0 0]
  'parch'             : [2 1 0 2]
  'fare'              : [46.9    39.6875  7.75   26.2833]
  'class'             : [b'Third' b'Third' b'Third' b'First']
  'deck'              : [b'unknown' b'unknown' b'unknown' b'D']
  'embark_town'       : [b'Southampton' b'Southampton' b'Queenstown' b'Southampton']
  'alone'             : [b'n' b'n' b'y' b'n']

אתה יכול להשתמש בארגומנט 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 1 1 0]
  'fare'              : [ 0.     12.2875 30.      7.75  ]
  'class'             : [b'Second' b'Third' b'First' 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/sunflowers/4868595281_1e58083785.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/5883162120_dc7274af76_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/12883412424_cb5086b43f_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/13264214185_d6aa79b3bd.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/6690926183_afedba9f15_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\x05(ICC_PROFILE\x00\x01\x01\x00\x00\x05\x18appl\x02 \x00\x00scnrRGB XYZ \x07\xd3\x00\x07\x00\x01\x00\x00\x00\x00\x00\x00acspAPPL\x00\x00\x00\x00'

b'sunflowers'

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

אצווה פשוט

הצורה הפשוטה ביותר של ערימות קבוצה של ערימות 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())
[ 90  75  39  84 102   5  98 101  51  72  54  33 104  59 110  29  92  50
  36 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:

[550 618 614 435 556 530 578 451 590 604]
[464 453 610 412 282 596 601 612 584 606]
[368 469 575 607 586 537 444 300]
[ 15  98  65  26  40  39 101  54  32  10]
[  8 102  68 108  12  96   2  87  80  37]

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

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:

[576 618 527   9 602 612  21 574 504 622]
[623  26  32 616 626 482 617 598   0 614]
[476   1 473  14  10 267  29  31  43  48]
[588  13 470 467  12 596 619  46  28 528]
[609   2  52 542 607  23  35  38 620 523]
[509 477 571  15  56  74 565 525  58  19]
[359  40  22 627 317  54 526  16 562  33]
[ 67 500 584 531  49  86  51  81  78 583]
[ 24 557 452  47 124 485 610  45  27  17]
[379  66  85  91 599  97 499 112 108  11]
[ 39 164 101  96 543  64 109 564  82  18]
[533 120  30  63 115  88  95  75 133  34]
[ 92  65 102 132  76 119 131 475 572  50]
[ 94 145 144 603 152 505 621 140 448 122]
[ 70 159 146  84  71 160  42  72  41 139]

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

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 = 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 של 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 [==============================] - 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.9952 0.0048]

גישה נפוצה לאימונים עם מערך נתונים לא מאוזן היא לאזן אותו. 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 0 1 0 1 1 0 0 1 0]
[0 0 1 1 1 1 0 0 1 1]
[1 1 0 1 1 0 1 1 0 0]
[1 0 1 1 0 0 0 0 0 1]
[1 1 0 1 1 0 0 0 1 0]
[1 0 1 1 1 0 0 0 1 1]
[0 1 1 0 0 0 1 0 1 0]
[0 1 1 1 1 0 1 1 1 0]
[0 0 1 1 1 1 0 0 1 1]
[0 0 0 0 1 0 0 1 0 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.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 1 0 1 1 1 0 1 1 1]
[0 1 1 1 1 0 1 0 0 1]
[1 0 1 1 0 1 0 1 1 1]
[1 0 0 1 1 0 0 0 1 0]
[1 1 1 1 1 0 0 0 1 0]
[0 0 0 0 1 0 1 1 0 1]
[0 1 0 1 1 1 0 1 1 0]
[1 0 0 0 0 1 0 1 0 0]
[0 1 1 1 0 1 1 1 1 0]
[0 1 1 1 1 0 1 1 1 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
1875/1875 [==============================] - 4s 2ms/step - loss: 0.7804 - accuracy: 0.7374
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4711 - accuracy: 0.8393

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

אם אתה מעביר מערך אינסופי, למשל על ידי קריאה ל- 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.4312 - accuracy: 0.8562
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4509 - accuracy: 0.8344

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

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

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4347 - accuracy: 0.8516
Loss : 0.43466493487358093
Accuracy : 0.8515999913215637

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

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.4131 - accuracy: 0.8750
Loss : 0.41311272978782654
Accuracy : 0.875

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