El objetivo de este artículo es implementar un sistema de clasificación utilizando deep learning (keras).
Se ha seleccionado un dataset descargado de kaggle.com (horse-or-human), que contiene dos clases (Caballos - Personas).
1027 imágenes en 2 clases. (Train)
256 imágenes en 2 clases. (Test)
Para más facilidad, este ejemplo en concreto lo he realizado en GoogleColab, una herramienta que está bastante bien si se quiere realizar experimentos no muy complejos.
En la realización de esta práctica se usan imágenes de Caballos y Personas. Éste es un problema que no es tan simple. De hecho, son muy pocos ejemplos para dar una solución lo mejor posible, pero si lo pensamos en la vida real pasa exactamente así, es por eso que este problema es bastante realista. Se trata de hacer un clasificador lo más acertado posible.
Gracias a que los modelos de aprendizaje profundo pueden ser reutilizables, en este caso usaremos un modelo de clasificación de imágenes y lo usaremos variando sus datos. De esta forma pretendo mejorar aún más el resultado final.
Preprocesamiento y aumento de datos
Para aprovechar al máximo nuestros pocos ejemplos de entrenamiento, los "aumentaremos". Realizaremos varias transformaciones aleatorias, y de esta manera, nuestro modelo nunca analice dos veces la misma imagen. Esto ayudará a evitar el sobreajuste y permitirá que el modelo se generalice mejor.
Data augmentation
El objetivo del aumento de datos es ampliar el conjunto de datos de entrenamiento aplicando diferentes transformaciones a las imágenes originales.
La transformación incluye: rotaciones, traducciones, cambio de escala, etc.
Ésto se realiza mediante la clase ImageDataGenerator.
Variables:
rotation_range: girar imágenes al azar (40).
width_shift y height_shift: traducir las imágenes al azar, vertical u horizontalmente (0.2, 0.2).
rescale: valor por el cual multiplicaremos los datos antes de cualquier otro procesamiento (1./255).
shear_range: para aplicar aleatoriamente transformaciones de cizallamiento.
zoom_range: para hacer zoom al azar dentro de las imágenes (0.2).
horizontal_flip: para voltear aleatoriamente la mitad de las imágenes horizontalmente (True).
fill_mode: estrategia utilizada para rellenar píxeles recién creados, que pueden aparecer después de una rotación o un cambio de ancho / altura.
Ej. Este es el resultado tras aplicar Data Augmentation a una imagen de ejemplo.
Estrategia principal
He decidido que mi estrategia para afrontar este problema será el uso de redes convolucionales y estará dividida en tres partes principales:
1ra – Entrenar una red convolucional desde el inicio.
2da – Uso de una red pre-entrenada (VGG16).
3ra – Se ajustarán las capas de la red pre-entrenada.
De esta forma, podré ir viendo la evolución en mi proceso a medida que se completan cada una de las partes.
Entrenando la red Convolucional desde el inicio
Al solo tener algunos ejemplos se debe prestar atención al sobreajuste. Antes vimos que con Data Augmentation podíamos en cierta manera generar más datos al problema, pero eso no es suficiente, ya que al hacer este tipo de prácticas, nuestras muestras aumentadas se encuentran muy relacionadas entre ellas.
El modelo que propongo es un convnet pequeño de pocas capas y pocos filtros por capa, junto con data augmentation y dropout ayudará a reducir el ajuste excesivo.
Primero creamos un modelo vacío de tipo Sequential. Este modelo se basa en crear una serie de capas de neuronas secuenciales.
El primer modelo: una pila simple de 3 capas de convolución con una activación RELU y seguida de max-pooling.
Encima de esto adiciono dos capas completamente conectadas.
Termino el modelo con una sola unidad y una activación sigmoide, que es la adecuada cuando queremos hacer clasificación binaria.
Para acompañarlo, también uso binary_crossentropy que se ocupará de entrenar el modelo.
La cantidad de iteraciones de aprendizaje (epochs) de entrenamiento son 50.
He decidido este número tras haber realizado de varias pruebas. Éste se ajusta bien teniendo en cuenta los resultados y la complejidad:
3 spochs 63%
100 epochs (61%-84%)
Muy buenos resultados en train con prácticamente un 100% en todas las iteraciones.
Sin embargo, realizando 50 iteraciones en test, se obtenía un menor porcentaje con lo que no merece la pena, considerando además la lentitud y el coste computacional.
Con 50 iteraciones trabaja bastante bien pero en modelos más grandes y complejos, necesitarán más iteraciones y a la vez será más lento el entrenamiento.
Como se muestra hay un total de 1.212.513 parámetros, y, tras varias pruebas se mantienen bastante constantes los resultados. De esta forma obtenemos una precisión de validación de 62%-84% tras hacer 50 Epoch. Éste no es un mal resultado si consideramos el tamaño de la muestra. En cualquier caso ha acertado más del 50% de los ejemplos en todas las iteraciones.
Usar las funciones de una red pre-entrenada
A continuación, haré uso de una red pre entrenada, que con anterioridad ha sido capacitada con un conjunto de datos de gran volumen. Esta red ya ha aprendido muchas características esenciales que son comunes en la mayoría de los problemas a los que nos podemos enfrentar.
Usaré la arquitectura VGG16, que me será de gran ayuda ya que hay características aprendidas que me pueden ser muy útiles para este problema. Esta arquitectura está entrenada con los datos de ImageNet.
La estrategia que usaré será instanciar solamente la parte convolucional del modelo. Lo haré hasta las capas totalmente conectadas y después, ejecutaré este modelo en mis datos.
Se muestra la arquitectura del modelo VGG16
En este caso se alcanza una precisión de validación de 83%-97%. Esto se debe, en parte, al hecho de que el modelo base se entrenó en un conjunto de datos que ya incluía personas y caballos (entre cientos de otras clases).
Se ajustarán las capas de la red pre-entrenada
Para mejorar todavía más el resultado anterior, ajustaré el último bloque convolucional del modelo VGG16 junto con el clasificador de nivel superior. El ajuste consiste en comenzar desde una red entrenada y luego, volver a entrenarlo en un nuevo conjunto de datos utilizando actualizaciones de muy poco peso.
Secuencia:
· Crear una instancia de la base convolucional de VGG16 y cargar sus pesos.
· Agregar nuestro modelo totalmente conectado, una vez enlazado en la parte superior se cargan sus pesos.
· Congelar las capas del modelo VGG16 hasta el último bloque convolucional.
Es importante tener en cuenta que todas las capas deben comenzar con pesos adecuadamente entrenados. Por ejemplo, no debe aplastar una red completamente conectada aleatoriamente inicializada sobre una base convolucional pre-entrenada. Esto se debe a que las actualizaciones de pesos inicializados aleatoriamente arruinarían los pesos aprendidos en la base convolucional.
He elegido ajustar el último bloque convolucional en lugar de toda la red para evitar el sobreajuste.
Como se sabe, las características aprendidas por los bloques convolucionales de bajo nivel, son más generales que las que se encuentran más arriba. En consecuencia, lógicamente, es mejor mantener los primeros bloques fijos e ir descongelando poco a poco a medida que vayamos viendo cómo evoluciona nuestra red. Por eso, empezaré descongelando solo el último bloque en primera instancia.
Esto ha de hacerse con una velocidad de aprendizaje lenta. Para ello, usaré el optimizador SGD que me asegura de no entorpecer las funciones aprendidas anteriormente.
De esta manera se obtiene una precisión de validación de 92% – 96% después de 50 epoich, lo cual denota bastante mejoría y es un resultado muy satisfactorio.
Tened en cuenta que podemos mejorar la solución descongelando un bloque más y aplicando una mayor regularización.
Comparación entre todos
Se denota la mejoría en la comparación entre los tres ejemplo de modelo analizados.
Probando el clasificador
Usaré el generador de validación para generar un lote de muestras y pasarlas al modelo.
A modo de interés, visualizaré las características que ha aprendido el convnet, observando cómo se transforma la entrada a medida que pasa por la red una imagen determinada. Cada fila de la figura es la salida de una capa y cada imagen que se encuentra en la fila, es un filtro específico en el mapa de características de salida (se ejecuta la imagen a través de la red y se va obteniendo todas sus representaciones intermedias. Esto se hace únicamente para las capas conv / maxpool, no para las capas completamente conectadas).
Como se muestra en la imagen, se va pasando de píxeles sin procesar de las imágenes a representaciones que se van haciendo cada vez más compactas. Las representaciones en sentido descendente muestran a que le presta más atención la red y se ve como cada vez se activan menos funciones. De este modo, en cada una de las iteraciones, las representaciones van llevando menos información sobre la imagen original. En otras palabras, se van quedando con la información más importante.
Si te ha servido no dudes en compartirlo y regálanos un "Me gusta", es la razón de ser de este blog. Puedes suscribirte y así no te perderás los posts que vayamos publicando.
¡Gracias y Buena suerte! ;)
Es un artículo muy interesante, que muestra todo el potencial que hay para tratamiento de imágenes. Muchas gracias 🤗 Enhorabuena 👏