Transfer Learning: Modelos de reconocimiento facial de humanos aplicados al ganado

Enrique Blanco    13 noviembre, 2019

Hace unas semanas, en este artículo, realizamos una introducción a la reutilización de modelos de Deep Learning para proyectos propios haciendo uso de Transfer Learning. En este post iremos en más detalle con un pequeño proyecto que estamos desarrollando en Ideas Locas y presentaremos algo de código haciendo uso de Keras.

Introducción

Este proyecto tiene como misión identificar ganado (vacas, cerdos, etc.) haciendo uso de redes neuronales profundas convolucionales. Las capas convolucionales actúan como extractores de características.

Con la intención de ahorrar tiempo, se pretende hacer uso de modelos ya entrenados de Deep Learning dedicados a reconocimiento facial de seres humanos para la identificación de ganado.

Para ello, se realiza Transfer Learning y Fine-Tuning de los modelos de Oxford VGGFace a través del endpoint de TensorFlow. El código que se ha generado pero que todavía está pendiente de ser probado soporta tanto los modelos VGG16 como RESNET50 o SENET50:

from keras_vggface.vggface import VGGFace

# Based on VGG16 architecture 
vggface = VGGFace(model='vgg16') # or VGGFace() as default

# Based on RESNET50 architecture 
vggface = VGGFace(model='resnet50')

# Based on SENET50 architecture 
vggface = VGGFace(model='senet50')

Tanto en su versión:

include_top=False

que permiten quedarte sólo con la parte convolucional de la red para hacer un stacking superior de un clasificador a placer.

Como en la versión:

include_top=False

la cual incluye la parte de clasificación original con todas las capas densas, lo que lo hace más pesado.

Los pesos para los modelos se pueden obtener en el siguiente enlace.

Setup del entorno

Por sencillez, hacemos uso de Anaconda para la creación de un entorno virtual. Mediante un fichero requirements.txt es sencillo crear uno para evitar problemas de incompatibilidad entre paquetes.

$ conda create --name <env> --file requirements.txt

Ejemplo de uso

Data Cleaning (opcional)

El primer paso es trabajar con el dataset de imágenes. Nos encontramos en el caso en el que el número de imágenes es bajo y, además, pueden ser muy similares entre ellas. Para evitar la baja varianza entre imágenes se emplea la medida del índice de similitud estructural (SSIM) para medir la similitud entre fotografías. Esto ayuda a evitar datos muy similares (casi idénticos en las particiones de datos de validación y entrenamiento.

En los subdirectorios anidados de ./dataset se checkea una imagen contra todas las demás y se van eliminando aquellas que sean similares por encima de un valor de similitud (entre 0 y 1) indicado por el usuario.

$ python ssim.py --dir "dir/to/images" --threshold 0.9

Customización de arquitectura para Transfer Learning

En el script donde se lanza el entrenamiento, basado en vgg16, resnet50 o senet50 debemos acabar la red en un clasificador que, por defecto, tiene la siguiente implementación Sequential de Keras:

self.x = self.last_layer
self.x = Dense(self.hidden_dim_1, activation='relu', name='fc1')(self.x)
self.x = Dense(self.hidden_dim_2, activation='relu', name='fc2')(self.x)
self.x = Dense(self.hidden_dim_3, activation='relu', name='fc3')(self.x)
self.out = Dense(self.nb_class, activation='softmax', name='out')(self.x)

Clasificador de capas densas totalmente conectadas que se meten tras la capa flatten de los modelos convolucionales.

A continuación, se muestra un fragmento de código donde se define la arquitectura de VGG16:

    # Block 1
    x = Conv2D(64, (3, 3), activation='relu', padding='same', name='conv1_1')(
        img_input)
    x = Conv2D(64, (3, 3), activation='relu', padding='same', name='conv1_2')(x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='pool1')(x)

    # Block 2
    x = Conv2D(128, (3, 3), activation='relu', padding='same', name='conv2_1')(
        x)
    x = Conv2D(128, (3, 3), activation='relu', padding='same', name='conv2_2')(
        x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='pool2')(x)

    # Block 3
    x = Conv2D(256, (3, 3), activation='relu', padding='same', name='conv3_1')(
        x)
    x = Conv2D(256, (3, 3), activation='relu', padding='same', name='conv3_2')(
        x)
    x = Conv2D(256, (3, 3), activation='relu', padding='same', name='conv3_3')(
        x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='pool3')(x)

    # Block 4
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='conv4_1')(
        x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='conv4_2')(
        x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='conv4_3')(
        x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='pool4')(x)

    # Block 5
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='conv5_1')(
        x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='conv5_2')(
        x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='conv5_3')(
        x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='pool5')(x)

    if include_top:
        # Classification block
        x = Flatten(name='flatten')(x)
        x = Dense(4096, name='fc6')(x)
        x = Activation('relu', name='fc6/relu')(x)
        x = Dense(4096, name='fc7')(x)
        x = Activation('relu', name='fc7/relu')(x)
        x = Dense(classes, name='fc8')(x)
        x = Activation('softmax', name='fc8/softmax')(x)
    else:
        if pooling == 'avg':
            x = GlobalAveragePooling2D()(x)
        elif pooling == 'max':
            x = GlobalMaxPooling2D()(x)

Congelación de capas para Fine-Tuning

Normalmente, para Transfer Learning y Fine-Tuning de modelos con dataset pequeños, lo que se hace es congelar la arquitectura transferida y entrenar sólamente el clasificador customizado por nosotros.

  • nb_freeze = None indica que no se congela ninguna capa. Todas las capas son entrenables.
  • nb_freeze = 10 indica que se congelan las 10 primeras capas. Las restantes son entrenables por defecto.
  • nb_freeze = -4 indica que se congelan todas menos las 4 últimas capas. Las restantes son entrenables por defecto.

Dataset

El dataset sobre el que se desea entrenar debe situarse en la carpeta ./dataset. Para cada clase, se deben agrupar todas las imágenes en subdirectorios.

Los batches de entrenamiento, validación, así como el número de clases a predecir y, por lo tanto, la arquitectura de salida del modelo, están definidas tanto por el generador ImageDataGenerator() como por la función flow_from_directory() soportadas por Keras.

Sobre el dataset disponible se hace data augmentation:

  • Rotación aleatoria de cada imagen de hasta 20 grados;
  • Desplazamiento en altura y anchura de hasta el 5% de la dimensión de la imagen;
  • Horizontal flipping.

Logueo del entrenamiento

Para el entrenamiento se han definido dos callbacks:

  • EarlyStopping para evitar overfitting o alargar innecesariamente el tiempo de entrenamiento.
  • TensorBoard Callback que permite loguear precisión y funciones de pérdida para su visualización en browser de las curvas de aprendizaje y del grafo creado y entrenado.
$ cd logs/folder_tbd/
$ tensorboard --logdir=./ --port 6006

De manera que con sólo ir a tu navegador a http://localhost:6006/ se puede visualizar cómo ha transcurrido el entrenamiento. Ver el siguiente artículo para aclarar dudas sobre el uso de Tensorboard.

Testeo

De cara al testeo de un modelo ya entrenado con una imagen de muestra, se ejecuta el script testing.py:

$ python testing.py --granja test  --img "path/to/img"

Esta rutina devuelve las predicciones de una imagen en base a todas las clases soportadas por el modelo. La rutina devuelve:

$ python testing.py --granja test  --img "path/to/img"
{
    "class 0": score 0,
    "class 1": score 1,
    ...
    "class N": score N
}

Grad-CAM Checking

Un inconveniente frecuentemente citado del uso de redes neuronales es que entender exactamente lo que están modelando es muy difícil. Esto se complica aún más utilizando redes profundas. Sin embargo, esto ha comenzado a atraer una gran cantidad de interés de investigación, especialmente para las CNNs para garantizar que la “atención” de la red se centre en las características reales y discriminativas del propio animal, en lugar de otras partes de la imagen que puedan contener información discriminatoria (por ejemplo, una etiqueta de clase, una marca de tiempo impresa en la imagen o un borde sin interés).

Para comprobar que nuestro modelo se centra en partes interesantes de la imagen, se puede elegir una imagen de prueba y comprobar qué regiones son de interés para la red neuronal, se puede ejecutar:

$ python grad_CAM.py --granja test --model resnet50 --img "path/to/img"

Lo cual te devuelve un mapa de calor sobre la imagen de las regiones de interés. Aquí vemos un ejemplo generado a partir de un modelo que distingue entre vacas, osos y gatos. Con un mínimo entrenamiento es capaz de distinguir con un 98% de precisión entre especies en base a estructuras faciales.

Figura 1. Región de interés de la imagen de testeo para el modelo.

Sabemos que nuestro modelo ha aprendido bien porque se centra en la región del animal que nos interesa, y no en el fondo u otras partes carentes de relevancia.

Enlaces de Soporte e Interés:

En cuanto tengamos datasets más consistentes continuaremos trabajando en esta prueba de concepto para investigar la eficiencia del Transfer Learning en este tipo de campos.

Para mantenerte al día con LUCA, visita nuestra página web,  suscríbete a LUCA Data Speaks o síguenos en TwitterLinkedIn YouTube.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *