![]() | ![]() | ![]() | ![]() |
Mit der tf.data
API können Sie komplexe Eingabe-Pipelines aus einfachen, wiederverwendbaren Teilen erstellen. Beispielsweise kann die Pipeline für ein Bildmodell Daten aus Dateien in einem verteilten Dateisystem aggregieren, zufällige Störungen auf jedes Bild anwenden und zufällig ausgewählte Bilder zu einem Stapel für das Training zusammenführen. Die Pipeline für ein Textmodell kann das Extrahieren von Symbolen aus Rohtextdaten, das Konvertieren dieser Symbole in das Einbetten von Bezeichnern mit einer Nachschlagetabelle und das Stapeln von Sequenzen unterschiedlicher Länge umfassen. Die tf.data
API ermöglicht es, große Datenmengen zu verarbeiten, aus verschiedenen Datenformaten zu lesen und komplexe Transformationen durchzuführen.
Die tf.data
API führt einetf.data.Dataset
Abstraktion ein, die eine Folge von Elementen darstellt, in denen jedes Element aus einer oder mehreren Komponenten besteht. Beispielsweise kann in einer Bildpipeline ein Element ein einzelnes Trainingsbeispiel sein, wobei ein Paar Tensorkomponenten das Bild und seine Beschriftung darstellt.
Es gibt zwei verschiedene Möglichkeiten, einen Datensatz zu erstellen:
Eine Datenquelle konstruiert einen
Dataset
von Daten im Speicher gespeichert oder in einer oder mehreren Dateien.Ein Datentransformationskonstrukten eine Datenmenge von einem oder mehreren
tf.data.Dataset
Objekten.
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)
Grundlegende Mechanik
Um eine Eingabe Pipeline zu erstellen, müssen Sie mit einer Datenquelle starten. Um zum Beispiel eines konstruieren Dataset
von Daten im Speicher, können Sie tf.data.Dataset.from_tensors()
oder tf.data.Dataset.from_tensor_slices()
. Wenn Ihre Eingabedaten in einer Datei im empfohlenen TFRecord-Format gespeichert sind, können Sie tf.data.TFRecordDataset()
.
Sobald Sie ein Dataset
Objekt haben, können Sie es in ein neues Dataset
umwandeln , indem Sie Methodenaufrufe für dastf.data.Dataset
Objekttf.data.Dataset
. Sie können beispielsweise Transformationen pro Element wie Dataset.map()
und Transformationen mit mehreren Elementen wie Dataset.batch()
. Eine vollständige Liste der Transformationen finden Sie in der Dokumentation zutf.data.Dataset
.
Das Dataset
Objekt ist ein iterierbares Python-Objekt. Dies ermöglicht es, seine Elemente mit einer for-Schleife zu konsumieren:
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
Oder indem Sie explizit einen Python-Iterator mit iter
erstellen und seine Elemente mit next
:
it = iter(dataset)
print(next(it).numpy())
8
Alternativ können Datensatzelemente mithilfe der reduce
werden, bei der alle Elemente reduziert werden, um ein einziges Ergebnis zu erzielen. Das folgende Beispiel zeigt, wie die reduce
, um die Summe eines Datensatzes von Ganzzahlen zu berechnen.
print(dataset.reduce(0, lambda state, value: state + value).numpy())
22
Datensatzstruktur
Ein Datensatz enthält Elemente, die jeweils dieselbe (verschachtelte) Struktur haben, und die einzelnen Komponenten der Struktur können von jedem Typ sein, der durch tf.TypeSpec
dargestellt werden tf.TypeSpec
, einschließlich tf.Tensor
, tf.sparse.SparseTensor
,tf.RaggedTensor
, tf.TensorArray
, odertf.data.Dataset
.
Mit der Eigenschaft Dataset.element_spec
können Sie den Typ jeder Elementkomponente überprüfen. Die Eigenschaft gibt eine verschachtelte Struktur von tf.TypeSpec
Objekten zurück, die mit der Struktur des Elements übereinstimmt. tf.TypeSpec
kann eine einzelne Komponente, ein Tupel von Komponenten oder ein verschachteltes Tupel von Komponenten sein. Beispielsweise:
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
Die Dataset
Transformationen unterstützen Datasets beliebiger Struktur. Bei Verwendung der Dataset.map()
und Dataset.filter()
, die auf jedes Element eine Funktion anwenden, bestimmt die Elementstruktur die Argumente der Funktion:
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,)
Eingabedaten lesen
NumPy-Arrays verbrauchen
Weitere Beispiele finden Sie unter Laden von NumPy-Arrays .
Wenn alle Ihre Eingangsdaten passt in Speicher, der einfachste Weg , um eine erstellen Dataset
von ihnen ist , sie zu konvertieren , um tf.Tensor
Objekte und die Verwendung 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)>
Python-Generatoren verbrauchen
Eine weitere häufige Datenquelle, die leicht alstf.data.Dataset
aufgenommen werden kann, ist der Python-Generator.
def count(stop):
i = 0
while i<stop:
yield i
i += 1
for n in count(5):
print(n)
0 1 2 3 4
Der Konstruktor Dataset.from_generator
konvertiert den Python-Generator in ein voll funktionsfähigestf.data.Dataset
.
Der Konstruktor verwendet einen aufrufbaren als Eingabe, keinen Iterator. Dadurch kann der Generator am Ende neu gestartet werden. Es wird ein optionales Argument args
, das als Argument des Aufrufers übergeben wird.
Das Argument output_types
ist erforderlich, da tf.data
intern einen tf.Graph
und für Diagrammkanten ein tf.dtype
erforderlich 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]
Das Argument output_shapes
ist nicht erforderlich , wird jedoch dringend empfohlen, da viele Tensorflow-Operationen keine Tensoren mit unbekanntem Rang unterstützen. Wenn die Länge einer bestimmten Achse unbekannt oder variabel ist, setzen Sie sie in den output_shapes
None
.
Es ist auch wichtig zu beachten, dass die output_shapes
und output_types
denselben Verschachtelungsregeln wie andere Dataset-Methoden folgen.
Hier ist ein Beispielgenerator, der beide Aspekte demonstriert. Er gibt Tupel von Arrays zurück, wobei das zweite Array ein Vektor mit unbekannter Länge ist.
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]
Die erste Ausgabe ist eine int32
die zweite ist eine float32
.
Das erste Element ist ein Skalar, Form ()
, und das zweite ist ein Vektor unbekannter Länge, Form (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)>
Jetzt kann es wie ein regulärestf.data.Dataset
. Beachten Sie, dass Sie beim Stapeln eines Datasets mit variabler Form Dataset.padded_batch
verwenden 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 ]]
Um ein realistischeres Beispiel versuchen Einwickeln preprocessing.image.ImageDataGenerator
alstf.data.Dataset
.
Laden Sie zuerst die Daten herunter:
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
Erstellen Sie den 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-Daten verbrauchen
Ein End-to-End-Beispiel finden Sie unter Laden von TFRecords .
Die tf.data
API unterstützt eine Vielzahl von Dateiformaten, sodass Sie große Datenmengen verarbeiten können, die nicht in den Speicher passen. Beispielsweise ist das TFRecord-Dateiformat ein einfaches aufzeichnungsorientiertes Binärformat, das viele TensorFlow-Anwendungen zum Trainieren von Daten verwenden. Mit der Klasse tf.data.TFRecordDataset
können Sie den Inhalt einer oder mehrerer TFRecord-Dateien als Teil einer Eingabe-Pipeline streamen.
Hier ist ein Beispiel mit der Testdatei der French Street Name Signs (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
Das filenames
für den TFRecordDataset
Initialisierer kann entweder eine Zeichenfolge, eine Liste von Zeichenfolgen oder ein tf.Tensor
von Zeichenfolgen sein. Wenn Sie also zwei Dateigruppen für Schulungs- und Validierungszwecke haben, können Sie eine Factory-Methode erstellen, die den Datensatz erstellt, wobei Dateinamen als Eingabeargument verwendet werden:
dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>
Viele TensorFlow-Projekte verwenden serialisierte tf.train.Example
Datensätze in ihren TFRecord-Dateien. Diese müssen dekodiert werden, bevor sie überprüft werden können:
raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())
parsed.features.feature['image/text']
bytes_list { value: "Rue Perreyon" }
Textdaten verbrauchen
Ein Ende-zu-Ende-Beispiel finden Sie unter Laden von Text .
Viele Datensätze werden als eine oder mehrere Textdateien verteilt. Das tf.data.TextLineDataset
bietet eine einfache Möglichkeit, Zeilen aus einer oder mehreren Textdateien zu extrahieren. Bei einem oder mehreren Dateinamen erzeugt ein TextLineDataset
ein Element mit Zeichenfolgenwert pro Zeile dieser Dateien.
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)
Hier sind die ersten Zeilen der ersten Datei:
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)'
Verwenden Sie Dataset.interleave
Zeilen zwischen Dateien zu Dataset.interleave
. Dies erleichtert das Zusammenmischen von Dateien. Hier sind die erste, zweite und dritte Zeile jeder Übersetzung:
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'
Standardmäßig liefert ein TextLineDataset
jede Zeile jeder Datei, was möglicherweise nicht wünschenswert ist, wenn die Datei beispielsweise mit einer Kopfzeile beginnt oder Kommentare enthält. Diese Zeilen können mit den Dataset.skip()
oder Dataset.filter()
werden. Hier überspringen Sie die erste Zeile und filtern dann, um nur Überlebende zu finden.
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-Daten verbrauchen
Weitere Beispiele finden Sie unter Laden von CSV-Dateien und Laden von Pandas DataFrames .
Das CSV-Dateiformat ist ein beliebtes Format zum Speichern von Tabellendaten im Klartext.
Beispielsweise:
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()
Wenn Ihre Daten in den Speicher Dataset.from_tensor_slices
, funktioniert dieselbe Dataset.from_tensor_slices
Methode für Wörterbücher, sodass diese Daten problemlos importiert werden können:
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'
Ein skalierbarerer Ansatz besteht darin, bei Bedarf von der Festplatte zu laden.
Das Modul tf.data
bietet Methoden zum Extrahieren von Datensätzen aus einer oder mehreren CSV-Dateien, die RFC 4180 entsprechen .
Die Funktion experimental.make_csv_dataset
ist die übergeordnete Schnittstelle zum Lesen von Gruppen von CSV-Dateien. Es unterstützt die Inferenz von Spaltentypen und viele andere Funktionen wie Stapeln und Mischen, um die Verwendung zu vereinfachen.
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']
Sie können das Argument select_columns
, wenn Sie nur eine Teilmenge von Spalten benötigen.
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']
Es gibt auch eine untergeordnete experimental.CsvDataset
Klasse, die eine feinkörnigere Steuerung bietet. Es wird keine Inferenz des Spaltentyps unterstützt. Stattdessen müssen Sie den Typ jeder Spalte angeben.
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']
Wenn einige Spalten leer sind, können Sie auf dieser übergeordneten Schnittstelle Standardwerte anstelle von Spaltentypen angeben.
%%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]
Standardmäßig liefert ein CsvDataset
jede Spalte jeder Zeile der Datei, was möglicherweise nicht wünschenswert ist, z. B. wenn die Datei mit einer Kopfzeile beginnt, die ignoriert werden sollte, oder wenn einige Spalten in der Eingabe nicht erforderlich sind. Diese Zeilen und Felder können mit den Argumenten header
und select_cols
entfernt werden.
# 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]
Dateisätze verbrauchen
Es gibt viele Datensätze, die als Dateisatz verteilt sind, wobei jede Datei ein Beispiel ist.
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)
Das Stammverzeichnis enthält ein Verzeichnis für jede Klasse:
for item in flowers_root.glob("*"):
print(item.name)
sunflowers daisy LICENSE.txt roses tulips dandelion
Die Dateien in jedem Klassenverzeichnis sind Beispiele:
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'
Lesen Sie die Daten mit der Funktiontf.io.read_file
und extrahieren Sie die Beschriftung aus dem Pfad.tf.io.read_file
(image, label)
Paare zurück:
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'
Batching von Datensatzelementen
Einfache Dosierung
Die einfachste Form des Stapelns von n
aufeinanderfolgenden Elementen eines Datensatzes zu einem einzelnen Element. Die Dataset.batch()
-Transformation führt genau dies mit denselben Einschränkungen aus wie der Operator tf.stack()
, der auf jede Komponente der Elemente angewendet wird: dh für jede Komponente i müssen alle Elemente einen Tensor mit genau derselben Form haben.
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])]
Während tf.data
versucht, tf.data
, führen die Standardeinstellungen von Dataset.batch
zu einer unbekannten Dataset.batch
da der letzte Stapel möglicherweise nicht voll ist. Beachten Sie die None
s in der Form:
batched_dataset
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.int64)>
Verwenden Sie das Argument drop_remainder
, um diesen letzten Stapel zu ignorieren und die vollständige drop_remainder
zu erhalten:
batched_dataset = dataset.batch(7, drop_remainder=True)
batched_dataset
<BatchDataset shapes: ((7,), (7,)), types: (tf.int64, tf.int64)>
Batching Tensoren mit Polsterung
Das obige Rezept funktioniert für Tensoren, die alle die gleiche Größe haben. Viele Modelle (z. B. Sequenzmodelle) arbeiten jedoch mit Eingabedaten, die unterschiedliche Größen haben können (z. B. Sequenzen unterschiedlicher Länge). Um diesen Fall zu Dataset.padded_batch
, können Sie mit der Dataset.padded_batch
Umwandlung Tensoren unterschiedlicher Form Dataset.padded_batch
, indem Sie eine oder mehrere Dimensionen Dataset.padded_batch
, in denen sie aufgefüllt werden können.
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]]
Mit der Dataset.padded_batch
Umwandlung können Sie für jede Dimension jeder Komponente unterschiedliche Auffüllungen Dataset.padded_batch
können eine variable Länge (im obigen Beispiel durch None
) oder eine konstante Länge haben. Es ist auch möglich, den Füllwert zu überschreiben, der standardmäßig 0 ist.
Trainingsworkflows
Verarbeitung mehrerer Epochen
Die tf.data
API bietet zwei Hauptmethoden zum Verarbeiten mehrerer Epochen derselben Daten.
Der einfachste Weg, ein Dataset in mehreren Epochen zu Dataset.repeat()
ist die Verwendung der Dataset.repeat()
Transformation. Erstellen Sie zunächst einen Datensatz mit Titandaten:
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')
Durch Anwenden der Dataset.repeat()
Transformation ohne Argumente wird die Eingabe auf unbestimmte Zeit wiederholt.
Die Dataset.repeat
Transformation verkettet ihre Argumente, ohne das Ende einer Epoche und den Beginn der nächsten Epoche zu signalisieren. Aus diesem Dataset.batch
Dataset.repeat
ein Dataset.batch
, der nach Dataset.batch
angewendet wird, Dataset.repeat
, die die Epochengrenzen überschreiten:
titanic_batches = titanic_lines.repeat(3).batch(128)
plot_batch_sizes(titanic_batches)
Wenn Sie eine klare Epochentrennung benötigen, setzen Sie Dataset.batch
vor die Wiederholung:
titanic_batches = titanic_lines.batch(128).repeat(3)
plot_batch_sizes(titanic_batches)
Wenn Sie am Ende jeder Epoche eine benutzerdefinierte Berechnung durchführen möchten (z. B. um Statistiken zu sammeln), ist es am einfachsten, die Datensatziteration für jede Epoche neu zu starten:
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
Zufälliges Mischen von Eingabedaten
Die Dataset.shuffle()
-Transformation verwaltet einen Puffer fester Größe und wählt das nächste Element gleichmäßig zufällig aus diesem Puffer aus.
Fügen Sie dem Datensatz einen Index hinzu, damit Sie den Effekt sehen können:
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)>
Da die buffer_size
100 und die buffer_size
20 beträgt, enthält der erste Stapel keine Elemente mit einem Index über 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]
Wie bei Dataset.batch
die Reihenfolge in Bezug auf Dataset.repeat
eine Dataset.repeat
.
Dataset.shuffle
signalisiert das Ende einer Epoche erst, wenn der Shuffle-Puffer leer ist. Ein Shuffle vor einer Wiederholung zeigt also jedes Element einer Epoche, bevor zur nächsten übergegangen wird:
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>
Aber eine Wiederholung vor einem Shuffle vermischt die Grenzen der Epoche:
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>
Daten vorverarbeiten
Die Dataset.map(f)
-Transformation erzeugt einen neuen Datensatz, indem eine bestimmte Funktion f
auf jedes Element des Eingabedatensatzes angewendet wird. Es basiert auf der Funktion map()
, die üblicherweise auf Listen (und andere Strukturen) in funktionalen Programmiersprachen angewendet wird. Die Funktion f
die tf.Tensor
Objekte, die ein einzelnes Element in der Eingabe darstellen, und gibt die tf.Tensor
Objekte zurück, die ein einzelnes Element im neuen Datensatz darstellen. Die Implementierung verwendet Standard-TensorFlow-Operationen, um ein Element in ein anderes umzuwandeln.
Dieser Abschnitt enthält allgemeine Beispiele für die Verwendung von Dataset.map()
.
Bilddaten dekodieren und ihre Größe ändern
Beim Trainieren eines neuronalen Netzwerks mit realen Bilddaten ist es häufig erforderlich, Bilder unterschiedlicher Größe in eine gemeinsame Größe zu konvertieren, damit sie in eine feste Größe gestapelt werden können.
Erstellen Sie den Datensatz mit den Blumendateinamen neu:
list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))
Schreiben Sie eine Funktion, die die Datensatzelemente bearbeitet.
# 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
Testen Sie, ob es funktioniert.
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)
Ordnen Sie es dem Datensatz zu.
images_ds = list_ds.map(parse_image)
for image, label in images_ds.take(2):
show(image, label)
Anwenden einer beliebigen Python-Logik
Verwenden Sie aus Leistungsgründen TensorFlow-Vorgänge, um Ihre Daten nach Möglichkeit vorzuverarbeiten. Manchmal ist es jedoch nützlich, externe Python-Bibliotheken aufzurufen, wenn Sie Ihre Eingabedaten analysieren. Sie können die Operation tf.py_function()
in einer Dataset.map()
-Transformation verwenden.
Wenn Sie beispielsweise eine zufällige Drehung anwenden möchten, verfügt das Modul tf.image
nur über tf.image.rot90
, was für die Bildvergrößerung nicht sehr nützlich ist.
Verwenden scipy.ndimage.rotate
stattdessen die Funktion tf.py_function
, um die Funktion scipy.ndimage.rotate
zu demonstrieren:
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).
Um diese Funktion mit Dataset.map
gelten dieselben Einschränkungen wie mit Dataset.from_generator
. Sie müssen die Rückgabeformen und -typen beschreiben, wenn Sie die Funktion anwenden:
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
Viele Eingabepipelines extrahieren tf.train.Example
aus einem TFRecord-Format. Jeder tf.train.Example
Datensatz enthält ein oder mehrere "Features", und die Eingabepipeline konvertiert diese Features normalerweise in Tensoren.
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>
Sie können mit tf.train.Example
Protos außerhalb einestf.data.Dataset
, um die Daten zu verstehen:
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])
Zeitreihenfensterung
Ein Beispiel für End-to-End-Zeitreihen finden Sie unter: Vorhersage von Zeitreihen .
Zeitreihendaten werden häufig mit intakter Zeitachse organisiert.
Verwenden Sie einen einfachen Dataset.range
, um Dataset.range
zu demonstrieren:
range_ds = tf.data.Dataset.range(100000)
In der Regel benötigen Modelle, die auf dieser Art von Daten basieren, eine zusammenhängende Zeitscheibe.
Der einfachste Ansatz wäre, die Daten zu stapeln:
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]
Um dichte Vorhersagen für einen Schritt in die Zukunft zu treffen, können Sie die Features und Beschriftungen um einen Schritt relativ zueinander verschieben:
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]
Um ein ganzes Fenster anstelle eines festen Versatzes vorherzusagen, können Sie die Stapel in zwei Teile aufteilen:
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]
Verwenden Sie Dataset.zip
, um eine gewisse Überlappung zwischen den Merkmalen eines Dataset.zip
und den Etiketten eines anderen 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
Während die Verwendung von Dataset.batch
funktioniert, gibt es Situationen, in denen Sie möglicherweise eine Dataset.batch
Kontrolle benötigen. Die Dataset.window
Methode gibt Ihnen die vollständige Kontrolle, aber erfordert eine gewisse Sorgfalt: es gibt Dataset
von Datasets
. Weitere Informationen finden Sie unter Datensatzstruktur .
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>
Die Dataset.flat_map
Methode kann einen Datensatz mit Datensätzen in einen einzelnen Datensatz 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
In fast allen Fällen sollten Sie .batch
den Datensatz .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]
Jetzt können Sie sehen, dass das shift
Argument steuert, um wie viel sich jedes Fenster bewegt.
Wenn Sie dies zusammenstellen, können Sie diese Funktion schreiben:
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]
Dann ist es einfach, Etiketten wie zuvor zu extrahieren:
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]
Resampling
Wenn Sie mit einem Datensatz arbeiten, der sehr klassenunausgewogen ist, möchten Sie den Datensatz möglicherweise erneut abtasten. tf.data
bietet dazu zwei Methoden. Der Datensatz für Kreditkartenbetrug ist ein gutes Beispiel für diese Art von Problem.
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()])
Überprüfen Sie nun die Verteilung der Klassen. Sie ist stark verzerrt:
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]
Ein üblicher Ansatz für das Training mit einem unausgeglichenen Datensatz besteht darin, ihn auszugleichen. tf.data
enthält einige Methoden, die diesen Workflow ermöglichen:
Abtastung von Datensätzen
Ein Ansatz zum sample_from_datasets
Abtasten eines Datasets ist die Verwendung von sample_from_datasets
. Dies gilt insbesondere dann, wenn Sie für jede Klasse ein separatesdata.Dataset
haben.
Verwenden Sie hier einfach den Filter, um sie aus den Kreditkartenbetrugsdaten zu generieren:
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]
Um tf.data.experimental.sample_from_datasets
übergeben Sie die Datasets und das Gewicht für jedes:
balanced_ds = tf.data.experimental.sample_from_datasets(
[negative_ds, positive_ds], [0.5, 0.5]).batch(10)
Jetzt erzeugt der Datensatz Beispiele für jede Klasse mit einer Wahrscheinlichkeit von 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]
Resampling der Ablehnung
Ein Problem mit dem obigen experimental.sample_from_datasets
Ansatz besteht darin, dass pro Klasse ein separatestf.data.Dataset
benötigt wird. Die Verwendung von Dataset.filter
funktioniert, führt jedoch dazu, dass alle Daten zweimal geladen werden.
Die Funktion data.experimental.rejection_resample
kann auf ein Dataset angewendet werden, um es neu data.experimental.rejection_resample
, während es nur einmal data.experimental.rejection_resample
. Elemente werden aus dem Datensatz entfernt, um ein Gleichgewicht zu erreichen.
data.experimental.rejection_resample
ein class_func
Argument. Diese class_func
wird auf jedes Dataset-Element angewendet und verwendet, um zu bestimmen, zu welcher Klasse ein Beispiel zum Zwecke des Ausgleichs gehört.
Die Elemente von creditcard_ds
sind bereits (features, label)
Paare. Die class_func
muss class_func
nur die folgenden Labels zurückgeben:
def class_func(features, label):
return label
Der Resampler benötigt außerdem eine Zielverteilung und optional eine anfängliche Verteilungsschätzung:
resampler = tf.data.experimental.rejection_resample(
class_func, target_dist=[0.5, 0.5], initial_dist=fractions)
Der Resampler behandelt einzelne Beispiele, daher müssen unbatch
den Datensatz unbatch
bevor Sie den Resampler anwenden:
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:
Der Resampler-Rückgabewert erstellt (class, example)
Paare aus der Ausgabe von class_func
. In diesem Fall war das example
bereits ein Paar (feature, label)
Verwenden Sie also map
, um die zusätzliche Kopie der Labels abzulegen:
balanced_ds = resample_ds.map(lambda extra_label, features_and_label: features_and_label)
Jetzt erzeugt der Datensatz Beispiele für jede Klasse mit einer Wahrscheinlichkeit von 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]
Iterator Checkpointing
Tensorflow unterstützt die Verwendung von Checkpoints, sodass beim Neustart Ihres Trainingsprozesses der neueste Checkpoint wiederhergestellt werden kann, um den größten Teil seines Fortschritts wiederherzustellen. Neben dem Checkpointing der Modellvariablen können Sie auch den Fortschritt des Dataset-Iterators überprüfen. Dies kann nützlich sein, wenn Sie über ein großes Dataset verfügen und das Dataset nicht bei jedem Neustart von vorne beginnen möchten. Beachten Sie jedoch, dass Iteratorprüfpunkte groß sein können, da Transformationen wie shuffle
und prefetch
Pufferelemente innerhalb des Iterators erfordern.
Um Ihren Iterator in einen Prüfpunkt aufzunehmen, übergeben Sie den Iterator an den Konstruktor 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]
Verwenden von tf.data mit tf.keras
Die tf.keras
API vereinfacht viele Aspekte beim Erstellen und Ausführen von Modellen für maschinelles Lernen. Die .fit()
und .evaluate()
und .predict()
unterstützen Datasets als Eingaben. Hier ist ein kurzer Datensatz und ein Modell-Setup:
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'])
Das Übergeben eines Datensatzes von (feature, label)
Paaren ist alles, was für Model.fit
und 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>
Wenn Sie ein unendliches Dataset übergeben, z. B. durch Aufrufen von Dataset.repeat()
, müssen Sie nur das Argument 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>
Zur Auswertung können Sie die Anzahl der Auswertungsschritte übergeben:
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
Legen Sie für lange Datensätze die Anzahl der auszuwertenden Schritte fest:
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
Die Beschriftungen werden beim Aufrufen von Model.predict
nicht benötigt.
predict_ds = tf.data.Dataset.from_tensor_slices(images).batch(32)
result = model.predict(predict_ds, steps = 10)
print(result.shape)
(320, 10)
Die Beschriftungen werden jedoch ignoriert, wenn Sie einen Datensatz übergeben, der sie enthält:
result = model.predict(fmnist_train_ds, steps = 10)
print(result.shape)
(320, 10)