Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

tf.data: Erstellen Sie TensorFlow-Eingabepipelines

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

Mit der tf.data API können Sie komplexe Eingabe-Pipelines aus einfachen, wiederverwendbaren Teilen erstellen. Beispielsweise kann die Pipeline für ein Bildmodell Daten aus Dateien in einem verteilten Dateisystem aggregieren, zufällige Störungen auf jedes Bild anwenden und zufällig ausgewählte Bilder zu einem Stapel für das Training zusammenführen. Die Pipeline für ein Textmodell kann das Extrahieren von Symbolen aus Rohtextdaten, das Konvertieren dieser Symbole in das Einbetten von Bezeichnern mit einer Nachschlagetabelle und das Stapeln von Sequenzen unterschiedlicher Länge umfassen. Die tf.data API ermöglicht es, große Datenmengen zu verarbeiten, aus verschiedenen Datenformaten zu lesen und komplexe Transformationen durchzuführen.

Die tf.data API führt eine tf.data.Dataset Abstraktion ein, die eine Folge von Elementen darstellt, in denen jedes Element aus einer oder mehreren Komponenten besteht. Beispielsweise kann in einer Bildpipeline ein Element ein einzelnes Trainingsbeispiel sein, wobei ein Paar Tensorkomponenten das Bild und seine Beschriftung darstellt.

Es gibt zwei verschiedene Möglichkeiten, einen Datensatz zu erstellen:

  • Eine Datenquelle konstruiert einen Dataset von Daten im Speicher gespeichert oder in einer oder mehreren Dateien.

  • Ein Datentransformationskonstrukten eine Datenmenge von einem oder mehreren tf.data.Dataset Objekten.

import tensorflow as tf
import pathlib
import os
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

np.set_printoptions(precision=4)

Grundlegende Mechanik

Um eine Eingabe Pipeline zu erstellen, müssen Sie mit einer Datenquelle starten. Um 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 ein Dataset Objekt haben, können Sie es in ein neues Dataset umwandeln , indem Sie Methodenaufrufe für das tf.data.Dataset Objekt tf.data.Dataset . Sie können beispielsweise Transformationen pro Element wie Dataset.map() und Transformationen mit mehreren Elementen wie Dataset.batch() . Eine vollständige Liste der Transformationen finden Sie in der Dokumentation zu tf.data.Dataset .

Das Dataset Objekt ist ein iterierbares Python-Objekt. Dies ermöglicht es, seine Elemente mit einer for-Schleife zu konsumieren:

dataset = tf.data.Dataset.from_tensor_slices([8, 3, 0, 8, 2, 1])
dataset
<TensorSliceDataset shapes: (), types: tf.int32>
for elem in dataset:
  print(elem.numpy())
8
3
0
8
2
1

Oder indem Sie explizit einen Python-Iterator mit iter erstellen und seine Elemente mit next :

it = iter(dataset)

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

Alternativ können Datensatzelemente mithilfe der reduce werden, bei der alle Elemente reduziert werden, um ein einziges Ergebnis zu erzielen. Das folgende Beispiel zeigt, wie die reduce , um die Summe eines Datensatzes von Ganzzahlen zu berechnen.

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

Datensatzstruktur

Ein Datensatz enthält Elemente, die jeweils dieselbe (verschachtelte) Struktur haben, und die einzelnen Komponenten der Struktur können von jedem Typ sein, der durch tf.TypeSpec dargestellt werden tf.TypeSpec , einschließlich tf.Tensor , tf.sparse.SparseTensor , tf.RaggedTensor , tf.TensorArray , oder tf.data.Dataset .

Mit der Eigenschaft Dataset.element_spec können Sie den Typ jeder Elementkomponente überprüfen. Die Eigenschaft gibt eine verschachtelte Struktur von tf.TypeSpec Objekten zurück, die mit der Struktur des Elements übereinstimmt. tf.TypeSpec kann eine einzelne Komponente, ein Tupel von Komponenten oder ein verschachteltes Tupel von Komponenten sein. Beispielsweise:

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

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

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

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

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

Die Dataset Transformationen unterstützen Datasets beliebiger Struktur. Bei Verwendung der Dataset.map() und Dataset.filter() , die auf jedes Element eine Funktion anwenden, bestimmt die Elementstruktur die Argumente der Funktion:

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

dataset1
<TensorSliceDataset shapes: (10,), types: tf.int32>
for z in dataset1:
  print(z.numpy())
[1 2 5 3 8 9 6 7 9 3]
[5 4 3 3 9 7 3 1 5 4]
[7 3 4 8 4 8 5 9 4 1]
[8 6 8 2 6 3 5 8 3 3]

dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2
<TensorSliceDataset shapes: ((), (100,)), types: (tf.float32, tf.int32)>
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3
<ZipDataset shapes: ((10,), ((), (100,))), types: (tf.int32, (tf.float32, tf.int32))>
for a, (b,c) in dataset3:
  print('shapes: {a.shape}, {b.shape}, {c.shape}'.format(a=a, b=b, c=c))
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)

Eingabedaten lesen

NumPy-Arrays verbrauchen

Weitere Beispiele finden Sie unter Laden von NumPy-Arrays .

Wenn alle Ihre Eingangsdaten passt in Speicher, der einfachste Weg , um eine erstellen Dataset von ihnen ist , sie zu konvertieren , um tf.Tensor Objekte und die Verwendung Dataset.from_tensor_slices() .

train, test = tf.keras.datasets.fashion_mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
32768/29515 [=================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 1s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
8192/5148 [===============================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step

images, labels = train
images = images/255

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

Python-Generatoren verbrauchen

Eine weitere häufige Datenquelle, die leicht als tf.data.Dataset aufgenommen werden kann, ist der Python-Generator.

def count(stop):
  i = 0
  while i<stop:
    yield i
    i += 1
for n in count(5):
  print(n)
0
1
2
3
4

Der Konstruktor Dataset.from_generator konvertiert den Python-Generator in ein voll funktionsfähiges tf.data.Dataset .

Der Konstruktor verwendet einen aufrufbaren als Eingabe, keinen Iterator. Dadurch kann der Generator am Ende neu gestartet werden. Es wird ein optionales Argument args , das als Argument des Aufrufers übergeben wird.

Das Argument output_types ist erforderlich, da tf.data intern einen tf.Graph und für Diagrammkanten ein tf.dtype erforderlich tf.dtype .

ds_counter = tf.data.Dataset.from_generator(count, args=[25], output_types=tf.int32, output_shapes = (), )
for count_batch in ds_counter.repeat().batch(10).take(10):
  print(count_batch.numpy())
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]

Das Argument output_shapes ist nicht erforderlich , wird jedoch dringend empfohlen, da viele Tensorflow-Operationen keine Tensoren mit unbekanntem Rang unterstützen. Wenn die Länge einer bestimmten Achse unbekannt oder variabel ist, setzen Sie sie in den output_shapes None .

Es ist auch wichtig zu beachten, dass die output_shapes und output_types denselben Verschachtelungsregeln wie andere Dataset-Methoden folgen.

Hier ist ein Beispielgenerator, der beide Aspekte demonstriert. Er gibt Tupel von Arrays zurück, wobei das zweite Array ein Vektor mit unbekannter Länge ist.

def gen_series():
  i = 0
  while True:
    size = np.random.randint(0, 10)
    yield i, np.random.normal(size=(size,))
    i += 1
for i, series in gen_series():
  print(i, ":", str(series))
  if i > 5:
    break
0 : [1.2226]
1 : [ 0.4785  1.1887 -0.2828  0.6047  1.3367 -0.4387  0.1822]
2 : [ 1.1343 -0.2676  0.0224 -0.111  -0.1384 -1.9315]
3 : [-0.8651]
4 : [0.6275]
5 : [ 0.8034  2.0773  0.6183 -0.3746]
6 : [-0.9439 -0.6686]

Die erste Ausgabe ist eine int32 die zweite ist eine float32 .

Das erste Element ist ein Skalar, Form () , und das zweite ist ein Vektor unbekannter Länge, Form (None,)

ds_series = tf.data.Dataset.from_generator(
    gen_series, 
    output_types=(tf.int32, tf.float32), 
    output_shapes=((), (None,)))

ds_series
<FlatMapDataset shapes: ((), (None,)), types: (tf.int32, tf.float32)>

Jetzt kann es wie ein reguläres tf.data.Dataset . Beachten Sie, dass Sie beim Stapeln eines Datasets mit variabler Form Dataset.padded_batch verwenden Dataset.padded_batch .

ds_series_batch = ds_series.shuffle(20).padded_batch(10)

ids, sequence_batch = next(iter(ds_series_batch))
print(ids.numpy())
print()
print(sequence_batch.numpy())
[18  1  8 22  5 13  6  2 14 28]

[[ 0.0196 -1.007   0.1843  0.0289  0.0735 -0.6279 -1.0877  0.    ]
 [ 0.8238 -1.0284  0.      0.      0.      0.      0.      0.    ]
 [ 0.544  -1.1061  1.2368  0.3975  0.      0.      0.      0.    ]
 [-1.1933 -0.7535  1.0497  0.1764  1.5319  0.9202  0.4027  0.6844]
 [-1.2025 -2.7148  1.0702  1.3893  0.      0.      0.      0.    ]
 [ 1.3787  0.6817  0.1197 -0.1178  0.9764  0.8895  0.      0.    ]
 [-1.523  -0.7722 -2.13    0.2761 -1.1094  0.      0.      0.    ]
 [ 0.203   2.1858 -0.722   1.2554  1.2208  0.1813 -2.3427  0.    ]
 [ 0.7685  1.7138 -1.2376 -2.6168  0.2565  0.0753  1.5653  0.    ]
 [ 0.0908 -0.4535  0.1257 -0.5122  0.      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 [==============================] - 9s 0us/step

Erstellen Sie den image.ImageDataGenerator

img_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255, rotation_range=20)
images, labels = next(img_gen.flow_from_directory(flowers))
Found 3670 images belonging to 5 classes.

print(images.dtype, images.shape)
print(labels.dtype, labels.shape)
float32 (32, 256, 256, 3)
float32 (32, 5)

ds = tf.data.Dataset.from_generator(
    lambda: img_gen.flow_from_directory(flowers), 
    output_types=(tf.float32, tf.float32), 
    output_shapes=([32,256,256,3], [32,5])
)

ds.element_spec
(TensorSpec(shape=(32, 256, 256, 3), dtype=tf.float32, name=None),
 TensorSpec(shape=(32, 5), dtype=tf.float32, name=None))
for images, label in ds.take(1):
  print('images.shape: ', images.shape)
  print('labels.shape: ', labels.shape)

Found 3670 images belonging to 5 classes.
images.shape:  (32, 256, 256, 3)
labels.shape:  (32, 5)

TFRecord-Daten verbrauchen

Ein End-to-End-Beispiel finden Sie unter Laden von TFRecords .

Die tf.data API unterstützt eine Vielzahl von Dateiformaten, sodass Sie große Datenmengen verarbeiten können, die nicht in den Speicher passen. Beispielsweise ist das TFRecord-Dateiformat ein einfaches aufzeichnungsorientiertes Binärformat, das viele TensorFlow-Anwendungen zum Trainieren von Daten verwenden. Mit der Klasse tf.data.TFRecordDataset können Sie den Inhalt einer oder mehrerer TFRecord-Dateien als Teil einer Eingabe-Pipeline streamen.

Hier ist ein Beispiel mit der Testdatei der French Street Name Signs (FSNS).

# Creates a dataset that reads all of the examples from two files.
fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001
7905280/7904079 [==============================] - 0s 0us/step

Das filenames für den TFRecordDataset Initialisierer kann entweder eine Zeichenfolge, eine Liste von Zeichenfolgen oder ein tf.Tensor von Zeichenfolgen sein. Wenn Sie also zwei Dateigruppen für Schulungs- und Validierungszwecke haben, können Sie eine Factory-Methode erstellen, die den Datensatz erstellt, wobei Dateinamen als Eingabeargument verwendet werden:

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

Viele TensorFlow-Projekte verwenden serialisierte tf.train.Example Datensätze in ihren TFRecord-Dateien. Diese müssen dekodiert werden, bevor sie überprüft werden können:

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

parsed.features.feature['image/text']
bytes_list {
  value: "Rue Perreyon"
}

Textdaten verbrauchen

Ein Ende-zu-Ende-Beispiel finden Sie unter Laden von Text .

Viele Datensätze werden als eine oder mehrere Textdateien verteilt. Das tf.data.TextLineDataset bietet eine einfache Möglichkeit, Zeilen aus einer oder mehreren Textdateien zu extrahieren. Bei einem oder mehreren Dateinamen erzeugt ein TextLineDataset ein Element mit Zeichenfolgenwert pro Zeile dieser Dateien.

directory_url = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
file_names = ['cowper.txt', 'derby.txt', 'butler.txt']

file_paths = [
    tf.keras.utils.get_file(file_name, directory_url + file_name)
    for file_name in file_names
]
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/cowper.txt
819200/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step

dataset = tf.data.TextLineDataset(file_paths)

Hier sind die ersten Zeilen der ersten Datei:

for line in dataset.take(5):
  print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b'His wrath pernicious, who ten thousand woes'
b"Caused to Achaia's host, sent many a soul"
b'Illustrious into Ades premature,'
b'And Heroes gave (so stood the will of Jove)'

Verwenden Sie Dataset.interleave Zeilen zwischen Dateien zu Dataset.interleave . Dies erleichtert das Zusammenmischen von Dateien. Hier sind die erste, zweite und dritte Zeile jeder Übersetzung:

files_ds = tf.data.Dataset.from_tensor_slices(file_paths)
lines_ds = files_ds.interleave(tf.data.TextLineDataset, cycle_length=3)

for i, line in enumerate(lines_ds.take(9)):
  if i % 3 == 0:
    print()
  print(line.numpy())

b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b"\xef\xbb\xbfOf Peleus' son, Achilles, sing, O Muse,"
b'\xef\xbb\xbfSing, O goddess, the anger of Achilles son of Peleus, that brought'

b'His wrath pernicious, who ten thousand woes'
b'The vengeance, deep and deadly; whence to Greece'
b'countless ills upon the Achaeans. Many a brave soul did it send'

b"Caused to Achaia's host, sent many a soul"
b'Unnumbered ills arose; which many a soul'
b'hurrying down to Hades, and many a hero did it yield a prey to dogs and'

Standardmäßig liefert ein TextLineDataset jede Zeile jeder Datei, was möglicherweise nicht wünschenswert ist, wenn die Datei beispielsweise mit einer Kopfzeile beginnt oder Kommentare enthält. Diese Zeilen können mit den Dataset.skip() oder Dataset.filter() werden. Hier überspringen Sie die erste Zeile und filtern dann, um nur Überlebende zu finden.

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/train.csv
32768/30874 [===============================] - 0s 0us/step

for line in titanic_lines.take(10):
  print(line.numpy())
b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone'
b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n'
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y'
b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'

def survived(line):
  return tf.not_equal(tf.strings.substr(line, 0, 1), "0")

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

CSV-Daten verbrauchen

Weitere Beispiele finden Sie unter Laden von CSV-Dateien und Laden von Pandas DataFrames .

Das CSV-Dateiformat ist ein beliebtes Format zum Speichern von Tabellendaten im Klartext.

Beispielsweise:

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
df = pd.read_csv(titanic_file, index_col=None)
df.head()

Wenn Ihre Daten in den Speicher Dataset.from_tensor_slices , funktioniert dieselbe Dataset.from_tensor_slices Methode für Wörterbücher, sodass diese Daten problemlos importiert werden können:

titanic_slices = tf.data.Dataset.from_tensor_slices(dict(df))

for feature_batch in titanic_slices.take(1):
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
  'survived'          : 0
  'sex'               : b'male'
  'age'               : 22.0
  'n_siblings_spouses': 1
  'parch'             : 0
  'fare'              : 7.25
  'class'             : b'Third'
  'deck'              : b'unknown'
  'embark_town'       : b'Southampton'
  'alone'             : b'n'

Ein skalierbarerer Ansatz besteht darin, bei Bedarf von der Festplatte zu laden.

Das Modul tf.data bietet Methoden zum Extrahieren von Datensätzen aus einer oder mehreren CSV-Dateien, die RFC 4180 entsprechen .

Die Funktion experimental.make_csv_dataset ist die übergeordnete Schnittstelle zum Lesen von Gruppen von CSV-Dateien. Es unterstützt die Inferenz von Spaltentypen und viele andere Funktionen wie Stapeln und Mischen, um die Verwendung zu vereinfachen.

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived")
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  print("features:")
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [0 0 0 0]
features:
  'sex'               : [b'male' b'male' b'male' b'male']
  'age'               : [18. 19. 31. 34.]
  'n_siblings_spouses': [0 0 0 1]
  'parch'             : [0 0 0 0]
  'fare'              : [ 8.3    8.05   7.775 26.   ]
  'class'             : [b'Third' b'Third' b'Third' b'Second']
  'deck'              : [b'unknown' b'unknown' b'unknown' b'unknown']
  'embark_town'       : [b'Southampton' b'Southampton' b'Southampton' b'Southampton']
  'alone'             : [b'y' b'y' b'y' b'n']

Sie können das Argument select_columns , wenn Sie nur eine Teilmenge von Spalten benötigen.

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived", select_columns=['class', 'fare', 'survived'])
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [1 0 0 1]
  'fare'              : [ 20.25     9.     110.8833  59.4   ]
  'class'             : [b'Third' b'Third' b'First' b'First']

Es gibt auch eine untergeordnete experimental.CsvDataset Klasse, die eine feinkörnigere Steuerung bietet. Es wird keine Inferenz des Spaltentyps unterstützt. Stattdessen müssen Sie den Typ jeder Spalte angeben.

titanic_types  = [tf.int32, tf.string, tf.float32, tf.int32, tf.int32, tf.float32, tf.string, tf.string, tf.string, tf.string] 
dataset = tf.data.experimental.CsvDataset(titanic_file, titanic_types , header=True)

for line in dataset.take(10):
  print([item.numpy() for item in line])
[0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 38.0, 1, 0, 71.2833, b'First', b'C', b'Cherbourg', b'n']
[1, b'female', 26.0, 0, 0, 7.925, b'Third', b'unknown', b'Southampton', b'y']
[1, b'female', 35.0, 1, 0, 53.1, b'First', b'C', b'Southampton', b'n']
[0, b'male', 28.0, 0, 0, 8.4583, b'Third', b'unknown', b'Queenstown', b'y']
[0, b'male', 2.0, 3, 1, 21.075, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 27.0, 0, 2, 11.1333, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 14.0, 1, 0, 30.0708, b'Second', b'unknown', b'Cherbourg', b'n']
[1, b'female', 4.0, 1, 1, 16.7, b'Third', b'G', b'Southampton', b'n']
[0, b'male', 20.0, 0, 0, 8.05, b'Third', b'unknown', b'Southampton', b'y']

Wenn einige Spalten leer sind, können Sie auf dieser übergeordneten Schnittstelle Standardwerte anstelle von Spaltentypen angeben.

%%writefile missing.csv
1,2,3,4
,2,3,4
1,,3,4
1,2,,4
1,2,3,
,,,
Writing missing.csv

# Creates a dataset that reads all of the records from two CSV files, each with
# four float columns which may have missing values.

record_defaults = [999,999,999,999]
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults)
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (4,), types: tf.int32>
for line in dataset:
  print(line.numpy())
[1 2 3 4]
[999   2   3   4]
[  1 999   3   4]
[  1   2 999   4]
[  1   2   3 999]
[999 999 999 999]

Standardmäßig liefert ein CsvDataset jede Spalte jeder Zeile der Datei, was möglicherweise nicht wünschenswert ist, z. B. wenn die Datei mit einer Kopfzeile beginnt, die ignoriert werden sollte, oder wenn einige Spalten in der Eingabe nicht erforderlich sind. Diese Zeilen und Felder können mit den Argumenten header und select_cols entfernt werden.

# Creates a dataset that reads all of the records from two CSV files with
# headers, extracting float data from columns 2 and 4.
record_defaults = [999, 999] # Only provide defaults for the selected columns
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults, select_cols=[1, 3])
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (2,), types: tf.int32>
for line in dataset:
  print(line.numpy())
[2 4]
[2 4]
[999   4]
[2 4]
[  2 999]
[999 999]

Dateisätze verbrauchen

Es gibt viele Datensätze, die als Dateisatz verteilt sind, wobei jede Datei ein Beispiel ist.

flowers_root = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
flowers_root = pathlib.Path(flowers_root)

Das Stammverzeichnis enthält ein Verzeichnis für jede Klasse:

for item in flowers_root.glob("*"):
  print(item.name)
sunflowers
daisy
LICENSE.txt
roses
tulips
dandelion

Die Dateien in jedem Klassenverzeichnis sind Beispiele:

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

for f in list_ds.take(5):
  print(f.numpy())
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/4933229095_f7e4218b28.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/18282528206_7fb3166041.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/3711723108_65247a3170.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/4019748730_ee09b39a43.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/475936554_a2b38aaa8e.jpg'

Lesen Sie die Daten mit der Funktion tf.io.read_file und extrahieren Sie die Beschriftung aus dem Pfad. tf.io.read_file (image, label) Paare zurück:

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

labeled_ds = list_ds.map(process_path)
for image_raw, label_text in labeled_ds.take(1):
  print(repr(image_raw.numpy()[:100]))
  print()
  print(label_text.numpy())
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xe2\x0cXICC_PROFILE\x00\x01\x01\x00\x00\x0cHLino\x02\x10\x00\x00mntrRGB XYZ \x07\xce\x00\x02\x00\t\x00\x06\x001\x00\x00acspMSFT\x00\x00\x00\x00IEC sRGB\x00\x00\x00\x00\x00\x00'

b'roses'

Batching von Datensatzelementen

Einfache Dosierung

Die einfachste Form des Stapelns von n aufeinanderfolgenden Elementen eines Datensatzes zu einem einzelnen Element. Die Dataset.batch() -Transformation führt genau dies mit denselben Einschränkungen aus wie der Operator tf.stack() , der auf jede Komponente der Elemente angewendet wird: dh für jede Komponente i müssen alle Elemente einen Tensor mit genau derselben Form haben.

inc_dataset = tf.data.Dataset.range(100)
dec_dataset = tf.data.Dataset.range(0, -100, -1)
dataset = tf.data.Dataset.zip((inc_dataset, dec_dataset))
batched_dataset = dataset.batch(4)

for batch in batched_dataset.take(4):
  print([arr.numpy() for arr in batch])
[array([0, 1, 2, 3]), array([ 0, -1, -2, -3])]
[array([4, 5, 6, 7]), array([-4, -5, -6, -7])]
[array([ 8,  9, 10, 11]), array([ -8,  -9, -10, -11])]
[array([12, 13, 14, 15]), array([-12, -13, -14, -15])]

Während tf.data versucht, tf.data , führen die Standardeinstellungen von Dataset.batch zu einer unbekannten Dataset.batch da der letzte Stapel möglicherweise nicht voll ist. Beachten Sie die None s in der Form:

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

Verwenden Sie das Argument drop_remainder , um diesen letzten Stapel zu ignorieren und die vollständige drop_remainder zu erhalten:

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

Batching Tensoren mit Polsterung

Das obige Rezept funktioniert für Tensoren, die alle die gleiche Größe haben. Viele Modelle (z. B. Sequenzmodelle) arbeiten jedoch mit Eingabedaten, die unterschiedliche Größen haben können (z. B. Sequenzen unterschiedlicher Länge). Um diesen Fall zu Dataset.padded_batch , können Sie mit der Dataset.padded_batch Umwandlung Tensoren unterschiedlicher Form Dataset.padded_batch , indem Sie eine oder mehrere Dimensionen Dataset.padded_batch , in denen sie aufgefüllt werden können.

dataset = tf.data.Dataset.range(100)
dataset = dataset.map(lambda x: tf.fill([tf.cast(x, tf.int32)], x))
dataset = dataset.padded_batch(4, padded_shapes=(None,))

for batch in dataset.take(2):
  print(batch.numpy())
  print()

[[0 0 0]
 [1 0 0]
 [2 2 0]
 [3 3 3]]

[[4 4 4 4 0 0 0]
 [5 5 5 5 5 0 0]
 [6 6 6 6 6 6 0]
 [7 7 7 7 7 7 7]]


Mit der Dataset.padded_batch Umwandlung können Sie für jede Dimension jeder Komponente unterschiedliche Auffüllungen Dataset.padded_batch können eine variable Länge (im obigen Beispiel durch None ) oder eine konstante Länge haben. Es ist auch möglich, den Füllwert zu überschreiben, der standardmäßig 0 ist.

Trainingsworkflows

Verarbeitung mehrerer Epochen

Die tf.data API bietet zwei Hauptmethoden zum Verarbeiten mehrerer Epochen derselben Daten.

Der einfachste Weg, ein Dataset in mehreren Epochen zu Dataset.repeat() ist die Verwendung der Dataset.repeat() Transformation. Erstellen Sie zunächst einen Datensatz mit Titandaten:

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
def plot_batch_sizes(ds):
  batch_sizes = [batch.shape[0] for batch in ds]
  plt.bar(range(len(batch_sizes)), batch_sizes)
  plt.xlabel('Batch number')
  plt.ylabel('Batch size')

Durch Anwenden der Dataset.repeat() Transformation ohne Argumente wird die Eingabe auf unbestimmte Zeit wiederholt.

Die Dataset.repeat Transformation verkettet ihre Argumente, ohne das Ende einer Epoche und den Beginn der nächsten Epoche zu signalisieren. Aus diesem Dataset.batch Dataset.repeat ein Dataset.batch , der nach Dataset.batch angewendet wird, Dataset.repeat , die die Epochengrenzen überschreiten:

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

png

Wenn Sie eine klare Epochentrennung benötigen, setzen Sie Dataset.batch vor die Wiederholung:

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

plot_batch_sizes(titanic_batches)

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

Fügen Sie dem Datensatz einen Index hinzu, damit Sie den Effekt sehen können:

lines = tf.data.TextLineDataset(titanic_file)
counter = tf.data.experimental.Counter()

dataset = tf.data.Dataset.zip((counter, lines))
dataset = dataset.shuffle(buffer_size=100)
dataset = dataset.batch(20)
dataset
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

Da die buffer_size 100 und die buffer_size 20 beträgt, enthält der erste Stapel keine Elemente mit einem Index über 120.

n,line_batch = next(iter(dataset))
print(n.numpy())
[ 29  39  94  10  81  85  74   3  91  53  87  17   1  64  54 107  20  63
 116  62]

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

Dataset.shuffle signalisiert das Ende einer Epoche erst, wenn der Shuffle-Puffer leer ist. Ein Shuffle vor einer Wiederholung zeigt also jedes Element einer Epoche, bevor zur nächsten übergegangen wird:

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.shuffle(buffer_size=100).batch(10).repeat(2)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(60).take(5):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[469 560 614 431 557 558 618 616 571 532]
[464 403 509 589 365 610 596 428 600 605]
[539 467 163 599 624 545 552 548]
[92 30 73 51 72 71 56 83 32 46]
[ 74 106  75  78  26  21   3  54  80  50]

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 0x7f79e43368d0>

png

Aber eine Wiederholung vor einem Shuffle vermischt die Grenzen der Epoche:

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.repeat(2).shuffle(buffer_size=100).batch(10)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(55).take(15):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[618 517  22  13 602 608  25 621  12 579]
[568 620 534 547 319   1  17 346  29 581]
[537 395 560  33 617 557 561 610 585 627]
[428 607  32   0 455 496 605 624   6 596]
[511 588  14 572   4  21  66 471  41  36]
[583 622  49  54 616  75  65  57  76   7]
[ 77  37 574 586  74  56  28 419  73   5]
[599  83  31  61  91 481 577  98  26  50]
[  2 625  85  23 606 102 550  20 612  15]
[ 24  63 592 615 388  39  45  51  68  97]
[ 46  34  67  55  79  94 548  47  72 436]
[127  89  30  90 126 601 360  87 104  52]
[ 59 130 106 584 118 265  48  70 597 121]
[626 105 135  10  38  53 132 136 101   9]
[512 542 109 154 108  42 114  11  58 166]

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 0x7f79e42b60b8>

png

Daten vorverarbeiten

Die Dataset.map(f) -Transformation erzeugt einen neuen Datensatz, indem eine bestimmte Funktion f auf jedes Element des Eingabedatensatzes angewendet wird. Es basiert auf der Funktion map() , die üblicherweise auf Listen (und andere Strukturen) in funktionalen Programmiersprachen angewendet wird. Die Funktion f die tf.Tensor Objekte, die ein einzelnes Element in der Eingabe darstellen, und gibt die tf.Tensor Objekte zurück, die ein einzelnes Element im neuen Datensatz darstellen. Die Implementierung verwendet Standard-TensorFlow-Operationen, um ein Element in ein anderes umzuwandeln.

Dieser Abschnitt enthält allgemeine Beispiele für die Verwendung von Dataset.map() .

Bilddaten dekodieren und ihre Größe ändern

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

Erstellen Sie den Datensatz mit den Blumendateinamen neu:

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

Schreiben Sie eine Funktion, die die Datensatzelemente bearbeitet.

# Reads an image from a file, decodes it into a dense tensor, and resizes it
# to a fixed shape.
def parse_image(filename):
  parts = tf.strings.split(filename, os.sep)
  label = parts[-2]

  image = tf.io.read_file(filename)
  image = tf.image.decode_jpeg(image)
  image = tf.image.convert_image_dtype(image, tf.float32)
  image = tf.image.resize(image, [128, 128])
  return image, label

Testen Sie, ob es funktioniert.

file_path = next(iter(list_ds))
image, label = parse_image(file_path)

def show(image, label):
  plt.figure()
  plt.imshow(image)
  plt.title(label.numpy().decode('utf-8'))
  plt.axis('off')

show(image, label)

png

Ordnen Sie es dem Datensatz zu.

images_ds = list_ds.map(parse_image)

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

png

png

Anwenden einer beliebigen Python-Logik

Verwenden Sie aus Leistungsgründen TensorFlow-Vorgänge, um Ihre Daten nach Möglichkeit vorzuverarbeiten. Manchmal ist es jedoch nützlich, externe Python-Bibliotheken aufzurufen, wenn Sie Ihre Eingabedaten analysieren. Sie können die Operation tf.py_function() in einer Dataset.map() -Transformation verwenden.

Wenn Sie beispielsweise eine zufällige Drehung anwenden möchten, verfügt das Modul tf.image nur über tf.image.rot90 , was für die Bildvergrößerung nicht sehr nützlich ist.

Verwenden scipy.ndimage.rotate stattdessen die Funktion tf.py_function , um die Funktion scipy.ndimage.rotate zu demonstrieren:

import scipy.ndimage as ndimage

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

png

Um diese Funktion mit Dataset.map gelten dieselben Einschränkungen wie mit Dataset.from_generator . Sie müssen die Rückgabeformen und -typen beschreiben, wenn Sie die Funktion anwenden:

def tf_random_rotate_image(image, label):
  im_shape = image.shape
  [image,] = tf.py_function(random_rotate_image, [image], [tf.float32])
  image.set_shape(im_shape)
  return image, label
rot_ds = images_ds.map(tf_random_rotate_image)

for image, label in rot_ds.take(2):
  show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

png

png

tf.Example

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

fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>

Sie können mit tf.train.Example Protos außerhalb eines tf.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])

Zeitreihenfensterung

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

Zeitreihendaten werden häufig mit intakter Zeitachse organisiert.

Verwenden Sie einen einfachen Dataset.range , um Dataset.range zu demonstrieren:

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

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

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

batch

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

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

Um dichte Vorhersagen für einen Schritt in die Zukunft zu treffen, können Sie die Features und Beschriftungen um einen Schritt relativ zueinander verschieben:

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

predict_dense_1_step = batches.map(dense_1_step)

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

Um ein ganzes Fenster anstelle eines festen Versatzes vorherzusagen, können Sie die Stapel in zwei Teile aufteilen:

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

def label_next_5_steps(batch):
  return (batch[:-5],   # Take the first 5 steps
          batch[-5:])   # take the remainder

predict_5_steps = batches.map(label_next_5_steps)

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

Verwenden Sie Dataset.zip , um eine gewisse Überlappung zwischen den Merkmalen eines Dataset.zip und den Etiketten eines anderen Dataset.zip :

feature_length = 10
label_length = 5

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

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

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]
[10 11 12 13 14 15 16 17 18 19]  =>  [20 21 22 23 24]
[20 21 22 23 24 25 26 27 28 29]  =>  [30 31 32 33 34]

window

Während die Verwendung von Dataset.batch funktioniert, gibt es Situationen, in denen Sie möglicherweise eine Dataset.batch Kontrolle benötigen. Die Dataset.window Methode gibt Ihnen die vollständige Kontrolle, aber erfordert eine gewisse Sorgfalt: es gibt Dataset von Datasets . Weitere Informationen finden Sie unter Datensatzstruktur .

window_size = 5

windows = range_ds.window(window_size, shift=1)
for sub_ds in windows.take(5):
  print(sub_ds)
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>

Die Dataset.flat_map Methode kann einen Datensatz mit Datensätzen in einen einzelnen Datensatz Dataset.flat_map :

 for x in windows.flat_map(lambda x: x).take(30):
   print(x.numpy(), end=' ')
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7f79e44309d8> and will run it as-is.
Cause: could not parse the source code:

for x in windows.flat_map(lambda x: x).take(30):

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function <lambda> at 0x7f79e44309d8> and will run it as-is.
Cause: could not parse the source code:

for x in windows.flat_map(lambda x: x).take(30):

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9 

In fast allen Fällen sollten Sie .batch den Datensatz .batch :

def sub_to_batch(sub):
  return sub.batch(window_size, drop_remainder=True)

for example in windows.flat_map(sub_to_batch).take(5):
  print(example.numpy())
[0 1 2 3 4]
[1 2 3 4 5]
[2 3 4 5 6]
[3 4 5 6 7]
[4 5 6 7 8]

Jetzt können Sie sehen, dass das shift Argument steuert, um wie viel sich jedes Fenster bewegt.

Wenn Sie dies zusammenstellen, können Sie diese Funktion schreiben:

def make_window_dataset(ds, window_size=5, shift=1, stride=1):
  windows = ds.window(window_size, shift=shift, stride=stride)

  def sub_to_batch(sub):
    return sub.batch(window_size, drop_remainder=True)

  windows = windows.flat_map(sub_to_batch)
  return windows

ds = make_window_dataset(range_ds, window_size=10, shift = 5, stride=3)

for example in ds.take(10):
  print(example.numpy())
[ 0  3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34 37]
[15 18 21 24 27 30 33 36 39 42]
[20 23 26 29 32 35 38 41 44 47]
[25 28 31 34 37 40 43 46 49 52]
[30 33 36 39 42 45 48 51 54 57]
[35 38 41 44 47 50 53 56 59 62]
[40 43 46 49 52 55 58 61 64 67]
[45 48 51 54 57 60 63 66 69 72]

Dann ist es einfach, Etiketten wie zuvor zu extrahieren:

dense_labels_ds = ds.map(dense_1_step)

for inputs,labels in dense_labels_ds.take(3):
  print(inputs.numpy(), "=>", labels.numpy())
[ 0  3  6  9 12 15 18 21 24] => [ 3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29] => [ 8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34] => [13 16 19 22 25 28 31 34 37]

Resampling

Wenn Sie mit einem Datensatz arbeiten, der sehr klassenunausgewogen ist, möchten Sie den Datensatz möglicherweise erneut abtasten. tf.data bietet dazu zwei Methoden. Der Datensatz für Kreditkartenbetrug ist ein gutes Beispiel für diese Art von Problem.

zip_path = tf.keras.utils.get_file(
    origin='https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip',
    fname='creditcard.zip',
    extract=True)

csv_path = zip_path.replace('.zip', '.csv')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip
69156864/69155632 [==============================] - 3s 0us/step

creditcard_ds = tf.data.experimental.make_csv_dataset(
    csv_path, batch_size=1024, label_name="Class",
    # Set the column types: 30 floats and an int.
    column_defaults=[float()]*30+[int()])

Überprüfen Sie nun die Verteilung der Klassen. Sie ist stark verzerrt:

def count(counts, batch):
  features, labels = batch
  class_1 = labels == 1
  class_1 = tf.cast(class_1, tf.int32)

  class_0 = labels == 0
  class_0 = tf.cast(class_0, tf.int32)

  counts['class_0'] += tf.reduce_sum(class_0)
  counts['class_1'] += tf.reduce_sum(class_1)

  return counts
counts = creditcard_ds.take(10).reduce(
    initial_state={'class_0': 0, 'class_1': 0},
    reduce_func = count)

counts = np.array([counts['class_0'].numpy(),
                   counts['class_1'].numpy()]).astype(np.float32)

fractions = counts/counts.sum()
print(fractions)
[0.9957 0.0043]

Ein üblicher Ansatz für das Training mit einem unausgeglichenen Datensatz besteht darin, ihn auszugleichen. tf.data enthält einige Methoden, die diesen Workflow ermöglichen:

Abtastung von Datensätzen

Ein Ansatz zum sample_from_datasets Abtasten eines Datasets ist die Verwendung von sample_from_datasets . Dies gilt insbesondere dann, wenn Sie für jede Klasse ein separates data.Dataset haben.

Verwenden Sie hier einfach den Filter, um sie aus den Kreditkartenbetrugsdaten zu generieren:

negative_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==0)
    .repeat())
positive_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==1)
    .repeat())
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7f79e43ce510> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==0)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function <lambda> at 0x7f79e43ce510> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==0)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7f79e43ceae8> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==1)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function <lambda> at 0x7f79e43ceae8> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==1)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert

for features, label in positive_ds.batch(10).take(1):
  print(label.numpy())
[1 1 1 1 1 1 1 1 1 1]

Um tf.data.experimental.sample_from_datasets übergeben Sie die Datasets und das Gewicht für jedes:

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

Jetzt erzeugt der Datensatz Beispiele für jede Klasse mit einer Wahrscheinlichkeit von 50/50:

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

Resampling der Ablehnung

Ein Problem mit dem obigen experimental.sample_from_datasets Ansatz besteht darin, dass pro Klasse ein separates tf.data.Dataset benötigt wird. Die Verwendung von Dataset.filter funktioniert, führt jedoch dazu, dass alle Daten zweimal geladen werden.

Die Funktion data.experimental.rejection_resample kann auf ein Dataset angewendet werden, um es neu data.experimental.rejection_resample , während es nur einmal data.experimental.rejection_resample . Elemente werden aus dem Datensatz entfernt, um ein Gleichgewicht zu erreichen.

data.experimental.rejection_resample ein class_func Argument. Diese class_func wird auf jedes Dataset-Element angewendet und verwendet, um zu bestimmen, zu welcher Klasse ein Beispiel zum Zwecke des Ausgleichs gehört.

Die Elemente von creditcard_ds sind bereits Paare (features, label) . Die class_func muss class_func nur die folgenden Labels zurückgeben:

def class_func(features, label):
  return label

Der Resampler benötigt außerdem eine Zielverteilung und optional eine anfängliche Verteilungsschätzung:

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

Der Resampler behandelt einzelne Beispiele, daher müssen unbatch den Datensatz unbatch bevor Sie den Resampler anwenden:

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


Der Resampler-Rückgabewert erstellt (class, example) Paare aus der Ausgabe von class_func . In diesem Fall war das example bereits ein Paar (feature, label) Verwenden Sie also map , um die zusätzliche Kopie der Labels abzulegen:

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

Jetzt erzeugt der Datensatz Beispiele für jede Klasse mit einer Wahrscheinlichkeit von 50/50:

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

Iterator Checkpointing

Tensorflow unterstützt die Verwendung von Checkpoints, sodass beim Neustart Ihres Trainingsprozesses der neueste Checkpoint wiederhergestellt werden kann, um den größten Teil seines Fortschritts wiederherzustellen. Neben dem Checkpointing der Modellvariablen können Sie auch den Fortschritt des Dataset-Iterators überprüfen. Dies kann nützlich sein, wenn Sie über ein großes Dataset verfügen und das Dataset nicht bei jedem Neustart von vorne beginnen möchten. Beachten Sie jedoch, dass Iteratorprüfpunkte groß sein können, da Transformationen wie shuffle und prefetch Pufferelemente innerhalb des Iterators erfordern.

Um Ihren Iterator in einen Prüfpunkt aufzunehmen, übergeben Sie den Iterator an den Konstruktor tf.train.Checkpoint .

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

iterator = iter(range_ds)
ckpt = tf.train.Checkpoint(step=tf.Variable(0), iterator=iterator)
manager = tf.train.CheckpointManager(ckpt, '/tmp/my_ckpt', max_to_keep=3)

print([next(iterator).numpy() for _ in range(5)])

save_path = manager.save()

print([next(iterator).numpy() for _ in range(5)])

ckpt.restore(manager.latest_checkpoint)

print([next(iterator).numpy() for _ in range(5)])
[0, 1, 2, 3, 4]
[5, 6, 7, 8, 9]
[5, 6, 7, 8, 9]

Verwenden von tf.data mit tf.keras

Die tf.keras API vereinfacht viele Aspekte beim Erstellen und Ausführen von Modellen für maschinelles Lernen. Die .fit() und .evaluate() und .predict() unterstützen Datasets als Eingaben. Hier ist ein kurzer Datensatz und ein Modell-Setup:

train, test = tf.keras.datasets.fashion_mnist.load_data()

images, labels = train
images = images/255.0
labels = labels.astype(np.int32)
fmnist_train_ds = tf.data.Dataset.from_tensor_slices((images, labels))
fmnist_train_ds = fmnist_train_ds.shuffle(5000).batch(32)

model = tf.keras.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(10)
])

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), 
              metrics=['accuracy'])

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

model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2
WARNING:tensorflow:Layer flatten is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2.  The layer has dtype float32 because its dtype defaults to floatx.

If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.

To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.

1875/1875 [==============================] - 3s 2ms/step - loss: 0.5973 - accuracy: 0.7969
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4629 - accuracy: 0.8407

<tensorflow.python.keras.callbacks.History at 0x7f79e43d8710>

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

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4800 - accuracy: 0.8594
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4529 - accuracy: 0.8391

<tensorflow.python.keras.callbacks.History at 0x7f79e43d84a8>

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.4386 - accuracy: 0.8508
Loss : 0.4385797381401062
Accuracy : 0.8507500290870667

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.3737 - accuracy: 0.8656
Loss : 0.37370163202285767
Accuracy : 0.8656250238418579

Die Beschriftungen werden beim Aufrufen von Model.predict nicht benötigt.

predict_ds = tf.data.Dataset.from_tensor_slices(images).batch(32)
result = model.predict(predict_ds, steps = 10)
print(result.shape)
(320, 10)

Die Beschriftungen werden jedoch ignoriert, wenn Sie einen Datensatz übergeben, der sie enthält:

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