Google I/O הוא עטיפה! התעדכן בהפעלות של TensorFlow. צפה בהפעלות

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 . לדוגמה, אתה יכול להחיל טרנספורמציות לכל אלמנט כגון Dataset.map() , ושינויים מרובי אלמנטים כגון Dataset.batch() . עיין בתיעוד עבור tf.data.Dataset לרשימה מלאה של טרנספורמציות.

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

dataset = tf.data.Dataset.from_tensor_slices([8, 3, 0, 8, 2, 1])
dataset
<TensorSliceDataset element_spec=TensorSpec(shape=(), dtype=tf.int32, name=None)>
for elem in dataset:
  print(elem.numpy())
8
3
0
8
2
1

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

it = iter(dataset)

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

לחלופין, ניתן לצרוך רכיבי מערך נתונים באמצעות ה- reduce transformation, אשר מפחית את כל הרכיבים כדי לייצר תוצאה אחת. הדוגמה הבאה ממחישה כיצד להשתמש בטרנספורמציה 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 .

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

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

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

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

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

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

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

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

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

dataset1
<TensorSliceDataset element_spec=TensorSpec(shape=(10,), dtype=tf.int32, name=None)>
for z in dataset1:
  print(z.numpy())
[3 3 7 5 9 8 4 2 3 7]
[8 9 6 7 5 6 1 6 2 3]
[9 8 4 4 8 7 1 5 6 7]
[5 9 5 4 2 5 7 8 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 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
<ZipDataset element_spec=(TensorSpec(shape=(10,), dtype=tf.int32, name=None), (TensorSpec(shape=(), dtype=tf.float32, name=None), TensorSpec(shape=(100,), dtype=tf.int32, name=None)))>
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
40960/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
26435584/26421880 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
16384/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
4431872/4422102 [==============================] - 0s 0us/step
images, labels = train
images = images/255

dataset = tf.data.Dataset.from_tensor_slices((images, labels))
dataset
<TensorSliceDataset element_spec=(TensorSpec(shape=(28, 28), dtype=tf.float64, name=None), TensorSpec(shape=(), dtype=tf.uint8, name=None))>

צורכת מחוללי Python

מקור נתונים נפוץ נוסף שניתן להטמיע בקלות כ- 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 אינו נדרש אך מומלץ מאוד מכיוון שפעולות TensorFlow רבות אינן תומכות בטנסורים עם דירוג לא ידוע. אם האורך של ציר מסוים אינו ידוע או משתנה, הגדר אותו כ- None ב- output_shapes .

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

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

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.3939]
1 : [ 0.9282 -0.0158  1.0096  0.7155  0.0491  0.6697 -0.2565  0.487 ]
2 : [-0.4831  0.37   -1.3918 -0.4786  0.7425 -0.3299]
3 : [ 0.1427 -1.0438  0.821  -0.8766 -0.8369  0.4168]
4 : [-1.4984 -1.8424  0.0337  0.0941  1.3286 -1.4938]
5 : [-1.3158 -1.2102  2.6887 -1.2809]
6 : []

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

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

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

ds_series
<FlatMapDataset element_spec=(TensorSpec(shape=(), dtype=tf.int32, name=None), TensorSpec(shape=(None,), dtype=tf.float32, name=None))>

כעת ניתן להשתמש בו כמו 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())
[ 8 10 18  1  5 19 22 17 21 25]

[[-0.6098  0.1366 -2.15   -0.9329  0.      0.    ]
 [ 1.0295 -0.033  -0.0388  0.      0.      0.    ]
 [-0.1137  0.3552  0.4363 -0.2487 -1.1329  0.    ]
 [ 0.      0.      0.      0.      0.      0.    ]
 [-1.0466  0.624  -1.7705  1.4214  0.9143 -0.62  ]
 [-0.9502  1.7256  0.5895  0.7237  1.5397  0.    ]
 [ 0.3747  1.2967  0.      0.      0.      0.    ]
 [-0.4839  0.292  -0.7909 -0.7535  0.4591 -1.3952]
 [-0.0468  0.0039 -1.1185 -1.294   0.      0.    ]
 [-0.1679 -0.3375  0.      0.      0.      0.    ]]

לקבלת דוגמה מציאותית יותר, נסה לעטוף את 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 [==============================] - 10s 0us/step
228827136/228813984 [==============================] - 10s 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 [==============================] - 1s 0us/step
7913472/7904079 [==============================] - 1s 0us/step

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

dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 element_spec=TensorSpec(shape=(), dtype=tf.string, name=None)>

פרויקטים רבים של 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
827392/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
819200/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step
819200/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
40960/30874 [=======================================] - 0s 0us/step
for line in titanic_lines.take(10):
  print(line.numpy())
b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone'
b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n'
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y'
b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
def survived(line):
  return tf.not_equal(tf.strings.substr(line, 0, 1), "0")

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

צורכת נתוני CSV

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

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

לדוגמה:

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

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

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

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

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

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

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

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

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

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

יש גם מחלקה experimental.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 element_spec=TensorSpec(shape=(4,), dtype=tf.int32, name=None)>
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 element_spec=TensorSpec(shape=(2,), dtype=tf.int32, name=None)>
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/5018120483_cc0421b176_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/8642679391_0805b147cb_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/8266310743_02095e782d_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/13176521023_4d7cc74856_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/19437578578_6ab1b3c984.jpg'

קרא את הנתונים באמצעות הפונקציה 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 גורמות לגודל אצווה לא ידוע מכיוון שהאצווה האחרונה עשויה לא להיות מלאה. שימו לב ל- None s בצורה:

batched_dataset
<BatchDataset element_spec=(TensorSpec(shape=(None,), dtype=tf.int64, name=None), TensorSpec(shape=(None,), dtype=tf.int64, name=None))>

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

batched_dataset = dataset.batch(7, drop_remainder=True)
batched_dataset
<BatchDataset element_spec=(TensorSpec(shape=(7,), dtype=tf.int64, name=None), TensorSpec(shape=(7,), dtype=tf.int64, name=None))>

אצווה טנסורים עם ריפוד

המתכון שלמעלה עובד עבור טנסורים שכולם בעלי אותו גודל. עם זאת, מודלים רבים (למשל דגמי רצף) עובדים עם נתוני קלט שיכולים להיות בגודל משתנה (למשל רצפים באורכים שונים). כדי לטפל במקרה זה, הטרנספורמציה של 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.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 element_spec=(TensorSpec(shape=(None,), dtype=tf.int64, name=None), TensorSpec(shape=(None,), dtype=tf.string, name=None))>

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

n,line_batch = next(iter(dataset))
print(n.numpy())
[ 52  94  22  70  63  96  56 102  38  16  27 104  89  43  41  68  42  61
 112   8]

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

Dataset.shuffle לא מסמן את סופה של תקופה עד שמאגר ה-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:

[509 595 537 550 555 591 480 627 482 519]
[522 619 538 581 569 608 531 558 461 496]
[548 489 379 607 611 622 234 525]
[ 59  38   4  90  73  84  27  51 107  12]
[77 72 91 60  7 62 92 47 70 67]
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 0x7f7e7061c650>

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:

[  6   8 528 604  13 492 308 441 569 475]
[  5 626 615 568  20 554 520 454  10 607]
[510 542   0 363  32 446 395 588  35   4]
[  7  15  28  23  39 559 585  49 252 556]
[581 617  25  43  26 548  29 460  48  41]
[ 19  64  24 300 612 611  36  63  69  57]
[287 605  21 512 442  33  50  68 608  47]
[625  90  91 613  67  53 606 344  16  44]
[453 448  89  45 465   2  31 618 368 105]
[565   3 586 114  37 464  12 627  30 621]
[ 82 117  72  75  84  17 571 610  18 600]
[107 597 575  88 623  86 101  81 456 102]
[122  79  51  58  80  61 367  38 537 113]
[ 71  78 598 152 143 620 100 158 133 130]
[155 151 144 135 146 121  83  27 103 134]
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 0x7f7e706013d0>

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.io.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

יישום לוגיקה שרירותית של Python

מטעמי ביצועים, השתמש בפעולות TensorFlow לעיבוד מקדים של הנתונים שלך במידת האפשר. עם זאת, לפעמים שימושי לקרוא לספריות Python חיצוניות בעת ניתוח נתוני הקלט שלך. אתה יכול להשתמש 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.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 element_spec=TensorSpec(shape=(), dtype=tf.string, name=None)>

אתה יכול לעבוד עם 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 element_spec=(TensorSpec(shape=(), dtype=tf.string, name=None), TensorSpec(shape=(), dtype=tf.string, name=None))>
image_batch, text_batch = next(iter(decoded.batch(10)))
image_batch.shape
TensorShape([10])

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

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

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

השתמש ב- Dataset.range פשוט כדי להדגים:

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

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

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

באמצעות batch

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

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

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

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

predict_dense_1_step = batches.map(dense_1_step)

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

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

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

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

predict_5_steps = batches.map(label_next_5_steps)

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

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

feature_length = 10
label_length = 3

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

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

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

שימוש window

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

window_size = 5

windows = range_ds.window(window_size, shift=1)
for sub_ds in windows.take(5):
  print(sub_ds)
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>

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

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

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

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

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

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

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

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

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

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

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

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

dense_labels_ds = ds.map(dense_1_step)

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

דגימה מחדש

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

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

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

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

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

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

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

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

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

fractions = counts/counts.sum()
print(fractions)
[0.9956 0.0044]

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

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

גישה אחת לדגימה מחדש של מערך נתונים היא להשתמש ב- 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.Dataset.sample_from_datasets , העבר את מערכי הנתונים והמשקל עבור כל אחד מהם:

balanced_ds = tf.data.Dataset.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 0 1 1 1 1]
[0 0 1 1 0 1 1 1 1 1]
[1 1 1 1 0 0 1 0 1 0]
[1 1 1 0 1 0 0 1 1 1]
[0 1 0 1 1 1 0 1 1 0]
[0 1 0 0 0 1 0 0 0 0]
[1 1 1 1 1 0 0 1 1 0]
[0 0 0 1 0 1 1 1 0 0]
[0 0 1 1 1 1 0 1 1 1]
[1 0 0 1 1 1 1 0 1 1]

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

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

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

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

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

def class_func(features, label):
  return label

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

השיטה צריכה התפלגות מטרה, ואופציונלית אומדן התפלגות ראשוני בתור תשומות.

resample_ds = (
    creditcard_ds
    .unbatch()
    .rejection_resample(class_func, target_dist=[0.5,0.5],
                        initial_dist=fractions)
    .batch(10))
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/ops/dataset_ops.py:5797: 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:

שיטת rejection_resample מחזירה (class, example) זוגות כאשר class היא הפלט של ה- 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())
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
[0 1 1 1 0 1 1 0 1 1]
[1 1 0 1 0 0 0 0 1 1]
[1 1 1 1 0 0 0 0 1 1]
[1 0 0 1 0 0 1 0 1 1]
[1 0 0 0 0 1 0 0 0 0]
[1 0 0 1 1 0 1 1 1 0]
[1 1 0 0 0 0 0 0 0 1]
[0 0 1 0 0 0 1 0 1 1]
[0 1 0 1 0 1 0 0 0 1]
[0 0 0 0 0 0 0 0 1 1]

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

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

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

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

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

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

save_path = manager.save()

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

ckpt.restore(manager.latest_checkpoint)

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

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

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

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.5984 - accuracy: 0.7973
Epoch 2/2
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4607 - accuracy: 0.8430
<keras.callbacks.History at 0x7f7e70283110>

אם אתה מעביר מערך נתונים אינסופי, למשל על ידי קריאה 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.4574 - accuracy: 0.8672
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4216 - accuracy: 0.8562
<keras.callbacks.History at 0x7f7e144948d0>

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

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4350 - accuracy: 0.8524
Loss : 0.4350026249885559
Accuracy : 0.8524333238601685

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

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.4345 - accuracy: 0.8687
Loss : 0.43447819352149963
Accuracy : 0.8687499761581421

התוויות אינן נדרשות בעת קריאת 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)