Sehen Sie sich Keynotes, Produktsitzungen, Workshops und mehr in Google I / O an. Siehe Wiedergabeliste

tf.data: TensorFlow-Eingabepipelines erstellen

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

Mit der tf.data API können Sie komplexe Eingabepipelines aus einfachen, wiederverwendbaren Teilen erstellen. 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, aus unterschiedlichen Datenformaten auszulesen und komplexe Transformationen durchzuführen.

Die tf.data API führt einetf.data.Dataset Abstraktion ein, 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 mehrerentf.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 beispielsweise ein Dataset aus Daten im Speicher zu tf.data.Dataset.from_tensors() , 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 über ein Dataset Objekt verfügen, können Sie es in ein neues Dataset umwandeln , indem Sie Methodenaufrufe für dastf.data.Dataset Objekttf.data.Dataset . Beispielsweise können Sie Element-Transformationen wie Dataset.map() und Multi-Element-Transformationen wie Dataset.batch() . Eine vollständige Liste der Transformationen finden Sie in der Dokumentation zutf.data.Dataset .

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
<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 verbrauchen:

it = iter(dataset)

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

Alternativ können Dataset-Elemente mit der reduce Transformation verwendet werden, die alle Elemente reduziert, um ein einziges Ergebnis zu erzielen. Das folgende Beispiel veranschaulicht, wie die reduce Transformation verwendet wird, um die Summe eines Datasets von Ganzzahlen zu berechnen.

print(dataset.reduce(0, lambda state, value: state + value).numpy())
22

Datensatzstruktur

Ein Datensatz erzeugt eine Folge von Elementen , wobei jedes Element dieselbe (verschachtelte) Struktur von Komponenten aufweist . Einzelne Komponenten der Struktur können von jedem durch tf.TypeSpec Typ tf.TypeSpec , einschließlich tf.Tensor , tf.sparse.SparseTensor ,tf.RaggedTensor , tf.TensorArray odertf.data.Dataset .

Die Python - Konstrukte , die verwendet werden können , die (nested) Struktur der Elemente umfassen auszudrücken tuple , dict , NamedTuple und OrderedDict . Insbesondere ist list kein gültiges Konstrukt, um die Struktur von Dataset-Elementen auszudrücken. Dies liegt daran, dass frühe tf.data-Benutzer stark waren, dass list (zB an tf.data.Dataset.from_tensors ) automatisch als Tensoren gepackt und list (zB Rückgabewerte benutzerdefinierter Funktionen) in ein tuple gezwungen werden. Wenn Sie also möchten, dass eine list als Struktur behandelt wird, müssen Sie sie in ein tuple konvertieren, und wenn Sie möchten, dass eine list eine einzelne Komponente ist, müssen Sie sie explizit mit tf.stack .

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 der Struktur des Elements entspricht, das eine einzelne Komponente, ein Komponententupel oder ein verschachteltes Komponententupel sein kann. 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 jeder Struktur. Bei Verwendung der Dataset.map() und Dataset.filter() , die eine Funktion auf jedes Element 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())
[2 7 7 7 5 9 3 3 5 2]
[9 9 7 3 6 2 3 8 7 1]
[2 9 7 1 8 7 8 3 8 2]
[7 5 2 6 9 4 4 1 6 4]
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

Weitere Beispiele finden Sie unter Laden von NumPy-Arrays .

Wenn alle Ihre Eingabedaten in den Speicher tf.Tensor , können Sie daraus am einfachsten ein Dataset erstellen, tf.Tensor sie in tf.Tensor Objekte konvertieren und 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 [==============================] - 0s 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)>

Verbrauchen von Python-Generatoren

Eine weitere gängige Datenquelle, die problemlos 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 Dataset.from_generator Konstruktor konvertiert den Python-Generator in ein voll funktionsfähigestf.data.Dataset .

Der Konstruktor nimmt ein Callable als Eingabe, keinen Iterator. Dadurch kann er den Generator neu starten, wenn er das Ende erreicht hat. Es benötigt ein optionales Argument args , das als Argumente des Callables ü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 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, legen Sie sie in den output_shapes None output_shapes .

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 : [0.2357]
1 : [-0.4635  0.0882 -0.7401 -1.2436 -0.1392  1.8694 -2.2567  1.5493 -1.0368]
2 : []
3 : []
4 : [1.1482 1.0136]
5 : [ 0.7923 -2.2942  0.4162  1.5056  1.6008  0.1861]
6 : [ 0.7311  0.9217  1.3697 -1.0795  1.0586 -1.0768]

Die erste Ausgabe ist ein int32 die zweite ein float32 .

Das erste Element ist ein Skalar, shape () , und das zweite ist ein Vektor unbekannter Länge, shape (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 eintf.data.Dataset . Beachten Sie, dass Sie beim Batching 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())
[17 11 18  8 23  2  6 13 26 28]

[[ 1.0784e+00  6.2397e-01  3.3750e-01 -2.1123e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [ 1.3756e-01 -1.5717e+00 -8.0335e-01  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-9.1093e-01  6.3951e-01  4.9384e-04  2.0273e+00 -3.6473e-01 -3.6264e-02
  -7.3862e-01 -5.3504e-01]
 [ 1.6893e-01  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-1.1584e+00 -7.9125e-01  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-1.7383e-01 -2.5935e-01  4.8755e-01  1.5578e+00 -4.7534e-01  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-4.0072e-01 -7.4969e-01 -1.1954e+00  9.0354e-02  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-8.6638e-02 -1.4680e+00  1.3155e+00  1.1772e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-1.1352e-01 -1.1264e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]
 [-1.2858e+00  7.7001e-02 -1.7588e-01  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00]]

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 [==============================] - 1s 0us/step

Erstellen Sie das 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

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 Datensätze verarbeiten können, die nicht in den Speicher passen. Das Dateiformat TFRecord ist beispielsweise ein einfaches datensatzorientiertes Binärformat, das viele TensorFlow-Anwendungen für Trainingsdaten verwenden. Mit der Klasse tf.data.TFRecordDataset können Sie den Inhalt einer oder mehrerer TFRecord-Dateien als Teil einer Eingabepipeline 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

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 serialisierte 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 von Text für ein Ende-zu-Ende-Beispiel.

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 Zeichenfolgenwertelement 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 paar 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 Zeilen zwischen Dateien zu wechseln, verwenden Sie 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 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'

Verbrauchen von CSV-Daten

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 Methode Dataset.from_tensor_slices für Wörterbücher, sodass diese Daten einfach 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 High-Level-Schnittstelle zum Lesen von Sätzen von CSV-Dateien. 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 1 1 1]
features:
  'sex'               : [b'female' b'female' b'male' b'female']
  'age'               : [40. 30. 17. 19.]
  'n_siblings_spouses': [0 0 0 0]
  'parch'             : [0 0 2 0]
  'fare'              : [ 13.      12.475  110.8833  26.    ]
  'class'             : [b'Second' b'Third' b'First' b'Second']
  'deck'              : [b'unknown' b'unknown' b'C' b'unknown']
  'embark_town'       : [b'Southampton' b'Southampton' b'Cherbourg' b'Southampton']
  'alone'             : [b'y' b'y' b'n' b'y']

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 0 0 0]
  'fare'              : [ 15.5   55.9    8.05 108.9 ]
  'class'             : [b'Third' b'First' b'Third' b'First']

Es gibt auch eine untergeordnete experimental.CsvDataset Klasse, die eine feinere Steuerung bietet. 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 liefert ein CsvDataset jede Spalte jeder Zeile der Datei, was möglicherweise nicht wünschenswert ist, wenn die Datei beispielsweise mit einer Kopfzeile beginnt, die ignoriert werden soll, oder wenn einige Spalten in der Eingabe nicht erforderlich sind. Diese Zeilen und Felder können mit den Argumenten header bzw. 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]

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/roses/3871586333_5a708d5cf4_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/19551343954_83bb52f310_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/13289268363_b9337d751e.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/3253243865_435c1f2c2b_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/22244161124_53e457bb66_n.jpg'

Lesen Sie die Daten mit der Funktiontf.io.read_file und extrahieren Sie das Label aus dem Pfad, indemtf.io.read_file (image, label) -Paare zurückgeben:

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\x05XICC_PROFILE\x00\x01\x01\x00\x00\x05Happl\x02 \x00\x00scnrRGB XYZ \x07\xd3\x00\x07\x00\x01\x00\x00\x00\x00\x00\x00acspAPPL\x00\x00\x00\x00'

b'roses'

Batching-Dataset-Elemente

Einfache Dosierung

Die einfachste Form der Stapelverarbeitung stapelt n aufeinanderfolgende Elemente eines Datensatzes zu einem einzigen Element. Die Dataset.batch() Transformation tut genau dies, mit den gleichen Einschränkungen wie der tf.stack() Operator, angewendet auf jede Komponente der Elemente: dh für jede Komponente i müssen alle Elemente einen Tensor der exakt gleichen 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 zu verbreiten, 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)>

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 handhaben, ermöglicht Ihnen die Dataset.padded_batch Transformation, Tensoren unterschiedlicher Form zu 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]]

Die Dataset.padded_batch Transformation ermöglicht es Ihnen, für jede Dimension jeder Komponente unterschiedliche Auffüllungen Dataset.padded_batch , und es kann eine variable Länge (im obigen Beispiel durch None ) oder eine konstante Länge sein. 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 Hauptmethoden, um mehrere Epochen derselben Daten zu verarbeiten.

Die einfachste Möglichkeit, ein Dataset in mehreren Epochen zu Dataset.repeat() besteht darin, die Dataset.repeat() Transformation zu verwenden. 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')

Wenn Sie die Dataset.repeat() Transformation ohne Argumente Dataset.repeat() 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 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 eine klare Epochentrennung benötigen, stellen Sie Dataset.batch vor die 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 verwaltet einen Puffer mit fester Größe und wählt das nächste Element gleichmäßig zufällig aus diesem Puffer aus.

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
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

Da buffer_size 100 und die Batchgröße 20 beträgt, enthält der erste Batch keine Elemente mit einem Index über 120.

n,line_batch = next(iter(dataset))
print(n.numpy())
[ 39   5  30  77  45 104 102  54  31  89  93  29  38  85  60   9  78  76
   1  41]

Wie bei Dataset.batch die Reihenfolge relativ zu Dataset.repeat wichtig.

Dataset.shuffle signalisiert das Ende einer Epoche erst, wenn 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:

[618 522 447 528 576 514 626 610 502 404]
[601 577 586 560 490 469 604 275 551 561]
[567 550 423 486 544 457 578 448]
[85 78 32 73 45 99 37 94 13 54]
[101  55   4 109  41  25  80 106   9 113]
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 0x7fe00c013690>

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:

[611 401 577 625   2 557 585   6 618 502]
[ 31 607 588  24   0 627 455 613 547 605]
[537 544 240  15  43 555 621  34 590   8]
[ 27 475 528 546 560  48  53 489  54  37]
[599 561  30 570  21 499 586  10   5  12]
[367  60 568 525   1 619 589  23 548  35]
[ 17 470 616  42 569  83  70 405  46 463]
[ 50  72 612 623  28 522 581  86  77  76]
[474 598 609  25  65 491 543  97 536  93]
[ 16 101  58  90  19  38 111 615 119  49]
[ 39 110  75  95 122  94   4  67  64  51]
[ 22 606 610  99 601 526 116 571  80 109]
[ 29  32 125 138 608  33 139 106 147 127]
[130 114 117  18  59  79  66 123 124 155]
[103 579 100 107 165 115  92  84   3  52]
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 0x7fe004095b50>

png

Datenvorverarbeitung

Die Dataset.map(f) -Transformation erzeugt ein neues Dataset, indem eine gegebene Funktion f auf jedes Element des Eingabe-Datasets Dataset.map(f) wird. Es basiert auf der Funktion map() , die häufig auf Listen (und andere Strukturen) in funktionalen Programmiersprachen angewendet wird. Die Funktion f nimmt 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 Dataset darstellen. Seine Implementierung verwendet standardmäßige TensorFlow-Operationen, um ein Element in ein anderes umzuwandeln.

In diesem Abschnitt werden allgemeine Beispiele für die Verwendung von 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 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.

Um tf.py_function zu demonstrieren, versuchen scipy.ndimage.rotate stattdessen mit der Funktion scipy.ndimage.rotate :

import scipy.ndimage as ndimage

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

png

Um diese Funktion mit Dataset.map die gleichen Vorbehalte wie bei 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).

png

png

Parsen von tf.Example Protokollpuffernachrichten

Viele Eingabepipelines extrahieren tf.train.Example Protokollpuffernachrichten aus einem TFRecord-Format. Jeder tf.train.Example Datensatz enthält ein oder mehrere "Features", und die Eingabepipeline wandelt diese Features normalerweise in Tensoren um.

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

png

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

Zeitreihen-Fensterung

Ein Beispiel für End-to-End-Zeitreihen finden Sie unter: Zeitreihenprognosen .

Zeitreihendaten werden oft mit intakter Zeitachse organisiert.

Verwenden Sie ein einfaches Dataset.range , um 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:

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 Funktionen eines Dataset.zip und den Etiketten eines anderen zu ermöglichen, verwenden Sie 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 Methode Dataset.window gibt Ihnen die vollständige Kontrolle, erfordert jedoch einige Sorgfalt: Sie gibt ein Dataset von Datasets . Weitere Informationen finden Sie unter Dataset-Struktur .

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 Methode Dataset.flat_map kann ein Dataset von Datasets nehmen und in ein einzelnes Dataset 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 möchten .batch den Datensatz zuerst .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, wie weit sich jedes Fenster bewegt.

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 dafür zwei Methoden. 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 [==============================] - 2s 0us/step
creditcard_ds = tf.data.experimental.make_csv_dataset(
    csv_path, batch_size=1024, label_name="Class",
    # Set the column types: 30 floats and an int.
    column_defaults=[float()]*30+[int()])

Ü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.9961 0.0039]

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:

Stichproben von Datensätzen

Ein Ansatz zum Resampling eines Datasets besteht darin, sample_from_datasets zu verwenden. Dies gilt umso mehr, wenn Sie für jede Klasse ein separatesdata.Dataset haben.

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 tf.data.experimental.sample_from_datasets übergeben Sie die Datensätze und die Gewichtung für jeden:

balanced_ds = tf.data.experimental.sample_from_datasets(
    [negative_ds, positive_ds], [0.5, 0.5]).batch(10)

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

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

Ablehnungs-Resampling

Ein Problem bei dem obigen experimental.sample_from_datasets Ansatz besteht darin, dass er pro Klasse ein separatestf.data.Dataset benötigt. 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 auszubalancieren, während es nur einmal data.experimental.rejection_resample . Elemente werden aus dem Dataset entfernt, um ein Gleichgewicht zu erreichen.

data.experimental.rejection_resample nimmt ein class_func Argument an. Diese class_func wird auf jedes Dataset-Element angewendet und wird 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 diese 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 das Dataset vor dem Anwenden des Resamplers aufheben:

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: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-Return erzeugt (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 zu löschen:

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

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

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

Iterator-Checkpointing

Tensorflow unterstützt das Erfassen von Prüfpunkten, sodass beim Neustart Ihres Trainingsprozesses der neueste Prüfpunkt wiederhergestellt werden kann, um den größten Teil seines Fortschritts wiederherzustellen. 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 können, da Transformationen wie shuffle und prefetch Pufferelemente innerhalb des Iterators erfordern.

Um Ihren Iterator in einen Prüfpunkt tf.train.Checkpoint , ü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]

tf.data mit tf.keras verwenden

Die tf.keras API vereinfacht viele Aspekte der Erstellung und Ausführung von Modellen für maschinelles Lernen. Die .fit() und .evaluate() und .predict() 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'])

Für Model.fit und Model.evaluate ist nur die Model.fit eines Datensatzes von (feature, label) Paaren Model.evaluate :

model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2
1875/1875 [==============================] - 3s 1ms/step - loss: 0.5999 - accuracy: 0.7962
Epoch 2/2
1875/1875 [==============================] - 3s 1ms/step - loss: 0.4618 - accuracy: 0.8428
<tensorflow.python.keras.callbacks.History at 0x7fdffe98bd10>

Wenn Sie ein unendliches Dataset übergeben, beispielsweise durch Aufrufen von Dataset.repeat() , müssen Sie nur noch das Argument steps_per_epoch :

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2
20/20 [==============================] - 0s 1ms/step - loss: 0.3764 - accuracy: 0.8656
Epoch 2/2
20/20 [==============================] - 0s 1ms/step - loss: 0.4980 - accuracy: 0.8406
<tensorflow.python.keras.callbacks.History at 0x7fe00c129050>

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 [==============================] - 2s 951us/step - loss: 0.4384 - accuracy: 0.8501
Loss : 0.4384005665779114
Accuracy : 0.8500999808311462

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 1ms/step - loss: 0.4299 - accuracy: 0.8625
Loss : 0.4299231171607971
Accuracy : 0.862500011920929

Die Labels sind beim Aufrufen von Model.predict nicht erforderlich.

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)