Explorando datasets en TensorFlow: tf.keras y tfds para tus Proyectos 🚀

Por Administrador

En el anterior post vimos algunas plataformas que podemos utilizar para obtener datasets, una de ellas fue Tensorflow. Por ello en este post vamos a ver como cargar distintos datasets con este framework, para que puedas usarlos en tus experimentos. Así qué, ¡Vamos a por ello!. 

En Tensorflow podemos acceder a distintos datasets mediante: tf.keras.datasets o tfds. A continuación veremos como hacerlo con cada una de estas modulos.

Nota: Recuerda haber instalado Tensorflow con pip install tensorflow.

1. ⭐ tf.keras.datasets

tf.keras.datasets es un módulo de TensorFlow que proporciona acceso a una serie de conjuntos de datos populares, listos para su uso en experimentos de aprendizaje automático y deep learning.

A nuestra disposición tenemos 8 datasets: boston_housing, california_housing, cifar10, cifar100, fashion_mnist, imdb, mnist y reuters. Veamos como acceder a ellos:

California housing

El California Housing Dataset es un conjunto de datos de regresión obtenido del repositorio StatLib, basado en el censo de EE.UU. de 1990. Contiene 20,640 muestras con 8 características que describen condiciones socioeconómicas y geográficas de distintos distritos en California. La variable objetivo es el valor medio de las viviendas en dólares. Las características incluyen el ingreso medio, la edad media de las casas, el número promedio de habitaciones y dormitorios por hogar, la población total, el promedio de ocupantes por hogar, así como la latitud y longitud del distrito.

Para cargar este dataset tendremos que dirigirnos a su documentación, y veremos las opciones de configuración:

  • Versión («small» o «large»): Podemos elegir entre dos tamaños del dataset. La versión «small» con 600 muestras, mientras que la versión «large» con 20,640 muestras.
  • Ruta (path): Especifica la carpeta donde se guardará el dataset en tu computadora.
  • División de prueba (test_split): Indica qué porcentaje de los datos se reservará para pruebas. Por ejemplo, si elegimos 0.2, significa que el 20% de los datos se usarán como conjunto de prueba y el resto para entrenamiento.
  • Semilla aleatoria (seed): Número utilizado para mezclar los datos antes de separar el conjunto de prueba. Si usamos el mismo número de semilla cada vez, los datos se dividirán siempre de la misma manera, esto es ideal para resultados reproducibles.

Como salida obtendremos: (x_train, y_train), (x_test, y_test). Que es una tupla de arrays de numpy, con las características y variable objetivo para el conjunto de entrenamiento y de prueba.

Entonces para cargar el dataset lo haremos de la siguiente manera:

import tensorflow as tf

(x_train, y_train), (x_test, y_test)= tf.keras.datasets.california_housing.load_data(
    version='large',
    test_split=0.2,
    seed=113)

# Verificar las dimensiones de los datos
print("Datos de entrenamiento:", x_train.shape, y_train.shape)
print("Datos de prueba:", x_test.shape, y_test.shape)

Si ejecutamos podremos ver que se descargará el conjunto de datos, y al momento de imprimir obtendremos 16512 datos para entrenamiento y 4128 para prueba. De hecho si queremos acceder a cada una de sus muestras y targets podemos hacerlo a partir del uso de sus índices: x_train[0], y_train[0].

MNIST

El MNIST Dataset es un conjunto de datos ampliamente utilizado en el aprendizaje automático, que contiene 60,000 imágenes en escala de grises de tamaño 28×28 píxeles, representando los dígitos del 0 al 9 para entrenamiento, junto con 10,000 imágenes adicionales para pruebas.

Este dataset presenta opciones de configuración diferentes a las que vimos en el anterior ejemplo. En este caso solo podemos modificar la ruta en donde se almacenará el dataset una vez que se ha descargado.

En cuanto a la salida, obtendremos: (x_train, y_train), (x_test, y_test), al igual que con el dataset anterior, con las particiones de datos para entrenamiento y validación.

Para cargar este dataset tendemos que realizar lo siguiente:

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()

# Verificar las dimensiones de los datos
print("Datos de entrenamiento:", x_train.shape, y_train.shape)
print("Datos de prueba:", x_test.shape, y_test.shape)

Si ejecutamos, tendremos los datos listos para utilizar. Y recuerda que para acceder a cada una de sus muestras y etiquetas podemos hacerlo a partir del uso de sus índices.

Los datasets disponibles en tf.keras.datasets son una excelente opción para introducirnos en el mundo del deep learning, ya que son fáciles de cargar, bien conocidos y permiten experimentar con redes neuronales sin necesidad de preprocesamiento adicional. Sin embargo, estos datasets tienen ciertas limitaciones. Por ejemplo, muchos de ellos son pequeños y simplificados en comparación con los datos del mundo real, lo que puede llevar a modelos que no generalicen bien fuera del entorno de prueba. Para trabajar con una mayor variedad de conjuntos de datos, podemos utilizar TensorFlow Datasets (TFDS), esto lo veremos en el siguiente apartado.

2. ⭐TensorFlow Datasets (tfds)

tensorflow_datasets (TFDS) es un módulo de TensorFlow que proporciona acceso a una gran variedad de datasets listos para usar en tareas de machine learning y deep learning. Es una alternativa más completa y amplia a tf.keras.datasets, ya que incluye datasets en diferentes formatos, como imágenes, texto, audio o series temporales.

Cabe destacar que uno de los problemas al que nos enfrentarnos al cargar los datasets, es que estos se alojan en la memoria RAM, y si son muchísimos datos, puede que esta colapse. Una ventaja muy importante al usar TFDS, es que permite cargar los datos poco a poco, sin necesidad de almacenarlos por completo en la RAM. Esto se logra gracias a la integración con la API tf.data, que permite leer los datos en batches pequeños, optimizando el uso de memoria y mejorando el rendimiento.

Para trabajar con TFDS, tendremos que instalarlo, para ello existen dos formas de hacerlo:

Versión estable (se actualiza cada pocos meses): pip install tensorflow-datasets

Versión «nightly» (se actualiza diariamente con las últimas versiones de los datasets): pip install tfds-nightly

Una vez instalado podremos imprimir todos los datasets disponibles mediante:

import tensorflow_datasets as tfds

tfds.list_builders() # Listar todos los datasets disponibles

Y para conocer el número de datasets podemos usar: len(tfds.list_builders()). En total contamos con 1296 datasets.

Wine quality

En esta ocasión vamos a tomar el dataset Wine Quality para explorar la carga del mismo mediante TFDS, pero antes de ello veamos de qué se trata.

El dataset Wine Quality contiene dos conjuntos de datos basados en muestras de vinos tintos y blancos del Vinho Verde portugués. Cada muestra se describe mediante 11 características fisicoquímicas, como: acidez, azúcar residual, pH, alcohol y dióxido de azufre, entre otras. La variable objetivo es un puntaje de calidad (de 0 a 10), determinado a partir de evaluaciones sensoriales realizadas por expertos en vino.

Este dataset ha sido utilizado en problemas de regresión y clasificación, y en estudios previos, los Support Vector Machines (SVMs) han obtenido los mejores resultados al modelar la calidad del vino. Contiene 1,599 muestras de vino tinto y 4,898 de vino blanco. Es un dataset ampliamente utilizado en ciencia de datos para experimentar con modelos de machine learning supervisado.

Vamos a ver como cargar este dataset con distintas opciones de configuración, ¡empecemos!.

ds, ds_info = tfds.load("wine_quality", with_info=True)
print(ds)

Para cargar el dataset tendremos que usar tf.load, y especificar en primer lugar el nombre del mismo. Luego podemos establecer en True a la opción de configuración with_info que por defecto viene en False, entonces obtendremos:

  • ds que contiene los datos del dataset en un formato compatible con tf.data.Dataset: {'train': <_PrefetchDataset element_spec={'features': {'alcohol': TensorSpec(shape=(), dtype=tf.float32, name=None), 'chlorides': TensorSpec(shape=(), dtype=tf.float32, name=None), 'citric acid': TensorSpec(shape=(), dtype=tf.float32, name=None), 'density': TensorSpec(shape=(), dtype=tf.float32, name=None), 'fixed acidity': TensorSpec(shape=(), dtype=tf.float32, name=None), 'free sulfur dioxide': TensorSpec(shape=(), dtype=tf.float32, name=None), 'pH': TensorSpec(shape=(), dtype=tf.float32, name=None), 'residual sugar': TensorSpec(shape=(), dtype=tf.float32, name=None), 'sulphates': TensorSpec(shape=(), dtype=tf.float64, name=None), 'total sulfur dioxide': TensorSpec(shape=(), dtype=tf.float32, name=None), 'volatile acidity': TensorSpec(shape=(), dtype=tf.float32, name=None)}, 'quality': TensorSpec(shape=(), dtype=tf.int32, name=None)}>}
  • ds_info (solo obtendremos esta salida cuando with_info esté en True) almacena información adicional sobre el dataset, como el número de muestras, las características y la estructura de los datos: tfds.core.DatasetInfo( name='wine_quality', full_name='wine_quality/white/1.0.0', description=""" Two datasets were created, using red and white wine samples. The inputs include objective tests (e.g. PH values) and the output is based on sensory data (median of at least 3 evaluations made by wine experts). Each expert graded the wine quality between 0 (very bad) and 10 (very excellent). Several data mining methods were applied to model
ds = tfds.load("wine_quality", as_supervised=True)
print(ds)

Otra opción de configuración es as_supervised, la cual modifica la estructura del dataset para que los datos se devuelvan en forma de tuplas (entrada, etiqueta) en lugar de diccionarios. Esto es importante porque prepara el dataset en el formato adecuado para entrenar un modelo. Si ejecutamos tendremos lo siguiente: {'train': <_PrefetchDataset element_spec=({'alcohol': TensorSpec(shape=(), dtype=tf.float32, name=None), 'chlorides': TensorSpec(shape=(), dtype=tf.float32, name=None), 'citric acid': TensorSpec(shape=(), dtype=tf.float32, name=None), 'density': TensorSpec(shape=(), dtype=tf.float32, name=None), 'fixed acidity': TensorSpec(shape=(), dtype=tf.float32, name=None), 'free sulfur dioxide': TensorSpec(shape=(), dtype=tf.float32, name=None), 'pH': TensorSpec(shape=(), dtype=tf.float32, name=None), 'residual sugar': TensorSpec(shape=(), dtype=tf.float32, name=None), 'sulphates': TensorSpec(shape=(), dtype=tf.float64, name=None), 'total sulfur dioxide': TensorSpec(shape=(), dtype=tf.float32, name=None), 'volatile acidity': TensorSpec(shape=(), dtype=tf.float32, name=None)}, TensorSpec(shape=(), dtype=tf.int32, name=None))>}

Pero a lo mejor te estarás preguntando, ¿cómo puedo saber cuantos elementos tiene el dataset?, porque hasta ahora solo habíamos tenido esta información en su propia documentación. Para ello podemos usar:

print(f"Número de elementos: {tf.data.experimental.cardinality(ds['train']).numpy()}")

Tendremos que especificar ds[‘train’], ya que es la única partición que tenemos de este dataset, y si imprimimos tendremos 4898 elementos que corresponden al vino blanco.

Ahora vamos a explorar otra opción de configuración, split.

ds_train1 = tfds.load("wine_quality", split='train[:80%]', as_supervised=True)
ds_test1 = tfds.load("wine_quality", split='train[80%:]', as_supervised=True)
print(f"Número de elementos: {tf.data.experimental.cardinality(ds_train1).numpy()}")
print(f"Número de elementos: {tf.data.experimental.cardinality(ds_test1).numpy()}")

El argumento split, permite dividir el dataset en subconjuntos específicos. En este caso, split='train[:80%]' asigna el 80% de los datos al conjunto de entrenamiento (ds_train1), mientras que split='train[80%:]' asigna el 20% restante al conjunto de prueba (ds_test1). De esta manera, se logra una división personalizada de los datos sin necesidad de hacerlo manualmente. Luego, con tf.data.experimental.cardinality() extraemos el número de elementos por cada partición. Y mucho ojo, que ahora ya no especificamos la partición train para extraer el número de elementos, esto es porque ya lo especificamos en el argumento split.

Si ejecutamos tendremos 3918 datos para entrenamiento y 980 para prueba.

for features, label in ds_train1.take(5):
    print(features)
    print(label)
    print("-----")

Si deseamos explorar los elementos que posee el dataset, podemos hacerlo a través de un ciclo de repetición for. Por ejemplo del conjunto de entrenamiento tomamos los 5 primeros elementos, esto lo hacemos con ds_train1.take(5). Veamos uno de ellos: {'alcohol': <tf.Tensor: shape=(), dtype=float32, numpy=9.0>, 'chlorides': <tf.Tensor: shape=(), dtype=float32, numpy=0.05400000140070915>, 'citric acid': <tf.Tensor: shape=(), dtype=float32, numpy=0.3400000035762787>, 'density': <tf.Tensor: shape=(), dtype=float32, numpy=1.0008000135421753>, 'fixed acidity': <tf.Tensor: shape=(), dtype=float32, numpy=7.599999904632568>, 'free sulfur dioxide': <tf.Tensor: shape=(), dtype=float32, numpy=44.0>, 'pH': <tf.Tensor: shape=(), dtype=float32, numpy=3.2200000286102295>, 'residual sugar': <tf.Tensor: shape=(), dtype=float32, numpy=18.350000381469727>, 'sulphates': <tf.Tensor: shape=(), dtype=float64, numpy=0.550000011920929>, 'total sulfur dioxide': <tf.Tensor: shape=(), dtype=float32, numpy=197.0>, 'volatile acidity': <tf.Tensor: shape=(), dtype=float32, numpy=0.3199999928474426>} tf.Tensor(5, shape=(), dtype=int32)

Ahora, ¿qué te parece si cargamos el conjunto de datos, lo desordenamos y luego realizamos las particiones tanto para el conjunto de entrenamiento como de prueba?

ds = tfds.load("wine_quality", split='train', as_supervised=True)
ds = ds.shuffle(buffer_size=100, seed=42)

total_examples = tf.data.experimental.cardinality(ds).numpy()

train_size = int(0.8 * total_examples)
#print(train_size)

ds_train = ds.take(train_size)
ds_test = ds.skip(train_size)

print(f"Número de elementos entrenamiento: {tf.data.experimental.cardinality(ds_train).numpy()}")
print(f"Número de elementos prueba: {tf.data.experimental.cardinality(ds_test).numpy()}")

En primer lugar, cargarmos el dataset y lo mezclamos aleatoriamente con shuffle(buffer_size=100, seed=42). El parámetro buffer_size define cuántos elementos se toman a la vez para desordenarlos, mientras que seed asegura que la mezcla sea reproducible en distintos experimentos, evitando sesgos en la distribución de los datos. Luego, calculamos el número total de ejemplos con tf.data.experimental.cardinality(). A partir de esto, se determina que el 80% de los datos se utilizará para entrenamiento (ds_train = ds.take(train_size)) y el 20% restante para prueba (ds_test = ds.skip(train_size)). Finalmente, se imprimen los tamaños de cada conjunto para verificar que la división se realizó correctamente, obteniendo 3,918 muestras para entrenamiento y 980 para prueba.

Finalmente veremos como usar el argumento batch_size:

ds = tfds.load("wine_quality", split='train', batch_size=16, as_supervised=True)

El argumento batch_size agrupa los datos en lotes, 16 en el ejemplo, en lugar de cargar todas las muestras a la vez. Esto es útil para optimizar el entrenamiento de modelos de deep learning.

tf.data.experimental.cardinality(ds).numpy()

Si exploramos cuantos elementos hay, tendremos 307. Esto se debe a que ahora el dataset está compuesto por batches y el resultado es el número de particiones o el número de grupos de 16 elementos.

De igual manera, si quisieramos visualizar cada elemento del dataset con ayuda de un ciclo de repetición for, como lo hicimos antes, este nos devolverá ya no elementos individuales, sino la información en batches o lotes. Por ejemplo si establecemos un batch size de 2, obtendríamos lo siguiente: {'alcohol': <tf.Tensor: shape=(2,), dtype=float32, numpy=array([ 9. , 12.2], dtype=float32)>, 'chlorides': <tf.Tensor: shape=(2,), dtype=float32, numpy=array([0.054, 0.063], dtype=float32)>, 'citric acid': <tf.Tensor: shape=(2,), dtype=float32, numpy=array([0.34, 0.49], dtype=float32)>, 'density': <tf.Tensor: shape=(2,), dtype=float32, numpy=array([1.0008, 0.9911], dtype=float32)>, 'fixed acidity': <tf.Tensor: shape=(2,), dtype=float32, numpy=array([7.6, 6.3], dtype=float32)>, 'free sulfur dioxide': <tf.Tensor: shape=(2,), dtype=float32, numpy=array([44., 35.], dtype=float32)>, 'pH': <tf.Tensor: shape=(2,), dtype=float32, numpy=array([3.22, 3.38], dtype=float32)>, 'residual sugar': <tf.Tensor: shape=(2,), dtype=float32, numpy=array([18.35, 1.2 ], dtype=float32)>, 'sulphates': <tf.Tensor: shape=(2,), dtype=float64, numpy=array([0.55000001, 0.41999999])>, 'total sulfur dioxide': <tf.Tensor: shape=(2,), dtype=float32, numpy=array([197., 92.], dtype=float32)>, 'volatile acidity': <tf.Tensor: shape=(2,), dtype=float32, numpy=array([0.32, 0.27], dtype=float32)>} tf.Tensor([5 6], shape=(2,), dtype=int32)

Y bien, hemos llegado al final de este tutorial, espero que les haya gustado. ¡Nos vemos en el siguiente!. Cuídate mucho, chao chao.