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

Cargar datos CSV

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

Este instructivo proporciona ejemplos de cómo usar datos CSV con TensorFlow.

Hay dos partes principales en esto:

  1. Cargando los datos del disco
  2. Preprocesarlo en una forma adecuada para entrenamiento.

Este tutorial se centra en la carga y ofrece algunos ejemplos rápidos de preprocesamiento. Para obtener un tutorial que se centra en el aspecto de preprocesamiento, consulte la guía y el tutorial de capas de preprocesamiento .

Preparar

import pandas as pd
import numpy as np

# Make numpy values easier to read.
np.set_printoptions(precision=3, suppress=True)

import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.keras.layers.experimental import preprocessing

En datos de memoria

Para cualquier conjunto de datos CSV pequeño, la forma más sencilla de entrenar un modelo de TensorFlow en él es cargarlo en la memoria como un marco de datos pandas o una matriz NumPy.

Un ejemplo relativamente simple es el conjunto de datos de abulón .

  • El conjunto de datos es pequeño.
  • Todas las características de entrada son valores de punto flotante de rango limitado.

Aquí se explica cómo descargar los datos en un Pandas DataFrame :

abalone_train = pd.read_csv(
    "https://storage.googleapis.com/download.tensorflow.org/data/abalone_train.csv", header=None, 
    names=["Length", "Diameter", "Height", "Whole weight", "Shucked weight",
           "Viscera weight", "Shell weight", "Age"])

abalone_train.head()

El conjunto de datos contiene un conjunto de medidas de abulón , un tipo de caracol marino.

una concha de abulón

"Concha de abulón" (por Nicki Dugan Pogue , CC BY-SA 2.0)

La tarea nominal de este conjunto de datos es predecir la edad a partir de las otras mediciones, así que separe las características y etiquetas para el entrenamiento:

abalone_features = abalone_train.copy()
abalone_labels = abalone_features.pop('Age')

Para este conjunto de datos, tratará todas las características de manera idéntica. Empaque las funciones en una sola matriz NumPy .:

abalone_features = np.array(abalone_features)
abalone_features
array([[0.435, 0.335, 0.11 , ..., 0.136, 0.077, 0.097],
       [0.585, 0.45 , 0.125, ..., 0.354, 0.207, 0.225],
       [0.655, 0.51 , 0.16 , ..., 0.396, 0.282, 0.37 ],
       ...,
       [0.53 , 0.42 , 0.13 , ..., 0.374, 0.167, 0.249],
       [0.395, 0.315, 0.105, ..., 0.118, 0.091, 0.119],
       [0.45 , 0.355, 0.12 , ..., 0.115, 0.067, 0.16 ]])

Luego haga un modelo de regresión para predecir la edad. Dado que solo hay un tensor de entrada único, un modelo keras.Sequential . Aquí es suficiente el modelo keras.Sequential .

abalone_model = tf.keras.Sequential([
  layers.Dense(64),
  layers.Dense(1)
])

abalone_model.compile(loss = tf.losses.MeanSquaredError(),
                      optimizer = tf.optimizers.Adam())

Para entrenar ese modelo, pase las características y etiquetas a Model.fit :

abalone_model.fit(abalone_features, abalone_labels, epochs=10)
Epoch 1/10
104/104 [==============================] - 0s 2ms/step - loss: 62.3697
Epoch 2/10
104/104 [==============================] - 0s 1ms/step - loss: 11.7374
Epoch 3/10
104/104 [==============================] - 0s 1ms/step - loss: 8.6329
Epoch 4/10
104/104 [==============================] - 0s 1ms/step - loss: 8.1600
Epoch 5/10
104/104 [==============================] - 0s 2ms/step - loss: 7.7406
Epoch 6/10
104/104 [==============================] - 0s 2ms/step - loss: 7.4095
Epoch 7/10
104/104 [==============================] - 0s 2ms/step - loss: 7.1435
Epoch 8/10
104/104 [==============================] - 0s 1ms/step - loss: 6.9596
Epoch 9/10
104/104 [==============================] - 0s 1ms/step - loss: 6.8104
Epoch 10/10
104/104 [==============================] - 0s 1ms/step - loss: 6.7002

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

Acaba de ver la forma más básica de entrenar un modelo utilizando datos CSV. A continuación, aprenderá a aplicar el preprocesamiento para normalizar columnas numéricas.

Preprocesamiento básico

Es una buena práctica normalizar las entradas a su modelo. Las capas de preprocesamiento experimental.preprocessing proporcionan una forma conveniente de incorporar esta normalización en su modelo.

La capa calculará previamente la media y la varianza de cada columna y las utilizará para normalizar los datos.

Primero creas la capa:

normalize = preprocessing.Normalization()

Luego usa el método Normalization.adapt() para adaptar la capa de normalización a sus datos.

normalize.adapt(abalone_features)

Luego use la capa de normalización en su modelo:

norm_abalone_model = tf.keras.Sequential([
  normalize,
  layers.Dense(64),
  layers.Dense(1)
])

norm_abalone_model.compile(loss = tf.losses.MeanSquaredError(),
                           optimizer = tf.optimizers.Adam())

norm_abalone_model.fit(abalone_features, abalone_labels, epochs=10)
Epoch 1/10
104/104 [==============================] - 0s 2ms/step - loss: 93.5696
Epoch 2/10
104/104 [==============================] - 0s 2ms/step - loss: 54.1816
Epoch 3/10
104/104 [==============================] - 0s 2ms/step - loss: 16.3253
Epoch 4/10
104/104 [==============================] - 0s 2ms/step - loss: 5.8225
Epoch 5/10
104/104 [==============================] - 0s 2ms/step - loss: 5.0510
Epoch 6/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9794
Epoch 7/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9541
Epoch 8/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9453
Epoch 9/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9258
Epoch 10/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9344

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

Tipos de datos mixtos

El conjunto de datos del "Titanic" contiene información sobre los pasajeros del Titanic. La tarea nominal de este conjunto de datos es predecir quién sobrevivió.

El titanic

Imagen de Wikimedia

Los datos sin procesar se pueden cargar fácilmente como un Pandas DataFrame , pero no se pueden usar de inmediato como entrada para un modelo de TensorFlow.

titanic = pd.read_csv("https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic.head()
titanic_features = titanic.copy()
titanic_labels = titanic_features.pop('survived')

Debido a los diferentes tipos de datos y rangos, no puede simplemente apilar las características en la matriz NumPy y pasarlas a un modelo keras.Sequential . Cada columna debe manejarse individualmente.

Como una opción, puede preprocesar sus datos sin conexión (con cualquier herramienta que desee) para convertir columnas categóricas en columnas numéricas y luego pasar la salida procesada a su modelo de TensorFlow. La desventaja de ese enfoque es que si guarda y exporta su modelo, el preprocesamiento no se guarda con él. Las capas de preprocesamiento experimental.preprocessing evitan este problema porque son parte del modelo.

En este ejemplo, creará un modelo que implementa la lógica de preprocesamiento utilizando la API funcional de Keras . También puede hacerlo subclasificando .

La API funcional opera con tensores "simbólicos". Los tensores "ansiosos" normales tienen un valor. En contraste, estos tensores "simbólicos" no lo hacen. En su lugar, realizan un seguimiento de las operaciones que se ejecutan en ellos y crean una representación del cálculo que puede ejecutar más tarde. He aquí un ejemplo rápido:

# Create a symbolic input
input = tf.keras.Input(shape=(), dtype=tf.float32)

# Do a calculation using is
result = 2*input + 1

# the result doesn't have a value
result
<tf.Tensor 'AddV2:0' shape=(None,) dtype=float32>
calc = tf.keras.Model(inputs=input, outputs=result)
print(calc(1).numpy())
print(calc(2).numpy())
3.0
5.0

Para construir el modelo de preprocesamiento, comience por construir un conjunto de objetos keras.Input simbólicos, haciendo coincidir los nombres y tipos de datos de las columnas CSV.

inputs = {}

for name, column in titanic_features.items():
  dtype = column.dtype
  if dtype == object:
    dtype = tf.string
  else:
    dtype = tf.float32

  inputs[name] = tf.keras.Input(shape=(1,), name=name, dtype=dtype)

inputs
{'sex': <tf.Tensor 'sex:0' shape=(None, 1) dtype=string>,
 'age': <tf.Tensor 'age:0' shape=(None, 1) dtype=float32>,
 'n_siblings_spouses': <tf.Tensor 'n_siblings_spouses:0' shape=(None, 1) dtype=float32>,
 'parch': <tf.Tensor 'parch:0' shape=(None, 1) dtype=float32>,
 'fare': <tf.Tensor 'fare:0' shape=(None, 1) dtype=float32>,
 'class': <tf.Tensor 'class:0' shape=(None, 1) dtype=string>,
 'deck': <tf.Tensor 'deck:0' shape=(None, 1) dtype=string>,
 'embark_town': <tf.Tensor 'embark_town:0' shape=(None, 1) dtype=string>,
 'alone': <tf.Tensor 'alone:0' shape=(None, 1) dtype=string>}

El primer paso en su lógica de preprocesamiento es concatenar las entradas numéricas juntas y ejecutarlas a través de una capa de normalización:

numeric_inputs = {name:input for name,input in inputs.items()
                  if input.dtype==tf.float32}

x = layers.Concatenate()(list(numeric_inputs.values()))
norm = preprocessing.Normalization()
norm.adapt(np.array(titanic[numeric_inputs.keys()]))
all_numeric_inputs = norm(x)

all_numeric_inputs
<tf.Tensor 'normalization_1/truediv:0' shape=(None, 4) dtype=float32>

Recopile todos los resultados del preprocesamiento simbólico, para concatenarlos posteriormente.

preprocessed_inputs = [all_numeric_inputs]

Para las entradas de cadena, use la función preprocessing.StringLookup para mapear desde cadenas a índices enteros en un vocabulario. A continuación, utilice preprocessing.CategoricalEncoding para convertir los índices en datos float32 apropiados para el modelo.

La configuración predeterminada para la capa de codificación CategoricalEncoding crea un vector único para cada entrada. A layers.Embedding La layers.Embedding también funcionaría. Consulte la guía y el tutorial de capas de preprocesamiento para obtener más información sobre este tema.

for name, input in inputs.items():
  if input.dtype == tf.float32:
    continue
  
  lookup = preprocessing.StringLookup(vocabulary=np.unique(titanic_features[name]))
  one_hot = preprocessing.CategoryEncoding(max_tokens=lookup.vocab_size())

  x = lookup(input)
  x = one_hot(x)
  preprocessed_inputs.append(x)

Con la colección de inputs y processed_inputs , puede concatenar todas las entradas preprocesadas juntas y construir un modelo que maneje el preprocesamiento:

preprocessed_inputs_cat = layers.Concatenate()(preprocessed_inputs)

titanic_preprocessing = tf.keras.Model(inputs, preprocessed_inputs_cat)

tf.keras.utils.plot_model(model = titanic_preprocessing , rankdir="LR", dpi=72, show_shapes=True)

png

Este model solo contiene el preprocesamiento de entrada. Puede ejecutarlo para ver qué hace con sus datos. Los modelos de Keras no convierten automáticamente Pandas DataFrames porque no está claro si debe convertirse a un tensor oa un diccionario de tensores. Así que conviértalo en un diccionario de tensores:

titanic_features_dict = {name: np.array(value) 
                         for name, value in titanic_features.items()}

Divida el primer ejemplo de entrenamiento y páselo a este modelo de preprocesamiento, verá las características numéricas y los one-hots de cadena todos concatenados juntos:

features_dict = {name:values[:1] for name, values in titanic_features_dict.items()}
titanic_preprocessing(features_dict)
<tf.Tensor: shape=(1, 33), dtype=float32, numpy=
array([[-0.61 ,  0.395, -0.479, -0.497,  0.   ,  0.   ,  0.   ,  1.   ,

         0.   ,  0.   ,  0.   ,  0.   ,  1.   ,  0.   ,  0.   ,  0.   ,
         0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  1.   ,  0.   ,
         0.   ,  0.   ,  0.   ,  1.   ,  0.   ,  0.   ,  0.   ,  1.   ,
         0.   ]], dtype=float32)>

Ahora construya el modelo encima de esto:

def titanic_model(preprocessing_head, inputs):
  body = tf.keras.Sequential([
    layers.Dense(64),
    layers.Dense(1)
  ])

  preprocessed_inputs = preprocessing_head(inputs)
  result = body(preprocessed_inputs)
  model = tf.keras.Model(inputs, result)

  model.compile(loss=tf.losses.BinaryCrossentropy(from_logits=True),
                optimizer=tf.optimizers.Adam())
  return model

titanic_model = titanic_model(titanic_preprocessing, inputs)

Cuando entrene el modelo, pase el diccionario de entidades como x y la etiqueta como y .

titanic_model.fit(x=titanic_features_dict, y=titanic_labels, epochs=10)
Epoch 1/10
20/20 [==============================] - 0s 3ms/step - loss: 0.6973
Epoch 2/10
20/20 [==============================] - 0s 3ms/step - loss: 0.5960
Epoch 3/10
20/20 [==============================] - 0s 3ms/step - loss: 0.5384
Epoch 4/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4984
Epoch 5/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4712
Epoch 6/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4527
Epoch 7/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4423
Epoch 8/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4342
Epoch 9/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4288
Epoch 10/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4262

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

Dado que el preprocesamiento es parte del modelo, puede guardar el modelo y volver a cargarlo en otro lugar y obtener resultados idénticos:

titanic_model.save('test')
reloaded = tf.keras.models.load_model('test')
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Model.state_updates (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Layer.updates (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
INFO:tensorflow:Assets written to: test/assets
WARNING:tensorflow:5 out of the last 5 calls to <function recreate_function.<locals>.restored_function_body at 0x7f57b0154d90> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/tutorials/customization/performance#python_or_tensor_args and https://www.tensorflow.org/api_docs/python/tf/function for  more details.

features_dict = {name:values[:1] for name, values in titanic_features_dict.items()}

before = titanic_model(features_dict)
after = reloaded(features_dict)
assert (before-after)<1e-3
print(before)
print(after)
tf.Tensor([[-1.808]], shape=(1, 1), dtype=float32)
tf.Tensor([[-1.808]], shape=(1, 1), dtype=float32)

Usando tf.data

En la sección anterior, confió en la mezcla y el procesamiento por lotes de datos integrados del modelo mientras entrenaba el modelo.

Si necesita más control sobre la canalización de datos de entrada o necesita usar datos que no caben fácilmente en la memoria: use tf.data .

Para obtener más ejemplos, consulte la guía tf.data .

Activado en datos de memoria

Como primer ejemplo de aplicación de tf.data a datos CSV, considere el siguiente código para tf.data manualmente el diccionario de características de la sección anterior. Para cada índice, toma ese índice para cada característica:

import itertools

def slices(features):
  for i in itertools.count():
    # For each feature take index `i`
    example = {name:values[i] for name, values in features.items()}
    yield example

Ejecute esto e imprima el primer ejemplo:

for example in slices(titanic_features_dict):
  for name, value in example.items():
    print(f"{name:19s}: {value}")
  break
sex                : male
age                : 22.0
n_siblings_spouses : 1
parch              : 0
fare               : 7.25
class              : Third
deck               : unknown
embark_town        : Southampton
alone              : n

El tf.data.Dataset más básico en el cargador de datos de memoria es el constructor Dataset.from_tensor_slices . Esto devuelve un tf.data.Dataset que implementa una versión generalizada de la función de slices anterior, en TensorFlow.

features_ds = tf.data.Dataset.from_tensor_slices(titanic_features_dict)

Puede iterar sobre un tf.data.Dataset como cualquier otro iterable de Python:

for example in features_ds:
  for name, value in example.items():
    print(f"{name:19s}: {value}")
  break
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'

La función from_tensor_slices puede manejar cualquier estructura de diccionarios anidados o tuplas. El siguiente código crea un conjunto de datos de pares (features_dict, labels) :

titanic_ds = tf.data.Dataset.from_tensor_slices((titanic_features_dict, titanic_labels))

Para entrenar un modelo con este Dataset , deberá al menos shuffle y batch los datos.

titanic_batches = titanic_ds.shuffle(len(titanic_labels)).batch(32)

En lugar de pasar features y labels a Model.fit , pasa el conjunto de datos:

titanic_model.fit(titanic_batches, epochs=5)
Epoch 1/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4231
Epoch 2/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4220
Epoch 3/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4217
Epoch 4/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4212
Epoch 5/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4203

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

De un solo archivo

Hasta ahora, este tutorial ha funcionado con datos en memoria. tf.data es un conjunto de herramientas altamente escalable para crear canalizaciones de datos y proporciona algunas funciones para manejar la carga de archivos CSV.

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

Ahora lea los datos CSV del archivo y cree un tf.data.Dataset .

(Para obtener la documentación completa, consulte tf.data.experimental.make_csv_dataset )

titanic_csv_ds = tf.data.experimental.make_csv_dataset(
    titanic_file_path,
    batch_size=5, # Artificially small to make examples easier to show.
    label_name='survived',
    num_epochs=1,
    ignore_errors=True,)

Esta función incluye muchas características convenientes para que sea fácil trabajar con los datos. Esto incluye:

  • Usar los encabezados de columna como claves de diccionario.
  • Determinación automática del tipo de cada columna.
for batch, label in titanic_csv_ds.take(1):
  for key, value in batch.items():
    print(f"{key:20s}: {value}")
  print()
  print(f"{'label':20s}: {label}")
sex                 : [b'male' b'female' b'female' b'female' b'female']
age                 : [28. 22. 28. 35. 48.]
n_siblings_spouses  : [0 1 0 0 1]
parch               : [0 1 0 0 2]
fare                : [  7.312  29.      7.879 512.329  65.   ]
class               : [b'Third' b'Second' b'Third' b'First' b'Second']
deck                : [b'unknown' b'unknown' b'unknown' b'unknown' b'unknown']
embark_town         : [b'Southampton' b'Southampton' b'Queenstown' b'Cherbourg' b'Southampton']
alone               : [b'y' b'n' b'y' b'y' b'n']

label               : [0 1 1 1 1]

También puede descomprimir los datos sobre la marcha. Aquí hay un archivo CSV comprimido con gzip que contiene el conjunto de datos de tráfico interestatal metropolitano

Un atasco de trafico.

Imagen de Wikimedia

traffic_volume_csv_gz = tf.keras.utils.get_file(
    'Metro_Interstate_Traffic_Volume.csv.gz', 
    "https://archive.ics.uci.edu/ml/machine-learning-databases/00492/Metro_Interstate_Traffic_Volume.csv.gz",
    cache_dir='.', cache_subdir='traffic')
Downloading data from https://archive.ics.uci.edu/ml/machine-learning-databases/00492/Metro_Interstate_Traffic_Volume.csv.gz
409600/405373 [==============================] - 1s 2us/step

Ajuste el compression_type argumento para leer directamente desde el archivo comprimido:

traffic_volume_csv_gz_ds = tf.data.experimental.make_csv_dataset(
    traffic_volume_csv_gz,
    batch_size=256,
    label_name='traffic_volume',
    num_epochs=1,
    compression_type="GZIP")

for batch, label in traffic_volume_csv_gz_ds.take(1):
  for key, value in batch.items():
    print(f"{key:20s}: {value[:5]}")
  print()
  print(f"{'label':20s}: {label[:5]}")
holiday             : [b'None' b'None' b'None' b'None' b'None']
temp                : [271.6  286.1  268.77 254.46 276.37]
rain_1h             : [0. 0. 0. 0. 0.]
snow_1h             : [0. 0. 0. 0. 0.]
clouds_all          : [90 92 64 56 90]
weather_main        : [b'Snow' b'Clouds' b'Mist' b'Clouds' b'Clouds']
weather_description : [b'heavy snow' b'overcast clouds' b'mist' b'broken clouds'
 b'overcast clouds']
date_time           : [b'2012-11-23 20:00:00' b'2013-06-09 02:00:00' b'2013-11-08 06:00:00'
 b'2013-02-02 14:00:00' b'2012-12-06 20:00:00']

label               : [2543 1432 5638 4684 2983]

Almacenamiento en caché

El análisis de los datos csv conlleva cierta sobrecarga. Para los modelos pequeños, esto puede ser el cuello de botella en el entrenamiento.

Dependiendo de su caso de uso, puede ser una buena idea usar Dataset.cache o data.experimental.snapshot para que los datos csv solo se analicen en la primera época.

La principal diferencia entre los métodos de cache y de snapshot es que los archivos de cache solo pueden ser utilizados por el proceso de TensorFlow que los creó, pero los archivos de snapshot pueden ser leídos por otros procesos.

Por ejemplo, iterar sobre traffic_volume_csv_gz_ds 20 veces, toma ~ 15 segundos sin almacenamiento en caché o ~ 2 segundos con almacenamiento en caché.

%%time
for i, (batch, label) in enumerate(traffic_volume_csv_gz_ds.repeat(20)):
  if i % 40 == 0:
    print('.', end='')
print()
...............................................................................................
CPU times: user 16.3 s, sys: 3.78 s, total: 20.1 s
Wall time: 12.8 s

%%time
caching = traffic_volume_csv_gz_ds.cache().shuffle(1000)

for i, (batch, label) in enumerate(caching.shuffle(1000).repeat(20)):
  if i % 40 == 0:
    print('.', end='')
print()
...............................................................................................
CPU times: user 1.57 s, sys: 135 ms, total: 1.71 s
Wall time: 1.4 s

%%time
snapshot = tf.data.experimental.snapshot('titanic.tfsnap')
snapshotting = traffic_volume_csv_gz_ds.apply(snapshot).shuffle(1000)

for i, (batch, label) in enumerate(snapshotting.shuffle(1000).repeat(20)):
  if i % 40 == 0:
    print('.', end='')
print()
...............................................................................................
CPU times: user 2.28 s, sys: 418 ms, total: 2.7 s
Wall time: 1.76 s

Si la carga de datos se ralentiza al cargar archivos csv y la cache y la snapshot son insuficientes para su caso de uso, considere la posibilidad de volver a codificar sus datos en un formato más simplificado.

Varios archivos

Todos los ejemplos hasta ahora en esta sección podrían hacerse fácilmente sin tf.data . Un lugar donde tf.data realmente puede simplificar las cosas es cuando se trata de colecciones de archivos.

Por ejemplo, el conjunto de datos de imágenes de fuentes de caracteres se distribuye como una colección de archivos csv, uno por fuente.

Fuentes

Imagen de Willi Heidelbach en Pixabay

Descarga el conjunto de datos y echa un vistazo a los archivos que contiene:

fonts_zip = tf.keras.utils.get_file(
    'fonts.zip',  "https://archive.ics.uci.edu/ml/machine-learning-databases/00417/fonts.zip",
    cache_dir='.', cache_subdir='fonts',
    extract=True)
Downloading data from https://archive.ics.uci.edu/ml/machine-learning-databases/00417/fonts.zip
160317440/160313983 [==============================] - 8s 0us/step

import pathlib
font_csvs =  sorted(str(p) for p in pathlib.Path('fonts').glob("*.csv"))

font_csvs[:10]
['fonts/AGENCY.csv',
 'fonts/ARIAL.csv',
 'fonts/BAITI.csv',
 'fonts/BANKGOTHIC.csv',
 'fonts/BASKERVILLE.csv',
 'fonts/BAUHAUS.csv',
 'fonts/BELL.csv',
 'fonts/BERLIN.csv',
 'fonts/BERNARD.csv',
 'fonts/BITSTREAMVERA.csv']
len(font_csvs)
153

Cuando trabaje con un montón de archivos, puede pasar un file_pattern estilo glob a la función experimental.make_csv_dataset . El orden de los archivos se baraja en cada iteración.

Utilice el argumento num_parallel_reads para establecer cuántos archivos se leen en paralelo y se intercalan juntos.

fonts_ds = tf.data.experimental.make_csv_dataset(
    file_pattern = "fonts/*.csv",
    batch_size=10, num_epochs=1,
    num_parallel_reads=20,
    shuffle_buffer_size=10000)

Estos archivos csv tienen las imágenes aplanadas en una sola fila. Los nombres de las columnas tienen el formato r{row}c{column} . Aquí está el primer lote:

for features in fonts_ds.take(1):
  for i, (name, value) in enumerate(features.items()):
    if i>15:
      break
    print(f"{name:20s}: {value}")
print('...')
print(f"[total: {len(features)} features]")
font                : [b'MONOTYPE' b'MV_BOLI' b'MAIANDRA' b'BODONI' b'VLADIMIR' b'MAIANDRA'
 b'MAIANDRA' b'JUICE' b'CAMBRIA' b'RICHARD']
fontVariant         : [b'MONOTYPE CORSIVA' b'MV BOLI' b'MAIANDRA GD'
 b'BODONI MT POSTER COMPRESSED' b'VLADIMIR SCRIPT' b'MAIANDRA GD'
 b'MAIANDRA GD' b'JUICE ITC' b'CAMBRIA' b'POOR RICHARD']
m_label             : [ 1059  1946   710   200   120   246  8211   224 10694   733]
strength            : [0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4]
italic              : [0 0 0 1 0 0 1 0 0 0]
orientation         : [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
m_top               : [38 61 38 20 61 40 67 35 46 41]
m_left              : [25 16 27 21 12 23 25 22 24 26]
originalH           : [52 33 10 62 19 45  4 46 38 10]
originalW           : [57 39 20 34 37 35 35 21 39 22]
h                   : [20 20 20 20 20 20 20 20 20 20]
w                   : [20 20 20 20 20 20 20 20 20 20]
r0c0                : [  1   1   1   1   1   1   1   1 255   1]
r0c1                : [  1   1   1   1   1   1 255   1 255   1]
r0c2                : [  1   1   1   1   1   1 255  16 255   1]
r0c3                : [  1   1   1   1   1   1 255 111 255   1]
...
[total: 412 features]

Opcional: campos de embalaje

Probablemente no desee trabajar con cada píxel en columnas separadas como esta. Antes de intentar usar este conjunto de datos, asegúrese de empaquetar los píxeles en un tensor de imagen.

Aquí hay un código que analiza los nombres de las columnas para crear imágenes para cada ejemplo:

import re

def make_images(features):
  image = [None]*400
  new_feats = {}

  for name, value in features.items():
    match = re.match('r(\d+)c(\d+)', name)
    if match:
      image[int(match.group(1))*20+int(match.group(2))] = value
    else:
      new_feats[name] = value

  image = tf.stack(image, axis=0)
  image = tf.reshape(image, [20, 20, -1])
  new_feats['image'] = image

  return new_feats

Aplique esa función a cada lote del conjunto de datos:

fonts_image_ds = fonts_ds.map(make_images)

for features in fonts_image_ds.take(1):
  break

Trace las imágenes resultantes:

from matplotlib import pyplot as plt

plt.figure(figsize=(6,6), dpi=120)

for n in range(9):
  plt.subplot(3,3,n+1)
  plt.imshow(features['image'][..., n])
  plt.title(chr(features['m_label'][n]))
  plt.axis('off')

png

Funciones de nivel inferior

Hasta ahora, este tutorial se ha centrado en las utilidades de más alto nivel para leer datos csv. Hay otras dos API que pueden ser útiles para usuarios avanzados si su caso de uso no se ajusta a los patrones básicos.

Esta sección recrea la funcionalidad proporcionada por make_csv_dataset , para demostrar cómo se puede utilizar esta funcionalidad de nivel inferior.

tf.io.decode_csv

Esta función decodifica una cadena o lista de cadenas en una lista de columnas.

A diferencia de make_csv_dataset esta función no intenta adivinar los tipos de datos de las columnas. Usted especifica los tipos de columna proporcionando una lista de record_defaults contiene un valor del tipo correcto, para cada columna.

Para leer los datos del Titanic como cadenas usando decode_csv , diría:

text = pathlib.Path(titanic_file_path).read_text()
lines = text.split('\n')[1:-1]

all_strings = [str()]*10
all_strings
['', '', '', '', '', '', '', '', '', '']
features = tf.io.decode_csv(lines, record_defaults=all_strings) 

for f in features:
  print(f"type: {f.dtype.name}, shape: {f.shape}")
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)

Para analizarlos con sus tipos reales, cree una lista de record_defaults de los tipos correspondientes:

print(lines[0])
0,male,22.0,1,0,7.25,Third,unknown,Southampton,n

titanic_types = [int(), str(), float(), int(), int(), float(), str(), str(), str(), str()]
titanic_types
[0, '', 0.0, 0, 0, 0.0, '', '', '', '']
features = tf.io.decode_csv(lines, record_defaults=titanic_types) 

for f in features:
  print(f"type: {f.dtype.name}, shape: {f.shape}")
type: int32, shape: (627,)
type: string, shape: (627,)
type: float32, shape: (627,)
type: int32, shape: (627,)
type: int32, shape: (627,)
type: float32, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)

tf.data.experimental.CsvDataset

La clase tf.data.experimental.CsvDataset proporciona una interfaz de Dataset CSV mínima sin las características convenientes de la función make_csv_dataset : análisis de encabezado de columna, inferencia de tipo de columna, barajado automático, intercalado de archivos.

El siguiente constructor usa record_defaults la misma manera que io.parse_csv :

simple_titanic = tf.data.experimental.CsvDataset(titanic_file_path, record_defaults=titanic_types, header=True)

for example in simple_titanic.take(1):
  print([e.numpy() for e in example])
[0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n']

El código anterior es básicamente equivalente a:

def decode_titanic_line(line):
  return tf.io.decode_csv(line, titanic_types)

manual_titanic = (
    # Load the lines of text
    tf.data.TextLineDataset(titanic_file_path)
    # Skip the header row.
    .skip(1)
    # Decode the line.
    .map(decode_titanic_line)
)

for example in manual_titanic.take(1):
  print([e.numpy() for e in example])
[0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n']

Varios archivos

Para analizar el conjunto de datos de fuentes con experimental.CsvDataset , primero debe determinar los tipos de columna para record_defaults . Empiece por inspeccionar la primera fila de un archivo:

font_line = pathlib.Path(font_csvs[0]).read_text().splitlines()[1]
print(font_line)
AGENCY,AGENCY FB,64258,0.400000,0,0.000000,35,21,51,22,20,20,1,1,1,21,101,210,255,255,255,255,255,255,255,255,255,255,255,255,255,255,1,1,1,93,255,255,255,176,146,146,146,146,146,146,146,146,216,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,141,141,141,182,255,255,255,172,141,141,141,115,1,1,1,1,163,255,255,255,255,255,255,255,255,255,255,255,255,255,255,209,1,1,1,1,163,255,255,255,6,6,6,96,255,255,255,74,6,6,6,5,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255

Solo los dos primeros campos son cadenas, el resto son enteros o flotantes, y puede obtener el número total de características contando las comas:

num_font_features = font_line.count(',')+1
font_column_types = [str(), str()] + [float()]*(num_font_features-2)

El constructor CsvDatasaet puede tomar una lista de archivos de entrada, pero los lee secuencialmente. El primer archivo de la lista de CSV es AGENCY.csv :

font_csvs[0]
'fonts/AGENCY.csv'

Entonces, cuando pasa la lista de archivos a CsvDataaset los registros de AGENCY.csv se leen primero:

simple_font_ds = tf.data.experimental.CsvDataset(
    font_csvs, 
    record_defaults=font_column_types, 
    header=True)
for row in simple_font_ds.take(10):
  print(row[0].numpy())
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'

Para intercalar varios archivos, utilice Dataset.interleave .

Aquí hay un conjunto de datos inicial que contiene los nombres de los archivos csv:

font_files = tf.data.Dataset.list_files("fonts/*.csv")

Esto baraja los nombres de archivo de cada época:

print('Epoch 1:')
for f in list(font_files)[:5]:
  print("    ", f.numpy())
print('    ...')
print()

print('Epoch 2:')
for f in list(font_files)[:5]:
  print("    ", f.numpy())
print('    ...')
Epoch 1:
     b'fonts/REFERENCE.csv'
     b'fonts/ONYX.csv'
     b'fonts/BODONI.csv'
     b'fonts/CALIFORNIAN.csv'
     b'fonts/SIMPLEX.csv'
    ...

Epoch 2:
     b'fonts/BANKGOTHIC.csv'
     b'fonts/BELL.csv'
     b'fonts/HARRINGTON.csv'
     b'fonts/IMPRINT.csv'
     b'fonts/EUROROMAN.csv'
    ...

El método de interleave toma un map_func que crea un Dataset de Dataset map_func para cada elemento del Dataset .

Aquí, desea crear un CsvDataset partir de cada elemento del conjunto de datos de archivos:

def make_font_csv_ds(path):
  return tf.data.experimental.CsvDataset(
    path, 
    record_defaults=font_column_types, 
    header=True)

El Dataset devuelto por intercalación devuelve elementos al recorrer un número de Dataset secundarios. Observe, a continuación, cómo el conjunto de datos cycle_length)=3 tres archivos de fuentes:

font_rows = font_files.interleave(make_font_csv_ds,
                                  cycle_length=3)
fonts_dict = {'font_name':[], 'character':[]}

for row in font_rows.take(10):
  fonts_dict['font_name'].append(row[0].numpy().decode())
  fonts_dict['character'].append(chr(row[2].numpy()))

pd.DataFrame(fonts_dict)

Actuación

Anteriormente, se señaló que io.decode_csv es más eficiente cuando se ejecuta en un lote de cadenas.

Es posible aprovechar este hecho, cuando se utilizan lotes de gran tamaño, para mejorar el rendimiento de carga de CSV (pero intente almacenar en caché primero).

Con el cargador integrado 20, los lotes de 2048 ejemplos tardan alrededor de 17 segundos.

BATCH_SIZE=2048
fonts_ds = tf.data.experimental.make_csv_dataset(
    file_pattern = "fonts/*.csv",
    batch_size=BATCH_SIZE, num_epochs=1,
    num_parallel_reads=100)
%%time
for i,batch in enumerate(fonts_ds.take(20)):
  print('.',end='')

print()
....................
CPU times: user 27.4 s, sys: 1.67 s, total: 29.1 s
Wall time: 10.7 s

Pasar lotes de líneas de texto a decode_csv ejecuta más rápido, en aproximadamente 5 decode_csv :

fonts_files = tf.data.Dataset.list_files("fonts/*.csv")
fonts_lines = fonts_files.interleave(
    lambda fname:tf.data.TextLineDataset(fname).skip(1), 
    cycle_length=100).batch(BATCH_SIZE)

fonts_fast = fonts_lines.map(lambda x: tf.io.decode_csv(x, record_defaults=font_column_types))
%%time
for i,batch in enumerate(fonts_fast.take(20)):
  print('.',end='')

print()
....................
CPU times: user 5.27 s, sys: 0 ns, total: 5.27 s
Wall time: 4.75 s

Para ver otro ejemplo de cómo aumentar el rendimiento de csv mediante el uso de lotes grandes, consulte el tutorial de sobreajuste y ajuste .

Este tipo de enfoque puede funcionar, pero considere otras opciones como la cache y la snapshot , o reenncoding sus datos en un formato más simplificado.