tf.data: TensorFlow-Eingabepipelines erstellen

Auf TensorFlow.org ansehen In Google Colab ausführen Quelle auf GitHub anzeigenNotizbuch herunterladen

Die tf.data API ermöglicht es Ihnen , komplexe Eingangsleitungen von einfachen, wiederverwendbaren Teilen zu bauen. Die Pipeline für ein Bildmodell könnte beispielsweise Daten aus Dateien in einem verteilten Dateisystem aggregieren, zufällige Störungen auf jedes Bild anwenden und zufällig ausgewählte Bilder zum Training zu einem Stapel zusammenführen. Die Pipeline für ein Textmodell kann das Extrahieren von Symbolen aus Rohtextdaten, deren Umwandlung in Einbettungskennungen mit einer Nachschlagetabelle und das Zusammenführen von Sequenzen unterschiedlicher Länge umfassen. Die tf.data API ermöglicht es , große Datenmengen zu verarbeiten, liest aus verschiedenen Datenformaten und komplexe Transformationen durchzuführen.

Die tf.data API stellt eine tf.data.Dataset Abstraktion , die eine Folge von Elementen darstellt, wobei jedes Element aus einer oder mehreren Komponenten besteht. In einer Bildpipeline kann ein Element beispielsweise ein einzelnes Trainingsbeispiel sein, mit einem Paar von Tensorkomponenten, die das Bild und sein Label darstellen.

Es gibt zwei verschiedene Möglichkeiten, ein Dataset 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)

Grundmechanik

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() . Alternativ kann , wenn Ihre Eingangsdaten in einer Datei in dem empfohlenen TFRecord Format gespeichert sind, können Sie tf.data.TFRecordDataset() .

Sobald Sie eine haben Dataset - Objekt, können Sie es in eine neue Transformation Dataset durch Verkettungsverfahren fordert die tf.data.Dataset Objekt. Zum Beispiel können Sie pro-Element - Transformationen wie anwenden Dataset.map() und Multi-Element - Transformationen wie Dataset.batch() . Lesen Sie die Dokumentation für tf.data.Dataset für eine vollständige Liste von Transformationen.

Das Dataset - Objekt ist ein Python iterable. 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
2021-08-17 01:21:10.108394: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-17 01:21:10.116914: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-17 01:21:10.117985: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-17 01:21:10.120131: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-08-17 01:21:10.120675: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-17 01:21:10.121645: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-17 01:21:10.122585: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-17 01:21:10.729146: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-17 01:21:10.730204: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-17 01:21:10.731071: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-17 01:21:10.731932: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1510] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 14648 MB memory:  -> device: 0, name: Tesla V100-SXM2-16GB, pci bus id: 0000:00:05.0, compute capability: 7.0
<TensorSliceDataset shapes: (), types: tf.int32>
for elem in dataset:
  print(elem.numpy())
8
3
0
8
2
1

Oder durch explizit eine Python zu schaffen Iterator iter und seine Elemente raubend mit next :

it = iter(dataset)

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

Alternativ können Daten - Set Elemente verwendet werden , verbrauchen die reduce Transformation, die alle Elemente reduziert ein einzelnes Ergebnis zu produzieren. Das folgende Beispiel zeigt , wie man die Verwendung reduce Transformation der Summe eines Datensatzes von ganzen Zahlen zu berechnen.

print(dataset.reduce(0, lambda state, value: state + value).numpy())
22
2021-08-17 01:21:11.080177: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)

Datensatzstruktur

Ein Datensatz erzeugt eine Folge von Elementen, wobei jedes Element die gleiche ist (verschachtelt) Aufbau der Komponenten. Einzelne Komponenten der Struktur können durch irgendeinen Typen seiner darstellbaren tf.TypeSpec , einschließlich tf.Tensor , tf.sparse.SparseTensor , tf.RaggedTensor , tf.TensorArray oder tf.data.Dataset .

Die Python - Konstrukte , die verwendet werden können , die (nested) Struktur der Elemente umfassen auszudrücken tuple , dict , NamedTuple und OrderedDict . Insbesondere list ist kein gültiges Konstrukt für die Struktur des Datensatzes Elemente ausdrückt. Dies liegt daran , früh tf.data Benutzer stark über Filz list Eingänge (zB bestanden tf.data.Dataset.from_tensors ) als Tensoren und automatisch verpackt list Ausgänge (zB Rückgabewerte von benutzerdefinierten Funktionen) in eine dazu gezwungen werden tuple . Als Konsequenz , wenn Sie einen möchten list Eingabe als eine Struktur zu behandeln, müssen Sie es in konvertieren tuple und wenn Sie einen möchten list Ausgabe eine einzelne Komponente sein, dann müssen Sie es explizit packen mit tf.stack .

Die Dataset.element_spec Eigenschaft können Sie den Typ jedes Elements Komponente inspizieren. Die Eigenschaft gibt eine verschachtelte Struktur tf.TypeSpec Objekte, die die Struktur der Elementanpassung, die eine einzelne Komponente sein kann, ein Tupel von Komponenten oder eine verschachtelte Tupel von Komponenten. 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 Datensätze beliebiger Struktur. Bei Verwendung der Dataset.map() und Dataset.filter() Transformationen, die eine Funktion für jedes Element gilt, 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 7 8 3 9 4 1 8 8 7]
[1 4 2 4 9 5 9 6 9 6]
[2 2 4 2 3 3 1 1 5 9]
[2 8 3 5 5 3 6 4 4 5]
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

Verbrauchen von NumPy-Arrays

Siehe Laden NumPy Arrays für weitere Beispiele.

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
40960/29515 [=========================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 0s 0us/step
26435584/26421880 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
16384/5148 [===============================================================================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step
4431872/4422102 [==============================] - 0s 0us/step
images, labels = train
images = images/255

dataset = tf.data.Dataset.from_tensor_slices((images, labels))
dataset
<TensorSliceDataset shapes: ((28, 28), ()), types: (tf.float64, tf.uint8)>

Verbrauchen von Python-Generatoren

Eine weitere gemeinsame Datenquelle , die leicht als aufgenommen werden kann tf.data.Dataset 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 Dataset.from_generator Konstruktor wandelt den Python Generator auf eine voll funktionsfähige tf.data.Dataset .

Der Konstruktor nimmt ein Callable als Eingabe, keinen Iterator. Dies ermöglicht es ihm, den Generator neu zu starten, wenn er das Ende erreicht hat. Es dauert ein optionales args Argument, das als aufrufbare Argumente übergeben wird.

Das output_types Argument ist erforderlich, weil tf.data eine baut tf.Graph intern und Graphkanten erfordern eine 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 output_shapes Argument nicht erforderlich ist , sondern sehr viele tensorflow Operationen empfohlen nicht Tensoren mit unbekanntem Rang unterstützen. Wenn die Länge einer bestimmten Achse oder unbekannte Variable ist, eingestellt , wie es None in dem output_shapes .

Es ist auch wichtig zu beachten , dass die output_shapes und output_types den gleichen Regeln wie Verschachtelung andere Daten - Set 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 : [-1.3701 -0.7887  0.6197  0.6221 -2.0639]
2 : [-1.2015e+00  1.0961e+00  4.1624e-01 -1.0658e-03 -1.6877e-01  1.2876e+00]
3 : [-1.1707  0.4387  2.3673  1.1982 -0.3148 -0.1861 -0.347   0.1736 -0.8394]
4 : []
5 : [ 1.7289 -0.4898]
6 : [ 0.2928 -0.0204 -0.1664  0.3429  0.1203 -0.3491  0.8653  0.4383]

Der erste Ausgang ist ein int32 der zweiten A ist float32 .

Das erste Element ist ein Skalar, der Form () , und das zweite ist ein Vektor von 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)>

Nun kann es wie ein normales verwendet werden tf.data.Dataset . Beachten Sie, dass , wenn ein Datensatz mit einer variablen Form Dosierung, müssen Sie 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())
[14 11  0 16  4  2 22 24 12  9]

[[ 0.      0.      0.      0.      0.      0.      0.      0.    ]
 [ 0.719   0.4194  1.7463  0.      0.      0.      0.      0.    ]
 [ 0.8542 -1.5093  0.1953 -0.1048  0.5721  1.1397 -0.4303  0.    ]
 [-0.4863 -0.3267  0.      0.      0.      0.      0.      0.    ]
 [ 0.4751  1.4618  0.      0.      0.      0.      0.      0.    ]
 [ 0.4197 -0.375   0.593   1.3426  0.7061 -0.182   0.7721 -1.3497]
 [-0.7842  0.6529  0.9791 -0.7132 -0.7331  0.4589  0.      0.    ]
 [-1.3942 -0.9258 -0.5273  0.7907 -0.1406  0.216   0.7791  0.    ]
 [ 0.1517 -0.0894  0.1203  0.4895  0.      0.      0.      0.    ]
 [ 0.1405 -0.6329 -0.7482  0.8446  0.5997  0.      0.      0.    ]]

Um ein realistischeres Beispiel versuchen Einwickeln preprocessing.image.ImageDataGenerator als tf.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
228827136/228813984 [==============================] - 11s 0us/step

Erstellen Sie die 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)

Verbrauchen von TFRecord-Daten

Siehe Laden TFRecords für ein End-to-End - Beispiel.

Die tf.data API unterstützt eine Vielzahl von Dateiformaten , so dass Sie große Datenmengen verarbeiten können , die im Speicher nicht passen. Das Dateiformat TFRecord ist beispielsweise ein einfaches datensatzorientiertes Binärformat, das viele TensorFlow-Anwendungen für Trainingsdaten verwenden. Die tf.data.TFRecordDataset - Klasse ermöglicht es Ihnen , den Inhalt einer oder mehrere TFRecord Dateien als Teil einer Eingangsleitung zu streamen.

Hier ist ein Beispiel mit der Testdatei von 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
7913472/7904079 [==============================] - 0s 0us/step

Die filenames Argument der TFRecordDataset Initializer entweder ein String sein kann, eine Liste von Strings oder eine tf.Tensor von Strings. Wenn Sie also zu Trainings- und Validierungszwecken über zwei Sätze von Dateien verfügen, können Sie eine Factory-Methode erstellen, die das Dataset erstellt und Dateinamen als Eingabeargument verwendet:

dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>

Viele TensorFlow Projekte verwenden serialisierten tf.train.Example Datensätze in ihren TFRecord Dateien. Diese müssen entschlüsselt werden, bevor sie inspiziert 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"
}

Verbrauchen von Textdaten

Siehe Laden Text für ein Ende zum Beispiel.

Viele Datensätze werden als eine oder mehrere Textdateien verteilt. Die tf.data.TextLineDataset bietet eine einfache Möglichkeit Linien zu extrahieren aus einem oder mehreren Textdateien. Angesichts einer oder mehreren Dateinamen, ein TextLineDataset produzieren ein String-wertige Element 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
827392/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
819200/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step
819200/807992 [==============================] - 0s 0us/step
dataset = tf.data.TextLineDataset(file_paths)

Hier 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)'

Um alternative Linien zwischen Dateien verwenden Dataset.interleave . Dies erleichtert das Zusammenfügen 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 wird ein TextLineDataset liefert jede Zeile jeder Datei, die nicht wünschenswert sein kann, zum Beispiel, wenn die Datei mit Kopfzeile beginnt, oder enthält Kommentare. Diese Leitungen können unter Verwendung der entfernt werden Dataset.skip() oder Dataset.filter() Transformationen. 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
40960/30874 [=======================================] - 0s 0us/step
for line in titanic_lines.take(10):
  print(line.numpy())
b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone'
b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n'
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y'
b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
def survived(line):
  return tf.not_equal(tf.strings.substr(line, 0, 1), "0")

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

Verbrauchen von CSV-Daten

Siehe Laden von CSV - Dateien und lädt Pandas Datenrahmen für weitere Beispiele.

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 passen im Speicher die gleichen Dataset.from_tensor_slices diese Daten Methode funktioniert auf Wörterbücher, so dass leicht importiert werden:

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 tf.data Modul stellt Methoden zu extrahieren Datensätze aus einer oder mehreren CSV - Dateien , die mit entsprechen RFC 4180 .

Die experimental.make_csv_dataset Funktion ist die hohe Schnittstelle für Sätze von CSV - Dateien zu lesen. Es unterstützt Spaltentyp-Inferenz und viele andere Funktionen wie Batching und Shuffling, 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': [1 0 0 1]
features:
  'sex'               : [b'female' b'male' b'male' b'male']
  'age'               : [50. 28. 20. 26.]
  'n_siblings_spouses': [0 0 0 0]
  'parch'             : [1 0 0 0]
  'fare'              : [26.      7.225   9.8458 18.7875]
  'class'             : [b'Second' b'Third' b'Third' b'Third']
  'deck'              : [b'unknown' b'unknown' b'unknown' b'unknown']
  'embark_town'       : [b'Southampton' b'Cherbourg' b'Southampton' b'Cherbourg']
  'alone'             : [b'n' b'y' b'y' b'y']

Sie können die Verwendung select_columns Argument , wenn Sie nur eine Teilmenge der 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': [1 1 0 0]
  'fare'              : [  7.7333 227.525   15.55    26.55  ]
  'class'             : [b'Third' b'First' b'Third' b'First']

Es gibt auch eine untergeordnete experimental.CsvDataset Klasse , die feinkörnige Kontrolle. Es unterstützt keinen Spaltentyprückschluss. 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 mit dieser Low-Level-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 wird ein CsvDataset ergibt jede Spalte jeder Zeile der Datei, die beispielsweise nicht wünschenswert sein, wenn die Datei mit Kopfzeile beginnt , die ignoriert werden sollen, oder wenn einige Spalten nicht in der Eingabe erforderlich. Diese Linien und Felder können mit den entfernt werden header und select_cols jeweils Argumente.

# 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]

Verbrauchen von Dateien

Es gibt viele Datensätze, die als Satz von Dateien verteilt werden, 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 Root-Verzeichnis 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/16616096711_12375a0260_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/123128873_546b8b7355_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/2481823240_eab0d86921.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/6931489544_2f35025f7b_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/921138131_9e1393eb2b_m.jpg'

Lesen der Daten , die unter Verwendung von tf.io.read_file Funktion und Extrahieren des Etiketts aus dem Pfad, Return (image, label) Paare:

def process_path(file_path):
  label = tf.strings.split(file_path, os.sep)[-2]
  return tf.io.read_file(file_path), label

labeled_ds = list_ds.map(process_path)
for image_raw, label_text in labeled_ds.take(1):
  print(repr(image_raw.numpy()[:100]))
  print()
  print(label_text.numpy())
b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff\xe1\t\xcbXMP\x00://ns.adobe.com/xap/1.0/\x00<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczk"

b'sunflowers'

Batching-Dataset-Elemente

Einfache Dosierung

Die einfachste Form der Dosierung Stapeln n aufeinanderfolgenden Elementen eines Datensatzes in einem einzigen Element. Die Dataset.batch() Transformation macht genau diese, mit den gleichen Einschränkungen wie die tf.stack() Operator, an jede Komponente der Elemente angewendet: dh für jede Komponente i alle Elemente müssen einen Tensor der exakt gleiche 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 Form Informationen zu verbreiten, die Standardeinstellungen von Dataset.batch sein Ergebnis in einem unbekannten Losgröße , weil die letzte Charge möglicherweise nicht voll. Beachten Sie die None s in der Form:

batched_dataset
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.int64)>

Verwenden Sie das drop_remainder Argument , dass die letzte Partie zu ignorieren, und voller Form Fortpflanzung erhalten:

batched_dataset = dataset.batch(7, drop_remainder=True)
batched_dataset
<BatchDataset shapes: ((7,), (7,)), types: (tf.int64, tf.int64)>

Stapeltensoren mit Polsterung

Das obige Rezept funktioniert für Tensoren, die alle die gleiche Größe haben. Viele Modelle (zB Sequenzmodelle) arbeiten jedoch mit Eingabedaten, die unterschiedlich groß sein können (zB Sequenzen unterschiedlicher Länge). Um diesen Fall zu behandeln, die Dataset.padded_batch Transformation ermöglicht es Ihnen, Batch - Tensoren verschiedener Form durch die Angabe einer oder mehreren Dimensionen , in denen sie aufgefüllt werden kann.

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]]

Die Dataset.padded_batch Transformation ermöglicht für jede Dimension der einzelnen Komponenten unterschiedliche Polsterung zu setzen, und es kann mit variabler Länge (bezeichnet durch seine None oder konstante Länge in dem obigen Beispiel). Es ist auch möglich, den Padding-Wert zu überschreiben, der standardmäßig auf 0 gesetzt ist.

Schulungsworkflows

Bearbeitung mehrerer Epochen

Die tf.data API bietet zwei Möglichkeiten , mehrere Epochen der gleichen Daten zu verarbeiten.

Die einfachste Möglichkeit , einen Datensatz in mehreren Epochen iterieren ist , die verwenden Dataset.repeat() Transformation. Erstellen Sie zunächst einen Datensatz mit Titanic-Daten:

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')

Die Anwendung der Dataset.repeat() Transformation ohne Argumente wird die Eingabe auf unbestimmte Zeit wiederholen.

Die Dataset.repeat Transformation verkettet ihre Argumente ohne Signalisierung das Ende einer Epoche und den Beginn der nächsten Epoche. Aus diesem Grund ein Dataset.batch angewendet , nachdem Dataset.repeat wird Chargen ergeben , dass Straddle Epoche Grenzen:

titanic_batches = titanic_lines.repeat(3).batch(128)
plot_batch_sizes(titanic_batches)

png

Wenn Sie klare Epoche Trennung benötigen, setzen Dataset.batch vor der Wiederholung:

titanic_batches = titanic_lines.batch(128).repeat(3)

plot_batch_sizes(titanic_batches)

png

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 unterhält einen Puffer mit fester Größe und wählt das nächste Element gleichmäßig zufällig aus diesem Puffer.

Fügen Sie dem Dataset 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
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/counter.py:66: scan (from tensorflow.python.data.experimental.ops.scan_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.scan(...) instead
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

Da die buffer_size 100 ist, und die Ansatzgröße 20 ist , enthält die erste Charge keine Elemente mit einem Index über 120.

n,line_batch = next(iter(dataset))
print(n.numpy())
[ 31  35  64  11  96 103  53  75  36 108   4  10  23  91  61   2  72  27
  79 104]

Wie bei Dataset.batch die Reihenfolge in Bezug auf Dataset.repeat Angelegenheiten.

Dataset.shuffle meldet nicht das Ende einer Epoche , bis der Shuffle - Puffer leer ist. Ein Mischen, das vor einer Wiederholung platziert wird, 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:

[494 600 564 621 480 623 603 606 618 572]
[569 541 593 605 501 563 594 546 330 585]
[619 622  25 577 570 597 477 379]
[33 65 17 14  6  5 34  7 57 29]
[ 93   8  84  54  73  40   0  56 108  87]
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 0x7f6fe0312190>

png

Aber eine Wiederholung vor einem Shuffle vermengt die Epochengrenzen:

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:

[527  18 562 259 478 602  19 619  24 466]
[575 537 617 566  26 611 496 524 510 585]
[614 262  37 599 414 385  17 549  46  13]
[ 16 605 525  30  21 547  22  33 469  44]
[345 592  47  23  32 578  67  56 621  45]
[ 35 528   8 555   4   3  12 600 548  15]
[ 61 627  20  59  50  14  72 557 618  73]
[ 74 623  91  69 615 593  88  28  84  81]
[ 41 590  97  68  75 579  11  51  86  76]
[ 80 610 613  55  94  89 117 538  27  78]
[ 99 398 588  83 110 100 126 116  34  38]
[624 286  36 101 534 302 108  53 622 130]
[ 98  87 608  49 441  64 129 501 598 606]
[487 144  63   1 571 109 141 128 111  70]
[ 77 136 120 523 164 103 560  29 102 138]
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 0x7f6fe0599850>

png

Datenvorverarbeitung

Die Dataset.map(f) Transformation erzeugt einen neuen Datensatz durch eine gegebene Funktion der Anwendung f zu jedem Element des Eingangsdatensatzes. Es basiert auf der map() Funktion , die häufig auf Listen angewandt wird (und andere Strukturen) in funktionalen Programmiersprachen. Die Funktion f nimmt die tf.Tensor Objekte , die ein einzelnes Element in der Eingabe repräsentieren, und gibt die tf.Tensor Objekte , die ein einzelnes Element in dem neuen Datensatz darstellen. Seine Implementierung verwendet standardmäßige TensorFlow-Operationen, um ein Element in ein anderes umzuwandeln.

In diesem Abschnitt werden gängige Beispiele dafür , wie die Verwendung Dataset.map() .

Bilddaten dekodieren und skalieren

Beim Trainieren eines neuronalen Netzes mit realen Bilddaten ist es häufig erforderlich, Bilder unterschiedlicher Größe in eine gemeinsame Größe umzuwandeln, damit sie in eine feste Größe gestapelt werden können.

Erstellen Sie das Dataset mit den Blumendateinamen neu:

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

Schreiben Sie eine Funktion, die die Datensatzelemente manipuliert.

# 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)

png

Ordnen Sie es über dem Datensatz zu.

images_ds = list_ds.map(parse_image)

for image, label in images_ds.take(2):
  show(image, label)

png

png

Anwenden beliebiger Python-Logik

Verwenden Sie aus Leistungsgründen nach Möglichkeit TensorFlow-Operationen zur Vorverarbeitung Ihrer Daten. Es ist jedoch manchmal nützlich, beim Parsen Ihrer Eingabedaten externe Python-Bibliotheken aufzurufen. Sie können die Verwendung tf.py_function() den Betrieb in einem Dataset.map() Transformation.

Zum Beispiel, wenn Sie eine zufällige Drehung anwenden möchten, das tf.image hat Modul nur tf.image.rot90 , die für die Bildvergrößerung ist nicht sehr nützlich.

Um zu demonstrieren , tf.py_function , versuchen Sie die Verwendung von scipy.ndimage.rotate Funktion statt:

import scipy.ndimage as ndimage

def random_rotate_image(image):
  image = ndimage.rotate(image, np.random.uniform(-30, 30), reshape=False)
  return image
image, label = next(iter(images_ds))
image = random_rotate_image(image)
show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

png

Um diese Funktion mit verwenden Dataset.map den gleichen Einschränkungen gelten wie bei Dataset.from_generator , müssen Sie die Rückkehr Formen und Arten 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).

png

png

Parsing tf.Example Protokollpuffer Meldungen

Viele Eingangsleitungen extrahieren tf.train.Example Protokollpuffer - Nachrichten von einem TFRecord Format. Jeder tf.train.Example Satz enthält ein oder mehr „Features“, und die Eingangsleitung wandelt typischerweise diese Eigenschaften 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 der Arbeit tf.train.Example protos außerhalb eines tf.data.Dataset 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])

png

raw_example = next(iter(dataset))
2021-08-17 01:21:34.232513: W tensorflow/core/data/root_dataset.cc:167] Optimization loop failed: Cancelled: Operation was cancelled
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])

Zeitreihen-Fensterung

Für ein Ende der Endzeit - Serie Beispiel sehen: Zeitreihenprognose .

Zeitreihendaten werden oft mit intakter Zeitachse organisiert.

Verwenden Sie einen einfachen Dataset.range zu demonstrieren:

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

Typischerweise benötigen Modelle, die auf dieser Art von Daten basieren, eine zusammenhängende Zeitscheibe.

Der einfachste Ansatz wäre, die Daten zu stapeln:

Mit 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]

Oder um dichte Vorhersagen einen Schritt in die Zukunft zu machen, 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],   # Inputs: All except the last 5 steps
          batch[-5:])   # Labels: The last 5 steps

predict_5_steps = batches.map(label_next_5_steps)

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

Um eine gewisse Überlappung zwischen den Merkmalen einer Charge zu ermöglichen und die Etiketten eines anderen, verwenden 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]

Mit window

Bei der Verwendung von Dataset.batch funktioniert, gibt es Situationen , in denen Sie eine genauere Kontrolle benötigen. Die Dataset.window Methode gibt Ihnen die vollständige Kontrolle, aber erfordert eine gewisse Sorgfalt: es gibt Dataset von Datasets . Siehe Dataset Struktur für weitere Einzelheiten.

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 eine Datenmenge von Datensätzen nehmen und es in einem einzelnen Datensatz glätten:

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 werden Sie wollen .batch den Datensatz zuerst:

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 die shift Argument steuert , wie viel jedes Fenster bewegt sich über.

Zusammenfassend könnten 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 ganz 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 Dataset arbeiten, das sehr unausgewogen ist, sollten Sie das Dataset möglicherweise erneut abtasten. tf.data bietet zwei Möglichkeiten , dies zu tun. Der Datensatz zum 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 [==============================] - 5s 0us/step
69165056/69155632 [==============================] - 5s 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 aktivieren:

Stichproben von Datensätzen

Ein Ansatz , einen Datensatz zu Resampling verwenden sample_from_datasets . Dies ist mehr anwendbar , wenn Sie eine separate haben data.Dataset für jede Klasse.

Verwenden Sie hier einfach einen 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 die Verwendung tf.data.experimental.sample_from_datasets passieren die Datensätze, und das Gewicht für jeden:

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

Jetzt liefert der Datensatz Beispiele für jede Klasse mit 50/50-Wahrscheinlichkeit:

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

Ablehnungs-Resampling

Ein Problem bei dem oben experimental.sample_from_datasets Ansatz ist , dass es einen separaten braucht tf.data.Dataset pro Klasse. Mit Dataset.filter funktioniert, aber die Ergebnisse in allen die gespeicherten Daten zweimal geladen.

Die data.experimental.rejection_resample Funktion kann zu einem Datensatz angewandt wird sie wieder im Gleichgewicht, während es nur einmal geladen werden . Elemente werden aus dem Dataset entfernt, um ein Gleichgewicht zu erreichen.

data.experimental.rejection_resample nimmt ein class_func Argument. Diese class_func wird jedes Datensatzelement aufgebracht wird , und wird verwendet, um zu bestimmen , welche Klasse ein Beispiel für die Zwecke des Ausgleichs gehört.

Die Elemente der creditcard_ds sind bereits (features, label) Paaren. So ist die class_func braucht nur diese Etiketten zurückzukehren:

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)

Die Neuabtasters befasst sich mit einzelnen Beispielen, so müssen Sie unbatch , den Datensatz vor dem Auftragen der Neuabtasters:

resample_ds = creditcard_ds.unbatch().apply(resampler).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/resampling.py:159: Print (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2018-08-20.
Instructions for updating:
Use tf.print instead of tf.Print. Note that tf.print returns a no-output operator that directly prints the output. Outside of defuns or eager mode, this operator will not be executed unless it is directly specified in session.run or used as a control dependency for other operators. This is only a concern in graph mode. Below is an example of how to ensure tf.print executes in graph mode:

Die Wiederabtastvorrichtung kehrt schafft (class, example) paarweise von dem Ausgang des class_func . In diesem Fall ist das example war schon ein (feature, label) Paar, so Verwendung der map , um die zusätzliche Kopie der Etiketten zu fallen:

balanced_ds = resample_ds.map(lambda extra_label, features_and_label: features_and_label)

Nun liefert der Datensatz Beispiele für jede Klasse mit 50/50-Wahrscheinlichkeit:

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
Proportion of examples rejected by sampler is high: [0.99521482][0.99521482 0.00478515634][0 1]
Proportion of examples rejected by sampler is high: [0.99521482][0.99521482 0.00478515634][0 1]
Proportion of examples rejected by sampler is high: [0.99521482][0.99521482 0.00478515634][0 1]
Proportion of examples rejected by sampler is high: [0.99521482][0.99521482 0.00478515634][0 1]
Proportion of examples rejected by sampler is high: [0.99521482][0.99521482 0.00478515634][0 1]
Proportion of examples rejected by sampler is high: [0.99521482][0.99521482 0.00478515634][0 1]
Proportion of examples rejected by sampler is high: [0.99521482][0.99521482 0.00478515634][0 1]
Proportion of examples rejected by sampler is high: [0.99521482][0.99521482 0.00478515634][0 1]
Proportion of examples rejected by sampler is high: [0.99521482][0.99521482 0.00478515634][0 1]
Proportion of examples rejected by sampler is high: [0.99521482][0.99521482 0.00478515634][0 1]
[0 1 1 1 0 1 1 0 0 1]
[1 1 0 1 0 1 1 0 0 0]
[0 1 0 1 0 1 1 1 1 0]
[0 1 0 1 1 1 0 0 1 0]
[1 1 0 1 0 0 0 0 0 0]
[0 0 1 0 1 1 0 0 0 0]
[1 0 0 1 1 0 1 1 1 0]
[0 0 0 1 0 0 0 1 1 0]
[1 1 0 1 0 0 1 1 1 1]
[1 1 1 1 1 0 0 1 0 1]

Iterator-Checkpointing

Tensorflow unterstützt Checkpoints nehmen , so dass , wenn Ihr Trainingsprozess neu gestartet wird es die neuesten Checkpoint wiederherstellen meisten seiner Fortschritte zu erholen. Sie können nicht nur die Modellvariablen mit Prüfpunkten versehen, sondern 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 Iterator Checkpoints groß sein kann, da Transformationen wie shuffle und prefetch - Pufferung Elemente innerhalb des Iterator erfordern.

Um Ihren Iterator in einem Checkpoint zu umfassen, den Iterator auf das Pass tf.train.Checkpoint Konstruktor.

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 mit tf.keras verwenden

Der tf.keras viele Aspekte Vereinfacht API des Erstellens und Maschinenlernmodelle auszuführen. Seine .fit() und .evaluate() und .predict() APIs unterstützen Datensätze als Eingaben. Hier ist eine schnelle Dataset- und Modelleinrichtung:

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'])

Übergeben eines Datensatzes von (feature, label) Paare ist alles , was für erforderlich ist Model.fit und Model.evaluate :

model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2
1875/1875 [==============================] - 4s 2ms/step - loss: 0.6010 - accuracy: 0.7969
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4616 - accuracy: 0.8422
<keras.callbacks.History at 0x7f6f907c9750>

Wenn Sie eine unendliche Datenmenge, zum Beispiel passieren durch den Aufruf Dataset.repeat() , müssen Sie nur auch den Pass steps_per_epoch Argument:

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4212 - accuracy: 0.8625
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4182 - accuracy: 0.8547
<keras.callbacks.History at 0x7f6f906a9810>

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 [==============================] - 3s 2ms/step - loss: 0.4345 - accuracy: 0.8516
Loss : 0.43451231718063354
Accuracy : 0.8516499996185303

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.4203 - accuracy: 0.8625
Loss : 0.4203265309333801
Accuracy : 0.862500011920929

Die Etiketten sind nicht erforderlich , wenn Aufruf 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)

Die Labels werden jedoch ignoriert, wenn Sie ein Dataset übergeben, das sie enthält:

result = model.predict(fmnist_train_ds, steps = 10)
print(result.shape)
(320, 10)