Ayuda a proteger la Gran Barrera de Coral con TensorFlow en Kaggle Únete Challenge

Cargar datos CSV

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHubDescargar 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 el entrenamiento.

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

Configuración

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

En los datos de la 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 abulón .

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

Aquí es 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",
    names=["Length", "Diameter", "Height", "Whole weight", "Shucked weight",
           "Viscera weight", "Shell weight", "Age"])

abalone_train.head()

El conjunto de datos contiene un conjunto de mediciones de la oreja de mar , un tipo de caracol de mar.

una concha de abulón

“Cáscara del olmo” (por Nicki Dugan Pogue , CC BY-SA 2.0)

La tarea nominal de este conjunto de datos es predecir la edad 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 ]])

A continuación, haga un modelo de regresión para predecir la edad. Puesto que sólo hay un único tensor de entrada, un keras.Sequential modelo es suficiente aquí.

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

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

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

abalone_model.fit(abalone_features, abalone_labels, epochs=10)
Epoch 1/10
104/104 [==============================] - 1s 2ms/step - loss: 57.8799
Epoch 2/10
104/104 [==============================] - 0s 2ms/step - loss: 11.6617
Epoch 3/10
104/104 [==============================] - 0s 2ms/step - loss: 8.5956
Epoch 4/10
104/104 [==============================] - 0s 2ms/step - loss: 8.0663
Epoch 5/10
104/104 [==============================] - 0s 2ms/step - loss: 7.6160
Epoch 6/10
104/104 [==============================] - 0s 2ms/step - loss: 7.2284
Epoch 7/10
104/104 [==============================] - 0s 2ms/step - loss: 6.9368
Epoch 8/10
104/104 [==============================] - 0s 2ms/step - loss: 6.7287
Epoch 9/10
104/104 [==============================] - 0s 2ms/step - loss: 6.5694
Epoch 10/10
104/104 [==============================] - 0s 2ms/step - loss: 6.4730
<keras.callbacks.History at 0x7fd1a0579ed0>

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 de Keras 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 = layers.Normalization()

A continuación, se utiliza el Normalization.adapt() método para adaptar la capa de la normalización de los datos.

normalize.adapt(abalone_features)

Luego usa la capa de normalización en tu 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: 92.6760
Epoch 2/10
104/104 [==============================] - 0s 2ms/step - loss: 54.4503
Epoch 3/10
104/104 [==============================] - 0s 2ms/step - loss: 17.1807
Epoch 4/10
104/104 [==============================] - 0s 2ms/step - loss: 5.9306
Epoch 5/10
104/104 [==============================] - 0s 2ms/step - loss: 5.0489
Epoch 6/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9627
Epoch 7/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9511
Epoch 8/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9162
Epoch 9/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9172
Epoch 10/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9027
<keras.callbacks.History at 0x7fd1a0286f90>

Tipos de datos mixtos

El conjunto de datos "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 en bruto puede ser fácilmente cargado como un pandas DataFrame , pero no es utilizable inmediatamente como entrada a un modelo 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 los rangos no se puede simplemente apilar las características en conjunto NumPy y pasarlo a un keras.Sequential modelo. Cada columna debe manejarse individualmente.

Como una opción, puede preprocesar sus datos sin conexión (usando cualquier herramienta que desee) para convertir columnas categóricas en columnas numéricas, 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 de Keras evitan este problema porque son parte del modelo.

En este ejemplo, se va a construir un modelo que implementa la lógica de decodificación previa usando Keras API funcional . También puede hacerlo por la subclasificación .

La API funcional opera con tensores "simbólicos". Los tensores "ansiosos" normales tienen un valor. Por el contrario, 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)

# Perform a calculation using the input
result = 2*input + 1

# the result doesn't have a value
result
<KerasTensor: shape=(None,) dtype=float32 (created by layer 'tf.__operators__.add')>
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 pre-procesamiento, iniciar la construcción de un conjunto de simbólicos keras.Input objetos, 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': <KerasTensor: shape=(None, 1) dtype=string (created by layer 'sex')>,
 'age': <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'age')>,
 'n_siblings_spouses': <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'n_siblings_spouses')>,
 'parch': <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'parch')>,
 'fare': <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'fare')>,
 'class': <KerasTensor: shape=(None, 1) dtype=string (created by layer 'class')>,
 'deck': <KerasTensor: shape=(None, 1) dtype=string (created by layer 'deck')>,
 'embark_town': <KerasTensor: shape=(None, 1) dtype=string (created by layer 'embark_town')>,
 'alone': <KerasTensor: shape=(None, 1) dtype=string (created by layer 'alone')>}

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 = layers.Normalization()
norm.adapt(np.array(titanic[numeric_inputs.keys()]))
all_numeric_inputs = norm(x)

all_numeric_inputs
<KerasTensor: shape=(None, 4) dtype=float32 (created by layer 'normalization_1')>

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

preprocessed_inputs = [all_numeric_inputs]

Para la cadena entradas utilizan el tf.keras.layers.StringLookup función para asignar cadenas a partir de índices enteros en un vocabulario. A continuación, el uso tf.keras.layers.CategoryEncoding para convertir los índices en float32 apropiada de datos para el modelo.

Los ajustes por defecto para el tf.keras.layers.CategoryEncoding capa de crear un vector de una sola caliente para cada entrada. Un layers.Embedding también funcionaría. Ver el pre-procesamiento guía capas y tutorial para más información sobre este tema.

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

  lookup = layers.StringLookup(vocabulary=np.unique(titanic_features[name]))
  one_hot = layers.CategoryEncoding(max_tokens=lookup.vocab_size())

  x = lookup(input)
  x = one_hot(x)
  preprocessed_inputs.append(x)
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:max_tokens is deprecated, please use num_tokens instead.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:max_tokens is deprecated, please use num_tokens instead.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:max_tokens is deprecated, please use num_tokens instead.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:max_tokens is deprecated, please use num_tokens instead.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:max_tokens is deprecated, please use num_tokens instead.

Con la colección de inputs y processed_inputs , puede concatenar todas las entradas preprocesados juntos, y construir un modelo que maneja el procesamiento previo:

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 pre-procesamiento de entrada. Puede ejecutarlo para ver qué hace con sus datos. Modelos Keras no se convierten automáticamente pandas DataFrames , ya que no está claro si se debe convertir en un tensor o un diccionario de los 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, 28), dtype=float32, numpy=
array([[-0.61 ,  0.395, -0.479, -0.497,  0.   ,  0.   ,  1.   ,  0.   ,

         0.   ,  0.   ,  1.   ,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ,
         0.   ,  0.   ,  0.   ,  1.   ,  0.   ,  0.   ,  0.   ,  1.   ,
         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 se entrena el modelo, pasar el diccionario de características como x , y la etiqueta como y .

titanic_model.fit(x=titanic_features_dict, y=titanic_labels, epochs=10)
Epoch 1/10
20/20 [==============================] - 1s 5ms/step - loss: 0.5580
Epoch 2/10
20/20 [==============================] - 0s 4ms/step - loss: 0.5019
Epoch 3/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4702
Epoch 4/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4495
Epoch 5/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4394
Epoch 6/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4340
Epoch 7/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4272
Epoch 8/10
20/20 [==============================] - 0s 5ms/step - loss: 0.4254
Epoch 9/10
20/20 [==============================] - 0s 5ms/step - loss: 0.4244
Epoch 10/10
20/20 [==============================] - 0s 5ms/step - loss: 0.4222
<keras.callbacks.History at 0x7fd2212c7fd0>

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')
2021-11-20 02:24:26.384348: W tensorflow/python/util/util.cc:368] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
INFO:tensorflow:Assets written to: test/assets
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.918]], shape=(1, 1), dtype=float32)
tf.Tensor([[-1.918]], 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 tubería de entrada de datos o la necesidad de utilizar datos que no encajan fácilmente en la memoria: el uso tf.data .

Para más ejemplos véase la guía tf.data .

Activado en los datos de la memoria

Como primer ejemplo de aplicación de tf.data de datos CSV considere el siguiente código al rebanada 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 más básico tf.data.Dataset en el cargador de datos de memoria es la Dataset.from_tensor_slices constructor. Esto devuelve una tf.data.Dataset que implementa una versión generalizada de los anteriores slices función, en TensorFlow.

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

Puede repetir un tf.data.Dataset como cualquier otro pitón iterable:

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'

El from_tensor_slices programa puede manejar cualquier estructura de diccionarios anidados o tuplas. El siguiente código hace que un conjunto de datos de (features_dict, labels) pares:

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

Para entrenar un modelo que utiliza este Dataset , necesitará 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 , se pasa el conjunto de datos:

titanic_model.fit(titanic_batches, epochs=5)
Epoch 1/5
20/20 [==============================] - 0s 5ms/step - loss: 0.4215
Epoch 2/5
20/20 [==============================] - 0s 5ms/step - loss: 0.4208
Epoch 3/5
20/20 [==============================] - 0s 5ms/step - loss: 0.4205
Epoch 4/5
20/20 [==============================] - 0s 5ms/step - loss: 0.4204
Epoch 5/5
20/20 [==============================] - 0s 5ms/step - loss: 0.4185
<keras.callbacks.History at 0x7fd22046cf50>

De un solo archivo

Hasta ahora, este tutorial ha funcionado con datos en memoria. tf.data es un conjunto de herramientas altamente escalable para la construcción de procesos de datos, y proporciona algunas funciones para hacer frente a 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
40960/30874 [=======================================] - 0s 0us/step

Ahora lee los datos CSV desde el archivo y crear 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'male' b'male' b'male' b'female']
age                 : [28. 70. 19.  6. 24.]
n_siblings_spouses  : [0 1 0 0 0]
parch               : [0 1 0 1 0]
fare                : [ 7.75  71.     7.775 12.475 13.   ]
class               : [b'Third' b'First' b'Third' b'Third' b'Second']
deck                : [b'unknown' b'B' b'unknown' b'E' b'unknown']
embark_town         : [b'Queenstown' b'Southampton' b'Southampton' b'Southampton' b'Southampton']
alone               : [b'y' b'n' b'y' b'n' b'y']

label               : [0 0 0 1 0]

También puede descomprimir los datos sobre la marcha. Aquí hay un archivo CSV gzip que contiene el conjunto de datos de tráfico de un estado a otro de metro

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 1us/step
417792/405373 [==============================] - 1s 1us/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                : [284.77 257.79 260.01 286.13 290.47]
rain_1h             : [0.   0.   0.   0.   0.25]
snow_1h             : [0. 0. 0. 0. 0.]
clouds_all          : [90 20 75  1 92]
weather_main        : [b'Mist' b'Clouds' b'Clouds' b'Clear' b'Rain']
weather_description : [b'mist' b'few clouds' b'broken clouds' b'sky is clear' b'light rain']
date_time           : [b'2012-10-25 17:00:00' b'2013-01-15 06:00:00' b'2012-12-24 08:00:00'
 b'2013-05-08 02:00:00' b'2013-08-12 06:00:00']

label               : [6488 5434 2174  298 5939]

Almacenamiento en caché

El análisis de los datos csv conlleva una sobrecarga. En el caso de modelos pequeños, este puede ser el cuello de botella en el entrenamiento.

Dependiendo de su caso de uso puede ser una buena idea utilizar Dataset.cache o data.experimental.snapshot para que los datos de CSV se analiza en la primera época.

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

Por ejemplo, iterar sobre los traffic_volume_csv_gz_ds 20 veces, toma ~ 15 segundos sin almacenamiento en caché, o ~ 2s con el 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.8 s, sys: 4.07 s, total: 20.8 s
Wall time: 12.7 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.46 s, sys: 243 ms, total: 1.7 s
Wall time: 1.34 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()
WARNING:tensorflow:From <timed exec>:1: snapshot (from tensorflow.python.data.experimental.ops.snapshot) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.snapshot(...)`.
...............................................................................................
CPU times: user 2.26 s, sys: 490 ms, total: 2.75 s
Wall time: 1.65 s

Si su carga de datos se hace más lenta por la carga de archivos CSV y cache y snapshot son insuficientes para su caso de uso, considere volver a codificar sus datos en un formato más ágil.

Varios archivos

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

Por ejemplo, las imágenes de fuentes de carácter conjunto de datos se distribuye como una colección de archivos CSV, uno por cada fuente.

Fuentes

Imagen de Willi Heidelbach de Pixabay

Descargue el conjunto de datos y eche 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
160325632/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 se trata de un grupo de archivos que puede pasar a un estilo de pegote file_pattern a la experimental.make_csv_dataset función. El orden de los archivos se baraja en cada iteración.

Usar la num_parallel_reads argumento para establecer cuántos archivos se leen en paralelo y entrelazados entre sí.

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 se formatean 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'NINA' b'FORTE' b'CALIFORNIAN' b'JAVANESE' b'FORTE' b'BAITI' b'HARLOW'
 b'NIRMALA' b'JAVANESE' b'NINA']
fontVariant         : [b'NINA' b'FORTE' b'CALIFORNIAN FB' b'JAVANESE TEXT' b'FORTE'
 b'MONGOLIAN BAITI' b'HARLOW SOLID ITALIC' b'NIRMALA UI SEMILIGHT'
 b'JAVANESE TEXT' b'NINA']
m_label             : [ 932  172   55  376  215 6156   65 7286   59  302]
strength            : [0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4]
italic              : [0 1 1 1 1 0 0 0 0 0]
orientation         : [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
m_top               : [38 45 36 45 40 41 34 44 76 38]
m_left              : [20 33 24 33 28 23 20 23 25 22]
originalH           : [49 23 47 59 34 33 60 49 40 62]
originalW           : [34 40 25 49 43 33 72 38 11 20]
h                   : [20 20 20 20 20 20 20 20 20 20]
w                   : [20 20 20 20 20 20 20 20 20 20]
r0c0                : [255   1  82   1   1   1   1   1   1 255]
r0c1                : [255 255 255   1   1   1   1   1   1 255]
r0c2                : [255 255 255   1   1   1   1  42   1 255]
r0c3                : [255 255 255   1   1   1   1 135  47 255]
...
[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 en el 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')
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/IPython/core/pylabtools.py:151: UserWarning: Glyph 64422 (\N{ARABIC LETTER HEH GOAL ISOLATED FORM}) missing from current font.
  fig.canvas.print_figure(bytes_io, **kw)

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 funcionalidad sección recrea proporcionada por make_csv_dataset , para demostrar cómo se puede utilizar esta funcionalidad 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 se trata de adivinar columna tipos de datos. Se especifican los tipos de columna, proporcionando una lista de record_defaults que contienen un valor del tipo correcto, para cada columna.

Para leer los datos Titanic como cadenas utilizando decode_csv que 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 analizar con sus tipos reales, crear 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

El tf.data.experimental.CsvDataset clase proporciona un mínimo CSV Dataset de interfaz sin las características de la conveniencia de la make_csv_dataset Función: análisis sintáctico columna de cabecera, la columna de tipo de inferencia, arrastrando los pies automática, archivo entrelazado.

Este constructor sigue los usos 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 las fuentes de datos utilizando experimental.CsvDataset , primero tiene que determinar los tipos de columna para las 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 CsvDatasaet constructor puede tomar una lista de archivos de entrada, pero los lee de forma secuencial. El primer archivo de la lista de CSV es AGENCY.csv :

font_csvs[0]
'fonts/AGENCY.csv'

Así que cuando se pasa la lista de archivos a CsvDataaset los registros de AGENCY.csv se leen en primer lugar:

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/CENTAUR.csv'
     b'fonts/JUICE.csv'
     b'fonts/COMIC.csv'
     b'fonts/SKETCHFLOW.csv'
     b'fonts/NIAGARA.csv'
    ...

Epoch 2:
     b'fonts/EUROROMAN.csv'
     b'fonts/YI BAITI.csv'
     b'fonts/LEELAWADEE.csv'
     b'fonts/GOUDY.csv'
     b'fonts/COMPLEX.csv'
    ...

La interleave método toma un map_func que crea un niño- Dataset para cada elemento del padre- Dataset .

En este caso, se desea crear un CsvDataset de cada elemento del conjunto de datos de los archivos:

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

El Dataset devuelto por elementos devuelve interleave ciclando durante un número de la niño- Dataset s. Tenga en cuenta, a continuación, cómo los ciclos de conjuntos de datos más cycle_length=3 tres ficheros:

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)

Rendimiento

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

Es posible tomar ventaja de este hecho, cuando se utilizan grandes tamaños de lote, para mejorar el rendimiento de carga CSV (pero trate de almacenamiento en caché en primer lugar).

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.74 s, total: 29.1 s
Wall time: 11.8 s

Pasando lotes de líneas de texto a decode_csv corre más rápidamente, en aproximadamente 5 años:

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 10.3 s, sys: 0 ns, total: 10.3 s
Wall time: 1.72 s

Para otro ejemplo de aumento de rendimiento en CSV mediante el uso de grandes lotes ver el sobreajuste y un tutorial underfit .

Este tipo de enfoque puede funcionar, pero considerar otras opciones como la cache y snapshot , o volver a codificar sus datos en un formato más ágil.