יום הקהילה ML הוא 9 בנובמבר! הצטרפו אלינו עדכונים מ- TensorFlow, JAX, ועוד למידע נוסף

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

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

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

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

או על ידי במפורש יצירת Python iterator באמצעות 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 .

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

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

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

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

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

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

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

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

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

dataset1
<TensorSliceDataset shapes: (10,), types: tf.int32>
for z in dataset1:
  print(z.numpy())
[1 9 8 8 1 8 6 9 6 2]
[6 6 2 5 7 2 8 6 6 2]
[7 6 5 1 5 8 6 1 6 9]
[1 5 1 4 3 6 4 1 1 3]
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
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 shapes: ((28, 28), ()), types: (tf.float64, tf.uint8)>

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

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

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 בנאי ממיר את הגנרטור Python על פונקציונליות מלאה tf.data.Dataset .

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

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 רבה אינו תומך tensors עם דרגה ידוע. אם אורך ציר מסוים אינו ידוע או משתנה, להגדיר את זה בתור None של output_shapes .

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

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

def gen_series():
  i = 0
  while True:
    size = np.random.randint(0, 10)
    yield i, np.random.normal(size=(size,))
    i += 1
for i, series in gen_series():
  print(i, ":", str(series))
  if i > 5:
    break
0 : [ 0.814   0.7245 -0.8296  0.106   2.6089  0.799 ]
1 : [0.6854]
2 : [-0.1255 -0.5735]
3 : [ 0.1669 -1.1696  0.0912 -0.4514  2.5346 -0.7017 -1.1124]
4 : [-0.9659]
5 : [-0.5551 -0.8024  0.264  -0.5541 -0.6733  1.6715  0.4508 -0.7317 -2.3218]
6 : [ 0.4648 -0.0541 -0.8733 -1.4034  0.29    0.669   1.2556]

הפלט הראשון הוא 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 . שים לב שכאשר batching במערך עם צורה משתנה, אתה צריך להשתמש 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())
[16  4  6  8 23  1 10  2 15 13]

[[ 0.3357 -0.3727  0.7409 -0.8574 -0.2802  0.2744 -0.2841 -1.3277]
 [ 0.7422  0.251   1.1745 -0.2416  0.7332  0.8136 -1.7633  0.    ]
 [ 0.3777 -2.2714  0.      0.      0.      0.      0.      0.    ]
 [ 0.7962 -0.3259 -0.9036  0.7381  0.      0.      0.      0.    ]
 [ 0.3902 -1.2637 -0.6834  0.5808 -0.3917  0.      0.      0.    ]
 [-1.32    0.      0.      0.      0.      0.      0.      0.    ]
 [ 1.9602  0.4305 -0.1827  0.0427 -0.7381 -0.0948 -0.0964  0.1041]
 [ 1.2071  0.0665 -0.4292  0.      0.      0.      0.      0.    ]
 [-0.7277  1.3547 -1.0543  1.9235  0.1442 -1.0691  0.2263  0.    ]
 [ 1.2949  1.5027  0.5522  0.083   0.665  -0.9897  0.0659  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 עבור דוגמה מקצה לקצה.

tf.data API תומך במגוון פורמטים של קבצים, כך שאתה יכול לעבד נתונים גדולים שאינם מתאימים בזיכרון. לדוגמה, פורמט הקובץ 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 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
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)
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 , ואת Loading פנדה 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 1]
features:
  'sex'               : [b'male' b'male' b'male' b'female']
  'age'               : [17. 18. 28.  5.]
  'n_siblings_spouses': [0 1 0 0]
  'parch'             : [2 1 0 0]
  'fare'              : [110.8833   7.8542   7.725   12.475 ]
  'class'             : [b'First' b'Third' b'Third' b'Third']
  'deck'              : [b'C' b'unknown' b'unknown' b'unknown']
  'embark_town'       : [b'Cherbourg' b'Southampton' b'Queenstown' b'Southampton']
  'alone'             : [b'n' b'n' b'y' b'y']

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

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived", select_columns=['class', 'fare', 'survived'])
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [0 1 0 0]
  'fare'              : [ 7.25   19.2583  7.8542  7.8958]
  'class'             : [b'Third' b'Third' b'Third' 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 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/dandelion/18687587599_3dd4fdf255.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/16863587471_cc3a6ffb29_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/9431890901_cd11bda584_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/3562861685_8b8d747b4d.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/14087326141_1906d5a373_n.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\x01\x00H\x00H\x00\x00\xff\xed\x00\x9aPhotoshop 3.0\x008BIM\x04\x04\x00\x00\x00\x00\x00b\x1c\x01Z\x00\x03\x1b%G\x1c\x02\x00\x00\x02\x00\x02\x1c\x027\x00\x0820100808\x1c\x02t\x006                 '

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

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

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.

זרימות עבודה

עיבוד עידנים מרובים

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

הדרך הפשוטה ביותר לחזר על הנתונים בעידנים מרובות היא להשתמש 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
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/counter.py:66: scan (from tensorflow.python.data.experimental.ops.scan_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.scan(...) instead
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

מאז buffer_size הוא 100, ואת גודל המנה הוא 20, את המנה הראשונה אינו מכיל מרכיבים עם מדד מעל 120.

n,line_batch = next(iter(dataset))
print(n.numpy())
[ 79   8  60  24  11  56  65  58  67 108  59  81  92  70  94  57  27  73
  17  75]

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

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:

[518 434 421 561 588 600 523 616 404 557]
[524 595 532 411 500 621 558 544 625 620]
[546 297 599 597 465 591 581 374]
[15  4  3  8 49 37 48 42 23 68]
[55 11 36 34 75 26 32 77 94 91]
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 0x7fbce04a2d10>

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:

[614  15 484 606 517 618  17  16 593 218]
[620 575 604 535 496 449  23 605   2  38]
[  3 543 409 480 624  36 430 607 465 531]
[373 422 608 497   4 387 623 440 591 600]
[586 609 506  35  56  13  60 603 625  55]
[ 29  58 572 279 468  37  62  46  77 545]
[  7  68  45 622 563 528  18 499  66  84]
[ 31 568  26 358 613 393 557 511 583  89]
[81 59 96 47 92 57 93 49 67 72]
[ 90 111  27  74 565  65  98  75  80 110]
[610 339  63  69  48 104 112 114 471  61]
[102  19 113  88 611  95 117  10 118  73]
[510 379 592  40  54 571  64 503 144 509]
[101  20  41 479  30 137  34  50 374 433]
[538  51  43  24 617  86 153 140 143 124]
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 0x7fbce04eb4d0>

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 לעיבוד הנתונים שלך בכל פעם שניתן. עם זאת, לפעמים כדאי להתקשר לספריות 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 שיא מכיל אחד או יותר "תכונות", ואת צינור הזנה בדרך כלל ממירה התכונות הללו לתוך tensors.

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 מחוץ Protos של tf.data.Dataset כדי להבין את הנתונים:

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

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

png

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

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

לסיום למשל סדרת שעת הסיום ראה: חיזוי סדרת זמן .

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

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

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

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

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

באמצעות batch

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

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

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

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

predict_dense_1_step = batches.map(dense_1_step)

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

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

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

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

predict_5_steps = batches.map(label_next_5_steps)

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

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

feature_length = 10
label_length = 3

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

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

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

באמצעות window

בעת השימוש Dataset.batch עבודות, יש מצבים שבהם אתה עשוי להזדקק שליטה מדויקת יותר. Dataset.window השיטה נותנת לך שליטה מלאה, אבל דורש קצת טיפול: הוא יחזיר את Dataset של 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 השיטה יכולה לקחת במערך של מערכי נתונים ולשטח אותו במערך יחיד:

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
69165056/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.9964 0.0036]

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

balanced_ds = tf.data.experimental.sample_from_datasets(
    [negative_ds, positive_ds], [0.5, 0.5]).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/interleave_ops.py:260: RandomDataset.__init__ (from tensorflow.python.data.ops.dataset_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.random(...)`.

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

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

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

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

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

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

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

def class_func(features, label):
  return label

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

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

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

resample_ds = creditcard_ds.unbatch().apply(resampler).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/resampling.py:159: 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())
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
[1 1 0 1 1 1 1 1 0 1]
[0 0 0 1 1 0 0 1 0 1]
[0 0 0 1 0 0 1 1 0 1]
[0 0 0 1 0 0 1 1 0 0]
[0 1 1 0 1 0 1 0 0 0]
[0 0 1 0 1 1 0 0 0 0]
[0 0 0 1 1 1 0 1 1 1]
[0 0 0 1 1 0 0 0 1 1]
[0 0 1 1 0 1 0 0 0 0]
[1 0 0 0 1 1 1 0 0 0]

מחסום איתור

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

כדי לכלול iterator שלך למחסום, לעבור את iterator אל 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

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

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.5993 - accuracy: 0.7991
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4618 - accuracy: 0.8410
<keras.callbacks.History at 0x7fbce01a3810>

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

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

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4328 - accuracy: 0.8520
Loss : 0.4328066110610962
Accuracy : 0.8520166873931885

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

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.5152 - accuracy: 0.8313
Loss : 0.5151785612106323
Accuracy : 0.831250011920929

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