![]() | ![]() | ![]() | ![]() |
La API tf.data
permite crear tuberías de entrada complejas a partir de piezas simples y reutilizables. Por ejemplo, la canalización de un modelo de imagen podría agregar datos de archivos en un sistema de archivos distribuido, aplicar perturbaciones aleatorias a cada imagen y combinar imágenes seleccionadas al azar en un lote para entrenamiento. La canalización de un modelo de texto puede implicar 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. La API tf.data
permite manejar grandes cantidades de datos, leer desde diferentes formatos de datos y realizar transformaciones complejas.
La API tf.data
introduce una abstraccióntf.data.Dataset
que representa una secuencia de elementos, en la que cada elemento consta de uno o más componentes. Por ejemplo, en una canalización de imágenes, un elemento 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
de datos a partir de datos almacenados en la memoria o en uno o más archivos.Una transformación de datos construye un conjunto de datos a partir de uno o más objetos
tf.data.Dataset
.
import tensorflow as tf
import pathlib
import os
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
np.set_printoptions(precision=4)
Mecanica basica
Para crear una canalización de entrada, debe comenzar con una fuente de datos. Por ejemplo, para construir un Dataset
de datos a partir de datos en la memoria, puede usar tf.data.Dataset.from_tensors()
o tf.data.Dataset.from_tensor_slices()
. Alternativamente, si sus datos de entrada están almacenados en un archivo en el formato TFRecord recomendado, puede usar tf.data.TFRecordDataset()
.
Una vez que tenga un objeto de Dataset
, puede transformarlo en un nuevo Dataset
de Dataset
encadenando llamadas a métodos en el objetotf.data.Dataset
. Por ejemplo, puede aplicar transformaciones por elemento como Dataset.map()
y transformaciones de varios elementos como Dataset.batch()
. Consulte la documentación detf.data.Dataset
para obtener una lista completa de transformaciones.
El objeto Dataset
es un iterable de Python. Esto hace posible consumir sus elementos usando un bucle for:
dataset = tf.data.Dataset.from_tensor_slices([8, 3, 0, 8, 2, 1])
dataset
<TensorSliceDataset shapes: (), types: tf.int32>
for elem in dataset:
print(elem.numpy())
8 3 0 8 2 1
O creando explícitamente un iterador de Python usando iter
y consumiendo sus elementos usando next
:
it = iter(dataset)
print(next(it).numpy())
8
Alternativamente, los elementos del conjunto de datos se pueden consumir utilizando la transformación de reduce
, que reduce todos los elementos para producir un único resultado. El siguiente ejemplo ilustra cómo utilizar la transformación de reduce
para calcular la suma de un conjunto de datos de números enteros.
print(dataset.reduce(0, lambda state, value: state + value).numpy())
22
Estructura del conjunto de datos
Un conjunto de datos contiene elementos que tienen la misma estructura (anidada) y los componentes individuales de la estructura pueden ser de cualquier tipo representable por tf.TypeSpec
, incluidos tf.Tensor
, tf.sparse.SparseTensor
,tf.RaggedTensor
, tf.TensorArray
, otf.data.Dataset
.
La propiedad Dataset.element_spec
permite inspeccionar el tipo de cada componente del elemento. La propiedad devuelve una estructura anidada de objetos tf.TypeSpec
, que coincide con la estructura del elemento, que puede ser un solo componente, una tupla de componentes o una tupla anidada de componentes. Por ejemplo:
dataset1 = tf.data.Dataset.from_tensor_slices(tf.random.uniform([4, 10]))
dataset1.element_spec
TensorSpec(shape=(10,), dtype=tf.float32, name=None)
dataset2 = tf.data.Dataset.from_tensor_slices(
(tf.random.uniform([4]),
tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))
dataset2.element_spec
(TensorSpec(shape=(), dtype=tf.float32, name=None), TensorSpec(shape=(100,), dtype=tf.int32, name=None))
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))
dataset3.element_spec
(TensorSpec(shape=(10,), dtype=tf.float32, name=None), (TensorSpec(shape=(), dtype=tf.float32, name=None), TensorSpec(shape=(100,), dtype=tf.int32, name=None)))
# Dataset containing a sparse tensor.
dataset4 = tf.data.Dataset.from_tensors(tf.SparseTensor(indices=[[0, 0], [1, 2]], values=[1, 2], dense_shape=[3, 4]))
dataset4.element_spec
SparseTensorSpec(TensorShape([3, 4]), tf.int32)
# Use value_type to see the type of value represented by the element spec
dataset4.element_spec.value_type
tensorflow.python.framework.sparse_tensor.SparseTensor
Los Dataset
transformaciones soportan los conjuntos de datos de cualquier estructura. Cuando se utilizan las Dataset.map()
y Dataset.filter()
, que aplican una función a cada elemento, la estructura del elemento determina los argumentos de la función:
dataset1 = tf.data.Dataset.from_tensor_slices(
tf.random.uniform([4, 10], minval=1, maxval=10, dtype=tf.int32))
dataset1
<TensorSliceDataset shapes: (10,), types: tf.int32>
for z in dataset1:
print(z.numpy())
[4 6 7 3 1 1 6 7 3 7] [6 6 1 7 3 8 9 8 9 4] [2 3 2 2 7 1 8 8 5 9] [6 6 7 8 8 9 2 3 7 8]
dataset2 = tf.data.Dataset.from_tensor_slices(
(tf.random.uniform([4]),
tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))
dataset2
<TensorSliceDataset shapes: ((), (100,)), types: (tf.float32, tf.int32)>
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))
dataset3
<ZipDataset shapes: ((10,), ((), (100,))), types: (tf.int32, (tf.float32, tf.int32))>
for a, (b,c) in dataset3:
print('shapes: {a.shape}, {b.shape}, {c.shape}'.format(a=a, b=b, c=c))
shapes: (10,), (), (100,) shapes: (10,), (), (100,) shapes: (10,), (), (100,) shapes: (10,), (), (100,)
Leer datos de entrada
Consumir matrices NumPy
Consulte Carga de matrices NumPy para obtener más ejemplos.
Si todos sus datos de entrada caben en la memoria, la forma más sencilla de crear un Dataset
de Dataset
partir de ellos es convertirlos en objetos tf.Tensor
y usar Dataset.from_tensor_slices()
.
train, test = tf.keras.datasets.fashion_mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz 32768/29515 [=================================] - 0s 0us/step Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz 26427392/26421880 [==============================] - 1s 0us/step Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz 8192/5148 [===============================================] - 0s 0us/step Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz 4423680/4422102 [==============================] - 0s 0us/step
images, labels = train
images = images/255
dataset = tf.data.Dataset.from_tensor_slices((images, labels))
dataset
<TensorSliceDataset shapes: ((28, 28), ()), types: (tf.float64, tf.uint8)>
Consumir generadores de Python
Otra fuente de datos común que se puede ingerir fácilmente como untf.data.Dataset
es el generador de Python.
def count(stop):
i = 0
while i<stop:
yield i
i += 1
for n in count(5):
print(n)
0 1 2 3 4
El constructor Dataset.from_generator
convierte el generador de Python en untf.data.Dataset
completamente funcional.
El constructor toma un invocable como entrada, no un iterador. Esto le permite reiniciar el generador cuando llega al final. Toma un argumento args
opcional, que se pasa como argumentos del invocable.
El argumento output_types
es necesario porque tf.data
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())
[0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19] [20 21 22 23 24 0 1 2 3 4] [ 5 6 7 8 9 10 11 12 13 14] [15 16 17 18 19 20 21 22 23 24] [0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19] [20 21 22 23 24 0 1 2 3 4] [ 5 6 7 8 9 10 11 12 13 14] [15 16 17 18 19 20 21 22 23 24]
El argumento output_shapes
no es obligatorio, pero se recomienda encarecidamente, ya que muchas operaciones de flujo tensorial no admiten tensores con rango desconocido. Si la longitud de un eje en particular es desconocida o variable, output_shapes
como None
en output_shapes
.
También es importante tener en cuenta que output_shapes
y output_types
siguen las mismas reglas de anidamiento que otros métodos de conjuntos de datos.
Aquí hay un generador de ejemplo que demuestra ambos aspectos, devuelve tuplas de matrices, donde la segunda matriz es un vector con longitud desconocida.
def gen_series():
i = 0
while True:
size = np.random.randint(0, 10)
yield i, np.random.normal(size=(size,))
i += 1
for i, series in gen_series():
print(i, ":", str(series))
if i > 5:
break
0 : [-1.8423 -0.1016 0.2763 0.815 0.0137 0.1228 0.0773] 1 : [ 0.4419 0.6819 -0.576 ] 2 : [-0.8961 -0.8613 -0.5917 0.7749 -0.2283 0.4406 -2.4833 0.1952 0.9962] 3 : [] 4 : [0.2609 0.854 2.96 ] 5 : [] 6 : [ 1.0899 -0.377 0.4295 -1.835 -0.4915 -0.0435 -0.6999 -0.9527]
La primera salida es un int32
la segunda es un float32
.
El primer elemento es un escalar, forma ()
, y el segundo es un vector de longitud desconocida, forma (None,)
ds_series = tf.data.Dataset.from_generator(
gen_series,
output_types=(tf.int32, tf.float32),
output_shapes=((), (None,)))
ds_series
<FlatMapDataset shapes: ((), (None,)), types: (tf.int32, tf.float32)>
Ahora se puede utilizar como untf.data.Dataset
normal. Tenga en cuenta que al procesar por lotes un conjunto de datos con una forma variable, debe usar Dataset.padded_batch
.
ds_series_batch = ds_series.shuffle(20).padded_batch(10)
ids, sequence_batch = next(iter(ds_series_batch))
print(ids.numpy())
print()
print(sequence_batch.numpy())
[12 0 21 20 19 2 13 6 16 15] [[ 0.6409 0. 0. 0. 0. 0. 0. 0. 0. ] [-0.3158 -1.1566 0.5766 0.2067 0.2566 -0.7567 0. 0. 0. ] [ 1.703 0. 0. 0. 0. 0. 0. 0. 0. ] [ 1.577 0. 0. 0. 0. 0. 0. 0. 0. ] [ 0. 0. 0. 0. 0. 0. 0. 0. 0. ] [-1.1427 1.779 1.5403 0.428 -0.0309 0.8038 -0.4779 0.3646 -0.3527] [-1.0069 0.6749 -1.4268 0.0887 0.4798 0.769 0.5454 0. 0. ] [-0.3393 0.5725 -0.8578 -3.5323 -0.9053 0.261 -1.7785 0.5377 -0.4388] [ 0.5343 1.609 -0.9407 1.1031 0.4216 0. 0. 0. 0. ] [ 1.1637 0.6195 1.6317 -0.759 -0.4261 -3.2933 1.9672 -0.2561 1.341 ]]
Para obtener un ejemplo más realista, intentetf.data.Dataset
preprocessing.image.ImageDataGenerator
como untf.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 [==============================] - 11s 0us/step
Crea la imagen. image.ImageDataGenerator
img_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255, rotation_range=20)
images, labels = next(img_gen.flow_from_directory(flowers))
Found 3670 images belonging to 5 classes.
print(images.dtype, images.shape)
print(labels.dtype, labels.shape)
float32 (32, 256, 256, 3) float32 (32, 5)
ds = tf.data.Dataset.from_generator(
lambda: img_gen.flow_from_directory(flowers),
output_types=(tf.float32, tf.float32),
output_shapes=([32,256,256,3], [32,5])
)
ds.element_spec
(TensorSpec(shape=(32, 256, 256, 3), dtype=tf.float32, name=None), TensorSpec(shape=(32, 5), dtype=tf.float32, name=None))
for images, label in ds.take(1):
print('images.shape: ', images.shape)
print('labels.shape: ', labels.shape)
Found 3670 images belonging to 5 classes. images.shape: (32, 256, 256, 3) labels.shape: (32, 5)
Consumir datos de TFRecord
Consulte Carga de TFRecords para ver un ejemplo de un extremo a otro.
La API tf.data
admite una variedad de formatos de archivo para que pueda procesar grandes conjuntos de datos que no caben en la memoria. Por ejemplo, el formato de archivo TFRecord es un formato binario simple orientado a registros que muchas aplicaciones de TensorFlow usan para entrenar datos. La clase tf.data.TFRecordDataset
permite transmitir el contenido de uno o más archivos TFRecord como parte de una canalización de entrada.
A continuación se muestra un ejemplo que utiliza el archivo de prueba de los carteles de nombres de calles franceses (FSNS).
# Creates a dataset that reads all of the examples from two files.
fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001 7905280/7904079 [==============================] - 0s 0us/step
El argumento de filenames
para el inicializador TFRecordDataset
puede ser una cadena, una lista de cadenas o un tf.Tensor
de cadenas. Por lo tanto, si tiene dos conjuntos de archivos con fines de entrenamiento y validación, puede crear un método de fábrica que produzca el conjunto de datos, tomando los nombres de archivo como argumento de entrada:
dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>
Muchos proyectos de TensorFlow utilizan registros tf.train.Example
serializados en sus archivos TFRecord. Estos deben decodificarse antes de que se puedan inspeccionar:
raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())
parsed.features.feature['image/text']
bytes_list { value: "Rue Perreyon" }
Consumir datos de texto
Consulte Carga de texto para ver un ejemplo de principio a fin.
Muchos conjuntos de datos se distribuyen como uno o más archivos de texto. tf.data.TextLineDataset
proporciona una manera fácil de extraer líneas de uno o más archivos de texto. Dados uno o más nombres de archivo, un TextLineDataset
producirá un elemento con valor de cadena por línea de esos archivos.
directory_url = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
file_names = ['cowper.txt', 'derby.txt', 'butler.txt']
file_paths = [
tf.keras.utils.get_file(file_name, directory_url + file_name)
for file_name in file_names
]
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/cowper.txt 819200/815980 [==============================] - 0s 0us/step Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt 811008/809730 [==============================] - 0s 0us/step Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt 811008/807992 [==============================] - 0s 0us/step
dataset = tf.data.TextLineDataset(file_paths)
Aquí están las primeras líneas del primer archivo:
for line in dataset.take(5):
print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;" b'His wrath pernicious, who ten thousand woes' b"Caused to Achaia's host, sent many a soul" b'Illustrious into Ades premature,' b'And Heroes gave (so stood the will of Jove)'
Para alternar líneas entre archivos, use Dataset.interleave
. Esto facilita la reproducción aleatoria de archivos. Aquí están la primera, segunda y tercera líneas de cada traducción:
files_ds = tf.data.Dataset.from_tensor_slices(file_paths)
lines_ds = files_ds.interleave(tf.data.TextLineDataset, cycle_length=3)
for i, line in enumerate(lines_ds.take(9)):
if i % 3 == 0:
print()
print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;" b"\xef\xbb\xbfOf Peleus' son, Achilles, sing, O Muse," b'\xef\xbb\xbfSing, O goddess, the anger of Achilles son of Peleus, that brought' b'His wrath pernicious, who ten thousand woes' b'The vengeance, deep and deadly; whence to Greece' b'countless ills upon the Achaeans. Many a brave soul did it send' b"Caused to Achaia's host, sent many a soul" b'Unnumbered ills arose; which many a soul' b'hurrying down to Hades, and many a hero did it yield a prey to dogs and'
De forma predeterminada, un TextLineDataset
produce cada línea de cada archivo, lo que puede no ser deseable, por ejemplo, si el archivo comienza con una línea de encabezado o contiene comentarios. Estas líneas se pueden eliminar mediante las Dataset.skip()
o Dataset.filter()
. Aquí, omite la primera línea y luego filtra para encontrar solo sobrevivientes.
titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/train.csv 32768/30874 [===============================] - 0s 0us/step
for line in titanic_lines.take(10):
print(line.numpy())
b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone' b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n' b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n' b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y' b'1,female,35.0,1,0,53.1,First,C,Southampton,n' b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y' b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n' b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n' b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n' b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
def survived(line):
return tf.not_equal(tf.strings.substr(line, 0, 1), "0")
survivors = titanic_lines.skip(1).filter(survived)
for line in survivors.take(10):
print(line.numpy())
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n' b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y' b'1,female,35.0,1,0,53.1,First,C,Southampton,n' b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n' b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n' b'1,female,4.0,1,1,16.7,Third,G,Southampton,n' b'1,male,28.0,0,0,13.0,Second,unknown,Southampton,y' b'1,female,28.0,0,0,7.225,Third,unknown,Cherbourg,y' b'1,male,28.0,0,0,35.5,First,A,Southampton,y' b'1,female,38.0,1,5,31.3875,Third,unknown,Southampton,n'
Consumir datos CSV
Consulte Carga de archivos CSV y Carga de Pandas DataFrames para obtener más ejemplos.
El formato de archivo CSV es un formato popular para almacenar datos tabulares en texto sin formato.
Por ejemplo:
titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
df = pd.read_csv(titanic_file)
df.head()
Si sus datos Dataset.from_tensor_slices
en la memoria, el mismo método Dataset.from_tensor_slices
funciona en diccionarios, lo que permite que estos datos se importen fácilmente:
titanic_slices = tf.data.Dataset.from_tensor_slices(dict(df))
for feature_batch in titanic_slices.take(1):
for key, value in feature_batch.items():
print(" {!r:20s}: {}".format(key, value))
'survived' : 0 'sex' : b'male' 'age' : 22.0 'n_siblings_spouses': 1 'parch' : 0 'fare' : 7.25 'class' : b'Third' 'deck' : b'unknown' 'embark_town' : b'Southampton' 'alone' : b'n'
Un enfoque más escalable es cargar desde el disco según sea necesario.
El módulo tf.data
proporciona métodos para extraer registros de uno o más archivos CSV que cumplen con RFC 4180 .
La función experimental.make_csv_dataset
es la interfaz de alto nivel para leer conjuntos de archivos csv. Admite la inferencia de tipo de columna y muchas otras funciones, como el procesamiento por lotes y la mezcla, para simplificar el uso.
titanic_batches = tf.data.experimental.make_csv_dataset(
titanic_file, batch_size=4,
label_name="survived")
for feature_batch, label_batch in titanic_batches.take(1):
print("'survived': {}".format(label_batch))
print("features:")
for key, value in feature_batch.items():
print(" {!r:20s}: {}".format(key, value))
'survived': [0 0 0 1] features: 'sex' : [b'male' b'male' b'male' b'female'] 'age' : [11. 16. 28. 19.] 'n_siblings_spouses': [5 4 0 0] 'parch' : [2 1 0 2] 'fare' : [46.9 39.6875 7.75 26.2833] 'class' : [b'Third' b'Third' b'Third' b'First'] 'deck' : [b'unknown' b'unknown' b'unknown' b'D'] 'embark_town' : [b'Southampton' b'Southampton' b'Queenstown' b'Southampton'] 'alone' : [b'n' b'n' b'y' b'n']
Puede usar el argumento select_columns
si solo necesita un subconjunto de columnas.
titanic_batches = tf.data.experimental.make_csv_dataset(
titanic_file, batch_size=4,
label_name="survived", select_columns=['class', 'fare', 'survived'])
for feature_batch, label_batch in titanic_batches.take(1):
print("'survived': {}".format(label_batch))
for key, value in feature_batch.items():
print(" {!r:20s}: {}".format(key, value))
'survived': [0 1 1 0] 'fare' : [ 0. 12.2875 30. 7.75 ] 'class' : [b'Second' b'Third' b'First' b'Third']
También hay una clase experimental.CsvDataset
nivel inferior que proporciona un control más detallado. No admite la inferencia de tipo de columna. En su lugar, debe especificar el tipo de cada columna.
titanic_types = [tf.int32, tf.string, tf.float32, tf.int32, tf.int32, tf.float32, tf.string, tf.string, tf.string, tf.string]
dataset = tf.data.experimental.CsvDataset(titanic_file, titanic_types , header=True)
for line in dataset.take(10):
print([item.numpy() for item in line])
[0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n'] [1, b'female', 38.0, 1, 0, 71.2833, b'First', b'C', b'Cherbourg', b'n'] [1, b'female', 26.0, 0, 0, 7.925, b'Third', b'unknown', b'Southampton', b'y'] [1, b'female', 35.0, 1, 0, 53.1, b'First', b'C', b'Southampton', b'n'] [0, b'male', 28.0, 0, 0, 8.4583, b'Third', b'unknown', b'Queenstown', b'y'] [0, b'male', 2.0, 3, 1, 21.075, b'Third', b'unknown', b'Southampton', b'n'] [1, b'female', 27.0, 0, 2, 11.1333, b'Third', b'unknown', b'Southampton', b'n'] [1, b'female', 14.0, 1, 0, 30.0708, b'Second', b'unknown', b'Cherbourg', b'n'] [1, b'female', 4.0, 1, 1, 16.7, b'Third', b'G', b'Southampton', b'n'] [0, b'male', 20.0, 0, 0, 8.05, b'Third', b'unknown', b'Southampton', b'y']
Si algunas columnas están vacías, esta interfaz de bajo nivel le permite proporcionar valores predeterminados en lugar de tipos de columna.
%%writefile missing.csv
1,2,3,4
,2,3,4
1,,3,4
1,2,,4
1,2,3,
,,,
Writing missing.csv
# Creates a dataset that reads all of the records from two CSV files, each with
# four float columns which may have missing values.
record_defaults = [999,999,999,999]
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults)
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (4,), types: tf.int32>
for line in dataset:
print(line.numpy())
[1 2 3 4] [999 2 3 4] [ 1 999 3 4] [ 1 2 999 4] [ 1 2 3 999] [999 999 999 999]
De forma predeterminada, un CsvDataset
genera cada columna de cada línea del archivo, lo que puede no ser deseable, por ejemplo, si el archivo comienza con una línea de encabezado que debe ignorarse o si algunas columnas no son necesarias en la entrada. Estas líneas y campos se pueden eliminar con los argumentos header
y select_cols
respectivamente.
# Creates a dataset that reads all of the records from two CSV files with
# headers, extracting float data from columns 2 and 4.
record_defaults = [999, 999] # Only provide defaults for the selected columns
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults, select_cols=[1, 3])
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (2,), types: tf.int32>
for line in dataset:
print(line.numpy())
[2 4] [2 4] [999 4] [2 4] [ 2 999] [999 999]
Consumir conjuntos de archivos
Hay muchos conjuntos de datos distribuidos como un conjunto de archivos, donde cada archivo es un ejemplo.
flowers_root = tf.keras.utils.get_file(
'flower_photos',
'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
untar=True)
flowers_root = pathlib.Path(flowers_root)
El directorio raíz contiene un directorio para cada clase:
for item in flowers_root.glob("*"):
print(item.name)
sunflowers daisy LICENSE.txt roses tulips dandelion
Los archivos de cada directorio de clases son ejemplos:
list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))
for f in list_ds.take(5):
print(f.numpy())
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/4868595281_1e58083785.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/daisy/5883162120_dc7274af76_n.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/tulips/12883412424_cb5086b43f_n.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/roses/13264214185_d6aa79b3bd.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/roses/6690926183_afedba9f15_n.jpg'
Lea los datos usando la funcióntf.io.read_file
y extraiga la etiqueta de la ruta, devolviendo pares (image, label)
:
def process_path(file_path):
label = tf.strings.split(file_path, os.sep)[-2]
return tf.io.read_file(file_path), label
labeled_ds = list_ds.map(process_path)
for image_raw, label_text in labeled_ds.take(1):
print(repr(image_raw.numpy()[:100]))
print()
print(label_text.numpy())
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xfe\x00\x0cAppleMark\n\xff\xe2\x05(ICC_PROFILE\x00\x01\x01\x00\x00\x05\x18appl\x02 \x00\x00scnrRGB XYZ \x07\xd3\x00\x07\x00\x01\x00\x00\x00\x00\x00\x00acspAPPL\x00\x00\x00\x00' b'sunflowers'
Agrupar elementos del conjunto de datos
Dosificación simple
La forma más simple de agrupamiento por lotes apila n
elementos consecutivos de un conjunto de datos en un solo elemento. La transformación Dataset.batch()
hace exactamente esto, con las mismas restricciones que el operador tf.stack()
, aplicado a cada componente de los elementos: es decir, para cada componente i , todos los elementos deben tener un tensor de la misma forma exacta.
inc_dataset = tf.data.Dataset.range(100)
dec_dataset = tf.data.Dataset.range(0, -100, -1)
dataset = tf.data.Dataset.zip((inc_dataset, dec_dataset))
batched_dataset = dataset.batch(4)
for batch in batched_dataset.take(4):
print([arr.numpy() for arr in batch])
[array([0, 1, 2, 3]), array([ 0, -1, -2, -3])] [array([4, 5, 6, 7]), array([-4, -5, -6, -7])] [array([ 8, 9, 10, 11]), array([ -8, -9, -10, -11])] [array([12, 13, 14, 15]), array([-12, -13, -14, -15])]
Si bien tf.data
intenta propagar información de formas, la configuración predeterminada de Dataset.batch
resultado un tamaño de lote desconocido porque es posible que el último lote no esté lleno. Tenga en cuenta los None
en la forma:
batched_dataset
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.int64)>
Use el argumento drop_remainder
para ignorar ese último lote y obtener la propagación de forma completa:
batched_dataset = dataset.batch(7, drop_remainder=True)
batched_dataset
<BatchDataset shapes: ((7,), (7,)), types: (tf.int64, tf.int64)>
Tensores por lotes con relleno
La receta anterior funciona para tensores que tienen el mismo tamaño. Sin embargo, muchos modelos (por ejemplo, modelos de secuencia) funcionan con datos de entrada que pueden tener diferentes tamaños (por ejemplo, secuencias de diferentes longitudes). Para manejar este caso, la transformación Dataset.padded_batch
permite Dataset.padded_batch
tensores de diferente forma especificando una o más dimensiones en las que se pueden rellenar.
dataset = tf.data.Dataset.range(100)
dataset = dataset.map(lambda x: tf.fill([tf.cast(x, tf.int32)], x))
dataset = dataset.padded_batch(4, padded_shapes=(None,))
for batch in dataset.take(2):
print(batch.numpy())
print()
[[0 0 0] [1 0 0] [2 2 0] [3 3 3]] [[4 4 4 4 0 0 0] [5 5 5 5 5 0 0] [6 6 6 6 6 6 0] [7 7 7 7 7 7 7]]
La transformación Dataset.padded_batch
permite establecer un relleno diferente para cada dimensión de cada componente, y puede ser de longitud variable (indicado por None
en el ejemplo anterior) o de longitud constante. También es posible anular el valor de relleno, que por defecto es 0.
Flujos de trabajo de formación
Procesando múltiples épocas
La API tf.data
ofrece dos formas principales de procesar varias épocas de los mismos datos.
La forma más sencilla de iterar sobre un conjunto de datos en varias épocas es usar la transformación Dataset.repeat()
. Primero, cree un conjunto de datos de datos titánicos:
titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
def plot_batch_sizes(ds):
batch_sizes = [batch.shape[0] for batch in ds]
plt.bar(range(len(batch_sizes)), batch_sizes)
plt.xlabel('Batch number')
plt.ylabel('Batch size')
La aplicación de la transformación Dataset.repeat()
sin argumentos repetirá la entrada indefinidamente.
La transformación Dataset.repeat
concatena sus argumentos sin señalar el final de una época y el comienzo de la siguiente. Debido a esto, un Dataset.batch
aplicado después de Dataset.repeat
producirá lotes que Dataset.repeat
límites de la época:
titanic_batches = titanic_lines.repeat(3).batch(128)
plot_batch_sizes(titanic_batches)
Si necesita una separación de época clara, coloque Dataset.batch
antes de la repetición:
titanic_batches = titanic_lines.batch(128).repeat(3)
plot_batch_sizes(titanic_batches)
Si desea realizar un cálculo personalizado (por ejemplo, para recopilar estadísticas) al final de cada época, lo más sencillo es reiniciar la iteración del conjunto de datos en cada época:
epochs = 3
dataset = titanic_lines.batch(128)
for epoch in range(epochs):
for batch in dataset:
print(batch.shape)
print("End of epoch: ", epoch)
(128,) (128,) (128,) (128,) (116,) End of epoch: 0 (128,) (128,) (128,) (128,) (116,) End of epoch: 1 (128,) (128,) (128,) (128,) (116,) End of epoch: 2
Mezcla aleatoria de datos de entrada
La transformación Dataset.shuffle()
mantiene un búfer de tamaño fijo y elige el siguiente elemento uniformemente al azar de ese búfer.
Agrega un índice al conjunto de datos para que puedas ver el efecto:
lines = tf.data.TextLineDataset(titanic_file)
counter = tf.data.experimental.Counter()
dataset = tf.data.Dataset.zip((counter, lines))
dataset = dataset.shuffle(buffer_size=100)
dataset = dataset.batch(20)
dataset
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>
Dado que buffer_size
es 100 y el tamaño del lote es 20, el primer lote no contiene elementos con un índice superior a 120.
n,line_batch = next(iter(dataset))
print(n.numpy())
[ 90 75 39 84 102 5 98 101 51 72 54 33 104 59 110 29 92 50 36 103]
Al igual que con Dataset.batch
el orden relativo a Dataset.repeat
importa.
Dataset.shuffle
no Dataset.shuffle
el final de una época hasta que el búfer de reproducción aleatoria esté vacío. Entonces, una reproducción aleatoria colocada antes de una repetición mostrará todos los elementos de una época antes de pasar a la siguiente:
dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.shuffle(buffer_size=100).batch(10).repeat(2)
print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(60).take(5):
print(n.numpy())
Here are the item ID's near the epoch boundary: [550 618 614 435 556 530 578 451 590 604] [464 453 610 412 282 596 601 612 584 606] [368 469 575 607 586 537 444 300] [ 15 98 65 26 40 39 101 54 32 10] [ 8 102 68 108 12 96 2 87 80 37]
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 0x7f3f083eebe0>
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: [576 618 527 9 602 612 21 574 504 622] [623 26 32 616 626 482 617 598 0 614] [476 1 473 14 10 267 29 31 43 48] [588 13 470 467 12 596 619 46 28 528] [609 2 52 542 607 23 35 38 620 523] [509 477 571 15 56 74 565 525 58 19] [359 40 22 627 317 54 526 16 562 33] [ 67 500 584 531 49 86 51 81 78 583] [ 24 557 452 47 124 485 610 45 27 17] [379 66 85 91 599 97 499 112 108 11] [ 39 164 101 96 543 64 109 564 82 18] [533 120 30 63 115 88 95 75 133 34] [ 92 65 102 132 76 119 131 475 572 50] [ 94 145 144 603 152 505 621 140 448 122] [ 70 159 146 84 71 160 42 72 41 139]
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 0x7f3f0838c860>
Procesamiento previo de datos
La Dataset.map(f)
produce un nuevo conjunto de datos aplicando una función f
dada a cada elemento del conjunto de datos de entrada. Se basa en la función map()
que se aplica comúnmente a listas (y otras estructuras) en lenguajes de programación funcionales. La función f
toma los objetos tf.Tensor
que representan un solo elemento en la entrada y devuelve los objetos tf.Tensor
que representarán un solo elemento en el nuevo conjunto de datos. Su implementación usa operaciones estándar de TensorFlow para transformar un elemento en otro.
Esta sección cubre ejemplos comunes de cómo usar Dataset.map()
.
Decodificar datos de imagen y cambiar su tamaño
Al entrenar una red neuronal con datos de imágenes del mundo real, a menudo es necesario convertir imágenes de diferentes tamaños a un tamaño común, de modo que puedan agruparse en un tamaño fijo.
Reconstruir el conjunto de datos de nombres de archivos de flores:
list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))
Escribe una función que manipule los elementos del conjunto de datos.
# Reads an image from a file, decodes it into a dense tensor, and resizes it
# to a fixed shape.
def parse_image(filename):
parts = tf.strings.split(filename, os.sep)
label = parts[-2]
image = tf.io.read_file(filename)
image = tf.image.decode_jpeg(image)
image = tf.image.convert_image_dtype(image, tf.float32)
image = tf.image.resize(image, [128, 128])
return image, label
Prueba que funciona.
file_path = next(iter(list_ds))
image, label = parse_image(file_path)
def show(image, label):
plt.figure()
plt.imshow(image)
plt.title(label.numpy().decode('utf-8'))
plt.axis('off')
show(image, label)
Asignelo sobre el conjunto de datos.
images_ds = list_ds.map(parse_image)
for image, label in images_ds.take(2):
show(image, label)
Aplicar lógica de Python arbitraria
Por motivos de rendimiento, utilice las operaciones de TensorFlow para preprocesar sus datos siempre que sea posible. Sin embargo, a veces es útil llamar a bibliotecas de Python externas al analizar sus datos de entrada. Puede usar la operación tf.py_function()
en una transformación Dataset.map()
.
Por ejemplo, si desea aplicar una rotación aleatoria, el módulo tf.image
solo tiene tf.image.rot90
, que no es muy útil para el aumento de imágenes.
Para demostrar tf.py_function
, intente usar la función scipy.ndimage.rotate
lugar:
import scipy.ndimage as ndimage
def random_rotate_image(image):
image = ndimage.rotate(image, np.random.uniform(-30, 30), reshape=False)
return image
image, label = next(iter(images_ds))
image = random_rotate_image(image)
show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Para usar esta función con Dataset.map
las mismas advertencias que con Dataset.from_generator
, debe describir las formas y tipos de retorno cuando aplica la función:
def tf_random_rotate_image(image, label):
im_shape = image.shape
[image,] = tf.py_function(random_rotate_image, [image], [tf.float32])
image.set_shape(im_shape)
return image, label
rot_ds = images_ds.map(tf_random_rotate_image)
for image, label in rot_ds.take(2):
show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Análisis de mensajes de búfer de protocolo de tf.Example
Muchas canalizaciones de entrada extraen tf.train.Example
mensajes de búfer de protocolo de un formato TFRecord. Cada registro tf.train.Example
contiene una o más "características", y la canalización de entrada normalmente convierte estas características en tensores.
fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>
Puede trabajar con tf.train.Example
protos fuera detf.data.Dataset
para comprender los datos:
raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())
feature = parsed.features.feature
raw_img = feature['image/encoded'].bytes_list.value[0]
img = tf.image.decode_png(raw_img)
plt.imshow(img)
plt.axis('off')
_ = plt.title(feature["image/text"].bytes_list.value[0])
raw_example = next(iter(dataset))
def tf_parse(eg):
example = tf.io.parse_example(
eg[tf.newaxis], {
'image/encoded': tf.io.FixedLenFeature(shape=(), dtype=tf.string),
'image/text': tf.io.FixedLenFeature(shape=(), dtype=tf.string)
})
return example['image/encoded'][0], example['image/text'][0]
img, txt = tf_parse(raw_example)
print(txt.numpy())
print(repr(img.numpy()[:20]), "...")
b'Rue Perreyon' b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02X' ...
decoded = dataset.map(tf_parse)
decoded
<MapDataset shapes: ((), ()), types: (tf.string, tf.string)>
image_batch, text_batch = next(iter(decoded.batch(10)))
image_batch.shape
TensorShape([10])
Ventana de series de tiempo
Para ver un ejemplo de serie temporal de principio a fin, consulte: Predicción de series temporales .
Los datos de series de tiempo a menudo se organizan con el eje de tiempo intacto.
Utilice un Dataset.range
simple para demostrar:
range_ds = tf.data.Dataset.range(100000)
Normalmente, los modelos basados en este tipo de datos necesitarán un intervalo de tiempo contiguo.
El enfoque más simple sería agrupar los datos:
Usando batch
batches = range_ds.batch(10, drop_remainder=True)
for batch in batches.take(5):
print(batch.numpy())
[0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19] [20 21 22 23 24 25 26 27 28 29] [30 31 32 33 34 35 36 37 38 39] [40 41 42 43 44 45 46 47 48 49]
O para hacer predicciones densas un paso hacia el futuro, puede cambiar las características y etiquetas un paso en relación con las demás:
def dense_1_step(batch):
# Shift features and labels one step relative to each other.
return batch[:-1], batch[1:]
predict_dense_1_step = batches.map(dense_1_step)
for features, label in predict_dense_1_step.take(3):
print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8] => [1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18] => [11 12 13 14 15 16 17 18 19] [20 21 22 23 24 25 26 27 28] => [21 22 23 24 25 26 27 28 29]
Para predecir una ventana completa en lugar de un desplazamiento fijo, puede dividir los lotes en dos partes:
batches = range_ds.batch(15, drop_remainder=True)
def label_next_5_steps(batch):
return (batch[:-5], # Take the first 5 steps
batch[-5:]) # take the remainder
predict_5_steps = batches.map(label_next_5_steps)
for features, label in predict_5_steps.take(3):
print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9] => [10 11 12 13 14] [15 16 17 18 19 20 21 22 23 24] => [25 26 27 28 29] [30 31 32 33 34 35 36 37 38 39] => [40 41 42 43 44]
Para permitir cierta superposición entre las características de un lote y las etiquetas de otro, use Dataset.zip
:
feature_length = 10
label_length = 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
Si bien el uso de Dataset.batch
funciona, hay situaciones en las que es posible que necesite un control más preciso. El método Dataset.window
le brinda un control completo, pero requiere cierto cuidado: devuelve un Dataset
de Dataset
de Dataset
de Datasets
. Consulte Estructura del conjunto de datos para obtener más detalles.
window_size = 5
windows = range_ds.window(window_size, shift=1)
for sub_ds in windows.take(5):
print(sub_ds)
<_VariantDataset shapes: (), types: tf.int64> <_VariantDataset shapes: (), types: tf.int64> <_VariantDataset shapes: (), types: tf.int64> <_VariantDataset shapes: (), types: tf.int64> <_VariantDataset shapes: (), types: tf.int64>
El método Dataset.flat_map
puede tomar un conjunto de datos de conjuntos de datos y aplanarlo en un solo conjunto de datos:
for x in windows.flat_map(lambda x: x).take(30):
print(x.numpy(), end=' ')
0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9
En casi todos los casos, primero querrá .batch
el conjunto de datos:
def sub_to_batch(sub):
return sub.batch(window_size, drop_remainder=True)
for example in windows.flat_map(sub_to_batch).take(5):
print(example.numpy())
[0 1 2 3 4] [1 2 3 4 5] [2 3 4 5 6] [3 4 5 6 7] [4 5 6 7 8]
Ahora, puede ver que el argumento de shift
controla cuánto se mueve cada ventana.
Poniendo esto junto podrías escribir esta función:
def make_window_dataset(ds, window_size=5, shift=1, stride=1):
windows = ds.window(window_size, shift=shift, stride=stride)
def sub_to_batch(sub):
return sub.batch(window_size, drop_remainder=True)
windows = windows.flat_map(sub_to_batch)
return windows
ds = make_window_dataset(range_ds, window_size=10, shift = 5, stride=3)
for example in ds.take(10):
print(example.numpy())
[ 0 3 6 9 12 15 18 21 24 27] [ 5 8 11 14 17 20 23 26 29 32] [10 13 16 19 22 25 28 31 34 37] [15 18 21 24 27 30 33 36 39 42] [20 23 26 29 32 35 38 41 44 47] [25 28 31 34 37 40 43 46 49 52] [30 33 36 39 42 45 48 51 54 57] [35 38 41 44 47 50 53 56 59 62] [40 43 46 49 52 55 58 61 64 67] [45 48 51 54 57 60 63 66 69 72]
Entonces es fácil extraer etiquetas, como antes:
dense_labels_ds = ds.map(dense_1_step)
for inputs,labels in dense_labels_ds.take(3):
print(inputs.numpy(), "=>", labels.numpy())
[ 0 3 6 9 12 15 18 21 24] => [ 3 6 9 12 15 18 21 24 27] [ 5 8 11 14 17 20 23 26 29] => [ 8 11 14 17 20 23 26 29 32] [10 13 16 19 22 25 28 31 34] => [13 16 19 22 25 28 31 34 37]
Remuestreo
Cuando trabaje con un conjunto de datos que está muy desequilibrado de clases, es posible que desee volver a muestrear el conjunto de datos. tf.data
proporciona dos métodos para hacer esto. El conjunto de datos sobre fraudes con tarjetas de crédito es un buen ejemplo de este tipo de problema.
zip_path = tf.keras.utils.get_file(
origin='https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip',
fname='creditcard.zip',
extract=True)
csv_path = zip_path.replace('.zip', '.csv')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip 69156864/69155632 [==============================] - 3s 0us/step
creditcard_ds = tf.data.experimental.make_csv_dataset(
csv_path, batch_size=1024, label_name="Class",
# Set the column types: 30 floats and an int.
column_defaults=[float()]*30+[int()])
Ahora, verifique la distribución de clases, está muy sesgada:
def count(counts, batch):
features, labels = batch
class_1 = labels == 1
class_1 = tf.cast(class_1, tf.int32)
class_0 = labels == 0
class_0 = tf.cast(class_0, tf.int32)
counts['class_0'] += tf.reduce_sum(class_0)
counts['class_1'] += tf.reduce_sum(class_1)
return counts
counts = creditcard_ds.take(10).reduce(
initial_state={'class_0': 0, 'class_1': 0},
reduce_func = count)
counts = np.array([counts['class_0'].numpy(),
counts['class_1'].numpy()]).astype(np.float32)
fractions = counts/counts.sum()
print(fractions)
[0.9952 0.0048]
Un enfoque común para entrenar con un conjunto de datos desequilibrado es equilibrarlo. tf.data
incluye algunos métodos que habilitan este flujo de trabajo:
Muestreo de conjuntos de datos
Un enfoque para sample_from_datasets
a sample_from_datasets
un conjunto de datos es usar sample_from_datasets
. Esto es más aplicable cuando tiene undata.Dataset
separado para cada clase.
Aquí, 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 usar tf.data.experimental.sample_from_datasets
pase los conjuntos de datos y el peso de cada uno:
balanced_ds = tf.data.experimental.sample_from_datasets(
[negative_ds, positive_ds], [0.5, 0.5]).batch(10)
Ahora, el conjunto de datos produce ejemplos de cada clase con una probabilidad del 50/50:
for features, labels in balanced_ds.take(10):
print(labels.numpy())
[1 0 1 0 1 1 0 0 1 0] [0 0 1 1 1 1 0 0 1 1] [1 1 0 1 1 0 1 1 0 0] [1 0 1 1 0 0 0 0 0 1] [1 1 0 1 1 0 0 0 1 0] [1 0 1 1 1 0 0 0 1 1] [0 1 1 0 0 0 1 0 1 0] [0 1 1 1 1 0 1 1 1 0] [0 0 1 1 1 1 0 0 1 1] [0 0 0 0 1 0 0 1 0 0]
Remuestreo de rechazo
Un problema con el enfoque experimental.sample_from_datasets
anterior es que necesita untf.data.Dataset
separado por clase. El uso de Dataset.filter
funciona, pero hace que todos los datos se carguen dos veces.
La función data.experimental.rejection_resample
se puede aplicar a un conjunto de datos para reequilibrarlo, mientras que solo se carga una vez. Los elementos se eliminarán del conjunto de datos para lograr el equilibrio.
data.experimental.rejection_resample
toma un argumento class_func
. Este class_func
se aplica a cada elemento del conjunto de datos y se utiliza para determinar a qué clase pertenece un ejemplo para fines de equilibrio.
Los elementos de creditcard_ds
ya son pares (features, label)
. Entonces, class_func
solo necesita devolver esas etiquetas:
def class_func(features, label):
return label
El remuestreador también necesita una distribución de destino y, opcionalmente, una estimación de distribución inicial:
resampler = tf.data.experimental.rejection_resample(
class_func, target_dist=[0.5, 0.5], initial_dist=fractions)
El remuestreador trata con ejemplos individuales, por lo que debe unbatch
el conjunto de datos antes de aplicar el remuestreador:
resample_ds = creditcard_ds.unbatch().apply(resampler).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/data/experimental/ops/resampling.py:156: Print (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2018-08-20. Instructions for updating: Use tf.print instead of tf.Print. Note that tf.print returns a no-output operator that directly prints the output. Outside of defuns or eager mode, this operator will not be executed unless it is directly specified in session.run or used as a control dependency for other operators. This is only a concern in graph mode. Below is an example of how to ensure tf.print executes in graph mode:
El remuestreador devuelve crea (class, example)
pares a partir de la salida de class_func
. En este caso, el example
ya era un par (feature, label)
, así que use map
para soltar la copia adicional de las etiquetas:
balanced_ds = resample_ds.map(lambda extra_label, features_and_label: features_and_label)
Ahora, el conjunto de datos produce ejemplos de cada clase con una probabilidad del 50/50:
for features, labels in balanced_ds.take(10):
print(labels.numpy())
[0 1 0 1 1 1 0 1 1 1] [0 1 1 1 1 0 1 0 0 1] [1 0 1 1 0 1 0 1 1 1] [1 0 0 1 1 0 0 0 1 0] [1 1 1 1 1 0 0 0 1 0] [0 0 0 0 1 0 1 1 0 1] [0 1 0 1 1 1 0 1 1 0] [1 0 0 0 0 1 0 1 0 0] [0 1 1 1 0 1 1 1 1 0] [0 1 1 1 1 0 1 1 1 0]
Punto de control del iterador
Tensorflow admite la toma de puntos de control para que, cuando se reinicie el proceso de entrenamiento, pueda restaurar el último punto de control para recuperar la mayor parte de su progreso. Además de señalar las variables del modelo, también puede controlar el progreso del iterador del conjunto de datos. Esto podría resultar útil si tiene un conjunto de datos grande y no desea iniciar el conjunto de datos desde el principio en cada reinicio. Sin embargo, tenga en cuenta que los puntos de control del iterador pueden ser grandes, ya que las transformaciones como la shuffle
y la prefetch
requieren elementos de almacenamiento en búfer dentro del iterador.
Para incluir su iterador en un punto de control, pase el iterador al constructor tf.train.Checkpoint
.
range_ds = tf.data.Dataset.range(20)
iterator = iter(range_ds)
ckpt = tf.train.Checkpoint(step=tf.Variable(0), iterator=iterator)
manager = tf.train.CheckpointManager(ckpt, '/tmp/my_ckpt', max_to_keep=3)
print([next(iterator).numpy() for _ in range(5)])
save_path = manager.save()
print([next(iterator).numpy() for _ in range(5)])
ckpt.restore(manager.latest_checkpoint)
print([next(iterator).numpy() for _ in range(5)])
[0, 1, 2, 3, 4] [5, 6, 7, 8, 9] [5, 6, 7, 8, 9]
Usando tf.data con tf.keras
La API tf.keras
simplifica muchos aspectos de la creación y ejecución de modelos de aprendizaje automático. Sus .fit()
y .evaluate()
y .predict()
admiten conjuntos de datos como entradas. Aquí hay un conjunto de datos rápido y una configuración del modelo:
train, test = tf.keras.datasets.fashion_mnist.load_data()
images, labels = train
images = images/255.0
labels = labels.astype(np.int32)
fmnist_train_ds = tf.data.Dataset.from_tensor_slices((images, labels))
fmnist_train_ds = fmnist_train_ds.shuffle(5000).batch(32)
model = tf.keras.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(10)
])
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
Pasar un conjunto de datos de pares (feature, label)
es todo lo que se necesita para Model.fit
y Model.evaluate
:
model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2 1875/1875 [==============================] - 4s 2ms/step - loss: 0.7804 - accuracy: 0.7374 Epoch 2/2 1875/1875 [==============================] - 3s 2ms/step - loss: 0.4711 - accuracy: 0.8393 <tensorflow.python.keras.callbacks.History at 0x7f3ef05b1390>
Si pasa un conjunto de datos infinito, por ejemplo, llamando a Dataset.repeat()
, solo necesita pasar también el argumento steps_per_epoch
:
model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2 20/20 [==============================] - 0s 2ms/step - loss: 0.4312 - accuracy: 0.8562 Epoch 2/2 20/20 [==============================] - 0s 2ms/step - loss: 0.4509 - accuracy: 0.8344 <tensorflow.python.keras.callbacks.History at 0x7f3ef062e198>
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 [==============================] - 4s 2ms/step - loss: 0.4347 - accuracy: 0.8516 Loss : 0.43466493487358093 Accuracy : 0.8515999913215637
Para conjuntos de datos largos, establezca el número 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.4131 - accuracy: 0.8750 Loss : 0.41311272978782654 Accuracy : 0.875
Las etiquetas no son necesarias al llamar a Model.predict
.
predict_ds = tf.data.Dataset.from_tensor_slices(images).batch(32)
result = model.predict(predict_ds, steps = 10)
print(result.shape)
(320, 10)
Pero las etiquetas se ignoran si pasa un conjunto de datos que las contiene:
result = model.predict(fmnist_train_ds, steps = 10)
print(result.shape)
(320, 10)