mayo 25, 2021 10:00 am

Jesús

Hoy te traigo un artículo bastante práctico en el que exploraremos algunas de las novedades introducidas en la versión 2 de TensorFlow, el framework por antonomasia de deep learning.

En particular, entrenaremos un clasificador de flores utilizando una red neuronal convolucional.

Al final de este artículo habrás aprendido:

  • Cómo descargar conjuntos de datos directamente desde TensorFlow.
  • Cómo crear pipelines de preparación de datos.
  • Cómo definir un modelo usando el Sequential API de Keras/TensorFlow.
  • Cómo llevar a cabo aumento de datos usando las capas experimentales de TensorFlow 2.

Creación del Ambiente de Desarrollo con Anaconda

¡No, no es un error! A partir de ahora usaremos Anaconda como nuestro manejador de ambientes por defecto.

¿A qué se debe esta decisión? Como sabrás, mis artículos típicamente funcionan en Mac y en Ubuntu (es decir, en sistemas basados en Unix) porque son los sistemas operativos a los que tengo más fácil acceso.

En otras palabras, no brindo soporte a Windows… ¡Hasta ahora!

Con el fin de proveer una experiencia más homogénea para todos los seguidores de DataSmarts, independientemente del SO que usen, he optado por usar Anaconda, puesto que considero que se alinea con esta meta, ya que es una herramienta más completa y madura que virtualenv.

Instalación de Anaconda

Si no tienes Anaconda instalado en tu máquina, descárgalo y sigue las instrucciones descritas aquí.

Es posible que tengas que reiniciar sesión para que tu sistema reconozca la instalación.

Una vez te hayas cerciorado de que Anaconda funciona correctamente, puedes seguir adelante con el tutorial. Por lo pronto, veamos la estructura del proyecto:

C:\USERS\JESUS\DATASMARTS\TF2-FLOWER-CLASSIFIER\DATASMARTS
    flower_classifier.py

Como ves, el único archivo relevante es datasmarts/flower_classifier.py, pues allí vive la implementación de nuestro programa.

A continuación encuentras la descripción del ambiente de Anaconda del proyecto, en formato YAML:

Para crear un ambiente con esta misma configuración, solo debes correr este comando:

conda env create -f env.yml

Después, actívalo como sigue:

conda activate tf2-flower-classifier

Voila! Eso es todo. Procedamos a la siguiente sección.

Clasificación de Flores con TensorFlow 2

Abre el archivo datasmarts/flower_classifier.py e inserta estas líneas para importar las dependencias del script:

Debido a que es fundamental entender de manera visual el paulatino aprendizaje de la red durante su entrenamiento, implementaremos la función auxiliar plot_model_history(), la cual, como su nombre indica, toma la historia de entrenamiento de una red, y produce un gráfico comparativo de las curvas de pérdida y exactitud (accuracy) de la misma, tanto en el conjunto de entrenamiento como de validación:

Como verás más adelante, entender estas gráficas es una gran forma de determinar si una red sufre de sobreajuste u overfitting.

La data que usaremos se halla en DATASET_URL. Es un conjunto de alrededor de 3700 fotos de flores divididas en estas cinco categorías:

  • daisy (Margarita).
  • dandelion (Diente de León).
  • roses (Rosas).
  • sunflowers (Girasoles).
  • tulips (Tulipanes)

Para evitar descargar innecesariamente los datos más de una vez, los cachearemos en la ubicación especificada en CACHE_LOCATION:

Usaremos la función tf.keras.utils.get_file() para descargar y descomprimir los datos en el directorio flower_photos, dentro del CACHE_LOCATION:

Contamos el número de imágenes en el conjunto de datos y lo mostramos en pantalla:

Ahora, definimos las siguientes constantes del programa:

  • BATCH_SIZE: La cantidad de imágenes que la red procesará a la vez.
  • IMAGE_HEIGHT: Altura de las imágenes pasadas a la red.
  • IMAGE_WIDTH: Ancho de las imágenes pasadas a la red.

Una de las grandes novedades de TensorFlow 2.0 es el API tf.data.Dataset, ya que nos permite escribir de forma declarativa y funcional complejos e intrincados flujos de entrada de datos, típicamente compuestos de múltiples transformaciones.

Todo comienza definiendo una fuente u origen, como en este caso, donde especificamos mediante la función tf.keras.preprocessing.image_dataset_from_directory() que obtendremos los datos del dataset directamente desde una estructura de directorios en disco, tanto para el conjunto de validación como de entrenamiento:

Imprimimos las clases del conjunto de datos:

Mostramos una muestra aleatoria de nueve imágenes:

A continuación, sacamos provecho de la expresividad del API tf.data.Dataset de TensorFlow para:

  • Cachear el los datos, mediante el método .cache(). Esto hace que subsecuentes iteraciones sobre este conjunto de datos sean más rápidas, ya que las imágenes se hallarán en memoria, no en disco.
  • Crea un buffer de 1000 imágenes (.buffer(1000)) del que se extraerán, con reemplazo, muestras aleatorias que serán pasadas a la red.
  • Mientras el buffer actual se está procesando, TensorFlow irá precargando el siguiente grupo de imágenes (.prefetch()).

En la primera parte de este programa crearemos una simple red neuronal estructurada así:

  • La capa de entrada reescalará cada imagen al rango [0, 1].
  • Tendremos tres grupos de convoluciones, activadas con ReLU, seguidas de MaxPooling2D, con las siguientes dimensiones: 16, 32 y 64 filtros, respectivamente.
  • Dos capas densas, la última compuesta por 5 neuronas, correspondientes a las cinco clases en nuestro problema.

Compilamos el modelo. Usaremos Adam como optimizador y tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) como función de pérdida. 

Debido a que las neuronas de salida de la red no fueron activadas con Softmax, como suele ser el caso, pasamos from_logits=True a SparseCategoricalCrossentropy.

Mostramos la estructura del modelo:

Entrenamos el modelo por 10 iteraciones, y visualizamos las respectivas curvas de entrenamiento y validación:

La red anterior es un tanto ingenua, por lo que sufre de overfitting. Para combatir esta problemática, usaremos dos técnicas:

  1. 1
    Aumento de datos.
  2. 2
    Dropout.

Otra de las cosas chéveres que TensorFlow 2.0 tiene para nosotros, es la posibilidad de especificar qué transformaciones queremos llevar a cabo mediante simples capas, en vez de tener que usar un objeto especial, como antiguamente en Keras (ImageDataGenerator):

A continuación mostramos un ejemplo de las transformaciones:

La red es la misma que la pasada, con estas salvedades:

  • Realizamos aumento de datos al principio de la misma.
  • Añadimos una capa de Dropout entre las capas convoluciones y las densas para combatir el overfitting.

Compilamos y mostramos el modelo:

Entrenamos el modelo, esta vez por 15 iteraciones:

Finalmente, mostramos las curvas pertinentes:

Corre este comando para dar vida al programa:

python datasmarts/flower_classifier.py

Si tienes una GPU, el programa debería culminar en 2 o 3 minutos. Si, en cambio, tienes acceso únicamente a una CPU, tendrás que esperar alrededor de 1 minuto por epoch, lo que se traduce en unos 25-30 minutos en total. Dicho esto, sea cual sea la espera, verás en pantalla una imagen como esta, donde se muestran algunas de las fotos que componen el conjunto de datos:

También verás este mensaje, que nos indica que contamos con 3670 imágenes en total, de las cuales 734 se usarán para validar los modelos:

Found 3670 files belonging to 5 classes.
Using 734 files for validation.

La estructura de la red se puede ver a continuación:

Y las curvas de dicho modelo, aquí:

La enorme divergencia entre el accuracy y la pérdida de entrenamiento y validación es un contundente indicio de que sufrimos de overfitting. Afortunadamente, la segunda red es más robusta a esta situación. Esta es su estructura:

¿Ves que es más pequeña que la anterior? ¡Aproximadamente 4 millones de parámetros versus más de 16 millones!

Al entrenarla y analizar sus curvas, notamos que esta segunda red es más eficaz, ya que la distancia que separa las curvas de entrenamiento y de validación es mucho menor que en el caso anterior, ubicándose el pico del accuracy de entrenamiento en 80.2%, y el de validación en 73.6%. Razonable.

Resumen

Hoy aprendimos a entrenar un simple clasificador de flores usando TensorFlow 2.0.

Aunque esta tarea puede resultar, a priori, sencilla, nos fue de gran utilidad para conocer varias de las atractivas nuevas características de TensorFlow 2, como:

  • El API tf.data.Dataset, que facilita la definición de complejos flujos de transformación de datos mediante una interfaz funcional altamente expresiva.
  • Las capas experimentales que nos permiten realizar aumento de datos de forma natural e idiomática directamente en la estructura de la red, sin tener que recurrir a un objeto aparte, como sucede en Keras.

Con un poco de experimentación y unos tantos ajustes pudimos mejorar nuestro primero intento de clasificador, ya que sufría de overfitting, lo que se traduce en una pobre capacidad de generalizar.

Al incorporar dropout y el aumento de datos, mitigamos el sobreajuste, obteniendo un razonable 73.6% de accuracy en los datos de validación.

Nada mal, ¿eh?

Puedes descargar el código de este artículo aquí:

¡Adiós!

Sobre el Autor

Jesús Martínez es el creador de DataSmarts, un lugar para los apasionados por computer vision y machine learning. Cuando no se encuentra bloggeando, jugando con algún algoritmo o trabajando en un proyecto (muy) cool, disfruta escuchar a The Beatles, leer o viajar por carretera.

Paso 2/2: Celebra tu NUEVO EMPLEO en Machine Learning ?

A %d blogueros les gusta esto: