tf.data: compila canalizaciones de entrada de TensorFlow

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHubDescargar cuaderno

El tf.data API le permite construir tuberías de entrada complejas a partir de piezas sencillas 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 fusionar imágenes seleccionadas al azar en un lote para entrenamiento. La canalización de un modelo de texto puede implicar extraer símbolos de datos de texto sin procesar, convertirlos en identificadores incrustados con una tabla de búsqueda y agrupar secuencias de diferentes longitudes. El tf.data API hace posible manejar grandes cantidades de datos, lectura de diferentes formatos de datos, y realizar transformaciones complejas.

El tf.data API introduce un tf.data.Dataset abstracción que representa una secuencia de elementos, en el que cada elemento se compone de uno o más componentes. Por ejemplo, en una canalización de imágenes, un elemento podría ser un solo ejemplo de entrenamiento, con un par de componentes tensoriales que representan la imagen y su etiqueta.

Hay dos formas distintas de crear un conjunto de datos:

  • Una fuente de datos construye un Dataset 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 de uno o más tf.data.Dataset objetos.

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 tubería de entrada, debe comenzar con una fuente de datos. Por ejemplo, para construir un Dataset a partir de datos en la memoria, puede utilizar tf.data.Dataset.from_tensors() o tf.data.Dataset.from_tensor_slices() . Alternativamente, si los datos de entrada se almacena en un archivo en el formato TFRecord recomendada, puede utilizar tf.data.TFRecordDataset() .

Una vez que tenga un Dataset de objeto, puede transformarlo en un nuevo Dataset por el encadenamiento de las llamadas de método en la tf.data.Dataset objeto. Por ejemplo, se puede aplicar transformaciones por elementos tales como Dataset.map() , y las transformaciones de elementos múltiples tales como Dataset.batch() . Consulte la documentación de tf.data.Dataset para obtener una lista completa de las transformaciones.

El Dataset objeto es un Python iterable. 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 mediante la creación explícitamente un iterador Python usando iter y el consumo de sus elementos utilizando next :

it = iter(dataset)

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

Alternativamente, de conjuntos de datos de elementos pueden ser consumidos usando el reduce transformación, lo que reduce todos los elementos para producir un único resultado. El siguiente ejemplo ilustra cómo utilizar el reduce transformación 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 produce una secuencia de elementos, donde cada elemento es la misma estructura (anidada) de los componentes. Los componentes individuales de la estructura pueden ser de cualquier tipo representable por tf.TypeSpec , incluyendo tf.Tensor , tf.sparse.SparseTensor , tf.RaggedTensor , tf.TensorArray , o tf.data.Dataset .

Las construcciones de Python que se pueden utilizar para expresar la estructura (anidada) de elementos incluyen tuple , dict , NamedTuple , y OrderedDict . En particular, list no es un constructo válido para la expresión de la estructura del conjunto de datos elementos. Esto es porque los usuarios tf.data primeros sentían fuertemente sobre list entradas (por ejemplo, pasa a tf.data.Dataset.from_tensors ) ser envasados automáticamente como tensores y list salidas (por ejemplo, valores de retorno de las funciones definidas por el usuario) ser coaccionado en una tuple . Como consecuencia, si desea una list de entrada debe ser tratada como una estructura, es necesario convertirlo en tuple y si desea una list de salida para ser un solo componente, entonces usted necesita para empacar explícitamente con tf.stack .

El Dataset.element_spec propiedad le permite inspeccionar el tipo de cada componente de elemento. La propiedad devuelve una estructura anidada de tf.TypeSpec objetos, igualando 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 utiliza el Dataset.map() , y Dataset.filter() transformaciones, que se 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())
[8 1 7 9 5 5 3 3 9 5]
[1 3 4 4 6 1 3 2 9 7]
[3 8 3 1 3 6 2 3 9 4]
[7 3 3 2 9 5 5 6 9 1]
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

Ver Cargando matrices NumPy para más ejemplos.

Si todos los ajustes de entrada de datos en la memoria, la forma más sencilla para crear un Dataset a partir de ellos es convertirlos a tf.Tensor objetos y usarlos Dataset.from_tensor_slices() .

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

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

Consumir generadores de Python

Otra fuente de datos común que puede ser ingerida fácilmente como tf.data.Dataset es el generador de pitón.

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 Dataset.from_generator constructor convierte el generador de pitón a un completamente funcional tf.data.Dataset .

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

El output_types se requiere argumento porque tf.data construye un tf.Graph internamente, y los bordes de gráficos requiere una 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 output_shapes argumento no es necesaria, pero es muy recomendable tantas operaciones tensorflow no son compatibles con los tensores con rango desconocido. Si la longitud de un eje particular es desconocida o variable, configurarlo como None en los output_shapes .

También es importante tener en cuenta que los output_shapes y output_types siguen las mismas reglas de anidación como 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 : [-0.5118  1.1338  0.4652 -0.1266  2.1672  0.6211]
1 : [-0.6128 -0.9794  0.8448 -2.0378  1.0311  0.3236 -1.2445]
2 : [-0.1676 -1.7922  0.9232  0.975   0.0882 -1.2209  2.9198]
3 : [ 1.3887  0.4012 -0.0488  0.6349  0.5027  0.445  -1.4106 -1.1119]
4 : [ 0.8127 -0.3852  2.5075 -0.9225 -1.3582  2.1959  0.3715  0.7565]
5 : [-0.7891  1.4259 -0.949  -0.7526 -1.6112  0.5935 -0.1808 -2.4721]
6 : [-0.2006  0.1787 -0.8684  0.7611  0.1345 -1.9513  0.0824 -0.2069]

La primera salida es un int32 el segundo 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 regular tf.data.Dataset . Tenga en cuenta que cuando un conjunto de datos de procesamiento por lotes con una forma variable, es necesario utilizar 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  3  9  2 21 24 10 16  1 25]

[[-0.1558  0.      0.      0.      0.      0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.3243  0.8113  1.3543  1.4365  1.7064  0.2669 -0.9977  0.      0.    ]
 [-1.0384  0.6793  0.      0.      0.      0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-1.1779  0.7456 -0.6888  0.      0.      0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-1.3821  0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.3428 -1.3348  0.      0.      0.      0.      0.      0.      0.    ]
 [-1.7189 -0.9511  0.3775  0.3023 -0.8825 -1.002  -0.349  -0.2439 -0.6234]]

Para un ejemplo más realista, trate de envolver preprocessing.image.ImageDataGenerator como 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 [==============================] - 8s 0us/step
228827136/228813984 [==============================] - 8s 0us/step

Crear el 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

Ver Cargando TFRecords para un ejemplo de extremo a extremo.

El tf.data API es compatible con 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. El tf.data.TFRecordDataset clase le permite transmitir sobre el contenido de uno o más archivos TFRecord como parte de una tubería de entrada.

A continuación, se muestra un ejemplo que utiliza el archivo de prueba de las señales de nombres de calles francesas (FSNS).

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

El filenames argumento a la TFRecordDataset inicializador puede ser una cadena, una lista de cadenas, o una 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 TensorFlow utilizan serializados tf.train.Example registros en sus archivos TFRecord. Estos deben decodificarse antes de poder 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

Ver Cargando texto que se ponga fin al ejemplo extremo.

Muchos conjuntos de datos se distribuyen como uno o más archivos de texto. El tf.data.TextLineDataset proporciona una manera fácil de extraer las líneas de uno o más archivos de texto. Teniendo en cuenta uno o más nombres de archivos, 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
827392/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
819200/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step
819200/807992 [==============================] - 0s 0us/step
dataset = tf.data.TextLineDataset(file_paths)

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 las líneas alternas entre los archivos utilizar 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'

Por defecto, un TextLineDataset produce cada línea de cada archivo, que puede no ser deseable, por ejemplo, si el archivo comienza con una línea de cabecera, o contiene comentarios. Estas líneas se pueden eliminar con los Dataset.skip() o Dataset.filter() transformaciones. Aquí, omite la primera línea, 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
40960/30874 [=======================================] - 0s 0us/step
for line in titanic_lines.take(10):
  print(line.numpy())
b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone'
b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n'
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y'
b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
def survived(line):
  return tf.not_equal(tf.strings.substr(line, 0, 1), "0")

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

Consumir datos CSV

Ver Carga de archivos CSV , y Cargando pandas tramas de datos para 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)
df.head()

Si sus ajustes de datos en la memoria de los mismos Dataset.from_tensor_slices método funciona en los diccionarios, permitiendo que estos datos para ser fácilmente importados:

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 tf.data módulo proporciona métodos para extraer registros de uno o más archivos CSV que cumplan con RFC 4180 .

El experimental.make_csv_dataset función es la interfaz de alto nivel para la lectura de conjuntos de archivos CSV. Es compatible con 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 1 0 0]
features:
  'sex'               : [b'male' b'female' b'male' b'male']
  'age'               : [16. 38. 22. 28.]
  'n_siblings_spouses': [4 1 0 0]
  'parch'             : [1 5 0 0]
  'fare'              : [39.6875 31.3875  8.05    7.8958]
  'class'             : [b'Third' b'Third' b'Third' b'Third']
  'deck'              : [b'unknown' b'unknown' b'unknown' b'unknown']
  'embark_town'       : [b'Southampton' b'Southampton' b'Southampton' b'Southampton']
  'alone'             : [b'n' b'n' b'y' b'y']

Se puede utilizar el select_columns argumento si sólo 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'              : [26.25   13.      6.8583 55.    ]
  'class'             : [b'Second' b'Second' b'Third' b'First']

También hay un bajo nivel experimental.CsvDataset clase que ofrece un control de grano fino. 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]

Por defecto, un CsvDataset produce cada columna de cada línea del archivo, que puede no ser deseable, por ejemplo si el archivo comienza con una línea de encabezado que debe ser ignorado, o si algunas columnas no son necesarios en la entrada. Estas líneas y campos se pueden eliminar con los header y select_cols argumentos 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/daisy/54377391_15648e8d18.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/18474740346_ffdaa18032.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/212720516_df4965ebda_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/14554906452_35f066ffe9_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/3393564906_f2df184b76_n.jpg'

Leer los datos mediante el tf.io.read_file función y extraer la etiqueta de la ruta, volviendo (image, label) pares:

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

Procesamiento por lotes de elementos del conjunto de datos

Procesamiento por lotes simple

La forma más simple de dosificadora de pilas de n elementos consecutivos de un conjunto de datos en un solo elemento. El Dataset.batch() transformación hace exactamente esto, con las mismas restricciones que el tf.stack() operador, aplicadas a cada componente de los elementos: es decir, para cada i componente, 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])]

Mientras tf.data intentos para propagar información de la forma, la configuración predeterminada de Dataset.batch resultar en un tamaño de lote desconocido debido a que el último lote puede no ser completa. Tenga en cuenta las None s en la forma:

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

Usar la drop_remainder argumento para ignorar esa última tanda, 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 hacer frente a este caso, el Dataset.padded_batch transformación que permite a los tensores de los lotes de forma diferente mediante la especificación de una o más dimensiones en las que pueden ser acolchado.

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

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

Flujos de trabajo de formación

Procesando múltiples épocas

Los tf.data ofertas API dos formas principales para procesar múltiples épocas de los mismos datos.

La forma más sencilla de iterar sobre un conjunto de datos en múltiples épocas es utilizar el Dataset.repeat() transformación. 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 Dataset.repeat() transformación sin argumentos se repetirá la entrada indefinidamente.

El Dataset.repeat transformación concatena sus argumentos sin que marcó el final de una época y el comienzo de la siguiente época. Debido a esto un Dataset.batch aplica después Dataset.repeat rendirá lotes que límites de épocas de pórtico:

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

png

Si necesita una separación clara época, puesto 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

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

Agregue un índice al conjunto de datos para que pueda 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
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/counter.py:66: scan (from tensorflow.python.data.experimental.ops.scan_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.scan(...) instead
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

Dado que el buffer_size es 100, y el tamaño del lote es de 20, el primer lote no contiene elementos con un índice sobre 120.

n,line_batch = next(iter(dataset))
print(n.numpy())
[49 10 47  5 41 22 24 12 63 95  6 81 93 33 32 46 62 38 34 59]

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

Dataset.shuffle no significa el fin de una época hasta que el buffer shuffle está vacía. 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:

[547 510 586 552  73 585 615 601 619 621]
[374 450 574 459 533 505 593 526 604 572]
[627 475 590 512 611 520 610 618]
[12 50 97 88 15 63 26 32 95  1]
[ 6 24 44 49 47 92 70 13 16 96]
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 0x7f97d44e5590>

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:

[ 70 591   8 621 617   3  15  28 367 597]
[599 514 500 604  13 532 580  32 381 616]
[371  16 458 553 625 567   7 478 627 602]
[ 37 571  24   5  42 605 601  17 341 321]
[ 34 484  18  39 576 626 551 412 578 618]
[ 43  33 610  20  21 563  46 603  19 590]
[ 71  73  55  25 492 483  14 515  59  67]
[ 81  65   4 577  57  58  86 513  90 583]
[100 556 534  70 619  48 459  12 537  68]
[ 38  45  87 102  97 101 442  77 111 554]
[ 99  78 377  93  26 564  98 614  49  10]
[ 35  72  79 611 624 122  96 594   1 579]
[  2 542 581 125  83  23  51 139  36  91]
[124 486 575 129 152  92 134 592  89 133]
[ 82 144 109 164 159  61 429 607 522 150]
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 0x7f97d41d3610>

png

Procesamiento previo de datos

El Dataset.map(f) transformación produce un nuevo conjunto de datos mediante la aplicación de una función dada f a cada elemento del conjunto de datos de entrada. Se basa en el map() función que se aplica comúnmente a las listas (y otras estructuras) en lenguajes de programación funcionales. La función f toma los tf.Tensor objetos que representan un único elemento en la entrada, y devuelve el tf.Tensor objetos que representar 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 utilizar 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. Se puede utilizar el tf.py_function() operación en un Dataset.map() transformación.

Por ejemplo, si desea aplicar una rotación aleatoria, la tf.image módulo sólo tiene tf.image.rot90 , que no es muy útil para el aumento de la imagen.

Para demostrar tf.py_function , intente utilizar el scipy.ndimage.rotate función de su 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 utilizar esta función con Dataset.map las mismas advertencias se aplican al igual que con Dataset.from_generator , es necesario describir las formas y tipos de retorno cuando se 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

De análisis tf.Example mensajes búfer de protocolo

Muchas tuberías de entrada extraen tf.train.Example mensajes de búfer de protocolo de un formato TFRecord. Cada tf.train.Example registro contiene uno o más "características", y la tubería de entrada normalmente convierte estas características en los 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 fuera de un Protos tf.data.Dataset para entender 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

Por un extremo a otro ejemplo de series de tiempo, véase: Tiempo de pronóstico de series .

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

Utilizar un simple Dataset.range de demostrar:

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

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

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

El uso de 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],   # Inputs: All except the last 5 steps
          batch[-5:])   # Labels: The last 5 steps

predict_5_steps = batches.map(label_next_5_steps)

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

Para permitir una cierta superposición entre las características de un lote y las etiquetas de otro, use Dataset.zip :

feature_length = 10
label_length = 3

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

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

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

usando window

Durante el uso de Dataset.batch obras, hay situaciones en las que puede necesitar un control más preciso. El Dataset.window método le da un control completo, pero requiere cierto cuidado: se devuelve un Dataset de Datasets . Ver la estructura de conjunto de datos para obtener más información.

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 Dataset.flat_map método puede tomar un conjunto de datos de conjuntos de datos y aplanar en un único conjunto de datos:

for x in windows.flat_map(lambda x: x).take(30):
   print(x.numpy(), end=' ')
0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9

En casi todos los casos, tendrá que .batch el conjunto de datos en primer lugar:

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, se puede ver que los shift controles argumento la cantidad de cada ventana deja atrás.

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

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.9951 0.0049]

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

Muestreo de conjuntos de datos

Un enfoque para volver a muestrear un conjunto de datos es utilizar sample_from_datasets . Esto es más aplicable cuando se tiene una separada data.Dataset 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())
for features, label in positive_ds.batch(10).take(1):
  print(label.numpy())
[1 1 1 1 1 1 1 1 1 1]

Para utilizar tf.data.experimental.sample_from_datasets pasan los conjuntos de datos, y el peso para cada uno:

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

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 0 1 1 0 0 1 0 0 1]
[1 1 0 1 0 1 0 1 1 1]
[0 1 1 1 1 0 0 1 0 0]
[0 1 1 1 0 0 1 1 0 1]
[1 0 0 0 0 0 1 1 1 0]
[0 1 1 1 0 1 1 1 0 1]
[0 1 1 1 1 0 0 1 0 1]
[1 0 1 0 0 1 1 0 0 1]
[1 0 0 1 1 0 1 1 0 1]
[0 1 1 1 1 1 1 0 1 0]

Remuestreo de rechazo

Un problema con lo anterior experimental.sample_from_datasets enfoque es que se necesita una separada tf.data.Dataset por clase. Usando Dataset.filter funciona, pero los resultados en todos los datos que se cargan dos veces.

El data.experimental.rejection_resample función se puede aplicar a un conjunto de datos para reequilibrar que, mientras que sólo la carga de una vez. Los elementos se eliminarán del conjunto de datos para lograr el equilibrio.

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

Los elementos de creditcard_ds ya son (features, label) pares. Por lo que el class_func sólo tiene que 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)

Los Resampler trata de ejemplos individuales, por lo que debe unbatch el conjunto de datos antes de aplicar la resampleador:

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

Los rendimientos remuestreador crea (class, example) pares de la salida del class_func . En este caso, el example ya era una (feature, label) par, por lo que el uso map dejar caer la copia extra 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())
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
[0 1 0 0 1 1 0 0 1 1]
[0 0 0 1 1 1 0 1 1 1]
[1 0 0 0 1 0 1 1 0 1]
[0 0 0 0 0 0 0 0 1 1]
[0 1 1 0 1 0 1 0 0 1]
[1 0 1 1 0 1 1 1 0 1]
[1 1 0 0 0 1 0 1 1 0]
[1 0 1 0 1 0 1 0 0 1]
[0 0 0 0 0 1 1 0 1 0]
[1 0 1 1 0 1 0 0 1 0]

Punto de control del iterador

Soportes Tensorflow tomar los puestos de control de manera que cuando se reinicie el proceso de formación que pueden restaurar la última puesto de control para recuperar la mayor parte de su progreso. Además de señalar las variables del modelo, también puede controlar el progreso del iterador del conjunto de datos. Esto podría ser útil si tiene un conjunto de datos grande y no desea iniciar el conjunto de datos desde el principio en cada reinicio. Tenga en cuenta sin embargo, que iterador puntos de control pueden ser grandes, ya que las transformaciones tales como shuffle y prefetch requieren elementos de tampón dentro del iterador.

Para incluir su iterador en un puesto de control, pase el iterador a la tf.train.Checkpoint constructor.

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

El tf.keras API simplifica muchos aspectos de la creación y ejecución de modelos de aprendizaje automático. Su .fit() y .evaluate() y .predict() API apoyan 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'])

El paso de un conjunto de datos de (feature, label) pares es todo lo que se necesita para Model.fit y Model.evaluate :

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

Si pasa un conjunto de datos infinita, por ejemplo llamando Dataset.repeat() , sólo tiene que pasar también la steps_per_epoch argumento:

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4131 - accuracy: 0.8609
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4523 - accuracy: 0.8281
<keras.callbacks.History at 0x7f97a4425590>

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 1ms/step - loss: 0.4349 - accuracy: 0.8489
Loss : 0.43494418263435364
Accuracy : 0.8488500118255615

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.5229 - accuracy: 0.8188
Loss : 0.5228594541549683
Accuracy : 0.8187500238418579

Las etiquetas no son necesarios en al llamar 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)