Cómo clasificar obras de arte por estilo en 7 líneas de código

Uso de fastai y técnicas avanzadas en el campo de la visión artificial (Parte 1)

Jaime Durán
MetaDatos
10 min readJun 3, 2019

--

Claude Monet — “Impression Sunrise”

Available in english too.

Motivación

Hace unos meses tuve la suerte de visitar una exposición conjunta sobre Claude Monet y Eugène Boudin. El primero, máximo exponente del Impresionismo, es uno de mis pintores favoritos (incluso le copié algún cuadro cuando era un chaval). Sorprendentemente no conocía nada del segundo, pero resulta que Monet fue discípulo suyo (empezó a pintar gracias a él), y ambos mantuvieron una larga relación de amistad, desencuentros, y admiración mutua por encima de todo.

En la exposición se podía comprobar la evolución de los dos pintores, desde un mismo punto de partida y misma temática, pero siguiendo distintos caminos, que con el tiempo volvieron a entrecruzarse. Resultado: una visita de casi 1 hora donde lo primero que hacías al ponerte delante de un nuevo cuadro era intentar adivinar su autor. ¡Pero no era nada sencillo!

Revisando la aplicación de redes neuronales convolucionales al reconocimiento de imágenes me vino a la cabeza aquella exposición. ¿Cómo de difícil resultaría para estas redes la tarea de reconocer al autor de un cuadro? ¿Será mucho más complicado que distinguir animales en fotografías? Pues nada; qué mejor que llevarlo a cabo para comprobarlo.

¿Y si aprovechando la ocasión creamos también un clasificador de estilos para poder categorizar las obras de arte presentes en el dataset? Y ya de paso os cuento cómo lograrlo con solamente 7 líneas de código usando fastai. ¡Vamos allá!

Introducción

Los datos

Para poder entrenar nuestra red neuronal necesitaremos las fotos de muchos cuadros. Por suerte contamos con un dataset de obras de arte recopilado de WikiArt para una competición de Kaggle. Cuenta con algo más de 100.000 cuadros pertenecientes a 2.300 artistas, cubriendo un montón de estilos y épocas. Todas las imágenes están etiquetadas en un fichero csv aparte, con información como la que se muestra en esta tabla:

Las herramientas

Para este pequeño proyecto vamos a utilizar la librería fastai, que funciona sobre PyTorch. Fastai simplifica el entrenamiento de redes neuronales facilitando la aplicación de técnicas punteras, de forma eficiente, y obteniendo resultados a la altura de los últimos avances en visión artificial, procesamiento del lenguaje natural, datos tabulares y filtrado colaborativo.

Creando nuestro clasificador de estilos

Como ya he avisado, lo primero que vamos a tratar de hacer con los datos, a modo de entrenamiento y por curiosidad, es crear un modelo para clasificar los cuadros por estilo. Contamos con dicha información en la columna style. Imprimimos todas las categorías para saber a lo que nos enfrentamos:

Nada más y nada menos que 136 estilos diferentes…

Ya tenemos el primer problema, ya que en mi opinión las categorías están demasiado atomizadas. Nos conviene usar sólo los estilos más repetidos en el dataset (en mi caso probé con 16 y con 25, obteniendo resultados similares).

Si usamos 25 categorías tendremos disponible un mínimo de 750 imágenes por cada una (como hay categorías que tienen muchas más podemos optar por balancear el dataset usando el mismo número para todas).

Aquí entra en juego la librería fastai. Con una única (larga) línea de código conseguiremos:

  • crear los subconjuntos de entrenamiento y validación a partir de un dataframe, siguiendo una estructura adecuada.
  • asignar una serie de transformaciones aleatorias para las imágenes (ds_tfms), que funcionan bastante bien en muchos escenarios (ver párrafo siguiente).
  • configurar el tamaño de las imágenes a introducir en nuestra red (size).
  • asignar el tamaño del batch (bs) para entrenamiento.
  • aplicar normalización; algo deseable siempre que vayamos a usar una red neuronal (normalize()).

El data augmentation (o aumentación de datos) es posiblemente la técnica de regularización más importante cuando se entrena un modelo para reconocimiento de imágenes. En lugar de alimentar nuestra red con las mismas imágenes una y otra vez, incluiremos pequeñas transformaciones aleatorias (un poco de rotación, traslación, zoom, etc.) que no cambien a simple vista lo que contiene la imagen, pero que sí modifiquen los valores de sus píxeles. Los modelos entrenados con esta técnica generalizarán mejor. En nuestro caso igual puede tener menos sentido que en otros escenarios, ya que los cuadros no se mueven, y las fotografias están centradas y sin espacio alrededor. Pero si pensamos en la posibilidad de poner nuestro modelo en producción, donde cualquiera pudiera enviar una imagen hecha con el móvil, seguro que vamos a querer aplicar esta técnica.

Imprimimos una pequeña muestra de las imágenes etiquetadas, para visionar el problema al que nos enfrentamos:

Creación y entrenamiento del modelo

Para llevar a cabo nuestra tarea, necesitamos elegir primero el tipo de red neuronal que vamos a usar, y la arquitectura subyacente.

Nos basaremos en una red neuronal convolucional (CNN), debido a su rendimiento contrastado en clasificación de imágenes. Para la arquitectura no partiremos de cero, sino que usaremos un modelo pre-entrenado sobre ImageNet (un dataset con más de 1 millón de imágenes), que ya sabe reconocer muchas cosas. Más concretamente probaremos ResNet-34 y ResNet-50 (el nº hace referencia a las capas). Estaremos por tanto aplicando lo aprendido con ImageNet a nuestra red (Transfer Learning), y tan sólo tendremos que añadir capas adicionales.

Para llevar a cabo la creación del modelo y su entrenamiento usaremos 2 etapas. En la primera etapa, con fastai tan sólo tendremos que ejecutar 2 líneas:

  • Primero instanciaremos un Learner especializado en CNN, especificando la arquitectura base (ResNet-50) y la métrica o métricas a obtener.
  • A continuación usaremos la función fit_one_cycle() del Learner, para llevar a cabo el entrenamiento de nuestro modelo usando un algoritmo muy eficiente para arquitecturas complejas (más sobre 1cycle). El parámetro principal es el número de epochs a ejecutar.

Tras esto, obtendremos los primeros resultados:

La idea de la segunda etapa es descongelar los pesos de las capas pertenecientes a la arquitectura de partida (los de ResNet-50), para volver a entrenar nuestro modelo completo. De esta forma podremos mejorar un poco más nuestro clasificador.

La clave en este punto está en elegir la tasa de aprendizaje máxima para las distintas capas del modelo. Para ello nos valemos de una gráfica que fastai se encarga de generar con 2 líneas:

Este es el resultado:

Decidiremos la tasa máxima para las primeras capas (una tasa pequeña, ya que no necesitan mucho ajuste). Para las últimas capas se suele elegir unas 10 veces menor que la usada en la primera fase (3e-3 por defecto), observando que no empeore el coste en la gráfica.

Descongelamos los pesos y volvemos a entrenar el modelo en 2 líneas:

Y ya está; con sólo 7 líneas de código habremos obtenido nuestro modelo y su evaluación:

La tasa de acierto de nuestro clasificador de estilo pictórico se queda en un 56,99%, que no está mal si tenemos en cuenta la dificultad de la tarea; decidir cuál de los 25 estilos es el adecuado. ¡Desde luego que a mí me supera!

Pasamos a analizar los errores para darle aún más valor a la tasa de acierto conseguida.

Resultados

La librería fastai nos proporciona varias herramientas para poder interpretar los resultados de nuestro modelo.

Podemos por ejemplo pintar cuáles son los errores más sonados del clasificador (incluso podríamos depurarlos usando una máscara de calor que nos dice en qué parte de cada imagen se fijó la red para tomar su decisión):

También podemos pintar la típica matriz de confusión, que nos permitirá observar el resumen de dónde se equivocó nuestro clasificador:

Existen varios estilos donde falla con mucha frecuencia. Pero fijémonos de momento dónde funciona bien. Es curioso, pero creo que coincido con la red neuronal en el estilo más sencillo de diferenciar: ¡el Ukiyo-e!

Vamos a visualizar ahora el top de equivocaciones entre estilos:

Así a primera vista no me extraña para nada la confusión entre:

  • Expresionismo abstracto e Informalismo (según puedo leer, son movimientos paralelos que ocurrieron en los años 40/50 en Estados Unidos y Europa respectivamente).
  • Neoclasicismo y Academicismo (para la Wikipedia en español son lo mismo; para la Wikipedia en inglés el segundo es una síntesis del primero y del Romanticismo).
  • Impresionismo, Post-Impresionismo y Expresionismo (aquí lo que me extraña es que no se confunda aún más).
  • Romanticismo y Realismo (el segundo sucedió al primero, diferenciándose sobre todo en la temática).
  • Barroco y Rococó (el segundo es una extensión del primero).
  • Los distintos estilos dentro del Renacimiento.

Si te interesa el tema, te invito a que busques un poco la diferencia entre los estilos de cada grupo ;)

Usando nuestro clasificador con nuevas imágenes

Una vez tenemos nuestro modelo entrenado, intentar predecir el estilo de una nueva imagen resulta sencillo. Usaremos las siguientes líneas de código:

Vamos allá…

Munch + Rick y Morty

El resultado del clasificador es bastante claro. Pero nosotros sabemos que el cuadro original de Munch pertenece al Expresionismo. ¿Qué tal si recortamos la imagen para dejar fuera el bicho enorme de arriba?

Munch + Morty

El recorte es clasificado dentro del Expresionismo, con más confianza que en su anterior decisión. En realidad se parece demasiado al original, así que no es de extrañar este resultado.

¿Y por qué no probar con uno de mis cuadros? Voy a escoger el único del que tengo una foto a mano (aviso: el cuadro es copia de otro).

En mi humilde opinión este cuadro no encaja dentro del Expresionismo, y el clasificador duda entre 2 estilos, con poca confianza en la decisión.

Quizás soy un artista demasiado ecléctico… xD

Conclusiones

No es nada sencillo categorizar determinadas obras por su estilo, y estoy seguro de que las personas que etiquetaron estos cuadros también tuvieron (y tendrían ahora mismo) sus dudas al hacerlo. Igual que no se puede etiquetar toda la obra de un pintor en una sola categoría, la delimitación entre estilos no es tan clara como puede ser en otros ámbitos, y hay pinturas que podrían perfectamente encajar bajo más de una etiqueta; así como categorías que yo creo que atienden más a formalismos que al propio estilo en sí.

Aún así los resultados conseguidos (56,99% de acierto) son muy buenos, comparando con lo que se consideraba estado-del-arte hace muy poco tiempo (2015, 2017), y teniendo en cuenta que no hemos hecho gran cosa para mejorar los datos de entrada (y prácticamente nada para modelar la red en sí misma, aunque esto es mérito de la librería fastai y del campo de la investigación en general).

Seguramente se podrían mejorar los resultados usando más imágenes, recortes de las mismas, categorías más generalistas, otras transformaciones que no sean las de por defecto, etc. Pero no era el propósito de este proyecto.

P.D.- En un entrenamiento posterior a la publicación del artículo conseguí un 57,91% de acierto, mejorando el resultado en un 1%.

Y después de comprobar lo complicado que resulta asignar un cuadro a un estilo determinado… ¿Qué tal si probamos ahora con nuestra intención inicial de reconocer al autor de una obra? ¿Obtendremos mejores resultados? Lo bueno es que en ese caso la etiqueta o variable dependiente no será para nada subjetiva :)

Pero creo que ya ha sido suficiente para un artículo; ¡eso será en el siguiente!

Ya disponible! pincha debajo 😃

--

--

Jaime Durán
MetaDatos

Yet another data scientist with a blog. In fact I write two (uno en español)