![]() | ![]() | ![]() | ![]() |
ממשק ה- API של tf.data
מאפשר לבנות צינורות קלט מורכבים מחלקים פשוטים לשימוש רב פעמי. לדוגמא, הצינור עבור מודל תמונה עשוי לצבור נתונים מקבצים במערכת קבצים מבוזרת, להחיל הפרעות אקראיות על כל תמונה, ולמזג תמונות שנבחרו באופן אקראי לקבוצת אימונים. הצינור של מודל טקסט עשוי לכלול חילוץ סמלים מנתוני טקסט גולמיים, המרתם להטמעה של מזהים עם טבלת בדיקה, ואיחוד רצפים באורכים שונים. ממשק ה- API של tf.data
מאפשר לטפל בכמויות גדולות של נתונים, לקרוא מתבניות נתונים שונות ולבצע טרנספורמציות מורכבות.
ממשק ה- API של tf.data
מציג הפשטתtf.data.Dataset
המייצגת רצף של אלמנטים, שכל אלמנט מורכב ממרכיב אחד או יותר. לדוגמא, בצינור תמונה, אלמנט עשוי להיות דוגמה לאימון יחיד, עם זוג רכיבי טנסור המייצגים את התמונה ואת התווית שלה.
ישנן שתי דרכים שונות ליצור מערך נתונים:
מקור נתונים בונה
Dataset
נתונים מנתונים המאוחסנים בזיכרון או בקובץ אחד או יותר.טרנספורמציה של נתונים בונה מערך נתונים מאובייקט
tf.data.Dataset
אחד או יותר.
import tensorflow as tf
import pathlib
import os
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
np.set_printoptions(precision=4)
מכניקה בסיסית
כדי ליצור צינור קלט, עליך להתחיל עם מקור נתונים. לדוגמה, כדי לבנות Dataset
נתונים מנתונים בזיכרון, אתה יכול להשתמש ב- tf.data.Dataset.from_tensors()
או tf.data.Dataset.from_tensor_slices()
. לחלופין, אם נתוני הקלט שלך מאוחסנים בקובץ בתבנית TFRecord המומלצת, תוכל להשתמש ב- tf.data.TFRecordDataset()
.
ברגע שיש לך אובייקט Dataset
, תוכל להפוך אותו Dataset
חדש על ידיtf.data.Dataset
שיטות שרשור לאובייקטtf.data.Dataset
. לדוגמה, באפשרותך להחיל טרנספורמציות לכל אלמנט כגון Dataset.map()
, וטרנספורמציות רב-אלמנט כגון Dataset.batch()
. עיין בתיעוד עבורtf.data.Dataset
לקבלת רשימה מלאה של טרנספורמציות.
אובייקט Dataset
הנתונים הוא ניתן להפעלה של פיתון. זה מאפשר לצרוך את האלמנטים שלו באמצעות לולאת for:
dataset = tf.data.Dataset.from_tensor_slices([8, 3, 0, 8, 2, 1])
dataset
<TensorSliceDataset shapes: (), types: tf.int32>
for elem in dataset:
print(elem.numpy())
8 3 0 8 2 1
או על ידי יצירת מפורש איטרטור של פייתון באמצעות iter
וצריכת האלמנטים שלו באמצעות next
:
it = iter(dataset)
print(next(it).numpy())
8
לחלופין, ניתן לצרוך אלמנטים של מערכי נתונים באמצעות reduce
הטרנספורמציה, המפחיתה את כל האלמנטים כדי לייצר תוצאה אחת. הדוגמה הבאה ממחישה כיצד להשתמש בטרנספורמציית reduce
כדי לחשב את סכום מערך הנתונים של מספרים שלמים.
print(dataset.reduce(0, lambda state, value: state + value).numpy())
22
מבנה מערך הנתונים
מערך נתונים מכיל אלמנטים שלכל אחד מהם אותו מבנה (מקונן) והמרכיבים הבודדים של המבנה יכולים להיות מכל סוג המיוצגים על ידי tf.TypeSpec
, כולל tf.Tensor
, tf.sparse.SparseTensor
,tf.RaggedTensor
, tf.TensorArray
, אוtf.data.Dataset
.
המאפיין Dataset.element_spec
מאפשר לך לבדוק את סוג כל רכיב האלמנטים. המאפיין מחזיר מבנה מקונן של אובייקטים tf.TypeSpec
, התואם את מבנה האלמנט, שעשוי להיות רכיב בודד, כפל של רכיבים או כותרת רכיבים מקוננת. לדוגמה:
dataset1 = tf.data.Dataset.from_tensor_slices(tf.random.uniform([4, 10]))
dataset1.element_spec
TensorSpec(shape=(10,), dtype=tf.float32, name=None)
dataset2 = tf.data.Dataset.from_tensor_slices(
(tf.random.uniform([4]),
tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))
dataset2.element_spec
(TensorSpec(shape=(), dtype=tf.float32, name=None), TensorSpec(shape=(100,), dtype=tf.int32, name=None))
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))
dataset3.element_spec
(TensorSpec(shape=(10,), dtype=tf.float32, name=None), (TensorSpec(shape=(), dtype=tf.float32, name=None), TensorSpec(shape=(100,), dtype=tf.int32, name=None)))
# Dataset containing a sparse tensor.
dataset4 = tf.data.Dataset.from_tensors(tf.SparseTensor(indices=[[0, 0], [1, 2]], values=[1, 2], dense_shape=[3, 4]))
dataset4.element_spec
SparseTensorSpec(TensorShape([3, 4]), tf.int32)
# Use value_type to see the type of value represented by the element spec
dataset4.element_spec.value_type
tensorflow.python.framework.sparse_tensor.SparseTensor
טרנספורמציות Dataset
הנתונים תומכות בערכות נתונים בכל מבנה. בעת שימוש Dataset.map()
ו- Dataset.filter()
, Dataset.filter()
פונקציה על כל אלמנט, מבנה האלמנט קובע את הארגומנטים של הפונקציה:
dataset1 = tf.data.Dataset.from_tensor_slices(
tf.random.uniform([4, 10], minval=1, maxval=10, dtype=tf.int32))
dataset1
<TensorSliceDataset shapes: (10,), types: tf.int32>
for z in dataset1:
print(z.numpy())
[4 6 7 3 1 1 6 7 3 7] [6 6 1 7 3 8 9 8 9 4] [2 3 2 2 7 1 8 8 5 9] [6 6 7 8 8 9 2 3 7 8]
dataset2 = tf.data.Dataset.from_tensor_slices(
(tf.random.uniform([4]),
tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))
dataset2
<TensorSliceDataset shapes: ((), (100,)), types: (tf.float32, tf.int32)>
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))
dataset3
<ZipDataset shapes: ((10,), ((), (100,))), types: (tf.int32, (tf.float32, tf.int32))>
for a, (b,c) in dataset3:
print('shapes: {a.shape}, {b.shape}, {c.shape}'.format(a=a, b=b, c=c))
shapes: (10,), (), (100,) shapes: (10,), (), (100,) shapes: (10,), (), (100,) shapes: (10,), (), (100,)
קריאת נתוני קלט
צורכים מערכי NumPy
ראה טעינת מערכי NumPy לקבלת דוגמאות נוספות.
אם כל ההתקפים נתוני הקלט שלך בזיכרון, הדרך הפשוטה ביותר ליצור Dataset
מהם היא להמיר אותם tf.Tensor
חפצים ולהשתמש Dataset.from_tensor_slices()
.
train, test = tf.keras.datasets.fashion_mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz 32768/29515 [=================================] - 0s 0us/step Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz 26427392/26421880 [==============================] - 1s 0us/step Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz 8192/5148 [===============================================] - 0s 0us/step Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz 4423680/4422102 [==============================] - 0s 0us/step
images, labels = train
images = images/255
dataset = tf.data.Dataset.from_tensor_slices((images, labels))
dataset
<TensorSliceDataset shapes: ((28, 28), ()), types: (tf.float64, tf.uint8)>
צורכים מחוללי פיתון
מקור נתונים נפוץ נוסף שניתן לבלוע בקלות כ-tf.data.Dataset
הוא מחולל הפיתון.
def count(stop):
i = 0
while i<stop:
yield i
i += 1
for n in count(5):
print(n)
0 1 2 3 4
Dataset.from_generator
ממיר את מחולל הפיתון ל-tf.data.Dataset
פונקציונלי לחלוטין.
הקונסטרוקטור לוקח קריאה כקלט, ולא איטרטור. זה מאפשר לו להפעיל מחדש את הגנרטור כשהוא מגיע לסוף. נדרש טיעון args
אופציונלי, המועבר כטיעונים של הקריאה.
הארגומנט output_types
נדרש מכיוון ש tf.data
בונה tf.Graph
פנימי, וקצוות הגרפים דורשים tf.dtype
.
ds_counter = tf.data.Dataset.from_generator(count, args=[25], output_types=tf.int32, output_shapes = (), )
for count_batch in ds_counter.repeat().batch(10).take(10):
print(count_batch.numpy())
[0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19] [20 21 22 23 24 0 1 2 3 4] [ 5 6 7 8 9 10 11 12 13 14] [15 16 17 18 19 20 21 22 23 24] [0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19] [20 21 22 23 24 0 1 2 3 4] [ 5 6 7 8 9 10 11 12 13 14] [15 16 17 18 19 20 21 22 23 24]
הארגומנט output_shapes
אינו נדרש אך מומלץ מאוד מכיוון שפעולות זרימת טנסור רבות אינן תומכות בטנורים בעלי דירוג לא ידוע. אם אורך ציר מסוים אינו ידוע או משתנה, להגדיר את זה בתור None
של output_shapes
.
חשוב גם לציין כי output_shapes
ו- output_types
פועלים לפי כללי קינון כמו שיטות מערך אחרות.
הנה מחולל לדוגמא המדגים את שני ההיבטים, הוא מחזיר צמרות מערכים, כאשר המערך השני הוא וקטור באורך לא ידוע.
def gen_series():
i = 0
while True:
size = np.random.randint(0, 10)
yield i, np.random.normal(size=(size,))
i += 1
for i, series in gen_series():
print(i, ":", str(series))
if i > 5:
break
0 : [-1.8423 -0.1016 0.2763 0.815 0.0137 0.1228 0.0773] 1 : [ 0.4419 0.6819 -0.576 ] 2 : [-0.8961 -0.8613 -0.5917 0.7749 -0.2283 0.4406 -2.4833 0.1952 0.9962] 3 : [] 4 : [0.2609 0.854 2.96 ] 5 : [] 6 : [ 1.0899 -0.377 0.4295 -1.835 -0.4915 -0.0435 -0.6999 -0.9527]
הפלט הראשון הוא int32
והשני הוא float32
.
הפריט הראשון הוא סקלר, צורה ()
, והשני הוא וקטור באורך, צורה לא ידוע (None,)
ds_series = tf.data.Dataset.from_generator(
gen_series,
output_types=(tf.int32, tf.float32),
output_shapes=((), (None,)))
ds_series
<FlatMapDataset shapes: ((), (None,)), types: (tf.int32, tf.float32)>
עכשיו זה יכול לשמש כמוtf.data.Dataset
רגיל. שים לב שכאשר אצווה מערך נתונים עם צורה משתנה, עליך להשתמש ב- Dataset.padded_batch
.
ds_series_batch = ds_series.shuffle(20).padded_batch(10)
ids, sequence_batch = next(iter(ds_series_batch))
print(ids.numpy())
print()
print(sequence_batch.numpy())
[12 0 21 20 19 2 13 6 16 15] [[ 0.6409 0. 0. 0. 0. 0. 0. 0. 0. ] [-0.3158 -1.1566 0.5766 0.2067 0.2566 -0.7567 0. 0. 0. ] [ 1.703 0. 0. 0. 0. 0. 0. 0. 0. ] [ 1.577 0. 0. 0. 0. 0. 0. 0. 0. ] [ 0. 0. 0. 0. 0. 0. 0. 0. 0. ] [-1.1427 1.779 1.5403 0.428 -0.0309 0.8038 -0.4779 0.3646 -0.3527] [-1.0069 0.6749 -1.4268 0.0887 0.4798 0.769 0.5454 0. 0. ] [-0.3393 0.5725 -0.8578 -3.5323 -0.9053 0.261 -1.7785 0.5377 -0.4388] [ 0.5343 1.609 -0.9407 1.1031 0.4216 0. 0. 0. 0. ] [ 1.1637 0.6195 1.6317 -0.759 -0.4261 -3.2933 1.9672 -0.2561 1.341 ]]
לדוגמא מציאותית יותר, נסה לעטוף את preprocessing.image.ImageDataGenerator
כ-tf.data.Dataset
.
הורד תחילה את הנתונים:
flowers = tf.keras.utils.get_file(
'flower_photos',
'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
untar=True)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz 228818944/228813984 [==============================] - 11s 0us/step
צור את התמונה. image.ImageDataGenerator
img_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255, rotation_range=20)
images, labels = next(img_gen.flow_from_directory(flowers))
Found 3670 images belonging to 5 classes.
print(images.dtype, images.shape)
print(labels.dtype, labels.shape)
float32 (32, 256, 256, 3) float32 (32, 5)
ds = tf.data.Dataset.from_generator(
lambda: img_gen.flow_from_directory(flowers),
output_types=(tf.float32, tf.float32),
output_shapes=([32,256,256,3], [32,5])
)
ds.element_spec
(TensorSpec(shape=(32, 256, 256, 3), dtype=tf.float32, name=None), TensorSpec(shape=(32, 5), dtype=tf.float32, name=None))
for images, label in ds.take(1):
print('images.shape: ', images.shape)
print('labels.shape: ', labels.shape)
Found 3670 images belonging to 5 classes. images.shape: (32, 256, 256, 3) labels.shape: (32, 5)
צריכת נתוני TFRecord
ראה טעינת TFRecords לקבלת דוגמה מקצה לקצה.
ממשק ה- API של tf.data
תומך במגוון פורמטים של קבצים כך שתוכלו לעבד מערכי נתונים גדולים שאינם מתאימים לזיכרון. לדוגמא, פורמט הקובץ TFRecord הוא פורמט בינארי פשוט מונחה רשומות בו משתמשים ביישומי TensorFlow רבים לצורך נתוני אימון. המחלקה tf.data.TFRecordDataset
מאפשרת לך להזרים את התוכן של קובץ TFRecord אחד או יותר כחלק מצינור קלט.
להלן דוגמא לשימוש בקובץ הבדיקה מתמרורי הרחוב הצרפתיים (FSNS).
# Creates a dataset that reads all of the examples from two files.
fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001 7905280/7904079 [==============================] - 0s 0us/step
הארגומנט של filenames
לאתחול TFRecordDataset
יכול להיות מחרוזת, רשימה של מחרוזות או tf.Tensor
של מחרוזות. לכן אם יש לך שתי קבוצות של קבצים למטרות אימון ואימות, תוכל ליצור שיטת מפעל המייצרת את מערך הנתונים, תוך לקיחת שמות קבצים כטיעון קלט:
dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>
פרויקטים רבים של TensorFlow משתמשים ב- tf.train.Example
בסידרה. רשומות לדוגמה בקבצי TFRecord שלהם. יש לפענח את אלה לפני שניתן יהיה לבדוק אותם:
raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())
parsed.features.feature['image/text']
bytes_list { value: "Rue Perreyon" }
צריכת נתוני טקסט
ראה טעינת טקסט לקבלת דוגמה מקצה לקצה.
מערכי נתונים רבים מופצים כקובץ טקסט אחד או יותר. tf.data.TextLineDataset
מספק דרך קלה לחילוץ שורות מקובץ טקסט אחד או יותר. בהינתן שם קובץ אחד או יותר, TextLineDataset
יפיק אלמנט אחד בעל ערך מחרוזת בכל שורה של קבצים אלה.
directory_url = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
file_names = ['cowper.txt', 'derby.txt', 'butler.txt']
file_paths = [
tf.keras.utils.get_file(file_name, directory_url + file_name)
for file_name in file_names
]
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/cowper.txt 819200/815980 [==============================] - 0s 0us/step Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt 811008/809730 [==============================] - 0s 0us/step Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt 811008/807992 [==============================] - 0s 0us/step
dataset = tf.data.TextLineDataset(file_paths)
להלן השורות הראשונות בקובץ הראשון:
for line in dataset.take(5):
print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;" b'His wrath pernicious, who ten thousand woes' b"Caused to Achaia's host, sent many a soul" b'Illustrious into Ades premature,' b'And Heroes gave (so stood the will of Jove)'
כדי להחליף קווים בין קבצים השתמש ב- Dataset.interleave
. זה מקל על דשדוש קבצים יחד. להלן השורה הראשונה, השנייה והשלישית מכל תרגום:
files_ds = tf.data.Dataset.from_tensor_slices(file_paths)
lines_ds = files_ds.interleave(tf.data.TextLineDataset, cycle_length=3)
for i, line in enumerate(lines_ds.take(9)):
if i % 3 == 0:
print()
print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;" b"\xef\xbb\xbfOf Peleus' son, Achilles, sing, O Muse," b'\xef\xbb\xbfSing, O goddess, the anger of Achilles son of Peleus, that brought' b'His wrath pernicious, who ten thousand woes' b'The vengeance, deep and deadly; whence to Greece' b'countless ills upon the Achaeans. Many a brave soul did it send' b"Caused to Achaia's host, sent many a soul" b'Unnumbered ills arose; which many a soul' b'hurrying down to Hades, and many a hero did it yield a prey to dogs and'
כברירת מחדל, TextLineDataset
מניב כל שורה מכל קובץ, מה שלא יכול להיות רצוי, למשל, אם הקובץ מתחיל בשורת כותרת, או מכיל הערות. ניתן להסיר שורות אלה באמצעות Dataset.skip()
או Dataset.filter()
. כאן, אתה מדלג על השורה הראשונה, ואז מסנן כדי למצוא רק ניצולים.
titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/train.csv 32768/30874 [===============================] - 0s 0us/step
for line in titanic_lines.take(10):
print(line.numpy())
b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone' b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n' b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n' b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y' b'1,female,35.0,1,0,53.1,First,C,Southampton,n' b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y' b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n' b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n' b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n' b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
def survived(line):
return tf.not_equal(tf.strings.substr(line, 0, 1), "0")
survivors = titanic_lines.skip(1).filter(survived)
for line in survivors.take(10):
print(line.numpy())
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n' b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y' b'1,female,35.0,1,0,53.1,First,C,Southampton,n' b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n' b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n' b'1,female,4.0,1,1,16.7,Third,G,Southampton,n' b'1,male,28.0,0,0,13.0,Second,unknown,Southampton,y' b'1,female,28.0,0,0,7.225,Third,unknown,Cherbourg,y' b'1,male,28.0,0,0,35.5,First,A,Southampton,y' b'1,female,38.0,1,5,31.3875,Third,unknown,Southampton,n'
צורכת נתוני CSV
ראה טעינת קבצי CSV וטענת Pandas DataFrames לקבלת דוגמאות נוספות.
פורמט קובץ CSV הוא פורמט פופולרי לאחסון נתונים טבלאיים בטקסט רגיל.
לדוגמה:
titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
df = pd.read_csv(titanic_file)
df.head()
אם הנתונים שלך נכנסים לזיכרון אותה שיטת Dataset.from_tensor_slices
עובדת על מילונים, ומאפשרת לייבא בקלות נתונים אלה:
titanic_slices = tf.data.Dataset.from_tensor_slices(dict(df))
for feature_batch in titanic_slices.take(1):
for key, value in feature_batch.items():
print(" {!r:20s}: {}".format(key, value))
'survived' : 0 'sex' : b'male' 'age' : 22.0 'n_siblings_spouses': 1 'parch' : 0 'fare' : 7.25 'class' : b'Third' 'deck' : b'unknown' 'embark_town' : b'Southampton' 'alone' : b'n'
גישה מדרגתית יותר היא לטעון מהדיסק לפי הצורך.
מודול tf.data
מספק שיטות לחילוץ רשומות מקובץ CSV אחד או יותר העומדים בתקן RFC 4180 .
הפונקציה experimental.make_csv_dataset
היא הממשק ברמה גבוהה לקריאת קבוצות של קבצי csv. הוא תומך בהסקת סוג העמודה ותכונות רבות אחרות, כמו אצווה ודשדוש, כדי להפוך את השימוש לפשוט.
titanic_batches = tf.data.experimental.make_csv_dataset(
titanic_file, batch_size=4,
label_name="survived")
for feature_batch, label_batch in titanic_batches.take(1):
print("'survived': {}".format(label_batch))
print("features:")
for key, value in feature_batch.items():
print(" {!r:20s}: {}".format(key, value))
'survived': [0 0 0 1] features: 'sex' : [b'male' b'male' b'male' b'female'] 'age' : [11. 16. 28. 19.] 'n_siblings_spouses': [5 4 0 0] 'parch' : [2 1 0 2] 'fare' : [46.9 39.6875 7.75 26.2833] 'class' : [b'Third' b'Third' b'Third' b'First'] 'deck' : [b'unknown' b'unknown' b'unknown' b'D'] 'embark_town' : [b'Southampton' b'Southampton' b'Queenstown' b'Southampton'] 'alone' : [b'n' b'n' b'y' b'n']
אתה יכול להשתמש בארגומנט select_columns
אם אתה זקוק רק לקבוצת משנה של עמודות.
titanic_batches = tf.data.experimental.make_csv_dataset(
titanic_file, batch_size=4,
label_name="survived", select_columns=['class', 'fare', 'survived'])
for feature_batch, label_batch in titanic_batches.take(1):
print("'survived': {}".format(label_batch))
for key, value in feature_batch.items():
print(" {!r:20s}: {}".format(key, value))
'survived': [0 1 1 0] 'fare' : [ 0. 12.2875 30. 7.75 ] 'class' : [b'Second' b'Third' b'First' b'Third']
יש גם מחלקה experimental.CsvDataset
ברמה נמוכה יותר. CsvDataset המספקת שליטה דקה יותר. הוא אינו תומך בהסקת סוג העמודה. במקום זאת עליך לציין את סוג כל העמודה.
titanic_types = [tf.int32, tf.string, tf.float32, tf.int32, tf.int32, tf.float32, tf.string, tf.string, tf.string, tf.string]
dataset = tf.data.experimental.CsvDataset(titanic_file, titanic_types , header=True)
for line in dataset.take(10):
print([item.numpy() for item in line])
[0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n'] [1, b'female', 38.0, 1, 0, 71.2833, b'First', b'C', b'Cherbourg', b'n'] [1, b'female', 26.0, 0, 0, 7.925, b'Third', b'unknown', b'Southampton', b'y'] [1, b'female', 35.0, 1, 0, 53.1, b'First', b'C', b'Southampton', b'n'] [0, b'male', 28.0, 0, 0, 8.4583, b'Third', b'unknown', b'Queenstown', b'y'] [0, b'male', 2.0, 3, 1, 21.075, b'Third', b'unknown', b'Southampton', b'n'] [1, b'female', 27.0, 0, 2, 11.1333, b'Third', b'unknown', b'Southampton', b'n'] [1, b'female', 14.0, 1, 0, 30.0708, b'Second', b'unknown', b'Cherbourg', b'n'] [1, b'female', 4.0, 1, 1, 16.7, b'Third', b'G', b'Southampton', b'n'] [0, b'male', 20.0, 0, 0, 8.05, b'Third', b'unknown', b'Southampton', b'y']
אם חלק מהעמודות ריקות, ממשק ברמה נמוכה זה מאפשר לך לספק ערכי ברירת מחדל במקום סוגי עמודות.
%%writefile missing.csv
1,2,3,4
,2,3,4
1,,3,4
1,2,,4
1,2,3,
,,,
Writing missing.csv
# Creates a dataset that reads all of the records from two CSV files, each with
# four float columns which may have missing values.
record_defaults = [999,999,999,999]
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults)
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (4,), types: tf.int32>
for line in dataset:
print(line.numpy())
[1 2 3 4] [999 2 3 4] [ 1 999 3 4] [ 1 2 999 4] [ 1 2 3 999] [999 999 999 999]
כברירת מחדל, CsvDataset
מניב כל עמודה של כל שורה בקובץ, מה שלא יכול להיות רצוי, למשל אם הקובץ מתחיל בשורת כותרת שיש להתעלם ממנה, או אם חלק מהעמודות אינן נדרשות בקלט. שורות ושדות אלה ניתנים להסרה באמצעות header
select_cols
בהתאמה.
# Creates a dataset that reads all of the records from two CSV files with
# headers, extracting float data from columns 2 and 4.
record_defaults = [999, 999] # Only provide defaults for the selected columns
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults, select_cols=[1, 3])
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (2,), types: tf.int32>
for line in dataset:
print(line.numpy())
[2 4] [2 4] [999 4] [2 4] [ 2 999] [999 999]
צורכת קבוצות קבצים
ישנם מערכי נתונים רבים המופצים כמערכת קבצים, כאשר כל קובץ הוא דוגמה.
flowers_root = tf.keras.utils.get_file(
'flower_photos',
'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
untar=True)
flowers_root = pathlib.Path(flowers_root)
ספריית הבסיס מכילה ספריה לכל מחלקה:
for item in flowers_root.glob("*"):
print(item.name)
sunflowers daisy LICENSE.txt roses tulips dandelion
הקבצים בכל ספריית מחלקה הם דוגמאות:
list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))
for f in list_ds.take(5):
print(f.numpy())
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/4868595281_1e58083785.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/daisy/5883162120_dc7274af76_n.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/tulips/12883412424_cb5086b43f_n.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/roses/13264214185_d6aa79b3bd.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/roses/6690926183_afedba9f15_n.jpg'
קרא את הנתונים באמצעות הפונקציהtf.io.read_file
וחלץ את התווית מהנתיב,tf.io.read_file
זוגות (image, label)
:
def process_path(file_path):
label = tf.strings.split(file_path, os.sep)[-2]
return tf.io.read_file(file_path), label
labeled_ds = list_ds.map(process_path)
for image_raw, label_text in labeled_ds.take(1):
print(repr(image_raw.numpy()[:100]))
print()
print(label_text.numpy())
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xfe\x00\x0cAppleMark\n\xff\xe2\x05(ICC_PROFILE\x00\x01\x01\x00\x00\x05\x18appl\x02 \x00\x00scnrRGB XYZ \x07\xd3\x00\x07\x00\x01\x00\x00\x00\x00\x00\x00acspAPPL\x00\x00\x00\x00' b'sunflowers'
אצוות אלמנטים של מערכי נתונים
אצווה פשוט
הצורה הפשוטה ביותר של ערימות קבוצה של ערימות n
אלמנטים עוקבים של מערך נתונים לאלמנט יחיד. טרנספורמציית Dataset.batch()
עושה בדיוק את זה, עם אילוצים זהים tf.stack()
, המופעל על כל רכיב האלמנטים: כלומר עבור כל רכיב i , על כל האלמנטים להיות בעלי טנסור של אותה צורה בדיוק.
inc_dataset = tf.data.Dataset.range(100)
dec_dataset = tf.data.Dataset.range(0, -100, -1)
dataset = tf.data.Dataset.zip((inc_dataset, dec_dataset))
batched_dataset = dataset.batch(4)
for batch in batched_dataset.take(4):
print([arr.numpy() for arr in batch])
[array([0, 1, 2, 3]), array([ 0, -1, -2, -3])] [array([4, 5, 6, 7]), array([-4, -5, -6, -7])] [array([ 8, 9, 10, 11]), array([ -8, -9, -10, -11])] [array([12, 13, 14, 15]), array([-12, -13, -14, -15])]
בעוד ש- tf.data
מנסה להפיץ מידע על צורות, הגדרות ברירת המחדל של Dataset.batch
גורמות לגודל אצווה לא ידוע מכיוון Dataset.batch
האחרונה עשויה שלא להיות מלאה. שימו לב ל- None
s בצורה:
batched_dataset
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.int64)>
השתמש בארגומנט drop_remainder
כדי להתעלם מאותה אצווה אחרונה ולקבל התפשטות צורה מלאה:
batched_dataset = dataset.batch(7, drop_remainder=True)
batched_dataset
<BatchDataset shapes: ((7,), (7,)), types: (tf.int64, tf.int64)>
אצווה טנזרים עם ריפוד
המתכון שלעיל עובד עבור טנזורים שכולם באותו הגודל. עם זאת, מודלים רבים (לדוגמא דגמי רצף) עובדים עם נתוני קלט שיכולים להיות בעלי גודל משתנה (למשל רצפים באורכים שונים). כדי לטפל במקרה זה, הטרנספורמציה Dataset.padded_batch
מאפשרת לך לאצווה טנזרים בעלי צורה שונה על ידי ציון ממד אחד או יותר שבו הם עשויים להיות מרופדים.
dataset = tf.data.Dataset.range(100)
dataset = dataset.map(lambda x: tf.fill([tf.cast(x, tf.int32)], x))
dataset = dataset.padded_batch(4, padded_shapes=(None,))
for batch in dataset.take(2):
print(batch.numpy())
print()
[[0 0 0] [1 0 0] [2 2 0] [3 3 3]] [[4 4 4 4 0 0 0] [5 5 5 5 5 0 0] [6 6 6 6 6 6 0] [7 7 7 7 7 7 7]]
טרנספורמציית Dataset.padded_batch
מאפשרת לך להגדיר ריפוד שונה לכל מימד של כל רכיב, והוא עשוי להיות באורך משתנה (מסומן ב- None
בדוגמה לעיל) או באורך קבוע. אפשר גם לעקוף את ערך הריפוד, אשר ברירת המחדל היא 0.
הכשרת תהליכי עבודה
עיבוד תקופות מרובות
ממשק ה- API של tf.data
מציע שתי דרכים עיקריות לעיבוד מספר תקופות של אותם נתונים.
הדרך הפשוטה ביותר לחזור על מערך נתונים בתקופות מרובות היא להשתמש Dataset.repeat()
. ראשית, צור מערך נתונים של נתונים טיטניים:
titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
def plot_batch_sizes(ds):
batch_sizes = [batch.shape[0] for batch in ds]
plt.bar(range(len(batch_sizes)), batch_sizes)
plt.xlabel('Batch number')
plt.ylabel('Batch size')
החלת Dataset.repeat()
ללא ארגומנטים תחזור על הקלט ללא הגבלת זמן.
טרנספורמציית Dataset.repeat
. Dataset.repeat
משרשרת את טיעוניה מבלי לסמן את סיומה של תקופה אחת ואת תחילת העידן הבא. מסיבה זו, Dataset.batch
המיושם לאחר Dataset.repeat
יניב קבוצות העומדות על גבולות העידן:
titanic_batches = titanic_lines.repeat(3).batch(128)
plot_batch_sizes(titanic_batches)
אם אתה זקוק להפרדה ברורה של עידן, שים את Dataset.batch
לפני החזרה:
titanic_batches = titanic_lines.batch(128).repeat(3)
plot_batch_sizes(titanic_batches)
אם ברצונך לבצע חישוב מותאם אישית (למשל לאסוף נתונים סטטיסטיים) בסוף כל תקופה, הפשוטה ביותר להפעיל מחדש את איטרציה של מערך הנתונים בכל תקופה:
epochs = 3
dataset = titanic_lines.batch(128)
for epoch in range(epochs):
for batch in dataset:
print(batch.shape)
print("End of epoch: ", epoch)
(128,) (128,) (128,) (128,) (116,) End of epoch: 0 (128,) (128,) (128,) (128,) (116,) End of epoch: 1 (128,) (128,) (128,) (128,) (116,) End of epoch: 2
דשדוש נתוני קלט באופן אקראי
טרנספורמציית Dataset.shuffle()
שומרת על מאגר בגודל קבוע ובוחרת את האלמנט הבא באופן אחיד באופן אקראי מאותו מאגר.
הוסף אינדקס למערך הנתונים כדי שתוכל לראות את ההשפעה:
lines = tf.data.TextLineDataset(titanic_file)
counter = tf.data.experimental.Counter()
dataset = tf.data.Dataset.zip((counter, lines))
dataset = dataset.shuffle(buffer_size=100)
dataset = dataset.batch(20)
dataset
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>
מכיוון ש- buffer_size
הוא 100 וגודל האצווה הוא 20, האצווה הראשונה לא מכילה אלמנטים עם אינדקס מעל 120.
n,line_batch = next(iter(dataset))
print(n.numpy())
[ 90 75 39 84 102 5 98 101 51 72 54 33 104 59 110 29 92 50 36 103]
כמו ב- Dataset.batch
, הסדר ביחס ל- Dataset.repeat
. Dataset.repeat
העניינים.
Dataset.shuffle
אינו מסמן את סיומה של תקופה עד Dataset.shuffle
הדשדוש ריק. אז דשדוש שהוצב לפני חזרה יראה כל אלמנט של תקופה אחת לפני שעבר למשנהו:
dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.shuffle(buffer_size=100).batch(10).repeat(2)
print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(60).take(5):
print(n.numpy())
Here are the item ID's near the epoch boundary: [550 618 614 435 556 530 578 451 590 604] [464 453 610 412 282 596 601 612 584 606] [368 469 575 607 586 537 444 300] [ 15 98 65 26 40 39 101 54 32 10] [ 8 102 68 108 12 96 2 87 80 37]
shuffle_repeat = [n.numpy().mean() for n, line_batch in shuffled]
plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7f3f083eebe0>
אבל חזרה לפני דשדוש מערבבת את גבולות העידן יחד:
dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.repeat(2).shuffle(buffer_size=100).batch(10)
print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(55).take(15):
print(n.numpy())
Here are the item ID's near the epoch boundary: [576 618 527 9 602 612 21 574 504 622] [623 26 32 616 626 482 617 598 0 614] [476 1 473 14 10 267 29 31 43 48] [588 13 470 467 12 596 619 46 28 528] [609 2 52 542 607 23 35 38 620 523] [509 477 571 15 56 74 565 525 58 19] [359 40 22 627 317 54 526 16 562 33] [ 67 500 584 531 49 86 51 81 78 583] [ 24 557 452 47 124 485 610 45 27 17] [379 66 85 91 599 97 499 112 108 11] [ 39 164 101 96 543 64 109 564 82 18] [533 120 30 63 115 88 95 75 133 34] [ 92 65 102 132 76 119 131 475 572 50] [ 94 145 144 603 152 505 621 140 448 122] [ 70 159 146 84 71 160 42 72 41 139]
repeat_shuffle = [n.numpy().mean() for n, line_batch in shuffled]
plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.plot(repeat_shuffle, label="repeat().shuffle()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7f3f0838c860>
עיבוד נתונים מראש
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)
ממפה אותו דרך מערך הנתונים.
images_ds = list_ds.map(parse_image)
for image, label in images_ds.take(2):
show(image, label)
החלת הגיון פיתון שרירותי
מטעמי ביצוע, השתמש בפעולות TensorFlow לעיבוד מראש של הנתונים שלך במידת האפשר. עם זאת, לפעמים כדאי להתקשר לספריות פייתון חיצוניות בעת ניתוח נתוני הקלט שלך. אתה יכול להשתמש tf.py_function()
הפעולה בתוך Dataset.map()
טרנספורמציה.
לדוגמה, אם אתה רוצה להחיל רוטציה אקראית, tf.image
מודול יש רק tf.image.rot90
, אשר אינו שימושי מאוד עבור הגדלת התמונה.
כדי להדגים את tf.py_function
, נסה להשתמש בפונקציה scipy.ndimage.rotate
במקום זאת:
import scipy.ndimage as ndimage
def random_rotate_image(image):
image = ndimage.rotate(image, np.random.uniform(-30, 30), reshape=False)
return image
image, label = next(iter(images_ds))
image = random_rotate_image(image)
show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
כדי להשתמש בפונקציה זו עם 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).
ניתוח tf.Example
הודעות חיץ פרוטוקול tf.Example
צינורות קלט רבים מחלצים את tf.train.Example
להודעות מאגר פרוטוקול מתבנית TFRecord. כל רשומת tf.train.Example
מכילה אחד או יותר "מאפיינים", וצינור הקלט ממיר בדרך כלל תכונות אלה לטנזורים.
fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>
אתה יכול לעבוד עם tf.train.Example
מחוץ ל-tf.data.Dataset
כדי להבין את הנתונים:
raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())
feature = parsed.features.feature
raw_img = feature['image/encoded'].bytes_list.value[0]
img = tf.image.decode_png(raw_img)
plt.imshow(img)
plt.axis('off')
_ = plt.title(feature["image/text"].bytes_list.value[0])
raw_example = next(iter(dataset))
def tf_parse(eg):
example = tf.io.parse_example(
eg[tf.newaxis], {
'image/encoded': tf.io.FixedLenFeature(shape=(), dtype=tf.string),
'image/text': tf.io.FixedLenFeature(shape=(), dtype=tf.string)
})
return example['image/encoded'][0], example['image/text'][0]
img, txt = tf_parse(raw_example)
print(txt.numpy())
print(repr(img.numpy()[:20]), "...")
b'Rue Perreyon' b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02X' ...
decoded = dataset.map(tf_parse)
decoded
<MapDataset shapes: ((), ()), types: (tf.string, tf.string)>
image_batch, text_batch = next(iter(decoded.batch(10)))
image_batch.shape
TensorShape([10])
חלונות סדרות זמן
לקבלת דוגמה לסדרת זמן מקצה לסיום ראה: חיזוי סדרות זמן .
נתוני סדרות זמן מסודרים לרוב עם ציר הזמן שלם.
השתמש פשוט Dataset.range
להפגין:
range_ds = tf.data.Dataset.range(100000)
בדרך כלל, מודלים המבוססים על נתונים מסוג זה ירצו פרוסת זמן רצופה.
הגישה הפשוטה ביותר תהיה קבוצה של הנתונים:
באמצעות batch
batches = range_ds.batch(10, drop_remainder=True)
for batch in batches.take(5):
print(batch.numpy())
[0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19] [20 21 22 23 24 25 26 27 28 29] [30 31 32 33 34 35 36 37 38 39] [40 41 42 43 44 45 46 47 48 49]
לחלופין, כדי לחזות צפיפות צעד אחד לעתיד, תוכל לשנות את התכונות והתוויות צעד אחד ביחס לשני:
def dense_1_step(batch):
# Shift features and labels one step relative to each other.
return batch[:-1], batch[1:]
predict_dense_1_step = batches.map(dense_1_step)
for features, label in predict_dense_1_step.take(3):
print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8] => [1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18] => [11 12 13 14 15 16 17 18 19] [20 21 22 23 24 25 26 27 28] => [21 22 23 24 25 26 27 28 29]
כדי לחזות חלון שלם במקום קיזוז קבוע ניתן לפצל את הקבוצות לשני חלקים:
batches = range_ds.batch(15, drop_remainder=True)
def label_next_5_steps(batch):
return (batch[:-5], # Take the first 5 steps
batch[-5:]) # take the remainder
predict_5_steps = batches.map(label_next_5_steps)
for features, label in predict_5_steps.take(3):
print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9] => [10 11 12 13 14] [15 16 17 18 19 20 21 22 23 24] => [25 26 27 28 29] [30 31 32 33 34 35 36 37 38 39] => [40 41 42 43 44]
כדי לאפשר חפיפה כלשהי בין התכונות של אצווה אחת לתוויות של אחר, השתמש ב- Dataset.zip
:
feature_length = 10
label_length = 3
features = range_ds.batch(feature_length, drop_remainder=True)
labels = range_ds.batch(feature_length).skip(1).map(lambda labels: labels[:label_length])
predicted_steps = tf.data.Dataset.zip((features, labels))
for features, label in predicted_steps.take(5):
print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9] => [10 11 12] [10 11 12 13 14 15 16 17 18 19] => [20 21 22] [20 21 22 23 24 25 26 27 28 29] => [30 31 32] [30 31 32 33 34 35 36 37 38 39] => [40 41 42] [40 41 42 43 44 45 46 47 48 49] => [50 51 52]
באמצעות window
בזמן השימוש ב- Dataset.batch
עובד, ישנם מצבים בהם ייתכן שתזדקק לשליטה טובה יותר. שיטת Dataset.window
נותנת לך שליטה מלאה, אך דורשת זהירות מסוימת: היא מחזירה Dataset
של Datasets
. ראה מבנה מערך הנתונים לפרטים.
window_size = 5
windows = range_ds.window(window_size, shift=1)
for sub_ds in windows.take(5):
print(sub_ds)
<_VariantDataset shapes: (), types: tf.int64> <_VariantDataset shapes: (), types: tf.int64> <_VariantDataset shapes: (), types: tf.int64> <_VariantDataset shapes: (), types: tf.int64> <_VariantDataset shapes: (), types: tf.int64>
השיטה Dataset.flat_map
יכולה לקחת מערך נתונים של מערכי נתונים Dataset.flat_map
אותו למערך נתונים יחיד:
for x in windows.flat_map(lambda x: x).take(30):
print(x.numpy(), end=' ')
0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9
כמעט בכל המקרים, תרצה .batch
תחילה את מערך הנתונים:
def sub_to_batch(sub):
return sub.batch(window_size, drop_remainder=True)
for example in windows.flat_map(sub_to_batch).take(5):
print(example.numpy())
[0 1 2 3 4] [1 2 3 4 5] [2 3 4 5 6] [3 4 5 6 7] [4 5 6 7 8]
עכשיו אתה יכול לראות שארגומנט shift
שולט כמה כל חלון עובר.
אם מרכיבים זאת, תוכלו לכתוב את הפונקציה הזו:
def make_window_dataset(ds, window_size=5, shift=1, stride=1):
windows = ds.window(window_size, shift=shift, stride=stride)
def sub_to_batch(sub):
return sub.batch(window_size, drop_remainder=True)
windows = windows.flat_map(sub_to_batch)
return windows
ds = make_window_dataset(range_ds, window_size=10, shift = 5, stride=3)
for example in ds.take(10):
print(example.numpy())
[ 0 3 6 9 12 15 18 21 24 27] [ 5 8 11 14 17 20 23 26 29 32] [10 13 16 19 22 25 28 31 34 37] [15 18 21 24 27 30 33 36 39 42] [20 23 26 29 32 35 38 41 44 47] [25 28 31 34 37 40 43 46 49 52] [30 33 36 39 42 45 48 51 54 57] [35 38 41 44 47 50 53 56 59 62] [40 43 46 49 52 55 58 61 64 67] [45 48 51 54 57 60 63 66 69 72]
אז קל לחלץ תוויות, כמו קודם:
dense_labels_ds = ds.map(dense_1_step)
for inputs,labels in dense_labels_ds.take(3):
print(inputs.numpy(), "=>", labels.numpy())
[ 0 3 6 9 12 15 18 21 24] => [ 3 6 9 12 15 18 21 24 27] [ 5 8 11 14 17 20 23 26 29] => [ 8 11 14 17 20 23 26 29 32] [10 13 16 19 22 25 28 31 34] => [13 16 19 22 25 28 31 34 37]
דגימה מחדש
כשעובדים עם מערך נתונים שאינו מאוזן מאוד בכיתה, כדאי לדגום מחדש את מערך הנתונים. tf.data
מספק שתי שיטות לעשות זאת. מערך ההונאה בכרטיסי אשראי הוא דוגמה טובה לבעיה מסוג זה.
zip_path = tf.keras.utils.get_file(
origin='https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip',
fname='creditcard.zip',
extract=True)
csv_path = zip_path.replace('.zip', '.csv')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip 69156864/69155632 [==============================] - 3s 0us/step
creditcard_ds = tf.data.experimental.make_csv_dataset(
csv_path, batch_size=1024, label_name="Class",
# Set the column types: 30 floats and an int.
column_defaults=[float()]*30+[int()])
עכשיו, בדקו את התפלגות השיעורים, היא מוטה מאוד:
def count(counts, batch):
features, labels = batch
class_1 = labels == 1
class_1 = tf.cast(class_1, tf.int32)
class_0 = labels == 0
class_0 = tf.cast(class_0, tf.int32)
counts['class_0'] += tf.reduce_sum(class_0)
counts['class_1'] += tf.reduce_sum(class_1)
return counts
counts = creditcard_ds.take(10).reduce(
initial_state={'class_0': 0, 'class_1': 0},
reduce_func = count)
counts = np.array([counts['class_0'].numpy(),
counts['class_1'].numpy()]).astype(np.float32)
fractions = counts/counts.sum()
print(fractions)
[0.9952 0.0048]
גישה נפוצה לאימונים עם מערך נתונים לא מאוזן היא לאזן אותו. tf.data
כולל כמה שיטות המאפשרות זרימת עבודה זו:
דגימת מערכי נתונים
גישה אחת sample_from_datasets
מחדש של מערך נתונים היא להשתמש ב- sample_from_datasets
. זה ישים יותר כאשר יש לך נפרדותdata.Dataset
עבור כל מחלקה.
כאן פשוט השתמש במסנן כדי ליצור אותם מנתוני הונאת כרטיסי האשראי:
negative_ds = (
creditcard_ds
.unbatch()
.filter(lambda features, label: label==0)
.repeat())
positive_ds = (
creditcard_ds
.unbatch()
.filter(lambda features, label: label==1)
.repeat())
for features, label in positive_ds.batch(10).take(1):
print(label.numpy())
[1 1 1 1 1 1 1 1 1 1]
כדי להשתמש ב- tf.data.experimental.sample_from_datasets
להעביר את מערכי הנתונים ואת המשקל עבור כל אחד מהם:
balanced_ds = tf.data.experimental.sample_from_datasets(
[negative_ds, positive_ds], [0.5, 0.5]).batch(10)
כעת מערך הנתונים מייצר דוגמאות של כל מחלקה בהסתברות 50/50:
for features, labels in balanced_ds.take(10):
print(labels.numpy())
[1 0 1 0 1 1 0 0 1 0] [0 0 1 1 1 1 0 0 1 1] [1 1 0 1 1 0 1 1 0 0] [1 0 1 1 0 0 0 0 0 1] [1 1 0 1 1 0 0 0 1 0] [1 0 1 1 1 0 0 0 1 1] [0 1 1 0 0 0 1 0 1 0] [0 1 1 1 1 0 1 1 1 0] [0 0 1 1 1 1 0 0 1 1] [0 0 0 0 1 0 0 1 0 0]
דגימה מחדש של דחייה
בעיה אחת עם מעל experimental.sample_from_datasets
הגישה היא שזה צריך נפרדtf.data.Dataset
לכל הכיתה. השימוש ב- Dataset.filter
עובד, אך גורם לכך שכל הנתונים נטענים פעמיים.
ניתן להחיל את הפונקציה data.experimental.rejection_resample
על מערך נתונים כדי לאזן אותו מחדש, תוך טעינה רק פעם אחת. אלמנטים יושמטו ממערך הנתונים כדי להשיג איזון.
data.experimental.rejection_resample
לוקח ארגומנט class_func
. class_func
זה מוחל על כל רכיב מערך נתונים ומשמש לקביעת איזו מחלקה דוגמה שייכת למטרות איזון.
האלמנטים של creditcard_ds
הם כבר (features, label)
זוגות. אז class_func
רק צריך להחזיר את התוויות האלה:
def class_func(features, label):
return label
הדגימה המחודשת זקוקה גם להפצת יעד, ואופציה לאומדן הפצה ראשוני:
resampler = tf.data.experimental.rejection_resample(
class_func, target_dist=[0.5, 0.5], initial_dist=fractions)
unbatch
עוסקת בדוגמאות בודדות, לכן עליך unbatch
את unbatch
של מערך הנתונים לפני החלת unbatch
מחדש:
resample_ds = creditcard_ds.unbatch().apply(resampler).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/data/experimental/ops/resampling.py:156: Print (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2018-08-20. Instructions for updating: Use tf.print instead of tf.Print. Note that tf.print returns a no-output operator that directly prints the output. Outside of defuns or eager mode, this operator will not be executed unless it is directly specified in session.run or used as a control dependency for other operators. This is only a concern in graph mode. Below is an example of how to ensure tf.print executes in graph mode:
מחזיר הדגימה מחדש יוצר (class, example)
זוגות מהפלט של class_func
. במקרה זה, example
כבר הייתה זוג (feature, label)
, לכן השתמש map
כדי להפיל את העותק הנוסף של התוויות:
balanced_ds = resample_ds.map(lambda extra_label, features_and_label: features_and_label)
כעת מערך הנתונים מייצר דוגמאות של כל מחלקה בהסתברות 50/50:
for features, labels in balanced_ds.take(10):
print(labels.numpy())
[0 1 0 1 1 1 0 1 1 1] [0 1 1 1 1 0 1 0 0 1] [1 0 1 1 0 1 0 1 1 1] [1 0 0 1 1 0 0 0 1 0] [1 1 1 1 1 0 0 0 1 0] [0 0 0 0 1 0 1 1 0 1] [0 1 0 1 1 1 0 1 1 0] [1 0 0 0 0 1 0 1 0 0] [0 1 1 1 0 1 1 1 1 0] [0 1 1 1 1 0 1 1 1 0]
מחסום איטרטור
Tensorflow תומך בנטילת מחסומים כך שכאשר תהליך האימון שלך יופעל מחדש הוא יוכל לשחזר את נקודת הביקורת האחרונה כדי לשחזר את מרבית התקדמותה. בנוסף לבדיקת משתני המודל, תוכל גם לבדוק את התקדמות איטרטור מערך הנתונים. זה יכול להיות שימושי אם יש לך מערך נתונים גדול ואינך רוצה להתחיל את מערך הנתונים מההתחלה בכל הפעלה מחדש. שים לב עם זאת שמחסומי איטרציה עשויים להיות גדולים, מכיוון שטרנספורמציות כגון shuffle
prefetch
מחייבות אלמנטים של חציצה בתוך האיטרטור.
כדי לכלול את האיטרטור שלך במחסום, העביר את tf.train.Checkpoint
.
range_ds = tf.data.Dataset.range(20)
iterator = iter(range_ds)
ckpt = tf.train.Checkpoint(step=tf.Variable(0), iterator=iterator)
manager = tf.train.CheckpointManager(ckpt, '/tmp/my_ckpt', max_to_keep=3)
print([next(iterator).numpy() for _ in range(5)])
save_path = manager.save()
print([next(iterator).numpy() for _ in range(5)])
ckpt.restore(manager.latest_checkpoint)
print([next(iterator).numpy() for _ in range(5)])
[0, 1, 2, 3, 4] [5, 6, 7, 8, 9] [5, 6, 7, 8, 9]
שימוש ב- tf.data עם tf.keras
ממשק ה- API של tf.keras
מפשט היבטים רבים של יצירה וביצוע של מודלים של למידת מכונה. ממשקי ה- API של .fit()
ו- .evaluate()
ו- .predict()
תומכים .predict()
נתונים .predict()
. הנה מערך נתונים מהיר והתקנת מודל:
train, test = tf.keras.datasets.fashion_mnist.load_data()
images, labels = train
images = images/255.0
labels = labels.astype(np.int32)
fmnist_train_ds = tf.data.Dataset.from_tensor_slices((images, labels))
fmnist_train_ds = fmnist_train_ds.shuffle(5000).batch(32)
model = tf.keras.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(10)
])
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
העברת מערך נתונים של זוגות (feature, label)
היא כל מה שנדרש עבור Model.fit
ו- Model.evaluate
:
model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2 1875/1875 [==============================] - 4s 2ms/step - loss: 0.7804 - accuracy: 0.7374 Epoch 2/2 1875/1875 [==============================] - 3s 2ms/step - loss: 0.4711 - accuracy: 0.8393 <tensorflow.python.keras.callbacks.History at 0x7f3ef05b1390>
אם אתה מעביר מערך אינסופי, למשל על ידי קריאה ל- Dataset.repeat()
, אתה רק צריך להעביר את הארגומנט steps_per_epoch
:
model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2 20/20 [==============================] - 0s 2ms/step - loss: 0.4312 - accuracy: 0.8562 Epoch 2/2 20/20 [==============================] - 0s 2ms/step - loss: 0.4509 - accuracy: 0.8344 <tensorflow.python.keras.callbacks.History at 0x7f3ef062e198>
להערכה תוכלו לעבור את מספר שלבי ההערכה:
loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4347 - accuracy: 0.8516 Loss : 0.43466493487358093 Accuracy : 0.8515999913215637
עבור מערכי נתונים ארוכים, הגדר את מספר השלבים להערכה:
loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.4131 - accuracy: 0.8750 Loss : 0.41311272978782654 Accuracy : 0.875
התוויות אינן נדרשות כאשר מתקשרים ל- Model.predict
.
predict_ds = tf.data.Dataset.from_tensor_slices(images).batch(32)
result = model.predict(predict_ds, steps = 10)
print(result.shape)
(320, 10)
אך מתעלמים מהתוויות אם אתה מעביר מערך נתונים המכיל אותם:
result = model.predict(fmnist_train_ds, steps = 10)
print(result.shape)
(320, 10)