Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/es-419/guide/data.ipynb
25115 views
Kernel: Python 3

Licensed under the Apache License, Version 2.0 (the "License");

#@title Licensed under the Apache License, Version 2.0 (the "License"); { display-mode: "form" } # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

tf.data: Construir canalizaciones de entrada de TensorFlow

La API tf.data le permite construir canalizaciones de entrada complejas con partes simples y reutilizables. Por ejemplo, la canalización para un modelo de imagen puede agregar datos desde archivos en un sistema de archivos distribuido, aplicar perturbaciones aleatorias a cada imagen y combinar imágenes seleccionadas de forma aleatoria en un lote para entrenamiento. La canalización para un modelo de texto puede incluir extraer símbolos de datos de texto sin procesar, convertirlos en identificadores incrustados con una tabla de búsqueda y combinar secuencias de diferentes longitudes en lotes. La API tf.data posibilita que se puedan manipular grandes cantidades de datos, leer desde distintos 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 el que cada elemento consiste en uno o más componentes. Por ejemplo, en una canalización de imagen, un elemento puede ser un ejemplo de un solo entrenamiento, con un par de componentes de tensor que representan la imagen y su etiqueta.

Hay dos formas distintas de crear un conjunto de datos:

  • Un origen de datos construye un Dataset con datos alamacenados en la memoria o en uno o más archivos.

  • Una transformación de datos construye un conjunto de datos con 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)

Mécanica básica

Para crear una canalización de entrada, debe comenzar con un origen de datos. Por ejemplo, para construir un Dataset con datos almacenados en la memoria, puede usar tf.data.Dataset.from_tensors() o tf.data.Dataset.from_tensor_slices(). Como alternativa, 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 el objeto, Dataset, puede transformarlo en un nuevo Dataset al encadenar las llamadas de método en el objeto tf.data.Dataset. Por ejemplo, puede aplicar transformaciones por cada elemento como Dataset.map y transformaciones de varios elementos como Dataset.batch. Consulte la documentación de tf.data.Dataset para ver una lista completa de las transformaciones.

El objeto Dataset es un elemento de iteración de Python. Esto hace que se puedan consumir sus elementos con un bucle for:

dataset = tf.data.Dataset.from_tensor_slices([8, 3, 0, 8, 2, 1]) dataset
for elem in dataset: print(elem.numpy())

O al crear explícitamente un elemento de iteración con iter y consumir sus elementos con next:

it = iter(dataset) print(next(it).numpy())

De forma alternativa, se pueden consumir los elementos del conjunto de datos con la transformación reduce, que reduce todos los elementos para producir un resultado único. En el siguiente ejemplo, se ilustra cómo usar la transformación reduce para calcular la suma de los conjuntos de datos de enteros.

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

Estructura del conjunto de datos

Un conjunto de datos produce una secuencia de elementos, donde cada elemento es la misma estructura (anidada) de componentes. Los componentes individuales de la estructura pueden ser de cualquier tipo que pueda representarse con tf.TypeSpec, incluidos el tf.Tensor, tf.sparse.SparseTensor, tf.RaggedTensor, tf.TensorArray o tf.data.Dataset.

Entre las construcciones de Python que pueden usarse para expresar una estructura (anidada) de elementos se incluyen tuple, dict, NamedTuple y OrderedDict. En especial, list no es una construcción válida para expresar la estructura de los elementos del conjunto de datos. Esto es así porque los primeros usuarios de tf.data estaban convencidos de que las entradas de list (por ejemplo, al pasarlas a tf.data.Dataset.from_tensors) se empaquetan automáticamente como tensores y las salidas de list (por ejemplo, valores de retorno de las funciones definidas por el usuario) se coaccionan en una tuple. Como consecuencia, si quiere que se trate una entrada de list como una estructura, deberá convertirla en tuple y si quiere que una salida de list sea un componente individual, deberá empaquetarla explícitamente con tf.stack.

La propiedad Dataset.element_spec le permite inspeccionar el tipo de cada componente del elemento. La propiedad devuelve una estructura anidada de objetos tf.TypeSpec para coincidir con la estructura del elemento, que puede ser un componente único, 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
dataset2 = tf.data.Dataset.from_tensor_slices( (tf.random.uniform([4]), tf.random.uniform([4, 100], maxval=100, dtype=tf.int32))) dataset2.element_spec
dataset3 = tf.data.Dataset.zip((dataset1, dataset2)) dataset3.element_spec
# 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
# Use value_type to see the type of value represented by the element spec dataset4.element_spec.value_type

Las transformaciones de Dataset admiten conjuntos de datos de cualquier estructura. Cuando se usan las transformaciones de Dataset.map y de 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
for z in dataset1: print(z.numpy())
dataset2 = tf.data.Dataset.from_tensor_slices( (tf.random.uniform([4]), tf.random.uniform([4, 100], maxval=100, dtype=tf.int32))) dataset2
dataset3 = tf.data.Dataset.zip((dataset1, dataset2)) dataset3
for a, (b,c) in dataset3: print('shapes: {a.shape}, {b.shape}, {c.shape}'.format(a=a, b=b, c=c))

Leer datos de entrada

Consumir arreglos de NumPy

Consulte el tutorial de Cargar arreglos de NumPy para ver más ejemplos.

Si todos los datos de entrada entran en la memoria, la forma más simple de crear un Dataset con ellos es convertirlos en objetos tf.Tensor y usar Dataset.from_tensor_slices.

train, test = tf.keras.datasets.fashion_mnist.load_data()
images, labels = train images = images/255 dataset = tf.data.Dataset.from_tensor_slices((images, labels)) dataset

Nota: El fragmento de código anterior encrustará los arreglos de features y labels como operaciones tf.constant() en su gráfico de TensorFlow. Esto funciona bien para conjuntos de datos pequeños, pero gasta memoria, porque el contenido del arreglo se copiará varias veces, y puede ejecutarse dentro del límite de 2 GB para el búfer del protocolo tf.GraphDef.

Consumir generadores de Python

Otro origen de datos común que puede ingresarse fácilmente como tf.data.Dataset es el generador de Python.

Advertencia: Si bien puede ser un enfoque práctico, tiene portabilidad y escalabilidad limitadas. Debe ejecutarse en el mismo proceso de Python que con el que se creó el generador, y aún así está sujeto al GIL (bloqueo global del intérprete) de Python.

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

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 elemento de iteración. Esto le permite reiniciar el generador cuando finaliza. Toma argumentos args opcionales, que se pasan como los argumentos del invocable.

Se requiere el argumento output_types porque tf.data construye 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())

El argumento de output_shapes no es obligatorio pero se recomienda su uso, ya que muchas operaciones de TensorFlow no admiten tensores con un rango desconocido. Si la longitud de un eje en particular es desconocida o variable, establezca el valor como None en el output_shapes.

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

A continuación, se muestra un generador de ejemplo que muestra los dos aspectos: devuelve tuplas de arreglos, donde el segundo arreglo es un vector con una 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

La primera salida es un int32 y 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

Ahora se puede usar como un tf.data.Dataset normal. Tenga en cuenta que al poner un conjunto de datos en un lote con una forma variable, se 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())

Para ver un ejemplo más realista, intente encapsular 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)

Cree 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))
print(images.dtype, images.shape) print(labels.dtype, labels.shape)
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
for images, labels in ds.take(1): print('images.shape: ', images.shape) print('labels.shape: ', labels.shape)

Consumir datos TFRecord

Consulte el tutorial de Cargar datos TFRecord para ver un ejemplo de principio a fin.

La API tf.data admite una variedad de formatos de archivos para poder procesar conjuntos de datos grandes que no entren en la memoria. Por ejemplo, el formato de archivo TFRecord es un formato binario simple orientado a registros que muchas de las 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.

Aquí tiene un ejemplo que usa el archivo de prueba del nombre de las calles francesas (FSNS, por sus siglas en inglés).

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

El argumento filenames del inicializador TFRecordDataset puede ser una cadena de texto, un lista de cadenas de texto o un tf.Tensor de cadenas de texto. Por eso, si tiene dos conjuntos de archivos para entrenamiento y validación, puede crear un método factory que genera los conjuntos de datos y toma los nombres de archivos como un argumento de entrada:

dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file]) dataset

Muchos de los proyectos de TensorFlow usan registros tf.train.Example en serie en sus archivos TFRecord. Deben decodificarse antes de inspeccionarlos:

raw_example = next(iter(dataset)) parsed = tf.train.Example.FromString(raw_example.numpy()) parsed.features.feature['image/text']

Consumir datos de texto

Consulte el tutorial de Cargar texto para ver un ejemplo de principio a fin.

Muchos conjuntos de datos están distribuidos en uno o más archivos de texto. El tf.data.TextLineDataset proporciona una forma fácil de extraer líneas de uno o más archivos de texto. Si se ingresan uno o más nombres de archivo, un TextLineDataset generará un elemento con valor de cadena de texto por cada línea de los 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 ]
dataset = tf.data.TextLineDataset(file_paths)

Estas son las primeras líneas del primer archivo:

for line in dataset.take(5): print(line.numpy())

Para alternar líneas entre archivos use Dataset.interleave. Esto hará que sea más fácil pasar de un archivo a otro de forma aleatoria. Estas son la primera, la segunda y la tercera línea 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())

De forma predeterminada, un TextLineDataset da cada línea de cada archivo, que tal vez no sea conveniente, por ejemplo, si el archivo comienza con una línea de encabezado o tiene comentarios. Se pueden eliminar estas líneas con las transformaciones Dataset.skip() o Dataset.filter. Aquí, se omite la primera línea y luego se filtra para encontrar solo las 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)
for line in titanic_lines.take(10): print(line.numpy())
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())

Consumir datos CSV

Consulte los tutoriales de Cargar archivos CSV y Cargar DataFrames de Panda para ver más ejemplos.

El formato de archivo CSV es un formato conocido 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 datos entran en la memoria, el mismo método de Dataset.from_tensor_slices funciona con diccionarios, lo que permite que se puedan importar los datos de forma fácil:

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

Un enfoque más escalable es cargar datos 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 cumplan con el estándar RFC 4180.

La función tf.data.experimental.make_csv_dataset es la interfaz de alto nivel para leer conjuntos de archivos CSV. Admite inferencias de tipo de columna y muchas otras funcionalidades, como poner datos en lotes y en orden aleatorio, 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))

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

También hay una clase experimental.CsvDataset de nivel más bajo que proporciona un control más específico. No admite las inferencias de tipo de columna. Se 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])

Si algunas columnas están vacías, la inferencia 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, ,,,
# 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
for line in dataset: print(line.numpy())

De forma predeterminada, un CsvDataset genera cada columna de cada línea del archivo, que quizás no sea conveniente si, por ejemplo, el archivo comienza con una línea de encabezado que debería ser ignorada o si no se necesitan algunas columnas en la entrada. Se pueden eliminar estas líneas y campos 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
for line in dataset: print(line.numpy())

Consumir conjuntos de archivos

Hay muchos conjuntos de datos distribuidos como conjuntos 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)

Nota: estas imágenes están bajo la licencia de CC-BY, consulte LICENSE.txt para obtener más detalles.

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

for item in flowers_root.glob("*"): print(item.name)

Los archivos en cada directorio de clase son ejemplos:

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*')) for f in list_ds.take(5): print(f.numpy())

Lea los datos con la función tf.io.read_file y extraiga la etiqueta desde la ruta de acceso, esto devolverá pares de (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())

Procesar elementos del conjunto de datos por lotes

Procesamiento por lotes

La forma más simple de procesamiento 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: esto significa que para cada componente i, todos los elementos deben tener un tensor de exactamente la misma forma.

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

Mientras que tf.data intenta propagar la información de la forma, la configuración predeterminada de Dataset.batch da como resultado un tamaño desconocido de lote porque tal vez el último lote no está lleno. Observe los Nones en la forma:

batched_dataset

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

batched_dataset = dataset.batch(7, drop_remainder=True) batched_dataset

Procesar tensores con espaciado por lotes

La receta anterior funciona con tensores del mismo tamaño. Sin embargo, muchos modelos (incluidos los modelos de secuencia) funcionan con datos de entrada que pueden tener varios tamaños (por ejemplo, secuencias de diferentes longitudes). Para tratar este caso, la transformación Dataset.padded_batch le permite procesar en lotes tensores de diferentes formas por lote al especificar una o más dimensiones en las que se pueda agregar espaciado.

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

La transformación Dataset.padded_batch permite establecer diferentes espaciados para cada dimensión de cada componente y puede tener longitudes variables (está representado con None en el ejemplo anterior) o longitudes constantes. También se puede reemplazar el valor del espaciado, cuyo valor predeterminado es 0.

Flujos de entrenamiento

Procesar varias épocas

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

La forma más simple de hacer una iteración en un conjunto de datos en varias épocas es con la transformación Dataset.repeat(). Primero, cree un conjunto de datos de datos del titanic:

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

Si se aplica la transformación Dataset.repeat() sin argumentos, se repetirá la entrada indefinidamente.

La transformación Dataset.repeat concatena sus argumentos sin marcar el final de una época ni el comienzo de la siguiente época. Por eso, si se aplica un Dataset.batch después de un Dataset.repeat generará lotes que amplían los límites de la época:

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

Si necesita una separación clara entre las épocas, escriba Dataset.batch antes de la repetición:

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

Si quiere realizar un cálculo personalizado (por ejemplo, para recopilar estadísticas) al final de cada época, lo más simple 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)

Poner los datos de entrada en orden aleatorio

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

Nota: Aunque los bufer_sizes grandes pueden ordenarse de manera aleatoriade forma más diligente, pueden usar mucha memoria y un tiempo considerable para completarse. Considere usar Dataset.interleave en los archivos si tiene este problema.

Agregue un índice al conjunto de datos para poder 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

Dado que el buffer_size es 100 y el tamaño del lote es 20, el primer lote no tiene ningún elemento con un índice mayor a 120.

n,line_batch = next(iter(dataset)) print(n.numpy())

Al igual que con Dataset.batch el orden en relación con Dataset.repeat importa.

Dataset.shuffle no señala el final de una época hasta que el búfer del orden aleatorio esté vacío. Entonces, si se agrega un orden aleatorio antes de una repetición se mostrará cada elemento de una época antes de ir 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())
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()

Pero si hay una repetición antes de un orden aleatorio se mezclan 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())
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()

Procesar datos

La transformación Dataset.map(f) genera un conjunto de datos nuevo al aplicar la función ingresada f en cada elemento del conjunto de datos de entrada. Se basa en la función map() que comúnmente se aplica a listas (y otras estructuras) en los lenguajes de programación funcionales. La función f toma los objetos de tf.Tensor que representan un elemento único en la entrada y devuelve los objetos tf.Tensor que representan un elemento único en el conjunto de datos nuevo. Al implementarse está función, se usan operaciones estándar de TensorFlow para transformar un elemento en otro.

En esta sección, se cubren ejemplos comúnes de cómo usar Dataset.map().

Decodificar datos de imagen y cambiar el tamaño

Cuando se entrena una red neuronal con datos de imágenes del mundo real, suele ser necesario convertir las imágenes de diferentes tamaños a un tamaño común, para que se puedan procesar por lotes en un tamaño fijo.

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

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

Escriba 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.io.decode_jpeg(image) image = tf.image.convert_image_dtype(image, tf.float32) image = tf.image.resize(image, [128, 128]) return image, label

Pruebe que funcione.

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)

Asígnela a un conjunto de datos.

images_ds = list_ds.map(parse_image) for image, label in images_ds.take(2): show(image, label)

Aplicar una lógica arbitraria de Python

Por el rendimiento, use operaciones de TensorFlow para preprocesar sus datos cuando sea posible. Sin embargo, a veces sirve llamar a las bibliotecas externas de Python cuando parsea sus datos de entrada. Puede usar la operación tf.py_function en una transformación Dataset.map.

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

Nota: tensorflow_addons tiene un rotate compatible de TensorFlow en tensorflow_addons.image.rotate.

Para demostrar tf.py_function, pruebe usar la función scipy.ndimage.rotate en 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)

Para usar la función con Dataset.map aplica la misma advertencia que con Dataset.from_generator, debe describir las formas y los tipos de retorno cuando aplique 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)

Parsear mensajes de búfer del protocolo tf.Example

Muchas canalizaciones de entrada extraen mensajes de búfer del protocolo tf.train.Example desde un formato TFRecord. Cada registro de tf.train.Example contiene una o más "funciones", y la canalización de entrada suele convertir estas funciones 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

Puede trabajar con protocolos de tf.train.Example fuera de un 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])
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]), "...")
decoded = dataset.map(tf_parse) decoded
image_batch, text_batch = next(iter(decoded.batch(10))) image_batch.shape

Segmentar series temporales

Para ver un ejemplo de principio a fin de una serie temporal consulte: Predecir series temporales.

Los datos de las series temporales suelen organizarse con el eje de tiempo intacto.

Use un Dataset.range simple para demostrar:

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

Por lo general, los modelos que se basan en este tipo de datos necesitarán un segmento de tiempo contiguo.

El enfoque más simple sería procesar los datos por lotes:

Con batch

batches = range_ds.batch(10, drop_remainder=True) for batch in batches.take(5): print(batch.numpy())

O para realizar predicciones densas un paso adelantado, es posible que quiera cambiar las características y etiquetas por un paso relacionado entre 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())

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

Para permitir que las características de un lote y las etiquetas de otro se superpongan un poco, 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())

Con window

Si bien usar Dataset.batch funciona, hay situaciones en las que tal vez necesite un control más específico. El método Dataset.window le da control total, pero necesita más atención: este método devuelve un Dataset de los Datasets. Para obtener más detalles, consulte la sección Estructura del conjunto de datos.

window_size = 5 windows = range_ds.window(window_size, shift=1) for sub_ds in windows.take(5): print(sub_ds)

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

for x in windows.flat_map(lambda x: x).take(30): print(x.numpy(), end=' ')

En la mayoría de los casos, primero querrá Dataset.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())

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

Si lo une todo, puede 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())

Y así, es más fácil extraer las 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())

Nuevo muestreo

Si tiene un conjunto de datos de diferentes clases, deberá realizar un nuevo muestreo del conjunto de datos. tf.data proporciona dos métodos para hacerlo. El conjunto de datos de fraude de tarjeta de crédito es un buen ejemplo de este tipo de problema.

Nota: visite Clasificación de datos desequilibrados para ver un tutorial completo.

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')
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á marcadamente asimétrica:

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)

Un enfoque común para entrenar con conjuntos de datos desequilibrados es equilibrarlos. tf.data incluye algunos métodos que permiten este flujo de trabajo:

Muestreo del conjuntos de datos

Un enfoque para realizar un nuevo muestreo de un conjunto de datos es usar sample_from_datasets. Es más aplicable cuando se tiene un tf.data.Dataset diferente para cada clase.

A continuación, solo use el filtro para generarlos con los datos de fraude de tarjeta 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())

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

balanced_ds = tf.data.Dataset.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 de 50/50.

for features, labels in balanced_ds.take(10): print(labels.numpy())

Rechazo del nuevo muestreo

Un problema con el enfoque Dataset.sample_from_datasets anterior es que necesita un tf.data.Dataset diferente por clase. Podría usar Dataset.filter para crear esos dos conjuntos de datos, pero esto hará que se carguen los datos dos veces.

Se puede aplicar el método tf.data.Dataset.rejection_resample en el conjunto de datos para volver a equilibrarlo, y solo se carga una vez. Se excluirán o repetirán elementos para lograr el equilibrio.

El método rejection_resample toma un argumento class_func. Se aplica class_func en cada elemento del conjunto de datos y se usa para determinar a qué clase pertenece un ejemplo con el propósito de equilibrio.

El objetivo es equilibrar la distribución de las etiquetas, y los elementos de creditcard_ds ya son pares de (features, label). Por eso la class_func solo debe devolver esas etiquetas:

def class_func(features, label): return label

El método de volver a muestrear trata ejemplos individuales, por eso, en este caso debe unbatch el conjunto de datos antes de aplicar el método.

El método necesita una distribución de destino y, de forma opcional, un cálculo estimado de la distribución inicial como entradas.

resample_ds = ( creditcard_ds .unbatch() .rejection_resample(class_func, target_dist=[0.5,0.5], initial_dist=fractions) .batch(10))

El método rejection_resample devuelve los pares de (class, example) donde la class es una salida de la class_func. En este caso, el example ya era un par de (feature, label), por eso use map para excluir 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 genera ejemplos de cada clase con una probabilidad de 50/50:

for features, labels in balanced_ds.take(10): print(labels.numpy())

Punto de verificación del elemento de iteración

TensorFlow admite guardar puntos de verificación para que cuando el proceso de entrenamiento se reinicie, se pueda restaurar el último punto de verificación para recuperar la mayoría del progreso. Además de guardar los puntos de verificación de las variables del modelo, también puede guardar los puntos de verificación del progreso del elemento de iteración del conjunto de datos. Eso podría servirle si tiene un conjunto de datos grande y no quiere empezar desde el principio del conjunto de datos cada vez que lo reinicia. De todas maneras, tenga en cuenta que los puntos de verificación de un elemento de iteración pueden ser grandes, ya que las transformaciones como Dataset.shuffle y Dataset.prefetch requieren almacenar elementos en el búfer dentro del elemento de iteración.

Para incluir su elemento de iteración en el punto de verificación, pase el elemento de iteración 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)])

Nota: no es posible guardar un punto de verificación de un elemento de iteración que depende de un estado externo, como una tf.py_function. Si lo intenta, provocará una excepción y reclamará el estado externo.

Usar tf.data con tf.keras

La API tf.keras simplifica muchos aspectos de la creación y ejecución de los modelos de aprendizaje automático. Sus API Model.fit, Model.evaluate y Model.predict admiten los conjuntos de datos como entradas. A continuación, encontrará una configuración rápida de conjuntos de datos y modelos:

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

Solo se necesita pasar un conjunto de datos de pares de (feature, label) para Model.fit y la Model.evaluate:

model.fit(fmnist_train_ds, epochs=2)

Si pasa un conjunto de datos infinito, por ejemplo, al llamar a Dataset.repeat, solo deberá pasar el argumento steps_per_epoch también:

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)

Para evaluarlo, también puede pasar la cantidad de pasos de la evaluación:

loss, accuracy = model.evaluate(fmnist_train_ds) print("Loss :", loss) print("Accuracy :", accuracy)

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

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10) print("Loss :", loss) print("Accuracy :", accuracy)

No se requieren las etiquetas cuando se llama a Model.predict.

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

Pero se ignorarán las etiquetas si pasa un conjunto de datos que tenga etiquetas:

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