Se usó la API de Cloud Translation para traducir esta página.
Switch to English

tf.data: compila canalizaciones de entrada de TensorFlow

Ver en TensorFlow.org Ver fuente en GitHub Descargar cuaderno

La API tf.data permite crear tuberías de entrada complejas a partir de piezas simples y reutilizables. Por ejemplo, la canalización de un modelo de imagen podría agregar datos de archivos en un sistema de archivos distribuido, aplicar perturbaciones aleatorias a cada imagen y combinar imágenes seleccionadas al azar en un lote para entrenamiento. La canalización de un modelo de texto puede implicar la extracción de símbolos de datos de texto sin procesar, convertirlos en identificadores incrustados con una tabla de búsqueda y agrupar secuencias de diferentes longitudes. La API tf.data permite manejar grandes cantidades de datos, leer desde diferentes formatos de datos y realizar transformaciones complejas.

La API tf.data introduce una abstracción tf.data.Dataset que representa una secuencia de elementos, en la que cada elemento consta de uno o más componentes. Por ejemplo, en una canalización de imágenes, un elemento puede ser un solo ejemplo de entrenamiento, con un par de componentes tensores que representan la imagen y su etiqueta.

Hay dos formas distintas de crear un conjunto de datos:

  • Una fuente de datos construye un Dataset de datos a partir de datos almacenados en la memoria o en uno o más archivos.

  • Una transformación de datos construye un conjunto de datos a partir de uno o más objetos tf.data.Dataset .

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

np.set_printoptions(precision=4)

Mecanica basica

Para crear una canalización de entrada, debe comenzar con una fuente de datos. Por ejemplo, para construir un Dataset de datos a partir de datos en la memoria, puede usar tf.data.Dataset.from_tensors() o tf.data.Dataset.from_tensor_slices() . Alternativamente, si sus datos de entrada están almacenados en un archivo en el formato TFRecord recomendado, puede usar tf.data.TFRecordDataset() .

Una vez que tenga un objeto de Dataset , puede transformarlo en un nuevo Dataset de Dataset encadenando llamadas a métodos en el objeto tf.data.Dataset . Por ejemplo, puede aplicar transformaciones por elemento como Dataset.map() y transformaciones de varios elementos como Dataset.batch() . Consulte la documentación de tf.data.Dataset para obtener una lista completa de transformaciones.

El objeto Dataset es un iterable de Python. Esto hace posible consumir sus elementos usando un bucle for:

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

O creando explícitamente un iterador de Python usando iter y consumiendo sus elementos usando next :

it = iter(dataset)

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

Alternativamente, los elementos del conjunto de datos se pueden consumir utilizando la transformación de reduce , que reduce todos los elementos para producir un único resultado. El siguiente ejemplo ilustra cómo utilizar la transformación de reduce para calcular la suma de un conjunto de datos de números enteros.

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

Estructura del conjunto de datos

Un conjunto de datos contiene elementos que tienen la misma estructura (anidada) y los componentes individuales de la estructura pueden ser de cualquier tipo representable por tf.TypeSpec , incluidos tf.Tensor , tf.sparse.SparseTensor , tf.RaggedTensor , tf.TensorArray , o tf.data.Dataset .

La propiedad Dataset.element_spec permite inspeccionar el tipo de cada componente del elemento. La propiedad devuelve una estructura anidada de objetos tf.TypeSpec , que coincide con la estructura del elemento, que puede ser un solo componente, una tupla de componentes o una tupla anidada de componentes. Por ejemplo:

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

Los Dataset transformaciones soportan los conjuntos de datos de cualquier estructura. Cuando se utilizan las Dataset.map() y Dataset.filter() , que aplican una función a cada elemento, la estructura del elemento determina los argumentos de la función:

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

Leer datos de entrada

Consumir matrices NumPy

Consulte Carga de matrices NumPy para obtener más ejemplos.

Si todos sus datos de entrada caben en la memoria, la forma más sencilla de crear un Dataset de Dataset partir de ellos es convertirlos en objetos tf.Tensor y usar 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)>

Consumir generadores de Python

Otra fuente de datos común que se puede ingerir fácilmente como un tf.data.Dataset es el generador de Python.

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

El constructor Dataset.from_generator convierte el generador de Python en un tf.data.Dataset completamente funcional.

El constructor toma un invocable como entrada, no un iterador. Esto le permite reiniciar el generador cuando llega al final. Toma un argumento args opcional, que se pasa como argumentos del invocable.

El argumento output_types es necesario porque tf.data crea un tf.Graph internamente y los bordes del gráfico requieren un 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]

El argumento output_shapes no es obligatorio, pero se recomienda encarecidamente, ya que muchas operaciones de flujo tensorial no admiten tensores con rango desconocido. Si la longitud de un eje en particular es desconocida o variable, output_shapes como None en output_shapes .

También es importante tener en cuenta que output_shapes y output_types siguen las mismas reglas de anidamiento que otros métodos de conjuntos de datos.

Aquí hay un generador de ejemplo que demuestra ambos aspectos, devuelve tuplas de matrices, donde la segunda matriz es un vector con longitud desconocida.

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]

La primera salida es un int32 la segunda es un float32 .

El primer elemento es un escalar, forma () , y el segundo es un vector de longitud desconocida, forma (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)>

Ahora se puede utilizar como un tf.data.Dataset normal. Tenga en cuenta que al procesar por lotes un conjunto de datos con una forma variable, debe usar 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.    ]]

Para obtener un ejemplo más realista, intente tf.data.Dataset preprocessing.image.ImageDataGenerator como un tf.data.Dataset .

Primero descargue los datos:

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

Crea la imagen. 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)

Consumir datos de TFRecord

Consulte Carga de TFRecords para ver un ejemplo de un extremo a otro.

La API tf.data admite una variedad de formatos de archivo para que pueda procesar grandes conjuntos de datos que no caben en la memoria. Por ejemplo, el formato de archivo TFRecord es un formato binario simple orientado a registros que muchas aplicaciones de TensorFlow usan para entrenar datos. La clase tf.data.TFRecordDataset permite transmitir el contenido de uno o más archivos TFRecord como parte de una canalización de entrada.

A continuación se muestra un ejemplo que utiliza el archivo de prueba de los carteles de nombres de calles franceses (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

El argumento de filenames para el inicializador TFRecordDataset puede ser una cadena, una lista de cadenas o un tf.Tensor de cadenas. Por lo tanto, si tiene dos conjuntos de archivos con fines de entrenamiento y validación, puede crear un método de fábrica que produzca el conjunto de datos, tomando los nombres de archivo como argumento de entrada:

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

Muchos proyectos de TensorFlow utilizan registros tf.train.Example serializados en sus archivos TFRecord. Estos deben decodificarse antes de que puedan inspeccionarse:

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

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

Consumir datos de texto

Consulte Carga de texto para ver un ejemplo de principio a fin.

Muchos conjuntos de datos se distribuyen como uno o más archivos de texto. tf.data.TextLineDataset proporciona una manera fácil de extraer líneas de uno o más archivos de texto. Dados uno o más nombres de archivo, un TextLineDataset producirá un elemento con valor de cadena por línea de esos archivos.

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)

Aquí están las primeras líneas del primer archivo:

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

Para alternar líneas entre archivos, use Dataset.interleave . Esto facilita la reproducción aleatoria de archivos. Aquí están la primera, segunda y tercera líneas de cada traducción:

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'

De forma predeterminada, un TextLineDataset produce cada línea de cada archivo, lo que puede no ser deseable, por ejemplo, si el archivo comienza con una línea de encabezado o contiene comentarios. Estas líneas se pueden eliminar mediante las Dataset.skip() o Dataset.filter() . Aquí, omite la primera línea y luego filtra para encontrar solo sobrevivientes.

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'

Consumir datos CSV

Consulte Carga de archivos CSV y Carga de Pandas DataFrames para obtener más ejemplos.

El formato de archivo CSV es un formato popular para almacenar datos tabulares en texto sin formato.

Por ejemplo:

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

Si sus datos Dataset.from_tensor_slices en la memoria, el mismo método Dataset.from_tensor_slices funciona en los diccionarios, lo que permite que estos datos se importen fácilmente:

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'

Un enfoque más escalable es cargar desde el disco según sea necesario.

El módulo tf.data proporciona métodos para extraer registros de uno o más archivos CSV que cumplen con RFC 4180 .

La función experimental.make_csv_dataset es la interfaz de alto nivel para leer conjuntos de archivos csv. Admite la inferencia de tipo de columna y muchas otras funciones, como el procesamiento por lotes y la mezcla, para simplificar el uso.

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

Puede usar el argumento select_columns si solo necesita un subconjunto de columnas.

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

También hay una clase experimental.CsvDataset nivel inferior que proporciona un control más detallado. No admite la inferencia de tipo de columna. En su lugar, debe especificar el tipo de cada columna.

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

Si algunas columnas están vacías, esta interfaz de bajo nivel le permite proporcionar valores predeterminados en lugar de tipos de columna.

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

De forma predeterminada, un CsvDataset genera cada columna de cada línea del archivo, lo que puede no ser deseable, por ejemplo, si el archivo comienza con una línea de encabezado que debe ignorarse o si algunas columnas no son necesarias en la entrada. Estas líneas y campos se pueden eliminar con los argumentos header y select_cols respectivamente.

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

Consumir conjuntos de archivos

Hay muchos conjuntos de datos distribuidos como un conjunto de archivos, donde cada archivo es un ejemplo.

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)

El directorio raíz contiene un directorio para cada clase:

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

Los archivos de cada directorio de clases son ejemplos:

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'

Lea los datos usando la función tf.io.read_file y extraiga la etiqueta de la ruta, devolviendo pares (image, label) :

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

labeled_ds = list_ds.map(process_path)
for image_raw, label_text in labeled_ds.take(1):
  print(repr(image_raw.numpy()[:100]))
  print()
  print(label_text.numpy())
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\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'

Agrupar elementos del conjunto de datos

Procesamiento por lotes simple

La forma más simple de agrupamiento por lotes apila n elementos consecutivos de un conjunto de datos en un solo elemento. La transformación Dataset.batch() hace exactamente esto, con las mismas restricciones que el operador tf.stack() , aplicado a cada componente de los elementos: es decir, para cada componente i , todos los elementos deben tener un tensor de la misma forma exacta.

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

Si bien tf.data intenta propagar la información de la forma, la configuración predeterminada de Dataset.batch resultado un tamaño de lote desconocido porque es posible que el último lote no esté lleno. Tenga en cuenta los None en la forma:

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

Use el argumento drop_remainder para ignorar ese último lote y obtener la propagación de forma completa:

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

Tensores por lotes con relleno

La receta anterior funciona para tensores que tienen el mismo tamaño. Sin embargo, muchos modelos (por ejemplo, modelos de secuencia) funcionan con datos de entrada que pueden tener diferentes tamaños (por ejemplo, secuencias de diferentes longitudes). Para manejar este caso, la transformación Dataset.padded_batch permite Dataset.padded_batch tensores de diferente forma especificando una o más dimensiones en las que se pueden rellenar.

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


La transformación Dataset.padded_batch permite establecer un relleno diferente para cada dimensión de cada componente, y puede ser de longitud variable (indicado por None en el ejemplo anterior) o de longitud constante. También es posible anular el valor de relleno, que por defecto es 0.

Flujos de trabajo de formación

Procesando múltiples épocas

La API tf.data ofrece dos formas principales de procesar varias épocas de los mismos datos.

La forma más sencilla de iterar sobre un conjunto de datos en varias épocas es usar la transformación Dataset.repeat() . Primero, cree un conjunto de datos de datos titánicos:

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

La aplicación de la transformación Dataset.repeat() sin argumentos repetirá la entrada indefinidamente.

La transformación Dataset.repeat concatena sus argumentos sin señalar el final de una época y el comienzo de la siguiente. Debido a esto, un Dataset.batch aplicado después de Dataset.repeat producirá lotes que Dataset.repeat límites de la época:

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

png

Si necesita una separación de época clara, coloque Dataset.batch antes de la repetición:

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

plot_batch_sizes(titanic_batches)

png

Si desea realizar un cálculo personalizado (por ejemplo, para recopilar estadísticas) al final de cada época, lo más sencillo es reiniciar la iteración del conjunto de datos en cada época:

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

Mezcla aleatoria de datos de entrada

La transformación Dataset.shuffle() mantiene un búfer de tamaño fijo y elige el siguiente elemento uniformemente al azar de ese búfer.

Agrega un índice al conjunto de datos para que puedas ver el efecto:

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

Dado que buffer_size es 100 y el tamaño del lote es 20, el primer lote no contiene elementos con un índice superior a 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]

Al igual que con Dataset.batch el orden relativo a Dataset.repeat importante.

Dataset.shuffle no Dataset.shuffle el final de una época hasta que el búfer de reproducción aleatoria esté vacío. Entonces, una reproducción aleatoria colocada antes de una repetición mostrará todos los elementos de una época antes de pasar a la siguiente:

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

Pero una repetición antes de una reproducción aleatoria mezcla los límites de la época:

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

Procesamiento previo de datos

La Dataset.map(f) produce un nuevo conjunto de datos aplicando una función f dada a cada elemento del conjunto de datos de entrada. Se basa en la función map() que se aplica comúnmente a listas (y otras estructuras) en lenguajes de programación funcionales. La función f toma los objetos tf.Tensor que representan un solo elemento en la entrada y devuelve los objetos tf.Tensor que representarán un solo elemento en el nuevo conjunto de datos. Su implementación usa operaciones estándar de TensorFlow para transformar un elemento en otro.

Esta sección cubre ejemplos comunes de cómo usar Dataset.map() .

Decodificar datos de imagen y cambiar su tamaño

Al entrenar una red neuronal con datos de imágenes del mundo real, a menudo es necesario convertir imágenes de diferentes tamaños a un tamaño común, de modo que puedan agruparse en un tamaño fijo.

Reconstruir el conjunto de datos de nombres de archivos de flores:

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

Escribe una función que manipule los elementos del conjunto de datos.

# 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

Prueba que funciona.

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

Asignelo sobre el conjunto de datos.

images_ds = list_ds.map(parse_image)

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

png

png

Aplicar lógica de Python arbitraria

Por motivos de rendimiento, utilice las operaciones de TensorFlow para preprocesar sus datos siempre que sea posible. Sin embargo, a veces es útil llamar a bibliotecas de Python externas al analizar sus datos de entrada. Puede usar la operación tf.py_function() en una transformación Dataset.map() .

Por ejemplo, si desea aplicar una rotación aleatoria, el módulo tf.image solo tiene tf.image.rot90 , que no es muy útil para el aumento de imágenes.

Para demostrar tf.py_function , intente usar la función scipy.ndimage.rotate lugar:

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

Para usar esta función con Dataset.map las mismas advertencias que con Dataset.from_generator , debe describir las formas y tipos de retorno cuando aplica la función:

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

Análisis de tf.Example mensajes de búfer de protocolo

Muchas canalizaciones de entrada extraen tf.train.Example mensajes de búfer de protocolo de un formato TFRecord. Cada registro tf.train.Example contiene una o más "características", y la canalización de entrada normalmente convierte estas características en tensores.

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>

Puede trabajar con tf.train.Example protos fuera de tf.data.Dataset para comprender los datos:

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

Ventana de series de tiempo

Para ver un ejemplo de serie temporal de principio a fin, consulte: Predicción de series temporales .

Los datos de series de tiempo a menudo se organizan con el eje de tiempo intacto.

Utilice un Dataset.range simple para demostrar:

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

Normalmente, los modelos basados ​​en este tipo de datos querrán un intervalo de tiempo contiguo.

El enfoque más simple sería agrupar los datos:

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

O para hacer predicciones densas un paso hacia el futuro, puede cambiar las características y etiquetas un paso en relación con las demás:

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]

Para predecir una ventana completa en lugar de un desplazamiento fijo, puede dividir los lotes en dos partes:

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]

Para permitir cierta superposición entre las características de un lote y las etiquetas de otro, use 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]

Usando window

Si bien el uso de Dataset.batch funciona, hay situaciones en las que es posible que necesite un control más preciso. El método Dataset.window le brinda un control completo, pero requiere cierto cuidado: devuelve un Dataset de Dataset de Dataset de Datasets . Consulte Estructura del conjunto de datos para obtener más detalles.

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>

El método Dataset.flat_map puede tomar un conjunto de datos de conjuntos de datos y aplanarlo en un solo conjunto de datos:

 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 

En casi todos los casos, primero querrá .batch el conjunto de datos:

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]

Ahora, puede ver que el argumento de shift controla cuánto se mueve cada ventana.

Poniendo esto junto, podrías escribir esta función:

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]

Entonces es fácil extraer etiquetas, como antes:

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]

Remuestreo

Cuando trabaje con un conjunto de datos que está muy desequilibrado de clases, es posible que desee volver a muestrear el conjunto de datos. tf.data proporciona dos métodos para hacer esto. El conjunto de datos sobre fraudes con tarjetas de crédito es un buen ejemplo de este tipo de problema.

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

Ahora, verifique la distribución de clases, está muy sesgada:

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]

Un enfoque común para entrenar con un conjunto de datos desequilibrado es equilibrarlo. tf.data incluye algunos métodos que habilitan este flujo de trabajo:

Muestreo de conjuntos de datos

Un enfoque para sample_from_datasets a sample_from_datasets un conjunto de datos es usar sample_from_datasets . Esto es más aplicable cuando tiene un data.Dataset separado para cada clase.

Aquí, solo use el filtro para generarlos a partir de los datos de fraude de tarjetas de crédito:

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]

Para usar tf.data.experimental.sample_from_datasets pase los conjuntos de datos y el peso de cada uno:

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

Ahora, el conjunto de datos produce ejemplos de cada clase con una probabilidad del 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]

Remuestreo de rechazo

Un problema con el enfoque experimental.sample_from_datasets anterior es que necesita un tf.data.Dataset separado por clase. El uso de Dataset.filter funciona, pero hace que todos los datos se carguen dos veces.

La función data.experimental.rejection_resample se puede aplicar a un conjunto de datos para reequilibrarlo, mientras que solo se carga una vez. Los elementos se eliminarán del conjunto de datos para lograr el equilibrio.

data.experimental.rejection_resample toma un argumento class_func . Este class_func se aplica a cada elemento del conjunto de datos y se usa para determinar a qué clase pertenece un ejemplo para fines de equilibrio.

Los elementos de creditcard_ds ya son pares (features, label) . Entonces, class_func solo necesita devolver esas etiquetas:

def class_func(features, label):
  return label

El remuestreador también necesita una distribución de destino y, opcionalmente, una estimación de distribución inicial:

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

El remuestreador trata con ejemplos individuales, por lo que debe unbatch el conjunto de datos antes de aplicar el remuestreador:

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:


El remuestreador devuelve crea (class, example) pares a partir de la salida de class_func . En este caso, el example ya era un par (feature, label) , así que use map para soltar la copia adicional de las etiquetas:

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

Ahora, el conjunto de datos produce ejemplos de cada clase con una probabilidad del 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]

Punto de control del iterador

Tensorflow admite la toma de puntos de control para que, cuando se reinicie el proceso de entrenamiento, pueda restaurar el último punto de control para recuperar la mayor parte de su progreso. Además de señalar las variables del modelo, también puede verificar el progreso del iterador del conjunto de datos. Esto podría resultar útil si tiene un conjunto de datos grande y no desea iniciar el conjunto de datos desde el principio en cada reinicio. Sin embargo, tenga en cuenta que los puntos de control del iterador pueden ser grandes, ya que las transformaciones como la shuffle y la prefetch requieren elementos de almacenamiento en búfer dentro del iterador.

Para incluir su iterador en un punto de control, pase el iterador al constructor 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]

Usando tf.data con tf.keras

La API tf.keras simplifica muchos aspectos de la creación y ejecución de modelos de aprendizaje automático. Sus .fit() y .evaluate() y .predict() admiten conjuntos de datos como entradas. Aquí hay un conjunto de datos rápido y una configuración del modelo:

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

Pasar un conjunto de datos de pares (feature, label) es todo lo que se necesita para Model.fit y 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>

Si pasa un conjunto de datos infinito, por ejemplo, llamando a Dataset.repeat() , solo necesita pasar también el argumento 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>

Para la evaluación puede pasar el número de pasos de evaluación:

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

Para conjuntos de datos largos, establezca la cantidad de pasos para evaluar:

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

Las etiquetas no son necesarias al llamar a Model.predict .

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

Pero las etiquetas se ignoran si pasa un conjunto de datos que las contiene:

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