Desde www.ra.•ma.es podrá
descargar material adicional.
Emilio Salia Olivas • Pablo Rodríguez Belenguer
Ouiaue Gar<fa Vidal • Fran vaauer Esta.lr1ich
Juan Vicent camisón • Jo·rge, Vila Tomás
a-Ma® -
-
-
Desde www.ra.•ma.es podrá
descargar material adicional.
Emilio Salia Olivas • Pablo Rodríguez Belenguer
Ouiaue Gar<fa Vidal • Fran vaauer Esta.lr1ich
Juan Vicent camisón • Jo·rge, Vila Tomás
a-Ma® -
-
-
Informática
Inteligencia artificial
Casos prácticos con aprendizaje
profundo
Emilio Soria Olivas
Pablo Rodríguez Belenguer
Quique García Vida/
Fran Vaquer Estarich
juan Vicent Camisón
jorge Vi/a Tomás
Conocimiento o su alcance
Soria Olivas, Emilio eta/.
Inteligencia artificial. Casos prácticos con aprendizaje profundo/ Emilio Soria Olivas,
Pablo Rodríguez Belenguer, Quique García Vidal, Fran Vaquer Estarich, Juan Vicent
Camisón, Jorge Vila Tomás --. Bogotá: Ediciones de la U, 2022
336 p.; 24 cm
ISBN 978-958-792-440-4
e-ISBN 978-958-792-441-1
1. Informática 2. Programación 3. Aprendizaje profundo 4. Modelos neuronales l.
Tít.
621.39 ed.
Edición original publicada por© Editorial Ra-ma (España)
Edición autorizada a Ediciones de la U para Colombia
Área: Informática
Primera edición: Bogotá, Colombia, octubre de 2022
ISBN. 978-958-792-440-4
© Emilio Soria Olivas, Pablo Rodríguez Belenguer, Quique García Vidal, Fran Vaquer
Estarich, Juan Vicent Camisón, Jorge Vila Tomás
© Ra-ma Editorial. Calle Jarama, 3-A (Polígono Industrial lgarsa) 28860 Paracuellos de Jarama
www.ra-ma.es y www.ra-ma.com/ E-mail: editorial @ra-ma.com
Madrid, España
© Ediciones de la U - Carrera 27 #27-43 - Tel. (+57-1) 3203510-3203499
www.edicionesdelau.com - E-mail: editor@edicionesdelau.com
Bogotá, Colombia
Ediciones de la U es una empresa editorial que, con una visión moderna y estratégica de
las tecnologías, desarrolla, promueve, distribuye y comercializa contenidos, herramientas
de formación, libros técnicos y profesionales, e-books, e-learning o aprendizaje en línea,
realizados por autores con amplia experiencia en las diferentes áreas profesionales e
investigativas, para brindar a nuestros usuarios soluciones útiles y prácticas que contribuyan
al dominio de sus campos de trabajo y a su mejor desempeño en un mundo global,
cambiante y cada vez más competitivo.
Coordinación editorial: Adriana Gutiérrez M.
Carátula: Ediciones de la U
Impresión: DGP Editores SAS
Calle 63 #70D-34, Pbx (57+1) 320351O
Impreso y hecho en Colombia
Printedandmade in Colombia
No está permitida la reproducción total o parcial de este libro, ni su tratamiento informático, ni la
transmisión de ninguna forma o por cualquier medio, ya sea electrónico, mecánico, por fotocopia,
por registro y otros medios, sin el permiso previo y por escrito de los titulares del Copyright.
ÍNDICE
AGRADECIMIENTOS ........................................................................................................ 9
AUTORES ........................................................................................................................... 11
INTRODUCCIÓN AL LIBRO .......................................................................................... 13
CAPÍTULO l. INTRODUCCIÓN AL APRENDIZAJE PROFUNDO ......................... 15
1.1
EL SIGLO DE LOS DATOS ............................................................................... 15
1.2
ANÁLISIS DE LOS DATOS. ETAPAS .............................................................. 22
1.3
APRENDIZAJE MÁQUINA. TIPOS Y APLICACIONES ................................ 33
1.4
APRENDIZAJE PROFUNDO. BREVE HISTORIA .......................................... 38
1.5
BIBLIOGRAFÍA. ................................................................................................. 44
CAPÍTULO 2. MODELOS NEURONALES MULTIFUNCIÓN .................................. 45
2.1
NEURONA ARTIFICIAL. ELEMENTOS QUE LA FORMAN ....................... .45
2.2
PERCEPTRÓN. ALGORITMO DE APRENDIZAJE ....................................... .49
2.3
ADALINA. DESCENSO POR GRADIENTE. LMS .......................................... 53
2.4
ESTRUCTURAS ADAPTATIVAS. VARIANTES DEL LMS............................ 57
2.5
PERCEPTRÓN MULTICAPA. BACKPROPAGATION .................................... 62
2.6
VARIANTES DEL BACKPROPAGATION. ELECCIÓN DE LA
ARQUITECTURA............................................................................................... 68
2.7
APLICANDO EL PERCEPTRÓN MULTICAPA............................................... 73
2.7.1 Arquitectura ............................................................................................ 73
2.7.2 Modo de funcionamiento ....................................................................... 77
2.7.3 Función de coste ..................................................................................... 77
2.7.4 Sobreajuste (overfitting) ......................................................................... 78
2.7.5 Preprocesado de las entradas .................................................................. 80
6 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
2.7.6
© RA-MA
Problemas con estructuras profundas ..................................................... 81
2.8
MODELOS NEURONALES PARA CLUSTERING. SOM ............................... 82
2.9
LABORATORIO ................................................................................................. 85
2.10 BIBLIOGRAFÍA ............................................................................................... 103
CAPÍTULO 3. MODELOS NEURONALES ORIENTADOS A VISIÓN................... 105
3.1
PROBLEMAS DEL MLP EN IMÁGENES ...................................................... 105
3.2
ARQUITECTURA DE UNA CNN. PARTES ESENCIALES .......................... 107
3.3
ARQUITECTURAS FAMOSAS ....................................................................... 113
3.3.1 Modelos más relevantes que han participado en el ILSVRC ............... 114
3.4
AUMENTO DE DATOS Y TRANSFERENCIA DE APRENDIZAJE ............. 119
3.5
OTRAS APLICACIONES DE LAS CNN ........................................................ 121
3.5.1 Detección de objetos ............................................................................ 121
3.5.2 Segmentación de imágenes .................................................................. 127
3.5.3 Laboratorio ........................................................................................... 136
3.6
BIBLIOGRAFÍA................................................................................................ 191
CAPÍTULO 4. MODELOS NEURONALES ORIENTADOS A DATOS
TEMPORALES................................................................................................................. 195
4.1
DATOS TEMPORALES. CARACTERÍSTICAS ............................................. 195
4.2
MODELOS MULTICAPA RECURRENTES CLÁSICOS ............................... 204
4.3
REDES RECURRENTES (RNN) ..................................................................... 206
4.4
LONG-SHORT-TERM MEMORY (LSTM) ..................................................... 211
4.5
REDES GATED RECURRENT UNIT (GRU) ................................................. 215
4.6
APLICACIONES DE LAS REDES RECURRENTES ..................................... 216
4.7
LABORATORIO ............................................................................................... 219
4.8
BIBLIOGRAFÍA................................................................................................ 244
CAPÍTULO 5. MODELOS GENERATIVOS ................................................................ 245
5.1
INTRODUCCIÓN A LOS MODELOS GENERATIVOS ................................ 245
5.2
AUTOENCODERS ........................................................................................... 248
5.3
AUTOENCODERS VARIACIONALES ........................................................... 249
5.4
GAN (GENERATIVE ADVERSARIAL NETWORKS) .................................. 252
5.5
PROBLEMAS EN EL AJUSTE DE LAS GAN ................................................ 255
5.6
VARIACIONES DE LAS GAN ........................................................................ 259
5.7
LABORATORIO ............................................................................................... 267
5.8
BIBLIOGRAFÍA. ............................................................................................... 304
© RA-MA
ÍNDICE 7
CAPÍTULO 6. APRENDIZAJE REFORZADO ........................................................... 305
6.1
6.2
INTRODUCCIÓN AL APRENDIZAJE REFORZADO ................................... 306
ELEMENTOS MATEMÁTICOS A TENER EN CUENTA EN EL
APRENDIZAJE REFORZADO ........................................................................ 310
6.3
MÉTODOS DE APRENDIZAJE POR DIFERENCIAS TEMPORALES:
SARSA YQ-LEARNING.................................................................................. 314
6.4
APRENDIZAJE REFORZADO PROFUNDO ................................................. 317
6.5
6.6
LABORATORIO ............................................................................................... 323
BIBLIOGRAFÍA. ............................................................................................... 334
MATERIALADICIONAL ............................................................................................... 335
AGRADECIMIENTOS
EMILIO SORIA
Este libro va dedicado a todas las personas con las que me he cruzado
en mi vida, amigos, docentes/alummos, etc. y que me han conducido hasta aquí.
Especialmente a mifamilia que está ahí arriba cuidándome (mis padres y mi hermano
pequeño) y la de aquí abajo. A mis compañeros de libro que han sido compañeros de
fatigas, risas (¡alguna que otra vez de enfados, pero todos momentáneos!) y trabajo.
Y cierro con esa persona que hace que todos los días sean una nueva y apasionante
aventura ¡gracias Ruth por estar siempre ahí!
QUIQUE GARCÍA
El mayor error que podríamos cometer en la Era de la Inteligencia
Artificial es, paradójicamente, dejar fuera a las personas. Por ello, me gustaría
agradecer a mi gente más cercana, mis padres, hermana y amigos la paciencia que
tienen y el entendimiento por no dedicar, muchas veces, el tiempo que merecen.
Este libro también es parte vuestro. Pocas veces en la vida puede uno plasmar sus
agradecimientos en un libro en el que ha participado. Tampoco podría olvidarme de
la paciencia y la inmensa vocación de ayudar de Emilio Soria, compañero de libro,
referente y amigo. Sin tu ayuda no habría sido posible sacarlo adelante. Muchas
gracias.
10 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
FRANCISCO VAQUER
Agradecer a todas aquellas personas que han contribuido durante todos
mis años de formación a que haya sido capaz de llegar donde estoy a día de hoy,
y a mis amigos, los de toda la vida, ellos saben quiénes son, por estar ahí siempre.
En especial a mis padres y a mi hermano, que siempre me han apoyado en todas
las decisiones que he ido tomando con respecto a mi vida académica y profesional.
Y por último agradecer también a mis compañeros de libro y, especialmente, al
profesor y amigo Emilio Soria sin el cual esto no habría sido posible.
PABLO RODRÍGUEZ
Me gustaría agradecer a toda mi familia y amigos, pero en especial a
mi mujer Candela por ser la mejor compañera del viaje de la vida, a mis padres
porque me lo habéis dado todo, a mi hermana por ser un ejemplo diario de lucha y
superación, a mi abuela que sé que estarás muy orgullosa allá donde estés, a mis
suegros que me han tratado desde el primer día como a un hijo más y a mi mejor
amigo Pablo que es como un hermano, todos vosotros dais sentido a mi vida. A mis
compañeros de libro, pero en especial al profesor Emilio Soria con el que un email
me cambió la vida, gracias y profundamente gracias a todos.
JUAN VICENT
Quisiera agradecer especialmente a mis padres Juan y Luisa, mi familia y
mis amigos, por aguantarme, que no es nada sencillo, y a pesar de esto estar ahí.
También agradecer a la gente con la que he colaborado en la realización de este
libro, por lo mucho que he aprendido de cada uno de ellos; en especial, a Emilio
Soria. Sin él, esto no hubiera sido posible: por las grandes aportaciones tanto al
libro como a los autores.
JORGE VILA
Cualquier excusa es buena para agradecerle a la gente que más me importa
que hayan sido capaces de soportarme mientras trabajábamos en el libro. Gracias
infinitas a mi madre y a Nuria por estar ahí siempre, y a todos mis amigos por
apoyarme y darme ánimos en todo lo que hago. ¡ Y al resto de autores por su duro
trabajo! ¡Espero que leáis el libro!
AUTORES
Emilio Soria Olivas. Catedrático de Universidad, Licenciado en Físicas
y Doctor Ingeniero Electrónico. Es director del Máster en Ciencia de Datos y del
Máster en Inteligencia Artificial ambos de la Universidad de Valencia.
Pablo Rodríguez Belenguer. Licenciado en Farmacia por la Universidad
de Valencia. Máster en Investigación y Uso Racional del Medicamento por la
Universidad de Valencia, Máster en Ciencia de Datos por MBIT School y Máster en
Inteligencia Artificial Aplicada y Avanzada por la Universidad de Valencia.
Quique García Vidal. Graduado en Ingeniería Electrónica Industrial.
Máster en Ciencia de Datos por la Universidad de Valencia y Máster en Inteligencia
Artificial Aplicada y Avanzada por la Universidad de Valencia. Cofundador de
deepsense SL.
Fran Vaquer Estalrich. Graduado en Ingeniería Electrónica Industrial
por la Universidad de Valencia, Máster en Big Data Analytics por la Universidad
Politécnica de Valencia y Máster en Inteligencia Artificial Aplicada y Avanzada por
la Universidad de Valencia. Cofundador de deepsense SL.
Juan Vicent Camisón. Graduado en Física, Máster en Inteligencia Artificial
Aplicada y Avanzada, ambos por la Universidad de Valencia.
Jorge Vila Tomás. Graduado en Física y Máster en Inteligencia Artificial
Aplicada y Avanzada por la Universidad de Valencia.
INTRODUCCIÓN AL LIBRO
"Aprende a resolver todos los problemas que ya hayan sido resueltos."
Richard Feynman
Este libro tiene como objetivo introducir al lector, de una manera teórica y
práctica, en el mundo de los modelos neuronales artificiales profundos que constituyen
la base de la Inteligencia Artificial moderna en gran cantidad de aplicaciones como
es el reconocimiento de imágenes, la predicción de series temporales (llamadas a
call centers, demanda de productos, consumo eléctrico por poner unos ejemplos),
el descubrimiento y desarrollo de fármacos de manera artificial y un largo etcétera
que nos hace la vida más fácil. Es un campo con una gran aplicación y con un
crecimiento exponencial tanto en la parte teórica como en la parte práctica.
Todos los días (¡todos!) se publican más de un millar de trabajos en todo
el mundo en diferentes medios, medios digitales (como el famoso arxiv.org donde
todos los artículos que se publican están disponibles de forma gratuita), revistas
científicas, así como actas de congresos, blogs, etc. Sumado a esto las grandes
empresas tecnológicas invierten grandes sumas de dinero en conseguir los mejores
recursos materiales y personales que impactan en el número de publicaciones
anteriormente comentado. Todo esto hace que los avances en este campo sean
muy rápidos y, continuamente, se produzcan importantes cambios que, en algunas
ocasiones, ponen en duda el conocimiento anterior. Se recomienda al lector que
consulte el informe anual sobre la IA que publica la Universidad de Stanford y que
se encuentra disponible en la página web https://aiindex.stanford.edu/.
Intentar reflejar en un libro todos los avances producidos en los últimos
años supone escribir una enciclopedia (¡y se está hablando en un sentido literal!).
Nuestro objetivo es más modesto; suponiendo un conocimiento mínimo en Python
14 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
(que se puede adquirir en cualquier tutorial de Internet) se lleva al lector desde el
concepto de neurona artificial planteado en 1943 hasta las últimas aplicaciones de
modelos generativos. Este viaje supone dejarse en el camino algoritmos, técnicas y
aplicaciones; por ejemplo, en el libro no se trata el Procesado de Lenguaje Natural
porque para ello se necesitaría todo un libro y sólo se han dado pinceladas en el
capítulo de Aprendizaje Reforzado por la misma razón anteriormente expuesta.
Aún con estos elementos ausentes el viaje planteado es muy interesante, tendremos
bioseñales (filtros adaptativos aplicados en señales), imágenes (problemas de
segmentación y reconocimiento de imágenes), series temporales (intentaremos
predecir el futuro que, como decía el gran físico Niels Bohr, ¡es lo más difícil
de predecir!) y jugaremos con sistemas de IA en videojuegos entre muchas otras
cosas! Todas las prácticas que aparecen en este libro se encuentran en el repositorio
github/LIA2P/laboratorio y se pueden ejecutar en la nube mediante la herramienta
de Google Colab https://colab.research.google.com/ sin necesidad de instalarse
ningún programa a nivel local (y de forma gratuita aprovechando la infraestructura
de Google) si no se desea dicha instalación.
El enfoque que se le ha dado al libro es docente, en cada capítulo hay una
parte de teoría para pasar, posteriormente, a la parte de laboratorio donde se harán
diferentes prácticas que reflejarán lo aprendido en la primera parte por lo que su uso
en diferentes cursos es muy adecuado.
Y ahora estimado lector es momento de comenzar nuestro viaje desde el
mundo de las neuronas de Ramón y Cajal hasta los videojuegos conducidos por
IA ..... ¡empezamos!
1
INTRODUCCIÓN AL APRENDIZAJE
PROFUNDO
1.1 EL SIGLO DE LOS DATOS
Al siglo XXI se le puede considerar el siglo de los datos; esta consideración
nace de 4 palancas que han conducido a que la generación, almacenamiento,
procesado y extracción de conocimiento de ellos sea más fácil (y barato) que nunca:
11" PRIMERA PALANCA. Tenemos un aumento sin precedentes en la cantidad
de datos que manejamos apareciendo una necesidad de conservarlos
cuidadosamente para poder basar las decisiones en ellos. De hecho, es
dificil decir exactamente cuántos datos son generados a diario. Existen
una gran cantidad de infografías que muestran lo que ocurre en un minuto
en Internet; en el año 2021 (por minuto) se generaban 200000 tweets,
2 millones de visualizaciones en Twitch, se enviaban 197 millones de
correos electrónicos, 69 millones de mensajes de texto, 1.6 millones de
dólares que se gastaban en compras a través de la web; las cifras son
abrumadoras. Si se compara con años anteriores, no se para de crecer en la
generación de datos. Por poner un ejemplo comparando esta infografía de
datos generados del 2021 con la del 2020 en cuanto a descargas de Tik Tok
se tiene un incremento de 3600 descargas en un minuto que, pareciendo
poco, si se multiplica por los minutos que tiene un año se obtiene un
incremento de 1892 millones de descargas en un año. Además, hay que
tener en cuenta que el concepto de dato ha cambiado. El dato concebido
como número con un tamaño fijo ya no es ese tipo de dato conocido como
estructurado y ha dado paso al no estructurado; aquí se engloban los datos
16 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
de tipo texto (correos electrónicos, comentarios en TripAdvisor, vídeos
de Twitch, etc.); imágenes (que actualmente son el dato predominante en
determinadas redes) o vídeos (destacar el actual crecimiento de Tik Tok
comentado anteriormente). Según un análisis realizado por Raconteur
(una importante consultora en temas de analítica y datos) en septiembre
de 2021 alcanzaron los 180 zetabytes. Esta es una cantidad que sobrepasa
la escala humana y las clásicas comparaciones con cantidad de películas
que se podrían visualizar o alguna parecida deja de tener sentido. Este
aumento de datos generados tiene diferentes fuentes; la primera es el
aumento de los dispositivos que se conectan a la Internet de las Cosas
(IoT) en dispositivos vestibles (wearables), dispositivos inteligentes en
el hogar (tipo Alexa de Amazon) y en la industria (que conducirá a la
conocida como Industria 4.0) y, por supuesto, los smartphones que no
dejan de ser dispositivos avanzados de adquisición de datos. Por ejemplo,
se tienen las fotos y los vídeos que se toman en estos dispositivos móviles
y que se comparten en tiempo real. Con esta situación se ha acuñado un
término que ha tenido mucha publicidad en los últimos años, el famoso
Big Data. Este término se usa para aquellos datos que cumplen con las
famosas 3 V:
l. Volumen: Aquí se hace mención al tamaño de los datos considerados
de tal manera que su cantidad supone un desafío importante para los
sistemas de almacenamiento y procesamiento. Aunque no hay una
distinción específica sobre el volumen de datos, normalmente éste
puede variar desde los terabytes (10 12 bytes) hasta los exabytes (10 18
bytes).
2. Velocidad: Los datos se generan a un ritmo muy rápido. Esta alta
velocidad puede evaluarse por el hecho que, una gran proporción de
los datos que se utilizan actualmente, pertenecen al pasado reciente.
3. Variedad: Los datos considerados podrían obtenerse de numerosas
fuentes como registros web, dispositivos del Internet de las Cosas
(loT), URLs, tweets de usuarios y patrones de búsqueda, etc.
Asimismo, los datos pueden tener diferentes formatos, como valores
separados por comas ( CSV), tablas, documentos de texto y gráficos.
Además, podrían ser estructurados o no estructurados.
Esta cantidad ingente de información es imposible de procesar por
nosotros. Tenemos una forma de procesar la información definida por la
evolución y la naturaleza.No podemos pensar que podemos estar en todos
los procesos de generación y procesado de los datos; especialmente en
los datos creados por máquinas para ser consumidos por otras máquinas
© RA-MA
Capítulo l. INTRODUCCIÚN AL APRENDIZAJE PROFUNDO 17
(esto es, básicamente, lo que promete la Industria 4.0). Según el famoso
economista y premio Nobel Herbert Simon se tienen dos tendencias
antagónicas; por una parte, la riqueza de información que actualmente
tenemos y, por otra, que la poca atención que podemos tener por nuestra
parte. Por otra parte, nuestra capacidad de procesamiento está muy
por detrás de la velocidad de generación de los datos. Además, nuestra
capacidad para concentrarnos en información de diferentes fuentes al
mismo tiempo está destinada a fallar. Ahí están todos los estudios de
la Economía del Comportamiento que demuestran nuestros sesgos e
incapacidades para manejar grandes cantidades de información. Por todo
ello se hace necesario plantear sistemas automáticos para procesar la
información que, actualmente, tenemos disponible.
,,. SEGUNDA PALANCA. Es la potencia de procesamiento donde juega un
papel central la famosa ley de Moore que ha definido la evolución de los
sistemas electrónicos durante los últimos 50 años. Esta ley, definida por
Gordon Moore, actual presidente emérito de Intel, refleja que el número
de transistores en un circuito integrado se duplica, aproximadamente,
cada dos años. Esto supone que la capacidad de procesamiento de los
datos crece exponencialmente en el tiempo como lo pone de manifiesto
cualquier infografia que se consulte en Internet sobre este tema. Esta ley
se ha mantenido durante cerca de 40 años y ha supuesto la evolución de
los dispositivos usados para el procesado de los datos. Así han surgido
las unidades de procesamiento gráfico, o GPU, diseñadas originalmente
para acelerar los cálculos necesarios para representar gráficos de
alta resolución. A partir de la década de 1990, estas unidades fueron
especialmente importantes en las consolas de videojuegos de gama alta
(P laystation/Xbox). Estas unidades están optimizadas para trabajar con
imágenes y vídeos (matrices de datos básicamente) por lo que pueden
realizar un gran número de cálculos en paralelo rebajando el tiempo de
obtención de resultados considerablemente. El descubrimiento del uso
de estos dispositivos supuso una revolución en el campo y se empezó
a recurrir en masa a las GPU's, que rápidamente se convirtieron en el
principal elemento hardware para el procesamiento avanzado de datos.
Una evidencia económica de lo comentado es el incremento de valor de
la empresa NVIDIA (uno de los principales fabricantes de GPUs); entre
enero de 2012 y enero de 2020 las acciones de NVIDIA se dispararon
más de un 1500% y, otra, es la gran escasez de estos dispositivos hoy en
día. Dado el aspecto clave del procesado de datos, la investigación en
nuevos dispositivos no ha parado. Existen start-ups que están planteando
el diseño de los chips de una forma radicalmente nueva. En los últimos
años se está planteando un nuevo tipo de chip que se acerque más a la
18 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
imitación del cerebro. Estos diseños emergentes de chips conocidos
como neuromó,ji,cos implementan versiones de hardware de las neuronas
directamente en silicio.
11" TERCERA PALANCA. Es la computación en la nube (o cloud computing).
Este concepto aparece de la mano de Amazon (¡cómo no!) en 2006 lanzando
lo que se conoció como Amazon Web Services y que, actualmente, se
conoce por sus iniciales AWS. El conocimiento que se tenía en la empresa
en aquel entonces sobre centros de datos lo aplicó a la venta de recursos
en la nube para sus clientes. Actualmente, según un estudio reciente, un
94% de las organizaciones, desde corporaciones multinacionales hasta
pequeñas y medianas empresas, utilizan la computación en la nube.
La computación en la nube está haciendo que tanto el almacenamiento
como la capacidad de procesamiento estén ampliamente disponibles
para todo el mundo de modo que, cualquiera, puede acceder y hacer uso
de complejas herramientas de IA a un precio que podríamos calificar
de irrisorio. Actualmente si una start-up tiene una idea de negocio y
necesita hacer un procesamiento de sus datos, que pueden entrar dentro
de lo ya mencionado como Big Data, no tiene que hacer inversiones
millonarias en equipo hardware, sino que puede contratar por un bajo
precio el equipo necesario para llevar a cabo el procesamiento en la nube
y desarrollar el modelo necesario para su negocio. Se ahorran pues los
servidores y el salario de un equipo de especialistas para mantenerlos.
Actualmente Amazon, Microsoft, Google e IBM ofrecen acceso a sus
herramientas, permitiendo a las organizaciones cargar datos, entrenar
modelos y desplegar soluciones dentro de sus aplicaciones. Esto reduce
enormemente el tiempo (y el precio) que se tarda en pasar de la idea
al prototipo y al despliegue a nivel de producción. Además, se ofrecen
modelos ya desarrollados; por ejemplo, reconocedores de imágenes,
recomendadores web, buscadores, etc., son los llamados servicios
cognitivos.
11" CUARTA PALANCA. Aquí aparece el bajo coste (o incluso cero) de las
herramientas implicadas en el dato. Se ha hablado anteriormente de la
potencia de procesamiento, pero, si el coste de los equipos electrónicos
hubiese aumentado de forma proporcional a su capacidad, no tendríamos
las posibilidades actuales. Sin embargo, a diferencia de otros sectores
productivos, el incremento de las capacidades de los sistemas ha ido
acompañado de una bajada de precios también exponencial. Hay
un ejemplo clásico en la industria automovilística que comenta que
si los automóviles hubiesen tenido el mismo ritmo de crecimiento de
capacidades y el decrecimiento de precio como en la industria informática
© RA-MA
Capítulo l. lNTRODUCCIÚN AL APRENDIZAJE PROFUNDO 19
ahora mismo tendríamos un Ferrari a un precio menor de un euro. Otro de
los elementos clave en el desarrollo de las aplicaciones basadas en datos
es la información. Actualmente existen un gran número de blogs y páginas
web que ofrecen gran cantidad de información sobre técnicas avanzadas
de análisis de datos. Entre estas páginas web se pueden destacar dos;
por una parte, se encuentra arxiv.org, repositorio donde, continuamente,
se sube todo tipo de artículos, tutoriales, revisiones de una determinada
técnica, etc. Además, ahí se envían las primeras versiones de los trabajos
que se mandan a los diferentes congresos especializados por lo que es
un buen sitio para recopilar información sobre un determinado tema. La
segunda página a considerar es https://paperswithcode.com/ en la que
se tienen conjuntos de datos y artículos todos ordenados según temática
y según rendimiento. La gran ventaja de esta página es que se ofrecen
implementaciones de los algoritmos en diferentes lenguajes ( que, la
mayoría de las veces, es Python).
¿Por qué es importante el crecimiento de los datos? La respuesta es que son
el combustible de los sistemas de Inteligencia Artificial actuales; la siguiente figura
es la clave para entender por qué es tan importante todo lo anterior.
Inteligencia
Artificial
Machine
Learning
Intentan imitar
Métodos de IA
las
basados en modelos
capacidades
matemáticos
cognitivas de
avanzados
los humanos
Deep
Learning
Derivan del Machine
Learning pero todavía
son más complejos
Figura 1.1. Esquema de la relación entre Inteligencia Artificial, Machine Learning (Aprendizaje Máquina)
y Deep Learning (Aprendizaje Profundo).
A nivel histórico, el término Inteligencia Artificial (IA) nace de la mente
de John McCarthy; en el verano de 1956 organiza una conferencia sobre este
tema incipiente. Se trataba de una conferencia de dos meses de duración a la que
se invitó a las principales figuras del nuevo campo emergente. Los objetivos eran
ambiciosos y optimistas a la vez; la propuesta de la conferencia declaraba que
"se intentará encontrar la forma de hacer que las máquinas utilicen el lenguaje,
formen abstracciones y conceptos, resuelvan tipos de problemas ahora reservados
© RA-MA
20 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
a los humanos y se mejoren a sí mismas" y prometía que "se podía lograr un
avance significativo en uno o varios de estos problemas si un grupo de científicos
cuidadosamente seleccionados trabajan juntos en él durante un verano" (¡ esto se
conoce como exceso de optimismo!). Desde sus comienzos hasta principios de los
años 80 las aproximaciones seguidas en este campo son variadas (reglas, agentes,
lógica, etc.) y en esta década se empieza a plantear otra estrategia. Esta consiste en
considerar todo problema de IA como un ajuste de un modelo matemático avanzado
entre dos conjuntos de datos; por ejemplo, si planteamos un reconocedor de imágenes
se tendría un conjunto inicial de datos, las imágenes a reconocer, y un conjunto final,
las diferentes etiquetas codificadas de la mejor manera posible. Esta aproximación se
conoce como Aprendizaje Máquina (Machine Learning) o Aprendizaje Automático,
un subcampo de la IA dedicado a los algoritmos que aprenden de los datos. A modo
de ejemplo muy sencillo consideraremos un modelo que se utiliza para determinar
el número de pulsaciones máximas que se deben tener al salir a practicar deporte si
no queremos tener ningún tipo de problema. Podemos recopilar edades de diferentes
deportistas, eje X, y recoger sus pulsaciones máximas antes de tener problemas; se
tendría una gráfica como lo que aparece en la figura 1.2
Pulsaciones
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X X
X
Edad
Figura 1.2. Datos recogidos para determinar un modelo de máximo número de pulsaciones cardíacas
(cada x corresponde a los datos de pulsaciones/edad un corredor).
En este caso se plantea un modelo definido como pulsaciones = A · edad + B
donde A y B son los términos por determinar usando los datos que se tienen (seria el
proceso de aprendizaje que se comentará posteriormente). Se intenta que ese modelo
se acerque (todo esto lo comentaremos más adelante) todo lo posible a los datos
obteniéndose un determinado valor de A y B que se conocen como los parámetros
del modelo.
© RA-MA
Capítulo l. INTRODUCCIÚN AL APRENDIZAJE PROFUNDO 21
Pulsaciones
MODELO
Edad
Figura 1.3. Modelo ajustado para el problema de los problemas cardíacos.
Lo comentado se puede considerar como un ejemplo muy sencillo de modelo
de Aprendizaje Máquina. Se tiene un modelo matemático con dos parámetros, 10
datos de corredores para ajustar dicho modelo, una variable de entrada, la edad del
corredor y una variable de salida, las pulsaciones máximas que se pueden tener con
una determinada edad. En el caso de un modelo de Aprendizaje Máquina típico
podemos tener decenas de entradas/salidas y del orden de millares de parámetros
(y, por tanto, el mismo orden de datos necesitaremos para ajustar el modelo). El
tercer nivel de la figura 1.1. es el Aprendizaje Profundo. En este caso se tienen
varios órdenes de magnitud en el número de parámetros del modelo con respecto a
los modelos de Aprendizaje Máquina lo que conlleva el mismo orden en cuanto el
número de datos necesarios para su ajuste. Por poner un par de ejemplos en modelos
generativos, que veremos al final del presente libro, en el GPT-3 (modelo generativo
de lenguaje) se tienen 175 mil millones de parámetros y el que ha ajustado Microsoft
posteriormente para el mismo fin (a fecha de octubre 2021) tiene 530 mil millones
de parámetros. Sin todos los elementos comentados anteriormente: capacidad de
procesamiento/almacenamiento, Big Data y computación en la nube sería imposible
ajustar estos modelos. ¿ Y son importantes estos sistemas de Aprendizaje Profundo?,
pues todos los sistemas de Inteligencia Artificial actuales comerciales e industriales
son sistemas de Aprendizaje Profundo. Sin estas técnicas las grandes tecnológicas no
podrían ofrecer sus productos y nuestro mundo cambiaría en gran medida.
En este libro se explicarán los principales modelos de Aprendizaje Profundo,
pero antes de eso es necesario conocer los procedimientos que conllevan de los datos
a los modelos, empecemos nuestro viaje ...
© RA-MA
22 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
1.2 ANÁLISIS DE LOS DATOS. ETAPAS
Los primeros conceptos que manejaremos serán los de variable y patrón.
Si pensamos en un problema de decisión clínica donde se tiene una base de datos
de pacientes con diferentes características, cada paciente sería un patrón y cada
característica una variable; así Emilio sería un patrón y el valor del peso sería una
variable. La tabla 1.1 muestra lo comentado, cada fila sería un patrón y cada columna
es una variable.
Paciente
Peso
Talla
Hemoglobina (g/dl)
Emilio
85
1,76
11
Quique
75
1,80
JO
Fran
74
1,85
11,2
Pablo
75
1,83
10,7
Juan
75
1,80
10,3
Jorge
72
1,79
12
Tabla 1.1. Tabla típica para un problema de análisis de datos,
cada fila es un patrón y cada columna es una variable.
Hay que tener en cuenta que podemos tener otras casuísticas, por ejemplo,
en un problema de clasificación de imágenes cada imagen sería un patrón y aquí
el papel de las variables sería cada pixel. Una vez que se dispone de la pregunta a
resolver, que siempre la tiene que plantear el especialista en la materia y no el analista
de datos, y se dispone de los datos relacionados con dicha pregunta se entra en una
serie de etapas que quedan reflejadas en la figura 1.4 y que pasamos a comentar:
Preprocesado:
DATOS
Limpieza
Normalización
Outliers
Datos ausentes
Ingeniería de
características
Análisis
exploratorio
de datos
Modelado:
Machine/Deep
Learning
PUESTA EN
PRODUCCIÓN
Figura 1.4. Esquema de un análisis de datos típico.
Análisis
de los
errores
MODELO
© RA-MA
Capítulo l. INTRODUCCIÚN AL APRENDIZAJE PROFUNDO 23
El punto de partida según la figura anterior son los datos y aquí se pueden
tener diferentes tipos de variables:
,- Categóricas: Estas variables toman valores de un conjunto discreto
de posibles valores, por ejemplo, si hablamos de los posibles colores
tendremos rojo, verde, negro, etc. Si hablamos de intensidad del dolor
podemos codificarlo en varios niveles como bajo, medio, alto. En el
primer caso se tiene una variable categórica nominal y en el segundo se
tiene una variable ordinal (existe una relación de orden y gradación en
los diferentes niveles).
,- Continuas: En este caso el valor de la variable puede ser un valor real,
ejemplos de este tipo de variable puede ser el peso de una persona, el nivel
de hemoglobina en sangre, tiempo de permanencia en una determinada
página web, etc.
La primera fase de un análisis de datos, el preprocesado, es la más importante
y supone el 80% del tiempo invertido en la resolución de cualquier problema basado
en datos. En esta etapa se tienen las siguientes fases:
l. Limpieza de datos. La tabla 1.1 representa un conjunto de datos "ideal";
no existen datos faltantes (todos los valores de las variables se conocen)
y, encima, los valores no parecen estar afectados de ruido ni aparecen
datos atípicos (outliers). En este punto es importante distinguir entre dato
atípico y dato incorrecto. Un dato atípico es un dato que presenta una
muy baja frecuencia de aparición, por ejemplo, si hablamos de pesos una
persona puede pesar 170 kg, pero no es lo normal; este dato entraría
dentro de lo atípico. Sin embargo, un dato de 1700 kg sí que sería un dato
incorrecto. El primer paso es eliminar este tipo de datos incorrectos y la
manera más directa es aplicar umbrales cuando se conocen los valores
correctos de las variables. Una fuente típica de estos datos incorrectos,
sobre todo en áreas de la salud, proceden del olvido del signo decimal (por
ejemplo, se quiere poner 70.5 kg y por olvido del signo de puntuación se
convierte en 705 kg). Hay otro tipo de variables, en las que existen una
dependencia espacial o temporal como las imágenes o series temporales,
que se pueden ver afectadas por interferencias de otras señales, en ese
caso se aplican técnicas de procesado de señales/imágenes para reducir/
eliminar dicho ruido.
2. Normalización. Algunos modelos de Aprendizaje Máquina son robustos
a una diferencia de rangos entre las variables de entrada. Por ejemplo,
podemos tener una entrada que va entre O y 10 6 y otra variable que va de
© RA-MA
24 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
O a 10-3. Los árboles de decisión y sus modelos derivados (por ejemplo,
Random Forest) son robustos a este hecho. Sin embargo, en la mayoría
de los modelos tendremos problemas en su ajuste. Además, en algunos
modelos, esta diferencia de rango provocará que el algoritmo de más
importancia a unas variables que otras no por su papel en el problema a
resolver sino por el rango de sus valores. Uno de los modelos que sufren
de este tipo de problemas es el perceptrón multicapa que se explica en
el siguiente capítulo por lo que se comentan ahí las diferentes formas de
normalización.
3. Detección/caracterización de outliers. Los datos atípicos (outliers)
pueden hacer inestable un determinado modelo matemático. Por ejemplo,
un ajuste de un modelo lineal se puede ver seriamente afectado por un
valor atípico porque estos modelos intentan ajustar la media de los
valores lo que hace que un valor extremo modifique en gran manera el
ajuste conduciendo a errores cuando se utilice para datos que no se han
observado. La figura 1.5 explica lo comentado anteriormente; en esta
figura se ha añadido un punto atípico con respecto a los que aparecen en
la figura 1.3 (marcado como un punto circular) lo que modifica en gran
manera el modelo obtenido anteriormente.
Pulsaciones
MODELOB
Edad
Figura 1.5. Modificación de un modelo por la presencia de outliers.
Existen diferentes procedimientos para determinar estos datos atípicos
dependiendo de si buscamos un valor atípico dentro de una variable, o
bien, un patrón atípico. El primer caso es más sencillo; para detectar la
presencia de outliers se observa la diferencia entre la media y la mediana
de una determinada variable y, si ésta toma un valor muy diferente a cero,
entonces podemos asegurar la existencia de outliers. Para encontrarlos
© RA-MA
Capítulo l. INTRODUCCIÚN AL APRENDIZAJE PROFUNDO 25
otro procedimiento clásico es determinar aquellos valores que están fuera
del rango definido por ím - 3 · O', m + 3 · CJ] donde m es el valor medio
de la variable y o es su desviación estándar. El caso de buscar patrones
atípicos es más complicado, existen algoritmos muy específicos para
este fin; entre ellos destacar todos los que derivan de los algoritmos de
agrupamiento de los datos (su filosofía es que todo lo que quede fuera
de esos grupos es un dato atípico) y el conocido como isolation forest.
Estos algoritmos han ganado mucha relevancia en los últimos años
quedando todos bajo el paraguas de lo que se conoce como detección/
caracterización de anomalías. Esta relevancia se debe al gran número
de aplicaciones que se tienen de estos métodos actualmente: detección
de fraude, ciberseguridad, mantenimiento predictivo, detección de fake
news, etc.
4. Datos ausentes. Existen modelos que pueden funcionar con datos
ausentes, por ejemplo, los modelos gráficos probabilísticos, pero son los
menos comunes. La gran mayoría de modelos necesitan que los patrones
de entrada estén completos. Aquí también se divide la estrategia según
se considere un punto de vista de variable o de patrón. Si se considera el
problema a nivel de variable se consideran dos casos según la variable
sea discreta o continua. Si es discreta el valor ausente de esa variable
en un determinado patrón se toma como la moda (valor que aparece
con mayor frecuencia) de dicha variable. Si es continua se considera
la media (o la mediana) de dicha variable para ser considerado como
el valor faltante. Si se escoge una aproximación de patrones lo que se
hace es que se buscan aquellos patrones más parecidos a los patrones que
presentan valores faltantes y se escogen los valores de las variables que
se tienen para ser consideradas en los patrones que no las tienen. Igual
que en el caso anterior existen gran cantidad de los algoritmos diferentes
a los comentados, pero estos son los más utilizados. Todo lo comentado
depende, evidentemente, del porcentaje de datos faltantes, en definitiva,
nos estamos inventando una realidad que no es seguro que tengamos.
5. Ingeniería de características. Uno de los pasos más importantes para el
aprendizaje máquina y que, a priori, no es necesario para el aprendizaje
profundo (de ahí la revolución que supuso frente a su antecesor). Esta
etapa puede consistir en otras tareas como: a) la transformación de
entradas en otras nuevas; por ejemplo, el índice de masa corporal que es
el resultado de combinar el peso y la altura de una determinada persona;
b) la selección de variables; donde se escogen aquellas variables que se
consideran que deben ser tenidas en cuenta en el modelo a desarrollar y c)
análisis de las entradas en esta etapa se investiga la importancia de cada
26 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
entrada en el modelo que se pretende desarrollar. Esta etapa tiene una
importancia clave cuando se desarrolla cualquier modelo de aprendizaje
máquina ya que define qué información vamos a pasar al modelo para
su ajuste. Aunque no se realiza para el aprendizaje profundo esta etapa
puede facilitar el ajuste de estos modelos si se realiza.
Como siguiente etapa en el proceso de extracción de conocimiento de los
datos se encuentra el análisis exploratorio de datos. Aquí se realiza una descripción
de los datos que se tienen calculando los estadísticos descriptivos más comunes
de las variables continuas y las frecuencias de las variables discretas. Además, se
determinan los contrastes de hipótesis más comunes (estadística inferencial) para
determinar normalidad, independencia y posibles relaciones lineales entre variables
continuas. Junto con todo lo anterior se establecen todas las visualizaciones posibles
para destacar todas las relaciones obtenidas. Seguidamente, y como paso final de
esta etapa, se suelen aplicar algoritmos de agrupamiento (clustering) para obtener
posibles zonas de alta densidad de patrones que nos pueden indicar comportamientos
similares en los datos lo cual nos puede ayudar a simplificar el problema que
intentamos resolver.
La siguiente etapa es el desarrollo del modelo que dependerá de la cantidad
de datos y del objetivo que se pretenda resolver; estos dos factores determinarán
si se escoge un modelo de Aprendizaje Máquina o uno de Aprendizaje Profundo.
Aquí hay que tener en cuenta lo que se conoce como el teorema de "No free lunch"
que refleja que, a priori, no existe el mejor modelo para todos los problemas; esto
conduce a que en esta etapa se tiene una búsqueda intensiva para encontrar el mejor
modelo que resuelva el problema planteado. En el siguiente apartado se detalla más
la búsqueda y desarrollo del modelo, pero destacaremos aquí el principal problema
que se tiene en esa búsqueda que es el sobreajuste. El problema consiste en que
el modelo memoriza los datos que se tienen, pero, ante datos nuevos, no es capaz
de dar una salida adecuada, esto es, no es capaz de generalizar. Para comprobar la
capacidad de generalización se plantea una primera estrategia, figura 1.6.
Datos de entrenamiento
Datos de test
DATOS------_.
Figura 1.6. División del conjunto de datos en entrenamiento y test.
© RA-MA
Capítulo l. INTRODUCCIÚN AL APRENDIZAJE PROFUNDO 27
En esta primera aproximación se divide el conjunto de datos en dos partes;
un conjunto de entrenamiento (que suele variar entre un 70% y un 90%) dependiendo
de la cantidad de datos que se tengan y el resto es el conjunto de test. Como regla
general para este caso, y lo que viene, el conjunto de test nunca se utiliza para nada;
se realiza la separación y sólo vuelve a aparecer en el proceso de comprobación
del modelo, este conjunto de test definirá el comportamiento del modelo ante datos
no observados, definiendo su capacidad de generalización. Esta estrategia básica se
modifica para mejorar el modelo de acuerdo con la figura 1.7.
DATOS------�
Datos de
entrenamiento
.,\..
Datos de
validación
Datos de test
Figura 1.7. División del conjunto de datos en entrenamiento/validación y test.
La nueva estrategia es dividir el conjunto de entrenamiento de la figura 1.6
en dos, uno que sigue siendo el conjunto de datos para ajustar el modelo (conjunto
de entrenamiento) y el nuevo que sería el conjunto de validación (un 80%- 20%
suele ser la proporción más típica). Este conjunto controla el ajuste del conjunto
de entrenamiento intentando evitar su sobreajuste; de forma intuitiva daría una
estimación del error de test que puede cometer el modelo.
La figura 1.8 muestra la evolución de la medida de funcionamiento usada
(J) para ajustar el modelo para el conjunto de entrenamiento y el de validación. En
cada iteración se determina el valor de la función J para el conjunto de entrenamiento
y para el conjunto de validación (se busca el mínimo de esta función). El error
de entrenamiento va disminuyendo (se va ajustando a los datos que se tienen) al
igual que el de validación (se tiene una cierta capacidad de generalización y ésta
va aumentando con el tiempo). Llega un momento en el que el error de validación
empieza a aumentar; en ese momento el modelo está "memorizando" los datos de
entrenamiento y empieza a perder su capacidad de generalización. El modelo que se
tenga en ese momento es con el que debemos quedamos ya que es el que presenta
la mayor capacidad de generalización. Hay que destacar que, cuando se ajustan
modelos no se tienen estas curvas tan limpias, sino que se producen oscilaciones en
ambas. La parada se produce entonces considerando varios puntos en la curva de
generalización (tomando promedios por ejemplo que suavizan estas oscilaciones).
© RA-MA
28 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
J
Validación
Iteración
Parada del
entrenamiento
Figura 1.8. Evolución del ajuste (medido por la métrica J) del modelo para el subconjunto de
entrenamiento y validación. El modelo óptimo sería el obtenido en el punto donde aparece parada de
entrenamiento.
1.9.
A nivel de ajuste del modelo se tendría la situación que aparece en la figura
Modelo
sobreajustado
y
!
■
■
Modelo
óptimo
, Modelo
original
X
Figura 1.9. Evolución del ajuste entre dos pares de entradas (x,y). Las cruces representan el conjunto de
entrenamiento y los cuadrados el conjunto de validación.
© RA-MA
Capítulo l. INTRODUCCIÚN AL APRENDIZAJE PROFUNDO 29
En la figura 1.9 se observa que, inicialmente, el modelo no se ajusta ni al
conjunto de entrenamiento ni al de validación. Llega un momento que el modelo
da una buena respuesta para el conjunto de entrenamiento y validación (que se
corresponde al mínimo de la figura 1. 8) para posteriormente ajustarse perfectamente
al de entrenamiento y dar mala respuesta al conjunto de validación; ahí tenemos el
sobreajuste.
Un paso más allá de la estrategia definida anteriormente es considerar lo que
se conoce como k1"old. Es un método adecuado cuando se tiene un conjunto de datos
con pocos patrones (aumenta la robustez de las medidas de error para los conjuntos
de validación y entrenamiento). En este caso el conjunto de entrenamiento/validación
(hay que recordar que siempre hay una primera división entre entrenamiento/
validación y test) se divide de acuerdo con el siguiente esquema:
Fold 1
Fold 1
Fold 2
Fold 2
Fold 3
Fold 4
Fold 5
Fold 3
Fold 4
Fold 5
Fold 4
Fold 5
Fold 1
Fold 2
Fold 1
Fold 2
Fold 3
Fold 1
Fold 2
Fold 3
Fold 3
Fold 4
Fold 4
Fold 5
Fold 5
Figura 1.10. División del conjunto de datos según el procedimiento k-fold (en este caso 5-fold). El
subconjunto que aparece como separado es el que se usa como validación entrenándose el modelo con el
resto.
30 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
En este caso se plantea dividir el conjunto entrenamiento/validación en un
conjunto de subconjuntos (folds) de tal manera que 1 subconjunto se usa para validad
y el resto para entrenar. Si se divide en k-folds significa que obtendremos k valores
para el conjunto de entrenamiento para la función que queremos optimizar y k
valores para el conjunto de validación. Tras esos cálculos se promedian esos valores
obteniendo una medida única para los dos procesos, entrenamiento y validación.
Los procedimientos comentados se suelen repetir varias veces para aumentar
la fiabilidad de las medidas de funcionamiento obtenidas, en cada una de estas
repeticiones la segmentación en los diferentes conjuntos se realiza de forma aleatoria
lo que aumenta la robustez de las medidas obtenidas.
Sobre las divisiones de los conjuntos hay que tener en cuenta que el
conjunto de test no se puede tocar en ningún momento. Si queremos aplicar una
transformación a los datos para desarrollar el modelo, esta transformación será la
misma para todos los datos (incluidos los de test) pero no se usará en ningún momento,
ninguna información que se derive del conjunto de test. Lo comentado se aplica
a la división entre entrenamiento/validación; la transformación realizada no puede
usar información del conjunto de validación (o del k-fold correspondiente). De todas
maneras, las implementaciones en los diferentes lenguajes de estos métodos hacen
estas divisiones de forma automática y no hay que preocuparse de la introducción
de información del conjunto de test/validación en el conjunto de entrenamiento (este
problema se conoce como fuga de información, leakage information).
Una vez desarrollado el modelo es necesario realizar un análisis de los
errores. Aquí hay que diferenciar dos tipos de medidas de error; las medidas que
dirigen el desarrollo de los modelos (y que se verán en el siguiente apartado) y
las medidas de error que se quieren analizar. A modo de ejemplo, ambas medidas
se verán más adelante, se puede ajustar un modelo neuronal usando una función
de coste o error entrópica y usar el área bajo la curva (AUC) como medida del
rendimiento del modelo. En ambas medidas tendremos que analizar el sesgo/
varianza del modelo. Podríamos ver el sesgo, como lo explica Pedro Domingos en
su libro, como la cantidad que mide la tendencia a aprender de forma sistémica cosas
equivocadas, siempre de la misma forma y la varianza como la tendencia a aprender
cosas irrelevantes para lo que queremos aprender. Otro punto de vista de ver estos
dos parámetros es que el sesgo deriva al modelo a un tipo determinado de resultado
y la varianza impacta en que el modelo da respuestas demasiado específicas para
el problema que se intenta resolver. La figura 1.11 es una representación clásica
de estas dos variables; se tiene una diana de dardos y se muestra el resultado del
lanzamiento de una serie de dardos mostrando diferentes tipos de resultados.
© RA-MA
Capítulo l. lNTRODUCCIÚN AL APRENDIZAJE PROFUNDO 31
Figura 1.11. Esquema del significado de sesgo/varianza. En la primera fila se tiene un sesgo bajo y en
segunda un sesgo alto. En la primera columna tenemos una baja varianza y en la segunda columna una
a Ita varianza.
Se observa que un modelo con bajo sesgo/varianza proporciona buenos
resultados, mientras que el sesgo desplaza el valor correcto del modelo a una cierta
cantidad y la varianza impacta en la dispersión de los datos.
Una vez desarrollado, y validado, el modelo se pasa a producción; esto es,
el modelo pasa de ser una prueba de concepto a ser utilizado dentro de la industria/
empresa. En este punto se deben tener en cuenta una serie de conceptos que
impactarán en su funcionamiento como son la experiencia de usuario, la seguridad
de acceso al modelo (se puede atacar a los modelos basados en datos), la velocidad
de actualización del modelo, la velocidad de respuesta del modelo (si se tienen datos
en streaming), el acceso a otras fuentes (si el modelo usa datos obtenidos por web
scraping, por ejemplo, hay que garantizar dicho acceso), etc. De todos ellos hay un
factor a nivel de analítica que cobra especial atención que es el del desplazamiento
de los modelos. Este problema aparece cuando las condiciones del problema que
se quiere resolver cambian con el tiempo y el modelo comienza a funcionar mal
dando salidas que no se corresponderían con lo que uno esperaría dadas las entradas.
Existen técnicas/algoritmos para solucionar este problema y, aunque muchas veces
se pasa por alto, es uno de los que es necesario solucionar.
Todo lo comentado anteriormente se encaja con todas las etapas que conlleva
resolver un problema basado en datos. Para ello se ideó una metodología, conocida
como CRISP-DM (CRoss Industry Standard Process for Data Mining). Esta
metodología incluía las etapas del análisis de datos, comentadas anteriormente, más
dos elementos claves en todo proyecto de datos: establecer la pregunta de negocio
y tener una perfecta comprensión de los datos. La pregunta de negocio es la clave y,
32 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
como se ha comentado y se quiere recalcar, la tiene que dar el especialista en el tema;
conlleva tres partes: a) ¿Qué se quiere resolver?, b) ¿Qué medida se va a usar para
comprobar el funcionamiento del modelo? y, por último, c) ¿Qué datos se usarán
para encontrar la solución del problema? Muchas veces, el fracaso de un proyecto
basado en datos se debe a que, inicialmente, no se responden estas tres preguntas.
Además, la otra fuente de fracaso es el abordar grandes proyectos. Todos los casos
de éxito en la industria/empresa se corresponden con problemas pequeños, bien
definidos, en los que el problema a resolver era una cuestión que causaba pérdidas
económicas importantes a la empresa o bien le impedía crecer dado que no se tenía
automatizado cierto proceso.
Una vez se tiene la pregunta de negocio bien definida es necesario que los
científicos de datos se impliquen en el proceso que se quiere resolver. Plantear la
resolución de un determinado problema aplicando algoritmos solamente conduce al
fracaso, es necesario que haya una conexión directa y continua entre el especialista
que conoce el problema (recursos humanos, economista, director de la fábrica, etc.) y
el analista de datos; se hace necesario que esta última figura conozca a la perfección
el problema planteado y las variables que entran a formar parte de dicho problema.
Una vez comprendido el problema se pasaría a resolverlo mediante las
diferentes etapas que aparecen en la figura 1.4 siempre con el objetivo de resolver
la pregunta de negocio. Esta metodología CRISP-DM aparece, simplificada, en la
figura 1.12.
PREGUNTA
DE NEGOCIO
COMPRENSIÓN
DE LOS DATOS
ANÁLISIS DE LOS
DATOS
Figura 1.12. Metodología CRISP-DM simplificada.
© RA-MA
Capítulo l. INTRODUCCIÚN AL APRENDIZAJE PROFUNDO 33
1.3 APRENDIZAJE MÁQUINA. TIPOS Y APLICACIONES
Hay dos definiciones para el área del Aprendizaje Máquina que describen
muy bien este campo; por una parte, está la del profesor Alpaydin que, en su libro,
la define como "Optimizing a performance criterion using example data and past
experience". Esta definición remarca una de las características de estas técnicas, junto
con las de Aprendizaje Profundo que ya vimos que eran un subconjunto, son sistemas
que utilizan datos para optimizar su funcionamiento y resolver un problema. Es una
diferencia fundamental frente a otras áreas de la Inteligencia Artificial (como por
ejemplo la basada en reglas o la que usa la lógica). Otra definición clásica es la dada
por uno de los pioneros en el campo, Tom Mitchell. En su libro Machine Learning
da la siguiente definición: "A computer program is said to learn from experience E
with respect to sorne class of tasks T and performance measure P, if its performance
at tasks in T, as measured by P, improves with experience E'. Por tanto, hay muchos
tipos diferentes de Aprendizaje Máquina dependiendo de la naturaleza de la tarea
T que se desea resolver, la medida de rendimiento P utilizada y la naturaleza de la
señal de experiencia E que le proporcionamos. Así se tienen los siguientes tipos de
aprendizaje:
,,. Supervisado; es la forma más común, en este problema, la tarea T consiste
en establecer una relación (desarrollar un modelo) entre las entradas x
E X a las salidas y E Y. Dentro de este aprendizaje se encuentran los
siguientes tipos de problemas:
• Clasificación. El espacio de salida es un conjunto de C etiquetas
conocidas como clases. Dentro de este tipo se encuadran problemas
como predecir si un determinado usuario comprará, o no, un
determinado producto o si un paciente tendrá una determinada
enfermedad a partir de unos determinados síntomas.
• Regresión. En este caso se quiere determinar un valor real (y) que
pertenece a los números reales en lugar de una etiqueta de clase. Por
ejemplo, podemos estar interesados en determinar el valor de una
acción de bolsa la semana que viene o bien determinar la concentración
de ozono troposférico a partir de otras variables meteorológicas.
,,. No supervisado; en el anterior aprendizaje el objetivo es establecer un
mapeo entre entrada-salida. Una tarea mucho más interesante es obtener
información de los datos, de ahí que en este tipo de aprendizaje sólo se
tengan las entradas. Dentro de este tipo de aprendizaje se encuentran todos
los procedimientos de agrupamiento de datos (clustering), reducción de
la dimensionalidad (embeddings y análisis factorial). Recientemente
ha aparecido una rama del aprendizaje no supervisado conocido como
34 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
aprendizaje autosupervisado (self-supervised learning). Aquí se plantea
obtener las salidas (y) necesarias para un problema de aprendizaje
supervisado usando solamente las entradas. Actualmente supone un
área de una activa investigación por lo que supone, el 95% de los datos
actuales son no supervisados (todos los datos de redes sociales e Internet,
por ejemplo) y su etiquetado para usarlos en aplicaciones de aprendizaje
supervisado es caro de ahí la aplicabilidad de este tipo de aprendizaje.
11" Reforzado; aquí el objetivo es interactuar con el entorno por parte del
modelo; se trata de obtener una serie de acciones (conocida como política)
que fija qué hacer en todo momento a partir de una determinada entrada.
La principal diferencia con el aprendizaje supervisado es que no se le dice
al sistema la mejor acción que debe realizar (en ese caso tendríamos un
aprendizaje supervisado) sino que sólo se le da una recompensa/castigo
dependiendo de la acción que realiza y las consecuencias de dicha acción.
Si observamos la definición de Mitchell hemos comentado las diferentes
tareas (T) y ahora pasaremos a comentar las diferentes medidas de precisión/ajuste
del modelo (P). Aquí, de nuevo, se abren una gran cantidad de opciones de las que
comentaremos las más extendidas dependiendo del tipo de problema que queremos
resolver con el modelo de aprendizaje máquina/profundo. Tenemos dos grandes
grupos de aplicaciones (y, por tanto, de medidas):
11" Modelización/regresión/predicción. En este caso el problema que se
plantea es determinar el valor exacto de una determinada cantidad. Por
ejemplo, podemos plantear un modelo para predecir el valor de una
determinada acción para los días de la semana que viene; obtener un modelo
que, con variables atmosféricas y concentraciones de gases determine el
nivel de ozono troposférico, determinar las coordenadas de un objeto en
una determinada imagen, etc. Como medidas (P) en este caso tenemos las
siguientes (aquí d se corresponde con el valor que se quiere obtener, valor
deseado y s es lo que se obtiene del modelo). En primer lugar, tenemos
2
el MSE (Mean Square Error) definido como MSE = � I%=1 (dk - sk)
siendo N el número de patrones considerados a la hora de calcular esta
medida. Derivando de esta medida se encuentra el RMSE Root Mean
Square Error) definido como RMSE =
�If=i (dk - sk) 2 la ventaja
frente al MSE es que sus unidades son las mismas que lo que se desea
obtener (en la expresión anterior la unidad de la señal deseada). Relacionada
con estas dos medidas aparece en MAE (Mean Absolute Error) definido
© RA-MA
Capítulo l. INTRODUCCIÚN AL APRENDIZAJE PROFUNDO 35
como MAE = � L�=l ldk - ski- Esta medida se considera cuando se
tienen datos atípicos, outliers, que producen valores extremos en el error;
esta medida suaviza dichos valores extremos frente a otras medidas
como el MSE. Por último, cuando se tienen series temporales se suele
considera el MAPE (Mean Absolute Percentage Error) definido como
"N
dk-Sk
MAPE = Ñ1 L...
k=l l---;¡¡;-l . En este caso se considera el valor absoluto
del error normalizado por la medida que quiere predecir.
11"'"
Clasificación. En este caso el modelo tiene como misión determinar a
qué clase (de las previamente definidas) pertenece el patrón de entrada.
Problemas típicos serían los clásicos de diagnóstico médico, predicción
de fuga de clientes, estimación de compra o no compra en una página
web, etc. Por simplicidad comenzaremos considerando sólo dos clases
(tenemos un problema de clasificación binaria). La tabla 1.2 se conoce
como matriz de confusión; aquí se tiene lo que plantea el modelo (por
columnas) y lo que sucede en la realidad (por filas). Para entenderla
mejor pensemos en un sistema para clasificar un tumor como maligno
(salida del modelo= l) o como benigno (salida del modelo= O).
Modelo=l
Modelo=O
Realidad=l
Verdaderos positivos
TP(A)
Falsos negativos
FN (B)
Realidad=O
Fa!sos positivos
FP(C)
Verdaderos negativos
TN(D)
Tabla 1.2. Matriz de confusión para un problema de clasificación binaria.
Como se aprecia en la tabla 1.2 la diagonal principal se corresponde con
los aciertos realizados por el modelo (que pueden ser de dos tipos, aciertos con los
tumores malignos y aciertos con los tumores benignos). En la diagonal secundaria se
tienen los fallos, que también pueden ser de dos tipos. De esta matriz de confusión se
pueden obtener diferentes medidas, muy utilizadas en la bibliografia clínica cuando
se abordan este tipo de problemas:
l. Aciertos. Definido como la proporción de patrones correctamente
clasificados sobre el total que se tienen; según la tabla 1.2 esta cantidad
vendría definida por (A+D)/(A+B+C+D). Es una medida que no se suele
© RA-MA
36 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
utilizar ya que, en muchos problemas, y sobre todo los clínicos, los
errores cometidos no tienen la misma importancia según sean de un tipo
u otro. En nuestro caso tener un error y diagnosticar un tumor maligno
siendo benigno conlleva el sobresalto del paciente, realizarle más pruebas
pero, al final, ese diagnóstico no tendrá consecuencias más graves que
el tremendo susto provocado. En el caso contrario el error, diagnosticar
un tumor como benigno cuando es maligno, tendrá consecuencias muy
graves ya que el paciente se va a casa, no se le trata, y ese tumor puede
acabar con él. Por ello se definen otras medidas para cuantificar el grado
de error cometido por el modelo, así como el tipo.
2. Sensibilidad. Se define como la proporción de casos positivos acertados
por el modelo; según la tabla se calcularía como A/(A+B).
3. Especificidad. Medida análoga a la anterior, pero para los casos negativos.
En este caso es la proporción de casos negativos acertados por el modelo,
según la tabla se calcularía como D/(C+D).
4. Valor predictivo positivo. Este parámetro, como indica su nombre,
proporciona una medida de la capacidad predictiva, para los casos
positivos, del modelo; se define como la proporción de casos positivos
correctamente clasificados entre los que predice el modelo como
positivos. En este caso se calcula como A/(A+C).
5. Valor predictivo negativo. Exactamente igual que el anterior, pero en este
caso para los casos negativos. Se define como la proporción de casos
negativos correctamente clasificados entre los que predice el modelo
como negativos. En este caso se calcula como D/(B+D).
La matriz de confusión se aplica directamente en aquellos casos donde el
modelo da una salida discreta directamente (0/1) como, por ejemplo, los árboles de
decisión. Hay otros casos donde el modelo proporciona una salida continua entre dos
valores, que, normalmente, suele estar entre O y 1. En este caso se plantea un umbral
de tal manera que, si la salida es menor que dicho umbral se asigna a la clase O y, si es
mayor, se asigna a la clase 1; la pregunta es, ¿cuál es el umbral óptimo?; aquí aparece
otro elemento que tuvo su nacimiento en la Segunda Guerra Mundial conocido como
ROC (Receiver Operating Characteristic). En esta curva se representa la proporción
de verdaderos positivos (definida anteriormente como sensibilidad y conocida por
sus siglas en inglés, TPR) frente a proporción de falsos positivos (que se puede
obtener como !-especificidad, y conocida por sus siglas en inglés, FPR). Cada uno de
estos factores queda defimdo como T P R = -- y FP R = -- . La figura 1.13
.
TP
TP+TN
muestra una curva ROC típica de un clasificador.
FP
FP+TN
© RA-MA
Capítulo l. INTRODUCCIÚN AL APRENDIZAJE PROFUNDO 37
TPR
1
iBuen
modelo!
iMal
modelo!
Clasificador
aleatorio
1
FPR
Figura 1.13. Curva ROC de un clasificador binario.
En el espacio de la figura 1.13 definido por puntos cuyas coordenadas son
(FPR; TPR) el mejor clasificador viene definido por las coordenadas (O,1), esto es,
se detectan todos los patrones como correctos, tanto positivos como negativos. La
forma de obtener la ROC es variar el umbral anteriormente comentado de forma
continua entre 1 y O de tal forma que, para cada valor de umbral, se tendrá un valor
de TPR y de FPR. Por ejemplo, inicialmente se tendrá un valor de umbral de 1
obteniéndose que todos los patrones se clasifican como O, es decir negativos, en
este caso se tiene que TPR=0 y FPR=0; confonne se disminuye el umbral se tendrá
la situación inversa, todos los patrones se clasificarán como positivos (y de ahí
se tendrá que TPR= l y FPR= l). En la figura 1.13 se tienen zonas que definen el
comportamiento de diferentes tipos de clasificadores. Un clasificador aleatorio se
correspondería con la diagonal que aparece en la figura; por debajo de él se tendría
un clasificador muy malo (¡peor que tirar una moneda al aire!). Por tanto, lo que
queda por encima podría considerarse un buen clasificador siendo el clasificador
perfecto el correspondiente a las coordenadas (0,1) en el espacio (FPR, TPR). El
umbral que se escoge es el del punto (O,1). De la curva ROC se obtiene otra medida
importante que es el Area Bajo la Curva, AUC (Area Under Curve). Si se tiene un
clasificador perfecto se tiene un valor de AUC de 1; un clasificador aleatorio tiene
un valor de AUC de 0.5. Este parámetro AUC en literatura clínica se conoce como
parámetro C y es una de las medidas más utilizadas para comparar clasificadores
porque, con un valor, se puede comparar directamente (independientemente del tipo
de error cometido, sea positivo o negativo). Relacionada con curva ROC aparece la
curva conocida por sus términos en inglés precision-recall. En este caso se representa
la sensibilidad (recall) frente al valor predictivo positivo (precision). Esta curva se
38 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
usa cuando el error cometido en los casos positivos es especialmente importante (por
ejemplo, se usan en problemas de fugas de clientes) o cuando se tienen problemas
altamente desbalanceados (por ejemplo, en problemas de fraude). Relacionada con
esta curva se encuentra el parámetro F, definido como la media armónica entre la
precision y el recall, esto es, � = � · (� + �) siendo R el recall y P la precisión. Otra
F
forma de expresarlo es F=
2
R
TP
TP+-i_(FP+FN)
P
donde aparecen diferentes términos de la
matriz de confusión.
De la matriz de confusión se puede extraer otra medida, un estadístico,
conocido como kappa, que, básicamente muestra el grado de concordancia entre
las decisiones tomadas por dos expertos, en este caso el modelo desarrollado y la
realidad. Se puede calcular a partir de la matriz de confusión mediante la siguiente
.,
2X(TPXTN-FNXFP)
·
·, sue 1e ser:
expres10n K = (
) (
) (
) (
) y la mterpretac1on
TP+FP X FP+TN + TP+FN X FN+TN
0.20-0.40 un clasificador regular, 0.41-0.60 un clasificador que presenta un
funcionamiento moderado, 0.61-0.80 un buen clasificador y 0.81-1 tendríamos un
muy buen clasificador.
Cuando se tienen problemas de múltiples clases éstos se pueden tratar
como problemas binarios (una clase frente al resto) por lo que se pueden aplicar las
medidas comentadas anteriormente. En caso de no querer usar esa aproximación en
problemas con múltiples clases se suele usar la matriz de confusión y el parámetro
kappa como medidas más usuales; aunque hay trabajos que generalizan la curva
ROC para múltiples clases.
1.4 APRENDIZAJE PROFUNDO. BREVE HISTORIA
En el apartado anterior se ha comentado la importancia de la ingeniería de
características en el desarrollo de un sistema experto. Este trabajo es el que marca la
diferencia entre el Aprendizaje Máquina y el Aprendizaje Profundo. Un modelo de
Aprendizaje Máquina seguiría el esquema planteado en la figura 1.14 supongamos
que tenemos un problema de reconocimiento de imágenes, por ejemplo queremos
determinar la presencia de un cierto animal en una imagen y, para ello, obtenemos
características de la imagen que, pensamos, pueden ayudar en la determinación; por
ejemplo, el animal es amarillo y verde, de ahí que sea importante determinar el
tanto por ciento de esos colores en la imagen, además las transformadas (Fourier/
Wavelets) ayudan en la determinación de la forma de dicho animal. Se tiene un
trabajo de experto humano que se refleja en el tiempo de desarrollo y en la calidad
© RA-MA
Capítulo l. INTRODUCCIÚN AL APRENDIZAJE PROFUNDO 39
del clasificador ( estas características pueden que no sean las correctas, en definitiva,
es lo que cree el humano que puede funcionar).
Imagen
- %Amarillo
- %Verde
- Wavelets
Modelo - SALIDA
- Transformada Fourier
Ingeniería de características
Figura 1.14. Esquema de un clasificador basado en Aprendizaje Máquina
Lo que propone el Aprendizaje Profundo es un cambio de paradigma, ese
trabajo de ingeniería de características no se realiza, sino que, directamente, el
modelo tiene como entradas los píxeles de la imagen, figura 1.15.
Imagen
- Modelo - SALIDA
Figura 1.15. Esquema de un clasificador basado en Aprendizaje Profundo.
El no tener que plantear un trabajo de ingeniería de características impacta
en dos puntos a nivel de analítica: a) este tipo de modelos presentan un mayor
número de parámetros a ajustar que los modelos de Aprendizaje Máquina ya que la
parte de ingeniería de características las tiene que realizar el modelo y b) dado ese
mayor número de parámetros se necesita un mayor número de datos para ajustar los
modelos lo que incrementa las necesidades computacionales para su desarrollo.
Los modelos de Aprendizaje Profundo toman como base los modelos
neuronales artificiales, que podríamos denominar clásicos, a los que se les añade un
plus de complejidad quedando agrupados, principalmente, en tres tipos de modelos
definidos según su estructura y el tipo de aplicaciones a las que van orientados:
,. Modelos neuronales multicapa profundos. Son una evolución de los
modelos neuronales que se utilizaron en los primeros tiempos de los
40 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
modelos neuronales artificiales. Estos consisten en combinar una serie
de elementos individuales, neuronas, en una serie de capas que están
conectadas entre sí y que van desde la capa de entrada (correspondiente
a los datos dependientes del problema) hasta la capa de salida
correspondiente al valor que se quiere determinar. Las capas que quedan
entre la capa de entrada y salida se conocen como capas ocultas. En los
primeros años de vida de los modelos neuronales este número era, como
máximo, de dos capas ocultas. Actualmente, cuando se habla de modelos
multicapa profundos se corresponde con modelos multicapa con más de
dos capas ocultas; estos modelos los veremos en el siguiente capítulo.
11" Redes convolucionales. Son modelos neuronales artificiales especialmente
diseñados para trabajar con datos que tengan una cierta relación temporal
(series temporales) o bien una cierta dependencia espacial (por ejemplo,
imágenes) o bien las dos (vídeo). Actualmente son los modelos que
presentan el mejor funcionamiento en todos los problemas relacionados
con imágenes (detección, segmentación, clasificación, etc.). Se analizarán
con más detalle en el capítulo 3.
11" Modelos recurrentes. Estos modelos consideran realimentaciones en las
conexiones neuronales de tal forma que están pensados para ser aplicados
en problemas donde se tienen relaciones secuenciales, por ejemplo, de
nuevo series temporales y, sobre todo, se están aplicando a problemas
relacionados con el procesado del lenguaje. Estos modelos se explicarán
en el capítulo 4.
Estos tres tipos de modelos se están utilizando en otro tipo de aplicaciones.
Entre estas aplicaciones se encuentran lo que se conoce como modelos generativos
cuya misión es, como su nombre indica, generar datos similares a los que tenemos
como conjunto de entrada. Actualmente, con la necesidad que se tiene de datos para
entrenar los modelos de Aprendizaje Profundo, están incrementando su importancia y
aplicabilidad; nos dedicaremos a analizar estos modelos en el capítulo 5. Por último,
en el capítulo 6, analizaremos la aplicación de los modelos profundos en problemas
de aprendizaje reforzado dando lugar a los que se conoce como aprendizaje reforzado
profundo.
Una breve historia de estos modelos quedaría reflejada en los siguientes
hitos donde se exponen los avances en modelos neuronales artificiales ya que son la
base de los modelos de Aprendizaje Profundo.
© RA-MA
Capítulo l. INTRODUCCIÚN AL APRENDIZAJE PROFUNDO 41
11" En 1943 Warren McCulloch y Walter Pitts crean un modelo de neurona
basándose en los conocimientos que se tenían en aquella época sobre
neurociencia.
11" En 1958 Frank Rosenblatt crea el perceptrón, un algoritmo para el
reconocimiento de patrones basado en el modelo neuronal de McCulloch
y Pitts. En ese mismo año John Von Neumann, un auténtico genio que
contribuyó a la matemática, economía, fisica y que estuvo en el proyecto
Manhattan (donde se desarrolló la bomba atómica en la Segunda Guerra
Mundial) publica el libro El ordenador y el cerebro donde comenta las
similitudes y diferencias entre la reciente teoría neuronal artificial y los
ordenadores primigenios.
11" En 1969, Marvin Minsky y Seymour Papert, demuestran las limitaciones
del perceptrón de una sola capa (sólo son capaces de resolver problemas
linealmente separables) en su libro Perceptrons. A partir de este trabajo la
investigación en modelos neuronales artificiales decayó de forma notable.
11" En 1979 el investigador japonés Kunihiko Fukushima plantea un modelo
neuronal artificial que recogía los últimos conocimientos en fisiología de
la visión, este modelo lo bautizó como Neocognitrón.
11" En 1982 John Hopfield plantea su modelo de red que se puede utilizar
como memoria asociativa y como método para resolver problemas de
optimización. En este mismo año Teuvo Kohonen propone su modelo
de red neuronal, SOM (Self Organizing Maps) que se corresponde con
un modelo artificial de la corteza cerebral usando un tipo de aprendizaje
competitivo.
11" En 1985 David H. Ackley, Geoffrey Hinton y Terrence Sejnowski crean
la Máquina de Boltzmann modelo muy cercano al de Hopfield pero
estocástico.
11" En 1986 Geoffrey Hinton junto con otros investigadores publica el
algoritmo de retropropagación (backpropagation) que se utiliza para
obtener los parámetros de estructuras neuronales multicapa. Se utiliza
todavía actualmente de manera usual en los modelos actuales de
aprendizaje profundo. Todavía hay controversia sobre la autoría de
este algoritmo. Ese mismo año Paul Smolensky propone una variación
de la Máquina de Boltzmann conocida como Máquina de Boltzmann
Restringida (RBM). Este modelo servirá como estímulo para el resurgir
de los modelos neuronales profundos.
42 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
11" En 1989 Yan LeCun propone mejoras al Neocognitrón de Fukushima con
su modelo LeNet que sienta las bases de las futuras redes convolucionales.
Esta red la aplicó al reconocimiento de dígitos manuscritos con el objetivo
de facilitar la labor al servicio de correos norteamericano.
11" En 1997 Hochreiter y Schmidhuber proponen la memoria a corto plazo
(LSTM) para redes neuronales recurrentes que, actualmente, es la base
para todos los modelos de aprendizaje profundo aplicados en el procesado
de lenguaje, reconocimiento de voz, predicción de series temporales por
poner unos cuantos ejemplos.
11" En 2006 Geoffrey Hinton y sus colaboradores publican un artículo en
el que apilan múltiples RBM en capas de tal manera que el proceso de
entrenamiento es mucho más eficiente para una gran cantidad de datos.
11" En 2009 el grupo de Andrew NG en Stanford usa, por primera vez, GPUs
para el entrenamiento de redes neuronales profundas. Este uso disminuye
el tiempo para obtener los parámetros de los modelos neuronales
artificiales.
11" En 2012, Geoffrey Hinton y su equipo ganaron la competición lmageNet
Large Scale Visual Recognition Challenge (ILSVRC) con su modelo de
aprendizaje profundo AlexNet, reduciendo la tasa de error del 26% al
15% (hay que tener en cuenta que el valor de error previo a ese 26% fue
un 28%). En ese mismo año y también Hinton junto con otro colaborador
plantea el procedimiento de regularización conocido como dropout que
mejora, en gran manera, la generalización de los modelos neuronales
profundos y que es uno de los algoritmos más utilizados actualmente
para evitar el sobreajuste en los modelos profundos.
11" En 2014 Google adquiere la tecnológica británica DeepMind
especializados en la combinación de aprendizaje reforzado y modelos
neuronales profundos. Esta adquisición provocará el incremento de
la investigación en Inteligencia Artificial en los próximos años. En
ese mismo año, 2014, Jan Goodfellow plantea las GAN (Generative
Adversaria/ Networks) que, rápidamente, se convierten en una referencia
en cuanto a modelos generativos se refiere.
11" En 2016 AlphaGo, un algoritmo desarrollado por DeepMind gana al
campeón mundial de Go. Este hecho fue un verdadero hito en la IA, en
ajedrez se tienen unas l 048 posibilidades mientras que en el Go se tienen
más de 10 148 posibilidades. Este hecho tuvo un gran impacto en China
© RA-MA
Capítulo l. INTRODUCCIÚN AL APRENDIZAJE PROFUNDO 43
donde, según muchos expertos, supuso el punto de partida para su carrera
en la inteligencia artificial.
,- En 2017 aparece el artículo que inicia la investigación en los Transformers,
idea que revolucionará primero el campo del NLP y, posteriormente, los
modelos convolucionales profundos.
,- En 2018, Yoshua Bengio, Geoffrey Hinton y Yann LeCun ganan el Premio
Turing por su contribución a los avances en el área del aprendizaje
profundo. Aquí aparece otra controversia, hay muchas opiniones que en
ese premio falta el investigador Jürgen Schmidhuber. A finales de este año
aparece Alphafold que predice la estructura de las proteínas lo que abre
un campo inmenso de aplicaciones en la síntesis de nuevos fármacos.
,- En 2019 la FDA de EE.UU. autorizó 3 productos de diagnóstico clínico
basados en la IA. En el campo del NLP varios avances en la investigación
(BERT, Transformer de Google AJ; ELvMo del Instituto A/len;
Transformer de OpenAI, ULMFiT de Ruder & Howard, MT-DNN de
Microsoft) ponen de manifiesto la potencia de los modelos preentrenados.
Este año aparece el GPT-2 que es un modelo generativo de lenguaje de
la compañía OpenAI.
,- En 2020 aparece la mejora del GPT-2, el GPT-3 con un incremento
sustancial en el número de parámetros del modelo (175 mil millones
de parámetros). Se incrementa el uso de las redes neuronales aplicables
a grafos, este año se obtiene un modelo que examinó millones de
compuestos, a priori usables como antibióticos, para encontrar un
antibiótico estructuralmente diferente. Aparecen nuevas aproximaciones
en los modelos convolucionales, por ejemplo la aplicación de los
Transformers (Google por ejemplo propone el modelo ViT, Vision
Transformer). A finales de este año aparece Alphafold 2 que mejora de
forma considerable la primera versión.
,- En 2021 OpenAI presenta a principios de año DALL-E que crea imágenes
a partir de texto expresado en lenguaje natural. DeepMind propone su
modelo para predecir lluvias basado en aprendizaje profundo que
mejora considerablemente los modelos existentes que se basan en otras
aproximaciones.
Y los próximos años seguro que nos deparan otros avances espectaculares,
¡comencemos el camino!
44 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
1.5 BIBLIOGRAFÍA
Aggarwal, C. (2018). Neural Networks and Deep Learning: A Textbook. Springer.
Alpaydin, E. (2021). Machine Learning. MIT Press.
Chollet, F. (2017). Deep Learning with Python. Manning Publications
Domingos, P. (2018). The Master Algorithm. Ingram Publisher.
Duda, R., Hart, P. (2000). Pattern Classification. Wiley.
Goodfellow, l., Bengio, Y., Courville, A. (2016). Deep Learning. MIT Press.
Gorelik, A. (2019). The Ente1prise Big Data Lake: Delivering the Promise of Big
Data and Data Science. O'Reilly.
Grus, J. (2019). Data Science from Scratch: First Principies with Python. O'Reilly.
Hastíe, T., Tibshirani, R., Friedman, J. (2017). The Elements ofStatistical Learning:
Data Mining, Inference, and Prediction. Springer.
Haykin, S. (2016). Neural Networks and Learning Machines. Prentice Hall.
Lee, Kai-Fu. (2020).Superpotencias de la Inteligencia Artificial. Deusto.
Marz, N., Warren, J. (2017). Big Data: Principies and best practices of scalable
rea/time data systems. Manning Publications.
Mitchell, T. (1997). Machine Learning. McGraw-Hill.
Murphy, K. (2012). Machine Learning: A Probabilistic Perspective. MIT Press.
Robinson, E., Nolis, J. (2020). Build a Career in Data Science. Manning Publications.
2
MODELOS NEURONALES MULTIFUNCIÓN
2.1 NEURONA ARTIFICIAL. ELEMENTOS QUE LA FORMAN
Un punto de partida en el estudio del cerebro lo podríamos fijar en pleno
siglo XX con los trabajos de Santiago Ramón y Caja!, uno de nuestros más grandes
científicos. Fue él quien desarrolla la idea de neurona como el componente más
pequeño en la estructura del cerebro. En casi todos los textos sobre redes neuronales
se establece una analogía entre estos elementos y los componentes básicos de un
ordenador: las puertas de silicio. Sin embargo, existen diferencias; las neuronas
son varios órdenes de magnitud más lentas que las puertas lógicas de silicio.
No obstante, el cerebro suple esta menor velocidad con un mayor número de
interconexiones. También hay que destacar la eficiencia del cerebro desde un punto
de vista energético; a pesar del gran número de operaciones realizadas, el cerebro no
necesita de un ventilador como los modernos elementos de cálculo ( CPUs, GPUs
o TPUs). La capacidad de operar en paralelo del cerebro le permite realizar tareas
que necesitan una gran cantidad de cálculos y tiempo en potentes ordenadores.
Un ejemplo cotidiano de esta característica es el reconocimiento de una cara en
una fotografía; aunque se haya tomado mal y la persona esté un poco girada, la
identificación de dicha persona no nos puede llevar mucho tiempo. Sin embargo,
este giro puede poner en un serio aprieto a cualquier sistema de reconocimiento de
imágenes. Otro ejemplo para destacar es el sistema de identificación de objetos de
un murciélago. En su cerebro, del tamaño de un garbanzo, alberga un sistema que
determina perfectamente la evolución de un obstáculo (velocidad relativa, posición,
tamaño, etc) y ¡todo en cuestión de milisegundos! siendo la envidia de cualquier
ingeniero que esté diseñando un sistema de sónar de última generación.
46 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
No existe una definición general de red neuronal artificial, existiendo
diferentes según la fuente consultada. Así, nos encontramos con las siguientes
definiciones:
1. Una red neuronal es un modelo computacional, paralelo, compuesto
de unidades procesadoras adaptativas con una alta interconexión entre
ellas.
2. Sistemas de procesado de la información que hacen uso de algunos de
los principios que organizan la estructura del cerebro humano.
3. Modelos matemáticos desarrollados para emular el cerebro humano.
4. Sistema de procesado de la información que tiene características de
funcionamiento comunes con las redes neuronales biológicas.
5. Sistema caracterizado por una red adaptativa combinada con técnicas
de procesado paralelo de la información.
6. Desde la perspectiva del reconocimiento de patrones las redes neuronales
son una extensión de métodos clásicos estadísticos.
En casi todas las definiciones aparece el componente de simulación del
comportamiento biológico; veremos más adelante (concretamente al tratar el
perceptrón multicapa) que no todas las redes emulan una determinada estructura
neuronal. Lo que sí tienen en común estos elementos con el cerebro humano es la
distribución de las operaciones a realizar en una serie de elementos básicos que, por
analogía con los sistemas biológicos, se conocen como neuronas. Estos elementos
están interconectados entre sí mediante una serie de conexiones que, siguiendo con
la analogía biológica, se conocen como pesos sinápticos. Estos pesos varían con el
tiempo mediante un proceso que se conoce como aprendizaje. Así pues, podemos
definir el aprendizaje de una red neuronal como el proceso por el cual modifica
las conexiones entre neuronas (los pesos sinápticos) para realizar una tarea
determinada.
En todo modelo artificial de neurona se tienen cuatro elementos básicos:
l. Un conjunto de conexiones, pesos o sinapsis que determinan el
comportamiento de la neurona. Estas conexiones pueden ser excitadoras
(presentan un signo positivo), o inhibidoras (conexiones negativas).
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 47
2. Un sesgo que proporciona al sistema un umbral de disparo, o activación,
de la neurona.
3. Un sumador que se encarga de sumar todas las entradas multiplicadas por
los respectivos pesos sinápticos (que, en definitiva, son los parámetros
del sistema).
4. Una función de activación no lineal para ampliar la capacidad
modelizadora de estos elementos.
Esquemáticamente, una neurona artificial quedaría representada por la
figura 2.1:
y
Función de
activación
salida
• . Wn
Xn---
Figura 2.1. Esquema de una neurona artificial.
El funcionamiento de la neurona representada en la figura 2.1 queda definido
por la ecuación 2.1:
Ecuación 2.1
Aquí, las X¡ son las entradas a la neurona con x0 tomando el valor de 1 siendo
su coeficiente correspondiente, w0 el sesgo de la neurona. El objetivo del algoritmo
de aprendizaje es determinar el valor óptimo para la tarea que se quiere resolver de
los coeficientes wk (conocidos, como se comentaba anteriormente, como coeficientes
sinápticos). Por otra parte, fes la función de activación, en la mayoría de los casos es
una función no lineal. Las funciones de activación más utilizadas quedan reflejadas
en la tabla 2.1.
48 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Función
Signo
Expresión
f(x) = (
1
-1
Sigmoide
f(x) -
Tangente
hiperbólica
f(x) -
ReLu
Soflmax
-
X�º)
Sl
x<O
si
1
1 + e-
X
1 -e - X
1 + e-X
f(x) = max(x, O)
f(xi)
-
e i
X
¿k e
xk
© RA-MA
Características
Es la que se usó en los primeros modelos
neuronales artificiales.
Función de activación muy usada en
los 80/90. Sin embargo, actualmente ha
perdido su hegemonía por los problemas de
saturación del gradiente.
Es la versión bipolar (+ 1, -1) de la anterior.
Es una de las más usadas actualmente.
Evita los problemas de las funciones de
activación anteriores.
Se utiliza cuando se tienen varias salidas
y se quieren comparar entre sí para
obtener el valor máximo siendo la versión
diferenciable de dicha función.
Tabla 2.1. Funciones de activación más usadas en los modelos neuronales artificiales.
A partir del modelo general de funcionamiento de la neurona se pueden
establecer otras operaciones que podrían complementar a las comentadas. Entre las
principales variaciones que han ido apareciendo a lo largo del tiempo se encuentran
las siguientes:
1. En el cálculo de la variable intermedia y (figura 2.1) lo que se hace es
aplicar el producto escalar entre el vector de entradas definido como
Xn ] = [1
xn ] y el vector de pesos sináptico definido
X= [Xo
como W= [Wo
Wn], esto es, W ·X = ¿�=O wk · xk. Este producto
escalar, en definitiva, es una medida del parecido entre los dos vectores.
Existen una gran cantidad de medidas de parecido, o similitud. Entre
estas medidas se encuentran todas las medidas de distancia (Manhattan,
Euclídea, Mahalanobis, etc). Un ejemplo de uso de esta medida de
distancia (en este caso la más extendida, la distancia euclídea) viene dada
por la siguiente expresión salida = f (y) = f([¿�=0 [wk - xk] 2] 1 1 2 ).
2. En la figura 2.1 no se considera como entrada a la neurona una salida
anterior, esto es, no se considera la posibilidad de realimentación de la
neurona. Esta realimentación es especialmente importante en problemas
donde existen relaciones temporales/espaciales en los datos considerados.
En esta aproximación la realimentación se puede considerar como una
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 49
entrada más, o bien, puede considerarse mediante una función adicional
(como ocurre en los modelos recurrentes en próximos capítulos).
3. Una tercera aproximación es considerar transformar las variables de
entrada mediante una serie de funciones, gk como aparece en la figura 2.2.
X1 --.._
91-w1
X2 -:92~w2
..
y
Función de
activación
salida
Figura 2.2. Esquema de una neurona artificial modificada.
La estructura de la neurona artificial es extremadamente flexible y, además de
las diferentes modificaciones planteadas en este capítulo, existen otras que aumentan
su capacidad de modelización; entre estas modificaciones destacan las que integran
el tipo en su modo de funcionamiento existiendo una gran bibliografía en este tipo
de neuronas, las conocidas como spiking neurons.
A pesar de disponer de una estructura extremadamente flexible la pregunta
que toca hacerse es, ¿cómo podemos aprender los parámetros óptimos para una
determinada tarea? Es aquí donde entra, en analogía con los sistemas biológicos, lo
que se conoce como algoritmo de aprendizaje, comencemos por el inicio.
2.2 PERCEPTRÓN. ALGORITMO DE APRENDIZAJE
La figura 2.3 muestra el esquema del perceptrón original planteado por
Rosenblatt. Se observa que se tiene la estructura de la neurona artificial en la que se
utiliza como función de activación la función signo (definida como + 1 si la entrada
es positiva y O, o -1, depende de la codificación si la entrada es negativa). Además,
aparece una señal, señal deseada, que es la que el sistema debe proporcionar cuando
se da una determinada entrada.
© RA-MA
50 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
deseada(+)
y
x2-:--w
2
•.. Wn
Xn-----
Función de
activación
salida(-)
�
Error ____.,
Figura 2.3. Esquema del perceptrón original.
El funcionamiento del perceptrón es muy sencillo; se basa en comparar la
salida del sistema con una señal deseada ( que es la que debería dar el sistema y de la
que, en principio se dispone). Por la función de activación utilizada se puede inferir
que el sistema funciona como un clasificador binario. El algoritmo del perceptrón
consiste en las siguientes etapas:
1. Inicialización aleatoria de los coeficientes, pesos sinápticos, wk.
2. Inicialización del parámetro a.
3. Obtención de la salida a
salida = signo(¿k=O wk · xk ).
partir
del
vector
de
entrada
4. Obtención del error error= deseada - salida.
5. Actualización de los coeficientes wk = wk + a · error· x k .
6. Vuelta al paso 3.
El modelo proporciona dos posibles salidas ± 1 correspondiendo a cada
una de las clases que el sistema debe clasificar al vector de entrada. La frontera
de decisión entre las dos clases se corresponde a la condición l.k=O wk · xk = O.
Esta expresión es la correspondiente a un hiperplano apareciendo una de las
principales limitaciones de este modelo, no es capaz de resolver problemas que no
sean linealmente separables. Para poner este punto de manifiesto vamos a analizar
una de las primeras aplicaciones de estos sistemas que eran la modelización de las
puertas lógicas. Si planteamos la tabla de verdad (para dos variables de entrada) de
las puertas lógicas AND, OR y XOR nos encontramos con la siguiente tabla:
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 51
© RA-MA
Xl
-1
X2
-1
AND
OR
XOR
1
-1
-1
-1
-1
1
-1
1
1
-1
-1
1
1
1
1
1
1
-1
Tabla 2.2. Puertas lógicas (se codifican como -1/1).
La figura 2.4 representa las cuatro puertas lógicas que aparecen en la última
tabla. Como se puede observar las puertas AND y OR son un problema linealmente
separable (es decir, se puede trazar una línea recta entre los casos positivos y
negativos) mientras que el problema XOR es un problema no linealmente separable
y, por tanto, no es resoluble mediante un perceptrón.
OR
X·
1
1
•·······••·••·······
¡ 1
-
*····················
··········•••·····•
1
Xz
-1
X1
X·····
Recta
separación
XOR
....................
¡ -1
•·· ········
-1 ....... ..
*
Xz
Figura 2.4. Puertas lógicas (se codifican como-1/1). La salida igual a 1 se representa como un círculo
y la salida igual a -1 como una cruz. Se muestran además las rectas de separación de las puertas ANO
y OR (problemas linealmente separables). En la puerta XOR no es posible establecer esa separación por
una línea.
52 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Hay que destacar que, si el problema es linealmente separable, el perceptrón
encontrará la solución (se puede demostrar este hecho matemáticamente). En el caso
que no se tenga este tipo de problemas la convergencia no está asegurada existiendo
variaciones del algoritmo (por ejemplo, la conocida como pocket o variable del
bolsillo) para este tipo de casos.
Existen diferentes opciones para resolver este tipo de problemas no
linealmente separables. Las dos más extendidas son:
1. Se pueden definir otras superficies de separación; el perceptrón tiene
la limitación de los problemas linealmente separables porque se ha
planteado esa función antes de aplicar la función signo. Se puede plantear
otras funciones como, por ejemplo, funciones cuadráticas que conllevan
elipses/parábolas/hipérbolas como separadores; en este caso, suponiendo
dos entradas (x 1 y x2) se tendría la siguiente superficie de separación:
(w1 'xf
+ Wz 'xi+ W3 'X1 'Xz + W4 'X1 + W5 'Xz + w6) = Ü
Ecuación 2.2
Dependiendo de los valores de los parámetros w se pueden tener diferentes
superficies de separación que no tienen por qué ser hiperplanos. Una
generalización de este concepto dará lugar a las máquinas de vectores
soporte (SVM) muy usadas en problemas de aprendizaje máquina.
2. La segunda solución surge de la combinación de diferentes perceptrones.
Desde un punto de vista operacional se sabe que cualquier puerta lógica
se puede sintetizar a partir de puertas AND y OR. En nuestro caso eso
significa que, combinando los perceptrones que dan lugar a las puertas
AND y OR se puede obtener una puerta XOR. El problema de esta
solución es la asignación de los valores de cada puerta para que se de
lugar finalmente a la puerta XOR; este problema tiene su importancia
en la historia de las redes neuronales y se conoce como asignación de
crédito. Se puede consultar la historia de las redes neuronales en el
excelente libro de Nilsson y que se encuentra disponible online.
Pasaremos ahora a describir otro modelo que, durante más de 60 años ha
demostrado su eficacia en todo tipo de problemas y aplicaciones prácticas. Conocido
inicialmente como ADALINE (Adaptive Linear Neuron) pasó posteriormente (en el
"invierno" neuronal) a ADAptive L/Near Element y dando lugar a lo que se conoce
actualmente como filtrado/control adaptativo. En lo que sigue usaremos la traducción
de este elemento, Adalina.
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 53
2.3 ADALINA. DESCENSO POR GRADIENTE. LMS
Este modelo queda representado por la figura 2.5; la principal diferencia
con respecto al perceptrón es dónde se obtiene la realimentación del error; en este
caso es antes del cálculo de la función signo. Este pequeño cambio, que parece
sin importancia, va a permitir utilizar uno de los algoritmos que más uso tiene
actualmente en los modelos de Inteligencia Artificial.
Función
signo
Wo
X2--:-w
2
•• •
X n ---------
deseada-
u
Wn
�Error
Figura 2.5. Esquema de la Adalina.
El cambio en la realimentación permitió plantear un elemento que no se
tenía antes con el perceptrón: una función de coste o de error. Esta función define la
calidad del modelo. La característica que tiene es que un extremo suyo (máximo o
mínimo) indica elfitncionamiento óptimo del sistema. Para simplificar los cálculos
que vienen a continuación se van a definir los siguientes vectores:
- = w ...... Wn l . ---+
u - u ...... uL] . w
des = [des ¡ ...... des L] ---+
err = [ err 1 ....... errL]
[ 0
-_ [ 1
'
'
Ecuación 2.3
Los vectores de la ecuación 2.3 son, respectivamente, el vector de salidas
lineales, el vector de coeficientes del sistema, el vector de señales deseadas y, por
último, el vector de errores. Estos cuatro vectores quedan relacionados si se tiene en
cuenta la siguiente relación U 5 = Li=a wk.x[. Considerando este hecho se puede
definir la matriz de entradas como:
Xo2 ... ... XoL
x� ... .... xk
l
Ecuación 2.4
Con estas definiciones se da la siguiente igualdad
Ecuación 2. 5
54 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
La función de coste a minimizar es el error cuadrático medio definido como
L�=1[err 5 ] 2 que, en forma vectorial, quedaría como:
Ecuación 2. 6
err
Donde II
11 2 indica la norma del vector al cuadrado. Como se desea
obtener el mínimo es necesario diferenciar la función con respecto a los parámetros
del sistema e igualar a O; esto es:
_
_ 0
a, - -z . m
--:::;
---= . [err 1 t aw L aw
Ecuación 2.7
Donde el superíndice t indica trasposición. Usando las ecuaciones 2.6 y 2.5
se llega a:
➔ [___,
X·
des -ü]t = O
Ecuación 2. 8
De las ecuaciones 2.8 y 2.5 se deduce la siguiente igualdad:
Ecuación 2. 9
Despejando el vector de coeficientes se obtiene el conjunto de parámetros
óptimos:
Wapt =
Ecuación 2.10
La expresión 2.1O aparece en muchas áreas de la ingeniería (procesado de
la señal, por ejemplo) bajo el nombre de ecuaciones normales. Cuando se tienen
señales aleatorias y se aplican valores esperados se llega a lo que se conoce como
filtro de Wiener que es un elemento clave en el área del procesado adaptativo de la
señal. La solución de estas ecuaciones presenta dos problemas principalmente:
1. Carga computacional que presentan. En las aplicaciones actuales de
procesado de señal que usan este elemento es usual encontrar tamaños
de vectores del orden de 512 o 1024 coeficientes. Esto significa resolver
un sistema de 1024 ecuaciones con todos los problemas computacionales
que puede suponer esto.
2. Las propiedades del entorno suelen ser cambiantes por lo que el conjunto
de parámetros óptimo puede cambiar con el tiempo y es necesario calcular
ese sistema de ecuaciones con cierta asiduidad.
Así pues, a pesar de que puede existir una solución directa al problema
planteado, ésta tiene problemas por lo que se consideran dos alternativas; en primer
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 55
lugar, se cambia la función de coste y, en segundo lugar, se plantea un procedimiento
iterativo para encontrar la solución. Este procedimiento es uno de los más usados en
Inteligencia Artificial y se conoce como descenso por gradiente. De forma intuitiva
consiste en inicializar de forma aleatoria los parámetros y descender por la superficie
de error hasta encontrar el mínimo de dicha función (si se busca el máximo se cambia
el signo de la función y, entonces, el objetivo pasa a ser un mínimo).
J
Figura 2.6. Esquema de un algoritmo de descenso del gradiente; J es la función de error a optimizar, w es
el parámetro que se quiere obtener. Las flechas indican la dirección para obtener el nuevo valor de w.
Además de cambiar la estrategia de obtención de la solución vamos a cambiar
la función de coste. En lo que sigue vamos a cambiar la notación para simplificar
las ecuaciones que se obtienen a partir de aquí de forma análoga a la obtención
de las ecuaciones normales. A continuación, usaremos el subíndice para indicar el
momento temporal cuando realizamos las operaciones (también lo podemos ver
como el orden del patrón considerado). Con este criterio se define la función de coste
error cuadrático instantáneo como:
fn = eJ = (desn - Yn) 2
Ecuación 2.11
donde, de forma análoga a lo ya obtenido, se tienen las siguientes igualdades:
'l.'L
k
k
--+ t ➔
Yn = ( Wn ) · Xn = L..k=O Wn · Xn
Ecuación 2.12
La diferencia que tenemos aquí con respecto a nuestro punto de partida
es que todo se obtiene para cada patrón/en cada instante temporal; dicho instante
se denota por el subíndice y el superíndice se utiliza para las componentes de los
vectores (W n = [w�
- w�]; Xn = [X� - x�] ). Al igual que en el caso anterior
56 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
se plantea un algoritmo de descenso por gradiente calculado en cada instante según
la siguiente expresión:
Ecuación 2.13
Donde a es un parámetro clave conocido como constante de adaptación.
Donde 'vfn = ª!_,n y,si se considera componente a componente se tiene V k]n =
Wn
awn
Aplicando las expresiones anteriores se llega a:
;q.
awn
Ecuación 2.14
Teniendo en cuenta las dos últimas ecuaciones se llega a (el factor 2 queda
englobado en la constante de adaptación a)
Ecuación 2.15
Que, expresada de forma vectorial, queda como:
Ecuación 2.16
Aquí, de forma análoga al algoritmo del perceptrón simple, se tienen los
siguientes pasos:
1. Inicialización aleatoria de los coeficientes, pesos sinápticos, wk.
2. Inicialización del parámetro a.
3. Obtención de la salida a partir del vector de entrada salida = ¿k=O wk · xk.
4. Obtención del error error= deseada - salida.
5. Actualización de los coeficientes Wk = Wk + a • error • Xk.
6. Vuelta al paso 3.
Se observa que el algoritmo es muy parecido al del perceptrón simple, pero
cambiando la forma de determinar la salida que alimenta a la señal de error. Sin
embargo, hay que tener en cuenta que en el algoritmo LMS hay un procedimiento
de optimización detrás. En este algoritmo, LMS, la constante de adaptación es clave.
Este parámetro controla el tamaño de paso de la figura 2.6; de forma intuitiva se
observa que si aumenta este parámetro la velocidad del algoritmo aumenta (el tamaño
de paso es mayor por lo que se puede alcanzar el mínimo en menos iteraciones). Por
otra parte, si el parámetro es grande el error que se puede cometer cuando se está
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 57
cerca del error mínimo es mayor que si se tuviera una constante menor ( en cuyo caso
se podría conseguir estar más cerca de ese mínimo).
El algoritmo LMS ha sido la base de las técnicas conocidas bajo el paraguas
de.filtrado adaptativo de la señal y que han tenido una gran cantidad de aplicaciones
prácticas como ecualizadores de canal de comunicaciones y, sobre todo, canceladores
activos de ruido; aplicaciones que se comentan en el siguiente apartado.
2.4 ESTRUCTURAS ADAPTATIVAS. VARIANTES DEL LMS
La Adalina comentada anteriormente (así como las estructuras neuronales
que vienen en capítulos posteriores) se puede usar en diferentes aplicaciones. Las
estructuras adaptativas agrupan diferentes aplicaciones según la disposición de las
diferentes señales que forman estos sistemas. A continuación, se van a comentar las
diferentes estructuras que se pueden tener. En primer lugar, tenemos la estructura
directa, figura 2.7.
SISTEMA
DESCONOCIDO
- Entrada
des
SISTEMA
ADAPTATIVO
Figura 2.7. Esquema de la estructura adaptativa directa.
Para comprender las estructuras hay que tener en cuenta que el objetivo es
minimizar el error cometido al cuadrado; el mínimo se tiene cuando, justamente, el
error es cero. Si se observa la figura 2.7 se tiene que los dos sistemas tienen la misma
entrada y, para que el error sea cero, deben tener la misma salida, esto es, los dos
sistemas deben ser iguales. Este tipo de estructura se plantea en situaciones en los
que se puede acceder a las entradas/salidas del sistema desconocido. Aplicaciones
prácticas de este tipo de estructuras son la modelización de recintos acústicos ( de cara
a reproducir un determinado "ambiente" musical) o en diseño de filtros digitales.
La siguiente estructura para analizar es la que se conoce como estructura
ínversa la cual viene representada por la figura 2.8.
© RA-MA
58 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
- Entrada
SISTEMA
DESCONOCIDO
SISTEMA
ADAPTATIVO
y
--- Error _____.
Figura 2.8. Esquema de la estructura adaptativa directa.
Si se analiza la variable error se tiene que la combinación sistema desconocido
junto con el sistema adaptativo debe dar como resultado no modificar la señal de
entrada. Se observa que la señal de salida (y) del sistema adaptativo debe ser igual
a la señal de entrada a la estructura. Una aplicación típica de esta estructura fue
la cancelación de ecos en la línea telefónica y en la ecualización de canales (con
aplicación directa a los sistemas de transmisión de datos vía fax).
La siguiente estructura está relacionada con la anterior y se aplica en datos
de tipo temporal, esta estructura queda reflejada en la figura 2.9.
--------des-----------,
- x(n)
RETARDO
x(n-k) ♦
SISTEMA
ADAPTATIVO
y
--- Error _____.
Figura 2.9. Esquema de la estructura predictora.
El nombre de predictora viene porque la salida del sistema adaptativo
se construye con muestras anteriores, x(n-k), y debe ser igual a x(n) esto es,
x(n)=f{x(n-k), x(n-k-1), ........, x(n-k-L)}. Cuando el sistema adaptativo haya
aprendido, lo que se hace es alimentarlo con la señal en el instante actual x(s)
proporcionando la señal futura predicha por él x(s+k). Este tipo de estructuras se han
usado en campos tan diversos como la economía (para predecir valores en bolsa) o
en control predictivo (si sé lo que ocurrirá en el futuro puedo establecer las acciones
correctivas si no encaja con lo que quiero).
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 59
La siguiente estructura es una de las más famosas y utilizadas, el cancelador
activo de ruido, figura 2.1 O.
-------des+rO-----�¡
-r1
SISTEMA
ADAPTATIVO
y+E>,
Error__J
Figura 2.1 O. Esquema de la estructura canceladora de ruido.
El problema que se quiere resolver es el siguiente, se tiene una señal que
queremos obtener (que denominaremos des, para nosotros es la señal deseada).
Esta señal se ve afectada por un ruido que denominaremos r0 • Este ruido está
correlacionado con otro, r 1 , pero no están relacionados ninguno de los dos con la
señal, des, que se quiere obtener. El sistema adaptivo, como mucho, podrá modelizar
el ruido r 0 . Si el sistema adaptativo logra modelizar el ruido (r 0) se tendrá como
señal de error des + r0 - r1 = des. Este sistema es de las pocas opciones que se
tienen cuando aparece el problema conocido como solapamiento de espectros. Este
problema aparece cuando la señal que se quiere obtener está solapada en el dominio
de la frecuencia con el ruido que se quiere eliminar. Este problema se da en un
gran número de situaciones; en ingeniería biomédica, por ejemplo, casi todas las
bioseñales (ECG, EEG, EOG, etc) tienen sus espectros solapados. Otro campo de
aplicación en el que, continuamente, están apareciendo nuevos algoritmos es el de
comunicaciones móviles; cuando una persona habla por teléfono, normalmente, la
principal interferencia son otras voces cercanas, por lo que se tiene claramente ese
problema de solapamiento de espectros.
La principal limitación de todas las estructuras comentadas es la restricción
del tipo de modelo que procede de la (Adalina); es un modelo matemático lineal por
lo que, si el objetivo que se persigue tiene características no lineales, se tendrá un
mal funcionamiento del sistema.
Estas estructuras adaptativas han dado lugar a un gran número de aplicaciones
prácticas a lo largo de los años que ha conllevado una investigación continuada en
mejorar el algoritmo básico LMS. Existen más de un centenar de variaciones del
algoritmo básico y nuestro objetivo no es repasarlos todos aquí. Se comentarán todos
aquellos algoritmos o familias de algoritmos que han tenido una especial relevancia
a lo largo del tiempo.
60 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
1. Variante con signo. A nivel histórico fueron las primeros que se plantearon
por la baja carga computacional que presentan. Lo que se propone es
usar la función signo en vez del valor de las variables consideradas en el
filtro adaptativo (error y entrada). Una multiplicación de un signo es una
operación de un bit, lo que reduce las necesidades de hardware avanzado
para implementar estos sistemas (¡hay que recordar al lector que estamos
hablando de la década de 1960!). Por ejemplo, la variante conocida como
signo-signo definida como Wn +l = Wn +a · sígn(en ) · sígn(xn ). Si la
constante de adaptación es una potencia de dos todas las operaciones
anteriores son sobre bits por lo que la carga computacional de la
adaptación es muy reducida. Este tipo de variantes han vuelto a ponerse
de moda por las enormes necesidades de cálculo que se tienen hoy en día
con los modelos de Aprendizaje Profundo.
2. Variantes con constante de adaptación variable. En este tipo de variantes
se persiguen dos objetivos: por una parte, incrementar la velocidad inicial
del algoritmo y, por otra, disminuir el error en el estado estacionario. La
estrategia a nivel numérico está clara, tener un valor alto inicialmente
(para tener un tamaño de paso grande y conseguir una alta velocidad del
algoritmo inicialmente) y, finalmente, tener una constante de adaptación
baja para conseguir un error en el estado estacionario lo menor posible.
Uno de los algoritmos consiste en la siguiente variación ªn = -1- donde
n+c
c es una constante de valor muy pequeño (se quiere evitar una división por
cero). El problema con estas aproximaciones es que, si se tienen cambios
en el entorno del sistema (con n grande) el sistema no podrá adaptarse a
ellos debido al valor bajo de la constante de adaptación. Otras variantes de
este tipo tienen en cuenta la evolución del error; por ejemplo, la constante
de adaptación se reduce si ocurre un número determinado de cambios
(m0) de signo en la estimación de J, o, se aumenta, si no se producen
cambios de signo en otro intervalo de tiempo fijado a priori (m 1 ). Este
tipo de comportamiento denota si se está alejando del mínimo o bien se
está cerca. Se definen dos formas para aumentar, o disminuir, la constante
an = an-1 + e y an = an-i donde d es mayor que uno, escogiéndose
como potencia de dos para �liminar la carga computacional del algoritmo,
así las multiplicaciones quedan reducidas a desplazamientos de bits.
3. Variantes con el gradiente filtrado. En este tipo de variantes lo que se
plantea es filtrar el término de gradiente definido por en · Xn . Este filtrado
sirve para mejorar la estimación del gradiente en ambientes que podríamos
definir como ruidosos. En caso de tener un ruido de tipo gaussiano se
plantea utilizar lo que se conoce como un promediador móvil (Moving
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 61
Average) que consiste en promediar los últimos valores de la estimación
del gradiente obteniéndose la siguiente actualización. Esta variante,
conocida como ALMS (Average Least Mean Square) puede ser una
buena alternativa en aplicaciones industriales donde se puede tener una
gran cantidad de interferencias diferentes que, de acuerdo con el teorema
del límite central, da lugar a un ruido de tipo gaussiano. Por otra parte, si
el ruido que se tiene es de tipo impulsivo se plantea usar la mediana de
las últimas estimaciones del gradiente en vez del promedio (la mediana
es un estadístico robusto a valores extremos). En este caso se tiene
Wn+l = Wn +a· mediana (en· Xn, en-1 · Xn-1, ... en-N+l · Xn-N+1)·
4. Variantes rápidas. Con estas aplicaciones se busca una alta
velocidad de convergencia; de ellas destacamos dos; la variante
con momento que, actualmente se usa de manera continua en los
modelos de aprendizaje profundo y, por otra, la variante normalizada.
En la variante con momento tenemos la siguiente actualización
Wn+l = Wn +o: · en· Xn + µ · (wn - Wn-1) donde el parámetro
µ es la constante de momento (toma un valor cercano a 1). Por
otra parte, la variante normalizada toma la siguiente expresión
en. Xn
Wn+l = Wn +a·
siendo b una constante de pequeño valor.
2
llxnll +b
Esta variante tiene como principales ventajas que es una de las más rápidas
y que se elimina la dependencia de la constante con las características de
la señal de entrada.
La Adalina ha tenido un papel central en el área del procesado de señales
durante el siglo XX (¡y lo sigue teniendo!). La estructura vista anteriormente tiene
un significado directo en señal cuando se toma como señal de entrada una señal
temporal en la que se consideran diferentes retardos temporales. En este caso se tiene
Xn = [ x(n), x(n - 1), -, x(n - L + 1)] siendo x(n) el valor de la variable x en el
instante n (por ejemplo, puede ser una temperatura, una tensión, etc, cualquier serie
temporal podría considerarse aquí). Desde un punto de vista de procesado de señales
la combinación lineal entre el vector de coeficientes y la señal de entrada se puede
interpretar como la convolución de un filtro FIR (Finite Impulse Response) y la
entrada que es la salida de dicho filtro. Este punto de vista ha hecho que la Adalina se
aplique en el área de teoría de la señal bajo el nombre de.filtro adaptativo existiendo
un gran número de aplicaciones prácticas de su aplicación en audio, vídeo y voz
en diferentes campos del conocimiento como es telecomunicaciones e ingeniería
biomédica entre otros.
62 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
2.5 PERCEPTRÓN MULTICAPA. BACKPROPAGATION
La Adalina, al igual que el perceptrón simple, presenta una estructura
lineal; las entradas se multiplican por los coeficientes (pesos sinápticos) para
posteriormente combinarse sumándose entre sí. El hecho de tener esta estructura
limita las capacidades de estos sistemas. Si nos centramos en problemas de
clasificación solo pueden resolver problemas linealmente separables como se ha
comentado en el primer apartado de este capítulo. Una posible solución a este
problema, que vamos a abordar en este capítulo, es la combinación de neuronas
artificiales para aumentar las capacidades de una neurona elemental. Las neuronas
se van a disponer en diferentes capas donde, la primera se conoce como capa
de entrada, la última capa se conoce como de salida y todas las intermedias se
conocen como capas ocultas, figura 2.11.
CAPA DE
ENTRADA
CAPAS OCULTAS
CAPA DE
SALIDA
Figura 2.11. Esquema de una estructura neuronal multicapa.
El esquema de la figura 2.11 es el de un perceptrón multicapa (se tienen
varias capas) y no recurrente (no aparecen realimentaciones, ni en las neuronas ni
entre capas). Históricamente con este tipo de estructuras se han tenido problemas
para encontrar la actualización de los parámetros de las capas ocultas, lo que se
conoce como el problema de asignación de crédito. Cuando se tiene sólo una
neurona es directo obtener la actualización de los coeficientes como se ha visto en
el apartado anterior, pero, si la neurona está en la capa oculta (o en la de entrada),
se hace necesario que las neuronas tengan una función de activación diferenciable
para poder obtener las ecuaciones de actualización. Veamos un ejemplo sencillo con
la estructura de la figura 2.12. En esta estructura tenemos dos entradas y dos salidas
con una capa oculta de dos neuronas.
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 63
...
x1 �w1a
\
f(x)
wOc
wac
w1b
X2.? w2b
f(x)
wbc
\
�
s-.
Figura 2.12. Esquema de una estructura multicapa simple.
Según la figura 2.12 (y lo comentado en el primer apartado) las salidas de las
neuronas quedarían de la siguiente forma:
= Woa + W1aX1 + W2a • X2; Oa = f(Ya);
Ob = f(yb);
Yb = W b + W1bXl + W2b • x2;
Ye = Woe + Wae · Oa + Wbe · Obi S = f(Ye)
Ya
0
Ecuación 2.17
Aquí todas las funciones de activación, f(x), son diferenciables y denotaremos
dichas derivadas por f'(x). A modo de ejemplo vamos a determinar la actualización
de los coeficientes de las diferentes capas. Se escogerá un parámetro de la capa de
salida, por ejemplo, w ' y otro de la capa oculta, por ejemplo, w 1b . Al igual que
ac
se hizo en el caso del algoritmo LMS aquí se plantea como función de coste la
cuadrática J=e2 • De nuevo, en búsqueda de la simplicidad, no usaremos un índice
temporal (¡ya tenemos dos subíndices!); para el parámetro de salida se tiene lo
siguiente, Wac = Wac - a· _!j__ Aplicando la regla de la cadena se tiene:
awac
� = a¡ . ae . !..:... . ayc
awac
ae
as
ayc awac
Ecuación 2.18
Sustituyendo valores se llega a (se ha integrado el dos que aparece en la
primera derivada en el término de la constante de adaptación):
Wac = Wac + a · e · f' CYc) · Oa
Ecuación 2.19
© RA-MA
64 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
En el caso del coeficiente de la capa oculta w1b = w1b - a · ..!.L se tendría:
aw1b
a¡ = a¡ · ae ·
ayc · ªºb · ayb
Ecuación 2.20
ae as ayc aob ayb aw1b
aw1b
!.!... ·
En la ecuación anterior hay que tener en cuenta el camino que sigue la señal
de entrada para luego calcular las correspondientes derivadas. En la figura 2.13
aparece el camino que hay que seguir desde la salida hasta el parámetro que se quiere
calcular y que define las anteriores derivadas.
wüa
......
x1 �w1a♦
�w1b
'
w'
x2 � w2b
\
wOc
f(X)
wac�s-+
@wbc
' ..... _,,, _,,,.
--
Camino de
- la señal de error
--
Figura 2.13. Camino de la señal de error para actualizar el coeficiente escogido.
Sustituyendo valores en la ecuación 2.20 se llega a:
Ecuación 2.21
El procedimiento comentado no es óptimo; en una estructura tan sencilla
como la anterior están implicados un gran número de términos/cálculos que, en
estructuras con un gran número de capas/neuronas, sería imposible determinar.
Aparece entonces el algoritmo conocido como retropropagación (Backpropagation).
Este procedimiento es uno de los más usados a lo largo de la historia del Aprendizaje
Máquina y del Aprendizaje Profundo (como iremos viendo a lo largo de los diferentes
capítulos). El origen del algoritmo sigue bajo discusión hoy en día entre varios
autores (de diferentes campos de conocimiento y nacionalidades); no entraremos en
este punto de autoría, pero es innegable su papel en la inteligencia artificial actual.
Para explicar este algoritmo vamos a considerar la estructura planteada en
la figura 2.14.
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 65
© RA-MA
1-1
1
0º
o1-1
�
1-1
Wk1
NEURONA
K
o
_..
o
W 1-1
o
o
CAPA 1+1
CAPAI
Figura 2.14. Esquema de una estructura neuronal general.
Para obtener una expresión general se necesita codificar cada peso mediante
tres coeficientes, el superíndice hace mención a la capa y los subíndices hacen
mención las neuronas que conectan. Así el peso Wit hace mención al peso de la
capa s que conecta la neurona t con la r. Según la notación de la figura 2.14 y las
operaciones en una neurona artificial se llega a las siguientes expresiones para
obtener las salidas de las neuronas.
Ecuación 2.22
Para obtener el algoritmo de retropropagación se define el siguiente término
que mide cómo impacta en la función de error la variación de la parte lineal (la más
directamente conectada a los coeficientes, pesos sinápticos, que se intentan obtener)
y que podríamos definir como el término local de error.
Ecuación 2.23
tiene:
Por otra parte, aplicando la regla del gradiente como en casos anteriores se
wZ-1
. = w kZ-1
. - a• n
v z-1 J
ki
i
Wki
Ecuación 2.24
Si se aplica la definición de gradiente y teniendo en cuenta la ecuación 2.22
y 2.23 se llega a:
Ecuación 2.25
66 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
De la ecuación 2.25 se observa el porqué de la definición de o como término
local de error; la actualización de los coeficientes viene dada por el producto de ese
factor y la entrada a la neurona considerada (siendo análoga la actualización a la
que se tiene en un perceptrón simple o la Adalina). Para obtener la actualización de
los coeficientes, es necesario calcular el término definido en la ecuación 2.23; este
término varía dependiendo de si la neurona es de la capa de salida o de una capa oculta.
Comencemos por el caso más sencillo que es considerar la neurona de salida. Como
en casos anteriores vamos a considerar una función de coste cuadrática definida como:
Ecuación 2.26
Donde se ha supuesto una estructura neuronal con P salidas, siendo éstas las
siguientes:
N
N
N
v
_J
Yk
=�
-1 . 0 -1 . 0 = f(yN)
� W kr
r
,
k
k
Ecuación 2.27
r
En este caso de ser una neurona de salida se tiene (considerando la expresión
de la función de coste, 2.26 y la ecuación 2.27):
Ecuación 2.28
Así pues, se tiene el término necesario para la actualización de los coeficientes
de la capa de salida que, usando la ecuación 2.25, define la actualización de los pesos
de salida de la red neuronal. Veamos ahora qué ocurre si la neurona es oculta. En este
caso se llega a:
Ecuación 2.29
!+1
El término � mide cómo impacta la neurona k de la capa 1 en la neurona
ayk
r de la capa l+1. Si se observa la figura 2.15 se observa dicho impacto a través de las
diferentes conexiones de los pesos sinápticos que conectan dicha neurona k.
NEURONA
O�
1
__..(J
K��?-o
o
CAPAI
wltk
\)
CAPA 1+1
Figura 2.15. Impacto de la neurona k en la capa I en las siguientes capas.
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 67
Para obtener este término hay que aplicar la ecuación 2.27 en la capa l+ 1.
l+l
l . al ol+l " wrt
Yrl+l - L...t
t r - f(yr )
De la última expresión se obtiene:
oyf+1 _
z
'
Ecuación 2.30
z
Ecuación 2.31
""""iiy[ - Wrk · f (Yk)
Recordando la definición del término local de error se llega a:
8t = {){) J =
yk
L 8� · w�k · f' (YD = f' (YD · L 8� · w�k Ecuación 2.32
+1
+1
r
r
Se observa que los términos 6 se calculan a partir de los obtenidos en capas
posteriores, de ahí el término de retropropagación. Es un cálculo óptimo en cuanto
que los términos 6 se calculan de forma recursiva usando los términos de capas
posteriores estableciéndose dos flujos de señales. Por una parte, los patrones de
entrada que entran por la capa inicial de la red y se van propagando hacia la salida de
la red. Con las salidas de la red se calculan los errores cometidos y éstos se propagan
hacia atrás (de ahí el nombre de retropropagación) calculándose los términos 6
primero en la capa de salida, estos términos locales de error se usan, posteriormente,
para la capa anterior de acuerdo con la expresión 2.32 y así sucesivamente hasta
llegar a la capa de entrada.
Lo calculado anteriormente para una estructura multicapa se puede generalizar
a cualquier tipo de estructura de tal forma que se tiene lo que se conoce como grafos
computacionales. Se trata de optimizar el cálculo de las derivadas que son la base de
la actualización de los parámetros que se tienen. Las librerías actuales de aprendizaje
profundo (Tensorflow o Pytorch) hacen uso de estos métodos de optimización de
derivadas para obtener la actualización de los modelos que el usuario puede plantear.
Básicamente un grafo computacional es un tipo de grafo dirigido (existe una dirección
de propagación de las señales) en el que los nodos describen operaciones, mientras que
las aristas representan los datos que fluyen entre esas operaciones. A modo de ejemplo
la figura 2.16 muestra un ejemplo de grafo computacional.
O=X-y
S=CO
Figura 2.16. Grafo computaciona I que representa la operación s=C(x-y).
© RA-MA
68 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
En lo que sigue todos los modelos que se comentan entran dentro de este
tipo de grafo computacional y, por tanto, se pueden aplicar este tipo de técnicas que
presentan las siguientes ventajas:
1. Posibilidad de paralelización. Los cálculos que se establecen tanto para
determinar las salidas como para actualizar los coeficientes se pueden
desarrollar en paralelo dependiendo de la estructura del grafo.
2. Optimizaciones de grafos. Se puede optimizar los grafos, reduciendo su
tamaño, aplicando teoría clásica de grafos.
3. Diferenciación automática. Los gradientes que determinan la
actualización de los coeficientes se pueden determinar a partir de la
estructura del grafo de forma automática lo que ha permitido una activa
investigación en nuevas estructuras.
Hay que destacar que se tiene una estructura especialmente flexible capaz
de establecer cualquier tipo de mapeo entre dos conjuntos de datos, esto es, capaz
de resolver cualquier problema con un alto nivel de precisión. Además de tener esa
estructura con esas capacidades hemos sido capaces de definir un algoritmo que
optimiza el cálculo de los parámetros, pesos sinápticos, que se tienen. Se recomienda
al lector consultar la web (https://playground.tensorflow.org) donde se puede
plantear el problema a resolver y la arquitectura del modelo neuronal. Esta página
web proporciona de forma interactiva las salidas de las neuronas lo que la hace
espacialmente interesante para comprender el funcionamiento de estos sistemas.
Se ha definido el algoritmo básico, retropropagación, que es la base para
otros algoritmos que presentan mejores prestaciones de funcionamiento; en el
siguiente apartado se hace una revisión de los algoritmos más extendidos y usados
actualmente.
2.6 VARIANTES DEL BACKPROPAGATION. ELECCIÓN DE LA ARQUITECTURA
Algunas de las variantes del LMS vistas anteriormente se pueden aplicar
en estructuras multicapa sin problema; la primera que vamos a comentar se vio
anteriormente y es la variante de momento. En este caso la actualización de los
coeficientes viene dada por la siguiente expresión:
Llw�+1
= -a · 'iJw¡k J + µ · Llw1k
i
Ecuación 2.33
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 69
Aquí se tiene el término de actualización ya visto del gradiente de la función
de coste y se le añade el incremento de los coeficientes en el instante anterior. Este
factor tiene como misión evitar que el algoritmo se pare cuando se llegan a zonas
planas de la función de error en las que el término de gradiente vale cero (sería el
equivalente a tener un término de inercia). Supone un pequeño incremento en la
carga computacional del algoritmo proporcionando una gran mejora en cuanto a
velocidad del algoritmo por lo que su uso se ha generalizado junto al término clásico
de gradiente. Existe un pequeño cambio en esta variante que incrementa de forma
notable la velocidad de convergencia del algoritmo y que conlleva el cálculo del
gradiente tras aplicar el término de momento, esto es:
Ecuación 2.34
Esta variante, conocida como variante de Nesterov, y publicada por primera
vez por llya Sutskever actualmente CTO de OpenAI.
·-----
NUEVA
ESTIMACIÓN
NUEVA
ESTIMACIÓN
Xo
Estimación
del gradiente
Xo
Figura 2.17. Algoritmo de Nesterov. Se observa que, calcular la estimación del gradiente tras la
actualización del momento mejora el acercamiento al mínimo.
Al igual que ocurría en el caso del LMS hay variantes que consideran el
incrementar/decrementar la constante de adaptación dependiendo de la lejanía al
mínimo que se intenta encontrar. Una manera de determinar esa lejanía es observar
los posibles cambios de sentido del gradiente; si apunta en la misma dirección en
iteraciones sucesivas significa que estamos lejos del mínimo y toca aumentar la
constante y, si se producen cambios en la dirección significa que estamos cerca del
mínimo como se refleja en la figura 2.18.
© RA-MA
70 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
J
J
w3
wl W
Figura 2.18. Sentido de gradientes sucesivos cuando se está lejos/cerca el mínimo buscado.
Teniendo en cuenta la figura 2.18 el algoritmo de Silva-Almeida plantea la
siguiente actualización para la constante de adaptación:
k)
\7 w -1J 2". Ü
7
(k+l) - {ªi . U if \7 w7J.
O\
(k)
ai
· d if \7 wkJ
1
· \7 wkl 1 J < O
Ecuación 2.35
donde u> l y d<l. Se aprecia que, si las dos últimas estimaciones del gradiente
tienen el mismo sentido (producto escalar positivo) la constante de adaptación se
incrementa multiplicando el valor actual por un valor mayor que uno (u) y, si no
es así, se decrementa multiplicando por un factor ( d) menor que uno. A partir de
esta idea inicial surgen muchas variaciones. La siguiente que consideraremos será la
variante conocida como Delta-Bar-Delta:
Ecuación 2.36
of-
1 siendo <j> una constante.
Donde se tiene of = (1 - <jJ) • í/ k J + <jJ •
wl
Esta expresión tiene como finalidad obtener el promediado exponencial de las
estimaciones del gradiente más recientes. De esta manera se reduce la incidencia del
ruido en dichas estimaciones por lo que mejora la estabilidad del algoritmo de Silva­
Almeida. Por otra parte, se plantea un crecimiento de la constante más lento que en
el algoritmo anterior al sumar una determinada cantidad a la constante de adaptación
en vez de multiplicarla.
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 71
Dentro de esta familia de algoritmos se encuentra el conocido Rprop
(Resilient Backpropagation) que se utiliza en la actualidad más que los anteriores.
Este algoritmo plantea la siguiente actualización para la constante de adaptación:
min( af · u, amax) if í/wk J · í/ wl(-1 J > O
�+D
ai
= {max( aik · d, ªmin) .lf í/w!< J · í/wk-i
' J<O
[
[
[
Ecuación 2.37
af otro caso
Y la siguiente para la actualización de los coeficientes:
llwf +1 = -at
+l
) · sign [ í/ wf J]
Ecuación 2.38
La aplicación de la función signo reduce de forma considerable la carga
computacional del algoritmo, se reduce el producto a operaciones sobre bits. El
siguiente algoritmo que considera constantes de adaptación variables se conoce
como AdaGrad (acrónimo de ADAptive GRADient). En este caso la actualización
planteada queda definida por:
flwlk+l = -
(
ª . íJ k J
✓i-;s¡-+e
W¡
Ecuación 2.39
2
Donde se tiene sf = L�=l í/ wf J) . Este término determina la suma al
cuadrado de las componentes del gradiente desde el comienzo de las iteraciones.
En aquellas dimensiones, características, donde siempre se tiene ese término de
gradiente se llega a una constante de adaptación baja (el valor 5 k es pequeño) y, en
casos de tener características poco frecuentes la constante de adaptación aumenta
compensando esa baja aparición. Este tipo de funcionalidades lo hace especialmente
útil con datos dispersos. Por otro lado, si los gradientes iniciales son grandes,
las tasas de aprendizaje serán bajas durante el resto del entrenamiento. Además,
debido a la continua acumulación de gradientes al cuadrado en el denominador, la
tasa de aprendizaje continuará reduciendo su valor a lo largo del entrenamiento,
disminuyendo finalmente a cero y deteniendo el entrenamiento por completo.
Una variación del algoritmoAdaGrad son los algoritmosAdadelta y RMSprop
que intentan resolver el problema; los dos plantean una alternativa al cálculo de sf
que conduce a valores muy altos/pequeños de la constante de adaptación, en estas
2
variantes se tiene sf = fJ sf- 1 + (1 - {J) · ( í/wf-1
La diferencia entre las dos
consiste en la actualización de los coeficientes; mientras en que el RMSprop sigue la
ecuación 2.39 en el Adadelta se plantea la siguiente actualización:
J) .
72 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Ecuación 2.40
2
Donde df = /3 · df- 1 + (1 - /3) · [Llwf ] . Se observa, como principal
ventaja, que no aparece ninguna constante de adaptación eliminándose la dependencia
con cualquier parámetro.
Uno de los algoritmos más utilizados actualmente es el que se conoce como
ADAM (Adaptive Moment Estimation). Es una mejora de los ya vistos y plantea las
siguientes actualizaciones:
Ecuación 2.41
Ecuación 2.42
Este algoritmo, básicamente, determina un promedio exponencial de
los gradientes (lo que puede reducir el ruido en sus estimaciones) y un promedio
exponencial de las normas de los gradientes (de forma análoga al RMSprop). Las
dos últimas ecuaciones eliminan el posible sesgo en las estimaciones realizadas. La
actualización de los coeficientes queda definida como:
Ecuación 2.43
Una variación de este algoritmo, también muy usada, es la conocida como
Nadam donde la N viene de Nesterov.
Todos los algoritmos comentados son algoritmos de primer orden, esto es,
sólo usan información del gradiente (primera derivada). Existen algoritmos que
utilizan información de la segunda derivada, Hessiano, para mejorar la convergencia.
Sin embargo, estos algoritmos tienen un coste computacional tan alto que no son
aplicables, de forma práctica, a los sistemas actuales de Aprendizaje Profundo; por
ello no los comentaremos aquí. De igual forma tampoco se comentarán los algoritmos
de búsqueda global como son los algoritmos genéticos y otros similares basados en
la naturaleza. Al final, en la sección de bibliografía, se dan recomendaciones de
libros para consultar.
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 73
2.7 APLICANDO EL PERCEPTRÓN MULTICAPA
2.7 .1 Arquitectura
Hemos visto cómo optimizar los parámetros, pero toca ver cómo se escoge
la arquitectura que, en definitiva, va a ser la que determinará, en gran manera, el
éxito o el fracaso del perceptrón multicapa. Hay que tener en cuenta que el número
de entradas y de salidas queda determinado por el problema que se quiere resolver.
El número de capas ocultas y el de neuronas por capa no queda parametrizado por
ninguna variable por lo que se puede escoger de forma libre. Hay que tener en cuenta
que una red neuronal con una capa oculta es capaz de establecer un mapeo continuo
entre dos conjuntos de datos y, si el conjunto de datos no es conexo se necesitan
dos capas ocultas. Este hecho motivó que, durante la época anterior al Aprendizaje
Profundo, no se plantearan arquitecturas con más de dos capas. En esa época se
planteaba aumentar el "grosor" de las capas (aumentando el número de neuronas
por capa) frente aumentar la profundidad que es la aproximación que se sigue
actualmente en estas técnicas de aprendizaje profundo. Para definir la arquitectura
de un perceptrón multicapa se tienen, básicamente, tres aproximaciones:
1. Prueba y error. Aunque pueda parecer lo contrario este método ha sido
el más usado. Se plantea variar capas/neuronas y, para ello, lo clásico
es encadenar dos bucles, uno para las capas y el otro para las neuronas
( estableciendo un mínimo/máximo para cada una de esas cantidades). Para
cada estructura se plantea una medida de funcionamiento que suele ser el
error cuadrático medio cometido en el error de validación. Aquí se tiene
lo que se conoce como una búsqueda en rejilla (grid search). Se podría
plantear una búsqueda alternativa, aleatoria, en la que no se hace esa
búsqueda exhaustiva de la arquitectura (dentro de los valores máximos/
mínimos establecidos) sino que se plantea una búsqueda aleatoria (random
search). Existen trabajos en los que se pone de manifiesto la superioridad
de una búsqueda aleatoria frente a una exhaustiva (además de que, en
determinados problemas, una búsqueda exhaustiva no se puede plantear
por el coste computacional).
2. Poda. En estos métodos se trata de partir de una arquitectura grande y
se van eliminando o bien coeficientes o bien neuronas. Actualmente han
vuelto a ponerse de moda (tuvieron mucho interés en la década de los 90)
debido a la necesidad que se tiene de simplificar y reducir los modelos
por la carga computacional que supone entrenarlos y por la cantidad de
datos necesarios para esta acción ( cuanto mayor es el modelo mayor es
la cantidad de datos necesarios para ello). Además, están creciendo de
© RA-MA
74 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
forma muy significativa las aplicaciones basadas en lo que se conoce
como Tiny ML. Aquí se trata de implementar modelos neuronales en
dispositivos electrónicos de bajo consumo y donde no se hace necesario
tener una conexión a Internet, esto conlleva muchas ventajas y de ahí
la enorme cantidad de estudios que están surgiendo actualmente sobre
este tema. Dadas las características de bajo consumo y bajas prestaciones
de los dispositivos electrónicos se necesitan modelos lo más pequeños
posibles por lo que la tarea de simplificar modelos es clave en este tipo de
aplicaciones. Dentro de los algoritmos de poda se tienen varias familias:
a. Métodos de regularización. Estos métodos consisten en añadir
un término a la función de error que penaliza valores altos de los
coeficientes de tal forma que dicho término hace tender los coeficientes
a cero. La expresión general tendría entonces la siguiente forma:
Jtotal = J + l X Tregularización donde el factor .íl determina el peso que le
damos al término de regularización; se escoge un valor pequeño para
que el objetivo principal sea la minimización de la función de error
inicial. Expresiones típicas de esta familia vendrían dadas por las
siguientes ecuaciones:
Jtot = J + .\ · ¿lwijl
i,j
Jtot = J + .\ ·
L w;j
Ecuación 2.43
i ,J
Jtot
L
w
2
= J + ,\ . . . 1 + ºwij2
i ,J
2
wo
Donde el factor wO es un factor constante que asume el papel de umbral
para determinar la importancia de cada coeficiente. El funcionamiento
de mecanismo del poda es claro, cuando los pesos, en módulo,
bajan de un determinado umbral se eliminan (la idea que hay detrás
es que solamente los coeficientes con los valores más altos son los
importantes). Estos métodos de regularización, además de ser usados
como métodos de poda se usan para reducir el posible sobreajuste del
modelo (básicamente al simplificar su estructura).
b. Como segunda gran familia tenemos los que se basan en la sensibilidad
del error frente a la salida de una neurona o frente a un determinado
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 75
coeficiente. En los años 90 cobraron especial importancia dos
métodos conocidos como OBD (Optima/ Brain Damage) y OBS
(Optima/ Brain Surgeon), el problema es que se basaban en el
Hessiano (segunda derivada) y actualmente, en estructuras profundas
por coste computacional no se puede plantear. Otro trabajo, también
de la década de los 90 donde se produjo una intensa investigación que
se paró, es el de Kamin en 1990 que plantea analizar el cambio que
se produce en la función de coste dependiendo de si eliminamos o
no un determinado peso. En este trabajo se determinaba la siguiente
lw¡-lo
cantidad Si} = - --- donde Jwf es el valor final de la función de
Wf
error con el valor de peso que se ha obtenido y]0 es el valor de la
función de error cuando se cancela ese coeficiente. Dentro de esta
familia Mozer y Smolensky plantearon en 1989 (¡ las redes neuronales
no son un invento nuevo!) la siguiente expresión para la salida de una
neurona ºJ = f (L Wij · ai · oi) si a ¡ es cero la neurona no existe y,
si a ¡ es igual a uno, la neurona funciona normalmente. Entonces la
importancia de una neurona queda definida por la derivada parcial
ªª
J M pi = � evaluada en a ¡= 1. Esta cantidad se ordena para todas las
neuronas y se eliminan aquellas cuya cantidad está por debajo de una
determinada cantidad.
c. Como tercera familia podríamos dejar todos aquellos métodos que no
caen en los anteriores. En primer lugar, se tienen los que están basados
en reglas, por ejemplo, se eliminan aquellas neuronas que presentan
una salida más o menos constante (luego, posteriormente, se ajusta el
término de umbral). Otro ejemplo de regla es eliminar aquellas neuronas
que están correlacionadas (positivamente/negativamente) para todos
los patrones de entrenamiento; en este caso ambas neuronas se pueden
integrar en una sola neurona. Otros métodos planteados para reducir
la arquitectura de un modelo neuronal es usar algoritmos genéticos;
se abandonaron dichos métodos en la época cuando se plantearon,
pero ahora debido a las capacidades computacionales que se tienen
son aplicables. Se han planteado estrategias donde las neuronas
compiten entre sí de tal forma que sólo unas cuantas sobreviven y
otras desaparecen. Otros métodos se basan en determinar las neuronas
más representativas de cara a establecer un mapeo mediante métodos
basados en aproximaciones de subespacios.
© RA-MA
76 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
3. Crecimiento. Estos métodos modifican la arquitectura de la red de una
forma inversa a la planteada en el punto anterior. En estos algoritmos se
parte de una arquitectura pequeña y se van añadiendo neuronas conforme
la red aprende. Este tipo de procedimientos están menos extendidos
que los métodos de poda por presentar una carga computacional más
elevada. Además, dadas las capacidades computacionales actuales han
perdido la vigencia que tuvieron en épocas pasadas. Por último, dada la
aproximación actual de hacer modelos neuronales inmensos tienen más
sentido los procedimientos de poda que los de crecimiento. Por comentar
algún algoritmo el planteado por Weng en 1996 por su sencillez y la
premisa de la que parte. Aquí nos fijamos en la variación de los pesos
de una neurona durante el entrenamiento de tal forma que su variación
nos indicará si es capaz de aprender la tarea encomendada. Si después
de un periodo largo de entrenamiento los coeficientes de la neurona
siguen fluctuando es porque esa neurona no es capaz de realizar la tarea
encomendada por lo que se escindirá en varias. El algoritmo tiene las
siguientes etapas:
l. Se parte de una arquitectura para la red y se fija un nivel de tolerancia
para el error.
2. Se aplica un algoritmo de aprendizaje hasta que el error se estabiliza.
3. Se determina la relación de fluctuación de los pesos de la neurona i en
la capa l como:
Ecuación 2.45
RF.¡1 = .1W.¡1 • .1salida!¡
Donde los incrementos se calculan entre iteraciones.
4. Una vez realizado este cálculo para todas las neuronas éstas se ordenan
de acuerdo con este valor.
5. La neurona con el mayor valor de RF} se divide en dos. Para el resto de
RF 1
las neuronas se determina la siguiente expresión 1 - � < umbral
RFM
donde umbral es una constante que debe ser especificada al comienzo
del algoritmo.
6. Si la desigualdad anterior es satisfecha entonces la neurona se divide
en dos. Los pesos de las nuevas neuronas son idénticos a los de las
neuronas madre.
7. Si el error cometido por la nueva red es superior al umbral entonces se
vuelve al paso 2 si esto no es así se finaliza.
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 77
2.7.2 Modo de funcionamiento
El MLP presenta tres etapas en su funcionamiento; por una parte, se
tiene la propagación de la señal hacia adelante obteniéndose la salida de la red,
posteriormente se calcula el error cometido por la red y, por último, usando ese error
se actualizan los coeficientes del modelo neuronal. En este procedimiento se tienen
varias posibilidades que conllevan diferentes formas de actualizar los coeficientes de
la red, cada uno de ellos con una serie de ventajas e inconvenientes (como cualquier
problema en ciencia e ingeniería). Veamos cada uno de ellos con más detalle:
1. Modo online o estocástico. En este caso la actualización de los parámetros
se produce tras el cálculo del error para cada patrón de entrada. Es
especialmente indicado cuando se busca rapidez y el modelo neuronal
se tiene que adaptar a cambios en el entorno de forma relativamente
repetitiva; por ejemplo, es especialmente indicado en problemas de series
temporales.
2. Modo en bloque o batch. En este caso la actualización de los coeficientes
se produce cuando han pasado todos los patrones que se tienen por la red
( aquí aparece la definición de época, que es justamente eso, el paso de todos
los patrones que se tienen por el modelo neuronal). Para la actualización
de los coeficientes se usa el promedio de los términos calculados por el
procedimiento de retropropagación para todos los patrones que se tienen.
Es un método que es más estable que el anterior pero que tiene como
principales problemas la necesidad de almacenar todos los términos de
actualización (lo que lo hace inviable en aplicaciones con un gran número
de patrones) y el hecho de presentar dificultades a la hora de escapar de
un mínimo local.
3. Modo mini-bloque o mini-batch. Es una mezcla de los dos anteriores,
aquí se plantea escoger bloques de datos que no se corresponden con todo
el conjunto de datos. Al ser una solución intermedia el tamaño del bloque
dependerá de nuestras capacidades computacionales y las características
de nuestro problema.
2.7.3 Función de coste
Una vez definida la arquitectura, por cualquiera de los métodos comentados
anteriormente, toca definir el objetivo que se persigue con el perceptrón multicapa,
este papel lo juega la función de coste, o de error, que se quiere minimizar. Las
más extendidas son las que se comentan a continuación, por simplicidad sólo se
considera la función de error para el patrón k (modo de funcionamiento online); se
© RA-MA
78 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
puede considerar la suma para todos los patrones (modo de funcionamiento batch)
o para un subconjunto de ellos (modo de funcionamiento mini-batch); la extensión
es inmediata:
11" Error cuadrático. Definida por la siguiente expresión J = LI=l ( eÜ
donde el superíndice i hace referencia a las neuronas en la capa de salida.
Se aplica para problemas de modelización y regresión. Cuando se utiliza
la neurona (o neuronas) de salida no tiene función de activación y se
calcula la señal de error como la señal deseada menos la salida de la
combinación lineal peso-coeficiente.
2
11" Error absoluto. Se utiliza para los mismos problemas que la anterior, pero
en situaciones donde se pueden tener outliers (datos atípicos) que pueden
conducir a inestabilidades en la red. La expresión es J = LI=l eH Aquí
se hace la aproximación que en x= O la función lxl tiene como derivada O.
I
11" Combinación de las anteriores. Se puede plantear una combinación de
las dos anteriores de la siguiente forma: J = A · J1 + ( 1 - íl) · fz donde
J x son las funciones definidas en el punto anterior y A es un parámetro que
toma un valor entre O y 1.
11" Entrópica. Se usa para problemas de clasificación; en este caso se aplica
la sigmoide a las neuronas de la capa de salida (o la función softmax
si son varias las neuronas de salida) y su expresión es la siguiente:
] = LI=l di· In (01) + (1- di)· In (1- 01), donde di es la señal
deseada para la neurona i y
es la salida de la neurona i. Si se usa como
función de salida una tangente hiperbólica en la función anterior hay que
hacer el cambio si=¼· (1 +al).
ot
2.7.4 Sobreajuste (overfitting)
Ésta es la principal dificultad que se enfrenta cualquier especialista en
Aprendizaje Máquina/Profundo a la hora de resolver un determinado problema.
Todos los sistemas expertos que se diseñan son para ser utilizados en situaciones
no vistas (si no se estaría diseñando sistemas que, simplemente, memorizan). El
tener una buena medida de funcionamiento no significa que el sistema diseñado
sea bueno. Es necesario conocer de dónde surge esa medida de funcionamiento y,
sobre todo, de qué patrones se ha considerado. Como se vio en el capítulo 1 para
comprobar esta capacidad de generalización se plantea la división del conjunto de
datos que se tiene en tres subconjuntos: entrenamiento, validación y test, cada uno
de ellos con una misión. El de entrenamiento se usa para ajustar los coeficientes
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 79
del modelo (en nuestro caso los pesos sinápticos), el de validación, junto al de
entrenamiento, se usa para encontrar los valores óptimos de lo que se conoce como
hiperparámetros (ese nombre deriva a que son más que parámetros), al influir su
valor sobre los parámetros, por ejemplo, el número de capas o de neuronas por capa.
Por último, el de test, que no se puede usar para nada, es el que define la capacidad
de funcionamiento del modelo neuronal para datos que no ha visto, básicamente
define su capacidad de generalización. Debido a su flexibilidad esta división se hace
especialmente importante en estos modelos. Una correcta división de los conjuntos
puede evitar muchos problemas en los procesos de validación/testeo de un modelo.
Si las distribuciones de los datos en los diferentes conjuntos son muy diferentes los
resultados obtenidos en los tres conjuntos, independientemente de la complejidad del
problema y la capacidad de nuestro modelo, serán diferentes y, por tanto, no seremos
capaces de llegar a un modelo que resuelva nuestro problema que, básicamente,
consiste en que generalice bien para datos no observados previamente. Dentro de
esta división que podemos clasificar como "clásica" se tienen variaciones (como
se vio en el primer capítulo) como es el procedimiento de k-fold. Sin embargo,
todas las variaciones deben respetar que las distribuciones de los datos en todos los
subconjuntos generados deben ser las mismas. Para ello se debería comprobar dicha
igualdad aquí se tienen dos métodos que se pueden aplicar: a) hacer uso de contraste
de hipótesis para comprobar la igualdad de estadísticas en las diferentes variables
que se tienen y b) plantear un clasificador que intente determinar si el patrón de
entrada pertenece al conjunto de entrenamiento, validación o test, si no lo logra,
¡es porque son completamente diferentes! Así pues, la primera etapa para evitar el
sobreajuste ¡es asegurarse que los datos de los que obtendremos dicha medida son
los correctos!.
Si hemos detectado, como se comentó en el primer capítulo, que aparece
un sobreajuste en el modelo neuronal se puede reducir ese sobreajuste mediante
diferentes técnicas:
1. Aplicando métodos de regularización, ya comentados en el apartado de
la arquitectura, estos métodos ayudan a reducir la complejidad de la red
neuronal y, por tanto, reducen el posible sobreajuste de la red neuronal.
El parámetro que controla la importancia del término de regularización
es un hiperparámetro a ajustar cuando se usan estos procedimientos.
Relacionados con estos métodos se encuentran los algoritmos de poda
(tanto los que eliminan pesos como neuronas completas) ya que, en
definitiva, lo que hacen es reducir la capacidad modelizadora del modelo.
2. Dropout. Propuesto inicialmente por Geoffrey Hinton que es personaje
central en el mundo académico que investiga en modelos neuronales;
estuvo en el trabajo del algoritmo de retropropagación, planteó el
80 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
resurgimiento de los modelos neuronales a través de los modelos
profundos y es uno de los autores del presente algoritmo. Se plantea
"desconectar" las neuronas con una cierta probabilidad (p). La idea que
hay detrás es evitar que haya neuronas que pueden dominar sobre el resto
de la estructura. Se busca que todas las neuronas contribuyan por igual
a la solución del problema. La figura 2.20 muestra un esquema de este
algoritmo. Un valor típico de p oscila entre 0.3 y 0.5.
DROPOUT
Figura 2.19. Aplicación del dropout.
2. 7 .5 Preprocesado de las entradas
El perceptrón multicapa es un modelo que es sensible a la forma de codificar
las entradas/salidas del modelo. Sólo hay que tener en cuenta que los términos de
actualización de los coeficientes de la primera capa toman en cuenta los valores
de las entradas. De forma intuitiva queda claro que aquellas entradas cuyo rango
sea superior al resto tendrán una mayor importancia en la actualización de los
coeficientes de la red, no por su papel en el problema que se pretende resolver sino
por su diferencia de valores. Por ello un primer preprocesado de las entradas consiste
en normalizarlas teniéndose varías opciones para esto comentando aquí las más
extendidas. La primera de ellas es la estandarización que consiste en obtener nuevas
variables con una media cero y desviación estándar de uno; esta transformación
toma la siguiente expresión
x
x=�
donde x es la variable transformada, x es
ax
la variable original, es el valor medio de la variable original y (Jx su varianza.
Otra normalización que se suele considerar es transformar los datos de entrada en
un determinado rango (0/1 y -1/1 suelen ser los más comunes; toma la siguiente
forma: = íiM-im] · (x - Xm ) + Xm donde X M y X m son los valores máximos/
l_;M-Xm
mínimos de la variable transformadas y xM , Xm son los valores máximo/mínimo de
las variables sin transformar. Otra transformación típica es cuando el rango de la
x
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 81
entrada es muy grande, en este caso se suelen aplicar transformaciones no lineales
como la función logaritmo o una función tangente hiperbólica que ayudan a reducir
dicho rango. Si consideramos variables discretas hay un valor que hay que evitar
en la codificación que es O; la razón es que la actualización de los coeficientes es
proporcional al valor de la entrada por lo que los pesos que estén conectados a esa
entrada no se actualizarán cuando tomen el valor de O.
Si nos fijamos en las capas ocultas y trabajamos en modo mini-batch se
plantea lo que se conoce como Batch Normalization que mejora, en gran manera, el
entrenamiento de los modelos profundos. Básicamente es una estandarización de los
datos que circulan en las capas interiores de los modelos neuronales; esta operación
tiene tres grandes ventajas:
1. Evita cambios bruscos (valores extremos, muy altos/bajos) en las entradas
a una determinada capa.
2. Permite una mayor libertad en la elección de los parámetros y su
inicialización.
3. Permite usar constantes de aprendizaje más altos que ayudan a acelerar el
entrenamiento de los modelos.
2.7.6 Problemas con estructuras profundas
La historia del perceptrón multicapa se puede dividir en un antes y un
después de considerar más de dos capas. Este hecho marca una clara diferencia en
cuanto a las capacidades de estos modelos a la hora de solucionar diferentes tipos de
problemas. Cuando se plantean más de dos capas se tienen dos principales problemas:
el sobreajuste del modelo y el desvanecimiento del gradiente. Para el primero la
solución son los métodos de regularización como el método ya descrito de Dropout.
En cuanto al desvanecimiento del gradiente es el problema que deriva del valor, cada
vez más pequeño, del término de gradiente conforme va de la capa de salida hacia la
capa de entrada. En ese camino se encuentra como término multiplicativo la derivada
de las funciones de activación. En la etapa previa al aprendizaje profundo, cuando se
planteaban como máximo dos capas ocultas, la función de activación más usada era
la sigmoidea (sigmoide/ tangente hiperbólica); sin embargo, esta función presenta
una derivada cercana a cero en sus extremos lo que hace que el gradiente tome un
valor muy pequeño en esos casos. Solución a adoptar, una función de activación
cuya derivada no se anulase, la elección inicial fue la ReL U para, posteriormente ir
adoptando las variaciones de dicha función. La tabla 2.3 muestra algunas variaciones
de la ReLU.
82 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Expresión
Nombre
LeakyReLU
LReLU(x) =
x�O
{�:OI-x, x<O
Parametric ReLU
X 2'. Ü
PReLU(x) = { x,
p•x, x<O
Concatenated ReLU
CReLU(x) = [ReLU(x),ReLU(-x)]
Logarithm ReLU
NLReLU(x) = ln(,8 · max(O, x) + 1.0)
Lipschitz ReLU
© RA-MA
L - Re LU(x) = {
max(q¡(x), O), x>O
- <p y r¡ son diferenciables
min(r¡(x), O), x<O
Tabla 2.3. Variaciones de la función ReLU básica.
2.8 MODELOS NEURONALES PARA CLUSTERING. SOM
Éste es un libro sobre modelos neuronales profundos orientados la mayor
parte de ellos a aprendizaje supervisado. Pese a ello no queríamos dejar pasar la
ocasión de explicar un modelo neuronal no supervisado ampliamente utilizado y
que, dentro de la historia de los modelos neuronales jugó un papel esencial en el
"invierno" de los modelos neuronales. Este modelo es el SOM (Self Organizing
Map, Mapas Autoorganizados) propuesto por el finlandés Teuvo Kohonen en 1984.
El modelo tiene una base neurocientífica, el agrupamiento de tareas afines que
tiene lugar en las diferentes zonas de la corteza cerebral. Tareas similares (o bien
la parte sensorial de zonas del cuerpo cercanas) se encuentran en zonas cercanas en
el cerebro, básicamente, el concepto que hay detrás es "si es similar en su origen
debe estar cercano en la corteza ". Este principio se utiliza en un gran número de
modelos en Aprendizaje Máquina (los conocidos como manifolds). En los Mapas
Autoorganizados se tiene una disposición de las neuronas en un plano con una
configuración rectangular, o hexagonal, como se muestra en la figura 2.20:
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 83
Figura 2.20. Arquitectura del mapa autoorganizado.
En esta estructura a cada una de las neuronas se le asigna un vector de pesos
que tiene la misma dimensión el vector de entrada, es decir, para la neurona ij (primer
índice fila, segunda columna de la estructura rectangular/hexagonal considerada) se
tendrá Wij = [wD, WD ......... w�] siendo N la dimensión del vector de entrada. El
funcionamiento de este modelo es el siguiente; se inicializan los coeficientes de las
neuronas (existen diferentes formas de hacerlo) y, posteriormente, para cada patrón
de entrada se determina el parecido entre dicho patrón y los vectores de cada neurona
del modelo. ¿Cómo se determina dicho parecido? Pues usando lo que se conocen
como distancias; aquí se tienen diferentes opciones que quedan reflejadas en la tabla
2.4.
DISTANCIA
CUADRÁTICA
Forma de obtenerla
[t,
(wf; - x') ] t
2
N
MANHATTAN
MINKOWSKI
s
�lwfj - x l
s =l
[t,
(wf; - x')f
Tabla 2.4. Diferentes métricas para comparar dos vectores.
84 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Existen muchas funciones de distancia para comparar el parecido entre dos
vectores y, lo que aparece en la tabla 2.4 es una pequeña muestra de dichas funciones.
La neurona que tiene el vector de coeficientes que más se parece al vector de entrada
(menor valor de distancia) se marca como la neurona ganadora. A partir de ahí se
actualizan todos los coeficientes del modelo de acuerdo con la siguiente expresión:
Ecuación 2.46
En negrilla aparecen los vectores (coeficientes y entrada), w * es el vector
de coeficientes correspondiente a la neurona ganadora, a es una constante de
aprendizaje y h (w ij , w.) es una función decreciente de la distancia entre la neurona
ij y la ganadora conocida como función vecindad. Veamos esta ecuación por partes
para ver su significado, iremos de derecha a izquierda. En primer lugar, aparece la
diferencia de vectores xk-wij luego multiplicamos por una constante de adaptación
y la función de vecindad; veamos estas operaciones de forma gráfica si los vectores
tuvieran dos dimensiones, figura 2.21:
X
Figura 2.21. Esquema de las operaciones implicadas en la actualización del modelo auto-organizado.
Como se observa en la figura 2.21 el resultado final es acercar el vector de
coeficientes al vector de entrada de forma proporcional a la constante de adaptación,
definida por el producto a· (xk w ij ), pero es que este "acercamiento" es
proporcional a la distancia entre la neurona que se actualiza y la neurona ganadora
mediante la función vecindad. Este producto conlleva la especialización de zonas
del mapa autoorganizado para determinados tipos de patrones de entrada los que
más se van a acercar al vector de entrada van a ser la neurona ganadora y sus vecinas
definidas por la función h. Existen muchos tipos diferentes de funciones de vecindad,
-
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 85
todas ellas comparten que son decrecientes con la distancia, a modo de ejemplo
lw-s1
2
se tiene hws = e -"T,;T" donde CT es un parámetro para controlar la vecindad que se
actualiza. Existen un gran número de variantes de lo comentado aquí, en cuanto
a variación de la constante de adaptación, tipo de función de vecindad utilizada y
ecuación de actualización, pero todas las variantes tienen como base lo que se ha
explicado aquí.
Estos mapas tienen una gran utilidad porque agrupan los patrones de entrada
de forma automática por regiones del mapa. Este hecho permite analizar regiones
determinadas y extraer conclusiones sobre los datos que se tienen y sobre relaciones
entre variables. Se recomienda al lector que profundice más en esta red cuando
quiera visualizar las variables de datos multidimensionales de forma conjunta es
una herramienta de visualización extremadamente potente.
2.9 LABORATORIO
Perceptrón simple
l. Implementa el algoritmo del perceptrón (simple) y utilízalo en los
siguientes casos (aquí x 1 y x2 son las entradas al perceptrón y de la señal
deseada):
d
X1
X2
1
o
o
o
o
1
1
1
o
o
o
1
X1
X2
1
o
o
o
1
PuertaAND
X1
X2
d
o
o
1
1
1
1
1
Puerta OR
o
d
o
X1
d
1
1
o
1
o
1
1
1
o
Puerta NOT
1
o
o
PuertaXOR
o
1
86 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Representa el error cometido en los cuatro patrones frente el número
de épocas. La medida a representar es la suma de los 4 errores (al
cuadrado) que se producen al pasar todos los patrones frente el número
de repeticiones (épocas).
SOLUCIÓN
Como hemos visto en teoría, el perceptrón multicapa se basa en comparar la
salida del sistema con una señal deseada. El algoritmo del perceptrón consiste en las
siguientes etapas:
l. Inicialización aleatoria de los coeficientes (pesos sinápticos) wk.
2. Inicialización del parámetro a.
3. Obtención de la salida a partir del vector de entrada: salida = signo O:r=o wk · xk ).
4. Obtención del error error= deseada - salida.
5. Actualización de los coeficientes: wk = wk +a · error· X k 6. Vuelta al paso 3.
Primero importaremos las librerías que vamos a utilizar. Principalmente
haremos uso de numpy y usaremos matplotlib y seaborn para realizar las
representaciones gráficas.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
Empezaremos definiendo la clase que contendrá los pesos de nuestro modelo:
class PerceptronSimple():
def _init_(self, input_shape, alpha):
## Inicializamos la bias a 1 y los pesos aleatoriamente
self.w_0 = 1
self.w_k = np.random.normal(size=input_shape)
## Guardamos el valor de alpha para la actualización de los pesos
self.alpha = alpha
def _call_(self, x):
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 87
El método _call_() es el que se ejecutará cuando hagamos
PerceptronSimple(x).
Tiene que implementar el sumatorio del producto de los pesos por las
entradas más el sesgo
activado con la función signo.
return np.sign(np.sum(self.w_k*x, axis=-1) + self.w_0)
def update(self, error, x):
## Actualizamos el valor de los pesos según la ecuación (5)
self.w_ 0 = self.w_0 + self.alpha*error
self.w_k = self.w_k + self.alpha*error*x
Definimos los vectores que contendrán las entradas al modelo:
X_AND_ORJOR = np.array(([0,0], (0,1], (1,0], (1,l]l)
X_NOT = np.array(((0], [ 1]])
Recordemos que hay que codificar los O como -1 por la función signo:
Y_AND = np.array((-1, -1, -1, 1])
Y_OR = np.array((-1, 1, 1, 1])
Y_XOR = np.array((-1, 1, 1, -1])
Y_NOT = np.array([l, -1])
Como tenemos que entrenar un modelo distinto para cada puerta lógica, lo
más cómodo es que definamos una función de entrenamiento que podamos reutilizar:
def train(model, X, Y, epochs=20, verbose=True):
## Creamos una lista para almacenar los errores de cada época.
## De esta manera luego podemos representarlos.
train_error = []
for epoch in range(epochs):
## También necesitamos una lista para almacenar los errores de cada
muestra.
epoch_error = []
## Iteramos sobre los ejemplos que tenemos
for X_i, Y_i in zip(X, Y):
## Obtención de la predicción del modelo
Y_i_pred = model(X_i)
## Cálculo del error
error = Y_i-Y_i_pred
© RA-MA
88 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
epoch_error.append(error**2)
## Actualización de los coeficientes
model.update(error, X_i)
train_error.append(np.sum(epoch_error))
if verbose:
print(f"Época {epoch+l} --> Error: {train_error[-1]}")
return train_error
Si entrenamos un modelo distinto para cada puerta lógica podemos
representar los valores de la función de coste durante el entrenamiento para cada
uno:
"
fpocns
"
Epoc�•
Figura 2.22. Evolución de la suma de los errores al cuadrado para las puertas lógicas consideradas
cuando se usa el perceptrón simple.
Resulta importante ver que el perceptrón simple es capaz de aprenderse
todas las puertas lógicas menos la XOR. ¿Por qué? La puerta XOR es una puerta
lógica no linealmente separable, por lo que el perceptrón simple no es capaz de
resolverlo. El resto de puertas sí que son todas linealmente separables y se resuelven
correctamente con este modelo. Podemos ir un paso más allá dibujando la frontera
de decisión del modelo, es decir, la recta que separa la zona en la que el modelo
predice un punto como 1 de la zona en la que predice un punto como -1. Para
encontrar esta frontera hay que igualar a O el producto de las entradas más el sesgo
w0 + ¿f=i wkxk = O. En nuestro caso bidimensional esto da lugar a la ecuación
de una recta: x1 = - wz x2 - wo_ Podemos crear una función que nos calcule esta
w1
w1
frontera de decisión para representarla cómodamente en todos los casos, pero hay
que tener en cuenta que la puerta NOT no es un problema bidimensional, así que la
expresión es ligeramente diferente haciendo x2 = 0 con lo que x1 = -w w 1.
✓
def decision_boundary(model, xmin=-10, xmax=10):
if len(model.w_k)==2:
pendiente = -(model.w_k[l]/model.w_k[0])
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 89
offset = - (model.w_0/model.w_k[0])
elif len(model.w_k)==l:
pendiente = 0
offset = - (model.w_0/model.w_k[0])
x = np.linspace(xmin, xmax, 500)
return x, pendiente*x + offset
º'
"'"
NOT
..
',
Figura 2.23. Fronteras de decisión establecidas por el perceptrón simple para las diferentes puertas
lógicas.
En esta figura se puede ver cómo funciona exactamente nuestro modelo y
porqué no es capaz de resolver la puerta XOR: no es posible separar las diferentes
clases con una única línea recta, es necesario incluir no-linealidades al modelo para
poder resolverlo.
Adalina. Algoritmo LMS
Vuelve a determinar el primer apartado (salvo para la puerta XOR) pero
usando el modelo Adalina el algoritmo LMS (Least Mean Square); recuerda que en
el perceptrón la salida se obtiene tras la función signo mientras que en el caso de
la Adalina se considera la salida antes de la función signo. Si inicializas de forma
diferente, ¿qué obtienes en el caso del perceptrón? ¿y en el caso de laAdalina?.
SOLUCIÓN
Este modelo queda representado por la figura 2.5. La principal diferencia
con respecto al perceptrón es dónde se obtiene la realimentación del error, en este
caso antes del cálculo de la función signo. Este pequeño cambio, que parece sin
importancia, va a permitir utilizar uno de los algoritmos que más uso tiene actualmente
en los modelos de Inteligencia Artificial. El cambio en la realimentación permitió
plantear un elemento que no se tenía antes con el perceptrón: una función de coste
o de error. Esta función define la calidad del modelo. La característica principal que
90 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
tiene es que un extremo suyo (máximo o mínimo) indica el funcionamiento óptimo
del sistema.
Se plantea un procedimiento iterativo para encontrar la solución conocido
como descenso por gradiente. De forma intuitiva, consiste en inicializar de forma
aleatoria los parámetros y descender por la superficie de error hasta encontrar el
mínimo de dicha función (si se busca el máximo se cambia el signo de la función
y, entonces, el objetivo pasa a ser un mínimo). De forma análoga al algoritmo del
perceptrón simple se tienen los siguientes pasos:
l. Inicialización aleatoria de los coeficientes, pesos sinápticos, wk.
2. Inicialización del parámetro a.
3. Obtención de la salida a partir del vector de entrada: salida = ¿�=O wk · xk.
4. Obtención del error error= deseada - salida.
5. Actualización de los coeficientes: Wk = Wk + a · error · Xk6. Vuelta al paso 3.
Igual que con el perceptrón simple, podemos definir la clase que represente
a la Adalina:
class Adalina():
def _init_(self, input_shape, alpha):
## Inicializamos la bias a 1 y los pesos aleatoriamente
self.w_0 = 1
self.w_k = np.random.normal(size=input_shape)
## Guardamos el valor de alpha para la actualización de los pesos
self.alpha = alpha
def _call_(self, x):
return np.sum(self.w_k*x, axis=-1) + self.w_0
def update(self, error, x):
## Actualizamos el valor de los pesos según la ecuación (5)
self.w_0 = self.w_0 + self.alpha*error
self.w_k = self.w_k + self.alpha*error*x
Si entrenamos ahora un modelo por cada puerta lógica y representamos las
funciones de pérdida se puede ver que, al no utilizar la función signo para el cálculo
del error, tienen una forma continua que baja suavemente hasta un mínimo en el que
se estabiliza:
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 91
OR
10
20
30
40
50
10
20
Époc:as
30
40
50
10
20
Épocas
30
40
50
Epocas
Figura 2.24. Evolución de la suma de los errores al cuadrado para la Adalina.
Esto permite, entre otras cosas, que las fronteras de decisión sean más suaves
y se adapten mejor a los distintos problemas (ya no aparecen fronteras de decisión
encima de los puntos, por ejemplo):
AND
NOT
OR
1.0
1.5
2.0
LO
1 5
o.o
0.5
-0.5
O.O
-1.0
0.6
0.4
0.2
-1.5
-0.5
-LO
-0.5
O.O
O.S
1.0
1.5
2.0
•l
l
0.8
0.5
1.0
e
a
o.o
-1.0
-0.5
o.o
0.5
1.0
1.5
2.0
-1.0
-0.S
O.O
0.5
1.0
1.5
2.0
Figura 2.25. Fronteras de decisión establecidas por la Adalina para las diferentes puertas lógicas.
Perceptrón Multicapa
En este ejercicio vamos a definir un perceptrón multicapa que luego
aplicaremos a dos problemas distintos: uno de clasificación y uno de regresión. Para
hacer el código lo más limpio y reutilizable posible vamos a crear dos clases:
,.- Layer: Representará una única capa del perceptrón y almacenará toda la
información necesaria (pesos, funciones de activación, salidas y las que
se utilizan para actualizar los pesos mediante retropropagación).
o
,.- LayerStack: Representará una apilación de capas Layer y permitirá
realizar el entrenamiento y la inferencia de forma cómoda.
92 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Antes de escribir ninguna línea de código lo primero es preguntarse ¿qué
información tiene que contener una capa? Basándonos en el desarrollo teórico
sabernos que el componente principal de una capa son los pesos. Además, es
importante que cada capa sepa qué función de activación le corresponde aplicar a
la salida. De cara a poder aplicar el algoritmo de retropropagación es conveniente
que cada capa sea capaz de recordar la salida y, por último, también es rnuy útil
almacenar el valor de la 8 correspondiente a cada capa para la actualización de los
pesos. Además de la información que debernos almacenar en cada capa, no podernos
olvidar que tenernos que conocer la longitud de los vectores de entrada y la cantidad
de neuronas de cada para poder generar los pesos adecuados.
Lo que rnás puede llamar la atención de la generación de los pesos son las
dimensiones, ya que parece que estén al revés de lo que se puede pensar a primera
vista. Esto es porque tendemos a pensar que las dimensiones de las matrices de pesos
deberían ser (entrada, salida) pero según se definen en la teoría (wkj representa el
peso que va a la neurona k desde la neurona J), resulta mucho rnás cómodo trabajar
con las matrices traspuestas (entrada, salida).
class Layer():
def _init_(self, input_shape, neurons, activation='linear'):
self.w_0 = np.ones(shape=(neurons,1))
self.w_k = np.random.normal(size=(input_shape, neurons)).T
self.activation, self.dactivation = self.set_activation(activation)
self.z_k = None
self.a_k = None
self.delta_k = None
def _call_(self, x):
# Trasponemos para que sea consistente con la teoría, ya que espera un
vector columna
z_k = self.w_k@ x.T + self.w_0
a_k = self.activation(z_k)
self.z_k = z_k
self.a_k = a_k
return a_k.T
def update(self, gradient):
self.w_0 = self.w_0 - gradient
self.w_k = self.w_k - gradient
def set_activation(self, activation):
possible_activations = {
'linear': linear_fn,
'relu': relu_fn,
'sigmoid': sigmoid_fn
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 93
}
possible_dactivations = {
'linear': dlinear_fn,
'relu': drelu_fn,
'sigmoid': dsigmoid_fn
}
return possible_activations[activation], possible_
dactivations[activation]
Ahora solamente queda definir las funciones de activación que queremos
utilizar en nuestras capas. Para aportar diferentes posibilidades vamos a trabajar con
tres: la función lineal, la ReLU y la sigmoide, de las cuales también tenemos que
proporcionar sus derivadas.
La función lineal nos devuelve exactamente la entrada, f(x) = X. Es el
equivalente a no hacer nada. Hay que tener cuidado porque, aunque su derivada es
f'(x)=l, es posible que x sea un vector de más de un elemento, así que tendremos
que devolver un vector de tantos unos como la entrada. Para eso sirve la función
np .ones_like( ), para devolver un vector de unos con la misma forma que el vector
dado.
def linear_fn(x):
return x
def dlinear_fn(x):
return np.ones_like(x)
Si recordamos la teoría, la función ReLU hace cero todos los números
negativos, así que la implementación más sencilla es buscar todos los elementos
de x que están por debajo de este umbral (esto lo hacemos con x<O) y colocarlos a
cero indexando el vector original. La derivada, igual que antes, también es uno para
los números mayores que cero, así que repetimos el mismo proceso de antes para
colocarlos a este valor.
def relu_fn(x):
x[x<0) = 0
return x
def drelu_fn(x):
x[x<0) = 0
x[x>=0) = 1
return x
94 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
La función sigmoide tiene la particularidad de que, al ser una exponencial,
podemos tener problemas con los valores muy grandes, así que la mejor solución
es limitar los valores para que nunca puedan ser exageradamente grandes, es decir,
cortar los valores que superan ciertos umbrales.
def sigmoid_fn(x):
x = np.clip(x, -500, 500)
return 1.0/(l+np.exp(-x))
def dsigmoid_fn(x):
loss = sigmoid_fn(x)*(l-sigmoid_fn(x))
loss = np.clip(loss, -100, 100)
return loss
Una vez definido el componente principal de nuestro perceptrón multicapa se
puede definir la clase que permitirá apilarlos e implementará el algoritmo necesario
para el aprendizaje: la retropropagación.
Como ya hemos encapsulado toda la información de las capas en Layer,
LayerStack solamente tendrá que almacenar las diferentes capas y la función de
coste a utilizar, que puede ser el error cuadrático medio (MSE) o la entropía cruzada
binaria (BCE) para problemas de regresión y clasificación respectivamente. El método
generate_layers () se encarga de generar una lista de Layers de forma que la entrada
de una capa tenga las dimensiones de la salida de la capa anterior. Como ya hemos
implementado la llamada de cada capa en el método _call_() de Layer, ahora
solamente tenemos que hacer pasar los datos de entrada por todas las capas. La clave del
funcionamiento del modelo reside en el método backpropagate(), que implementa
la retropropagación desde la neurona de salida al resto de capas. Nótese que el cálculo
se realiza de forma matricial, lo cual aporta eficiencia y legibilidad. Una vez calculada
la O L correspondiente a cada capa L la almacenamos en la capa correspondiente. Esto
nos permite implementar el método update() de forma muy sencilla para actualizar
los pesos de cada capa. Lo único que hay que tener en cuenta es que la primera capa del
modelo se actualiza a partir de la entrada, mientras que el resto de capas se actualizan a
partir de la salida de la capa anterior. Finalmente implementamos el método fit() que
implementa el bucle de entrenamiento explicado en teoría:
1. Se calcula una predicción.
2. Se calcula el error de la predicción.
3. Se retropropaga para obtener el coeficiente de ajuste de los pesos.
También se encarga de almacenar el error cometido en cada época y lo
devuelve para poder representarlo después del entrenamiento.
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 95
class LayerStack():
def _init_(self, input_shape, neurons, activations, alpha, loss):
self.layers = self.generate_layers(input_shape, neurons, activations)
self.alpha = alpha
self.loss_fn = mse if loss=='mse' else (bce if loss=='bce' else
print("El parámetro loss tiene que ser 'mse' o 'bce'."))
self.dloss_fn = dmse if loss=='mse' else (dbce if loss=='bce' else
print("El parámetro loss tiene que ser 'mse' o 'bce'."))
def _call_(self, x):
for layer in self.layers:
x = layer(x)
return x
def generate_layers(self, input_shape, neurons, activations):
layers = []
## Creamos la primera capa y la metemos en la lista de capas
input_layer = Layer(input_shape=input_shape, neurons=neurons[0],
activation=activations[0])
layers.append(input_layer)
## Creamos ahora las capas ocultas
for i, (input_dim, output_dim) in enumerate(zip(neurons[0:-1], neu­
rons[l:-1]), 1):
layer = Layer(input_shape=input_dim, neurons=output_dim,
activation=activations[i])
layers.append(layer)
## Finalmente añadimos la última capa
output_layer = Layer(input_shape=neurons[-2], neurons= neurons[-1],
activation=activations[-1])
layers.append(output_layer)
return layers
def backpropagate(self, Y_true, Y_pred):
## Calculamos la delta final
delta_F_k = self.dloss_fn(Y_true, Y_pred)*self.layers[-1].
dactivation(self.layers[-1].z_k)
self.layers[-1].delta_k = delta_F_k
## Utilizamos la delta final para calcular la delta de cada capa
for i, layer in reversed(list(enumerate(self.layers[l:]))):
delta_k = self.layers[i].dactivation(self.layers[i].z_k)*layer.w_k.T
@ layer.delta_k
self.layers[i].delta_k = delta_k.copy()
def update(self, X):
## Actualizamos los pesos de la primera capa teniendo en cuenta que su
input es X_i
self.layers[0].w_k self.layers[0].w_k - self.alpha*self.layers[0].
delta_k@ X
self.layers[0].w_0 self.layers[0].w_0 - self.alpha*self.layers[0].
96 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
delta_k
## Actualizamos los pesos menos los de la primera capa
for L in range(l, len(self.layers)):
self.layers[L].w_k = self.layers[L].w_k - self.alpha*self.layers[L].
delta_k@ self.layers[L-1].a_k.T
self.layers[L].w_0 = self.layers[L].w_0 - self.alpha*self.layers[L].
delta_k
def fit(self, X, Y, epochs, verbose=True):
losses = []
for epoch in range(epochs):
losses_epoch = []
for X_i, Y_i in zip(X, Y):
## Añadimos una dimensión por consistencia con la teoría
X_i = np.expand_dims(X_i,0)
## Calculamos una predicción y su error
pred = self(X_i)
loss = self.loss_fn(Y_true=Y_i, Y_pred=pred)
losses_epoch.append(loss)
Backpropagation para calcular las deltas
self.backpropagate(Y_true=Y_i, Y_pred=pred)
## Actualizamos los pesos
self.update(X_i)
##
losses.append(np.mean(losses_epoch))
if verbose:
print(f"Época {epoch+l}: (Loss] {losses(-1]}")
return losses
MLP usado como clasificador
En este apartado vamos a usar el perceptrón multicapa para resolver un
problema de clasificación clínica: trataremos de clasificar si un paciente tiene o no
cáncer de mama. Es un conjunto de datos con 32 características referentes a una
masa en el pecho y una variable objetivo: tiene o no tiene cáncer de mama.
Para resolver un problema de clasificación con un MLP tenemos que tener en
consideración que la salida de la última capa tiene que representar una probabilidad
y que tenemos que utilizar una función de coste acorde. Esto lo conseguimos
utilizando la función de activación sigmoide en la última capa, lo que obliga a que
nuestra salida esté contenida en el intervalo [O, 1 ], de forma que se puede interpretar
como una probabilidad. Como función de coste se utiliza la entropía cruzada binaria
(Binary CrossEntropy). Esta función de coste no se encarga solamente de penalizar
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 97
las predicciones erróneas, también penaliza una predicción correcta pero poco
segura. Esto quiere decir que al optimizarla estamos buscando un modelo que acierte
y que, además, lo haga con seguridad.
Los datos que vamos a utilizar se pueden descargar directamente a través de
la librería sklearn mediante la función load_breast_cancer( ).
from sklearn.datasets import load_breast_cancer
X, Y = load_breast_cancer(return_X_y=True)
Siempre que carguemos unos datos nuevos es muy importante comprobar
las distribuciones de las variables que vamos a introducir al modelo. Idealmente
deberían estar en el rango [-1, 1] para asegurar la convergencia del modelo. En la
siguiente figura se observa que esto no se cumple para todas las variables, así que lo
mejor que podemos hacer es estandarizarlas restándoles su media y dividiendo por
su desviación estándar.
Distribuciones de las variables
º
rn:lIJ :ll}:liJ':l[}íl}:l[J
10
20
20
40
50 100150
0.2
0.1
o.os 0.10 0.15
10002000
º
0.1 0.2 0.3
":�}:�}:íl}:í[}O
:[J
º
º
º
,:D :iD :[}:D :[J
":l[} ,:ll:tlI}:JLr:CJ
, :□
0.00
O
0.25
20
o.o
□
O
0.025.0�075 0.00
500
0.0 2
0.2
0.3
O.OJO 0,0 03
10 20 30
o.oso 0.075
O
2
O.O
0.00
0.25
0.1
25
50
100
200
2.5
0.00
O
5.0
O.OS
2500
J
'°:íIII[J:ilJ':i
':U
:[J
o.o
o.so
0.1
0. 2
O
1
O
1
0. 2
0.25
Figura 2.26. Histogramas de las variables de entrada al modelo.
0.1
0. 2
© RA-MA
98 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Debemos usar los parámetros del conjunto de entrenamiento para estandarizar
el conjunto de test. Así nos aseguramos de que no se filtra ningún tipo de información
del conjunto de test al conjunto de entrenamiento y podemos estar tranquilos de que
las métricas que obtengamos son fiables. Para hacer la división podemos utilizar la
función train_test_split() de skleam:
from sklearn.model_selection import train_test_split
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.3,
stratify=Y, random_state=42)
Una vez divididos los conjuntos, se estandarizan
train_mean = X_train.mean(axis=0)
train_std = X_train.std(axis=0)
X_train_std = (X_train - train_mean)/train_std
X_test_std = (X_test - train_mean)/train_std
Una vez divididos los conjuntos, podemos definir un modelo para entrenarlo
con estos datos:
model = LayerStack(X_train_std.shape[-1), [15, 15, 1), ['linear', 'linear',
'sigmoid'J, alpha=0.001, loss='bce')
loss = model.fit(X_train_std, Y_train, epochs=10)
¡ Si representamos la función de coste durante el entrenamiento parece que
nuestro modelo ha sido capaz de aprender!
1.4
1.2
1.0
.3 0.8
0.6
0.4
0.2
o
2
6
4
Épocas
Figura 2.27. Evolución de la función de coste
8
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÓN 99
Para terminar podemos calcular, por ejemplo, la precisión para el conjunto
de entrenamiento y para el conjunto de test, donde observamos un resultado bastante
bueno a pesar de que parece sufrir un poco de sobreajuste Nótese que la salida de
nuestro modelo es un numero entre cero y uno, pero esto aún no representa ninguna
clase. Un procedimiento estándar es establecer un umbral a partir del cual se
considera que predice la clase positiva y por debajo la negativa, uno o cero. Como
punto de partida se suele tomar un umbral de 0.5. Esto es lo que hacemos mediante
la función np.where():
pred_train = model(X_train_std)
pred_train = np.where(pred_train > 0.5, 1, 0)
pred_test = model(X_test_std)
pred_test = np.where(pred_test > 0.5, 1, 0)
Precisión -> [Entrenamiento] 0.98% [Test] 0.94%
MLP usado como modelizador (regresión)
En este apartado se plantea usar un perceptrón multicapa para predecir la
progresión de la diabetes a un año vista para diferentes pacientes. Las dos diferencias
principales respecto a un problema de clasificación como el anterior son:
1. La función de activación de la última capa ya no será sigmoide sino lineal.
2. La función de coste ya no será la entropía cruzada binaria, se sustituye
por el error cuadrático medio (Mean Squared Error).
El cambio de función de activación se hace porque ahora queremos predecir
únicamente un valor numérico que no está acotado, así que la función lineal es la más
adecuada. Por otro lado, al cambiar la función de activación por el error cuadrático
medio buscamos que nuestras predicciones estén lo más cerca posible del valor
deseado. Sí que es cierto que para esto podríamos utilizar quizás alguna otra función
de coste como el error absoluto medio, pero el MSE penaliza más las predicciones
más alejadas del valor real, por lo que suele dar mejores resultados.
Al igual que antes, este conjunto de datos también se puede obtener a través
de sklearn mediante la función load_diabetes():
from sklearn.datasets import load_diabetes
X, Y = load_diabetes(return_X_y=True)
© RA-MA
100 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Podemos representar de nuevo las distribuciones de las variables para
comprobar que se encuentran dentro del intervalo deseado y que, además, no tienen
escalas diferentes entre ellas, lo cual es ideal.
Distribuciones de las variables
sex
bmi
age
100
75
200
100
50
50
100
50
25
o.o
o
0.1
0.00
s2
s3
100
100
so
50
o
sl
75
50
25
bp
o.os
o.o
0.2
-0.l O.O 0.1
so
25
o.o
0.1
o
-0.1 o.o 0.1
100
so
o
o
s6
75
so
o
0.1
sS
54
100
o.o
o.o
0.1
-0.1 o.o
0.1
o
-0.1 o.o 0.1
Figura 2.28. Histograma de las funciones de entrada al clasificador.
Viendo esto podemos pensar que nuestros datos están listos para entrenar
el modelo, ¡pero nos falta comprobar la distribución de la variable objetivo! Esto es
extremadamente importante, porque si no transformamos también la variable objetivo
para que esté en el mismo rango que comentábamos, nuestro modelo será incapaz
de aprender a predecirla. Pensemos que hay que realizar un montón de iteraciones
para actualizar los pesos de forma que al multiplicarlos por un número muy pequeño
se obtenga un número muy grande. Como podemos ver, esta vez también tendremos
que pre-procesar la variable objetivo.
variable objetivo
80
70
60
50
40
30
20
10
50
100
150
200
250
300
Figura 2.29. Histograma de la variable objetivo.
350
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÚN 101
Igual que antes, primero realizamos la división entre conjunto de
entrenamiento y conjunto de test y luego estandarizamos la variable objetivo:
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.3,
random_state=42)
mean_Y=Y_train.mean(axis=0)
std_Y=Y_train.std(axis=0)
Y_train_std = (Y_train - mean_Y)/std_Y
Y_test_std = (Y_test - mean_Y)/std_Y
Podemos comprobar que ahora los rangos de la variable objetivo están
mucho más cercanos al rango deseado:
Variable objetivo
Conjunto de entrenamiento
so
20.0
40
15 O
Conjunto de test
17 5
12.5
30
10.0
20
7.5
5.0
10
2.5
-1
o.o
-1
Figura 2.30. Distribución de la variable objetivo para el conjunto de entrenamiento y el de test.
Finalmente, solo queda entrenar el modelo y comprobar cómo de bien somos
capaces de modelizar los datos:
model = LayerStack(X_train.shape[-1], [15, 15, 1], [ 'relu', 'relu',
'linear'], alpha=0.001, loss='mse')
loss = model.fit(X_train, Y_train_std, epochs=10)
© RA-MA
102 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
1.4
1.3
1.2
_J
1.1
1.0
0.9
o
2
6
4
8
Épocas
Figura 2.31. Evolución de la función de coste para el conjunto de test.
La evolución del entrenamiento parece indicar que el modelo es capaz de
aprender a predecir la variable objetivo, pero podemos calcular el error absoluto
medio (Mean Absolute Error) para cuantificar cómo de bien realiza esta predicción:
from sklearn.metrics import mean_absolute_error
pred_train = model(X_train)
pred_test = model(X_test)
Un punto muy importante a tener en cuenta cuando realizamos
transformaciones en las variables objetivo es que luego tenemos que invertirlas si
queremos trabajar con las unidades de los datos originales. En este caso, invertir la
estandarización es tan sencillo como multiplicar por la desviación estándar y sumar
la media:
pred_train_o = pred_train*std_Y + mean_Y
pred_test_o = pred_test*std_Y + mean_Y
Para terminar, podemos ver los valores del MAE tanto para el conjunto
de entrenamiento como el de test y, además, podemos comprobar cómo varían si
deshacemos el estandarizado o no:
Sin deshacer: MAE -> [Entrenamiento] 0.94 [Test] 0.91
Deshaciendo: MAE -> [Entrenamiento] 72.02 [Test] 70.07
© RA-MA
Capítulo 2. MODELOS NEURONALES MULTIFUNCIÚN 103
Al contrario que en el caso anterior, aquí no parece haber tanto sobreajuste y
los valores que se obtienen son relativamente buenos, así que podemos considerar que
hemos podido modelizar los dos tipos de problemas con nuestro propio perceptrón
multicapa personalizado.
2.1 O BIBLIOGRAFÍA
Arbib, M. A. (2002). The Handbook of Brain Theory and Neural Networks. MIT
Press.
Buduma, N., Locascio, N. (2017). Fundamentals ofDeep Learning. O'Reilly Media.
Dubey, S. R., Singh, S. K., & Chaudhuri, B. B. (2021). A Comprehensive Survey and
Performance Analysis of Activation Functions in Deep Learning. https://arxiv.
org/abs/2109.14545v1.
Goodfellow, l., Bengio, Y., Courville, A. (2016). Deep Learning. MIT Press.
Hassoun, M. (1995). Fundamentals ofArtificial Neural Networks. MIT Press.
Haykin, S. (2013). Adaptive Filter Theory. Pearson.
Haykin, S. (2008). Neural Networks and Learning Machines. Pearson.
Kohonen, T. (2001). Self-Organizing Maps. Springer.
Kung, S.Y. (2008). Digital Neural Networks. Pearson.
Nilsson, N. (2009). The Quest ofArtificial Intelligence. Standford University Press.
Patterson, J., Gibson, A (2017). Deep Learning. O'Reilly.
Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, l., & Salakhutdinov, R. (2014).
Dropout: A Simple Way to Prevent Neural Networks from Overfitting. Journal
of Machine Learning Research, 15 (56), 1929-1958. http://jmlr.org/papers/vl5/
srivastaval 4a.html.
3
MODELOS NEURONALES ORIENTADOS A
VISIÓN
3.1 PROBLEMAS DEL MLP EN IMÁGENES
El análisis de imágenes puede tener muchos usos, como el reconocimiento de
objetos por parte de robots, clasificación de enfermedades, exploración de entornos
o el reconocimiento facial, entre muchos otros. El objetivo final de estas técnicas
consiste en analizar el contenido de la información visual.
El campo de análisis de imágenes ha sufrido una auténtica revolución gracias
a las conocidas Redes Neuronales Convolucionales ( Convolutional Neural Networks,
CNN), de las cuales hablaremos en el siguiente apartado. Cabe preguntarse por qué
se plantea una nueva arquitectura teniendo un modelo tan versátil como el MLP, esto
es, ¿por qué surgieron las CNN? Básicamente surgieron debido a que la utilización
de perceptrones en este campo tiene dos inconvenientes fundamentalmente. Para
explicarlos supongamos que tenemos que procesar una imagen en color de tamaño
224x224 píxeles (que es una imagen pequeña):
1. El primer inconveniente es el número de parámetros, para un ejemplo
como el comentado, se tendría como dimensionalidad de entrada
224x224x3=150.528 entradas. Si suponemos dos capas ocultas cada una
de ellas con 200 neuronas (0.1 % con respecto a la dimensionalidad de
la capa de entrada) se tendría un total de 30.146.201 parámetros para
ajustar. Si se tienen más capas ocultas o bien la imagen de entrada es más
grande la cantidad de pesos sinápticos, parámetros del modelo, que la red
neuronal debe aprender se volvería inmanejable, ¡imagina trabajar con
imágenes de 2048x2048!
106 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
2. El segundo inconveniente es que no mantienen las relaciones espaciales,
o temporales, que se pueden tener en los datos de entrada. Esto es, si
tenemos una imagen y, de forma aleatoria, cambiamos todos los píxeles
de lugar, la imagen cambiará completamente y, sin embargo, la salida del
perceptrón no lo hará; se tendrán las mismas entradas al modelo y, por
tanto, la salida del modelo se mantendrá.
Estos dos inconvenientes conducen a un nuevo enfoque para la resolución
de las aplicaciones basadas en el procesado de imágenes. Para ello hay que bucear
en la historia de la neurociencia computacional hasta llegar a la década de los años
60; aquí el trabajo publicado por Hubel y Wiesel jugó un papel crucial para entender
el funcionamiento de la corteza visual. En esta investigación tan importante lo que
se pudo comprobar es que se activaban neuronas de la corteza cerebral de los gatos
cuando se activaban regiones específicas del campo visual, estas neuronas recibieron
el nombre de campo receptivo. Posteriormente, en 1968, publicaron otro artículo
donde hablaban de la existencia de dos tipos de células:
11" Células simples, la salida es maximizada por bordes rectos que tienen
orientaciones específicas dentro de su campo receptivo. Esta naturaleza
lineal de la respuesta es una de las características principales de las
células simples. El estímulo óptimo para una célula simple es aquel que
mejor se ajusta a la forma de su campo receptivo.
11" Células complejas, como es evidente, hace mención a cualquier célula
que no sea simple, además logran ser invariantes a la posición del
estímulo visual. Por tanto, responden a un estímulo de la orientación
adecuado independientemente de la posición dentro del campo receptivo.
Comparten con las células simples la cualidad de responder solamente a
líneas que tienen una orientación específica.
Tomando la base neurocientífica del trabajo de Hubel y Wiesel (que les
valió un premio Nobel) se produce el siguiente gran avance en el campo de modelos
neuronales artificiales aplicados en visión de la mano del Neocognitron. Este modelo
neuronal artificial vio la luz en 1980 gracias a Kunihiko Fukushima. En este trabajo
se propone que la red se autoorganice, no se necesita supervisión, adquiriéndose la
capacidad de reconocer patrones de estímulos basados en la similitud de sus formas
sin verse afectados por sus posiciones relativas. La red estaba compuesta de una capa
de entrada de los datos seguida de una conexión en cascada de varias estructuras
modulares, cada una de las cuales se compone de dos capas de células conectadas en
cascada (figura 3.1), todo hasta llegar a la capa de reconocimiento.
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 107
\
Capa de
Reconocimiento
E�1:racci6n de
contrastes
Extraccion de
bordes
Figura 3.1. Arquitectura del Neocognitron propuesto por Fukushima.
Tomando como base el modelo de Fukushima y planteando mejoras sobre
éste Yann LeCun y colaboradores, en 1989, plantean lo que se conocen actualmente
como Redes Convolucionales (ConvNets) o Redes Neuronales Convolucionales
(Convolutional Neural Networks, CNN). En su trabajo utilizaron la retropropagación
para aprender los coeficientes del modelo además de plantear la convolución como
base del modelo. Se podría decir que en aquellos años se tenía el conocimiento, pero
no se tenía la potencia computacional para aplicarlos a un gran nivel, se podían hacer
pequeñas redes con pocas convoluciones. Hubo que esperar hasta la llegada del Big
Data, GPU's y Cloud Computing para que estas arquitecturas iniciales pudieran
crecer y pudieran ser aplicadas en problemas más complejos.
3.2 ARQUITECTURA DE UNA CNN. PARTES ESENCIALES
El elemento clave de una CNN que resuelve los dos problemas del MLP
comentados en el apartado 1 ( elevado número de parámetros y pérdida de la
información espacial/temporal de los datos) es el concepto de núcleo (kernel, que es
como habitualmente se conoce) y su operación asociada, la convolución. En lugar
de tener los parámetros globales como ocurre con el MLP se tienen parámetros
compartidos por todo el modelo y estos parámetros son los que definen el kernel. A
modo de ejemplo, para comprender esta operación de convolución, se selecciona una
imagen de tamaño 4 x 4 y un kernel de tamaño de 2 x 2. Primero, el kernel se desliza
sobre toda la imagen, horizontal y verticalmente. Después de cada desplazamiento, se
determina el producto escalar entre la porción de la imagen de entrada y el kernel, donde
sus valores se multiplican y luego se suman para crear un único valor. Esta operación,
108 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
aunque se le ha llamado históricamente convolución es, realmente, una correlación
que es una operación que mide el parecido entre dos datos, en nuestro caso la parte de
la imagen donde está el kernel y dicho kernel. Lo comentado anteriormente se repite
hasta que ya no es posible deslizar más el kernel sobre la imagen. La figura 3.2 permite
observar gráficamente los cálculos ejecutados en cada paso.
Paso 1
-2
o
Paso 2
2
o
0■
-----.
0■
-----.
8B
�
Paso 3
-1
O
o
O
2
o
Figura 3.2. Operación de convolución (correlación) entre la imagen de 4x4 y un kernel de 2x2
(sólo se muestra el desplazamiento en la parte superior de la imagen).
Matemáticamente lo comentado quedaría expresado como (aquí I hace
referencia al dato de entrada, en el ejemplo de la imagen de dimensiones de 4x4 y
K al núcleo):
Ecuación 3.1
(K* I)(i,j) = Lm,n K(m, n) · I(i + n,j + m)
En muchas ocasiones se plantea añadir un término de umbral, o sesgo, por lo
que la expresión final quedaría como:
(K* J)(i,j) = Lm,n K(m, n) · I(i + n,j + m) + b
Ecuación 3. 2
Con esta aproximación se solucionan los dos problemas comentados; los
coeficientes del kernel permiten extraer conocimiento local contenido en los datos
de entrada y estos coeficientes son compartidos por toda la imagen por lo que se
reduce, en gran manera, el número de parámetros.
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 109
Es importante señalar que en el anterior ejemplo no hemos aplicado Padding,
P, que consiste en agregar filas/columnas de ceros y que se usa para mantener tamaños
entre capas y preservar la información de los bordes; un ejemplo de Padding se tiene
en la figura 3.3.
Padding
o
o
o
o
o
o
o
o
o o o o
o o o o
o 12 95 4
o
o
o o o
o o o
1
3
o o
8
2
1
o o
o 15 27
o 25 39 41 11
o
8
o
o o
o
2
o o
IJ
10 18 57 71 o o
o
o
o
o
o
o
o o o
o o o
Figura 3.3. Ejemplo de Padding=Z
Otro parámetro importante en esta operación es el Stride, S, que, básicamente,
se utiliza para reducir el tamaño del dato obtenido tras la aplicación de la etapa de
convolución. A modo de ejemplo, tenemos un Stride=2, significaría que el filtro se
movería en pasos de 2 píxeles. Obviamente es una operación muy útil cuando el
número de píxeles de los datos de entrada es muy grande (figura 3.4).
NUCLEO
-
NUCLEO
�
NUCLEO
Figura 3.4. Ejemplo de Stride=l y Stride=2. El punto de partida es la figura que queda en la parte
superior izquierda.
© RA-MA
110 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Hasta aquí se ha comentado el caso de una sola imagen/mapa de características
de entrada, ¿qué ocurre si se tienen varios?; por ejemplo, en una imagen en blanco y
negro se tiene una sola imagen mientras que en una imagen RBG se tienen 3 matrices
(una correspondiente a la componente roja, otra a la componente azul y otra a la
componente verde). En este caso se tienen 3 kernels (uno para cada componente) y
la salida final es la suma de los tres. Si se generaliza para un número determinado de
canales aparece un nuevo sumatorio en la expresión 3.2 obteniéndose lo siguiente:
z(i,j) = L p Ii= i ¿f= i Ks,1,n(P, k, l)Ip (i + l,j + k) + b
Ecuación 3.3
Tras la convolución se suele añadir una función no lineal (que, en la mayoría
de las aplicaciones, suele ser la ReLu o derivadas) por lo que la salida final de este
bloque de Padding+Stride+convolución+ReLu se tiene lo siguiente:
salida= f[L p Ii= i ¿f= i Ks,1,n(P, k, l)Ip (i + l,j + k) + b] Ecuación 3.4
siendo f la función de activación no lineal, aplicada. La salida definida por la
ecuación 3.4 se conoce como mapa de características ofeature maps. El tamaño de
la salida queda definido por la siguiente expresión:
salida =
[entrada-Kernel+Z ·Padding +
Stride
i]
Ecuación 3. 5
Donde el corchete hace referencia a la operación de truncamiento. A modo
de ejemplo, si la entrada tiene un tamaño de 28x28 y se aplica un kernel de tamaño
4x4 junto con un Padding de 1 y un Stride de 2 el tamaño del mapa de características
de salida será de 14x14.
El siguiente paso consiste en reducir el tamaño de cada mapa de características,
etapa conocida como Pooling. Esto conduce a una reducción del tamaño de los
mapas de características, lo que acelera el proceso de entrenamiento y, a su vez,
permite manejar el problema del sobreajuste. Además, también es de utilidad para
hacer más robustas las CNN a traslaciones/rotaciones en las imágenes. Para todos
los mapas de características, el Pooling se aplica a un área adyacente de tamaño p
x p, donde p es el tamaño del kernel. Hay varios tipos de métodos de Pooling: Max
Pooling, Average Pooling, Global Max Pooling y Global Average Pooling (GAP)
que se comentan a continuación:
,.. Max Pooling: Toma el valor máximo, lo que es comúnmente usado ya
que los objetos de interés probablemente producen los valores de píxel
más grandes.
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 111
Figura 3.5. Representación de aplicar Max Pooling sobre una matriz de 4x4 para obtener
una resultante de 2x2.
,,. Average Pooling: Este tipo de Pooling es diferente del anterior en
el sentido de que retiene mucha información sobre los elementos
"menos importantes" de un bloque o grupo. Mientras que Max Pooling
simplemente los descarta eligiendo el valor máximo, Average Pooling los
mezcla. Ahora la pregunta es, ¿cuándo utilizar Max Pooling o Average
Pooling?, pues depende de tu problema.
e:=>
Figura 3.6. Representación de aplicar Average Pooling sobre una matriz de 4x4 para obtener una
resultante de 2x2.
,,. Global Max Pooling: Consiste en una selección del valor más alto de la
capa de entrada del Pooling.
�
[TI
Figura 3.7. Representación de aplicar Global Max Pooling sobre una matriz de 4x4.
© RA-MA
112 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
11" Global Average Pooling: se toma el valor promedio de la capa de entrada
del Pooling.
�
Figura 3.8. Representación de aplicar Global Average Pooling sobre una matriz de 4x4.
Cabe decir que tanto Global Max Pooling como Global Average Pooling se
aplican a todo el mapa de características.
Todo lo comentado anteriormente se puede repetir en diferentes capas
funcionando como extractores de características de tal forma que, tras su aplicación
en cadena, se necesita un clasificador/modelizador según el objetivo que se persiga.
El esquema de una red CNN básica vendría dado por la figura 3.9.
Extracción de características
Mapa de corecteristicos
Clasificación
Predicción
Mopo de características
Datos de entrada
�
Capas convoluc1onales
Aplanamiento
Capas FC
Capa de salida
Figura 3.9. Esquema de una red CNN básica. Los mapas de características tienen las etapas de
convolución (con su posible Stride/Padding), Pooling y función de activación.
Por tanto, apilaríamos estas etapas en diferentes capas de tal forma que
los parámetros (coeficientes de los kernels y coeficientes del clasificador de salida
que, normalmente, es un MLP) se inicializarían de forma aleatoria durante el
entrenamiento de la red, calcularíamos el error y retropropagaríamos el error usando
una determinada función de coste para actualizar los parámetros. Esto es un resumen
de alto nivel de como funciona una CNN.
Ahora que ya hemos explicado como funcionan las CNN, sería interesante
calcular nosotros nosotros mismos el número de parámetros de una CNN a modo de
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 113
ejemplo. Supongamos una CNN (tabla 3.1) que consta de imágenes de entrada de un
tamaño de 28x28 y 1 canal (blanco y negro). Para la primera capa convolucional, el
número de filtros es de 32 y el tamaño del kernel de 5x5, aplicando un Max Pooling
de 2x2. Para la segunda capa convolucional, el número de filtros es de 32 también y
el tamaño del kernel de 3x3, también se aplica un Max Pooling de 2x2. La siguiente
capa es la entrada a un MLP que consta de 256 unidades seguida por una capa de
salida que consta de 10 unidades, una por cada clase (recordemos que el problema
es clasificar dígitos manuscritos del O al 9) . Para este ejemplo no se han aplicado
las operaciones de Padding ni Stride, para verlo de forma más sencilla, pero si
por ejemplo añades un Padding de 2x2, el cálculo sería de (28+2x2-(5-1))=28. Si
además añadimos un Stride=2, la expresión se convierte en ((28 + 2x2 - 5)/2) +1=15
(recuerda la operación de truncamiento de la ecuación 3.5).
Nombre
Tamaño
Entrada
lx28x28
Parámetros
o
Convolución
(28-(5-1))=24 -+ 32x24x24
(5x5x1 +1)x32= 832
Max pooling
32xl2xl2
o
Convolución
(12-(3-1))=10 -+ 32xl0xl0
(3x3x32+ 1)x32= 9.248
Max pooling
32x5x5
o
Densa
256
(32x5x5+1)x256=205.056
Salida
10
(256+1)x10=2.570
Total= 217.706
Tabla 3.1. Cálculo del número de parámetros del ejemplo planteado.
3.3 ARQUITECTURAS FAMOSAS
Como no podría ser de otra forma la primera arquitectura, y que se hizo más
famosa dando inicio a los grandes hitos que se han ido sucediendo hasta la fecha
en el procesado de imágenes, vendría de la mano de Yann LeCun en 1998, la cual
recibiría el nombre de LeNet-5 (figura 3.10), y constaba de 7 capas. La primera capa
consta de un tamaño para las imágenes de entrada de 32 x 32, con 6 filtros de tamaño
5 x 5 que dan como resultado una dimensión de 28x28x6. La segunda capa es un
Pooling de tamaño 2 x 2 y un Stride de 2. Por lo tanto, la dimensión de la imagen
resultante será 14x14x6. La tercera capa es una convolución con 16 filtros de tamaño
5 x 5 seguida de una cuarta capa de Pooling con un tamaño de filtro de 2 x 2 y
Stride de 2. Por lo tanto, la dimensión de la imagen resultante se reducirá a 5x5x16.
Una vez que se reduce la dimensión de la imagen, la quinta capa es una FC de 120
unidades. La sexta capa también es una capa totalmente conectada con 84 unidades.
© RA-MA
114 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
La séptima capa final será una capa de salida con una función de activación softmax
con 10 unidades, una por cada clase. En total, tendremos unos 67.706 parámetros
para entrenar.
S4:m.
C3:m. caracteristicas
16@10x10
características
16@5x5
F6:capa
Ct:mapade
84
E)ITRADAS
3h32
ConYoluciones
Submuestreo
Com·oluciones
1
Comple\am,me
Submuestreo
conectadas
l
Comple mente
conectadas
Figura 3.10. Esquema de la arquitectura LeNet-5.
Ya entrados en el siglo XXI fue muy importante la creación de lmageNet
(2006) por la profesora en Ciencias de la Computación de la Universidad de Stanford,
Fei-Fei Li. lmageNet contiene más de 15 millones de imágenes de alta resolución, lo
que la convirtió en una base de datos muy popular cubriendo casi 22000 categorías.
Es curioso saber que la profesora Fei-Fei Li, se decidió a crear esta base de datos ya
que estaba harta de no disponer de imágenes de calidad y, mientras otros se centraban
en mejorar los modelos, ella se centró en lo más importante, mejorar la calidad de los
datos. Posteriormente, y gracias a disponer de una base de datos de tan alta calidad,
en 2010 se inauguró la competición anual de clasificación de imágenes conocida
como lmageNet Large Scale Visual Recognition Challenge (ILSVRC). El recuento
total de imágenes de entrenamiento es de 1.3 millones con 1000 categorías. Los
modelos que participan en esta competición tienen que realizar tareas de detección
de objetos y clasificación de imágenes a gran escala y el modelo que logra las tasas
de error mínimas se proclama como vencedor.
3.3.1 Modelos más relevantes que han participado en el ILSVRC
l. AlexNet
AlexNet, con una tasa de error del 15.3%, fue el modelo vencedor de la
competición de 2012. Para superar las limitaciones de hardware se utilizaron dos
GPU's (NVIDIA GTX 580) en paralelo para entrenar este modelo, pese a ello fue
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 115
un entrenamiento que duró 6 días, ya que tenía nada más y nada menos que, ¡60
millones de parámetros para entrenar! El número de capas respecto a LeNet-5 se
incrementó en una, ya que en AlexNet tenemos 8 capas (5 convolucionales + 3 capas
totalmente conectadas) frente a las 7 de LeNet-5. Para mejorar el rendimiento de
las redes anteriores, se realizaron otras modificaciones mediante el uso de filtros de
mayor tamaño, llxll, 5x5 y 3x3. También utilizaron como función de activación
ReLU en vez de funciones sigmoideas como es el caso de AlexNet. De esta forma
se evitaba el problema del desvanecimiento del gradiente y la saturación de las
neuronas. La figura 3.11 muestra el esquema de esta red.
FC6 FC7 FCS
Entradas
Conv 1
Conv 2
Conv 2
Conv 3
Conv4
227x227x3
55x55x96
27x27x256
13xl3x384
13xl3x384
13xl3x256
Número
de Clases
4096 4096
Figura 3.11. Esquema de la arquitectura AlexNet.
2. ZFNet
ZFNet(figura 3.12) fue la vencedora de la competición de ImageNet en 2013,
con una tasa de error de 11.2% superando a AlexNet. Esta red tiene las iniciales de
los investigadores: Dr. Rob Fergus y Dr. Matthew D. Zeiler. Al principio se conocía
como DeconvNet, ya que utiliza una red neuronal deconvolucional multicapa,
con el objetivo de visualizar capas de características intermedias y, por tanto, el
funcionamiento del clasificador, ya que casi todas las redes que se construían estaban
hechas a través de ensayo y error, sin entender bien el funcionamiento de cada
mejora. Gracias a esto, descubrieron que solo unas pocas neuronas funcionaban,
mientras que otras habían dejado de hacerlo desde la segunda capa de la red. ZFNet
es considerada como una versión extendida de AlexNet con algunas modificaciones
en el tamaño del filtro para lograr una mayor precisión. Emplea filtros de tamaño
7x7 en vez de llxl 1 como AlexNet, con la idea de evitar la pérdida de información
de los píxeles.
Esta red aplica normalización de contraste local la cual es un tipo de
normalización que opera por píxeles, restando la media y dividiendo por la desviación
típica de sus elementos, en vez de operar sobre el rango de valores de la imagen
completa.
© RA-MA
116 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Es cierto que se mejoró la información extraída de los píxeles pero no se
redujo el coste computacional del modelo. La figura 3.12 muestra el esquema de
esta red.
Tamaño
entr:\lla=ZZ4
"
110
�-��, ,f;_,'T""
Entrada
Imagen
.,....
"v
Capa 1
13��
�
Ca11al
Cap:14
Capa S
Capa 6
Ca1rn 7
Salifb
Figura 3.12. Esquema de la arquitectura ZFNet.
3. Inception Vl (GoogLeNet)
lnception (figura 3.13) fue el modelo de aprendizaje profundo ganador de
2014 con una tasa de error de 6.67%, esta red fue diseñada por Google, por ese
motivo también se conoce como GoogLeNet. Su objetivo principal, era lograr una
alta precisión con un menor coste computacional. La figura 3.13 muestra el esquema
de los nuevos elementos planteados en esta red que es la concatenación en paralelo
de diferentes tamaños de kemels, así como el uso de convoluciones de 1 xi para la
reducción del número de parámetros.
Jx.l,nm-p,,c/zng
a) Versión Inicial de !nception
b) !nception con reducción de dimensionalidad
Figura 3.13. Esquema de la arquitectura lnception.
Se elaboró una red con 22 capas que era algo que no se había visto en ZFNet
o AlexNet. Se hicieron algunos trucos como seleccionar convoluciones de lxl junto
con funciones de activación ReLU lo que sirve para reducir el número de parámetros
al reducir el número de mapas de características aumentando así la eficiencia. A
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 117
modo de ejemplo, si tienes un número de filtros de 32 y un kernel de 5x5 tendrás 832
parámetros entrenables, mientras que si mantienes el número de filtros con un kernel
de lxl tendrás tan sólo 64 parámetros a entrenar. Esta arquitectura incorpora filtros
de diferentes tamaños (5x5, 3x3, y lxl). Además, utiliza una capa Global Average
Pooling. Por tanto, el número de parámetros también se redujo significativamente de
40 a 5 millones debido a estos ajustes. Se utilizó RMSProp como optimizador y se
aplicó una nonnalización por lotes.
4. VGG-16
Una arquitectura en la competición de 2014, con una tasa de error del 7.3%
muy próxima a lnception fue VGG-16, la cual se proclamó como subcampeona
ese mismo año. Esta red fue construida por los doctores Simonyan y Zisserman y
sus iniciales vienen de "Visual Geometry Group" (VGG). Es un modelo multicapa
que incluía diecinueve capas más que ZFNet y AlexNet. En VGG-16 (figura 3.14)
la característica principal es que, en lugar de utilizar filtros de gran tamaño como
AlexNet y ZFnet, utiliza filtros de menor tamaño de 3x3 y las capas ocultas de la red
aprovechan la función de activación ReLU. Por tanto, consiguió demostrar que los
filtros de pequeño tamaño eran igualmente eficientes que los filtros de gran tamaño.
Sin embargo, el coste computacional de VGG fue excesivo debido a sus 140 millones
de parámetros, su principal defecto.
.i
,;
uo
�..,
.i
i•
u,
.i
..,¡
¡¡,.
¡¡,.
.. �,;
,; ► t-
uo
�..,
:E:
�
::§
.i
..,¡
11
o
u
�..,
u,
..
,; ► ..
�
:E:
11
=>.
o
u
�..,
�
.i
"'""
.i
ª
�
�
u,
,;11
,;
�
�
¡¡,.
a
�
..=
t:
"'""
..g
t:
"'""
t:.,
..,11 :S.. u., u., u�g
• ► t- ► i- ► i- ► .,= �..,
0
"8
"8
�""
c..,
u
u
u
c.'.;8
0
=o
8
.!:
0
Figura 3.14. Esquema de la arquitectura VGG-16.
5. ResNet
En 2015 Xaiming He y colaboradores desarrollaron ResNet (Residual
Network), siendo el modelo ganador de aquel año con una tasa de error de
3.57%. Su objetivo principal era diseñar una red muy profunda sin el problema
del desvanecimiento del gradiente. El tipo de ResNet más común es ResNet50,
118 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
que comprende 49 capas convolucionales más una capa totalmente conectada
(fully connected, FC). El número total de parámetros de la red es 25.5 millones.
ResNet es una red que utiliza bloques residuales (figura 3.15), siendo éstos la parte
fundamental de la red, en los cuales la entrada, x, se agrega a la salida, f(x)+x, este
tipo de uniones se conoce como conexiones de salto. Mediante el uso de bloques
residuales en las capas más profundas se permite un camino al gradiente de la capa
de salida a la entrada lo que permite arquitecturas muy profundas evitando problema
de desvanecimiento de gradiente.
l
l
Pesos de la capa
f (x)
ReLU
1 Pesos de la capa
h(x) = f (x) + X
1
0
1
1
identidad
ReLU
Figura 3.15. Esquema de un bloque residual de ResNet.
Otra arquitectura muy popular cuyo objetivo era también el de eliminar el
problema de desvanecimiento de gradiente era DenseNet que iba en una línea similar
a ResNet. Uno de los problemas fundamentales de las ResNet es que tienen capas que
extraen poca información. DenseNet (figura 3.16) empleó conectividad entre capas
para tratar de solucionar este problema. Mientras que, en una red convolucional
1 (!+1)
típica, hay 1 conexión entre la capa anterior y la capa actual, en DenseNet, hay
2
conexiones directas o, lo que es lo mismo, la entrada de cada capa proviene de la
salida de las capas anteriores. Una de las ventajas es que la red es más estrecha y,
por tanto, hay menos parámetros. Además, también se incluyeron capas de Dropout
después de cada convolución.
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 119
Figura 3.16. Esquema de la arquitectura de DenseNet.
3.4 AUMENTO DE DATOS Y TRANSFERENCIA DE APRENDIZAJE
El tamaño de las redes convolucionales típicas es extremadamente grande
lo que conlleva un gran número de parámetros necesitándose una gran cantidad de
datos para su ajuste. Es una realidad que no siempre se tiene una cantidad de datos
aceptables para aplicar redes convolucionales, y para combatir este problema hay
técnicas como el Aumento de Datos (Data Augmentation) a las cuales podemos
acudir, que consiste en generar datos artificiales alterando los datos de entrada.
En imagen es, por tanto, una técnica muy poderosa, pero con la que tendremos
que llevar cuidado con las transformaciones que hacemos ya que podríamos estar
creando imágenes carentes de sentido dado el contexto del problema. Cabe señalar
la importancia de que esta tarea solo se aplica sobre el conjunto de entrenamiento,
(nunca se debe utilizar el conjunto de test). A continuación, podemos ver la imagen
de un perro a la que se han aplicado diferentes rotaciones o desplazamientos:
Figura 3.17. Diferentes estrategias de Data Augmentation aplicadas de forma aleatoria.
120 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Podemos ver que, de forma aleatoria, para una misma imagen se han aplicado
4 tipos diferentes de transformaciones y, por tanto, la CNN reconocerá cada una
de las imágenes como diferentes. Más tarde veremos en el ejercicio propuesto las
diferentes opciones de Data Augmentation, pero entre ellas se tiene: rotar la imagen,
aplicar desplazamientos horizontales y/o verticales, aplicar zoom, recortar o relleno
de píxeles entre otras opciones.
Los beneficios de utilizar esta técnica son:
11" Mejora de la precisión de predicción del modelo al tener más datos para
entrenar.
11" Reducción del sobreajuste al crear variabilidad en los datos.
11" Ayudar a resolver problemas de desequilibrio de clases en la clasificación.
11" Reducir el costoso proceso de recopilación y etiquetado de datos.
Además de estas técnicas de incremento del número de patrones se tienen
otras técnicas que ayudan a obtener modelos neuronales profundos con pocos datos.
En todos los modelos de visión las capas iniciales de las CNN realizan el mismo
papel, y conforme se avanza a la salida de la red, ésta se va especializando. Las capas
más cercanas a la entrada funcionan como extractores de características de los datos
(por ejemplo, detección de bordes en imágenes). Por ello podríamos usar las capas
profundas de los modelos ya entrenados y modificar las de salida, esta es la esencia
de la Transferencia de Aprendizaje o Transfer Learning.
Cabe resaltar dos razones para utilizar esta técnica: la reducción de los
tiempos de entrenamiento y que puedes utilizarlo en conjuntos de datos no tan
grandes. Además, hay que tener en cuenta la valiosa opinión de Demis Hassabis,
CEO de DeepMind, quien afirma que "/ think transfer learning is the key to general
intelligence. And 1 think the key to doing transfer learning will be the acquisition of
conceptual knowledge that is abstracted away from perceptual details of where you
learned itfrom".
Hay dos formas de abordar el Transfer Learning y son a través de:
11" Feature Extraction (Extracción de Características): Utiliza los parámetros
de una CNN previamente entrenada para obtener las características de una
nueva imagen, fijando los coeficientes internos del modelo y adaptando
los más externos.
11" Fine Tuning (Ajuste fino): Selecciona capas, o incluso, bloques de la
red preentrenada, suele resultar muy interesante coger las últimas capas,
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 121
que son las que tienen características más abstractas, y posteriormente se
añade una capa totalmente conectada propia o bien otro clasificador, por
ejemplo una SVM (máquina de vector soporte).
Hoy en día es bastante sencillo utilizar la técnica de Transfer Learning,
ya que Keras cuenta con muchos modelos para poder emplearlo, de hecho, todos
los que hemos visto en el punto de arquitecturas famosas y muchos más. También
tenemos TensorFlow Hub donde también hay modelos preentrenados como el de
Faster R-CNN que se verá más adelante.
3.5 OTRAS APLICACIONES DE LAS CNN
3.5.1 Detección de objetos
La detección de objetos es la técnica que trata de encontrar determinados
patrones de una determinada clase dentro de una imagen o vídeo como puedan ser
perros, gatos, coches, personas, caras o cualquier otro tipo de instancia. Con este
tipo de modelos, a diferencia de otros mencionados anteriormente como los de
clasificación, se puede detectar y clasificar más de un objeto en cada imagen, así
como indicar la ubicación de éste. El modelo devuelve la siguiente información para
cada uno de los objetos detectados en la imagen:
,. Coordenadas de las zonas delimitadas por el modelo: Estas zonas son las
regiones rectangulares donde se ubica el objeto detectado en la imagen. El
modelo devolverá las coordenadas de cada una de las zonas delimitadas
por éste en la imagen.
,. La clase del objeto detectado: Al igual que en un modelo de clasificación,
los modelos de detección de objetos, una vez detectados los mismos,
realizan un proceso de clasificación en el que asignan una clase a cada
uno de los objetos identificados.
,. Probabilidad: De nuevo, e igual que en los modelos de clasificación, se
devuelve una probabilidad en el rango [O, 1] que indica la probabilidad
de que el objeto detectado de la clase asignada esté ubicado en el lugar
marcado.
Entre los modelos de detección de objetos existen diferentes técnicas para
afrontar el problema. Una de ellas es la conocida como ventana deslizante, que
consiste en deslizar un clasificador a través de la imagen, de forma que se utilizará
122 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
cada imagen situada dentro de la zona delimitada como entrada al clasificador,
devolviendo éste la clase del objeto en dicha región. Esta es una técnica que, pese a
poder utilizarse con cualquier modelo de clasificación, se considera lenta y propensa
a errores.
Otro enfoque es el conocido como método de dos etapas. La primera etapa
es la encargada de detectar y proponer las zonas delimitadas donde podrían ubicarse
los objetos, esto se realiza con una red CNN que escanea completamente la imagen.
La segunda etapa será la encargada de tomar las regiones detectadas anteriormente y
clasificar los objetos según su clase.
Por último, otro método muy utilizado es el conocido como método de
detección de una sola etapa. En este caso es una única red CNN la encargada de
marcar las zonas delimitadas, así como de clasificar el objeto detectado dentro de
estas zonas. Este es un enfoque más rápido, pero menos preciso que el comentado
anteriormente de dos etapas.
El modelo Fast R-CNN es la evolución del modelo R-CNN, el cual fue
el primer modelo que dio unos resultados aceptables en la detección de objetos
utilizando redes convolucionales. Fast R-CNN mejoró la velocidad de detección de
su predecesor y, al mismo tiempo, aumentó la precisión de detección.
El modelo original R-CNN comenzaba el proceso mediante un primer
módulo donde se proponían regiones en las cuales pudieran existir objetos a detectar
para, más tarde, usar la función del módulo de extracción de características. Fast
R-CNN aplica las redes convolucionales para extraer características de la imagen y,
posteriormente, propone las regiones. Otro cambio sustancial entre el modelo R-CNN
clásico y su evolución, Fast R-CNN, es el de utilizar la misma red convolucional con
una capa softmax para realizar el proceso de clasificación en lugar del modelo SVM,
como ocurría en el modelo clásico. Con un solo modelo somos capaces de realizar
tanto la extracción de características como la clasificación. El gran inconveniente del
modelo Fast R-CNN es el cuello de botella que se crea al generar propuestas de región
debido a que el algoritmo de búsqueda es muy lento. Esto provoca que otro modelo
por separado tenga que generarlo aparte. Este problema se resolvería utilizando un
sistema de detección de objetos que, mediante redes convolucionales, sea capaz de
realizar la propuesta de región, esto es lo propone el modelo Faster R-CNN. Este
modelo es muy similar al Fast R-CNN, a la entrada se le proporciona una imagen y,
mediante una red convolucional, se extraen las características. La diferencia es que,
en este caso, no aplicamos un algoritmo selectivo de búsqueda para identificar las
regiones, sino que se utiliza una red RPN (red de propuesta de región por sus siglas en
inglés). Una vez predicha la región se utiliza una capa de agrupación de regiones de
interés para clasificar la imagen dentro de la región propuesta y predecir los valores
de los cuadros delimitadores. Con estas mejoras se consigue que el modelo funcione
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 123
prácticamente en tiempo real. La arquitectura del modelo Faster R-CNN se basa en
dos redes principales quedando explicado en la figura 3.18:
RED DE PROPUESTA
DE REGIÓN (Rl'N)
Extracción de las
coordenadas de las
potenciales regiones de
Interés.
{x,y,w,h)
IMAGEN DE
ENTRADA
==;>
Coordenadas
de las regiones
ele interés.
()(, y,w, h)
Clasificaci6n de la clase
detectada.
. Clase A
· Clase B
-Clase N
FASTR-CNN
----·-········-'
Figura 3.18. Arquitectura de alto nivel del modelo Faster R-CNN.
Por un lado, tenemos la red de propuesta de región o RPN, la cual se encarga
de proponer regiones de interés de los mapas de características extraídos para ser
considerados como potenciales regiones en las que se puede encontrar un objeto.
Esta red tiene dos salidas, el indicador de si existe, o no, un objeto (Objectness seo re)
y la ubicación de las zonas delimitadas por el modelo. Por otro lado, tendríamos la
red Fast R-CNN que, como su nombre indica, tiene los componentes del modelo
que recibe el mismo nombre. Contiene un extractor de características de la imagen
de entrada mediante redes convolucionales, una capa de agrupación de regiones de
interés y una capa de salida con dos capas diferentes. Por un lado, un clasificador
softmax que hará la función de clasificar las clases y, por otro lado, las predicciones
del cuadro delimitador. Aunque Faster R-CNN mejora mucho a sus predecesores, no
llega a ser un detector de objetos en tiempo real debido a que la red es demasiado
lenta durante el proceso de inferencia.
Para lograr un detector de objetos en tiempo real, en los últimos años se ha
creado una nueva arquitectura, el modelo conocido como YOLO (You Only Look
Once). Se trata, probablemente, del modelo de detección de objetos más utilizado
actualmente. Este modelo se caracteriza por su alta eficacia trabajando en tiempo real
debido a su rapidez. Sus iniciales en inglés se podrían traducir como "Solo necesitas
verlo una vez", esto es debido a que basta con pasar una única vez la imagen por
las capas convolucionales de la red neuronal para detectar los objetos. Para ello se
divide la imagen en una cuadrícula de S por S y, si el centro de un objeto cae en una
celda de la cuadrícula, ésta será la responsable de detectar ese objeto.
124 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Figura 3.19. División de la imagen en una cuadrícula de S por S.
Se realiza la predicción de un número determinado de zonas delimitadas
por el modelo, donde podrían ubicarse los objetos y se le asigna a cada una de estas
zonas un intervalo de confianza. Este intervalo refleja qué tan seguro está el modelo
de que el cuadro delimitador contiene un objeto. Cada celda de la cuadrícula predice
B cuadros delimitadores y le asigna a cada uno de ellos el intervalo de confianza
mencionado anteriormente. La red predice 4 coordenadas para cada cuadro
delimitador (x, y, w, h ), siendo x e y el centro del cuadro en relación con los límites
de la celda de la cuadrícula y w y h el ancho y alto del cuadro delimitador en relación
con la imagen completa.
Figura 3.20. Predicción de las zonas delimitadas por el modelo y asignación del intervalo de confianza
sobre la existencia del objeto.
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 125
Se filtran aquellas zonas delimitadas cuyo intervalo de confianza está por
debajo del umbral introducido al modelo que, por defecto, es de 0.6. Además, en
el caso de que haya varias zonas delimitadoras sobre un mismo objeto, se elegirán
aquellas con un mayor grado de confianza y se descartarán todas aquellas cuyo
IoU (Intersection Over Union) sea inferior a 0.5 por defecto. La métrica JoU es la
relación entre el área de la intersección de la zona delimitada y el área de su unión.
Por ejemplo, si tomamos la imagen anterior, nos quedaríamos con los siguientes
objetos detectados que cumplen las características mencionadas:
Figura 3.21. Filtrado de los objetos detectados según el intervalo de confianza y el loU.
Para el entrenamiento de este modelo existen dos fases clave. Por un lado,
la fase de Data Augmentation, explicado en el apartado 4 de este capítulo y, por otro
lado, el cálculo del error total a partir de la función de pérdidas del objeto y la clase.
Estos elementos pueden construirse para maximizar la precisión del modelo. En cada
uno de los cuadros delimitadores se realiza la predicción de las clases que puede
contener dicho cuadro. Al contrario que en el modelo Faster R-CNN, YOL0v3 no
utiliza un clasificador softma:x ya que se observó durante el desarrollo del mismo que,
usando clasificadores logísticos independientes, se obtenía un mejor rendimiento.
Además, tal y como se indica en el artículo YOL0v3: An Incremental Improvement
by Joseph Redmon Ali Farhadi, University of Washington, durante el entrenamiento
del modelo se utiliza la entropía cruzada binaria como función de coste para la
predicción de las clases. Con este enfoque se resuelve un problema que planteaba la
utilización de una función softma:x, que obligaba a suponer que cada caja contenía
una única clase, cosa que no sucede cuando se encuentran etiquetas superpuestas
(animal y perro, por ejemplo).
126 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
La función de coste del modelo YOLO está compuesta de tres partes
fundamentales. La primera de ellas se muestra en la ecuación 3.6 y es la que hace
referencia al error de coordenadas donde x e y representan el centro del cuadro
delimitador en relación con los límites de la celda de la cuadrícula, w y h el ancho
b
y alto del cuadro delimitador en relación con la imagen completa. El término n� j
b
indica si el objeto aparece, o no, en la celda y n� j indica que elj-ésimo predictor
del cuadro delimitador i es el encargado de realizar la predicción. Hay que tener
en cuenta que solo penaliza el error de coordenadas del cuadro delimitador si éste
contiene el Jo U más alto que el resto de los cuadros delimitadores en esa celda de la
cuadrícula.
A coord
¿ ¿ l�Ji [ (xi - xi) + (Yi - yJ 2
S2
B
2
+A coord
]
t, t, [ ( VW, - Vw,) + ( Jh, - Jr}]
i=O j=O
2
1:i
Ecuación 3.6
En segundo lugar, tenemos el error de confianza que, a su vez, está compuesto
de dos elementos, el mostrado en la ecuación 3.7 hace referencia a la pérdida de
confianza cuando se ha detectado un objeto, mientras que la ecuación 3.8 indica
el error de confianza cuando no se detecta ningún objeto. En estas ecuaciones, C
representa el grado de confianza del cuadro delimitador} en la celda i.
Ecuación 3. 7
52
noobj (
+ AcoordL=oL j8=o nij
[ C¡ - e i
�
)2
Ecuación 3. 8
Por último, tenemos el error de clasificación mostrado en la ecuación 3.9.
Donde p .(e) representa las probabilidades de clase condicionadas, es decir, la
probabilidad de que un objeto pertenezca a una determinada clase. Hay que tener en
cuenta que la función de coste del modelo solo penaliza el error de clasificación de
un objeto si éste se encuentra presente en esa celda de la cuadrícula.
Ecuación 3. 9
Tal y como se había indicado, la función de coste del modelo estaría
compuesta por estas tres componentes descritas y que se muestra en la ecuación 3.1 O.
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 127
"'ª
obj
,
d"' 52
A )2
/\COOr Lú=O L.j=Ollij [(xi - xi
. 17" )2
vw i
A
C • )2
,
+
+
( r;:
vh¡-
"'ª obj r.;,
,
d"'
+ (Yi- yA i ) 2] + /\COOr
L.i=O L.j=Ollij [( v W¡ 52
✓�h i ) 2 ] + Li=oL
)2
5
5
obj
noobj
8
[ ( ci- ci
+ ACOOrd Li=oL 8=Oll
=Oll
[ ( ci2
j
ij
A
obj '\'
A ( )) 2
'\'
L.cEclases (Pi ( C ) - Pi e
L.i=O 11 ,
2
j
ij
52
Ecuación 3.10
YOLO consta de una única red neuronal, encargada tanto de la detección de
las coordenadas de las regiones delimitadoras como de la clasificación de los objetos
que se encuentran en ellas. La arquitectura de la red de extracción de características
se inspiró en el modelo GoogLeNet (lnception), con la diferencia de que YOLO usa
capas de reducción lxl seguidas por capas convolucionales 3x3. A esto se le conoce
como DarkNet. En la versión YOL0v3, el modelo incorpora una variante deDarkNet
llamada DarkNet-53. Esta variante contiene 53 capas de entrada pre-entrenadas en
lmagenet y otras 53 capas dedicadas a la detección, obteniendo así una arquitectura
de 106 capas totalmente convolucionales. Esto dio a YOLOv3 un gran salto de calidad
en cuanto a la precisión en la detección frente a YOLOv2. La figura 3.22 muestra el
esquema de esta arquitectura.
IMAGEN DE
ENTRADA
ARQUITECTURA
DARKNET
CAPAS
TOTALMENTE
CONECTADAS
Coordenadas
de las regiones
de interés.
(x. y. w. h)
Clasificación de la clase
detectada.
-Clase A
-Clase B
-Clase N
Figura 3.22. Arquitectura de alto nivel del modelo YOLO.
3.5.2 Segmentación de imágenes
La segmentación de imágenes es un subdominio de la visión por ordenador
y el procesamiento digital de imágenes que tiene como objetivo agrupar regiones
similares de una imagen bajo una etiqueta descriptiva única. Puesto que tratamos con
imágenes digitales, cada una de estas regiones a agrupar serán cada uno de los píxeles.
Por otro lado, este tipo de problemas se puede entender como una extensión de los
problemas de clasificación y detección donde, además de clasificar una determinada
sección resaltamos, mediante la delineación de su contorno, la localización exacta en
la que se encuentra dicha sección a etiquetar.
© RA-MA
128 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
La visión humana hace uso de este tipo de técnicas para adquirir el
conocimiento de la realidad que nos rodea. De forma natural somos capaces de
distinguir y delimitar perfectamente los distintos elementos que observamos a nuestro
alrededor. Lo mismo sucede en infinidad de aplicaciones que emplean este tipo de
estrategias. Por ejemplo, en robótica, la segmentación se emplea para solucionar
tareas donde el robot debe agarrar un determinado objeto. Los coches autónomos
son, probablemente, las aplicaciones donde mayor importancia y complejidad
requiere la tarea de segmentación: delimitar las zonas de la imagen por donde puede
conducirse, detectar peatones y otros elementos de la vía pública ... Otra aplicación
algo más alejada de la robótica, pero donde también la segmentación juega un papel
fundamental, es la medicina, donde este tipo de técnicas permiten extraer anomalías
de las imágenes como tumores o detectar enfermedades de forma incluso más precisa
que un médico experto.
(A) Detección.
(B) Segmentación.
Figura 3.23. Diferencia entre las salidas de un modelo de detección (A) y otro de segmentación (B).
Por ejemplo, un mapa de segmentación típico sería el siguiente:
(A)
(B)
Figura 3.24. Imagen de entrada de un modelo de segmentación (A) y mapa de características generado a
la salida del mismo modelo (B).
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 129
En muchos de los problemas de segmentación de imágenes, los modelos
empleados consisten en redes neuronales con arquitecturas encoder-decoder, que
introduciremos a continuación y de las que hablaremos más en detalle en el Capítulo
5: Modelos Generativos. De acuerdo con la figura 3.24 la imagen (A) correspondería
a la entrada del encoder y la imagen (B) correspondería a la salida del decoder.
Dentro de los problemas de segmentación de imágenes podemos encontrar
dos tipos de tareas a resolver dependiendo del nivel de detalle y tipo de información
que se quiere obtener a la salida de la red neuronal: segmentación semántica y
segmentación de instancias.
1. Segmentación semántica. Resuelve la problemática de clasificar cada
uno de los píxeles de la imagen a una determinada clase en particular
sin tener en cuenta otra información o contexto. Es decir, en una imagen
donde, por ejemplo, aparecen personas en la calle, extraeríamos que todas
las personas pertenecen a la misma clase y, por otro lado, los píxeles
asociados a la calle caerían en una segunda clase. Dicho de otra forma,
la segmentación semántica no permite diferenciar instancias distintas
dentro de una misma clase, por lo que el nivel de detalle en la extracción
de información es menor en este tipo de problemas.
2. Segmentación de instancias. Aquí se clasifica cada uno de los píxeles
sobre la base de las instancias que aparecen en la imagen en lugar de
clases genéricas. Es decir, este tipo de algoritmos no permite extraer la
clase a la que pertenece cada uno de los elementos de una imagen, pero sí
son capaces de segmentar de forma independiente regiones de distintos
objetos, ya sean elementos superpuestos o muy similares. Esto es, en
el ejemplo que tratábamos anteriormente de las personas en la calle, un
modelo de segmentación de instancias sería capaz de separar a cada una
de las personas y cada uno de los elementos que aparecen en la calle.
Las tareas de segmentación no son problemas nuevos. Durante años se han
intentado buscar distintas soluciones para tratar de resolver estos problemas. Muchas
de las técnicas clásicas basaban su funcionamiento en los cambios de intensidad
entre los píxeles de las imágenes, o bien en modelos de agrupación que buscaban
similitudes entre los propios píxeles. No obstante, las técnicas más avanzadas se
desarrollan en el campo del Aprendizaje Profundo. Veremos a continuación algunas
de ellas.
En la actualidad, los modelos más empleados en tareas de segmentación
de imágenes se basan en técnicas de Aprendizaje Profundo donde, mediante redes
neuronales, se han conseguido rendimientos notables, superando a los métodos
130 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
empleados en los inicios de las tareas de segmentación. Vamos a conocer más en
detalle los modelos más avanzados basados en redes neuronales profundas para
realizar tanto tareas de segmentación semántica como de segmentación de instancias.
Comenzaremos en primer lugar con la segmentación semántica en la que
se trata de etiquetar cada píxel de la imagen con una etiqueta de una categoría
determinada, pero sin ser capaces de diferenciar instancias dentro de una misma
clase. Estas técnicas reciben, por tanto, una imagen como entrada y mostrarán a la
salida un mapa de clasificación para cada uno de los píxeles. Dentro de estas técnicas
podemos encontrar varias aproximaciones a la resolución del problema.
La primera aproximación a la segmentación semántica trata de generar
ventanas deslizantes para ir analizando partes de cada imagen y clasificar, mediante
redes CNN como las vistas hasta el momento, el píxel central de cada una de dichas
partes en base a la información que las rodea.
Sub imágenes
Imagen completa
NIÑO
NIÑO
JUGUETE
Figura 3.25. Ejemplo de un modelo de segmentación semántica dividiendo la imagen en distintas partes.
Genera a la salida una etiqueta para cada una de dichas partes e la imagen.
No obstante, esta primera aproximación supone varios problemas. El primero
es que este método resulta computacionalmente ineficiente puesto que tenemos que
clasificar cada uno de los píxeles de la imagen. Además, las ventanas deslizantes
generan imágenes independientes por lo que, con esta metodología, dos imágenes
superpuestas con características idénticas no compartirían información entre ellas.
Como segunda aproximación a la resolución de la tarea de segmentación
semántica se desarrolla una red donde todas sus capas son convolucionales, a
diferencia de la primera aproximación donde se combinaban capas convolucionales
con capas densas. Esta arquitectura permite hacer las predicciones sobre todos los
píxeles de una misma imagen en el mismo instante, generando a la salida un tensor
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 131
con la anchura y la altura de la imagen y una profundidad (canales) con tantas capas
como clases que existen para segmentar.
Input:
3xHxW
CxHxW
Output (predicciones):
HxW
Figura 3.26. Ejemplo de una red neuronal con todas las capas convolucionales, donde entra una imagen
y a la salida se obtiene el mapa de características de la segmentación.
La función de pérdida se calcula, como para cualquier problema de
clasificación, usando una función entrópica para cada uno de los píxeles de la imagen,
tomando el error promedio sobre cada uno de ellos y empleando el algoritmo de
retropropagación del mismo modo que sucede en el resto de redes neuronales.
No obstante, esta metodología, aunque supone ventajas respecto a la primera
aproximación, tampoco está exenta de problemas, puesto que sigue suponiendo un
coste computacional también elevado.
Las técnicas más avanzadas actualmente para la resolución de las tareas de
segmentación suelen emplear arquitecturas de autoencoders convolucionales. Este
tipo de modelos, tal y como se comenta en la introducción de este apartado, se verán
más en detalle en el Capítulo 5: Modelos Generativos. No obstante, realizaremos a
continuación un pequeño adelanto con sus aplicaciones en segmentación.
Entrada:
Salida (máscara de segmentación):
3 canales x altura x anchura
altura x anchura
Compresión
Expansión
Figura 3.27. Ejemplo de un autoencoder convolucional con la entrada de una imagen y la salida de la
máscara de segmentación.
© RA-MA
132 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Las arquitecturas de autoencoders constan de dos bloques: uno de
codificación de la información (encoder) y otro denominado decoder que
descomprime la información. Los autoencoders convolucionales, por tanto,
permiten la compresión de la información de las imágenes en su etapa codificadora
(encoder) para, posteriormente, reconstruirla a su tamaño original en su etapa
decodificadora (decoder). Como aprenderás en próximos capítulos, las arquitecturas
del encoder y el decoder son simétricas. Ahora bien, la reconstrucción de la imagen
supone la aplicación de nuevas técnicas; algunas de las más empleadas se exponen
a continuación.
11" Unpooling. Las técnicas de upsampling con imágenes más simples se
denominan unpooling y tratan de aumentar el tamaño de la imagen
repitiendo los valores de los píxeles de la matriz más pequeña a la de
mayor tamaño siguiendo distintas técnicas comentadas a continuación.
Salida 4x4
Salida 4x4
(B)
(A)
Figura 3.28. Ejemplos de técnicas de Unpooling.
La imagen (A) de la figura 3.28 muestra uno de los métodos más
comunes dentro de estas técnicas, el método del vecino más cercano.
Este método consiste en tomar como referencia el valor del píxel en la
matriz de menor tamaño y propagarlo a las nuevas celdas que aparecen al
incrementar el tamaño de la matriz de píxeles resultante. Por otro lado,
la forma de aumentar el número de píxeles en la imagen (B) de la misma
figura, supone repetir el valor de la matriz de menor tamaño en uno de
los píxeles de la resultante de mayor dimensión y rellenar con ceros las
nuevas celdas.
11" Max Unpooling. Este método incluye mejoras sobre los métodos
comentados anteriormente. Cuando incrementemos el tamaño de la
matriz de píxeles situaremos el nuevo valor en el lugar que ocupaba el
valor máximo en la fase de compresión, y el resto de las posiciones se
rellenarán con ceros. Así, por ejemplo, si la cuadrícula superior en la
fase de compresión tenía como valor máximo el 6, ahora en la fase de
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 133
© RA-MA
descompresión tomaremos el valor correspondiente de la matriz de menor
tamaño, en este caso el 1, y lo situaremos en la posición que ocupaba el
valor 6.
3
1
2
3
o
o
o
o
o
o
2
4
2
5
1
3
1
5
4
o
o
4
o
4
2
4
4
3
o
o
o
6
Figura 3.29. Ejemplo de la técnica Max unpooling empleado en la fase de descompresión
de los Autoencoders convolucionales.
A continuación, nos centraremos en la segmentación de instancias que
consiste en la tarea de detectar y delinear cada uno de los objetos de interés que
aparecen dentro de una imagen, diferenciando también aquellos que pertenecen a la
misma clase.
Figura 3.30. Ejemplo de segmentación de instancias donde el modelo es capaz de diferenciar los
distintos elementos dentro de una misma clase.
Como en todas las ramas del Aprendizaje Profundo, existen infinidad
de modelos que solucionan una misma tarea, cada uno con unas características
determinadas. En este punto vamos a tratar dos de los más conocidos: U-Net y Mask
R-CNN.
U-Net es uno de los modelos más conocidos en las tareas de segmentación de
imágenes mediante técnicas de Aprendizaje Profundo. Este modelo, desarrollado con
© RA-MA
134 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
fines médicos con la intención de encontrar tumores, tiene una arquitectura encoder­
decoder donde se comprime la información de las imágenes de entrada en un mapa
de características por medio del encoder para luego descomprimirlas en la etapa del
decoder. La figura 3.31 muestra el esquema de esta red, donde los bloques oscuros
corresponden a los mapas de características generados a partir de las imágenes de
entrada, mientras que los bloques blancos corresponden a bloques convolucionales
que incrementan el tamaño de los mapas de características que reciben como entrada.
1 6464
128 6464 2
om:;:::.:: � -------� Mapa de
segmentación
.J128 128
256 128 f
[HI
.J 256 256
HI
.J
512
512
1024
512
f
.... --+ CIIIIIHIII
.J
1024
f
Figura 3.31. Arquitectura de la red neuronal de segmentación U-Net propuesta en su publicación de
investigación.
No obstante, la novedad que incorpora esta red neuronal difiere de las
arquitecturas más clásicas de encoder-decoder, pues añade conexiones directas entre
capas del encoder y capas del decoder que evitan la pérdida de información al realizar
la compresión de la imagen en el encoder. Explicado de otro modo, si enviamos la
información comprimida desde una capa del encoder hasta su capa simétrica en el
decoder (recordemos que en este tipo de arquitecturas existe simetría entre las capas
de compresión con las de la etapa de descompresión de la información), podemos
mantener más información sin incrementar el coste computacional del entrenamiento
del modelo. La función de pérdida del modelo U-Net consiste en aplicar una
función de activación softmax seguida de una entropía cruzada, el objetivo de esto
es convertir cada píxel a una clase, transformando un problema de segmentación
en uno de clasificación multiclase. Dado que función de coste entrópica evalúa las
predicciones de clase para cada vector de píxeles individualmente y luego promedia
sobre todos los píxeles, todos ellos tienen la misma importancia. En el artículo de
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 135
© RA-MA
la U-net se ponderan los errores dando un mayor peso en el borde de los objetos
segmentados.
Otro de los modelos más usados en tareas de segmentación semántica
es el modelo Mask R-CNN, que extiende el modelo de detección Faster R-CNN,
introducido en el punto de Detección de Objetos de este capítulo, añadiendo una
rama para realizar la segmentación. De este modo, combinando ambas salidas, se
consigue el resultado de segmentación de las distintas instancias que se desean,
como es el caso de la figura 3.32 donde además de delimitar los coches (elementos
de una misma clase), somos capaces de diferenciar cada uno de ellos por separado.
La arquitectura del modelo Mask R-CNN puede entenderse de forma
intuitiva. Su base fundamental se sustenta sobre el modelo Faster R-CNN, que ya
conocemos del apartado de Detección de imágenes, en este mismo capítulo. Como
ya se ha comentado tanto el modelo Faster R-CNN como su antecesor, el modelo
Fast R-CNN, cuentan con dos salidas: una que permite identificar la clase a la que
pertenece una determinada instancia (objeto) y otra que determina las cajas que
definen la posición para dicha instancia. Mask R-CNN añade una nueva salida al
modelo anterior que, por medio de una red convolucional completa, genera como
salida un mapa de píxeles asociados a las clases de los objetos de la imagen.
Combinando las tres salidas, el modelo es capaz de segmentar y determinar cada
uno de los distintos objetos, así como la clase a la que pertenece.
,---1
Imagen
, ____ J
..,
V,
ro
ro
Propuesta s
de regiones
"'e
V,
ro o
u
g- !e
u
CNN
"'
Mapa de características
V,
"' ro
...
"' e -o
ro "' ro
a.
E t
ro<11
u� e
o
o u
...
"' V,
...
ro
"' e -o
ro "' ro
a. E t
roa,
u ,::ge
o
o u
...
1
1 Cl a sifica ción ',
1
ca
1
)
[ _ - � � �: _ - �
--------- ..
1
1
: Boundary box :
, (detección) ,
1 -------- 1
---------------------¡
1
1
1
1
1
Máscara de 1
segmentación 1
ro
ro
ro ·¡:;
a. "'
ro ·u
a. "'
5
a�e
5
a]e
1
1
1
,--------;1 1
1
Másca ra
, 1
•1
• ________ 2
1
8
8
1
1
1
1
1 ____________________ J
Figura 3.32. Arquitectura del modelo Mask R-CNN.
136 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
En la práctica, el modelo emplea una función de pérdida multitarea, que
combina los errores de las tres salidas: el error de clasificación, Lc1as , el error
cometido en el cálculo de las cajas que definen la posición de cada instancia, L bax' y
el error de la máscara de segmentación, L mask :
Loss = Lelas + L bax + L mask
Ecuación 3.11
Donde Lc1as se calcula como la función de pérdida de un clasificador
multiclase:
Ecuación 3.12
En segundo lugar, Lb ox se calcula como:
Lb ax = _,1,_Lk Pk'L 1 (t i - t i ')
Nbox
Ecuación 3.13
Siendo Pk la probabilidad de un determinado objeto k, Pk la clase real del
objeto, t i las coordenadas de la predicción de la caja que delimita al objeto y ti * los
valores reales de las cajas que delimitan el objeto; Ji, es el parámetro que determina el
peso que tiene la función Lb o x en el total del cálculo del Loss (en el artículo se fija un
valor a 10) y Nbox es un término de normalización (fijado en -2400 en el artículo
de Mask R-CNN). L 1 es Ll norma entre los valores reales (t/) y las predicciones (
ti). Por último, Lmask viene definida como el promedio de la función de entropía
cruzada entre las distintas máscaras k de la imagen:
Lmask = �2 L1sijsm [Yijlogyj + (1- Yij)log (1- yj)]
Ecuación 3.14
Donde k es el número de máscaras, que corresponde al número de clases a
segmentar en la imagen, y donde Yij es la etiqueta de la celda o píxel (i,j) de la región
de tamaño m x m, e
es el valor de la predicción para la celda (i,j) de la clase k
correspondiente.
Yb
3.5.3 Laboratorio
PRÁCTICA l. Clasificación de imágenes utilizando redes convolucionales
El objetivo de esta práctica es resolver el problema de clasificación de dígitos
escritos a manos (MNIST). Consiste en clasificar correctamente los dígitos del O al
9 a partir de imágenes. Es uno de los problemas más usuales para introducirse en el
campo de la clasificación de imágenes con redes neuronales, ya que es relativamente
sencillo obtener un buen resultado.
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 137
Importación de las librerías
Es una buena práctica colocar todas las importaciones de librerías al principio
de nuestro código. Para este ejercicio solamente nos hará falta utilizar tensorflow,
numpy y matplotlib:
import numpy as np
import matplotlib.pyplot as plt
import tensorílow as tf
from tensorílow.keras import layers
from tensorílow.keras.utils import plot_model
Carga de los datos
Al ser un problema tan conocido en aprendizaje profundo, la mayoría de
librerías incluyen el conjunto de datos para facilitar su acceso. Podemos obtenerlo
directamente de la librería TensorFlow con la función tf. keras. datasets. mnist.
load_data (). Esta función nos devuelve dos conjuntos de datos: el de entrenamiento
y el de test.
(X_train, Y_train), (X_test, Y_test) = tf.keras.datasets.mnist.load_data()
X_train.shape, Y_train.shape, X_test.shape, Y_test.shape
> ((60000, 28, 28), (60000,), (10000, 28, 28), (10000,))
Exploración de los datos
Siempre es recomendable inspeccionar los datos con los que vamos a trabajar.
Al utilizar imágenes esto es especialmente sencillo, ya que solamente tenemos que
representarlas. Las dos cosas en las que deberíamos fijamos son:
Las dimensiones de las imágenes.
El rango de valores de los píxeles.
11"' El formato de las etiquetas.
11"'
11"'
Para comprobar las dimensiones podemos utilizar el atributo . shape,
mientras que para observar el rango de valores podemos realizar un histograma o
representar la imagen junto a su mapa de color.
© RA-MA
138 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Las capas convolucionales que utilizaremos requieren que las imágenes de
entrada tengan unas dimensiones determinadas: (alto, ancho, canales). Además, si
trabajamos con imágenes muy grandes puede ser recomendable reducir su tamaño
para acelerar los cálculos. En este caso, las dimensiones de las imágenes que hemos
importado son (28,28). Esto sucede porque las imágenes de este conjunto de datos
están en blanco y negro, por lo que solamente tienen un canal y han decidido no
incluirlo. Podemos añadir este canal de color utilizando la función np. expand_
dims (), que permite añadir dimensiones "fantasma" a cualquier np. array:
X_train = np.expand_dims(X_train, axis=-1)
X_test = np.expand_dims(X_test, axis=-1)
X_train.shape, X_test.shape
> ((60000, 28, 28, 1), (10000, 28, 28, 1))
Para observar el rango de valores de las imágenes vamos a optar por
representar una de ellas junto a su mapa de color:
Muestra del conjunto de entrenamiento
5
250
200
10
150
15
100
20
50
25
5
10
15
20
25
Figura 3.33. Imagen ejemplo del conjunto de datos MNIST.
Como se puede ver, los valores de los píxeles están en el rango [0,255].
Como las redes neuronales trabajan mejor con datos en el rango [0,1], dividimos por
255 para normalizar todas las imágenes al rango [0,1]:
X_train = X_train / 255
X_test = X_test / 255
plt.figure()
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN
139
plt.imshow(X_train[0])
plt. colorbar()
plt. show()
Muestra del conjunto de entrenamiento
LO
5
0.8
10
0.6
15
0.4
20
0.2
25
5
10
15
20
25
o.o
Figura 3.34. Imagen ejemplo del conjunto de datos MNIST normalizada.
Por último, nos queda ver en qué formato están las etiquetas. Esto es
importante porque, como veremos más adelante, hay que elegir la función de coste
en función de si tenemos etiquetas one-hot o etiquetas discretas. Lo más sencillo es
ver cualquiera de las etiquetas del conjunto:
Y_train[0]
> 5
Vemos que los resultados obtenidos son números discretos.
Separación en entrenamiento-validación-test
Para poder ajustar algunos hiperparámetros del modelo es conveniente
utilizar también un conjunto de validación, así que tomaremos 10000 muestras del
conjunto de entrenamiento y las pasaremos a un nuevo conjunto de validación. De
esta forma obtenemos los tres conjuntos con los que vamos a trabajar:
Entrenamiento: 50000 muestras.
Validación: 10000 muestras.
11"' Test: 10000 muestras.
11"'
11"'
140 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Hemos elegido esta proporción para que el conjunto de validación y el de
entrenamiento tengan el mismo tamaño. Esto es simplemente una forma de aseguramos
de que los resultados que obtengamos en validación puedan ser representativos del test.
Podríamos hacer esta división de manera manual, pero hay que asegurarse de que la
distribución de las clases se mantiene lo más parecida posible en todos los conjuntos,
así que esta división tiene que hacerse de manera estratificada. Esto quiere decir que
se tendrá en cuenta la etiqueta de cada imagen para mantener la proporción al hacer
la división. La librería sklearn tiene implementada la función sklearn. model_
selection.train_test_split() que permite hacer esta división fácilmente:
X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train,
test_size=10000, stratify=Y_train, random_state=42)
X_train.shape, X_val.shape, X_test.shape
> ((50000, 28, 28, 1), (10000, 28, 28, 1), (10000, 28, 28, 1))
Definición del modelo
Una vez ya hemos inspeccionado y preparado nuestros datos, solamente nos
queda definir el modelo que vamos a utilizar. Se pueden utilizar una gran cantidad de
arquitecturas diferentes para un ejercicio de clasificación, pero todas tienen que tener
algo en común: la salida del modelo tiene que ser una probabilidad (clasificación
binaria, la probabilidad de la clase positiva normalmente) o una distribución de
probabilidad (clasificación multi-clase, la probabilidad de pertenecer a cada una de
las clases). A la hora de implementarlo, esto quiere decir que nuestra última capa
tiene que ser una capa densa (layers. Dense ) con una o varias neuronas y función
de activación sigmoide o softmax respectivamente.
En nuestro caso particular estamos planteando un problema de clasificación
multi-clase (tenemos que clasificar entre 10 dígitos distintos), por lo que nuestra
última capa será una capa densa de 1O neuronas con activación sigmoide.
Además de la arquitectura, tendremos que elegir una función de coste
acorde: la entropía cruzada. TensorFlow implementa dos variantes de esta función
de coste: tf. los ses. BinaryCrossentropy() para el caso binario, y tf.losses.
CategoricalCrossentropy()(ytf.losses.SparseCategoricalCrossentropy())
para el caso multiclase. Llama la atención que haya dos variantes para el caso multi­
clase. Esto es porque las funciones cambian dependiendo de cómo introduzcamos las
etiquetas al modelo: si utilizamos etiquetas en formato one-hot utilizaremostf.losses.
CategoricalCrossentropy( ), mientras que si nuestras etiquetas son números
discretos(O, 1, 2, 3 ...) utilizaremostf.losses.SparseCategoricalCrossentropy().
Como hemos visto antes, la forma de nuestras etiquetas nos indica que debemos elegir
tf.losses.SparseCategoricalCrossentropy().
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 141
De cara a la definición de los modelos, Keras permite utilizar tres
aproximaciones que difieren en la sencillez y la personalización de los modelos:
,- API Secuencial: Es la más sencilla de utilizar, pero solo permite definir
modelos donde las capas se conectan una detrás de la otra.
,- API Funcional: Permite mucha más flexibilidad que la API Secuencial,
pero requiere que especifiquemos qué capas se conectan entre sí y cómo
lo hacen.
,.. Herencia de clases: Permite un control total de lo que pasa en el modelo.
Solamente nos hará falta utilizarla en casos específicos donde no podamos
utilizar las otras dos APis. Puede dar algunos problemas a la hora de
guardar y cargar los modelos.
En general, la mayoría de problemas los podemos afrontar utilizando las dos
.
.
pnmeras opciones.
La arquitectura que vamos a plantear en este ejercicio es muy sencilla, así
que podemos utilizar la API Secuencial. Para ello instanciamos un objeto tf. keras.
models. Sequential (), al cual le pasamos una lista con las capas que queremos que
tenga el modelo. Esta clase ajusta automáticamente las dimensiones de las matrices
de pesos de cada capa, pero necesita conocer las dimensiones de los datos de entrada.
Esta información se la daremos mediante el parámetro input_shape en la primera
capa. El resto de capas no necesitan este parámetro.
A modo de apunte, kernel_size y pool_size pueden tomar tanto
valores únicos como tuplas ( dl, d2). Si introducimos un único valor d, lo tomará
internamente como una tupla (d, d). Esto quiere decir que utilizaremos tamaños
de kernel y pooling simétricos. Si queremos utilizar alguno que no sea simétrico
tendremos que introducir directamente (dl, d2).
model = tf.keras.models.Sequential([
layers.Conv2D(filters=32, kernel_size=3, activation="relu",
input_shape=(28,28,1)),
layers.MaxPool2D(pool_size=2),
layers.Conv2D(filters=64, kernel_size=3, activation="relu"),
layers.MaxPool2D(pool_size=2),
layers.Flatten(),
layers.Dense(10, activation="softmax")
])
142 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Una vez hemos definido la arquitectura del modelo, tendremos que
compilarlo para especificar una función de coste (que ya hemos explicado antes),
un optimizador (lo estándar suele ser elegir Adam) y las métricas que queremos
monitorizar durante el entrenamiento (precisión en este caso). Además, podemos
utilizar el método . summary() para obtener un resumen del modelo que hemos
creado. También podemos utilizar la función tf. keras. utils. plot_model () para
obtener un resumen más visual, aunque requiere de la instalación de algunas otras
librerías externas.
Podemos utilizar tanto las versiones "objeto" de los optimizadores y las
funciones de pérdida como sus nombres asociados. Al utilizar los nombres no
podremos cambiar parámetros como el learning_rate, pero es suficiente para este
ejercicio.
Ejercicio
Utiliza
los
objetos
tf. optimizers.Adam()
y
tf. losses.
SparseCategoricalCrossentropy() y cambia alguno de sus parámetros, por
ejemplo el learning_rate.
model.compile(optimizer="adam",
loss="sparse_categorical_crossentropy", metrics=["accuracy"])
model.summary()
> Model: "sequential_l"
> __________________________
> Layer (type) Output Shape Param #
> ----------------------------------------------------------------> conv2d_6 (Conv2D) (None, 26, 26, 32) 320
> __________________________
> max_pooling2d_6 (MaxPooling2 (None, 13, 13, 32) 0
> __________________________
> conv2d_7 (Conv2D) (None, 11, 11, 64) 18496
> __________________________
> max_pooling2d_7 (MaxPooling2 (None, 5, 5, 64) 0
> __________________________
> flatten_3 (Flatten) (None, 1600) 0
> __________________________
> dense_3 (Dense) (None, 10) 16010
> =================================================================
> Total params:34,826 Trainable params: 34,826 Non-trainable params: 0
> __________________________
plot_model(model,show_shapes=True,dpi=300)
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 143
conv2d_input: lnputLayer
conv2d: Conv2D
input:
[(None, 28, 28, l)]
output:
[(None, 28, 28, 1 )]
input:
t
(None, 28, 28, l)
output:
(None, 26, 26, 32)
t
max_pooLing2d: MaxPooLing2D
conv2d - 1: Conv2D
t
t
input:
(None, 26, 26, 32)
output:
(None, 13, 13, 32)
input:
(None, 13, 13, 32)
output:
(None, 11, 11, 64)
max_pooling2d_l MaxPooling2D
flatten: F latten
dense: Dense
input:
(None, 11, 11, 64)
output:
(None, 5, 5, 64)
input:
(None, 5, 5, 64)
output:
t
(None, 1600)
input:
(None, 1600)
output:
(None, 10)
Figura 3.35. Esquema del modelo utilizado.
144 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Ejercicio
Define el mismo modelo, pero utilizando la API Funcional.
Entrenamiento del modelo
Llegados hasta aquí solo resta utilizar el método . fit () del modelo para
entrenarlo. Este método nos permite utilizar un conjunto de validación, validation_
data, durante el entrenamiento para observar cómo de bien generaliza nuestro
modelo. Esto resulta muy útil cuando queremos utilizar técnicas más avanzadas
como early stopping, que detienen el entrenamiento cuando el modelo empieza a
sobreajustar al conjunto de entrenamiento.
Los parámetros batch_size y epochs hacen referencia a la cantidad de
imágenes que se utilizarán para cada ajuste de los pesos y las veces que recorremos
el conjunto de datos respectivamente. Normalmente se recomienda utilizar un
batch_size lo más grande posible para exprimir al máximo el hardware y acelerar
el entrenamiento, aunque vale la pena experimentar con distintos valores porque está
estrechamente relacionado con el learning_rate. El parámetro verbose indica la
cantidad de información que queremos ver por pantalla durante el entrenamiento.
Es interesante en modelos que se entrenan rápido como éste, pero en modelos con
entrenamientos largos o con muchas épocas no será demasiado útil.
Además, el método . fit () devuelve un objeto particular que permite acceder
a todas las métricas que se han calculado durante el entrenamiento. Es muy útil para
representar las dinámicas de entrenamiento del modelo.
history = model.fit(X_train, Y_train,
batch_size=64, epochs=10,
validation_data=(X_val, Y_val))
> Epoch 1/10 782/782 [-----------------] - 13s 7ms/step - loss: 0.4946
> - accuracy: 0.8592 - val_loss: 0.0896 - val_accuracy: 0.9733
> Epoch 2/10 782/782 [-----------------] - 3s 4ms/step - loss: 0.0748
> - accuracy: 0.9768 - val_loss: 0.0626 - val_accuracy: 0.9808
> Epoch 3/10 782/782 [-----------------] - 3s 4ms/step - loss: 0.0478
> - accuracy: 0.9845 - val_loss: 0.0572 - val_accuracy: 0.9828
> Epoch 4/10 782/782 [-----------------] - 3s 4ms/step - loss: 0.0409
> - accuracy: 0.9866 - val_loss: 0.0636 - val_accuracy: 0.9822
> Epoch 5/10 782/782 [-----------------] - 3s 4ms/step - loss: 0.0334
> - accuracy: 0.9899 - val_loss: 0.0465 - val_accuracy: 0.9870
> Epoch 6/10 782/782 [-----------------] - 3s 4ms/step - loss: 0.0253
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 145
> - accuracy: 0.9921 - val_loss: 0.0506 - val_accuracy: 0.9857
> Epoch 7/10 782/782 [-----------------] - 3s 4ms/step - loss: 0.0216
> - accuracy: 0.9926 - val_loss: 0.0469 - val_accuracy: 0.9868
> Epoch 8/10 782/782 [-----------------] - 3s 4ms/step - loss: 0.0169
> - accuracy: 0.9943 - val_loss: 0.0471 - val_accuracy: 0.9864
> Epoch 9/10 782/782 [-----------------] - 3s 4ms/step - loss: 0.0132
> - accuracy: 0.9960 - val_loss: 0.0508 - val_accuracy: 0.9852
> Epoch 10/10 782/782 [-----------------] - 3s 4ms/step - loss: 0.0136
> - accuracy: 0.9954 - val_loss: 0.0549 - val_accuracy: 0.9866
Una vez completado el entrenamiento representamos las dinámicas de
entrenamiento a partir de history. De forma sencilla, history tiene un atributo
. history, que es un diccionario usual de Python, dónde se guardan los diferentes
valores de las métricas que se muestran durante el entrenamiento.
Loss del modelo
Tasa de acierto del modelo
-.- Entrenamiento
-• - Validación
0.20
99
98
·�ro 97
-oQ)
ro 96
0.15
U\
U\
0.10
� 95
O.OS
94
o
2
4
Épocas
6
8
-.- Entrenamiento
-• - Validación
o
2
4
Épocas
6
8
Figura 3.36. Dinámicas de entrenamiento.
De la figura anterior podemos extraer dos conclusiones:
11"'
El modelo parece adecuado para resolver esta tarea: ha sido capaz de
aprenderse el conjunto de entrenamiento. Esto que parece una obviedad es
algo importante. Cuando nos enfrentamos a un problema nuevo es posible
que no tengamos claro, por ejemplo, cómo de grande tendrá que ser
nuestro modelo. Una forma de descubrirlo puede ser intentar sobre ajustar
146 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
primero al conjunto de entrenamiento y ver si es capaz de aprendérselo,
esto nos dirá si el modelo parece adecuado para la tarea que tenemos que
resolver. Una vez tengamos esto claro ya pasamos a la fase de ajuste donde
intentamos obtener el mejor resultado posible en validación.
11"'"
Se produce un cierto sobreajuste apreciable porque las gráficas de
entrenamiento y validación se separan conforme aumentan las épocas.
Para tratar de resolver estos problemas podemos utilizar técnicas de
regularización (Ll, L2 y Dropout), técnicas de aumento de datos, etc.
Antes de acabar el ejercicio restarían hacer dos cosas:
11"'"
11"'"
Evaluar nuestro modelo en el conjunto de test.
Guardar el modelo final para poder utilizarlo en otras ocasiones sm
necesidad de volverlo a entrenar.
Evaluación del modelo
El método . evaluate() permite calcular todas las métricas que hemos
compilado anteriormente en el modelo sobre el conjunto de datos que queramos. Es
una buena práctica evaluar el modelo en los diferentes conjuntos de datos al acabar el
entrenamiento para obtener una métrica final del trabajo realizado. Este método nos
devuelve una lista con las métricas, en este caso la función de coste y la precisión:
eval_train ; model.evaluate(X_train, Y_train, verbose;0)
eval_val ; model.evaluate(X_val, Y_val, verbose;0)
eval_test ; model.evaluate(X_test, Y_test, verbose;0)
print("[Entrenamiento] Loss: {:0.3f} 1 Accuracy:
{:0.3f}".format(*eval_train))
print("[Validación] Loss: {:0.3f} 1 Accuracy: {:0.3f}".format(*eval_val))
print("[Test] Loss: {:0.3f} 1 Accuracy: {:0.3f}".format(*eval_test))
> [Entrenamiento] Loss: 0.010 1 Accuracy: 0.997
> [Validación] Loss: 0.048 1 Accuracy: 0.987
> [Test] Loss: 0.036 1 Accuracy: 0.989
Así podemos ver que el modelo rinde ligeramente mejor en el conjunto de
test que en el de validación. En líneas generales, hemos obtenido un resultado muy
bueno y podemos considerar el problema como resuelto.
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 147
Guardar el modelo
En este ejercicio el modelo utilizado es relativamente rápido de entrenar,
pero en problemas reales podemos encontrar tiempos de ejecución mucho más
largos. Esto quiere decir que es muy importante saber guardar el modelo una vez
ha sido entrenado para poder utilizarlo sin tener que repetir el entrenamiento cada
vez. Para ello podemos utilizar el método . save(). Hay varias formas posibles de
guardar los modelos, aunque la más sencilla es guardarlos en formato . hS:
model.save("model_cnn_mnist.hS")
Una vez guardado podemos volver a cargarlo con la función tf. keras.
models. load_model() y utilizarlo normalmente:
model_loaded = tf.keras.models.load_model("model_cnn_mnist.hS")
antes:
Podemos comprobar que obtenemos los mismos resultados que obteníamos
eval_train = model_loaded.evaluate(X_train, Y_train, verbose=0)
eval_val = model_loaded.evaluate(X_val, Y_val, verbose=0)
eval_test = model_loaded.evaluate(X_test, Y_test, verbose=0)
print("[Entrenamiento] Loss: {:0.3f} 1 Accuracy: {:0.3f}".format(*eval_train))
print("[Validación] Loss: {:0.3f} 1 Accuracy: {:0.3f}".format(*eval_val))
print("[Test] Loss: {:0.3f} 1 Accuracy: {:0.3f}".format(*eval_test))
> [Entrenamiento] Loss: 0.010 1 Accuracy: 0.997
> [Validación] Loss: 0.048 1 Accuracy: 0.987
> [Test] Loss: 0.036 1 Accuracy: 0.989
PRÁCTICA 2. Data Augmentation y Transfer Learning
En la práctica anterior hemos visto la aproximación más sencilla para
resolver un problema de clasificación de imágenes utilizando redes convolucionales.
En esta práctica vamos a ir un paso más allá introduciendo los conceptos de Data
Augmentation y Transfer Learning para resolver un problema de clasificación en el
que tendremos que diferenciar entre perros y gatos. Aunque puede parecer sencillo,
148 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
este problema es bastante más complicado que el anterior porque existen perros y
gatos muy diferentes.
Para comprobar cómo de efectivas son las nuevas técnicas primero
estableceremos una baseline sin utilizarlas. Esta es una práctica muy habitual en
la experimentación y muy importante, ya que es necesario que seamos capaces de
determinar cuánto mejora o empeora un modelo al realizar ciertos cambios.
Importación de las librerías
Antes de empezar a trabajar, cargamos todas las librerías que vamos a
necesitar.
from collections import Counter
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import tensorílow as tf
from tensorílow.keras import layers
from tensorílow.keras.utils import plot_model
import tensorílow_datasets as tfds
Carga de los datos
Para poder centrarnos en la parte interesante del ejercicio utilizaremos el
dataset que se incluye en tensorflow_datasets. Podemos cargarlo utilizando la
función load (). No todos los conjuntos de datos alojados en esta librería devuelven
lo mismo al cargarlos, hay algunos que tienen varias versiones, otros que ya están
divididos en entrenamiento y test, etc. Este en particular no tiene ninguna división,
pero podemos utilizar el parámetro split para cargar directamente el dataset
dividido, teniendo en cuenta que las divisiones se introducen como una lista de
strings.
train, validation, test = tfds.load('cats_vs_dogs', with_info=False,
data_dir="./data/",
split=['train[:70%]', 'train[70%:85%]',
'train[85%:]'])
print(f"Muestras de entrenamiento: {len(train)}.")
print(f"Muestras de validación: {len(validation)}.")
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 149
print(f"Muestras de test: {len(test)}.")
> Muestras de entrenamiento: 16283.
> Muestras de validación: 3490.
> Muestras de test: 3489.
Esta función nos devuelve tres objetos PrefetchDataset de TensorFlow.
Estos objetos pueden parecer un poco incómodos, pero tienen características muy
útiles con las que nos tendremos que acostumbrar a trabajar. La más útil para nosotros
es que permiten cargar los datos en memoria únicamente cuando se van a utilizar, por
lo que podemos trabajar con conjuntos de datos muy grandes.
Para agilizar los entrenamientos y el desarrollo de la práctica vamos a utilizar
un subconjunto de los datos. Se recomienda al lector repetir la práctica con todos los
datos y comprobar cómo afecta la cantidad de datos a los resultados que se obtienen.
Para obtener un subset a partir de un tf. data. Dataset podemos utilizar el
método . take(), que permite extraer tantos elementos como queramos del dataset
original. Podemos utilizar . shuffle() primero para barajar los ejemplos en caso de
que lo consideremos oportuno. Vamos a realizar el ejercicio utilizando 4000 muestras
de entrenamiento, 500 de validación y 500 de test. Todos los métodos que se aplican
sobre un 'tf.data.Datasef devuelven otro objeto del mismo tipo, por lo que es muy
cómodo realizar este tipo de operaciones y encadenarlas.
N_train = 8000
N_val = 1000
N_test = 1000
train_red = train.take(N_train)
validation_red = validation.take(N_val)
test_red = test.take(N_test)
Exploración de los datos
Estos objetos no se pueden indexar, pero sí que se puede iterar sobre
ellos. Además, cada elemento es un diccionario de Python que contendrá diferente
información en función del dataset que hayamos cargado. Veamos qué contiene el
nuestro:
for sample in train_red:
break
© RA-MA
150 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
sample.keys()
> dict_keys( [ 'image', 'image/filename', 'label'])
Contiene tres elementos:
11" La imagen en cuestión.
11" La ubicación de la imagen en el sistema.
11" La etiqueta de la imagen.
Más adelante, en la práctica de segmentación, trabajaremos con otro conjunto
de datos que incluye más información todavía. Vamos a representar nuestra imagen:
plt.figure()
plt.imshow(sample['image'])
plt.title(sample['label'].numpy())
plt.show()
o
50
100
150
200
250
o
50
100
150
200
250
300
Figura 3.37. Imagen de muestra del conjunto de datos.
Como ya sabemos, es muy importante conocer los rangos en los que se
encuentran los valores de los píxeles que vamos a introducir en nuestra red neuronal.
Al tratarse de una imagen a color sabemos que vamos a trabajar con tres canales, así
que tenemos que comprobar el rango de los tres canales:
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 151
maxs = sample['image'].numpy().max(axis=(0,1))
mins = sample['image'].numpy().min(axis=(0,1))
ranges = [(m, M) for m, M in zip(mins, maxs)]
print("Rangos: {}, {}, {}".format(*ranges))
> Rangos: (0, 255), (0, 255), (0, 255)
También es buena idea comprobar las dimensiones de la imagen con
la que estamos trabajando y comprobar si todas las imágenes tienen las mismas
dimensiones. Esto último es algo especialmente crítico, ya que podría ocasionarnos
errores en el futuro que pueden ser difíciles de identificar.
sample['image'].shape
> Tensor5hape([262, 350, 3])
Una forma rápida de identificar si tenemos diferentes tamaños de imágenes
es recorrer el dataset guardando en una lista el atributo . shape de la imagen. Luego
obtenemos los valores únicos con set() y contamos la cantidad de elementos. Este
método es curioso porque presenta un uso del objeto set(), aunque en realidad nos
valdría simplemente con comprobar las dimensiones de dos imágenes consecutivas
y comprobar si son iguales o no.
shapes = []
for s in train_red:
shapes.append(s['image'].numpy().shape)
len(set(shapes))
> 2817
Ejercicio
Para elegir una transformación adecuada puede ser útil conocer la
distribución de dimensiones de nuestro conjunto de datos. Obtén la cantidad de
veces que aparece cada dimensión distinta.
Por último, vamos a comprobar también que las etiquetas que tenemos son
las correctas y su distribución. Para este problema esperaríamos tener solamente dos
etiquetas, O y 1, que representasen gato o perro. Comprobémoslo:
labels_train = [sample['label'].numpy() for sample in train_red]
labels_val = [sample['label'].numpy() for sample in validation_red]
152 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
labels_test = [sample['label'].numpy() far sample in test_red]
print(Counter(labels_train))
print(Counter(labels_val))
print(Counter(labels_test))
> Counter({l: 4017, 0: 3983})
> Counter({0: 518, 1: 482})
> Counter({l: 510, 0: 490})
Vemos que solamente tenemos las dos etiquetas que esperábamos y, además,
parecen estar distribuidas de forma más o menos equitativa, por lo que no deberíamos
tener problema de desbalanceo de clases.
Preprocesado de los datos
Como era de esperar, las imágenes se encuentran dentro del rango [0,255],
por lo que tendremos que normalizarlas. Además, las imágenes tienen diferentes
tamaños. Por otra parte, tal y como están los conjuntos de datos no podemos
utilizarlos para el entrenamiento (no podemos utilizar diccionarios en el método
. fit()).Tenemos que transformarlos en conjuntos que solo tengan las imágenes para
poder introducirlos como argumentos del método . fit() y poder entrenar fácilmente
los modelos que definamos. Para ello vamos a definir una función prepare_data()
que aplicaremos a todo el conjunto mediante el método . map(). Este método es
muy útil porque permite aplicar funciones a todo el conjunto de datos de forma muy
sencilla.
La función prepare_data() simplemente se encargará de extraer la imagen
y la etiqueta correspondiente de cada diccionario y devolverla. También podemos
aprovechar para normalizarlas y transformar las imágenes para que tengan todas el
mismo tamaño.
Cada método que se aplica sobre los objetos Dataset de TensorFlow
devuelve otro Dataset', lo que facilita mucho aplicar transformaciones consecutivas
para preparar nuestros datos de la forma más cómoda.
def prepare_data(sample, reshape_dims={128, 128)):
image
sample['image']
label sample['label']
image/255
image
image = tf.image.resize(image, size=reshape_dims)
return image, label
train_rdy = train_red.map(prepare_data)
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 153
validation_rdy = validation_red.map(prepare_data)
test_rdy = test_red.map(prepare_data)
Siempre es buena idea comprobar si hemos obtenido el resultado esperado:
for img, label in train_rdy:
maxs = img.numpy().max(axis=(0,1))
mins = img.numpy().min(axis=(0,1))
ranges = [(m, M) for m, M in zip(mins, maxs)]
print("Rangos: {}, {}, {}".format(*ranges))
print(f"Dimensiones : {img.shape}")
break
Rangos: (0.0, 1.0), (0.017385684, 0.9953764), (0.0, 0.99994713)
Dimensiones : (128, 128, 3)
Una vez que hemos preparado los conjuntos de entrenamiento podemos
pasar a la definición de nuestro modelo baseline.
Generalidades
Durante esta práctica vamos a entrenar y comparar muchos modelos. Para
realizar estas comparaciones vamos a representar sus dinámicas de entrenamiento
(es una buena forma de apreciar el sobre-entrenamiento) y también calcularemos
sus métricas (pérdida y precisión) en los diferentes conjuntos que hemos creado
anteriormente. Por todo esto es buena idea definir previamente unas funciones
genéricas que nos sirvan para hacer esto de forma mucho más rápida y limpia.
def plot_history(history):
plt.subplot(l,2,1)
plt.plot(history.history['loss'J, 'k-o',
label = "Entrenamiento")
plt.plot(history.history['val_loss'], 'k--*',
label = "Validación")
plt.ylabel('Loss')
plt.xlabel('Épocas')
plt.title('Loss del modelo')
plt.legend()
plt.subplot(l,2,2)
plt.plot(np.array(history.history['accuracy'])*100, 'k-o',
label="Entrenamiento")
plt.plot(np.array(history.history['val_accuracy'])*100, 'k--*',
154 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
label="Validación")
plt.ylabel('% Tasa de acierto')
plt.xlabel('Épocas')
plt.title('Tasa de acierto del modelo')
plt.legend()
return
def evaluate_model(model, train, validation, test,
batch_size=256, verbose=True):
eval_train = model.evaluate(train.batch(batch_size), verbose=0)
eval_val = model.evaluate(validation.batch(batch_size), verbose=0)
eval_test = model.evaluate(test.batch(batch_size), verbose=0)
if verbose:
print("[Entrenamiento] Loss: {:0.3f} 1 Accuracy: {:0.3f}".format(*eval_
train))
print("[Validación] Loss: {:0.3f} 1 Accuracy: {:0.3f}".format(*eval_
val))
print("[Test] Loss: {:0.3f} 1 Accuracy: {:0.3f}".format(*eval_test))
return eval_train, eval_val, eval_test
Modelo Baseline (Sin Data Augmentation ni Transfer Learning)
Como base vamos a tomar el modelo utilizado en la práctica anterior, definido
mediante la API Secuencial, pero introduciremos dos cambios fundamentales:
,. Ahora estamos trabajando en un problema binario, por lo que la capa
final tendrá que ser una Dense de una neurona a la cual aplicaremos la
función sigmoide. De esta forma obtenemos un valor que representa la
probabilidad de pertenecer a la clase positiva, es decir, la probabilidad de
que la etiqueta sea 1.
,. Tenemos que utilizar una función de coste diferente. Ahora utilizaremos
tf.losses.BinaryCrossentropy().
model = tf.keras.models.Sequential([
layers.Conv2D(filters=32, kernel_size=3, activation="relu",
padding='same', input_shape=(128,128,3)),
layers.Conv2D(filters=32, kernel_size=3, activation='relu',
padding='same'),
layers.MaxPool2D(pool_size=2),
layers.Conv2D(filters=64, kernel_size=3, activation="relu",
padding='same'),
layers.MaxPool2D(pool_size=2),
layers.Flatten(),
© RA-MA
])
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 155
layers.Dense(l, activation="sigmoid")
model.compile(optimizer="adam", loss="binary_crossentropy",
metrics=[ naccuracy"])
model.summary()
Model: nsequential"
Layer (type) Output Shape Param #
conv2d (Conv2D) (None, 128, 128, 32) 896
conv2d_l (Conv2D) (None, 128, 128, 32) 9248
max_pooling2d (MaxPooling2D) (None, 64, 64, 32) 0
conv2d_2 (Conv2D) (None, 64, 64, 64) 18496
max_pooling2d_l (MaxPooling2D) (None, 32, 32, 64) 0
ílatten (Flatten) (None, 65536) 0
dense (Dense) (None, 1) 65537
Total params: 94,177
Trainable params: 94,177
Non-trainable params: 0
Entrenamiento del modelo
Como ya hemos preparado anteriormente nuestros conjuntos de datos, ahora
solamente es necesario utilizar el método . fit ( ) del modelo que acabamos de definir.
Al estar trabajando con un tf. data. Datas et, no es necesario especificar los valores
de X ni de Y, internamente la librería ya se encarga de obtenerlos a partir del objeto
que le pasamos. Utilizaremos los mismos parámetros de entrenamiento que en la
práctica anterior:
history = model.fit(train_rdy.batch(64),
epochs=10,
validation_data=validation_rdy.batch(64))
Una vez completado el entrenamiento representamos las dinámicas de
entrenamiento a partir de history.
plot_history(history)
plt.show()
© RA-MA
156 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Loss del modelo
Tasa de acierto del modelo
90
-+- Entrenamiento
-•- Validación
85
0.6
t: 80
"ü
ro
:¡¡ 0.5
.3
w 75
ro
� 70
0.4
0.3
iÍ-
-+- Entrenamiento
-•- Validación
o
2
4
Épocas
I
65
8
55
,.
,,,,.
.,.,• .... .... .,..__ ,..,,. ..... , '•'
.,...--•
I
I
I
I
60
6
I
I
o
2
4
6
8
Épocas
Figura 3.38. Dinámicas de entrenamiento del modelo. Se puede apreciar el sobreajuste.
Esta figura nos permite ver que, si bien el modelo elegido es capaz de
aprenderse los datos del conjunto de entrenamiento, no tiene la capacidad suficiente
como para generalizar. Estamos ante un ejemplo claro de sobreajuste: el rendimiento
en el conjunto de entrenamiento mejora mientras que el rendimiento en validación
no cambia o incluso empeora.
Una solución a este problema es aplicar técnicas de regularización a los
pesos (Ll, L2 y Dropout) o Data Augmentation. El aumento de los datos dificulta
que el modelo aprenda a memorizar los datos de entrenamiento y favorece una mejor
capacidad de generalización, por lo que también se puede considerar una técnica
de regularización. Esto quiere decir que habrá que tener cuidado de no aumentar
demasiado los datos o podríamos perder rendimiento si alteramos demasiado las
imágenes originales.
El aumento de los datos (data augmentation) puede servir como una forma
de paliar la falta de datos, pero necesitamos partir de una cantidad suficiente de datos
para obtener mejoras considerables y resultados significativos.
Evaluación del modelo
El método . evaluate() permite calcular todas las métricas que hemos
compilado anteriormente en el modelo sobre el conjunto de datos que queramos. Es
una buena práctica evaluar el modelo en los diferentes conjuntos de datos al acabar el
entrenamiento para obtener una métrica final del trabajo realizado. Este método nos
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 157
devuelve una lista con las métricas, en este caso la función de coste y la precisión. A
la hora de hacer la evaluación podemos utilizar tamaños del batch distintos a los de
entrenamiento porque no vamos a actualizar los pesos y no afectará al rendimiento
del modelo. Utilizando la función genérica definida anteriormente obtenemos:
> [Entrenamiento] Loss: 0.311 1 Accuracy: 0.858
> [Validación] Loss: 0.814 1 Accuracy: 0.707
> [Test] Loss: 0.807 1 Accuracy: 0.707
Callbacks: EarlyStopping y ModeLCheckpoint
Hasta ahora hemos estado trabajando con un conjunto de entrenamiento,
uno de validación y otro de test, pero en realidad no hemos utilizado el conjunto
de validación para nada en particular. El uso más habitual de este conjunto es el de
detener el entrenamiento cuando deja de mejorar la métrica de interés. De esta forma
podemos olvidamos de tener que ajustar la cantidad de épocas durante las cuales
queremos entrenar el modelo: utilizaremos una cantidad de épocas suficientemente
grande como para alcanzar el sobreajuste y detendremos el entrenamiento cuando se
empiece a producir.
Para hacer esto podemos utilizar dos callbacks: EarlyStopping y
ModelCheckpoint. Un callback simplemente es una función que se ejecuta en puntos
determinados del entrenamiento, por ejemplo, estos dos callbacks comprueban
el valor de una detenninada métrica al final de cada época y toman una acción
determinada:
,.. EarlyStopping: detiene el entrenamiento si hace más de N épocas que
no mejora la métrica. Resulta útil para evitar el sobreajuste y reducir el
tiempo innecesario de cálculo.
,.- ModelCheckpoint: guarda los pesos del modelo si se mejora la métrica
en cuestión. Es útil tanto como para evitar el sobreajuste como para
poder continuar un entrenamiento que se ha cortado por algún fallo de
la máquina.
Podemos elegir cualquier métrica ( o función de coste) para tomar estas
decisiones, aunque la recomendación habitual es utilizar la métrica que realmente nos
interesa optimizar. En este problema de clasificación lo que nos interesa es alcanzar
el mejor valor posible de la precisión, por lo que controlaremos la val_accuracy.
158 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Ser capaces de definir nuestros propios callbacks puede darnos mucha
flexibilidad durante el entrenamiento, aunque es algo más avanzado que queda
fuera del alcance de esta práctica. Para utilizarlos en el entrenamiento de un modelo
simplemente tendremos que pasar una lista de callbacks al parámetro callbacks del
método . fit ( ) :
model = tf.keras.models.Sequential([
layers.Conv2D(filters=32, kernel_size=3, activation="relu",
padding='same', input_shape=(128,128,3)),
layers.Conv2D(filters=32, kernel_size=3, activation='relu',
padding='same'),
layers.MaxPool2D(pool_size=2),
layers.Conv2D(filters=64, kernel_size=3, activation="relu",
padding='same'),
layers.MaxPool2D(pool_size=2),
layers.Flatten(),
layers.Dense(l, activation="sigmoid")
])
model.compile(optimizer="adam", loss="binary_crossentropy",
metrics=[«accuracy"])
from tensorfiow.keras.callbacks import EarlyStopping, ModelCheckpoint
## Definición de los callbacks
cb_earlystopping = EarlyStopping(patience=4,monitor='val_accuracy')
cb_modelcheckpoint = ModelCheckpoint(filepath='model_cnn_baseline.hS',
monitor='val_accuracy',
save_best_only=True)
## Entrenamiento del modelo
history = model.fit(train_rdy.batch(64),
epochs=50,
validation_data=validation_rdy.batch(64),
callbacks=[cb_earlystopping, cb_modelcheckpoint])
Vemos que, aunque hemos configurado el entrenamiento para que durase 50
épocas, se ha cortado automáticamente en la época 1 O reduciendo enormemente el
tiempo perdido sobre ajustando el modelo. Una vez el entrenamiento se detiene hay
que recordar cargar los pesos del mejor modelo y evaluar los resultados:
model.load_weights('model_cnn_baseline.h5')
metrics_baseline_cb = evaluate_model(model, train_rdy, validation_rdy, test_rdy)
> [Entrenamiento] Loss: 0.258 1 Accuracy: 0.901
> [Validación] Loss: 0.604 1 Accuracy: 0.743
> [Test] Loss: 0.567 1 Accuracy: 0.757
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 159
Con este sencillo cambio vemos que se puede mejorar bastante el rendimiento
final del modelo mientras reducimos el tiempo de cálculo desperdiciado. A partir de
ahora utilizaremos estos callbacks en todos los entrenamientos.
Data Augmentation
Como ya sabemos, el aumento de los datos o Data Augmentation consiste en
introducir alteraciones en las imágenes de nuestro conjunto de datos para obtener un
conjunto de datos aumentado que contenga imágenes más diversas que favorezcan la
generalización de los modelos. Hay una infinidad de transformaciones posibles que
se pueden aplicar, pero algunas de ellas son:
Rotaciones
Traslaciones
11"" Cortes
11"" Zoom
11"" Volteados
11""
11""
Hasta hace relativamente poco tiempo se recomendaba utilizar el objeto
tf. keras. preprocessing. image. ImageDataGenerator() para utilizar Data
Augmentation, pero esto ha cambiado con las últimas versiones de Keras. El enfoque
más actual consiste en utilizar un tf. data. Dataset para gestionar la carga de los
datos (que es lo que hemos hecho en la parte de carga de datos) y utilizar las capas
de preprocesado para aplicar el aumento de los datos. Por medio de estas capas se
pueden aplicar todas las transformaciones que hemos comentado:
•
•
•
•
•
tf.keras.layers.RandomRotation()
tf.keras.layers.RandomTranslation()
tf.keras.layers.Randomcrop()
tf.keras.layers.Randomzoom()
tf.keras.layers.RandomFlip()
Esta nueva herramienta permite plantear de dos formas distintas el proceso
de aumento:
11""
Podemos añadir las capas que queramos a nuestro modelo y entrenarlo
como un modelo normal. Esto es lo más sencillo. Además, las capas están
configuradas para dejar de funcionar al realizar la inferencia, por lo que
no supondrán un problema.
160 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
11" Podemos definir un modelo secuencial que implemente todas las
transformaciones que queramos y aplicarlo sobre el tf. data. Data set
que ya hemos definido utilizando el método . map(). Esto es ligeramente
más laborioso, pero da más libertad a la hora de gestionar los recursos
computacionales, por lo que se puede optimizar para acelerar los
entrenamientos.
Optaremos por el segundo enfoque y dejaremos el primero como ejercicio
para el lector.
Hay que tener en cuenta que todas estas transformaciones tienen parámetros
que habrá que ajustar correctamente para obtener el mejor resultado posible. Puede
haber casos en los que no tenga sentido aplicar ciertas transformaciones, por ejemplo,
si queremos entrenar un clasificador de paisajes no tiene sentido voltear las imágenes
verticalmente porque no existen ese tipo de paisajes.
Nótese que el parámetro factor de la capa RandomRotation () se
multiplica por 2rr para determinar, en radiantes, el rango de rotación de las imágenes:
(-factor* 2rr,factor * 2rr)augmentations = tf.keras.models.Sequential([
layers.RandomRotation(factor=0.35, fill_mode='reílect'),
layers.RandomTranslation(height_factor=0.1, width_factor=0.1,
fill_mode='reílect'),
layers.RandomZoom(height_factor=0.1, width_factor=0.1,
fill_mode='reílect'),
layers.RandomFlip(mode='horizontal')
])
Un detalle muy importante a tener en cuenta cuando apliquemos Data
Augmentation es que solamente hay que hacerlo sobre el conjunto de entrenamiento.
Los conjuntos de validación y test están creados con la intención de tener unos datos
fijos sobre los que probar nuestro modelo, por lo que no tiene sentido introducirles
variaciones aleatorias a los datos de estos conjuntos.
train_aug = train_rdy.map(lambda x, y: (augmentations(x), y))
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 161
Antes de pasar al entrenamiento del modelo podemos comprobar el resultado
de estas transformaciones sobre una imagen:
aug_sample = [next(iter(train_aug))[0] far i in range(12)]
plt.figure()
far i in range(12):
plt.subplot(3,4,i+l)
plt.imshow(aug_sample[i])
plt.axis('off')
plt.tight_layout()
plt.show()
Ejemplos Transformaciones
Figura 3.39. Efecto del aumento de datos sobre una imagen de muestra. Se aprecian los efectos de
rellenado de huecos.
Lo primero que vemos es que hay algunas 1magenes que están muy
deformadas. Esto sucede cuando la transformación deja un hueco en blanco en la
imagen al hacer, por ejemplo, una rotación. Entonces el hueco se rellena conforme al
parámetro fill_mode que hayamos seleccionado en cada capa.
162 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Ejercicio
Prueba varios tipos de fill_mode y elige el que más te guste. Luego
comprueba si influyen mucho en los resultados que se obtienen.
Entrenamiento del modelo
Ahora que ya hemos definido todas las transformaciones que vamos a aplicar
a los datos podemos pasar al entrenamiento del modelo para comprobar si estos
cambios influyen en los resultados que obtenemos. Hay que recordar que tenemos
que volver a definir el modelo para evitar utilizar los pesos entrenados en el apartado
anterior.
Es normal que este entrenamiento tarde bastante más tiempo que el anterior.
Ahora cada imagen se procesa antes de entrar en el modelo y esto tiene cierta carga
computacional.
model_aug = tf.keras.models.Sequential([
layers.Conv2D(filters=32, kernel_size=3, activation="relu",
padding='same', input_shape=(128,128,3)),
layers.Conv2D(filters=32, kernel_size=3, activation='relu',
padding='same'),
layers.MaxPool2D(pool_size=2),
layers.Conv2D(filters=64, kernel_size=3, activation="relu",
padding='same'),
layers.MaxPool2D(pool_size=2),
layers.Flatten(),
layers.Dense(l, activation="sigmoid")
])
model_aug.compile(optimizer="adam", loss="binary_crossentropy",
metrics=[ naccuracy"])
Cuando trabajemos con Data Augmentation es recomendable aumentar la
cantidad de épocas que dejamos pasar antes de detener el entrenamiento (la paciencia)
porque es normal que fluctúen más las métricas. Esto se debe principalmente a que en
cada época las transformaciones de los datos son distintas, por lo que los resultados
pueden oscilar más.
cb_earlystopping = EarlyStopping(patience=4,monitor='val_accuracy')
cb_modelcheckpoint = ModelCheckpoint(filepath='model_cnn_aug.hS',
monitor='val_accuracy',
save_best_only=True)
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 163
history_aug
model_aug.fit(train_aug.batch(64),
epochs=50,
validation_data=validation_rdy.batch(64),
callbacks=[cb_earlystopping, cb_modelcheckpoint])
plot_history(history_aug)
plt. show()
Loss del modelo
Tasa de acierto del modelo
so---------------+- Entrenamiento
-*- Validación
-+- Entrenamiento
-*- Validación
0.75
0.70
75
t 70
'ü
0.65
"'
u,
u,
Q/
o
1J 65
-' 0.60
O.SS
';f. 60
o.so
55
o
10
20
Épocas
30
40
50
o
10
20
Épocas
30
40
50
Figura 3.40. Dinámicas de entrenamiento del modelo con aumento de datos. La mejoría es significativa.
La figura superior ejemplifica a la perfección el resultado de haber aplicado
regularización al modelo. Mientras que en el entrenamiento anterior habíamos
alcanzado el sobreajuste en la tercera época, en este caso parece que después de
50 épocas aún podríamos seguir entrenando el modelo. Por contra, la evolución de
la precisión es mucho menor. Esta ralentización del aprendizaje se produce porque
todas las transformaciones que hemos introducido hacen que el aprendizaje sea más
complicado, por lo que suele ser necesario utilizar más épocas para entrenar los
modelos.
Evaluación del modelo
model_aug.load_weights('model_cnn_aug.h5')
metrics_aug = evaluate_model(model_aug, train_rdy, validation_rdy, test_rdy)
> [Entrenamiento] Loss: 0.517 1 Accuracy: 0.749
> [Validación] Loss: 0.542 1 Accuracy: 0.730
> [Test] Loss: 0.510 1 Accuracy: 0.755
164 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Finalmente comprobamos como estos cambios han servido para mejorar los
resultados que habíamos obtenido anteriormente sin tener que cambiar absolutamente
ningún parámetro del modelo ni del entrenamiento. Solo ha sido necesario modificar
ligeramente los datos para obtener una mejor generalización.
Transfer Learning
Para terminar la práctica vamos a realizar una pequeña introducción al
aprendizaje por transferencia o transfer learning en inglés. Este concepto parte
de la idea de que las capas de una red convolucional aprenden características
jerárquicamente, es decir, las primeras capas aprenden características generales
mientras que las capas más profundas aprenden características más específicas para
cada problema. Esto da pie a pensar que una red entrenada en un conjunto de datos
suficientemente diverso podría ser capaz de extraer características útiles de datos
que no ha visto nunca, por lo que podría resultar especialmente interesante cuando
trabajemos con conjuntos de datos pequeños con los que no podemos entrenar un
modelo desde cero.
Por suerte para nosotros, podemos descargamos de intemet los pesos de
una gran variedad de arquitecturas que ya han sido entrenadas en conjuntos de datos
muy diversos y utilizarlas para nuestras propias aplicaciones sin tener que hacer
demasiados cambios. TensorFlow pone a nuestra disposición una selección de las
arquitecturas más famosas dentro del módulo applications y, además, nos da la
posibilidad de utilizar fácilmente cualquier modelo del Hub (https:/ /tfhub.dev/),
dónde usuarios de todo el mundo comparten sus modelos entrenados para el uso de
todo el mundo.
En este caso nosotros vamos a centramos únicamente en la primera opción,
pero se recomienda explorar también la segunda porque puede ser una fuente muy
útil de información y modelos.
VGG19
A modo de ejemplo vamos a utilizar el modelo VGG 19 pre-entrenado en el
conjunto de datos ImageNet para resolver el problema de clasificación de gatos y
perros. Para ello lo único que tenemos que hacer es importar el modelo a través de
TensorFlow, pero hay que tener un par de consideraciones:
,. Como solamente queremos aprovechar las características que ha
aprendido a extraer el modelo, lo importaremos con el parámetro
include_top=False. De esta forma importaremos únicamente las capas
convolucionales y podremos poner detrás nuestras propias capas densas
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 165
adaptadas al problema en cuestión. En nuestro caso una capa densa con
una neurona porque estamos resolviendo un problema de clasificación.
En el caso de modelos entrenados en lmageNet, la última capa tiene mil
neuronas porque se clasifican las imágenes entre mil clases diferentes.
11"'
Si seguimos actualizando los pesos de las capas que hemos importado
corremos el riesgo de que "olviden lo que ya saben". Por eso es importante
actualizar únicamente los pesos de las capas densas que coloquemos
nosotros al final, los cuales sí que necesitan actualizarse porque se
inicializan aleatoriamente. Esto podemos hacerlo estableciendo el
atributo . trainable=False de las capas importadas.
from tensorfiow.keras.applications import VGG19
model base
VGG19(input_shape=(128,128,3),
include_top=False,weights='imagenet')
for layer in model_base.layers:
layer.trainable = False
model_base.summary()
Model: "vgg19"
Layer (type)
input_l (Inputlayer)
blockl-convl (Conv2D)
blockl-conv2 (Conv2D)
Output Shape
[(None, 128, 128, 3)]
(None, 128, 128, 64)
(None, 128, 128, 64)
Param #
0
1792
36928
blockl_pool (MaxPooling2D) (None, 64, 64, 64)
0
block2-conv2 (Conv2D)
147584
block2-convl (Conv2D)
(None, 64, 64, 128)
block2_pool (MaxPooling2D)
(None, 32, 32, 128)
block3-convl (Conv2D)
block3-conv2 (Conv2D)
(None, 64, 64, 128)
(None, 32, 32, 256)
(None, 32, 32, 256)
73856
0
295168
590080
----------------------------------------------------------------- Total params:
20,024,384 Trainable params: 0 Non-trainable params: 20,024,384
Como vemos, se ha importado únicamente la parte de las capas
convolucionales. Ahora tenemos que añadir las capas finales y entrenar nuestro
modelo. Podemos hacerlo utilizando tanto laAPI Secuencial como laAPI Funcional:
166 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
model_transfer = tf.keras.models.Sequential([
model_base,
layers.Flatten(),
layers.Dense(l, activation='sigmoid')
])
model_transfer.compile(optimizer="adam", loss="binary_crossentropy",
metrics=["accuracy"])
model_transfer.summary()
Model: "sequential_4
Layer (type) Output Shape Param #
vgg19 (Functional) (None, 4, 4, 512) 20024384
ílatten_3 (Flatten) (None, 8192) 0
dense_3 (Dense) (None, 1) 8193
Total params: 20,032,577
Trainable params: 8,193
Non-trainable params: 20,024,384
Ejercicio
Obtén el mismo resultado utilizando la API Funcional.
En general, como los filtros convolucionales ya han sido aprendidos, no
suele ser necesario realizar entrenamientos demasiado largos, así que podemos
utilizar solamente 1 O épocas.
cb_earlystopping = EarlyStopping(patience=4,monitor='val_accuracy')
cb_modelcheckpoint = Mode1Checkpoint(filepath='model_cnn_transfer.h5',
monitor='val_accuracy',
save_best_only=True)
history_transfer model_transfer.fit(train_aug.batch(64),
epochs=10,
validation_data=validation_rdy.batch(64),
callbacks=[cb_earlystopping, cb_modelcheckpoint])
plot_history(history_transfer)
plt.show()
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 167
Tasa de acierto del modelo
Loss del modelo
0.60
-- Entrenamiento
-• - Validación
82
80
0.55
_g
-- Entrenamiento
-•- Validación
78
(lJ
"' O.SO
-� 76
0.45
';f. 72
I
I
I
?,.
*
I\
I \
I
\
I
\
,.;,,... , ...i
_,,__..,,
\__.,....._....__.
I
I
(lJ
:)l 74
70
0.40
68
o
2
4
Épocas
6
8
o
2
4
Épocas
6
8
Figura 3.41. Dinámicas de entrenamiento del modelo con transfer learning. Mejoramos el resultado
anterior con menos tiempo de entrenamiento.
model_transfer.load_weights('model_cnn_transfer.hS')
metrics_transfer = evaluate_model(model_transfer,
train_rdy, validation_rdy, test_rdy)
> [Entrenamiento] Loss: 0.370 1 Accuracy: 0.835
> [Validación] Loss: 0.401 1 Accuracy: 0.807
> [Test] Loss: 0.404 1 Accuracy: 0.827
Vemos que aprovechando las características aprendidas en otro conjunto de
datos somos capaces de mejorar los resultados que habíamos obtenido anteriormente.
Esta aproximación es muy potente porque nos permite tener un prototipo funcional
relativamente rápido, aunque aún se puede mejorar más realizando ajuste fino (o
fine-tuning en inglés).
Fine-Tuning
Una vez hemos entrenado la parte final de nuestra red (la que hemos añadido
nosotros que se inicializa aleatoriamente), podemos optar por realizar un último
paso: ajuste fino o fine-tuning en inglés.
Esto consiste en descongelar las capas del modelo que hemos importado y
entrenar el modelo en conjunto utilizando un learning rate muy bajo. De esta forma
lo que conseguimos es que los filtros convolucionales se adapten a la tarea específica
168 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
que intentamos resolver, pero minimizando el riesgo de que olviden lo que ya habían
aprendido antes. Un learning rate pequeño podría estar en el rango en tomo a 10-s.
Es muy importante que este ajuste fino lo hagamos únicamente después de
haber entrenado las capas que añadamos nosotros manualmente. Si no lo hacemos,
al estar inicializadas aleatoriamente, no proporcionarán una buena señal al resto de
capas y podemos perder todo el conocimiento que tenían aprendido anteriormente.
Ejercicio
Si recordamos lo que ya se ha comentado, las primeras capas suelen
aprender filtros muy generales. Prueba a dejar congeladas las primeras capas
y descongelar únicamente las capas más profundas de la red y compara los
resultados.
far layer in model_transfer.layers:
layer.trainable = True
model_transfer.compile(optimizer=tf.optimizers.Adam(learning_rate=le-5),
loss="binary_crossentropy",
metrics=["accuracy"])
model_transfer.summary()
Model: "sequential_4"
Layer (type) Output Shape Param #
vgg19 (Functional) (None, 4, 4, 512) 20024384
ílatten_3 (Flatten) (None, 8192) 0
dense_3 (Dense) (None, 1) 8193
Total params: 20,032,577
Trainable params: 20,032,577
Non-trainable params: 0
cb_earlystopping = Early5topping(patience=4,monitor='val_accuracy')
cb_modelcheckpoint = ModelCheckpoint(filepath='model_cnn_transfer.hS',
monitor='val_accuracy',
save_best_only=True)
history_transfer_fine model_transfer.fit(train_aug.batch(64),
epochs=10,
validation_data=validation_rdy.batch(64),
callbacks=[cb_earlystopping, cb_modelcheckpoint])
plot_history(history_transfer_fine)
plt.show()
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 169
Loss del modelo
Tasa de acierto del modelo
-- Entrenamiento
-•- Validación
0.35
94
o
0.30
t
.'!!
92
.,,.
,, ,
,,,,
..
,,
,
, , •----.-
- - - •--- ,....___-•
� 90
:g 0.25
V,
Q)
'O
()l 88
0.20
............. """ ... ...
0.15
o
1
�
86
',-___ ........
2
3
.............. _~-•---·
Épocas
4
5
6
-- Entrenamiento
-•- Validación
84
o
1
2
3
Épocas
4
5
6
Figura 3.42. Dinámicas de entrenamiento del modelo con transfer learning, data augmentation y fine
tuning. Mejora con creces el rendimiento del resto de los modelos.
model_transfer.load_weights('model_cnn_transfer_fine.h5')
metrics_transfer_fine = evaluate_model(model_transfer,
train_rdy, validation_rdy, test_rdy)
> [Entrenamiento] Loss: 0.100 1 Accuracy: 0.957
> [Validación] Loss: 0.122 1 Accuracy: 0.950
> [Test] Loss: 0.158 1 Accuracy: 0.943
Para terminar, podemos mostrar una tabla resumen de todas las métricas
obtenidas durante la práctica:
metrics = np.concatenate([metrics_baseline, metrics_baseline_cb,
metrics_aug, metrics_transfer, metrics_transfer_fine])
metrics = metrics.ílatten()
model_names = ['Baseline', 'Baseline (ES)', 'Data Augmentation',
'Transfer Learning', 'TL+Fine Tuning']
split_names = ['Train', 'Validation', 'Test']
multiindex = pd.Multiindex.from_product([model_names, split_names],
names=['Modelo', 'Conjunto'])
results = pd.DataFrame(metrics.reshape(15,2), index=multiindex,
columns=['Loss', 'Accuracy']).style.background_gradient(cmap='magma')
results
170 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Modelo
Conjunto
Baseline
Validation
Baseline (ES)
Data Augmentation
Transfer Learning
Transfer Learning + Fine
Tuning
Train
Test
Train
Validation
Test
Train
Validation
Test
Train
Validation
Test
Train
Validation
Test
© RA-MA
Loss
Accuracy
0.311199
0.858375
0.814448
0.707000
0.806751
0.707000
0.258290
0.901250
0.604232
0.743000
0.566523
0.757000
0.517172
0.748875
0.542310
0.730000
0.509590
0.755000
0.369702
0.834500
0.401312
0.807000
0.404072
0.827000
0.100082
0.957250
0.121653
0.950000
0.158389
0.943000
Aquí es dónde vemos el potencial que tiene este proceso, los resultados que
obtenemos al realizar ajuste fino mejoran considerablemente todos los resultados
anteriores. Además, aún podríamos exprimirlo más utilizando diferentes learning
rates para diferentes partes del modelo, etc.
Ejercicio
Repite el proceso de transfer learning y fine tuning con otras arquitecturas
como ResNet e Inception.
Como se puede ver, tenemos infinitas posibilidades para afrontar un
problema. Podría parecer que la opción lógica sería empezar siempre con un modelo
pre-entrenado y ajustarlo a nuestros datos, pero el no free lunch theorem nos dice que
no existe ninguna solución que siempre vaya a proporcionarnos el mejor resultado
posible. Los modelos personalizados nos dan muchas más posibilidades que los
modelos preentrenados pero pueden darnos peores resultados si no tenemos muchos
datos. En estos casos, el transfer learning suele ser una buena alternativa. Además,
utilizar modelos pre-entrenados nos da la posibilidad de crear prototipos funcionales
muy rápido a costa de perder personalización.
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 171
PRÁCTICA 3. Segmentación de imágenes
Dejando atrás los ejercicios de clasificación, en esta práctica vamos a
enfrentamos a un nuevo tipo de problema: la segmentación de imágenes. Vamos
a utilizar el dataset de Oxford IIIT Pet, que consiste en más de 7000 imágenes de
diferentes razas de perros junto a su máscara de segmentación. El objetivo del
ejercicio es conseguir entrenar un modelo que sea capaz de proporcionamos las
máscaras de segmentación a partir de las imágenes originales realizado un problema
de clasificación multiclase por píxel.
Igual que en la práctica anterior, descargaremos el conjunto de datos de la
librería tensorflow_datasets.
Importación de las librerías
Antes de empezar a trabajar, cargamos todas las librerías que vamos a
necesitar.
from collections import Counter
import numpy as np
import matplotlib.pyplot as plt
import tensorílow as tf
from tensorílow.keras import layers
from tensorílow.keras.utils import plot_model
import tensorílow_datasets as tfds
Carga de los datos
Para poder centrarnos en la parte interesante del ejercicio utilizaremos el
dataset que se incluye en tensorflow_datasets. Podemos cargarlo de la siguiente
manera:
dataset, info = tfds.load('oxford_iiit_pet:3.*.*', with_info=True,
data_dir="./data/")
A diferencia de la práctica anterior, si no especificamos ningún valor para
el parámetro spli t al utilizar . load(), dataset es un diccionario que contiene
tanto el dataset de entrenamiento como el de test. Esto depende de cómo ha sido
construido el dataset original.
172 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
print(dataset.keys())
> dict_keys(['test', 'train'])
Podemos ver la cantidad de elementos que hay en cada conjunto de datos a
partir del objeto info. Vemos que están prácticamente repartidos por igual:
print(f"Muestras de entrenamiento: {info.splits['train'].num_examples}")
print(f"Muestras de test: {info.splits['test'].num_examples}")
> Muestras de entrenamiento: 3680
> Muestras de test: 3669
Exploración de los datos
Como ya sabemos, los conjuntos de datos son objetos PrefetchDataset.
Podemos explorar uno de estos ejemplos para ver qué información contiene y cómo
son las imágenes y las máscaras iterando sobre el objeto:
for sample in dataset['train']:
break
sample.keys()
> dict_keys(['file_name', 'image', 'label', 'segmentation_mask',
> 'species'])
Ahora, cada elemento del mismo contiene:
,,. La ruta de la imagen a cargar.
,,. La propia imagen.
,,. La etiqueta.
,,. La máscara de segmentación correspondiente.
,,. La especie.
Y el problema está planteado de forma que en cada imagen podemos tener
3 etiquetas:
,,. Animal (1)
,,. Borde del animal (2)
,,. Fondo (3)
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 173
Podemos representar la imagen junto a su máscara de segmentación:
plt.figure()
plt.subplot(l,2,1)
plt.imshow(sample['image'].numpy().squeeze())
plt.title('Original Image')
plt.axis('off')
plt.subplot(l,2,2)
plt.imshow(sample['segmentation_mask'].numpy().squeeze())
plt.title('Segmentation Mask')
plt.axis('off')
plt.show()
Original lmage
Segmentation Mask
Figura 3.43. Imagen y máscara de segmentación del conjunto de datos.
Siguiendo el procedimiento habitual vamos a comprobar los rangos de las
imágenes por canal y sus dimensiones:
maxs = sample['image'].numpy().max(axis=(0,1))
mins = sample['image'].numpy().min(axis=(0,1))
ranges = [(m, M) for m, M in zip(mins, maxs)]
print("Rangos: {}, {}, {}".format(*ranges))
print("Dimensiones: ", sample['image'].shape)
> Rangos: (0, 253), (0, 255), (0, 254)
> Dimensiones: (500, 500, 3)
174 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Comprobamos también si todas las imágenes tienen el mismo tamaño:
shapes = [)
far sample in dataset['train']:
shapes.append(sample['image').numpy().shape)
len(set(shapes))
> 709
En el caso de un problema de segmentación también es recomendable que
comprobemos cómo son las etiquetas de las máscaras y su distribución. Esto nos
permitirá ver si el problema está muy desbalanceado o no:
np.unique(sample['segmentation_mask').numpy())
> array([l, 2, 3), dtype=uint8)
Counter(sample['segmentation_mask'J.numpy().ravel())
> Counter({2: 169695, 3: 24633, 1: 55672})
Como viene siendo habitual las imágenes están en el rango [0,255], y las
etiquetas de las máscaras son { 1,2,3}. También hemos visto que hay muchos menos
píxeles de borde (como era de esperar) que de fondo o animal, por lo que podríamos
tener algunos problemas prediciendo esta clase. Vsto esto, sabemos que tenemos que
normalizar las imágenes para que queden en el rango [O, 1] y restar 1 a las etiquetas de
las máscaras para que sean {0,1,2}. Además, también hemos visto que las imágenes
tienen tamaños diferentes, así que también tendremos que transformarlas para que
tengan todas el mismo.
La normalización es importante para que nuestra red pueda aprender,
mientras que el cambio en las etiquetas se hace por convención, ya que las funciones
de coste esperan recibir así las etiquetas.
Prep rocesado de los datos
Dado que estamos trabajando con un Datas et de TensorFlow, la forma más
cómoda de aplicar una transformación sobre el dataset completo es utilizando el
método . map(), que permite aplicar una función a todos los elementos del conjunto y
nos devuelve un nuevo 'Datasef con los resultados. Para ello crearemos una función
de preprocesado, prepare_data(), y la aplicaremos utilizando . map().
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 175
Esta función tiene que servimos para 4 cosas:
l. Normalizar las imágenes.
2. Ajustar las etiquetas de las máscaras de segmentación.
3. Redimensionar las imágenes para reducir su tamaño y hacer que sean
todas iguales. Hay que prestar especial atención al método que se utiliza
para redimensionar las máscaras, ya que algunos aplican interpolación
y pueden generar números intermedios que no correspondan a ninguna
etiqueta real. Para evitarlo utilizaremos el método nearest.
4. Devolver únicamente las imágenes y las máscaras para poder utilizar el
Dataset directamente en el método . fit ().
def prepare_data(sample, reshape_dims=(128,128)):
image = sample['image']
segmentatien_mask = sample['segmentatien_mask'J
image = tf.image.resize(image, reshape_dims)
segmentatien_mask = tf.image.resize(segmentatien_mask,
reshape_dims,
methed='nearest')
image = image/255
segmentatien_mask = segmentatien_mask-1
return image, segmentatien_mask
train = dataset['train'J.map(prepare_data)
test = dataset['test'].map(prepare_data)
Una vez aplicado podemos volver a representar una imagen junto a su
máscara para ver el resultado. También podemos comprobar que los rangos de las
imágenes y las máscaras son correctos.
fer img, seg_mask in train:
maxs img.numpy().max(axis=(0,1))
mins = img.numpy().min(axis=(0,1))
ranges = [(m, M) fer m, M in zip(mins, maxs)]
print("Ranges: {}, {}, {}".fermat(*ranges))
print("Dimensienes:", img.shape)
print("Etiquetas: ", np.unique(seg_mask.numpy()))
176 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
break
> Rangos: (0.0, 0.7803567), (0.0, 0.7918505), (0.0, 0.6729023)
> Dimensiones: (128, 128, 3)
> Etiquetas: [0 1 2]
plt.figure()
plt.subplot(l,2,1)
plt.imshow(img.numpy().squeeze())
plt.title('Original Image')
plt.axis('off')
plt.subplot(l,2,2)
plt.imshow(seg_mask.numpy().squeeze())
plt.title('5egmentation Mask')
plt.axis('off')
plt.show()
Original lmage
Segmentation Mask
Figura 3.44. Imagen y máscara de segmentación del conjunto de datos tras el escalado y la
normalización.
La única diferencia apreciable es que ahora están menos definidos los bordes.
Generalidades
Durante esta práctica vamos a entrenar y comparar muchos modelos. Para
realizar estas comparaciones vamos a representar sus dinámicas de entrenamiento
(es una buena forma de apreciar el sobre-entrenamiento) y también representaremos
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 177
los resultados en los diferentes conjuntos que hemos creado anteriormente. Por todo
esto es buena idea definir previamente unas funciones genéricas que nos sirvan para
mantener el código más limpio y sencillo.
def plot_history(history):
plt.subplot(l,2,1)
plt.plot(history.history['loss'], 'k-o',
label = "Entrenamiento")
plt.plot(history.history['val_loss'J, 'k--*',
label = "Validación")
plt.ylabel('Loss')
plt.xlabel('Épocas')
plt.title('Loss del modelo')
plt.legend()
plt.subplot(l,2,2)
plt.plot(np.array(history.history['accuracy'])*100, 'k-o',
label="Entrenamiento")
plt.plot(np.array(history.history['val_accuracy'])*100, 'k--*',
label="Validación")
plt.ylabel('% Tasa de acierto')
plt.xlabel('Épocas')
plt.title('Tasa de acierto del modelo')
plt.legend()
return
def show_results(model, dataset, n=S, random=False):
if random: dataset=dataset.shuffle(n)
far x, y in dataset.batch(n):
pred = model(x)
break
fig, axes = plt.subplots(2, n)
far col in range(n):
axes[0,col].imshow(y[col].numpy().squeeze())
axes(0,col].set_title('Original')
axes[0,col].axis('off')
axes(l,col].imshow(np.argmax(pred[col].numpy().squeeze(),-1))
axes[l,col].set_title('Predicted')
axes[l,col].axis('off')
fig.tight_layout()
178 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Definición del modelo
Los modelos por excelencia para resolver problemas de segmentación se
basan en una estructura encoder-decoder que toma una imagen como entrada y
devuelve una imagen que tiene tantos canales como clases diferentes a predecir. Esta
parte es clave, ya que es lo que nos permite plantear un problema de clasificación
por píxel.
El objetivo es conseguir que la salida del modelo sea una imagen del mismo
tamaño que la imagen de entrada con tantos canales como clases posibles tenemos,
de forma que solamente habrá que aplicar una función softmax en la dirección de
los canales para obtener el canal (o la clase) más probable para cada pixel.
Esta arquitectura se puede plantear con la API Secuencial. Como es
habitual, utilizaremos Adam como optimizador y la función de coste tf. losses.
SparseCategoricalCrossentropy porque tenemos un problema multiclase donde
las etiquetas vienen dadas por números enteros en el rango [O, Nc iases).
Ahora hemos utilizado operaciones dePoolingpara reducir ladimensionalidad
de los mapas de características conforme nos adentramos en la red, pero en este caso
vamos a utilizar strides. La justificación principal es que las operaciones de Pooling
son muy útiles para resumir información, pero pierden la referencia de la localización
espacial de esta información. Al reducir la dimensionalidad utilizando strides
esta información espacial se mantiene mejor durante la red y proporciona mejores
resultados para tareas de segmentación o generación de datos dónde la espacialidad
es importante. De forma contraria, utilizaremos convoluciones traspuestas para
recuperar el tamaño de la imagen original. En posteriores capítulos profundizaremos
un poco más en esta operación, pero por ahora solamente tenemos que saber que
es la operación contraria a la convolución y que, si utilizamos strides, podemos
aumentar el tamaño de las imágenes por un factor igual a la cantidad de strides.
model = tf.keras.models.Sequential([
layers.Conv2D(filters=32, kernel_size=3, padding='same',
activation='relu', strides=2,
input_shape=(128,128,3)),
layers.Conv2D(filters=64, kernel_size=3, padding='same',
activation='relu', strides=2),
layers.Conv2D(filters=128, kernel_size=3, padding='same',
activation='relu', strides=2),
layers.Conv2D(filters=256, kernel_size=3, padding='same',
activation='relu', strides=2),
layers.Conv2DTranspose(filters=128, kernel_size=3, padding='same',
activation='relu', strides=2),
© RA-MA
])
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 179
layers.Conv2DTranspose(filters=64, kernel_size=3, padding='same',
activation='relu', strides=2),
layers.Conv2DTranspose(filters=32, kernel_size=3, padding='same',
activation='relu', strides=2),
layers.Conv2DTranspose(filters=3, kernel_size=3, padding='same',
activation='softmax', strides=2, ),
model.summary()
model.compile(optimizer='adam',
loss=tf.losses.SparseCategoricalCrossentropy(),
metrics=['accuracy'])
Model: "sequential"
Layer (type) Output Shape Param #
conv2d (Conv2D) (None, 64, 64, 32) 896
conv2d_l (Conv2D) (None, 32, 32, 64) 18496
conv2d_2 (Conv2D) (None, 16, 16, 128) 73856
conv2d_3 (Conv2D) (None, 8, 8, 256) 295168
conv2d_transpose (Conv2DTran (None, 16, 16, 128) 295040
conv2d_transpose_l (Conv2DTr (None, 32, 32, 64) 73792
conv2d_transpose_2 (Conv2DTr (None, 64, 64, 32) 18464
conv2d_transpose_3 (Conv2DTr (None, 128, 128, 3) 867
Total params: 776,579
Trainable params: 776,579
Non-trainable params: 0
En este punto ya podemos entrenar nuestro modelo con normalidad:
history = model.fit(train.batch(64),
epochs=15,
validation_data=test.batch(64))
plot_history(history)
plt.show()
© RA-MA
180 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Tasa de acierto del modelo
Loss del modelo
..._ Entrenamiento
-•- Validación
0.9
75
o
1::
(l)
0.8
-� 70
"'"'
.3 0.7
(l)
"O
"'"'
*
� 6
5
0.6
60
o.o
2.5
5.0
7.5
10.0
o.o
12.5
2.5
5.0
Épocas
7.5
10.0
12.5
Épocas
Figura 3.45. Dinámicas de entrenamiento del modelo.
show_results(model, train, n=5)
plt. show()
Original
Original
Original
Original
Original
Predicted
Predicted
Predicted
Predicted
Predicted
Figura 3.46. Predicciones del modelo sobre el conjunto de entrenamiento.
show_results(model, test, n=5)
plt. show()
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 181
Predicted
Predicted
Predicted
Predicted
Predicted
Figura 3.47. Predicciones del modelo sobre el conjunto de test.
Puede resultar llamativo la mala calidad de los resultados pese a que la
precisión alcanzada por el modelo no era mala. Esto es, en parte, por el desequilibrio
de clases que hemos visto antes. Veamos ahora cómo mejorar este resultado.
U-Net: Añadiendo conexiones entre capas
El resultado que obtenemos no está mal, pero no es todo lo bueno que
podríamos esperar. Sí que es verdad que el modelo capta bastante rápido el contorno
de las formas, pero falta definir mejor el resultado. Para ello Ronnenberger et. al
(https://arxiv.org/pdf/1505.04597vl.pdf) decidieron incluir conexiones entre las
capas del encoder y del decoder para favorecer la propagación de la información,
algo que mejoró notablemente los resultados.
Como ya podemos intuir, la arquitectura que queremos utilizar ya no es
un modelo secuencial, así que tendremos que utilizar o bien la API Funcional o la
herencia de clases. En este caso optamos por utilizar la API Funcional.
Ejercicio
Plantea la misma arquitectura utilizando herencia de clases.
API Funcional
Al utilizar la API Funcional el proceso de definición de modelos es algo
diferente: ahora definimos por un lado las capas que vamos a utilizar, y por otro
definimos el flujo de la información a través de la red. Como ahora no tenemos que
182 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
incluir todas las capas en una lista ordenada, podemos ir almacenando las salidas de
cada capa en variables diferentes para elegir qué salidas entran en qué capas. Esto
nos da unas posibilidades enormes a la hora de definir diferentes arquitecturas.
Introducimos también la capa de concatenación layers. concatenate(),
que sirve para concatenar dos tensores en la dirección dada. Por defecto toma la última
dimensión, las de los canales. Puede llamar la atención que esta capa en realidad no
es un objeto como las demás, si no que es una función. En realidad existen las dos
versiones, pero como es una capa que no contiene ningún tipo de información, no es
necesario utilizar la versión objeto cuando utilizamos la API Funcional.
Una vez hemos definido todas las capas, definimos un objeto tf. keras.
Input() que representa una supuesta entrada al modelo. Este objeto en realidad
contiene información de las dimensiones de los datos de entrada, y es necesario para
que las capas puedas adaptar sus matrices de pesos acorde a esto. A partir de este
objeto, aplicamos las diferentes capas hasta obtener la salida de la red. Teniendo
la entrada y la salida, utilizamos el objeto tf. keras. Model(input=inputs,
output=outputs) para instanciar el modelo final.
Veámoslo:
## Encoder
conv_l
layers.Conv2D(32, kernel_size=3, padding='same',
activation='relu', strides=2,
conv_l_l
conv_2
conv_2_1
input_shape=(128,128,3))
layers.Conv2D(32, kernel_size=3, padding='same',
activation='relu', strides=l)
layers.Conv2D(64, kernel_size=3, padding='same',
activation='relu', strides=2)
layers.Conv2D(32, kernel_size=3, padding='same',
activation='relu', strides=l)
conv_3 layers.Conv2D(128, kernel_size=3, padding='same',
activation='relu', strides=2)
conv_3_1
layers.Conv2D(32, kernel_size=3, padding='same',
activation='relu', strides=l)
layers.Conv2D(256, kernel_size=3, padding='same',
activation='relu', strides=2)
conv_4_1 = layers.Conv2D(32, kernel_size=3, padding='same',
activation='relu', strides=l)
conv_4
## Decoder
convT_4_up
layers.Conv2DTranspose(128,kernel_size=3, padding='same',
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 183
activation='relu', strides=2)
conv_3_up = layers.Conv2D(256, 3, padding='same', activation='relu')
convT_3_up = layers.Conv2DTranspose(64, kernel_size=3, padding='same',
activation='relu', strides=2)
conv_2_up = layers.Conv2D(128, 3, padding='same', activation='relu')
convT_2_up = layers.Conv2DTranspose(32, kernel_size=3, padding='same',
activation='relu', strides=2)
conv_l_up = layers.Conv2D(64, 3, padding='same', activation='relu')
convT_l_up = layers.Conv2DTranspose(3, kernel_size=3, padding='same',
activation='relu', strides=2)
conv_up_l layers.Conv2D{32, 3, padding='same', activation='relu')
conv_up_2 layers.Conv2D(3, 3, padding='same', activation='softmax')
# Definición del flujo de datos
## Definición de la entrada
x = tf.keras.Input(shape=(128,128,3))
## Encoding
x_conv_l conv_l(x) # 64
x_conv_2 conv_2(x_conv_l) # 32
x_conv_3 conv_3(x_conv_2) # 16
x_conv_4 conv_4(x_conv_3) # 8
## Decoding
convT_4_up(x_conv_4)
x_up_3
x_up_3 layers.concatenate((x_conv_3, x_up_3))
x_up_3 conv_3_up(x_up_3)
x_up_2
convT_3_up(x_up_3)
x_up_2
x_up_2
x_up_l
x_up_l
x_up_l
layers.concatenate((x_conv_2, x_up_2))
conv_2_up(x_up_2)
convT_2_up(x_up_2)
layers.concatenate((x_conv_l, x_up_l))
conv_l_up(x_up_l)
x_up convT_l_up(x_up_l)
x_up layers.concatenate((x, x_up))
x_up
conv_up_l(x_up)
conv_up_2(x_up)
x_up
u_net = tf.keras.Model(x, x_up)
u_net.compile(optimizer='adam',
loss=tf.losses.SparseCategoricalCrossentropy(),
metrics=['accuracy'])
plot_model(u_net, to_file="Images/u-net.png", show_shapes=True)
© RA-MA
184 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
(Nouc. 12:8. 118. 6)
input (None. 118. 128. 32)
conv2d_l6: Conv2D >-�-+----------<
outpul: (None. 118. 128. 3)
Figura 3.48. Esquema de nuestro modelo U-Net.
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 185
cb_earlystopping = EarlyStopping(patience=4,
cb_ modelchckpnt
monitor='val_accuracy')
Mode1Checkpoint(filepath='model_unet.h5',
monitor='val_accuracy',
save_best_only=True)
history_unet
u_net.fit(train.batch(64),
epochs=50,
validation_data=test.batch(64),
callbacks=[cb_earlystopping,cb_modelchckpnt])
plot_history(history_unet)
plt. show()
Tasa de acierto del modelo
Loss del modelo
-+- Entrenamiento
0.9
85
-•- Validación
0.8
-+- Entrenamiento
-•- Validación
80
QJ
0.7
"' 75
·¡::;
VI
VI
QJ
"' 70
_g 0.6
0.5
65
0.4
0.3
60
o
5
10
Épocas
15
o
5
10
Épocas
Figura 3.49. Dinámicas de entrenamiento del modelo U-Net.
show_results(u_net, train, n=5)
plt. show()
15
© RA-MA
186 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Original
Original
Original
Original
Original
Figura 3.50. Predicciones del modelo U-Net sobre el conjunto de entrenamiento.
show_results(u_net, test, n=S)
plt. show()
Predicted
Predicted
Predicted
Predicted
Predicted
Figura 3.51. Predicciones del modelo U-Net sobre el conjunto de test.
Data Augmentation
En la práctica anterior hemos comprobado lo efectivo que puede ser el Data
Augmentation cuando trabajamos con conjuntos de datos no demasiado grandes o
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 187
sufrimos de sobreajuste. Ya hemos visto que en un problema de clasificación de
imágenes no es demasiado complicado de implementar, pero al trabajar en un
problema de segmentación aumenta ligeramente la complicación: tenemos que
aplicar las mismas transformaciones a las imágenes y a las máscaras. No tiene
sentido que rotemos la imagen original hacia un lado y la máscara correspondiente
hacia el otro, nuestro modelo no podría extraer ninguna información útil de eso.
Para solventar esto podemos utilizar las semillas, al definir las transformaciones
aleatorias del aumento de datos es posible establecer diferentes semillas para que el
proceso aleatorio siempre sea igual. De esta manera nos podemos asegurar de que las
imágenes y las máscaras se transforman de la misma manera.
Además de esta opción también podemos usar la librería Albumentations,
que es una librería diseñada para implementar de forma rápida y sencilla una gran
cantidad de aumento de datos para muchos problemas distintos basados en imágenes.
Esta librería se va a encargar de transformar igual las imágenes y las máscaras para
hacemos la vida más fácil.
Ejercicio
Investiga como podrías aplicar Data Augmentation a un problema de
segmentación sin utilizar Albumentations. Presta especial atención al uso de las
semillas en los generadores aleatorios.
Albumentations
La librería se puede instalar fácilmente utilizando pip: pip install -U
albumentations.
import albumentations as A
De forma muy similar a cómo lo hacíamos antes, solamente tendremos
que definir una lista de las transformaciones que queremos utilizar y encapsularla
dentro del objeto A. Compose (). La lista de transformaciones disponibles es muy
extensa
(https://albumentations.ai/docs/getting_started/transforms_and_targets/)
pero vamos a utilizar rotación y volteado horizontal.
augmentations = A.Compase([
A.Rotate(limit=45),
A.HorizontalFlip()
])
© RA-MA
188 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Una vez hemos definido las transformaciones deseadas simplemente
tenemos que aplicarlas sobre la imagen y la máscara al mismo tiempo. El resultado
de esta operación es un diccionario con las dos imágenes en las claves image y
mask respectivamente. Para poder apreciar los resultados podemos aplicar la misma
transformación varias veces y representar los diferentes resultados:
n = 5
fig, axes = plt.subplots(2, n)
for col in range(n):
aug_test = augmentations(image=img.numpy(), mask=seg_mask.numpy())
axes[0,col).imshow(aug_test['image'].squeeze())
axes[0,col).set_title('Original')
axes[0,col].axis('off')
axes[l,col).imshow(aug_test['mask'].squeeze())
axes[l,col).set_title('Mask')
axes(l,col).axis('off')
fig.tight_layout()
plt.show()
Original
Original
Original
Original
Original
Figura 3.52. Imagen de muestra junto a su máscara de segmentación tras aplicar distintas
transformaciones para aumentar los datos.
Solamente nos falta poder aplicarlas durante el entrenamiento a las imágenes
de nuestro conjunto original. Para ello podemos creamos una función que aplique
las transformaciones y aplicarla sobre todo el dataset con el método . map(). Hay
que tener en cuenta que ahora nuestros datasets devuelven dos elementos, la imagen
y la máscara, por lo que habrá que cambiar ligeramente la definición de la función
respecto a cuando definimos prepare_data().
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 189
def augment_data(img, seg_mask):
augmented_data = augmentations(image=img, mask=seg_mask)
return augmented_data['image'], augmented_data['mask']
Hay que tener cuidado porque las funciones de Albumentations están hechas
para utilizarse con arrays de NumPy. Esto quiere decir que tendremos que envolver
la función que acabamos de crear dentro de una tf.numpy_function() para que
TensorFlow sepa cómo manejarla:
def augment_data_2(img, seg_mask):
aug_img = tf.numpy_function(func=augment_data,
inp=[img, seg_mask],
Tout=(tf.fioat32, tf.uint8))
return aug_img[0], aug_img[l]
Además, al utilizar una tf.numpy_function() el Dataset pierde las formas de
los objetos que contiene, por lo que tendremos que volver a decirle cómo son los datos:
def set_shapes(img, seg_mask,
img_shape=(128,128,3),
seg_mask_shape=(128,128,1)):
img.set_shape(img_shape)
seg_mask.set_shape(seg_mask_shape)
return img, seg_mask
Finalmente aplicamos las transformaciones sobre el Dataset de entrenamiento:
train_aug = train.map(augment_data_2).map(set_shapes)
Volvemos a definir las capas y el modelo para reiniciar los pesos y lo
volvemos a entrenar:
cb_earlystopping = Early5topping(patience=8,
monitor='val_accuracy')
cb_ modelchckpnt = Mode1Checkpoint(filepath='model_unet.h5',
monitor='val_accuracy',
save_best_only=True)
history_unet u_net.fit(train.batch(64),
epochs=50,
validation_data=test.batch(64),
© RA-MA
190 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
callbacks=[cb_earlystopping,cb_modelchckpnt])
plot_history(history_unet)
plt. show()
Loss del modelo
Tasa de acierto del modelo
--.- Entrenamiento
-•- Validación
0.9
0.8
Vl
Vl
o
...J
85
o
t'.
QJ
80
0.7
'ü 75
0.6
ro
ro
QJ
"C
70
0.5
?f:. 65
0.4
60
0.3
o
10
30
20
Épocas
40
50
--.- Entrenamiento
-•- Validación
o
10
30
20
Épocas
40
50
Figura 3.53. Dinámicas de entrenamiento del modelo U-Net utilizando aumento de datos.
show_results(u_net, train, n=5)
plt. show()
Original
Original
Original
Original
Original
Figura 3.54. Predicciones sobre el conjunto de entrenamiento del modelo U-Net utilizando aumento de
datos.
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 191
show_results(u_net, test, n=S)
plt. show()
Original
Original
Original
Original
Figura 3.55. Predicciones sobre el conjunto de test del modelo U-Net utilizando aumento de datos.
3.6 BIBLIOGRAFÍA
Alzubaidi, L., Zhang, J., Humaidi, A. J., Al-Dujaili, A., Duan, Y., Al-Shamma, O.,
Santamaría, J., Fadhel, M. A., Al-Amidie, M., & Farhan, L. (2021). Review of
deep learning: concepts, CNN architectures, challenges, applications, future
directions. Joumal of Big Data 2021 8:1, 8(1), 1-74. https://doi.org/10.1186/
S40537-021-00444-8.
Chollet, F. (2017). Deep Learning with Python. Manning Publications.
Christlein, V., Spranger, L., Seuret, M., Nicolaou, A., Kral, P., & Maier, A. (2019).
Deep generalized max pooling. Proceedings of the Intemational Conference
on Document Analysis and Recognition, ICDAR, 1090-1096. https://doi.
org/10.1109/ICDAR.2019.00177.
Dahl, G. E., Sainath, T. N., & Hinton, G. E. (2013). Improving deep neural networks
for LVCSR using rectified linear units and dropout. ICASSP, IEEE Intemational
Conference on Acoustics, Speech and Signal Processing - Proceedings, 86098613. https://doi.org/10.1109/ICASSP.2013.6639346.
192 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Donahue, J., Jia, Y., Vinyals, O., Hoffman, J., Zhang, N., Tzeng, E., & Darrell, T.
(2013). DeCAF: A Deep Convolutional Activation Feature for Generic Visual
Recognition. 31st Intemational Conference on Machine Leaming, ICML 2014, 2,
988-996. https://arxiv.org/abs/1310.1531 vl.
Fukushima, K. (1980). Neocognitron: A self-organizing neural network model for
a mechanism of pattern recognition unaffected by shift in position. Biological
Cybemetics 1980 36:4, 36(4), 193-202. https://doi.org/10.1007/BF00344251.
Girshick, R., Donahue, J., Darrell, T., & Malik, J. (2013). Rich feature hierarchiesfor
accurate object detection and semantic segmentation. Proceedings of the IEEE
Computer Society Conference on Computer Vision and Pattem Recognition,
580-587. https://doi.org/10.1109/CV PR.2014.81.
He, K., Zhang, X., Ren, S., & Sun, J. (2015). Deep Residual Learning for lmage
Recognition. Proceedings of the IEEE Computer Society Conference on
Computer Vision and Pattem Recognition, 2016-December, 770-778. https://doi.
org/10.1109/CVPR.2016.90.
Huang, G., Liu, Z., Van Der Maaten, L., & Weinberger, K. Q. (2016). Dense/y
Connected Convolutional Networks. Proceedings - 30th IEEE Conference on
Computer Vision and Pattem Recognition, CVPR 2017, 2017-January, 22612269. https://doi.org/10.l109/CVPR.2017.243.
Hubel, D. H., & Wiesel, T. N. (1959). Receptive fields of single neurones in the
cat's striate cortex. The Journal of Physiology, 148(3), 574-591. https://doi.
org/10.l113/JPHYSIOL.1959.SP006308.
Hubel, D. H., & Wiesel, T. N. (1968). Receptivefields andfunctional architecture of
monkey striate cortex. The Journal of Physiology, 195(1), 215-243. https://doi.
org/10.l113/JPHYSIOL.1968.SP008455.
LeCun, Y., Boser, B., Denker, J. S., Henderson, D., Howard, R. E., Hubbard, W.,
& Jackel, L. D. (1989). Backpropagation Applied to Handwritten Zip Code
Recognition. Neural Computation, 1(4), 541-551. https://doi.org/10.1162/
NECO.1989.1.4.541.
LeCun, Y., Bottou, L., Bengio, Y., & Haffner, P. (1998). Gradient-based learning
applied to document recognition. Proceedings of the IEEE, 86(11), 2278-2323.
https://doi.org/10.1109/5.726791.
Ronneberger, O., Fischer, P., & Brox, T. (2015). U-Net: Convolutional Networks
for Biomedical lmage Segmentation. Lecture Notes in Computer Science
(Including Subseries Lecture Notes in Artificial Intelligence and Lecture Notes in
Bioinformatics ), 9351, 234-241. https://arxiv.org/abs/1505.04597vl.
© RA-MA
Capítulo 3. MODELOS NEURONALES ORIENTADOS A VISIÚN 193
Simonyan, K., & Zisserman, A. (2014). Very Deep Convolutional Networks for
Large-Scale lmage Recognition. 3rd Intemational Conference on Leaming
Representations, ICLR 2015 - Conference Track Proceedings. https://arxiv.org/
abs/1409.1556v6.
Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, l., & Salakhutdinov, R. (2014).
Dropout: A Simple Way to Preven( Neural Networks from Overfitting. Joumal
of Machine Learning Research, 15(56), 1929-1958. http://jmlr.org/papers/v15/
srivastaval4a.html.
Sudholt, S., & Fink, G. A. (2016). PHOCNet: A Deep Convolutional Neural Network
for Word Spotting in Handwritten Documents. Proceedings of International
Conference on Frontiers in Handwriting Recognition, ICFHR, O, 277-282.
https://doi.org/10.1109/ICFHR.2016.0060.
Vasilev, l., Slater, D., Spacagna, G., Roelants, P. and Zocca, V. Python Deep
Learning: Exploring deep learning techniques and neural network architectures
with PyTorch, Keras and TensorFlow. (2019). Packt.
Zeiler, M. D., & Fergus, R. (2013). Visualizing and Understanding Convolutional
Networks. Lecture Notes in Computer Science (Including Subseries Lecture
Notes in Artificial Intelligence and Lecture Notes in Bioinformatics), 8689
LNCS(PART 1), 818-833. https://doi.org/10.1007/978-3-319-10590-1_53.
4
MODELOS NEURONALES ORIENTADOS A
DATOS TEMPORALES
4.1 DATOS TEMPORALES. CARACTERÍSTICAS
Tal y como se ha comentado en anteriores capítulos, procesar correctamente
diferentes tipos de datos requiere de transformaciones matemáticas concretas
(por ejemplo, transformar una característica categórica en one-hot-encoding) o
estructuras de redes especiales (por ejemplo, redes convolucionales para procesar
imágenes) para poder tener en cuenta aquellas características concretas que tienen
esas estructuras de datos. De forma análoga, parece interesante pensar que un estudio
acerca de qué son los datos de naturaleza secuencial puede llevar a proporcionar
la intuición sobre la forma en que se debería estructurar una red que sea capaz de
extraer información de este tipo de datos. Aquí hay que partir de lo que se considera
una serie temporal; cualquier dato que haya sido recogido en intervalos de tiempo
constituye una serie temporal. Es un conjunto de observaciones donde la relación de
obtención de cada punto importa y puede aportar mucha información del fenómeno
que se intenta resolver. Cambiar su orden cambiaría el significado de los datos y, por
tanto, sería una fuente de ruido para los modelos. Un ejemplo de serie temporal puede
ser el ejemplo que se muestra a continuación: el número total de coches vendidos en
un concesionario por mes, entre enero de 2018 y diciembre de 2020:
© RA-MA
196 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
400
350
300
250
Ventas
200
150
100
so
//////////////////
Tiempo
Figura 4.1. Ejemplo de serie temporal, coches vendidos por mes en un concesionario, entre enero de
2018 y enero de 2020.
En este caso, hay una variable dependiente del tiempo que va desde enero
de 2018 hasta diciembre de 2020 con frecuencia mensual. Para cada mes, se tiene el
número de coches vendidos y hay, por tanto, un total de 36 valores en la serie temporal.
El ejemplo es un caso de serie temporal univariante. Si hubiera múltiples variables
dependientes del tiempo (por ejemplo, las ventas de coches en los concesionarios de
la ciudad) se estaría hablando de serie temporal multivariante.
2000
1800
1600
1400
Ventas
1
1\
1200
&
1000
,'\
\
I\
800
'
1
I
\
,,
,' , ,""" '
,' \ ,
,
600
400
'
I \ 1' 1
t I '1
' I
11
f
I 'I
1
1
\
' ./
200
Tiempo
······Conceslonariol
- - Concesionario2
-con�slonarlo3
Figura 4.2. Ejemplo de serie temporal multivariable: coches vendidos por mes en tres concesionarios
distintos, entre enero de 2018 y enero de 2020.
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 197
Hay muchas disciplinas científicas que analizan series temporales por lo que
la aplicabilidad de su análisis es extremadamente grande. Por citar unos cuantos
campos tenemos la Economía donde es clave analizar las tendencias globales/
locales de los precios/demandas de los productos analizados. Relacionado con esto
se encuentra el área del marketing donde conocer las ventas de los productos y la
evolución temporal de un producto es clave. Cambiando de tipo de aplicación nos
encontramos con la Medicina donde todas las bioseñales (ECG, EOG, EEG, etc) se
pueden considerar series temporales como las que vamos a analizar. Sin abandonar
el tema clínico nos encontramos con series temporales con una gran importancia
de cara a analizarlas: número de pacientes que llegan a urgencias en un hospital,
demanda de especialistas en una determinada área, número de llamadas al 112, etc.
Otros campos donde el análisis, caracterización, clasificación y predicción de series
temporales es clave es la meteorología/climatología tan importantes hoy en día por
el cambio climático. Otro campo importante de aplicación de las series temporales
tienen que ver con lo que se conoce como IoT (Internet of Things, Internet de las
Cosas); aquí todos los datos recogidos por los sensores que adquirirán variables de
procesos/personas/dispositivos tendrán que ser analizados mediante estas técnicas.
Más allá de las series temporales, hay datos de tipología secuencial que
pueden ser tratados con los modelos que veremos en este capítulo; entre estos datos
se encuentran los derivados del lenguaje (donde se tienen una serie de símbolos
escritos, o bien, una serie de sonidos donde el orden importa y, por tanto, se deben
aplicar técnicas de series temporales). Ejemplos de problemas a resolver dentro de
este campo del lenguaje serían:
11"'"
11"'"
Tratamiento de texto: predicción de la siguiente palabra, resúmenes de
textos, traducción de textos, generación de imágenes a través de textos.
Tratamiento de sonidos: identificación del género de una canción,
identificación de la similitud entre dos canciones, transcripción de voz a
texto, generación de imágenes a partir de sonidos.
Se proporcionarán ciertas nociones sobre cómo estructurar estos problemas
más adelante en el capítulo, pero su tratamiento con detalle queda más allá del alcance
de los contenidos del libro y se aconseja al lector interesado consultar bibliografía
especializada en NLP (Natural Language Processing).
En todo problema de aprendizaje profundo, el proceso de limpieza y
enriquecimiento de los datos es muy importante para poder obtener modelos precisos
como se ha visto en el primer capítulo. Para poder llevar a cabo este proceso de forma
efectiva, resulta interesante comentar algunas de las propiedades más importantes
que pueden ser detectadas en una serie temporal. En concreto, se comentará la
198 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
tendencia, la estacionalidad, y cómo estas dos magnitudes están relacionadas con la
estacionariedad de una serie temporal.
,- Tendencia. Es un comportamiento que muestra el desplazamiento lineal
de los valores de la serie temporal sobre un gran periodo de tiempo. Dicho
de otra forma, se observa una tendencia cuando hay un comportamiento
lineal que puede tener una pendiente positiva o negativa dentro de la serie
temporal.
400
350
300
250
Ventas 200
150
100
so
o
nempo
400
350
300
250
Ventas
150
100
so
s� �s �s �s �� �s s� �s �s �s �� ss s� �s �s �s �� ss
r�moo
Figura 4.3. Arriba, serie temporal con tendencia ascendente.
Abajo, serie temporal con tendencia descendente.
La tendencia no tiene por qué ser constante en una serie temporal: puede
cambiar de creciente a decreciente, o puede incluso cambiar su pendiente
según va avanzando el tiempo. Existe incluso la posibilidad de que no
aparezca una tendencia clara en la serie temporal.
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 199
Matemáticamente, la tendencia se puede calcular utilizando la media
móvil; consiste en determinar la media de una ventana, de tamaño N, de
valores que comienzan en el valor actual:
-
Xt
1
'°'N-1
= N -'-'k=O
Xt-k
Ecuación 4.1
La media móvil permite eliminar pequeñas oscilaciones obteniendo la
tendencia de la serie temporal en cada punto. La serie temporal obtenida
es menos sensible a las vibraciones de alta frecuencia, centrándose en los
comportamientos a largo plazo.
Ventas
16
14
12
10
8
6
4
o
=================
s s�"' s� i s� s s� s� s� I s �� s� s s�
o
o
o
o
o
o
o
is
3 ::¡ :!: ::; :!: � � � � �
:al
o
I
o
:al
Tiempo
�o
Figura 4.4. Serie temporal original y serie obtenida al aplicar una media móvil con N= 3-
El cálculo de la media móvil depende del parámetro N; este parámetro
determina las frecuencias de las oscilaciones de alta frecuencia que son
eliminadas/amortiguadas. Se puede demostrar que esta operación de
promediado actúa como un filtro pasa-baja, sistema que deja pasar las
bajas frecuencias y elimina las altas. A mayor valor de N, más datos
se cogen para el cálculo de la media y una mayor cantidad de altas
frecuencias son eliminadas. En la práctica, es interesante escoger un
valor de N que permita eliminar el ruido a corto plazo, pero no tan grande
como para obviar los movimientos de la tendencia que, precisamente, se
quieren obtener con la media móvil; aquí el conocimiento del experto en
el problema es fundamental.
200 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Vantas
40
"
30
"
20
"
10
Tiempo
Figura 4.5. Serie temporal con dos medias móviles. La primera, línea fina continua,
tiene N=5. La línea punteada tiene N=l5. Se aprecia cómo la línea punteada ignora
movimientos de alta frecuencia de la serie temporal que la otra media móvil no, sobre todo
en la parte izquierda de la gráfica.
Esta herramienta sigue siendo muy usada en series temporales de
bolsa para estudiar las tendencias de las acciones. En este caso, se usan
valores pequeños de N para comprobar la tendencia de la acción a
corto plazo y realizar inversiones de alta frecuencia (en bajos intervalos
temporales). Además, se usan valores grandes para comprobar dicha
tendencia en intervalos de tiempo mayores para así obtener una idea de
su comportamiento a largo plazo.
,.. Estacionalidad. Es una característica de las series temporales en la
que los datos experimentan cambios regulares y predecibles con una
frecuencia constante. Esta frecuencia puede ser, por ejemplo, diaria,
semanal o mensual. Cualquier fluctuación predecible de frecuencia
constante que aparezca en una serie temporal se dice que es estacional.
Aparece de forma natural en muchos procesos medidos. El consumo
eléctrico en una casa presenta una frecuencia estacional diaria (cuando
estamos durmiendo, no se consume electricidad, por ejemplo). Además,
el consumo eléctrico crece en las épocas frías como otoño e invierno, por
lo que se aprecia también una estacionalidad de frecuencia anual. Otro
ejemplo son las ventas de productos por intemet. Un ejemplo concreto
de este último caso descrito aparece detallado en las gráficas siguientes:
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 201
Ventas
200
180
160
140
120
100
80
60
40
20
I
¡ s� sa
i
i
;,i
Ventas
;,i
¡¡ j
.1!
;,i
= � :f ... ::J
I �.e.��.a
j
;JI
(A)
¡¡ pi! j
.1!
� .: l
;,i
.e.,:¡ iº .e.a
¡¡
.1!
n
Tiempo
;,i -
3S00
3000
2S00
2000
1S00
1000
soo
1 I�
j
;,i
¡¡ j
l ;,i
��
¡¡ j
.1! :!
i i ! i ¾ i � ! ! ! 1 ! Tiempo
H.1! � 3 l :ii .: l � 3 l :!
¡¡ j i ¡¡ j i ¡¡ pi!
(B)
Figura 4.6. Dos gráficas con series temporales con estacionalidad semanal.
En la figura 4.6 (A), se puede observar que la gente tiende a comprar
más productos en las cercanías del fin de semana: se puede apreciar
gráficamente la estacionalidad semanal. En la figura 4.6 (B) se representa
la misma cantidad que en la gráfica superior, pero en un intervalo temporal
anterior. Este intervalo temporal coincidió con una campaña de marketing
agresiva que popularizó la tienda e hizo que las ventas crecieran mucho.
Las dos series temporales presentan la misma estacionalidad, a pesar de que
sus valores máximos y mínimos en distintos puntos sean tan distintos. Uno
de los métodos utilizados comúnmente para cuantificar esta estacionalidad
de forma matemática es usar la operación de diferenciación. Consiste
en obtener una nueva serie temporal a partir de la original calculando
diferencias de valores en diferentes instantes de tiempo. La diferenciación
de primer orden de una serie temporal sería la siguiente:
1
X t = Xt - Xt-1
Ecuación 4.2
202 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
En este caso, la nueva serie temporal diferenciada tendría una entrada
menos que la serie temporal original. La diferenciación necesaria para
eliminar la tendencia estacional es la diferenciación estacional. Se calcula
aplicando la diferencia entre la observación y la observación anterior en
la misma posición de la estación. Por ejemplo, en el ejemplo descrito
anteriormente, la diferencia estacional del día jueves 28/02 se calcularía
restando el valor original del jueves 28/02 con el valor del jueves anterior,
21/02, expresado matemáticamente:
1
Xt
= Xt - Xt-p
Ecuación 4.3
Donde pes el periodo estacional, en el ejemplo descrito anteriormente,
p = 7.
En la figura 4.7 se pueden apreciar las series anteriores aplicando una
diferenciación de primer orden y una diferenciación estacional. Se
puede comprobar claramente que la diferenciación estacional elimina el
comportamiento que ha sido identificado fácilmente de forma visual:
Ventas
400
(A)
300
200
100
o
150
(B)
-150
20
10
(C)
o
-10
-20
-30
Tiempo
Figura 4.7. (A) Serie temporal original. (B) Serie obtenida aplicando una diferenciación de primer orden a
la serie original. (C) Serie obtenida aplicando una diferenciación estacional (p = 7), que es el periodo de
la estacionalidad observada en la serie original. El impacto de la estacionalidad se ha eliminado.
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 203
,.- Estacionariedad. Una serie temporal es estacionaria si su media y su
desviación estándar se mantienen constantes en el tiempo. Gráficamente,
esto se puede apreciar cuando los valores de la serie oscilan alrededor
de una media constante y la variabilidad con respecto a esa media
también permanece constante en el tiempo. Las series que presenten
una tendencia o una estacionalidad no son estacionarias, pero se pueden
utilizar los métodos descritos anteriormente para intentar transformar una
serie en estacionaria. Existe la posibilidad de que simplemente con estos
métodos no se pueda transformar la serie en estacionaria y haga falta
echar mano de otros métodos, y afortunadamente las redes recurrentes no
van a requerirlo para poder hacer predicciones precisas.
Una vez vistas las características básicas de una serie temporal haremos un
rápido repaso a los métodos que podríamos definir cómo clásicos antes de explicar
los basados en Aprendizaje Profundo. Existe un gran número de modelos que se han
aplicado para modelizar y realizar predicciones en el campo de las series temporales.
Estos modelos tienen como característica general su linealidad, esto es, la mayoría
de estos modelos presentan una dependencia lineal con los errores cometidos y/o con
anteriores valores de la serie. Estos modelos se conocen como modelos MA (Moving
Average, términos que consideran los errores), AR (Autoregressive, términos que
consideran valores anteriores de la serie) y su combinación, modelos ARMA. Son
modelos extremadamente flexibles porque el número de entradas/salidas anteriores al
modelo considerado se puede variar según las características del problema a resolver.
Estos parámetros, básicamente el orden del modelo, se pueden determinar de forma
directa con el uso de la autocorrelación y la correlación parcial de la serie temporal
que se está analizando. A estos modelos básicos se les pueden añadir "extras" de tal
forma que la capacidad del modelo aumenta; ejemplo de esto son:
,.- ARIMA. Aquí se integra el cálculo de la diferencia entre valores de la serie
para eliminar posibles valores constantes que se tengan en la serie. Una
evolución de este método sería el SARIMA que entre sus características
está el intentar integrar posibles estacionalidades de la serie temporal en
el modelo.
,.- ARMAX. La X hace mención a la consideración de variables exógenas
( de ahí la X, variables externas a la serie analizada) a la propia serie
temporal. Esta aproximación se puede combinar con las anteriores para
mejorar la predicción del modelo; por ejemplo, se puede tener modelos
ARIMAX o SARIMAX. Derivados de estos modelos se encuentran
los que se conocen como NARX (la primera N hace referencia a que
son modelos no lineales) y que se diferencian de los anteriores en que
se pierde su linealidad y, por lo tanto, son más difíciles de ajustar e
204 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
interpretar, pero, sin embargo, aumentan su capacidad de modelización
de forma considerable; serían los modelos más cercanos, en cuanto a
funcionamiento, a los que se van a comentar en el apartado 2 del presente
capítulo.
11"'
Aparte de los anteriores se encuentran los que consideran que los
parámetros de la serie cambian de forma no estacionaria y de forma
brusca como pueden ser las series económicas. En este caso aparece un
nuevo modelo que considera esta circunstancia ARCH (Autoregressive
Conditional Heteroskedasticity) y sus variaciones GARCH (Generalized
AutoRegressive Conditional Heteroscedasticity).
Dado que el objetivo de este libro es explicar los modelos neuronales y su
aplicación en diferentes problemas, no entraremos a explicar los modelos comentados
anteriormente pero sí aconsejamos al lector que utilice estos modelos como nivel
de referencia en los problemas asociados a las series temporales antes de analizar
versiones más complejas como pueden ser los modelos que veremos a continuación.
4.2 MODELOS MULTICAPA RECURRENTES CLÁSICOS
La manera más directa de usar un modelo neuronal en problemas de series
temporales es considerar como entradas los valores de la serie actuales y anteriores
junto con el resto de entradas que se quieran considerar, esta estructura conocida
como TDNN (Time Delay Neural Network) queda representada por la siguiente
figura:
CAPA
OCULTA
1
CAPA
OCULTA
2
CAPA
OCULTA--►�
� Yt_.
N
Figura 4.8. Esquema de una TDNN. Se observan los valores de la serie retardados de la serie x junto con
el resto de las entradas consideradas (e). En este esquema se plantea una salida que puede ser una
predicción de la serie, por ejemplo \+p siendo p el horizonte de predicción.
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 205
Una TDNN no deja de ser un perceptrón multicapa donde algunas de las
entradas son valores temporales pero no hay ningún cambio en cuanto a algoritmo
de aprendizaje se refiere por lo que se puede aplicar directamente lo aprendido en el
capítulo 2. En el caso de la TDNN la memoria del sistema se considera en la entrada
del modelo al utilizar valores anteriores en la serie temporal a la entrada del modelo.
Una modificación a esta aproximación es considerar esos retardos en la(s) capa(s)
oculta(s). La siguiente figura muestra esta aproximación conocida como la red de
Elman.
CAPA DE
ENTRADA
CAPA
OCULTA
CAPA
SALIDA
CAPA
CONTEXTO
Figura 4.9. Esquema de una red de E/man.
Por simplicidad se ha considerado una capa oculta y un retardo en la salida
de dicha capa, pero se puede generalizar a cualquier arquitectura y cualquier retardo.
Según la figura 4.9 los valores de salida de la capa oculta y la salida en el instante t
vendrían dados por las siguientes expresiones:
ht
= f[Wh · ht-1 + Wx · Xt + bh]
Yt = f[Wo · ht + bo]
Ecuación 4.4
Donde los diferentes W son los pesos sinápticos que conectan las capas
de entrada con la oculta y la oculta con la de salida, b son los sesgos de la capas
correspondientes y f la función de activación. Al igual que con la TDNN, el algoritmo
de retropropagación no sufre una gran variación y se puede aplicar directamente a
esta estructura.
El último modelo neuronal que vamos a considerar antes de pasar a los
modelos neuronales profundos recurrentes es la red de Jordan que plantea otra
aproximación a la hora de trabajar con series temporales. En este caso la parte de
memoria de la red procede de la capa de salida, que vuelve a alimentar a la capa
oculta; el esquema de esta red se muestra en la figura 4.10.
206 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
CAPA DE
ENTRADA
CAPA
OCULTA
© RA-MA
CAPA
h __
...►I
t
► SALIDA
CAPA
CONTEXTO
Figura 4.10. Esquema de una red de )ardan.
En base a la figura 4.1 O los valores de salida de la capa oculta y la salida en
el instante t vendrían dados por las siguientes expresiones:
ht = f [Wh · Yt-1 + Wx · Xt + bh]
Yt = J[Wo · ht + bo]
Ecuación 4. 5
Estos tres modelos neuronales proporcionan buenos resultados cuando el
problema a resolver presenta no linealidades y no se tiene una gran cantidad de datos
que permita el uso de modelos más avanzados como los que se verán a continuación.
4.3 REDES RECURRENTES (RNN)
Las redes neuronales recurrentes son una clase de red neuronal artificial en
las que las neuronas se estructuran de forma que las series temporales introducidas se
gestionan de forma secuencial, teniéndose en cuenta los valores anteriores al actual,
¡los datos presentan una cierta memoria! Tienen una estructura similar a una red
densa, en la que cada valor de la serie temporal pasa por una neurona distinta y
se multiplica por unos pesos distintos. Sin embargo, en este caso, los resultados
obtenidos en cada neurona se usan también para calcular el valor de salida siguiente
en la serie temporal. Es decir, se transmite información de los pasos anteriores a los
pasos posteriores.
Para entender mejor esta explicación pongamos un ejemplo. Imaginemos una
serie temporal {x0 , x1 , ... , xt}, donde x i , i E [O, t] es el elemento de la serie en la
posición i. El primer elemento de la serie temporal x 0 pasa por la primera neurona de
la capa y, tras un producto por unos pesos y sumarle un sesgo se obtiene el resultado
de la celda: Yo. Hasta aquí, todo es análogo a las redes densas vistas en capítulos
anteriores. La novedad comienza en los siguientes elementos de la serie temporal: para
obtener el resultado de la segunda celda: y1 se utilizarán tanto el segundo elemento
de la serie temporal x 1 como el valor obtenido de la celda anterior y0. Cada uno de
estos dos elementos se multiplica por una matriz de pesos diferente y al resultado de
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 207
la suma de ambos se le suma nuevamente un sesgo. Esta estructura se repite en todas
las celdas de la capa, de forma que las neuronas se comunican información de forma
secuencial, desde el primer dato de la serie temporal hasta el último.
Tras pasar por toda la red, se obtiene la salida Yt; un vector cuyo tamaño es
igual al número de características de salida con el que se ha diseñado la red.
Figura 4.11. Una capa de red neuronal recurrente donde se muestra la dirección en la que viajan los
datos. Por simplicidad, se suele representar como una red 'enrollada' en sí misma, tal y como aparece a
la izquierda.
En resumen, cada neurona en una red recurrente obtiene datos de dos fuentes
diferentes: una son los datos de entrada que se le proporcionan a la red y otra los
resultados obtenidos de la celda anterior. Es por esto por lo que la RNN va a necesitar
dos conjuntos de parámetros, uno para cada una de las fuentes. El proceso matemático
que ocurre dentro de una neurona se puede expresar matemáticamente de esta forma:
Yt = f(W · Xt +U· Yt-1 + b)
Ecuación 4. 6
Donde W son los pesos de la matriz relacionada con las entradas actuales,
y U los pesos de la matriz relacionada con las salidas anteriores de la red. Algunos
aspectos interesantes de estas redes a tener en cuenta son:
1. La idea detrás de ir transportando los valores de la celda desde el
inicio hasta el final de la secuencia es extraer la información relevante
de los datos temporales, es decir, el contexto. Los pesos que calculan
estos estados se entrenarán para acabar entendiendo el comportamiento
intrínseco de la serie temporal.
2. El valor final de salida que arroja la red es el estado de la última celda
Ytinal. Será este valor el que normalmente se utilizará para comparar con
un valor real, obtener la función de pérdidas y realizar posteriormente
toda la retropropagación de la red. Por ejemplo, si nuestro objetivo es
predecir el comportamiento de la serie temporal en el paso t + 1, este
208 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
será el valor que utilicemos para ello. Sin embargo, hay situaciones en las
que puede interesar utilizar toda la cadena de valores que ha producido
cada una de las neuronas de la red para cada uno de los pasos temporales
de la secuencia: {Yo, Y1, ... , Ytl Estos casos son menos usuales, pero se
comentarán más adelante.
Para usar de forma satisfactoria una RNN, las entradas a la red han de ser
conjuntos de características que estén medidas a través del tiempo o tener algún
carácter secuencial. Una entrada típica a una RNN presenta 3 dimensiones: (n, t, f),
donde n es el número de muestras que tenemos, t el número de pasos que tienen las
series temporales y f el número de características con serie temporal que se están
introduciendo en la red. Si se trata de un problema univariante, f = 1. Sin embargo,
si es multivariante, f > 1. Tras pasar por la red recurrente, la salida tendrá la forma
(n, f'). Es decir, cada una de las n muestras tendrá f' características de salida. Cada
una de estas características puede ser, por ejemplo, la predicción de la serie en los
próximos f' pasos.
Para entrenar la red y que sea capaz de predecir el comportamiento a futuro,
es necesario construir muestras a partir de las series temporales de entrada de las
que se disponen, y transformarlo en un problema de aprendizaje supervisado. Esto
se realiza mediante el enventanado de los datos que consiste en transformar las
series temporales para que puedan ser utilizados como datos de entrada por redes
recurrentes. Es decir, se está transformando un conjunto de series temporales en una
colección relativamente grande de datos de entrada para que la red se pueda entrenar
con ellos. El proceso concreto es ligeramente diferente en función del problema
que se tenga. Por simplicidad, se explicará el procedimiento para el problema de
predicción del paso siguiente de la serie. Para llevarlo a cabo, se sigue el siguiente
procedimiento:
,., Se define, al principio, un tamaño de ventana: w que definirá la cantidad
de datos temporales considerados a la entrada de la red.
,., Comenzando por el principio de la serie temporal, se cogen los primeros
w elementos de ésta. Es decir, el intervalo [x 0, ..., x w_ 1]. Esos elementos
formarán el primer patrón de entrada a la red. Por otro lado, se coge el
elemento siguiente de la serie, Xw, y se guarda como la etiqueta asociada
a esta ventana.
,., Desplazando el punto inicial un paso, se cogen los siguientes w valores.
Es decir, ahora el patrón de entrada a la red está formado por el intervalo
[x 1, ..., xJ. En este caso, el elemento que se cogerá como etiqueta será
Xw+l·
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 209
,.- Se repite este proceso hasta terminar la serie temporal. Para una serie
temporal con T pasos, se han hecho un total de T - w - 1 ventanas, y ese
es el número de patrones que habrá para entrenar la red. El -1 es debido
al elemento que se usa como etiqueta tras cada ventana.
Ventana (w=n
Ventas
Etiqueta
25
e:
20
15
10
Cambio de
ventana
25
20
15
10
:l
I1i
:l
:l
:l
is:
::;
:l
is:
:l
is:
I
:l
:l
is:
iis: i;: ::: i:,J
Tiempo
Figura 4.12. Proceso de creación de dos muestras de entrenamiento utilizando el método de enventanado
descrito anteriormente con w= 7.
Por último, dos comentarios adicionales sobre el proceso de enventanado:
,.- Se ha explicado un ejemplo del proceso para un caso concreto pudiendo
ser modificado acorde con las necesidades del problema. Si, por ejemplo,
se pretende predecir varios pasos de la serie temporal a la vez, habrá
también una ventana de salida de tamaño w', siendo este valor el número
de pasos a futuro a predecir. En este caso, el número de datos que se
extraen de la serie temporal es T - w - w'.
,.- El tamaño de ventana w ha de ser lo suficientemente grande como para
poder realizar un proceso de inferencia a través de ella y poder realizar
una predicción correcta, pero lo suficientemente pequeño como para que
en nuestra serie temporal haya suficiente multiplicidad de situaciones.
Por ejemplo, si tenemos un total de 3 años de datos con frecuencia diaria
© RA-MA
210 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
y nuestro tamaño de ventana es de 2 años, la mayor parte de las muestras
generadas tras este proceso de enventanado van a contener una secuencia
similar de datos todas ellas, por lo que la variabilidad de los datos de
entrada va a ser baja y el espacio de probabilidades que va a inferir la red
va a ser bajo y va a sufrir de sobreajuste. En esencia, es un hiperparámetro
más de la red con el que trabajar.
De la misma manera que en el resto de los problemas vistos anteriormente,
es conveniente dividir la totalidad de los datos de los que se dispone en conjuntos
de entrenamiento, validación y test. Sin embargo, los datos temporales tienen la
característica de que el orden importa, por lo que no se pueden dividir de forma
arbitraria. Los datos temporales han de dividirse en conjuntos ordenados en 3
fragmentos de tiempo. El tamaño de cada uno de los fragmentos será equivalente
al porcentaje de datos que se destinarán a entrenamiento, validación y test. Esta
división ha de ser previa al proceso de enventanado descrito anteriormente, ya que,
si el corte se hace con la serie temporal original va a producirse una transferencia de
información entre los conjuntos de datos. Por ejemplo, se puede tener que datos de las
ventanas finales del conjunto de entrenamiento también aparecerán en el conjunto de
validación y lo mismo ocurrirá con el conjunto de validación y de test. Destacar que
estos fragmentos no hace falta que sigan ningún orden concreto. Incluso se puede
intercalar el conjunto de test en el interior del conjunto de entrenamiento. En la
figura 4.13 se da un ejemplo de partición de una serie temporal.
Entrenamiento
Validación
Test
Ventas
40
35
30
25
20
15
10
=:
�2
�
=:
I:s
=:
I
2
=:
2
'.;
�
::l
=:
2
!g
=:
2
!
=:
2
!:::
Tiempo
Figura 4.13. Ejemplo de subdivisión de serie temporal en entrenamiento, validación y test.
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 211
En una red neuronal (MLP convencional o un modelo convolucional) la
información pasa únicamente de forma secuencial entre las capas no existiendo
realimentación entre capas ni entre neuronas. Por tanto, en el proceso de
retropropagación, la información seguirá un camino similar, pero a la inversa:
comenzando desde la última capa y llegando hasta la primera. Sin embargo, en las
redes recurrentes, los datos no sólo pasan de capa en capa, sino también de neurona
en neurona en el interior de la capa interna, por lo que la cantidad de operaciones
implicadas en la obtención de las salidas de las redes recurrentes es mucho mayor. El
algoritmo de aprendizaje, derivado del algoritmo de retropropagación, que se aplica
a este tipo de modelos recurrentes se conoce como retropropagación a través del
tiempo (BTT, Backpropagation Through Time) y tiene en cuenta todos los procesos
de realimentación que se producen en las neuronas. Sin embargo, este algoritmo
presenta una serie de problemas entre los que destacamos dos:
1. Es fácil que una red recurrente olvide lo que ha ocurrido en muchos pasos
anteriores, ya que la información ha de circular celda a celda. Si los pesos
de una de las celdas de la red que se encargan de pasar los valores de la
celda anterior son muy pequeños, toda la información que ha viajado de
celdas anteriores acaba desvaneciéndose.
2. Por otro lado, debido a la naturaleza del algoritmo BTT los datos pasan por
una gran cantidad de cálculos intermedios por lo que se incrementan las
probabilidades de sufrir problemas como desvanecimiento del gradiente
( o su explosión) que dificultan el aprendizaje de la red.
Las redes GRU ( Gated Recurren! Unit) y LSTM (Long-Short-Term Memory)
utilizan métodos más refinados para asegurar que la información se pasa y gestiona
de forma adecuada entre celdas. También plantean mejoras para evitar problemas
relacionados con el desvanecimiento de gradiente.
4.4 LONG-SHORT-TERM MEMORY (LSTM)
Como ya se ha comentado anteriormente, una de las principales
problemáticas presentadas por las redes recurrentes (RNN) es su ineficacia para
trabajar con dependencias temporales a largo plazo, cuya causa viene generada por
el desvanecimiento del gradiente al propagar los errores cometidos por la red en la
fase de entrenamiento. En 1997, Sepp Hochreiter y Jürgen Schmidhuber propusieron
un nuevo modelo neuronal para solucionar este problema: las redes Long Short­
Term Memory (LSTM), cuya estructura se muestra en la figura 4.14. Estas redes
están específicamente diseñadas para permitir el reconocimiento de dependencias
temporales tanto en el corto como en el largo período de tiempo.
© RA-MA
212 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
T+l
T-1
x,-1
Figura 4.14. Visión general de neuronas de una capa LSTM.
Una de las principales diferencias que incorporan las redes LSTM en su
arquitectura, y que son fundamentales para garantizar la memoria a largo plazo,
es la celda estado (figura 4.15), que recorre toda la red incluyendo mínimas
transformaciones lineales, y que permiten añadir, o eliminar, información del pasado.
Figura 4.15. Celda estado de la LSTM.
El primer elemento de la red LSTM es, precisamente, lo que se conoce como
forget gate, que permite determinar cuan relevante es la información del pasado a
seguir manteniendo en la memoria de la red, en la celda estado. Permite añadir,
o eliminar, información pasada estando compuesta por una función sigmoide (o"),
cuya salida genera valores entre O y 1, donde O implica que no va a añadirse nueva
información a la memoria y 1 que toda la nueva información va a añadirse a la
memoria. La figura 4.16 muestra un esquema de esta puerta.
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 213
Figura 4.16. Esquema de la forget gate de la LSTM.
Funcionalmente, la salida de la forget gate pone los valores temporales
pasados con más peso a valores cercanos a uno, mientras que los menos relevantes
los pondría a valores cercanos a cero. Matemáticamente, se expresa como:
Ecuación 4.12
El siguiente elemento que forma la arquitectura de la red LSTM se denomina
input gate (figura 4.17) y permite determinar qué nueva información se va a añadir a
la celda estado. Este nuevo elemento presenta dos partes. La primera, es una nueva
función sigmoide, que decide qué valores van a ser actualizados. La segunda, donde
se aplica una función tangente hiperbólica (tanh), genera un vector con nuevos
valores candidatos a ser incluidos en la celda estado.
Figura 4.17. Esquema de la Input gate de una LSTM.
214 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Podemos expresar de forma matemática esta sección de la red con las
siguientes ecuaciones:
it = CJ (Wi · [ht-l, Xt] + bi)
Ct = tanh (Wc · [ht-1, Xt] + be)
Ecuación 4.13
Ecuación 4.14
A continuación, se debe añadir toda esta nueva información obtenida de las
salidas de las puertas anteriores (input gate y forget gate) al nuevo valor actualizado
de la celda estado, Ct. El resultado se muestra en la figura 4.18.
---+----------
f,l ;,r4é
,
Figura 4.18. Combinación de la infor mación proceden te de la forget gate y la input gateen la cel da
estado.
Ecuación 4.15
El último paso de la celda LSTM será decidir qué salida, h t, va a obtenerse.
Este resultado, mostrado en la figura 4.19, dependerá de la celda estado y la salida,
h t -l, de la celda anterior. Para conseguir el valor de salida la señal pasará finalmente
por una función sigmoide. Por otro lado, se toman los valores de la memoria y se pasan
por una función tangente hiperbólica. La salida de ambas funciones se multiplica,
finalmente obteniéndose así la predicción para la celda LSTM en cuestión.
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 215
Figura 4.19. Esquema de la output gate de la LSTM.
Matemáticamente esta última fase puede expresarse con las siguientes
ecuaciones:
Ot = (J (Wo · [ht-1, Xt] + ha)
ht = Ot · tanh(Ct)
Ecuación 4.16
Ecuación 4.17
4.5 REDES GATEO RECURRENT UNIT (GRU)
Tras la publicación del modelo LSTM, las líneas de investigación se centraron
en cómo poder mejorar el funcionamiento de dicha arquitectura. Existen distintas
versiones de las redes Long Short-Term Memory que plantearon ligeros cambios en
el modelo. No obstante, una de las propuestas que más difieren de las LSTM clásicas
son las redes denominadas Gated Recurrent Unit (GRU, por sus siglas en inglés), y
que se muestra en la figura 4.20.
Figura 4.20. Representación esquemática de una neurona GRU.
216 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Este modelo combina las puertasforget gatee input gateen una única puerta
denominada update gate. Además, este modelo también combina la celda estado
(cell state) y el estado oculto. Este modelo simplifica las redes LSTM comentadas
anterionnente. Como es de esperar, la red sufre también cambios en el modo de
calcular los valores de las distintas puertas hasta llegar a la salida. Las ecuaciones de
la red GRU se pueden expresar matemáticamente de la siguiente forma. En este caso,
la update gate viene definida matemáticamente como:
Zt
= CJ' (Wz · [ht-1, Xt])
Ecuación 4.18
Por otro lado, el cálculo de la reset gate (recordemos, la puerta que es usada
para decidir cuánta información pasada debemos olvidar o recordar), obtendría su
salida de aplicar la siguiente ecuación:
Ecuación 4.19
Por último, la cogibinación de la salida de la reset gate con la memoria del
instante pasado generan h t, que se utilizará para calcular la salida de la celda en el
instante he:
= tanh (W · [rt. ht-1, Xt])
ht = (1 - Zt) . ht-1 + Zt o ht
ht
Ecuación 4.20
Ecuación 4.21
4.6 APLICACIONES DE LAS REDES RECURRENTES
Hasta ahora, se han utilizado las redes recurrentes para resolver problemas
de predicción en series temporales. Sin embargo, las posibilidades de uso de las
redes recurrentes van mucho más allá, desde usar diferentes tipos de datos hasta
aplicaciones distintas a predicción. Los textos se pueden interpretar como datos
secuenciales. El orden de las palabras importa a la hora de interpretar un texto y,
gracias a las reglas definidas por la sintaxis y la morfología, se pueden obtener métodos
para interpretar el significado de textos y transformar las palabras en elementos que
puedan ser interpretados por una red secuencial. Los métodos concretos para poder
llevar esto a cabo están más allá del alcance de este libro, pero se le deja al lector que
investigue por su cuenta estas metodologías. Además, no sólo texto, sino formas de
datos secuenciales como audio o video también pueden ser la base de un problema
que puede ser resuelto con este tipo de modelos. Hay que tener en cuenta que de la
misma manera que las redes que procesan imágenes grandes van a necesitar más
capas y filtros para extraer la información de la imagen, las secuencias largas, van
a necesitar de redes secuenciales más grandes y complejas. En este último caso hay
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 217
unas cuantas herramientas que se van a explicar brevemente que pueden ser útiles al
resolver problemas con estos tipos de datos secuenciales complejos:
,.. Apilado (Stack) de redes recurrentes: Añadir capas recurrentes una
encima de otra incrementa el poder representacional y de abstracción
de la red, pero añade la desventaja de una mayor carga computacional.
Los estados internos de las neuronas de una capa se usan como entradas
para la capa siguiente. Sin embargo, hay que recordar que la cantidad de
operaciones que hay en una red secuencial es mucho mayor a una red
convolucional o densa, por lo que no será necesario apilar más de dos o
tres capas para los problemas más complejos.
,.. Capas secuenciales bidireccionales: Añade un segundo flujo de
información en las neuronas de la capa secuencial: adelante-atrás y atrás­
adelante, presentando así la información en la red de formas distintas,
aumentando la precisión y mitigando los problemas de olvido que tienen
las redes. Así, cada neurona presenta dos estados: uno lo comunica a la
neurona que se encuentra adelante, y el otro a la neurona de atrás. Este
tipo de modelos no se puede utilizar en aplicaciones de tipo streaming,
esto es, en aplicaciones donde los datos se van procesando en tiempo real.
Los problemas que pueden ser resueltos mediante redes recurrentes se
pueden agrupar en 5 tipos, en función de la naturaleza secuencial de la entrada y
salida y, por tanto, de la forma de estructurar la red:
,.. Uno a uno (One-to-one): Este es el caso en que ni el dato de entrada ni el
de salida son de naturaleza secuencial. La mayor parte de los problemas
que se han visto en capítulos anteriores caen en este conjunto. Por
ejemplo, un problema de clasificación de imágenes.
,.. Uno a muchos (One-to-many): Problemas donde se usan datos no
secuenciales para generar datos de naturaleza secuencial. Para ello,
las capas de la RNN se construyen con un número de neuronas igual
al número de elementos secuenciales que tenga la salida, y el dato de
entrada de naturaleza no secuencial se introduce en la primera de las
neuronas de la capa. Este método se utiliza en generación de música a
partir de una única nota, o en la generación de pies de foto a partir de la
foto.
218 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
11" Muchos a uno (Many-to-one): Problemas donde se usan datos de
naturaleza secuencial para obtener elementos no secuenciales. Para ello,
únicamente se usará el elemento de salida de la última neurona de la
red recurrente. Este es el tipo de red que se ha usado en los ejemplos de
predicción descritos anteriormente, en los que se proporciona a la ventana
de una serie temporal para predecir el punto siguiente. También se usa,
por ejemplo, para generar imágenes a partir de texto, para identificar el
género musical de una canción, o para análisis de sentimientos de texto
( es decir, cuantificar numéricamente la positividad/negatividad de un
texto)
11" Muchos a muchos (Many-to-many): Problemas donde el tamaño
secuencial de la entrada es igual al de la salida. La salida de la red son
los estados de cada una de las neuronas de la capa secuencial. Se pueden
resolver problemas, por ejemplo, de clasificación deframes en un video.
11" Muchos a muchos (estructura encoder-decoder): Esta estructura
permite obtener datos secuenciales a partir de otros datos secuenciales en
los que la entrada y la salida tienen tamaños distintos. Problemas como
traducción de textos, chatbots y el resumen de textos son algunas de
las aplicaciones de estos métodos. Las estructuras encoder-decoder son
consideradas modelos generativos, que se verán con mayor profundidad
en el siguiente capítulo, pero, en esencia, se basan en utilizar dos redes,
una después de la otra y con misiones antagónicas. La primera red
transforma la información de entrada de forma que se mantenga la máxima
información posible sobre el significado, pero en un espacio dimensional
más pequeño desde un punto de vista matemático. Esto se lleva a cabo
mediante los estados internos de las capas secuenciales del encoder.
La segunda red utiliza este concentrado de información sin significado
aparente para el ser humano para obtener la nueva secuencia. En el caso
de la traducción de textos, muy grosso modo, el encoder transformaría
la información de la frase a una información de significado agnóstica de
lenguaje y la segunda red cogería esta información de significado para
transformarla al idioma de llegada.
A pesar de la complejidad de los problemas que estas redes pretenden
resolver, continúan manteniendo los mismos problemas de base que se
han visto hasta ahora de las redes secuenciales. Entre ellos es difícil
mantener el contexto de secuencias largas como por ejemplo secuencias
de texto. Es por esto que se desarrolló el mecanismo de atención que ha
supuesto una auténtica revolución en el procesado del lenguaje.
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 219
(A)
(C)
(B)
(D)
Encoder
Decoder
Figura 4.21. Representación gráfica de las formas de plantear un problema de series temporales
descritas. (A) uno a uno, (B) uno a muchos, (C) muchos a uno, (D) muchos a muchos, (E) muchos a
muchos con estructura encoder-decoder.
4. 7 LABORATORIO
PRÁCTICA l. ARIMA vs RNN vs LSTM vs GRU vs LSTM + Attention
En esta práctica exploraremos diferentes técnicas para el estudio de series
temporales. Utilizaremos el conocido conjunto de datos de pasajeros de avión, que
contiene los pasajeros mensuales de una aerolínea internacional. El objetivo del
ejercicio es ser capaces de predecir los pasajeros que habrá un determinado mes
a partir de los datos de los meses anteriores: es un claro ejemplo de un problema
de series temporales. Plantearemos el problema para resolverlo tanto con métodos
clásicos (SARIMA) como con métodos basados en redes neuronales recurrentes.
Importación de las librerías
A las librerías usuales que venimos utilizando en otras prácticas (numpy,
matplotlib, tensorflow y sklearn) añadiremos pandas y seaborn. La primera
la utilizaremos para la carga de los datos, y la segunda para realizar algunas
representaciones gráficas que nos ayudarán entender mejor el problema.
220 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
import math
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
from sklearn.metrics import mean_squared_error
import tensorílow as tf
from tensorílow.keras import layers
from tensorílow.keras.utils import plot_model
from attention import Attention
Carga de los datos
Los datos que vamos a utilizar están alojados en un repositorio de GitHub
(https://github.com/jbrownlee/Datasets). Esto quiere decir que para poder utilizarlos
tendremos dos posibilidades:
11" Descargar el dataset y cargarlo con pandas.
11" Cargarlo directamente con pandas sin tener que descargar nada
manualmente.
Esta última opción es muy práctica por varios motivos: nos evita tener
que descargar y almacenar los archivos manualmente, y permite que el código sea
mucho más autocontenido, ya que si queremos que otra persona pueda ejecutar
nuestro código no será necesario que compartamos el conjunto de datos. Para
hacerlo solamente tendremos que introducir el enlace directo al archivo de datos en
la función pd. read_csv(). Para obtener el enlace adecuado tendremos que pulsar el
botón Raw de GitHub que aparece al abrir el archivo.
Siempre que importemos un dataframe con pandas es buena idea visualizar
las primeras columnas con el método . head(). También podemos utilizar
.describe() e . info() para obtener información general de los datos.
df=pd.read_csv('https://raw.githubusercontent.com/jbrownlee/Datasets/master/
airline-passengers.csv')
print("El conjunto de datos tiene{} filas y{} columnas.".format(*df.shape))
df.head()
> El conjunto de datos tiene 144 filas y 2 columnas.
> Month Passengers
0 1949-01 112
1 1949-02 118
2 1949-03 132
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 221
3 1949-04 129
4 1949-05 121
Exploración de los datos
Vemos que el conjunto de datos tiene solo dos columnas: la fecha y los
pasajeros. Como cabe la posibilidad de que pandas no interprete correctamente el
tipo de datos de cada columna, antes de empezar a trabajar es recomendable revisarlo
con el método . info():
df.info()
> <class 'pandas.core.frame.DataFrame'>
> Rangelndex: 144 entries, 0 to 143
> Data columns (total 2 columns):
> # Column Non-Null Count Dtype
> --- ------ -------------- -----
> 0 Month 144 non-null object
> 1 Passengers 144 non-null int64
> dtypes: int64(1), object(l)
> memory usage: 2.4+ KB
Esto permite ver que la columna Month no es de tipo fecha (datetime),
sino un tipo object genérico que aparece cuando pandas no sabe interpretarlo. Los
datos de tipo datetime tienen propiedades especialmente útiles, así que vamos a
convertirlo utilizando la función pd. to_datetime (). Al utilizarla aparece también
el día del mes, que como es desconocido se considera como el día 1.
df['Month'] = pd.to_datetime(df['Month'], format="%Y-%m")
df.head(l)
> Month Passengers
> 0 1949-01-01 112
También vamos a renombrar la columna porque en realidad no representa un
mes, representa una fecha. Para ello utilizamos el método . rename ():
df=df.rename(columns={'Month':'Date'})
df.head(l)
> Date Passengers
> 0 1949-01-01 112
222 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Ahora mismo, tal y como tenemos codificada la fecha no podemos
utilizarla como una característica de nuestro modelo porque no es un número. Una
aproximación usual es crear columnas nuevas que representen únicamente el día, el
mes, el año, etc. Esto da pie a realizar una pequeña ingeniería de características y
plantear cómo se tienen que tratar este tipo de datos.
Al haber transformado la columna Date a formato datetime podemos
extraer de forma cómoda el mes y el año con los atributos . month y . year.
df["Month"] = df['Date'].apply(lambda x: x.month)
df["Year"] = df['Date'].apply(lambda x: x.year)
df. head()
> Date Passengers Month Year
> 0 1949-01-01 112 1 1949
> 1 1949-02-01 118 2 1949
> 2 1949-03-01 132 3 1949
> 3 1949-04-01 129 4 1949
> 4 1949-05-01 121 5 1949
El último cambio que podemos hacer es convertir la columna Date en el
indice de nuestro dataframe. No es un cambio que sea realmente determinante, pero
como vamos a hacer todas las representaciones en función de esta columna, puede
hacemos el trabajo algo más fácil. Lo podemos hacer con el método . set_index ():
df = df.set_index('Date')
df.head(l)
> Passengers Month Year
> Date
> 1949-01-01 112 1 1949
Visualización de los datos
Una vez hemos preparado los datos para el análisis es recomendable
representarlos gráficamente para ver de forma sencilla cómo es la información con
la que estamos trabajando. En un problema de series temporales nos interesa ver la
evolución de las características (los pasajeros en este caso) con el tiempo. Esto da pie
a realizar tres visualizaciones:
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 223
11"'"
11"'"
11"'"
Pasajeros en función del tiempo: Es la representación más básica.
Representa nuestra variable respuesta frente a la variable independiente.
Nos da información de la tendencia general de los datos.
Pasajeros por año: Resume la evolución general de los pasajeros. Es una
buena representación para estudiar la tendencia general cuando tenemos
tantos datos que no podemos representarlos todos, pero puede enmascarar
la dependencia con los meses y esconder los ciclos cortos.
Pasajeros por mes: Otra forma de resumir la información que permite
estudiar los ciclos cortos, pero enmascara la tendencia a largo plazo.
Siempre es buena idea utilizar tanto representaciones directas, como
representaciones resumen, ya que cada una nos permite ver cosas diferentes. Además,
en conjuntos de datos muy grandes puede ser inviable representar todos los datos y
tendremos que utilizar siempre representaciones resumen.
-
600
Pasajeros
Passengers
500
Vl 400
"i 300
200
100
o
.-<
o
.-<
o
d,
.,.,o
.-<
.-<
"'
"'
.-<
.-<
o
o
.-<
"'
rl
"'.,.,
e!.
.,.,
.-<
.-<
.-<
o
o
"'
"'
,;.,
.,.,
.-<
.,.,st
.-<
.-<
o
"'.,.,
J,
.-<
Fecha
.-<
o
"'.,.,
J,
.-<
.-<
o
.-<
,..:.
CX)
o
o
.-<
.-<
"'
"'.,.,
.,.,"'
d,
.-<
Figura 4.22. Representación de los pasajeros en función del tiempo.
Esta figura nos permite ver que la tendencia general de la serie es que
aumentan la cantidad de personas que viajan en avión conforme aumenta el tiempo.
También nos sirve para ver que parece existir cierta periodicidad con los meses del
año pero no se puede ver demasiado bien, para indagar más en esto utilizaremos las
representaciones resumen.
© RA-MA
224 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
plt.figure()
data = df.groupby("Year")["Passengers"] .sum()
data.plot.bar(title = 'Pasajeros por año', color
newidth = 0.5)
plt.xlabel('Año')
plt.ylabel('Pasajeros')
plt.show()
'black', width = 0.7, li­
5000
e
V1
4000
-1,¡, 3000
V1
&
2000
1000
o
"'...
"'rl
"'
o
en
rl
"'"'
rl
rl
"'"'rl
N
"'"'
"'rl
...
"'"'rl
Año
"'"'
"'rl
"'"'
"'rl
"'"'
rl
r-­
"'rl"'
co
"'"'
"'rl
"'"'
rl
o
Figura 4.23. Representación de los pasajeros en función del tiempo agrupados por año.
Agrupar por años nos permite ver que, efectivamente, conforme pasan los
años aumenta la cantidad de pasajeros de avión. Si bien es cierto que se pierde la
capacidad de apreciar los ciclos cortos.
Al trabajar con series temporales de este tipo (heterocedasticas) es común
transformar la variable respuesta con el logaritmo. Esto permite eliminar la
heterocedasticidad y facilita la modelización de los datos.
df['Passengers_Log']=np.log(df['Passengers'])
figure = plt.figure()
data = df.groupby("Year")["Passengers_Log"].sum()
data.plot.bar(title = 'Pasajeros por año (Log)', color
linewidth = 0.5)
plt.xlabel('Año')
plt.ylabel('Log(Pasajeros)')
plt.show()
'black', width = 0.7,
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 225
Pasajeros por año (Log)
70
60
20
10
o
"'"'
rl
o
"'"'rl
N
"
"'"'rl
"'
"'"'
rl
CX)
o
Año
Figura 4.24. Representación del logaritmo de los pasajeros en función del tiempo agrupados por año.
Vemos que esta sencilla transformación permite obtener una serie mucho
más estacionaria que antes.
figure = plt.figure()
data = df.groupby("Month")["Passengers"] .mean()
data.plot.bar(title = 'Pasajeros por mes', color
plt.xlabel('Mes')
plt.ylabel('Pasajeros')
plt.show()
350
'black', width
0.9)
Pasajeros por mes
300
e
·�
250
200
� 150
100
so
o
"'
CX)
Mes
"'
o
rl
rl
rl
N
rl
Figura 4.25. Representación de los pasajeros en función del tiempo agrupados por mes.
226 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Por último, agrupando los datos por mes se puede ver que existe cierta
periodicidad en los viajeros: aumentan en los meses de verano y disminuyen en
Febrero y Noviembre. Es importante que pensemos si esto tiene sentido. Lo normal
es que la gente viaje cuando tiene vacaciones y el periodo vacacional más usual son
los meses de verano, así que parece tener sentido lo que obtenemos.
Modelos clásicos
Antes de empezar el análisis lo primero que tenemos que hacer es separar
la serie en los conjuntos de entrenamiento y test. En el capítulo anterior hacíamos
esta separación de manera aleatoria, pero no podemos hacerlo igual al trabajar con
series temporales porque no tiene sentido mezclar datos de diferentes momentos
temporales. La práctica habitual es utilizar los N primeros datos como conjunto
de entrenamiento, los siguientes M como validación y los siguientes Q como test.
Por consistencia con lo que haremos más adelante tomaremos 116 valores para el
conjunto de entrenamiento, 14 para el de validación y 14 para el de test. En este caso
no vamos a utilizar el conjunto de validación para nada, pero si no separamos los
datos de la misma forma no podemos realizar una comparativa justa con los modelos
neuronales que entrenaremos en el apartado siguiente. También redefinimos el índice
de las series como un objeto pd. Datatimelndex(). Esto nos permite especificar la
frecuencia de la serie ('MS' significa que cada dato corresponde al inicio de un mes),
algo que nos pedirán algunas de las funciones que vamos a utilizar.
Como es habitual, realizaremos todo el análisis con el conjunto de
entrenamiento y solo utilizaremos el de validación y el de test para comprobar los
resultados obtenidos.
data_train=df.Passengers_Log[:116]
data_val=df.Passengers_Log[116:130]
data_test=df.Passengers_Log[130:]
data_train.index = pd.Datetimelndex(data_train.index.values, freq='MS')
data_val.index = pd.Datetimelndex(data_val.index.values, freq='MS')
data_test.index = pd.Datetimelndex(data_test.index.values, freq='MS')
El análisis clásico de una serie temporal empieza por estudiar la tendencia y
la estacionalidad de la serie. Es posible descomponer la serie utilizando la función
seasonal_decompose() de la librería statsmodels:
from statsmodels.tsa.seasonal import seasonal_decompose
decomposition = seasonal_decompose(data_train, model='additive')
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 227
decomposition.plot()
plt. show()
:
1949
Passengers_Log
1950
1951
1952
1953
1954
1955
1956
1957
1958
1950
1951
1952
1953
1954
1955
1956
1957
1958
� 5.5
F 5.0
1949
�
0.2
� o.o
� -0.2 --�--�--�---.....,___.......,___..........,.__.........,__�--�-�
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
Figura 4.26. Descomposición de la serie temporal.
Representando la descomposición se puede ver que la serie presenta
claramente una tendencia creciente y una estacionalidad clara. La estacionalidad,
como ya sabíamos, es de 12 meses, que es el tiempo que tarda en repetirse el ciclo de
viajeros que hemos visto en la sección de exploración de los datos. Basándonos en
estas dos cosas podemos considerar que la serie no es estacionaria.
El primer paso del análisis es determinar la transformación que hace que la
serie sea estacionaria. Para ello calculamos la diferencia estacional de la serie, es
decir, obtenemos una nueva serie ts' _{i} = tsi+D - tsi, dónde Des la estacionalidad
(12 meses en nuestro caso). Esto es muy sencillo de calcular utilizando el método
. di ff ().También hay que utilizar el método . dropna () porque al hacer la diferencia
perdemos muestras que no queremos mantener como NaNs.
pas_log_D = data_train.diff(12).dropna()
Si representamos el resultado parece que hemos quitado prácticamente la
estacionalidad. Si siguiésemos viendo mucha estacionalidad podríamos repetir la
diferencia, pero no suele ser necesario.
pas_log_D.plot()
plt. show()
© RA-MA
228 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Diferencia estacional de Log(Pasajeros)
0.30
0.25
0.20
0.15
0.10
o.os
0.00
-o.os 1----.���������������������������--.-'
1951
1956
1958
1954
1955
1953
1957
1950
1952
Figura 4.27. Diferencia estacional del logaritmo de la serie.
Para quitar la tendencia vamos a seguir el mismo principio, pero ahora
haremos la diferencia respecto al punto siguiente. Aplicaremos la transformación:
ts[' = ts[ +1 - ts[:
pas_log_D_d = pas_log_D.diff(l).dropna()
pas_log_D_d.plot()
plt. show()
Diferencia punto siguiente de Log(Pasajeros) con diferencia estacional
1951
1952
1953
1954
1955
1956
1957
1958
Figura 4.28. Diferencia punto siguiendo de la diferencia estacional del logaritmo de la serie.
Tras estas transformaciones vemos que la serie ya no presenta ni estacionalidad
ni tendencia. Llegados a este punto es conveniente examinar el correlograma y el
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 229
correlograma parcial. Para ello podemos utilizar las funciones plot_acf() y plot_
pacf() de statsmodels respectivamente. Estas representaciones nos servirán para
estimar los parámetros (p, q, P, Q) que tendremos que utilizar en nuestro modelo
SARIMA.
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
plot_acf(pas_log_D_d, lags=50)
plt. show()
Autocorrelation
1.0
0.8
0.6
0.4
0.2
o.o
-0.2
-0.4
o
10
20
30
40
50
40
50
Figura 4.29. Autocorrelación de la serie.
plot_pacf(pas_log_D_d, lags=50, method='ywm')
plt. show()
Partial Autocorrelation
1.0
0.8
0.6
0.4
0.2
o.o
-0.2
-0.4
o
10
20
30
Figura 4.30. Autocorrelación parcial de la serie.
230 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Estas figuras tenemos que analizarlas desde dos escalas distintas: primero
nos fijaremos en los primeros retardos, que nos darán los valores de p y q, mientras
que los retardos estacionales (por ejemplo en 10, 20, etc.) nos servirán para estimar
pyQ.
Los primeros retardos de las dos figuras se cortan después del primer punto
significativo, por lo que podemos asumir que p = q = 1. Casualmente observamos lo
mismo para los retardos estacionales en el ACF, después del primer punto (en 11) no
hay ningún punto significativo. Esto quiere decir que tendremos también Q = 1. En
cambio, en la gráfica del PACF se puede entender que se produce un decrecimiento
exponencial que lleva asociado P = O. Este método depende ciertamente de
la interpretación de cada uno pero nos sirve para entender por qué elegimos los
parámetros que elegimos. Además, si tenemos dudas entre algunos valores siempre
podemos entrenar varios modelos con cada combinación y quedamos finalmente
con el que proporcione mejor métrica. A la hora de elegir un mejor modelo se suele
utilizar el AIC (Akaike lnformation Criterion ).
Después
de
este
análisis
podemos
plantear
un
modelo
SARIMA(1,1,1)x(0,1,1) 12 y ver qué resultados obtenemos. Para definir el modelo
utilizaremos el objeto SARIMAX() de la librería statsmodels. Hay que tener en
cuenta que tendremos que pasarle la serie sin ninguna de las transformaciones de
diferenciación que hemos hecho, porque el modelo las aplicará en función de los
valores de (p, d, q)x(P, D, Q, s) que le pasemos. Estas transformaciones solamente
las hemos hecho para poder analizar la serie y obtener los parámetros adecuados. Los
valores (p, d, q) tenemos que introducirlos en el argumento order, y los (P, D, Q, s)
en el argumento seasonal_order:
from statsmodels.tsa.statespace.sarimax import SARIMAX
mod = SARIMAX(data_train,
order=(l,1,1),
seasonal_order=(0,1,1,12),
enforce_stationarity=False,
enforce_invertibility=False)
results = mod.fit(maxiter=200, method='powell')
print('AIC:{}'.format(results.aic))
> Optimization terminated successfully.
> Current function value: -1.438483
> Iterations: 3
> Function evaluations: 157
> AIC:-325.72796810372637
Una vez hemos entrenado el modelo podemos utilizar el método plot_
diagnostics() del objeto results para representar las figuras de diagnóstico
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 231
que se suelen utilizar. En este caso nos interesa fijamos principalmente en que los
residuos tengan una distribución aleatoria en tomo al O y en que el Q-Q plot sea lo
más parecido posible a una recta. Vemos que se cumplen las dos cosas:
Standardized residual for "P"
Histogram plus estimated density
0.4
- Hist
- KDE
- N(0,1)
0.2
1952 1953 1954 1955 1956 1957 1958
Normal Q-Q
H�. .------,--------.1) j l
-2
-1
O
1
Theoretical Quantiles
2
o
-2
1
.
:
O
Correlogram
.
:
.
:
.
:
.
:
2
4
6
8
1
10
Figura 4.31. Estudio del ajuste.
Cuando trabajamos con series temporales no es suficiente con fijamos en
el valor de una métrica porque se puede dar el caso de que un modelo obtenga un
buen valor prediciendo simplemente lo mismo que vio en el instante anterior. Por
eso resulta muy útil representar la serie predicha junto a la serie real. Como es algo
que vamos a hacer para todos los modelos resulta conveniente definir una función
que podamos reutilizar:
def plot_results(Y_train, Y_val, Y_test,
pred_train, pred_val, pred_test):
plt.figure()
Y_tot = np.concatenate([Y_train, Y_val, Y_test])
pred_tot = np.concatenate([pred_train, pred_val, pred_test])
plt.plot(Y_tot, 'k-', label = "True")
plt.plot(pred_tot, 'k-', label = "Pred", alpha=0.5)
1 = plt.gca()
ylims = l.get_ylim()
legend_handles, legend_labels = l.get_legend_handles_labels()
plt.vlines(len(Y_train), ymin=ylims[0], ymax=ylims[l], linestyle='--', color = 'k')
plt.vlines(len(Y_train)+len(Y_val), ymin=ylims[0], ymax=ylims[l], linestyle='--', color='k')
plt.vlines(len(Y_train)+len(Y_val)+len(Y_test), ymin=ylims[0],
ymax=ylims[l], linestyle='--', color='k')
train_bg = plt.fill_betweenx(ylims, x1=0, x2=len(Y_train), color= 'k',
© RA-MA
232 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
alpha=0.1)
val_bg = plt.fill_betweenx(ylims, xl=len(Y_train), x2=len(Y_train)+len(Y_
val), color='k', alpha=0.2)
test_bg = plt.fill_betweenx(ylims, xl=len(Y_train)+len(Y_val), x2=len(Y_
train)+len(Y_val)+len(Y_test), color='k', alpha=0.3)
legend_handles.extend([train_bg, val_bg, test_bg])
legend_labels.extend( ["Train", "Validation", "Test"])
plt.legend(legend_handles, legend_labels)
Hay que recordar que este modelo ha sido entrenado con los datos
transformados con el logaritmo, por lo que para poder estimar correctamente su
rendimiento tendremos que deshacer la transformación de las predicciones utilizando
la exponencial antes de calcular las métricas o representar los resultados:
pred_train = results.get_prediction(start=data_train.index.values[0], end=data_
train.index.values[-1]).predicted_mean
pred_val = results.get_prediction(start=data_val.index.values[0], end=data_val.
index.values[-1]).predicted_mean
pred_test = results.get_prediction(start=data_test.index.values[0], end=data_
test.index.values[-1]).predicted_mean
pred_train_o = np.exp(pred_train)
pred_val_o = np.exp(pred_val)
pred_test_o = np.exp(pred_test)
plot_results(np.exp(data_train), np.exp(data_val), np.exp(data_test),
np.exp(data_train), pred_val_o, pred_test_o)
plt.title("SARIMA(l,1,l)x(0,1,1,12)")
plt.show()
SARIMA(l,l,l)x(O,l,1,12)
600
500
- True
- Pred
Train
Validation
Test
400
300
200
100
o
20
40
60
80
100
120
Figura 4.32. Predicción del modelo para los diferentes conjuntos.
140
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 233
Parece que todo el trabajo previo que hemos realizado para estimar los
parámetros del modelo SARIMA han dado sus frutos, ya que las predicciones parecen
ajustarse mucho a los valores reales. Para confirmarlo vamos a calcular el RMSE de
las predicciones en validación y test:
rmse_val = np.sqrt(mean_squared_error(np.exp(data_val), pred_val_o))
rmse_test = np.sqrt(mean_squared_error(np.exp(data_test), pred_test_o))
print(f"[Validation] RMSE: {rmse_val: .3f} 1 [Test] RMSE: {rmse_test: .3f}")
> [Validation] RMSE: 22. 905 1 [Test] RMSE: 25.321
Finalmente comprobamos que el modelo ajusta muy bien los datos. Pasamos
a estudiar ahora los modelos neuronales.
Ejercicio
¿Crees que los modelos neuronales obtendrán mejores o peores
resultados? Ten en cuenta la cantidad de datos que tenemos.
Modelos Neuronales: preparación de los datos
Como ya sabemos, los modelos neuronales funcionan mejor cuando los
datos de entrada están en el rango [O, 1]. La forma más sencilla de transformar los
datos para llevarlos a este rango es restar el valor mínimo y dividir por el rango
de los datos (M ax - M in). Hay que tener cuidado al realizar transformaciones
en los datos porque si hay que estimar algún parámetro, habrá que hacerlo con el
conjunto de entrenamiento y utilizar este valor para transformar los tres conjuntos.
Es importante hacerlo de esta manera para asegurarnos de que no se filtra ningún tipo
de información a los conjuntos de validación y test del conjunto de entrenamiento.
transf_min = data_train.min()
transf_max = data_train.max()
data_train_scaled = (data_train - transf_min) / (transf_max-transf_min)
data_val_scaled = (data_val - transf_min) / (transf_max-transf_min)
data_test_scaled = (data_test - transf_min) / (transf_max-transf_min)
Podemos observar que esta transformación nos traslada los datos al rango
deseado:
© RA-MA
234 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
plt. figure()
plt.hist(data_train_scaled)
plt. show()
Distribución de los datos transformados
16
14
12
10
8
6
4
2
O.O
0.2
0.4
0.6
0.8
1.0
Figura 4.33. Distribución de los datos transformados.
Ejercicio
Representa también las distribuciones del conjunto de validación y del
conjunto de test. ¿Qué observas? ¿Tiene sentido? ¿Funcionará nuestro modelo?
Ejercicio
Investiga la librería sklearn y utiliza MinMaxScaler para obtener la
misma transformación.
Hasta ahora hemos estado procesando y visualizando la serie temporal pero
hay algo que no hemos visto: las etiquetas. El problema que estamos planteando
es un problema supervisado, por lo que está claro que tenemos que proporcionarle
unas etiquetas a nuestro modelo para que sea capaz de aprender a modelar la señal.
Además, sabemos que queremos predecir los valores futuros a partir de los valores
anteriores, por lo que conceptualmente parece estar claro qué va a ser la X y qué
va a ser la Y. Toda esta información ya la tenemos disponible pero nos hace falta
obtenerla. Tenemos que enventanar nuestra serie temporal.
Para hacerlo vamos a crear una función que tome una serie temporal y
nos devuelva pares (valores anteriores, valores futuros). La cantidad de valores
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 235
anteriores que consideramos se suele llamar lag o retardo, y será un hiperparámetro
más de nuestro modelo que tendremos que ajustar. Como tenemos muy pocos datos
tomaremos un retardo pequeño de 7, y predeciremos un único valor. Podemos
hacer las ventanas superpuestas o no, pero en este ejemplo optamos por hacerlas
superpuestas por la poca cantidad de datos que tenemos.
def create_dataset(ts, lag=l, pred_len=l):
## Creamos dos listas vacías que llenaremos con las ventanas
x, Y = [ L [ l
## Creamos un índice con el que recorrer la serie temporal
## el +1 final es necesario para coger el último elemento
## porque Python no coge el elemento b al hacer [a:b]
for i in range(len(ts)-lag-pred_len+l):
X.append(ts[i:i+lag])
Y.append(ts[i+lag:i+lag+pred_len])
return np.array(X), np.array(Y)
Ejercicio
Rellenar listas utilizando . append () es mucho menos eficiente que
rellenar un np. array de tamaño predefinido. Define una nueva función que
haga lo mismo que la anterior pero de forma más eficiente.
Es buena práctica utilizar ejemplos sencillos para comprobar que nuestras
funciones se comportan como queremos antes de aplicarlas al conjunto real de datos,
sobre todo si nuestros conjuntos son muy grandes. Podemos crear un np. array de
15 elementos consecutivos para comprobar que nuestra función devuelve lo que
esperamos.
test = np.arange(lS)
X, Y = create_dataset(test, lag=7, pred_len=l)
for a, b in zip(X, Y):
print(f"X: {a} 1 Y: {b}")
> X: [0 1 2 3 4 5 6] 1 Y: [7]
> X: [1 2 3 4 5 6 7] 1 Y: [8]
> X: [2 3 4 5 6 7 8] 1 Y: [9]
> X: [3 4 5 6 7 8 9] 1 Y: [10]
> X: [ 4 5 6 7 8 9 10] 1 Y: [11]
> X: [ 5 6 7 8 9 10 11] 1 Y: [12]
> X: [ 6 7 8 9 10 11 12] 1 Y: [13]
> X: [ 7 8 9 10 11 12 13] 1 Y: [14]
236 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Ahora que estamos seguros de que obtenemos el resultado deseado, aplicamos
esta función a los tres conjuntos de datos que hemos creado con anterioridad para
generar los conjuntos finales:
lag = 7
pred_len = 1
X_train, Y_train = create_dataset(data_train_scaled, lag=lag, pred_len=pred_len)
X_val, Y_val = create_dataset(data_val_scaled, lag=lag, pred_len=pred_len)
X_test, Y_test = create_dataset(data_test_scaled, lag=lag, pred_len=pred_len)
X_train.shape, Y_train.shape, X_val.shape, Y_val.shape, X_test.shape, Y_test.
shape
> ((109, 7), (109, 1), (7, 7), (7, 1), (7, 7), (7, 1))
Generalidades
Todas las capas que vamos a probar esperan que los datos de entrada tengan
la forma (batch, tímesteps,feature). En nuestro caso vamos a utilizar 7 timesteps
(porque miramos 7 instantes temporales, lag=7) y 1 feature (porque en cada
momento temporal tenemos 1 solo valor, los viajeros). Para ajustar nuestros datos a
esto podemos utilizar la función np. expand_dims ( axis=-1):
X_train = np.expand_dims(X_train, axis=-1)
X_val = np.expand_dims(X_val, axis=-1)
X_test = np.expand_dims(X_test, axis=-1)
X_train.shape, Y_train.shape, X_val.shape, Y_val.shape, X_test.shape, Y_test.
shape
> ((109, 7, 1), (109, 1), (7, 7, 1), (7, 1), (7, 7, 1), (7, 1))
Además, como queremos probar muchos modelos diferentes bajo las mismas
condiciones, lo más justo y cómodo es definir una función a la que le pasaremos
el modelo y los datos, y entrenará el modelo determinado de la misma forma. Al
mantener todo constante menos el modelo podemos atribuirle a éste todos los
cambios en los resultados. Algo curioso a tener en cuenta es que no es necesario que
esta función nos devuelva el modelo porque como es un objeto, se modifica dentro
de la función y no hace falta devolverlo. Lo que sí que devolveremos es el histórico
del entrenamiento para poder representar las dinámicas de entrenamiento.
Al estar resolviendo un problema de regresión utilizaremos como función
de coste el error cuadrático medio o MSE (Mean Squared Error en inglés) y también
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 237
calcularemos el error absoluto medio o MAE (Mean Absolute Error) en cada
época. Como optimizador utilizaremos Adam porque siempre suele dar resultados
suficientemente buenos con los valores por defecto.
def train_model(model,
X_train, Y_train,
X_val, Y_val,
epochs=100, verbose=l):
model.compile(loss="mean_squared_error",
optimizer="adam",
metrics=["mae"])
history = model.fit(X_train, Y_train, epochs=epochs, validation_data=(X_val,
Y_val), verbose=verbose)
return history
Por el mismo motivo vamos a definir una función que se encargará de evaluar
el rendimiento de los modelos. Como el ejercicio se ha planteado como un problema
de regresión utilizaremos como métrica el RMSE. También hay que tener en cuenta
que hemos transformado la variable respuesta por lo que si calculamos el error de la
variable transformada podríamos estar infraestimando el error. Lo más correcto es
deshacer las transformaciones que hemos hecho y calcular el RMSE de la variable
en la escala original.
Para deshacer la transformación de escala que hemos hecho antes tendremos
que hacer:
Y para deshacer el logaritmo tendremos que calcular la exponencial de
los resultados. Es conveniente escribir una función que lo implemente para poder
aplicarla cómodamente:
def inverse_transform(X, max, min):
X = X*(max-min)+min
return np.exp(X)
Es recomendable comprobar que tras aplicar esta función se recupera la
distribución original:
© RA-MA
238 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
original_data_train = inverse_transform(data_train_scaled, transf_max,
transf_min)
plt.figure()
plt.subplot(l,2,1)
plt.title("0riginal Data")
plt.hist(df.Passengers[:116], color='k')
plt.subplot(l,2,2)
plt.title("Inverse Transformed Data")
plt.hist(original_data_train, color='k')
plt.show()
Original Data
lnverse Transformed Data
20
20
15
15
10
10
5
5
o
100
200
300
400
500
o
100
200
300
400
500
Figura 4.34. Distribución de los datos originales y deshaciendo la transformación.
Ahora que ya sabemos que somos capaces de deshacer todas las
transformaciones que hemos realizado previamente a los datos podemos definir la
función de evaluación de resultados.
def evaluate_model(model,
X_train, Y_train,
X_val, Y_val,
X_test, Y_test,
max, min,
verbose=False,
plot=False):
## Cálculo de las predicciones
pred_train = model.predict(X_train)
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 239
pred_val = model.predict(X_val)
pred_test = model.predict(X_test)
## Invertimos las transformaciones
pred_train = inverse_transform(pred_train, max, min)
Y_train_o = inverse_transform(Y_train, max, min)
pred_val = inverse_transform(pred_val, max, min)
Y_val_o = inverse_transform(Y_val, max, min)
pred_test = inverse_transform(pred_test, max, min)
Y_test_o = inverse_transform(Y_test, max, min)
## Cálculo del RMSE
score_train = np.sqrt(mean_squared_error(Y_train_o, pred_train))
score_val = np.sqrt(mean_squared_error(Y_val_o, pred_val))
score_test = np.sqrt(mean_squared_error(Y_test_o, pred_test))
if verbose:
print(f"[Train] RMSE: {score_train: .2f} 1 [Validation] RMSE: {score_
val: .2f} 1 [Test] RMSE: {score_test: .2f}")
if plot:
plot_results(Y_train_o, Y_val_o, Y_test_o,
pred_train, pred_val, pred_test)
return score_train, score_val, score_test
Comparativa conjunta
Gracias a todo el trabajo que hemos realizado para definir funciones genéricas
estamos en disposición de probar una gran variedad de modelos de forma sencilla y
sistemática, algo realmente importante cuando queremos comparar el rendimiento
de distintas aproximaciones correctamente. En general, todo el trabajo que podamos
hacer para mejorar nuestra capacidad de iterar sobre una idea nos será de gran ayuda
para obtener el mejor resultado posible.
Para ponerlo todo en práctica solamente queda definir los distintos modelos
que queremos probar: una RNN simple, un modelo con capa LSTM, otro con capa
GRU y finalmente uno en el que implementamos el mecanismo de atención. Los tres
primeros modelos son muy sencillos de implementar, ya que Keras tiene definidas
las capas layers. SimpleRNN(), layers. LSTM() y layers. GRU(), por lo que
solamente tendremos que introducirlas en un modelo secuencial sencillo. En cambio,
la capa layers. Attention() no está pensada para usarse en redes recurrentes, así
que no la podemos utilizar. Por suerte para nosotros existe una librería llamada
attention (instalable con pip install attention) que implementa una capa
Attention() pensada para problemas muchos a uno. Esta capa sí que la podemos
240 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
introducir en un modelo secuencial como el resto. Además de las capas específicas
también hay que recordar que estamos resolviendo un problema de regresión, por lo
que se suele utilizar una activación lineal en la última capa y el MSE como función
de coste.
model_rnn = tf.keras.models.Sequential([
layers.SimpleRNN(120, input_shape=(lag,1)),
layers.Dense(16),
layers.Dense(l)
])
model_lstm = tf.keras.models.Sequential([
layers.LSTM(16, input_shape = (lag, 1)),
layers.Dense(16),
layers.Dense(l)
])
model_gru = tf.keras.models.Sequential([
layers.GRU(16, input_shape=(lag,1)),
layers.Dense(16),
layers.Dense(l)
])
model_attention = tf.keras.models.Sequential([
layers.LSTM(16, return_sequences=True, input_shape=(lag,1)),
Attention(),
layers.Dense(16),
layers.Dense(l)
])
Una vez hemos definido todos los modelos podemos organizar el experimento
de la siguiente manera:
1. Creamos un diccionario con los modelos que queremos probar y un
nombre que los pueda identificar.
2. Definimos otro diccionario con las métricas que vamos a guardar de cada
modelo.
3. Iteramos respecto al primer diccionario para entrenar los modelos y
almacenar los resultados en el segundo diccionario.
4. Transformamos el diccionario de resultados en un pd. DataFrame para
presentar los resultados que hemos obtenido.
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 241
Nótese que esta forma de experimentar con distintos modelos es absolutamente
genérica y puede semos útil para una gran cantidad de problemas diferentes. Esta es
una de las virtudes de utilizar funciones genéricas, nos permite reutilizar el código
no solo en el problema en cuestión, sino en varios problemas distintos. Inicializamos
el diccionario de resultados con los valores que hemos obtenido con SARJMA para
tener todos los resultados juntos.
models = {"RNN":model_rnn, "LSTM":model_lstm, "GRU":model_gru,
"LSTM+Attention":model_attention}
results = {"Model":["SARIMA"], "Train_RMSE":[np.nan], "Validation_RMSE":[rmse_
val], "Test_RMSE":[rmse_ test]}
far model_name, model in models.items():
train_model(model, X_train, Y_train, X_val, Y_val, epochs=100, verbose=0)
rmse_train, rmse_val, rmse_test
evaluate_model(model,
X_train, Y_train,
X_val, Y_val,
X_test, Y_test,
transf_max, transf_min,
plot=True)
plt.title(model_name)
plt.show()
results["Model"].append(model_name)
results["Train_RMSE"].append(rmse_train)
results["Validation_RMSE"].append(rmse_val)
results["Test_RMSE"].append(rmse_test)
RNN
600
500
- True
- Pred
Train
Validation
- Test
400
300
200
100
o
20
40
60
80
100
Figura 4.35. Predicciones del modelo RNN sobre los diferentes conjuntos.
120
© RA-MA
242 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
LSTM
600
500
- True
- Pred
Train
Validation
- Test
400
300
200
100
o
20
40
60
80
100
120
Figura 4.36. Predicciones del modelo LSTM sobre los diferentes conjuntos.
GRU
600
500
- True
- Pred
Train
Validation
- Test
400
300
200
100
o
20
40
60
80
100
Figura 4.37. Predicciones del modelo GRU sobre los diferentes conjuntos.
120
© RA-MA
Capítulo 4. MODELOS NEURONALES ORIENTADOS A DATOS TEMPORALES 243
LSTM +Attention
600
500
- True
- Pred
Train
Validation
- Test
400
300
200
100
o
20
40
60
80
100
120
Figura 4.38. Predicciones del modelo LSTM+Atención sobre los diferentes conjuntos.
df_results = pd.DataFrame(results).set_index("Model")
df_results
Train_RMSE Validation_RMSE Test_RMSE
Model
SARIMA NaN 22.905146 25.320911
RNN 24.769366 50.924344 55.547452
L5TM 28.456899 56.161029 67.088460
GRU 25.994434 48.850462 58.595613
L5TM+Attention 26.660537 53.253428 60.622701
A partir de estos resultados vemos que el modelo que obtiene mejores
resultados es el más sencillo (SARIMA) seguido del que utiliza redes recurrentes
sencillas. Esto no nos debería sorprender dada la cantidad de datos de entrenamiento
que estamos utilizando. Los modelos más complejos requieren de muchos más
datos para aprender que los modelos sencillos, y esto es algo muy importante que
tendremos que tener siempre en cuenta. No existe un mejor modelo genérico. No
siempre va a ser el modelo más complicado el que nos proporcionará los mejores
resultados. Una de las primeras problemáticas que tendremos que resolver en cada
problema es adaptamos a la cantidad de datos con la que vamos a trabajar, por lo
que siempre es importante conocer y manejar técnicas más sencillas y técnicas más
complejas.
244 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
4.8 BIBLIOGRAFÍA
Auffarth, B. (2021). Machine Learning for Time-Series with Python: Forecast,
predict, and detect anomalies with state-of-the-art machine learning methods.
Apress.
Brockwell, P., Davis, R. (2016). lntroduction to Time Series and Forecasting.
Springer.
Chollet, F. (2017). Deep Learning with Python. Manning Publications.
Lara-Benítez, P., Carranza-García, M., & Riquelme, J. C. (2021). An Experimental
Review on Deep Learning Architectures for Time Series Forecasting.
Intemational Joumal ofNeural Systems, 31(3), 2130001. https://doi.org/10.1142/
S0129065721300011.
Lazzeri, F. (2019). Machine Learning for Time Series Forecasting with Python.
Wiley.
Nielsen, A. (2019). Practica! Time Series Analysis: Prediction with Statistics and
Machine Learning. O'Reilly.
5
MODELOS GENERATIVOS
5.1 INTRODUCCIÓN A LOS MODELOS GENERATIVOS
En los anteriores capítulos hemos estado conociendo modelos
discriminativos, cuyo objetivo es determinar el valor de la clase de entrada en función
del vector de entrada. De manera probabilística, este hecho queda establecido por
la siguiente probabilidad p(y I x) siendo y la clase y x el vector de entrada. En
el caso de los modelos generativos, el objetivo es obtener la distribución de los
datos de entrada p(x) para poder generar nuevos datos. Si el conjunto de datos está
etiquetado, el objetivo que se podría plantear sería p(x I y), lo que permitiría usar la
información de la etiqueta para modelizar el vector de entrada. En este capítulo nos
centraremos en entender, analizar y desarrollar modelos generativos según diferentes
aproximaciones; algunas de las aplicaciones de estos modelos son:
11" Super-Resolución. Se generan imágenes de alta resolución a partir de
imágenes de baja resolución. Estos algoritmos son importantes en áreas
como videovigilancia de entornos, diagnóstico médico y las aplicaciones
derivadas de la teledetección.
11" Mejora de imágenes. Aquí el objetivo es la reconstrucción de partes
perdidas o dañadas de imágenes y vídeos. Consiste en rellenar
artificialmente la región en la que falta información; los métodos
convencionales suelen utilizar la información de la imagen no dañada
para estimar las partes que faltan y rellenarlas automáticamente. Este
relleno lo puede hacer un modelo generativo.
11" Transformación de imágenes. En este campo han aparecido un gran
número de aplicaciones como es el coloreado de imágenes en blanco
246 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
y negro, la generación de caras 3D a partir de la fotografía 2D o la
transformación de dominios (el ejemplo clásico de esta aplicación es
el de generar imágenes de noche usando las de día o bien obtener una
imagen completa a partir de un bosquejo de ella).
11"'
11"'
11"'
11"'
11"'
11"'
Uso en moda. En este campo se tienen trabajos en los que se plantean
nuevos artículos de moda, así como la generación de sugerencias
personalizadas y diferentes a la existentes; de alguna manera el modelo
generativo actúa como recomendador y diseñador.
Cómics/Arte. Esta aplicación ha tenido un importante eco mediático; se
han desarrollado sistemas que generan cómics de forma automática, más
concretamente personajes manga/anime (entre estos trabajos se encuentra
el que genera los Pokémon de forma automática). Asimismo, se tienen
modelos generativos que plantean la transformación de una fotografia en
un cuadro.
Medicina. Los métodos tradicionales de aumento de datos (data
augmentation) sólo pueden generar datos que compartan una distribución
cercana con los originales. Si el conjunto de datos es demasiado pequeño,
estos métodos no funcionan. Los modelos generativos proporcionan una
solución a esta escasez de datos en medicina.
Ciencia. Aquí las aplicaciones se han desarrollado en la misma línea que
la anterior, la generación de nuevos datos que pueden ayudar a consolidar
teorías (o incluso crear nuevas); bajo este paraguas, por poner un ejemplo,
se tienen trabajos en Física de Partículas y Astrofisica. Además, sirven
para acelerar simulaciones que tienen un alto coste computacional.
Detección de anomalías. Este campo es uno de los más extendidos
actualmente por el gran número de aplicaciones que se tienen. Se aplican
en problemas donde una de las clases presenta una muy baja proporción
frente a la otra. Dentro de este tipo de problemas se encuentran todos
los relacionados con fraudes, mantenimiento predictivo y ciberseguridad.
Los modelos generativos han demostrado su utilidad en este campo frente
a otras aproximaciones que podríamos clasificar como clásicas al poder
generar datos correspondientes a anomalías de forma artificial.
Lenguaje. Actualmente es donde los modelos generativos están teniendo
más impacto mediático. Las grandes empresas tecnológicas, sobre
todo Microsoft y OpenAI, están invirtiendo mucho dinero y esfuerzo
en desarrollar modelos generativos que, a partir de una determinada
semilla (una frase, un pequeño texto) sean capaces de desarrollar textos
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 247
completos. Otro modelo generativo diferente a los anteriores es Dall-e
al que se le proporciona una frase y devuelve imágenes relacionadas
con ella. Estos modelos han abierto un debate sobre el tamaño de sus
arquitecturas y los recursos necesarios para entrenarlos y desarrollarlos.
Como se ha visto, la generación artificial de datos tiene mucha utilidad
en diferentes campos del conocimiento. En este capítulo tenemos el objetivo
de determinar la función que genera los datos que se tienen. Dado el carácter
probabilístico de esta misión (siempre tendremos errores que se pueden calificar
como ruido o bien los propios datos que pueden tener naturaleza aleatoria), el objetivo
será modelizar la densidad de probabilidad que define nuestros datos y utilizar dicha
densidad, u otra función derivada de ella, para obtener nuevos datos relacionados
con los que se tienen.
Si se tienen datos univariantes se podría plantear implementar un histograma,
con él podemos inferir de una manera gráfica y analítica la distribución de dicha
variable. Se podría pensar en generalizar este procedimiento cuando se tiene más
de una variable, pero aparece el problema de la maldición de la dimensionalidad.
Este problema consiste en que, conforme aumentamos el número de entradas, el
número de datos necesarios para cubrir el espacio de nuestros datos crece de forma
exponencial, por lo que este método y cualquier otro método de estimación de la
distribución de los datos a partir de los mismos, no se puede aplicar para datos con
una alta dimensionalidad. Esta casuística es la que se tiene cuando se trabaja con
imágenes y texto. Otros modelos generativos que podríamos definir como clásicos
son los que se conocen como mezcla de gaussianas (gaussian mixture models).
En este caso se tiene que la distribución de probabilidad de nuestros datos es una
combinación lineal de una serie de distribuciones con unos ciertos valores medios y
varianzas, esto es, p(x) = ¿J= 1 Wj · N(x 1 µj ,Lj ). Aquí la condición que se tiene
es =l Wj = l con O � Wj � l. El problema de esta aproximación es el cálculo
de los parámetros (los estadísticos de las normales y los coeficientes w) así como su
número, que crece de forma cuadrática con la dimensión (por la matriz de covarianzas
de las distribuciones normales). Otra aproximación para estimar la distribución de
un determinado conjunto de datos son los modelos gráficos probabilísticos. En este
caso el objetivo es encontrar la distribución p(x) (una distribución conjunta de las
variables X; que forman el vector x). En este caso se tienen en cuenta las relaciones
de independencia entre las diferentes variables para establecer un determinado grafo
de dependencia (que puede ser dirigido o no); básicamente se trata de obtener la
distribución de nuestros datos (D) de la siguiente forma p(x) = Ili=i p(xk I pak)
, donde pak son los padres de la variable xk en el grafo. Una gran ventaja de este
tipo de modelos generativos es que consideran relaciones de causalidad que
es, actualmente, uno de los principales problemas en el mundo de la IA, obtener
IJ
248 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
causalidad en un determinado problema. Dentro de estos modelos gráficos existe
un gran número de modelos como son: Naive Bayes, modelos ocultos de Markov,
filtros de Kalman, filtros de partículas, análisis factorial, análisis de componentes
principales probabilístico e, incluso, los modelos de combinación de gaussianas
vistos anteriormente se pueden considerar modelos gráficos probabilísticos. Estos
modelos tienen problemas con datos de alta dimensionalidad y en los que existe una
fuerte relación espacial o temporal (como pueden ser las imágenes y los textos) a
la hora de determinar grafos óptimos de relaciones entre variables, lo que limita su
aplicación, ¡veamos pues otras aproximaciones neuronales profundas!
5.2 AUTOENCODERS
Un autoencoder es una red neuronal multicapa no recurrente, del tipo MLP
visto en el capítulo 2, cuyo objetivo es obtener a la salida del modelo los datos que
se tienen a la entrada. Es decir, la salida debería ser igual a la entrada para cada una
de las muestras del conjunto de datos, salídak � entradak. A simple vista, puede
parecer no tener sentido esta idea, ¿para qué querríamos conseguir a la salida los
mismos datos que en la entrada? Para entenderlo, debemos conocer más a fondo su
arquitectura. Un autoencoder está formado por dos partes simétricas, denominadas
codificador y decodificador:
,- Codificador. El objetivo del codificador es mapear los datos de entrada
en una representación interna y comprimida de la misma información
( denominada espacio latente). Debido a este objetivo el número de
neuronas en las capas ocultas se va reduciendo.
,- Decodificador. Por su parte, el decodificador toma la información
comprimida y la reconstruye hasta conseguir la salida deseada. Se tiene
una estructura espejo del codificador.
CODIFICADOR
ESPACIO
LATENTE
DECODIFICADOR
Figura 5.1. Esquema de un autoencoder.
En cuanto al ajuste de parámetros, se realiza de la misma manera que
cualquier red neuronal, mediante el algoritmo de retropropagación minimizando
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 249
una función de pérdida denominada error de reconstrucción L = (x, y), que mide la
diferencia entre la entrada y su reconstrucción a la salida. Por lo general, la función
de coste a minimizar es el error cuadrático medio (MSE) definido como:
MSE = �}::f=1 (y¡ - X¡) 2
Ecuación 5.1
Este proceso de compresión y descompresión de los datos nos permite obtener,
de forma no supervisada, un método para extraer características representativas y
codificadas (de forma no lineal) de los datos de entrada que se tienen.
La peculiaridad de estos modelos los hace muy útiles a la hora de resolver
problemas como los que se comentan a continuación:
11"'"
11"'"
11"'"
Eliminar ruido de imágenes (y datos en general): dada como entrada la
propia imagen a la que hemos añadido ruido de forma artificial y, a la
salida, la propia imagen sin ruido. De tal forma que, una vez entrenado el
modelo, seamos capaces de generar imágenes más nítidas a partir de una
imagen con ruido. Aquí el autoencoder estaría funcionando como una
memoria autoasociativa.
Los autoencoders permiten aprovechar su codificador como codificador
de información (extrayendo los datos generados en el espacio latente) y
emplear el decodificador como método de decodificación. Así es posible
encriptar documentos los cuales únicamente podrán ser desencriptados
conociendo los pesos de la red neuronal.
Otra de las aplicaciones que permiten este tipo de modelos es la
detección de comportamientos anómalos. Al entrenar el modelo con
comportamientos correctos como datos de entrada y reconstruir la
misma señal a la salida, el sistema aprende la normalidad. Si aparece
un patrón con una determinada anomalía el error de reconstrucción a la
salida aumentará, por lo que podemos usar un umbral para determinar la
presencia de la anomalía.
El papel como modelo generativo para un autoencoder sería utilizar el
espacio latente como semilla para obtener nuevos datos.
5.3 AUTOENCODERS VARIACIONALES
El problema de los autoencoders usados como modelos generativos es que
en su estructura y en su algoritmo de aprendizaje no se garantiza que el espacio
latente sea continuo y, por lo tanto, su uso como modelo generativo puede provocar
© RA-MA
250 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
problemas. No podemos asegurar que, si se escogen dos puntos cercanos en el
espacio latente, las salidas sean parecidas debido a esa posible pérdida de continuidad;
esto conduce a que si consideramos un vector aleatorio del espacio latente lo más
probable es que el decodificador no sepa como procesarlo y genere cualquier cosa.
Para solucionar este inconveniente se plantean los autoencoders variacionales en
los que cada patrón de entrada no se asigna a un punto en el espacio latente sino a
una distribución normal multivariante alrededor de un punto del espacio latente que
es, posteriormente, muestreada y que se pasa al decodificador como se describe en
la figura 5.2:
IMAGEN
X
,---...i
IMAGEN
PREDICHA
Xp
DECODER
P0(Xp lZ)
Figura 5.2. Esquema de un autoencoder variacional.
Se tienen entonces como salida del espacio latente dos vectores, el
correspondiente a la media de la distribución y el correspondiente a la diagonal de la
matriz de covarianza (se asume que no existe relación entre las variables del espacio
latente). Además, se suele considerar el logaritmo de este último vector (las varianzas
son siempre positivas por lo que no tendremos problemas con esta transformación);
así se tiene el dominio del vector de varianzas de (- oo, oo). Posteriormente se
muestrea la distribución normal multivariante con esos vectores de media/varianza
y se alimenta al decodificador. ¿Por qué se introduce esta aproximación? la idea es
asegurar la continuidad del espacio latente. Ahora, al muestrear en un área cercana
al valor medio del espacio latente, el decodificador se asegurará que todos los puntos
en esa vecindad tengan salidas similares, por lo que tenemos un modelo generativo
más robusto que el anterior.
A nivel matemático denotaremos al codificador como una probabilidad
condicionada Q q, (ZIX), donde et> son los parámetros y el sesgo de la red neuronal
codificadora, siendo X los datos de entrada. Aquí la variable Z es la variable
latente probabilística que, por razones de simplicidad, se considera que sigue una
distribución normal estandarizada, esto es, una distribución con un vector de medias
cero y varianzas unidad. Existen trabajos que consideran esta variable perteneciente
a otras distribuciones, pero aquí seguiremos la aproximación estándar a este sistema.
Por otro lado, el decodificador puede denotarse como P0 (XIZ), donde 0 son los pesos
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 251
y el sesgo de la red neuronal decodificadora. El problema de este tipo de autoencoder
radica en su estructura. La propagación de la señal hacia delante conlleva muestrear
una distribución aleatoria lo que provoca que no se pueda aplicar un procedimiento
de retropropagación. Aparece, por tanto, lo que se conoce como el truco de la
reparametrización (reparameterization trick) en el que sustituimos el proceso de
muestreo de la figura 5.2 por una aproximación. En este caso el vector que entra al
decodificador es el definido por la siguiente expresión z = µ + a x E donde todos los
términos de la ecuación son vectoriales y E es una distribución normal multivariante
de media cero y varianza unidad; ahora el proceso es el siguiente:
IMAGEN
X
�-...i
ENCODER
Q ;, (ZIX)
IMAGEN
PREDICHA
Xp
DECODER
N(0,1)
P0(Xp lZ)
Figura 5.3. Aplicación del truco de reparametrización para poder aplicar a un autoencoder variacional el
algoritmo de retropropagación.
Con esta modificación se puede aplicar el algoritmo de retropropagación
porque se ha eliminado del camino de la señal el proceso de muestreo del espacio
latente. Para su entrenamiento se emplea una función de coste distinta a las vistas
hasta ahora y formada por dos términos:
Ecuación 5. 2
El primer término es el error de reconstrucción, que mide la diferencia
entre los datos de entrada y su reconstrucción a la salida. El segundo término se
conoce como la divergencia de Kullback-Leibler, y marca cómo de cerca están las
distribuciones de probabilidades Q0(ZIX)y P(Z). Tiene varios papeles: por una parte,
actúa como un regularizador; por otra parte, "obliga" a la distribución de los datos
a tender hacia el centro del espacio latente dándole continuidad a dicho espacio, lo
que tiene una gran utilidad en su papel de sistema generador. Si nuestra distribución
está muy alejada de la gaussiana, la divergencia será muy grande y los pesos se
actualizarán para corregir esta situación.
© RA-MA
252 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
5.4 GAN (GENERATIVE ADVERSARIAL NETWORKS)
Otro de los modelos generativos muy popular es el que se conoce como
Red Generativa Adversaria! (GANs, por sus siglas en inglés Generative Adversaria!
Network), propuesta en 2014 por Ian Goodfellow. En el artículo original se presenta
como un nuevo modelo no supervisado que puede trabajar con cualquier tipo de dato,
aunque su principal aplicación es la generación de imágenes. Las redes generativas
adversariales están formadas por dos componentes principales: el generador y el
discriminador, como aparece en la figura 5.4.
1
: Datos reales
1
:
·- - - - - - - - - - �
:
X~Pdatos(X)
1
Discriminador
D(x,0 d)
,----------'
1
• Clasificación :
, Real/Sintético 1'
1
-------,
1
1
: z~ N(O,l)
1
1
1
1
1
1
-------•
L - - - - - - - - - _I
Generador
G(z,09 )
1
1
1
Datos sintéticos :
·- ----- - --- 1
x~p9 (x)
Figura 5.4. Esquema de una red Generativa Adversaria!.
11"'"
11"'"
Generador. La función del generador es usar vectores aleatorios
procedentes de una distribución de probabilidad aleatoria determinada y
tratar de generar un dato realista. La distribución que se suele considerar,
como en el caso anterior, es una distribución normal multivariante con
un vector de media igual a cero y un vector de varianza igual a uno. Esta
red neuronal puede entenderse como el decodificador de un autoencoder
variacional donde se utiliza un espacio probabilístico conocido para
simular la distribución de los datos que se quieren modelizar.
Discriminador. Es otra red neuronal que, en este caso, presenta dos tipos
de entradas. Uno de ellos son los datos reales del conjunto de datos que
se tienen y, el otro, serán las salidas del generador que intenta modelizar
la distribución de datos que se tienen.
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 253
Por tanto, el objetivo del discriminador será diferenciar qué datos proceden
del conjunto de datos reales y cuáles proceden del generador. Por su parte, el objetivo
del generador será tratar de crear imágenes lo suficientemente similares a las del
conjunto de datos original para poder engañar al discriminador. El entrenamiento
de la red se puede entender como un "tira y afloja" en el que el generador tratará de
engañar al discriminador y éste intentará encontrar los datos falsos.
Ahora que ya conocemos la arquitectura de GANs nos vamos a centrar en
entender su proceso de entrenamiento. No obstante, antes de entrar en detalle, vamos
a conocer los elementos matemáticos que utilizaremos:
,.- El vector de entrada al generador lo denotaremos comoz, cuya distribución
de probabilidad Pz (z ), será una distribución normal multivariante
de media cero y varianza unidad. Por otro lado tenemos la salida del
generador, x, con una distribución de probabilidad p9 (x), cuyo objetivo
será conseguir una distribución lo más similar a los datos reales que se
tienen.
,.- Aparte se tiene el discriminador, que se denotará como D(x, 0d),
donde 0 d son los pesos de la red neuronal. El discriminador tomará como
entrada los datos originales que se tienen, que vendrán generados por
una determinada distribución X~Pdatos(x), y los datos generados por la
distribución de salida en el generador x~p9 (x). El discriminador, que
será un clasificador binario, proporcionará una salida de 1 si considera
que los datos de entrada son datos reales y O si considera que son datos
sintéticos provenientes del generador.
Hay que tener en cuenta el tipo de aprendizaje diferente que tienen los
dos modelos, de ahí el nombre de adversaria/. Por una parte se tiene el generador,
cuyo objetivo es sintetizar datos lo más cercanos a los datos reales y, por otra, el
discriminador, que intenta identificar los datos sintetizados. Si analizamos los dos
objetivos, se tendría para el discriminador el siguiente objetivo:
mgx = IEx~Pc1araCx)[log D(x)] + IEz~pz (z)[log (1- D(G(z)))]
Ecuación 5.3
Básicamente consiste en que proporcione una salida de 1 para los datos
reales (primer término) y una salida de O para los datos que proceden del generador
(segundo término). Para el generador se tendría el siguiente objetivo (el generador
no tiene dependencia con los datos que se quieren modelizar (x):
Ecuación 5.4
254 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Si se combinan ambos objetivos se tiene un problema de optimización que
combina un mínimo y un máximo (en teoría de juegos se conoce como un problema
minimax) obteniéndose el siguiente criterio:
mjnmgx V(D, G) = IEx~Pc1ata (x) [log D(x)] + IEz~pz(z) [log (1 - D(G(z)))] Ecuación 5.5
El entrenamiento de las GANs tendrá las siguientes etapas:
1. Seleccionar m de datos reales del conjunto de entrenamiento
{x C 1 ) , x C2) , .. . , x Cm)}~Pdatos(x).
2. Introducir vectores de ruido aleatorio en el generador para producir datos
sintéticos. {zC1),z( 2), . . . ,z(m)}~p9 (z)
3. Entrenar el discriminador utilizando tanto los datos sintéticos como los
reales. Esto actualizará sólo los pesos del discriminador etiquetando
todas las imágenes reales como 1 y las falsas como O. En este caso sólo
se actualizan los pesos del discriminador, 0 d , mediante ascenso por
gradiente (sin modificar los pesos del generador) usando:
Ecuación 5.6
4. Seleccionar un nuevo conjunto de m muestras aleatorias del espacio
latente: {zC1),zC2), . . . ,z(m )}~p9 (z). La secuencia de este paso sería: se
genera otro conjunto de vectores aleatorios, se introducen en el generador
para producir nuevos datos sintéticos. Seguidamente se introducen en el
discriminador utilizándose su resultado para actualizar únicamente los
pesos del generador.
5. Actualizar el generador usando descenso por gradiente a partir de
've9 � ¿� 1 log (1- D(G(z Ci) ))) o bien ascenso por gradiente mediante
've � ¿� 1 log (D(G(z (i) ))); este último objetivo se ha demostrado más
9
estable que el anterior.
La figura 5.5 muestra las diferentes señales que se tienen para actualizar el
discriminador y el generador.
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 255
.------------ ..
�
y
1
---->! DISCRIMINADOR
FUNCIÓN DE
COSTE
GENERADOR
z~N(O,l) '--,--�
- "v 0, �
f
m i=
l
log ( 1 - D ( G ( z (
i)))) or "v0, � f log (D ( G (z i))))
(
m i=
l
Figura 5.5. Señales de actualización que se tienen en una Red Generativa Adversarial.
5.5 PROBLEMAS EN EL AJUSTE DE LAS GAN
A diferencia de los autoencoders y, en concreto, de la variante variacional,
el generador de la GAN no está en contacto en ningún momento con los patrones
de entrada que ha de ser capaz de generar, y su error no procede de un error de
reconstrucción sino de la realimentación que le proporciona el discriminador que es
otra red entrenada de forma simultánea diseñada con el objetivo, precisamente, de
discernir entre datos reales y los generados.
La idea detrás de las GAN es aprovecharse de este equilibrio (en concreto,
un equilibrio de Nash de un juego no cooperativo de dos jugadores) entre el
aprendizaje del generador y el discriminador. Dicho de otro modo, un generador y
un discriminador que están entrenándose de forma correcta y antagónica. Mientras
el generador aprende a generar datos similares a los que se tienen, el discriminador
está aprendiendo a diferenciar las muestras reales de las sintéticas.
En un entrenamiento satisfactorio de una GAN, el generador entrena con
el objetivo de 'ponérselo dificil' al discriminador, generando muestras que el
discriminador no sea capaz de clasificar con claridad. Mientras tanto, el discriminador
tiene el objetivo de aprender qué es lo que diferencia las muestras reales de las
sintéticas obtenidas por el generador. Es esta dinámica la que motiva que ambos
mejoren en sus objetivos a lo largo del tiempo. Resumiendo, para una GAN, la
convergencia sigue una evolución inestable por las dos tendencias comentadas
anteriormente.
256 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Los problemas más comunes que se producen durante el entrenamiento son:
11"'
11"'
Colapso de modos (Mode Collapse): Durante el entrenamiento, el
generador puede converger a una configuración en la que produce siempre
un conjunto de muestras muy parecidas entre ellas, y sin la suficiente
variedad en comparación con el conjunto total de muestras reales que se
le está suministrando al discriminador. Este problema es muy común en
el entrenamiento de las GAN. En esta situación, el discriminador no le
proporciona información válida al generador porque las imágenes que
genera éste tendrían la suficiente calidad como para engañarlo, haciendo
que el valor de las pérdidas sea pequeño y no habiendo suficiente
gradiente como para salir de esta situación de estancamiento. Un ejemplo
de colapso de modos sería entrenar una GAN para que muestre imágenes
de números (utilizando, por ejemplo, los datos MNIST), pero el generador
sólo ha conseguido aprender a generar determinados números. También
es interesante mencionar que el problema opuesto se puede dar: el
generador genera muestras demasiado generales y no lo suficientemente
específicas para el problema a tratar; tenemos lo que se conoce como
sobregeneralización. Por ejemplo, una GAN entrenada con imágenes de
caras de personas que genere caras con 3 ojos. A veces, basta con reiniciar
el entrenamiento de la red para resolver el problema.
Inestabilidad del entrenamiento: Tal y como se ha comentado, las GAN
resultan de la constante interacción de dos redes: generador y discriminador.
Para que esta interacción sea satisfactoria, el ritmo de aprendizaje de ambas
redes ha de ser similar. Es importante que este equilibrio en el entrenamiento
no se pierda. Un discriminador que aprenda rápidamente a diferenciar las
muestras reales y generadas (produciendo salidas cercanas a O cuando las
muestras son generadas, y 1 cuando las muestras son reales) hará que no
se tenga una señal de gradiente suficientemente intensa para que aprenda
el generador. Por otro lado, un generador que aprende más rápido que
un discriminador producirá falsos negativos (siempre lo engañará). Esta
situación de aprendizaje simultáneo se puede apreciar si se observan las
pérdidas de generador y discriminador durante el entrenamiento, y se tienen
los siguientes aspectos (failure modes):
1. Las pérdidas del discriminador se acercan a O; con ello la señal de
gradiente al generador es casi nula y éste no se puede actualizar.
2. Las pérdidas del discriminador crecen indefinidamente cuando se
le presentan imágenes obtenidas por el generador; el discriminador
no converge y esto deja al generador sin información válida para
aprender.
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 257
3. Precisión del discriminador divergente; el discriminador aprende un
procedimiento por el que acaba clasificando todas las muestras como
reales o sintéticas. Esto se puede detectar comparando las pérdidas
del discriminador de imágenes reales con las de imágenes generadas.
11"'
Ausencia de una métrica que determine el estado del entrenamiento:
Durante el entrenamiento, mientras el generador mejora, el rendimiento del
discriminador se vuelve peor porque no puede diferenciar tan fácilmente
entre las muestras reales y las sintéticas. Si el generador se entrena a la
perfección, la precisión del discriminador ha de ser del 50%; funciona
como un clasificador binario aleatorio. Esta progresión señala un problema
para el entrenamiento de las GANs: la realimentación que proporciona
el discriminador al generador se reduce con el tiempo. Si se continúa el
entrenamiento de la GAN más allá del punto en el que el discriminador
funciona como un clasificador aleatorio, entonces el generador entrenará
con información que carece de sentido y se perderá el equilibrio entre
los dos modelos y, por tanto, el funcionamiento global. Este problema
se acrecienta si además se tiene en cuenta que las GAN no tienen una
buena métrica que pueda informar de forma objetiva sobre el estado del
entrenamiento. A diferencia de una red clásica, donde se espera que las
pérdidas se reduzcan durante el entrenamiento, en las GAN se produce un
vaivén entre generador y discriminador. Un ejemplo de pérdidas de una
GAN entrenada satisfactoriamente aparece en la figura 5.6.
Error
0,8
0,7
0,6
0,5
-Error discriminador
0,4
-Error generador
0,3
0,2
0,1
101
201
301
401
501
601
Iteraciones
Figura 5.6. Evolución de las funciones de pérdidas para el discriminador y generador en una GAN.
258 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
A continuación, se proponen varios procedimientos que ayudan en el
entrenamiento de una GAN:
,- Evitar capas en la red con una gran cantidad de ceros. Estas capas
podrían contribuir a la inestabilidad de la GAN. Por ejemplo, usar
LeakyRELU en vez de RELU, o sustituir las capas de Max Pooling por
las de Average Pooling. De esta forma los gradientes son más altos.
,- Usar etiquetas ruidosas para el discriminador (Label Smoothing). El
discriminador se entrena asignando una etiqueta de O para imágenes
sintéticas y 1 si la imagen es real. En vez de eso, se puede utilizar
números aleatorios bajos (por ejemplo, entre O y 0.3) para las etiquetas de
las muestras generadas y números aleatorios cercanos a 1 (por ejemplo,
entre O. 7 y 1.2) para las muestras reales. De forma similar, introducir algo
de ruido en las muestras tanto reales como generadas que se introducen
en el discriminador. Esto puede servir en situaciones en las que el
entrenamiento se atasca desde el principio evitando saturaciones de las
neuronas.
,- Si se trabaja con imágenes, gran parte de la literatura apunta a
normalizarlas en el intervalo [-1, 1}.
,- Ajustar la constante de adaptación para que la velocidad de aprendizaje
de ambas redes sea similar. Alternativamente, se puede cambiar el
número de iteraciones en descenso por gradiente que hace cada una de
las redes en cada paso del bucle de entrenamiento. Jugar con la ratio de
veces que se entrena cada red puede ser útil para ajustar desbalances
en el entrenamiento de las redes. Por ejemplo, la literatura apunta a
hacer 5 bucles de entrenamiento para el discriminador por cada bucle de
entrenamiento para el generador.
,- Aumentar el tamaño de los lotes (batch) de entrenamiento. De esta forma,
más modos se cubren en cada paso del entrenamiento y se dan mejores
gradientes para que las redes aprendan; se puede evitar así el colapso
de los modos. Por otro lado, es conveniente también que cada lote que
se proporciona al discriminador contenga únicamente, o bien, muestras
reales, o bien, muestras sintéticas.
,- Durante el entrenamiento hay que supervisar constantemente las
muestras sintéticas así como la evolución de las pérdidas de generador y
discriminador.
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 259
Aquí se dan algunas recomendaciones, pero el entrenamiento de las GANs
es todo un arte, y hay muchísimas metodologías para conseguir grandes resultados,
recomendándose revisar la bibliografía dada al final del capítulo.
5.6 VARIACIONES DE LAS GAN
Desde la aparición de las primeras GAN, han surgido una gran variedad de
variantes con el objetivo de mejorar las técnicas y solucionar algunos de los problemas
ya comentados, como también expandir sus utilidades a problemas diferentes. En
esta sección se comentarán algunas variaciones de la GAN original, pero también se
advierte al lector que hay todo un zoo de GANs y que es un campo abierto hoy en
día a la investigación. En primer lugar hay que comentar que el modelo original de
GAN, cuando se utilizan redes de tipo convolucional (tanto para el discriminador
como para el generador), se conoce como DCGAN (Deep Convolutional Neural
Networks) y es una de las primeras variaciones que se usaron en las aplicaciones
prácticas de este tipo de algoritmos. Frente a otras aproximaciones previas, por
ejemplo, las LGAN (Laplacian GAN), Radford y sus colaboradores introdujeron
técnicas que permitieron a las CNN ser utilizadas dentro del marco de las GANs.
Una de las técnicas clave que utilizaron es la normalización por lotes, que ayuda a
estabilizar el proceso de entrenamiento normalizando las entradas en cada capa en
la que se aplica.
La primera variante que vamos a considerar es la conocida como Wasserstein
GAN o WGAN si atendemos a su acrónimo. Su origen está en el intento de solucionar
los problemas que se tienen derivados de la elección de la distancia estadística entre
los datos que se desean (reales) y los datos sintéticos obtenidos por el generador. El
nombre de esta variación procede de lo que se conoce como distancia de Wasserstein
que mide la distancia entre dos detenninadas distribuciones estadísticas. Esta
distancia también recibe el nombre en inglés de Earth Movers Distance (traducido
literalmente, la distancia del movedor de tierra) y este nombre tiene su base en
considerar las dos distribuciones donde queremos aplicar esta distancia, p(x) y q(x)
, como dos montones de tierra siendo la distancia de Wasserstein el equivalente al
trabajo necesario para mover la tierra de p (x) para que sea igual a q (x). Un ejemplo
muy simple sería el proporcionado por la figura 5. 7:
260 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Px
Oy
0.6
0.6
0.4
0.4
0.2
0.2
2
3
X
© RA-MA
2
3
y
Figura 5.7. La distancia de Wasserstein entre la distribución Px y Qy es de 0.8 correspondiente al
producto de 0.4 (cantidad a desplazar) y 2 (que es la distancia de 1 a 3 que hay que desplazar ese valor
de exceso para igualar las dos distribuciones).
En el ejemplo de la figura 5.7 sólo hay una forma de transformar la
distribución Px a Q Y. Si se aumenta el número de dimensiones de la distribución (en
el caso de la figura son distribuciones univariantes) el número de posibilidades para
transformar una distribución en la otra aumenta quedando definida la distancia de
Wasserstein como:
W(p, q) =
inf
y-n(p,q)
IE(xy)-y[llx - yll]
Ecuación 5. 7
Donde inf denota el valor ínfimo y II(p, q) indica el conjunto de todas las
posibles distribuciones de probabilidad conjuntas entre p y q. Cada una de estas
distribuciones establece un plan de transporte entre p y q teniendo en cuenta la
cantidad a transportar (diferencia entre x e y). Aquí la condición que se tiene es
L x y(x, y) = q(y) y es L y y(x, y) = p(x). Hay que tener en cuenta que, si se
trata x como punto de partida e y como destino, la cantidad total "tierra" que se
desplaza es y(x, y) siendo la distancia de viaje llx - yll. El coste de ese transporte
es y(x, y) · 11 X - YII . El coste esperado promediado entre todos los pares (x,y) se
obtendría como:
L x ,y y(x,y) 11 X -y 11= IE x ,y -y 11 X - Y 11
Ecuación 5.8
siendo la distancia de Wasserstein el ínfimo de todos los valores que se pueden
obtener de la ecuación 5.8. Hay que tener en cuenta que, como se ha comentado
anteriormente, conforme aumenta la dimensionalidad de los vectores x e y aumenta
exponencialmente las posibilidades de transformación de una distribución a otra
por lo que el cálculo de esta distancia es un problema computacionalmente intenso
y, en muchos casos, se hace de forma aproximada. En la GAN original se puede
demostrar que el objetivo que se persigue es equivalente a minimizar la divergencia
de Jensen-Shannon (que deriva de la clásica Kullback-Leibler pero que, a diferencia
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 261
de esta última es simétrica y cumple la desigualdad triangular). Esta divergencia
presenta problemas para determinar distancias entre distribuciones de probabilidad
cuando éstas se asemejan poco. En el artículo original de la WGAN se da el siguiente
ejemplo; consideremos estas dos distribuciones P y Q (aquí U denota distribución
uniforme y 0 es un parámetro):
'v'(x,y) E P,x = O andy ~ U(0,1)
Ecuación 5. 9
'v'(x, y) E Q,x = 0, O ::; 0 ::; 1 and y~ U(O,l)
La figura 5.8 es una representación gráfica de estas dos representaciones.
■
■
■
■
■
■
■
■
■
■
■
■
0.8
0.6
0.4
0.2
o
0
Figura 5.8. Esquema de las distribuciones P (línea continua) y Q (línea discontinua).
Se puede demostrar que cuando 0 -=/=- O la divergencia de Jensen-Shannon es
log(2) y cuando 0 = O es O. Se tiene pues un salto en esta función en O lo que hace
que no sea diferenciable. Sin embargo, si se calcula la distancia de Wasserstein en el
primer caso es 0 (¡lo que es evidente!) y en el segundo caso, en que 0 = O, es O (por
lo tanto, es una función diferenciable y continua). Este ejemplo demuestra que el
uso de otras medidas diferentes a la divergencia de Jensen-Shannon puede evitar
problemas cuando las dos distribuciones que entran en la GAN en juego (la de los
datos reales y la de los datos sintéticos) se parecen poco.
Dado que la expresión 5.7 es intratable de calcular cuando se tienen
problemas de alta dimensionalidad (y es lo que tenemos al tratar, por ejemplo, con
imágenes) los autores de la WGAN propusieron una transformación de la expresión
5.7 basada en la dualidad Kantorovich-Rubinstein (y ésta es la parte verdaderamente
compleja de todo lo comentado por lo que se recomienda al lector la lectura del
© RA-MA
262 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
artículo original) para llegar a la siguiente expresión para el cálculo de la distancia
de Wasserstein:
Ecuación 5.10
W(IP\, 1Jl>0) = sup IEx-lP' r [f(x)] - IEx -lP' e [f(x)]
llfllL<l
Donde sup denota supremo, y fes una función 1-Lipschitz que cumple la
siguiente desigualdad para todo x 1 y xjf (x1 ) - f (xz) 1 :5 lx1 - Xz l.
Ahora el discriminador ya no tiene la misión de discriminar muestras
sintéticas de las reales; de hecho, en esta arquitectura, se le conoce como crítico (crític).
En su lugar, se entrena para aprender una función continua de tipo K-Lipschitz que
ayude a determinar la distancia de Wasserstein. Según lo comentado anteriormente
la función de coste del crítico será (aquí w es el conjunto de parámetros de los
modelos):
Ecuación 5.11
fcritico = maxIEx-lP'r [fw(x)] - IEz-z[fw(Ge(z))]
wEW
Por otra parte, el objetivo del generador es obtener datos sintéticos lo más
cercanos a la realidad por lo que intentará reducir la distancia de Wasserstein, es
decir:
Ecuación 5.12
]gen = minIE x-lP'r [fw (x)] - IEz-z fw (Ge(z))]
0
A medida que la función de coste disminuye en el entrenamiento, la distancia
de Wasserstein se hace más pequeña y la salida del modelo generador se acerca más a
la distribución de los datos reales. El gran problema aquí es mantener la continuidad
K-Lipschitz de fv durante el entrenamiento. El artículo original presenta un truco
sencillo; después de cada actualización del gradiente, se limitan los coeficientes del
discriminador en una ventana [-c, c] (en el artículo original se escogió c=0.0 1). El flujo
de los diferentes gradientes en esta variante de la GAN se describe en la figura 5 .9.
ATOREA�
--- GENERADOR
z(i)
&F
FUNCIÓN DE
COSTE
Figura 5.9. Esquema de los diferentes términos de gradiente que se tienen en la WGAN.
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 263
Siguiendo con la idea de modificar la función de coste de la GAN original
se tienen las LSGANs (Least Squares GANs). En esta variación se plantea usar la
función de coste de mínimos cuadrados frente a la función de coste entrópica de la
GAN original en la que sólo se tiene en cuenta si se clasifica correctamente, o no, la
imagen que entra al discriminador; con la nueva función de coste se penaliza el error
en función de la distancia a la superficie de separación. Este error es mayor que en la
función entrópica y, por tanto, proporciona una señal de gradiente mayor que en la
GAN original lo que reduce el desvanecimiento del gradiente. En el artículo original
se da la siguiente función de coste para el generador:
m¿n hsGAN = ½IEx~PctataCx)[(D(x) - b) ] + ½IEz~pz (z)[(D(G(z))- a) ]
2
2
Ecuación 5.13
Donde b =1 (datos reales) y a= O (datos sintéticos). Por otra parte, para el
generador se tiene:
Ecuación 5.14
m¿nhsGAN = ½IEz~pz (z)[(D(G(z)) - c) 2 ]
Aquí c= 1 ya que nuestra intención con el generador es engañar al
discriminador. En el artículo se plantea una arquitectura inspirada en la VGG (vista
en el capítulo 3) y que representa en la figura 5.1 O.
Dato aleatorio (z), 1024
fe, 7x7x256, BN
3x3, deeonv, 256, stride=2, BN
5x5, eonv, 64, stride=2, BN
3x3, deeonv, 256, stride=1, BN
5x5, eonv, 128, stride=2, BN
3x3, deeonv, 256, stride=2, BN
5x5, deeonv, 256, stride=2, BN
3x3, deeonv, 256, stride=1, BN
5x5, eonv, 512, stride=2, BN
3x3, deconv, 128, stride=2, BN
fe, 1
3x3, deeonv, 64, stride=2, BN
GENERADOR
3x3, deeonv, 3, stride=1
DISCRIMINADOR
Figura 5.10. Esquema del generador/discriminador de la red LSAGAN original.
BN indica Batch Normalization y fe indica totalmente conectada.
© RA-MA
264 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
La siguiente variación importante que comentaremos en este apartado es la
que se conoce como GAN condicional, Conditional GAN (CGAN). Aquí el cambio
con respecto a la GAN original es considerar la información que pueden contener las
etiquetas para, con ella, mejorar los patrones generados. En el algoritmo original el papel
del generador de una GAN era generar muestras del espacio de probabilidad que subyace
a los datos que se le muestran al discriminador durante el entrenamiento. Sin embargo,
no hay ninguna forma de determinar a priori cómo va a ser la conexión del espacio
latente de partida del generador con las variedades de muestras que puede generar. Por
ejemplo, si se entrena una GAN con imágenes de dígitos como MNIST, se pueden generar
imágenes con elementos que se asemejen mucho a los dígitos deMNIST, pero no se tiene
control sobre qué características de la señal de entrada aleatoria produce cada uno de
los distintos dígitos. En este ejemplo podemos generar el número que se desee en cada
momento. Durante el entrenamiento del modelo, el generador de la CGAN aprende a
generar muestras realistas para cada una de las etiquetas de los datos. De esta forma, se
tiene control sobre las muestras que se generan. Por un lado, la información adicional
que se introduce tanto al generador como al discriminador de la CGAN se puede ver
como una división impuesta ya a priori por las diferentes etiquetas del espacio latente
de la red. Aquí el discriminador aprende a identificar los pares (dato de entrada más
etiqueta) reales que coinciden, mientras que rechaza los pares que no coinciden. A modo
de ejemplo si usáramos el MNIST, el discriminador de la CGAN debería aprender a
rechazar el par (número manuscrito 7, 8), independientemente de si el ejemplo (número
manuscrito 7) es real o falso, porque no coincide con la etiqueta, 8. Además debería
aprender a rechazar todos los pares imagen sintética-etiqueta incluso si la etiqueta
coincide con la imagen. Por lo tanto, para engañar al discriminador, no basta con que
el generador CGAN produzca datos de aspecto realista además los ejemplos generados
deben presentar las etiquetas correctas. Una vez que el generador está completamente
entrenado podemos obtener lo que queramos que sintetice la CGAN pasándole la etiqueta
deseada. El esquema de una CGAN queda definido en la figura 5 .11.
DISCRIMINADOR
Figura 5.11. Esquema de una GAN condicional, el subíndice R hace referencia a los ejemplos reales y el
subíndice Fa los ejemplos falsos o sintéticos. Destacar que en este modelo la entrada al discriminador
son los pares dato sintético generado-etiqueta.
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 265
En esta variante de las GAN se plantea una función de coste ligeramente
modificada de la original, ecuación 5.5, en la que se usan distribuciones de
probabilidad condicionadas como aparece en la siguiente expresión:
�nm_gx V(D, G) = lE"'~Pdata (x) [log D(x I y)] + lEz~pz (z) [log(l - D(G(z I y)))
Ecuación 5.15
Aquí se pueden tener diferentes aplicaciones dependiendo de lo que se
considere como etiqueta; por ejemplo, en sus primeras aplicaciones se consideró
usarla en la aplicación conocida como traslación imagen-imagen (image-image
translation) que, básicamente, consiste en cambiar de dominio una imagen; ejemplos
de esta tarea se tienen cuando se tiene una fotografía de día y se quiere conocer su
aspecto de noche o se tiene una imagen de Google Maps y se quiere tener una vista
de satélite.
Esta aplicación condujo a la variante que se pasa a describir y que es conocida
como CycleGAN. El nombre de esta variación hace referencia al ciclo que se genera
entre los dos dominios usándolo para obtener la consistencia que debería tener la
traslación de uno a otro. Por poner un ejemplo en lenguaje si se traduce una frase del
español al inglés y se usa esta traducción para volver al español el resultado debería
ser igual al original. La siguiente figura, que es análoga a la que aparece en el artículo
original que se da en la bibliografía, muestra la idea que hay tras esta variante. Hay
que destacar que el uso del ciclo entre dominios evita que los datos (imágenes) de
esos dos dominios tengan que ser exactamente iguales para su aplicación.
Figura 5.12. Esquema de una CycleGAN. Aquí las transformaciones G y F son los generadores entre
dominios.
Esta variante usa tres términos en su función de error que se pasan a comentar:
1. Al igual que enla GAN original tenemos la función de coste correspondiente
al generador y al discriminador. Si se continua con la nomenclatura de
la figura 5.12 en esta variante tendríamos dos generadores (Gxv y F yx
266 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
el primer subíndice es el dominio de origen y el segundo el de llegada)
y dos discriminadores (Dx y Dy ). Se tendría para la transformación del
dominio X al Y:
mJn ��LGAN(G, Dy, X, Y) = Ev~Prlata (y) [log Dy(y)] + Ex~rrla1, (x) [log(l - Dy(G(x))]
Ecuación 5.16
De forma análoga para la transformación Y a X se tiene
mínmaxLGAN (F, Dx, Y,X).
F
Dx
2. Usando el ejemplo anterior la diferencia entre la frase original en español
y la obtenida tras pasar al inglés y volver al español se puede usar como
medida de calidad del funcionamiento de esta variante (y se conoce como
la consistencia en el ciclo, cycle-consistency). Esta condición tiene su
correspondencia con la siguiente función de coste:
Ecuación 5.17
Aquí 11111 hace referencia a la norma L 1 (también conocida como distancia
de Manhattan).
3. En el artículo original se hacen experimentos mediante la combinación
de las funciones de coste definidas anteriormente:
L(G,F,Dx,Dy ) = LGAN (G,D y,X, Y)+ LGAN(F,Dx, Y,X) + ílLcyc (G,F)
Ecuación 5.18
Siendo 11, un parámetro que pesa la aportación de la función de coste
cíclica. El objetivo que se plantea es el siguiente:
G*,F* = mínmaxL(G,F,Dx ,D y)
G,F Dx,DY
Ecuación 5.19
Más adelante en el artículo se plantea añadir otro término a la ecuación
5.19 que se asegura que si se plantea una transformación a un dominio
usando los datos de ese dominio; el resultado de dicha transformación
debería ser el mismo dato usado. Básicamente se está exigiendo que la
CycleGAN se aplique cuando corresponda. Aplicando esta condición se
llega a lo que se conoce como función de coste identidad:
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 267
Lidentidad ( G, F) = IEy~Pctata (y) [11 G (y) - Y 111] + IEx~Pctata (x) [11 F (x) - X 111]
Ecuación 5.20
Esta función se integra en la ecuación 5 .19 y se aplican las condiciones
de la ecuación 5 .16 para obtener los parámetros óptimos de esta variante
de la GAN.
5. 7 LABORATORIO
PRÁCTICA l. Autoencoders Variacionales (VAE)
En esta práctica vamos a trabajar con otro conjunto de datos muy conocido,
Fashion MNIST. Es un conjunto de datos de imágenes de artículos de ropa (de
Zalando) considerado la evolución del conjunto MNIST que vimos en la primera
práctica porque siguen siendo imágenes pequeñas en blanco y negro pero de mayor
complejidad. Cuenta con 60.000 imágenes para el conjunto de entrenamiento y
10.000 para el conjunto de test, todas de 28x28 píxeles. Nuestro objetivo durante
esta práctica será entrenar un autoencoder variacional a partir de estas imágenes y ser
capaces de generar nuestras propias prendas de ropa. Utilizaremos un autoencoder
variacional basado en capas densas.
Importación de las librerías
Es una buena práctica colocar todas las importaciones de librerías al principio
de nuestro código. No utilizaremos ninguna librería nueva:
import numpy as np
import matplotlib.pyplot as plt
import tensorílow as tf
from tensorílow.keras import layers
from tensorílow.keras.callbacks import EarlyStopping, ModelCheckpoint
Carga de los datos
Al ser un problema tan estandarizado en el aprendizaje profundo, podemos
obtener los datos directamente de la librería TensorFlow con la función tf. keras.
268 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
datasets.fashion_mnist.load_data(). Esta función nos devuelve dos conjuntos
de datos: el de entrenamiento y el de test.
(X_train,Y_train),(X_test,Y_test)=tf.keras.datasets.fashion_mnist.load_data()
print(f"Muestras de entrenamiento: {len(X_train)}.")
print(f"Muestras de test: {len(X_test)}.")
> Muestras de entrenamiento: 60000.
> Muestras de test: 10000.
Recordemos que, al contrario que la librería tensorflow_datasets, los
conjuntos de datos que cargamos de esta manera son simplemente arrays de
NumPy. Esto no es ningún problema porque ya sabemos trabajar con los dos tipos
de formatos, además, es posible crear un tf.data. Dataset a partir de uno o varios
array de NumPy con el método .from_tensor_slices(). Esto puede semos de
utilidad porque nos permite aprovechar todas las funcionalidades de los Dataset de
TensorFlow que ya hemos visto. Como en esta práctica solamente queremos utilizar
las imágenes de la ropa para generar nuevas, no necesitamos las etiquetas para nada,
por lo que no es necesario que las incluyamos:
train = tf.data.Dataset.from_tensor_slices(X_train)
test = tf.data.Dataset.from_tensor_slices(X_test)
len(train), len(test)
> (60000, 10000)
Exploración de los datos
Siguiendo el procedimiento habitual, podemos representar algunas imágenes
del dataset para ver los datos con los que vamos a trabajar:
class_names = [ 'T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat',
'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
plt.figure(figsize=(9,9))
for i in range(25):
plt.subplot(S,S,i+l)
plt.xticks([])
plt.yticks([])
plt.grid(False)
plt.imshow(X_train[i])
plt.xlabel(class_names[Y_train[i]])
plt.show()
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 269
�[I'] ,,._
�
.,.._ 1 :;·I
Pullover
Sneaker
[l]CI
�[lf][IJ
Ankle boot
Trouser
Dress
Trouser
[I] [I]
[]lJ
�8
Sandal
Sandal
Sandal
Sneaker
T-shirt/top
Bag
Figura 5.13. Muestra de algunas imágenes del conjunto de datos junto a su etiqueta.
También representamos una imagen con su correspondiente barra de color
para identificar el rango de los píxeles de las imágenes:
plt.imshow(X_train[15])
plt. colorbar()
plt. show()
o
250
5
200
10
150
15
100
20
50
25
5
10
15
20
25
Figura 5.14. Imagen del conjunto de datos junto a su barra de color. Vemos que los valores de los píxeles
están en el intervalo [0,255].
270 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
El tamaño de las imágenes no hace falta que lo comprobemos porque,
sabiendo que provienen de un array, sabemos que deben tener todas las mismas
dimensiones.
Preprocesado de los datos
Como es habitual, tendremos que normalizar las imágenes al rango íü, 1] antes
de introducirlas en nuestro modelo. Además, como vamos a utilizar un autoencoder
basado únicamente en capas densas, tenemos que "estirar" las imágenes para que
tengan la forma de un vector unidimensional que puedan procesar las capas densas.
Para "estirar" las imágenes podemos utilizar la función tf.reshape(tensor,
shape= [ -1]). Tampoco podemos olvidar que los autoencoders variacionales
se entrenan comparando la imagen de entrada con la imagen de salida, es decir,
nuestro tf.data.Datas et tendrá que devolver dos veces la misma imagen. Una
de las ventajas de los objetos tf.data.Datas et es que podemos encapsular este
preprocesado en una función y aplicarla en todos los conjuntos de datos de manera
muy cómoda.
Ejercicio
Otra cosa que podríamos hacer es utilizar como primeras capas del
modelo layers. Flatten() y layers. Rescaling(). Pruébalo.
def prepare_data(image):
image = image/255
image = tf.reshape(image, [-1])
return image, image
train_rdy = train.map(prepare_data)
test_rdy = test.map(prepare_data)
Definición del modelo
En la práctica anterior ya trabajamos con la arquitectura encoder-decoder,
pero ahora le daremos una vuelta de tuerca para hacerla apta para la generación de
nuevas muestras. El único cambio que tenemos que hacer es modificar la forma
en la que se codifican los datos al espacio latente con el objetivo de que el espacio
latente tenga una estructura lo más parecida posible a una distribución normal. Para
conseguirlo modificaremos el encoder para que, en lugar de codificar los datos a un
solo punto, lo codifique a un punto y su entorno. O lo que es lo mismo, que nos de
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 271
la media y la desviación estándar del punto en el espacio latente. Una vez tengamos
estos dos parámetros podemos utilizar la divergencia de Kullback-Leibler (mide
la distancia entre dos distribuciones de probabilidad) para forzar la estructura del
espacio latente a la distribución normal.
Este sutil cambio sirve para que el espacio latente sea en general mucho
más continuo, lo que mejora enormemente la generación de datos nuevos a partir
del mismo, pero trae consigo una pega muy importante: tal y como se planteaba
originalmente esta operación no es diferenciable, por lo que se tiene que utilizar un
pequeño truco para poder entrenar el modelo mediante descenso del gradiente.
Reparametrization Trick
Este truco no es más que utilizar la media y la desviación estándar que nos
proporciona el encoder para generar un nuevo valor mediante la expresión:
dónde E es un número muy pequeño que extraeremos de una distribución gaussiana
de media O y desviación estándar 1. Para implementarlo definimos una función que
tomará los valores deµ y CY, y devolverá el valor de z.A modo de detalle, normalmente
se trabaja con el logaritmo de la desviación estándar porque está definido en todo el
espacio y favorece el buen funcionamiento de la red. Más adelante utilizaremos esta
función dentro de una capa layers. Lambda () que solo acepta una entrada, por eso
definimos la función de esta manera.
def reparametrization(input):
mean, log_sigma = input
epsilon = tf.random.normal(shape=tf.shape(log_sigma),
mean=0, stddev=l)
return mean + epsilon*tf.exp(log_sigma)
API Funcional
La arquitectura que queremos plantear tiene una bifurcación, por lo que
tendremos que utilizar o bien laAPI Funcional o bien herencia de clases. Cada opción
tiene sus pros y sus contras, pero como la documentación oficial tiene implementado
un modelo similar con herencia de clases (https://w ww.tensorflow.org/tutorials/
generative/cvae), se ha optado por utilizar la API Funcional.
272 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Como el modelo tiene dos partes perfectamente diferenciadas que pueden
tener usos distintos, tiene sentido definirlos como dos modelos diferentes y juntarlos
posteriormente. No hay que olvidar que podríamos utilizar únicamente el encoder
para hacer tareas de compresión o solamente el decoder para generar datos nuevos.
Encoder
Consideramos que el encoder es la parte de la red que recibe una imagen
y devuelve su ubicación en el espacio latente. Para poder aplicar la función que
hemos definido que implementa el Reparametrization Trick utilizaremos una
capa layers. Lambda (), que permite definir una capa con cualquier función que
queramos.Fijaremos una dimensión del espacio latente de 2 para poder realizar
algunas representaciones más adelante.
latent_dim = 2
inputs = tf.keras.Input(shape=(784,))
output = layers.Dense(128, activation='relu')(inputs)
mean = layers.Dense(latent_dim)(output)
log_sigma = layers.Dense(latent_dim)(output)
z = layers.Lambda(reparametrization)((mean, log_sigma))
encoder = tf.keras.Model(inputs, z, name='encoder')
encoder.summary()
> Model: "encoder"
> ____________________________
> Layer (type) Output Shape Param # Connected to
> -------------------------------------------------------------------> input_8 (Inputlayer) ((None, 784)] 0
> ____________________________
> dense_39 (Dense) (None, 128) 100480 input_8[0][0]
> ____________________________
> dense_40 (Dense) (None, 2) 258 dense_39(0](0]
> ____________________________
> dense_41 (Dense) (None, 2) 258 dense_39(0][0]
> ____________________________
> lambda_7 (Lambda) (None, 2) 0 dense_40[0][0] dense_41[0][0]
> -------------------------------------------------------------------> Total params: 100,996
> Trainable params: 100,996
> Non-trainable params: 0
> ____________________________
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 273
Decoder
Se construye de forma que tenga una función de activación Sigmoide en la
última capa porque no podemos tener píxeles negativos. También se puede utilizar
una función ReLU.
Ejercicio
Utiliza una función ReLU como activación de la última capa y compara
los resultados. ¿Qué observas?
latent_inputs = tf.keras.Input(shape=(latent_dim,), name='z_sampleo')
output = layers.Dense(128, activation='relu')(latent_inputs)
output = layers.Dense(784, activation='relu')(output)
decoder = tf.keras.Model(latent_inputs, output, name='decoder')
decoder.summary()
> Model: "decoder"
> ___________________________
> Layer (type) Output Shape Param #
> ----------------------------------------------------------------> z_sampleo (InputLayer) [(None, 2)] 0
> ___________________________
> dense_42 (Dense) (None, 128) 384
> ___________________________
> dense_43 (Dense) (None, 784) 101136
> ----------------------------------------------------------------> Total params: 101,520
> Trainable params: 101,520
> Non-trainable params: 0
> ___________________________
Modelo final
Una vez definidas las dos partes, podemos juntarlas de manera sencilla:
output = decoder(encoder(inputs))
model = tf.keras.Model(inputs, output, name='VAE')
model.summary()
> Model: "VAE"
> ___________________________
274 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
> Layer (type) Output Shape Param #
> ----------------------------------------------------------------> input_8 (Inputlayer) ((None, 784)] 0
> __________________________
> encoder (Functional) (None, 2) 100996
> __________________________
> decoder (Functional) (None, 784) 101520
> ----------------------------------------------------------------> Total params: 202,516
> Trainable params: 202,516
> Non-trainable params: 0
> __________________________
Funciones de coste
Como hemos comentado antes, vamos a utilizar la divergencia de Kullback­
Leibler para controlar la distribución del espacio latente, pero también tenemos
que utilizar el error de reconstrucción para que nuestro modelo aprenda qué tipo
de imágenes tiene que generar. Los modelos de TensorFlow nos permiten definir
funciones de coste de manera similar a cómo se definen los modelos mediante laAPJ
Funcional. El primer paso es definir la función que calcule la divergencia KL, que en
el caso de una gaussiana deµ = O y 6 = 1 es:
def kl_divergence(mean, log_sigma):
kl_loss = 1 + log_sigma - tf.square(mean) - tf.exp(log_sigma)
kl_loss = tf.math.reduce_sum(kl_loss, axis=-1)
kl_loss *= -0.5
return kl_loss
Para calcular el error de reconstrucción se puede utilizar tanto el Error
Cuadrático Medio como la Entropía Cruzada, pero esta última favorece que los
píxeles tomen valores intermedios y puede producir imágenes más borrosas, así que
utilizaremos el MSE. De forma nativa TensorFlow calcula el error de reconstrucción
medio por píxel, por lo que se suele multiplicar por la cantidad de píxeles para
equilibrar el peso frente al valor de la divergencia.
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 275
Ejercicio
Utiliza la entropía cruzada y compara los resultados. ¿ Ves alguna
diferencia?
kl_loss = kl_divergence(mean, log_sigma)
reconstruction_loss = 784*tf.losses.mean_squared_error(inputs, output)
total_loss = reconstruction_loss + kl_loss
model.add_loss(total_loss)
model.compile(optimizer='adam')
Nuestro modelo ya está listo para ser entrenado:
## Definición de los callbacks
cb_earlystopping = EarlyStopping(patience=4,
monitor='val_loss')
cb_modelcheckpoint ModelCheckpoint(filepath='model_vae_dense.hS',
monitor='val_loss',
save_best_only=True,
save_weights_only=True)
history model.fit(train_rdy.batch(128),
epochs=50,
validation_data=test_rdy.batch(128),
callbacks=[cb_earlystopping, cb_modelcheckpoint])
model.load_weights('model_vae_dense.hS')
Representando las dinámicas de entrenamiento vemos que el modelo aprende
muy rápido:
plt.figure()
plt.plot(history.history['loss'], 'k-o',
label = "Entrenamiento")
plt.plot(history.history['val_loss'], 'k--*',
label = "Validación")
plt.ylabel('Loss')
plt.xlabel('Épocas')
plt.title('Loss del modelo')
plt.legend()
plt.show()
© RA-MA
276 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Loss del modelo
so.o
--.- Entrenamiento
-•- Validación
47.5
45.0
42.5
..J 40.0
37.5
35.0
32.5
o
5
10
15
20
25
30
Épocas
Figura 5.15. Dinámicas de entrenamiento del modelo.
Comprobando la capacidad de reconstrucción
Una vez hemos entrenado nuestro modelo podemos comprobar qué tal es
capaz de reconstruir algunas imágenes del conjunto de test:
n = 8
far img, _ in test_rdy.batch(n):
img_rec = model.predict(img)
break
plt.figure(figsize=(9, 4))
far i in range(n):
plt. gray()
ax = plt.subplot(2, n, i+l)
plt.imshow(X_test[i].reshape(28, 28))
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax = plt.subplot(2, n, i +l+n)
plt.imshow(img_rec[i].reshape(28, 28))
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 277
IBI lll
fil
Figura 5.16. Imágenes reconstruidas por el modelo. ¿Por qué crees que aparecen píxeles negros?
Llaman la atención los huecos negros de las imágenes reconstruidas. Esto
sucede porque hemos colocado una función ReLU en la última capa. ¿Sucederá lo
mismo con la sigmoide? Explica el resultado.
Generando prendas de ropa nuevas
Lo último que nos queda por ver es cómo utilizar el modelo que hemos
entrenado para generar prendas de ropa nuevas. Lo primero a tener en cuenta es que
en realidad no nos hace falta utilizar el modelo entero. Para generar imágenes nuevas
solamente necesitamos utilizar el decoder.
La idea es generar números aleatorios a partir de una distribución normal,
es decir, muestrear del espacio latente, e introducirlos en el decoder para obtener
imágenes nuevas. Probémoslo:
z = np.random.normal(size=(l, latent_dim))
gen_img = decoder.predict(z)
plt.figure()
plt.imshow(gen_img.reshape(28,28))
plt.show()
© RA-MA
278 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
o
5
10
15
20
25
o
5
10
15
20
25
Figura 5.17. Imagen generada por el autoencoder variacional que hemos entrenado.
También podemos recorrer el espacio latente y representar las imágenes que
vamos generando:
n = 15 # imagen de 15x15
tamaño_imagen = 28
figura np.zeros((tamaño_imagen * n, tamaño_imagen * n))
std_x = np.linspace(-15, 15, n)
std_y = np.linspace(-15, 15, n)
for i, yi in enumerate(std_x):
for j, xi in enumerate(std_y):
z_sample = np.array([[xi, yi]])
x_decoded = decoder.predict(z_sample)
ropa = x_decoded(0].reshape(tamaño_imagen, tamaño_imagen)
figura[i * tamaño_imagen: (i + 1) * tamaño_imagen,
j * tamaño_imagen: (j + 1) * tamaño_imagen] = ropa
plt.figure(figsize=(9, 9))
plt.imshow(figura, cmap='gray')
plt.show()
© RA-MA
o
50
100
Capítulo 5. MODELOS GENERATIVOS 279
• ,A,iO. �Mi .. ,i �1-1 ("l. (1
f'q
'
1
• ,A1M . ,Ah6 _,l,A
.( �1
{ 1_
,AD. �.o 4',.á
◄ '¡
¡
. "'·ª
--�
. ..:') b
150
¡
" ,_ PI �-.,,..,MM
,,-,
�
¡ t
,F ,t,.
! '
.. "
�
.....
. ....
200
... ., ·�' -,�.,�,r,
� ;i.,
,o.,�
t ��, M
,... M
r . ,.,
�
.�
'"''
:'"ft
250
"
' ·'
''
300
,.
�
.. :
350
4 )
400
�l
o
1
.
.
f \
'
·1r it ' ' '
1 f,,
. '
:
'i
50
;
1
' <'
i
'1
1
100
1
¡
1 ·1 ·1
�
1
1
150
•l
350
400
1
¡; '' ,f
1
200
'1
250
1
1.l
'�
300
.,
,., ,,..,
, �1 1
-1
•
f,!t
(,
.'
Figura 5.18. Exploración del espacio latente del autoencoder variaciona I que hemos entrenado. Resulta
muy interesante ver como las transiciones entre diferentes prendas son continuas.
Ejercicio
Repite el ejercicio utilizando capas convolucionales en lugar de capas
densas y compara los resultados. Recuerda que puedes apoyarte en el ejemplo
resuelto de la documentación oficial.
280 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
PRÁCTICA 2. Detección de anomalías con autoencoders
En esta práctica vamos a ver un ejemplo práctico del uso de los autoencoders
para la detección de anomalías. Como se ha explicado en este capítulo, los
autoencoders son modelos generativos que se utilizan para la reconstrucción
de datos de alta dimensionalidad usando dos redes: un encoder y un decoder. El
conjunto de datos que vamos a utilizar contiene información sobre fraude en tarjetas
de crédito. Pertenece a una competición de Kaggle y es totalmente abierto y está
anonimizado. Se puede descargar del siguiente enlace https://www.kaggle.com/
mlg-ulb/creditcardfraud/download. Los datos corresponden a las transacciones
realizadas durante dos días, en los que se produjeron 492 fraudes de entre 284.807
transacciones totales. Esta diferencia de frecuencias nos puede indicar que se puede
plantear como un problema de detección de anomalías. El objetivo es detectar en
qué casos las transacciones siguen un comportamiento normal y cuando se trata de
fraude, es decir, cuando hay una anomalía.
Importación de las librerías
A las librerías usuales añadimos seaborn, que nos resultará útil para hacer
algunas representaciones gráficas.
from collections import Counter
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import tensorílow as tf
from tensorílow.keras import layers
from tensorílow.keras.callbacks import ModelCheckpoint, EarlyStopping
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import confusion_matrix, recall_score, accuracy_score, pre­
cision_score, roc_auc_score
Carga de los datos
Asumiremos que los datos se han almacenado en una carpeta /data/ a la
que tenemos acceso:
df = pd.read_csv("data/creditcard.csv")
print("El conjunto de datos tiene{} filas y{} columnas.".format(*df.shape))
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 281
> El conjunto de datos tiene 284807 filas y 31 columnas.
Exploración de los datos
Por motivos de anonimato la mayoría de las variables proceden de realizar
una PCA de los datos originales (VX). Las únicas variables que no se han transformado
son Time (el tiempo transcurrido desde la primera transacción), Amount (la cantidad
de la transacción) y Class (si es fraudulenta o no).
Lo primero que hacemos es comprobar que los tipos de las variables son los
adecuados utilizando el método . info() (la salida ha sido recortada):
> Rangeindex: 284807 entries, 0 to 284806
> Data columns (total 31 columns):
Dtype
> # Column Non-Null Count
-------------> --> 0
Time
284807 non-null float64
> 1 Vl
284807 non-null float64
> 2 V2
284807 non-null float64
> 3 V3
284807 non-null float64
> 4 V4
284807 non-null float64
Una vez hemos visto que todas parecen tener el tipo adecuado es recomendable
comprobar que las etiquetas tienen los valores esperados. En este caso tenemos un
problema binario, fraude/no fraude, por lo que esperamos que las etiquetas sean 1
(fraude) o O (no fraude). Lo comprobamos fácilmente con el método . unique():
df.Class.unique()
> array([0, 1], dtype=int64)
Aunque en este caso ya nos avisan de que es un problema desbalanceado, en
general, también es recomendable observar la distribución de las etiquetas. Podemos
utilizar el objeto Counter ():
Counter(df.Class)
> Counter({0: 284315, 1: 492})
© RA-MA
282 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Comprobamos que se trata de un dataset muy desbalanceado: se tienen
284315 muestras de transacciones normales y 492 fraudulentas.
Visualización de los datos
Antes de introducir los datos en nuestro modelo siempre es recomendable
representar las distribuciones de cada variable para ver si es necesario realizar alguna
transformación. También puede ser útil estudiar las correlaciones entre las variables
para identificar posibles variables redundantes. En este caso, como sabemos que
las características se han extraído de una PCA, podemos asumir que las variables
se comportan correctamente y no están correlacionadas, y representar únicamente
la variable Amount. Es necesario ajustar el rango de la representación porque la
presencia de outliers evita que podamos ver si no lo hacemos.
df.hist(column='Amount', range=[0,2000], grid=False, color='black')
plt. show()
Amount
250000
200000
150000
100000
50000
o
o
250
500
750
1000
1250
1500
1750
2000
Figura 5.19. Histograma de la variable Amount.
Vemos que la mayoría de las transacciones que se realizan son de cantidades
bajas. Este tipo de distribuciones pueden beneficiarse de algunas transformaciones
para "normalizarlas" pero no prestaremos especial atención a eso en esta práctica.
Finalmente podemos representar la cantidad de las transacciones frente al porcentaje
que representan del total. Esto nos puede situar en los rangos que podrían indicar que
una transacción es fraudulenta o no:
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 283
df_normal = df[df.Class == 0]
df_fraud = df[df.Class == 1]
bins = np.linspace(200, 2500, 100)
plt.figure()
plt.hist(df_normal.Amount, bins=bins, alpha=0.8,
density=True, label='Normal', color='black')
plt.hist(df_fraud.Amount, bins=bins, alpha=0.4,
density=True, label='Fraude', color='black')
plt.legend(loc='upper right')
plt.title("Cantidad de la transacción vs Porcentaje de las transacciones")
plt.xlabel("Cantidad de la transacción (USD)")
plt.ylabel("Porcentaje de las transacciones")
plt.show()
Cantidad de la transacción vs Porcentaje de las transacciones
"'
V,
e
- Normal
0.005
- Fraude
"ü 0.004
"'V,
e
� 0.003
.!!1
"'
w 0.002
"'u
o 0.001
0.000
500
1000
1500
Cantidad de la transacción (USO)
2000
2500
Figura 5.20. Representación de las cantidades relativas de transacciones y la cantidad de éstas en los
casos de fraude y no fraude.
A partir de esta figura vemos que sí que existen más transacciones fraudulentas
de mayor importe.
Preparación de los datos
En los problemas de detección de anomalías, lo normal es entrenar
los autoencoders únicamente con los datos normales. De esta forma, como el
modelo aprende a reconstruirlos, si intenta reconstruir un dato que no ha visto
nunca (anómalo), debería hacerlo considerablemente mal y podríamos detectar la
284 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
anomalía.Esto quiere decir que primero vamos a dividir los datos en dos conjuntos:
transacciones normales y fraudes (esto ya lo hemos hecho para la representación
anterior). Después dividiremos cada conjunto en entrenamiento, validación y test.
De esta forma tendremos los seis conjuntos separados correctamente.
train_normal,test_normal=train_test_split(df_normal.drop(labels=["Time",
"Class"], axis=l), test_size=0.3)
val_normal, test_normal = train_test_split(test_normal, test_size=0.3)
train_fraud,test_fraud = train_test_split(df_fraud.drop(labels=["Time",
"Class"], axis=l), test_size=0.3)
val_fraud, test_fraud = train_test_split(test_fraud, test_size=0.3)
train = np.concatenate([train_normal, train_fraud])
val = np.concatenate([val_normal, val_fraud])
test = np.concatenate([test_normal, test_fraud])
Ahora podemos utilizar los datos del conjunto de entrenamiento para
estandarizar el resto de los datos. Es muy importante que solo utilicemos los datos
de entrenamiento para estimar los parámetros de la estandarización, porque si
no estaríamos filtrando información del resto de conjuntos al de entrenamiento y
podríamos obtener resultados que no representasen la realidad.
El objeto StandardScaler() de sklearn facilita mucho este proceso:
scaler = StandardScaler()
scaler.fit(train)
train_normal_std, train_fraud_std = scaler.transform(train_normal), scaler.
transform(train_fraud)
val_normal_std, val_fraud_std = scaler.transform(val_normal), scaler.
transform(val_fraud)
test_normal_std, test_fraud_std = scaler.transform(test_normal), scaler.
transform(test_fraud)
Definición del modelo
Un autoencoder se caracteriza por tener una arquitectura que comprime los
datos y luego los descomprime de forma simétrica. Esto quiere decir que tendremos
que colocar capas de forma que la cantidad de neuronas vaya decreciendo hasta
cierto punto (hasta la dimensión latente que consideremos) para volver a recuperar su
dimensión original de forma inversa. Como este tipo de modelos no tiene conexiones
entre capas, podemos utilizar la API Secuencial para definirlo cómodamente. Hay
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 285
que recordar que en este caso la función de activación de la capa de salida tiene
que ser una función ReLU, porque tiene que ser capaz de producir cualquier valor
posible. Como función de coste utilizaremos el Error Cuadrático Medio (MSE en
inglés), y utilizaremos el optimizador Adam como viene siendo habitual:
model = tf.keras.models.Sequential([
layers.Dense(lS, activation='relu', input_shape=(train.shape[-1],)),
layers.Dense(B, activation='relu'),
layers.Dense(4, activation='relu'),
layers.Dense(B, activation='relu'),
layers.Dense(lS, activation='relu'),
layers.Dense(train.shape[-1], activation='linear')
])
model.summary()
model.compile(optimizer='adam',
loss='mse',
metrics=['mae'])
> Model: "sequential"
> Layer (type) Output Shape Param #
> ----------------------------------------------------------------> dense (Dense) (None, 15) 450
>
> dense_l (Dense) (None, 8) 128
>
> dense_2 (Dense) (None, 4) 36
>
> dense_3 (Dense) (None, 8) 40
>
> dense_4 (Dense) (None, 15) 135
>
> dense_S (Dense) (None, 29) 464
> -----------------------------------------------------------------
> Total params: 1,253
> Trainable params: 1,253
> Non-trainable params: 0
> ___________________________
A la hora de elegir el mejor modelo posible y aplicar early stopping podría
tener sentido quedarnos con el que obtenga mayor error en los datos de fraude,
pero esto no suele proporcionar demasiados buenos resultados. Lo normal es que
el error en los dos tipos de muestras sea diferente pero vaya decreciendo durante el
entrenamiento, por lo que si utilizamos el máximo error para parar el entrenamiento
286 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
lo normal es que lo paremos después de un par de épocas. Lo que suele funcionar
mejor es utilizar el conjunto de validación normal para evitar el sobreajuste.
Ejercicio
Prueba a elegir el mejor modelo basándote en el máximo del error de
reconstrucción del fraude. ¿ Qué observas?
## Definición de los callbacks
cb_earlystopping = EarlyStopping(patience=8,
monitor='val_mae',
mode='min')
cb_modelcheckpoint = Mode1Checkpoint(filepath='model_ae.h5',
monitor='val_mae',
mode='min',
save_best_only=True)
## Entrenamiento del modelo
history = model.fit(train_normal_std,
train_normal_std,
epochs=20,
batch_size=256,
validation_data=(val_normal_std,val_normal_std),
callbacks=[cb_earlystopping, cb_modelcheckpoint])
plt.figure()
plt.subplot(l,2,1)
plt.plot(history.history['loss'], 'k-o',
label = "Entrenamiento")
plt.plot(history.history['val_loss'J, 'k--*',
label = "Validación")
plt.ylabel('MSE')
plt.xlabel('Épocas')
plt.title('MSE')
plt.legend()
plt.subplot(l,2,2)
plt.plot(np.array(history.history['mae']), 'k-o',
label="Entrenamiento")
plt.plot(np.array(history.history['val_mae']), 'k--*',
label="Validación")
plt.ylabel('MAE')
plt.xlabel('Épocas')
plt.title('MAE')
plt.legend()
plt.show()
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 287
MSE
-•-
MAE
-+- Entrenamiento
0.85
Validación
-+- Entrenamiento
-•- Validación
0.60
0.80
0.58
w
:;:
0.75
;¡; 0.56
0.70
0.54
0.65
0.52
0.60
o
5
10
Épocas
15
o
10
15
Épocas
Figura 5.21. Dinámicas de entrenamiento del modelo.
Una vez hemos entrenado nuestro autoencoder podemos calcular los errores
de reconstrucción de las muestras normales y las muestras de fraude:
def calculate_error(model, data):
pred = model.predict(data)
error = np.mean((pred-data)**2, axis=l)
return error
error_train_normal = calculate_error(model, train_normal_std)
error_train_fraud = calculate_error(model, train_fraud_std)
Representando los histogramas de los dos errores podemos ver que, aunque
hay valores de los dos conjuntos que se superponen, también hay errores mucho
más grandes para los datos de fraude que para los datos normales. Esto es lo que
buscábamos obtener.
plt.figure()
plt.hist(error_train_normal, color='k', bins=50, alpha=0.8, label="Normal", ran­
ge=[0,100], density=True)
plt.hist(error_train_fraud, color='k', bins=50, alpha=0.4, label="Fraud", ran­
ge=[0,100], density=True)
plt. legend ()
plt. show()
© RA-MA
288 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
0.5
- Normal
- Fraud
0.4
0.3
0.2
0.1
o
20
40
60
80
100
Figura 5.22. Histograma de los errores al reconstruir las transacciones normales y las fraude.
También podemos calcular la media de los errores normales y fraude:
error_train_normal.mean(), error_train_fraud.mean()
> (0.5850001111764015, 19.26729352009007)
Vemos que hay una enorme diferencia entre los errores de reconstrucción de
los dos tipos, por lo que parece que el modelo hace lo que esperábamos.
Umbral: detectando anomalías
El problema que surge a continuación es cómo utilizar este error para detectar
fraude. Una primera aproximación puede ser establecer un umbral y marcar como
fraude las transacciones cuyo error de reconstrucción supere este umbral. No es una
mala idea, pero tiene dos problemas:
,.. Si no tenemos suficientes datos, este umbral puede ser complicado de
establecer.
,.. Además, es otro hiperparámetro que puede sobreajustar nuestro conjunto
de datos.
Basándonos en la figura anterior podemos considerar un umbral de l O y
comprobar lo que obtenemos. Como el conjunto de datos está muy desbalanceado
lo que haremos será obtener la precisión, el recall y el F1-Score para medir el
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 289
rendimiento del modelo. Cuando sea posible también calcularemos el área bajo la
curvaROC.
Ejercicio
En problemas desbalanceados como este el área bajo la curva ROC
siempre es un buen estimador del rendimiento de los modelos. Intenta calcularla.
¿ Qué problemas encuentras?
Antes de poder calcular alguna métrica tenemos que volver a asociar las
etiquetas correspondientes a las transacciones normales y fraude:
def rebuild_dataframe(error_normal, error_fraud):
normal_labels = np.zeros(shape=(len(error_normal),))
fraud_labels = np.ones(shape=(len(error_fraud),))
labels = np.concatenate([normal_labels, fraud_labels])
error = np.concatenate([error_normal, error_fraud])
df_error = pd.DataFrame({'Error':error,
'Label':labels})
return df_error
error_val_normal = calculate_error(model, val_normal_std)
error_val_fraud = calculate_error(model, val_fraud_std)
error_test_normal = calculate_error(model, test_normal_std)
error_test_fraud = calculate_error(model, test_fraud_std)
df_error_train=rebuild_dataframe(error_train_normal, error_train_fraud)
df_error_val = rebuild_dataframe(error_val_normal, error_val_fraud)
df_error_test = rebuild_dataframe(error_test_normal, error_test_fraud)
Y para calcular la predicción final solamente tenemos que determinar
diferentes umbrales para los errores:
def pred_threshold(error, threshold):
return 1 if error > threshold else 0
df_error_train('Pred'] = df_error_train.Error.apply(pred_threshold, thres­
hold=10)
df_error_val['Pred'] = df_error_val.Error.apply(pred_threshold, threshold=10)
df_error_test('Pred'] = df_error_test.Error.apply(pred_threshold, threshold=10)
Finalmente calculamos las métricas:
def evaluate_model(y_true, y_pred, verbose=True):
290 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
acc = accuracy_score(y_true, y_pred)
recall = recall_score(y_true, y_pred)
precision = precision_score(y_true, y_pred)
fl = 2 * (precision * recall) / (precision + recall)
if verbose:
print("Accuracy: ", acc)
print("Recall: ", recall)
print("Precision: ", precision)
print("Fl: ", fl)
return acc, recall, precision, fl
print("[Train]")
metrics_threshold_train = evaluate_model(df_error_train.Label, df_error_train.
Pred)
print("[Validation]")
metrics_threshld_val = evaluate_model(df_error_val.Label, df_error_val.Pred)
print("[Test]")
metrics_threshld_test = evaluate_model(df_error_test.Label, df_error_test.Pred)
> [Train]
> Accuracy: 0.9969653498124035
> Recall: 0.4331395348837209
> Precision: 0.26654740608228983
> Fl: 0.3300110741971208
> [Validation]
> Accuracy: 0.9969402598271163
> Recall: 0.4563106796116505
> Precision: 0.27011494252873564
> Fl: 0.33935018050541516
> [Test]
> Accuracy: 0.996996176952485
> Recall: 0.3333333333333333
> Precision: 0.24193548387096775
> Fl: 0.28037383177570097
Utilizando otro modelo como umbral
Ya hemos visto los inconvenientes que tiene el método del umbral. Si
queremos ir un paso más allá, podemos entrenar otro modelo que nos prediga la
clase de la transacción a partir del error de reconstrucción. De esta forma evitamos
tener que establecerlo a mano. En general es complicado que un modelo sea capaz de
extraer mucha información a partir de una única característica ( el error), así que lo que
haremos será obtener un nuevo dataframe que contenga el error de reconstrucción
de cada característica en lugar del error medio de la muestra entera y utilizarlo como
entradas de nuestro modelo.
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 291
Ejercicio
No es necesario que este modelo sea un modelo neuronal. Prueba
también con otros modelos no neuronales como Random Forest y compara el
resultado.
def calculate_error_2(model, data):
pred = model.predict(data)
error = (pred-data)**2
return error
error_train_normal_2 = calculate_error_2(model, train_normal_std)
error_train_fraud_2 = calculate_error_2(model, train_fraud_std)
error_val_normal_2 = calculate_error_2(model, val_normal_std)
error_val_fraud_2 = calculate_error_2(model, val_fraud_std)
error_test_normal_2 = calculate_error_2(model, test_normal_std)
error_test_fraud_2 = calculate_error_2(model, test_fraud_std)
error_train_2 = np.concatenate([error_train_normal_2, error_train_fraud_2])
error_val_2 = np.concatenate([error_val_normal_2, error_val_fraud_2])
error_test_2 = np.concatenate([error_test_normal_2, error_test_fraud_2])
model_th = tf.keras.models.Sequential([
layers.Dense(S, activation='relu', input_shape=(error_train_2.shape[l],)),
layers.Dense(l, activation='sigmoid')
])
model_th.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', 'AUC'])
## Definición de los callbacks
cb_earlystopping = EarlyStopping(patience=4,
monitor='val_auc',
mode='max')
cb_modelcheckpoint Mode1Checkpoint(filepath='model_ae_th_2.h5',
monitor='val_auc',
mode='max',
history_th
save_best_only=True)
model_th.fit(error_train_2,
df_error_train.Label,
epochs=10, batch_size=256,
validation_data=(error_val_2, df_error_val.Label),
callbacks=[cb_earlystopping, cb_modelcheckpoint])
plt.figure()
plt.subplot(l,2,1)
plt.plot(history_th.history['loss'], 'k-o',
label = "Entrenamiento")
plt.plot(history_th.history['val_loss'], 'k--*',
© RA-MA
292 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
label = "Validación")
plt.ylabel('Loss')
plt.xlabel('Épocas')
plt.title('Loss del modelo')
plt.legend()
plt.subplot(l,2,2)
plt.plot(np. array(history_th. history['auc']), 'k-o',
label="Entrenamiento")
plt.plot(np.array(history_th.history['val_auc']), 'k--*',
label="Validación")
plt.ylabel('AUC')
plt.xlabel('Épocas')
plt.title('Area bajo la curva ROC del modelo')
plt.legend()
plt.show()
Loss del modelo
0.40
Area bajo la curva ROC del modelo
--.- Entrenamiento
0.9
-• - Validación
0.35
0.8
0.30
0.25
u 0.7
::,
� 0.20
0.15
0.6
0.10
o.os
0.00
--.- Entrenamiento
0.5
-•- Validación
o
2
4
6
8
o
2
Épocas
4
6
Épocas
Figura 5.23. Dinámicas de entrenamiento del modelo utilizado para actuar como umbral.
Y comprobamos si mejoran, o no, los resultados:
Ejercicio
Ahora sí que podemos calcular el AUC. ¿Por qué?
def evaluate_model(model, x, y_true, verbose=True):
y_score = model.predict(x)
8
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 293
y_pred = np.where(y_score>0.5, 1, 0)
acc = accuracy_score(y_true, y_pred)
recall = recall_score(y_true, y_pred)
precision = precision_score(y_true, y_pred)
fl = 2 * (precision * recall) / (precision + recall)
auc = roc_auc_score(y_true, y_score)
if verbose:
print(«Accuracy: «, acc)
print(« Recall: «, recall)
print(« Precision: «, precision)
print(« Fl: «, fl)
print(«AUC: «, auc)
return acc, recall, precision, fl, auc
No nos podernos olvidar de cargar los pesos del mejor modelo:
model_th.load_weights("model_ae_th_2.h5")
print(« [Train]")
metrics_threshold_nn_train_2 evaluate_model(model_th,
error_train_2,
df_error_train.Label)
print(« [Validation]")
metrics_threshold_nn_val_2
evaluate_model(model_th,
error_val_2,
df_error_val.Label)
print(« [Train]")
metrics_threshold_nn_test 2
evaluate_model(model_th,
error_test_2,
df_error_test.Label)
> [Train]
> Accuracy: 0.9990218896089564
> Recall: 0.5058139534883721
> Precision: 0.8743718592964824
> Fl: 0.6408839779005525
> AUC: 0.9169841452769735
> [Validation]
> Accuracy: 0.9991974452005551
> Recall: 0.6213592233009708
> Precision: 0.8767123287671232
> Fl: 0.7272727272727272
> AUC: 0.9082010752362953
> [Test]
> Accuracy: 0.9989467113989233
294 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
> Recall: 0.4666666666666667
> Precision: 0.875
> Fl: 0.608695652173913
> AUC: 0.9054802193650917
Ejercicio
Comprueba qué pasa si se utiliza únicamente el error como entrada al
modelo. ¿Se obtiene lo mismo?
PRÁCTICA 3. Redes Generativas Adversariales
Hasta ahora solamente hemos utilizado el bucle estándar de Keras para
entrenar nuestros modelos, pero podemos modificar lo que sucede al utilizar el
método . fit ( ) para entrenar todo tipo de modelos. En esta práctica final de modelos
generativos vamos a entrenar una red generativa adversarial (GAN) para que sea
capaz de generar imágenes de pájaros. El conjunto de datos que vamos a utilizar fue
creado por Google en 2017 y consiste en una gran cantidad de dibujos sencillos hechos
a mano de diferentes temáticas. Para el ejercicio utilizaremos únicamente los dibujos
de pájaros, que pueden obtenerse directamente desde este link: https://console.cloud.
google.com/storage/browser/_details/ quickdraw_dataset/full/numpy _bitmap/bird.
npy;tab=live_object.
Las imágenes están disponibles en varios formatos: binary, numpy_bitmap,
raw y simplified, pero utilizaremos el formato numpy_bitmap porque facilita mucho
la carga y gestión de los datos. Además, es un formato que no hemos utilizado hasta
ahora y resulta útil familiarizarse con él.
Importación de las librerías
Antes de empezar a trabajar, cargamos todas las librerías que vamos a
necesitar.
import numpy as np
import matplotlib.pyplot as plt
import tensorílow as tf
from tensorílow.keras import layers
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 295
Carga de los datos
La ventaja del formato numpy_bitmap es que una vez lo hemos descargado,
podemos importar los datos de manera muy sencilla con la función load ( ) de
NumPy:
data = np.load("full_numpy_bitmap_bird.npy")
data.shape
> (133572, 784)
Exploración de los datos
Y representamos una muestra para comprobar que los datos están cargados
y procesados correctamente:
plt.figure()
plt.imshow(data[7).reshape((28,28)))
plt. colorbar()
plt.axis('off')
plt.show()
250
200
150
100
50
o
Figura 5.24. Muestra del conjunto de datos junto a su barra de color.
296 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Preprocesado de los datos
El resultado de la carga es un array de 133572 muestras que contiene
las imágenes "estiradas". Como vamos a utilizar capas convolucionales, vamos a
transformarlas para obtener las imágenes representadas como matrices de dimensiones
(28,28) con el método . reshape(). También normalizaremos los valores de los
píxeles al intervalo [-1,1]. Como hemos visto en la teoría, esto lo hacemos porque
empíricamente se ha visto que proporciona mejores resultados. Esto quiere decir que
tendremos que utilizar la función de activación tanh en la capa final. Para realizar esta
transformación tendremos que restar y dividir por el valor máximo. También añadimos
una dimensión extra al final para representar los canales de color. Esto es necesario
porque todas las capas convolucionales de Keras esperan esta dimensión de canales,
aunque sea uno. Todo este preprocesado lo podemos encapsular dentro de una función
prepare_data() y la aplicamos a todo el conjunto de datos utilizando . map(),para lo
que utilizaremos el método tf. data. Dataset. from_ tensor_slices() para pasar de
un array deNumPy a un Dataset de TensorFlow:
Ejercicio
Repite el ejercmo normalizando las imágenes al rango $[0,1]$.
¿Observas alguna diferencia? ¿Habría que cambiar algo del modelo?
def prepare_data(img):
img = tf.cast(img, tf.float32)
img_max = tf.reduce_max(img)
img = (img - img_max/2) / (img_max/2)
img = tf.reshape(img, shape=(28,28,1))
return img
dataset = tf.data.Dataset.from_tensor_slices(data)
dataset_rdy = dataset.map(prepare_data)
Aunque ya sabemos que estos tf. data. Dataset no se pueden indexar, otra
forma de obtener el primer elemento es utilizar las funciones i ter() y next() para
convertirlo en un iterable y coger el primer elemento. Esto nos permite representar el
primer elemento para comprobar que el preprocesado es correcto:
sample = next(iter(dataset_rdy))
plt.figure()
plt.imshow(sample.numpy().squeeze())
plt. colorbar()
plt.axis('off')
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 297
plt. show()
1.00
0.75
O.SO
0.25
0.00
-0.25
-o.so
-0.75
-1.00
Figura 5.25. Muestra del conjunto de datos preprocesada.
Definición del modelo
Igual que sucedía en la práctica anterior con el VAE, una GAN también
tiene dos partes completamente diferenciadas: el discriminador y el generador. Lo
que haremos será definirlos por separado y luego juntarlos para definir la lógica
de entrenamiento. De forma particular, las GAN suelen sufrir cuando hay capas
que introducen muchos ceros en los cálculos, por lo que se recomienda utilizar
activaciones Leaky ReLU en lugar de ReLU, y Average Pooling o convoluciones con
strides en lugar de Max Pooling. Esta activación no está disponible desde el parámetro
activation de las capas, así que tendremos que utilizar una activation=linear y
colocar después una capa layers. LeakyReLU( ).
Ejercicio
Comprueba qué pasa si utilizamos activaciones ReLU y Max Pooling.
Por suerte para nosotros, en esta arquitectura no hay nada que no nos permita
utilizar la API Secuencial, así que los definiremos así:
Discriminador
Su objetivo es predecir si una imagen es real o no. Esto es un problema de
clasificación binaria, por lo que podemos usar una red convolucional usual.
298 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
discriminator = tf.keras.models.Sequential([
layers.Conv2D(32, kernel_size=3, padding='same', strides=2,
activation='linear', input_shape=(28,28,1)),
layers.LeakyReLU(alpha=0.2),
layers.Conv2D(64, kernel_size=3, padding='same', strides=2,
activation='linear'),
layers.LeakyReLU(alpha=0.2),
layers.Flatten(),
layers.Dense(l, activation='sigmoid')
])
discriminator.summary()
> Model: "sequential"
> Layer (type) Output Shape Param #
> ----------------------------------------------------------------> conv2d (Conv2D) (None, 14, 14, 32)
> 320 leaky_re_lu (leakyReLU) (None, 14, 14, 32) 0
> conv2d_l (Conv2D) (None, 7, 7, 64) 18496
> leaky_re_lu_l (LeakyReLU) (None, 7, 7, 64) 0
> flatten (Flatten) (None, 3136) 0
> dense (Dense) (None, 1) 3137
> ----------------------------------------------------------------> Total params: 21,953
> Trainable params: 21,953
> Non-trainable params: 0
> ___________________________
Generador
Normalmente, la entrada del generador es un vector extraído de una
distribución normal multivariante mientras que la salida es una imagen con las
mismas dimensiones que las imágenes originales. Esta descripción recuerda a los
autoencoders variacionales, y eso es porque el generador de una GAN actúa bajo
el mismo principio que el decoder de un VAE: transforma un vector del espacio
latente en una imagen (aunque podría ser cualquier otra cosa). La idea de mapear el
espacio latente al espacio original es algo muy común en el modelado generativo,
ya que podemos manipular el espacio latente para afectar a las características de
los elementos generados. Vamos a utilizar un espacio latente de vectores de 100
elementos que modificaremos mediante capas convolucionales traspuestas hasta que
tengan la misma forma que las imágenes del conjunto de datos. La única restricción
que tenemos que es la activación de la última capa, que tiene que ser una función
tanh por la normalización que hemos elegido.
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 299
generator = tf.keras.models.Sequential([
layers.Dense(7*7*32, activation="relu",
input_shape=(100,)),
layers.Reshape(target_shape=(7,7,32)),
layers.Conv2DTranspose(filters=64, kernel_size=3, strides=2,
padding='same', activation='linear'),
layers.LeakyReLU(alpha=0.2),
layers.Conv2DTranspose(filters=128, kernel_size=3, strides=2,
padding='same', activation='linear'),
layers.LeakyReLU(alpha=0.2),
layers.Conv2D(filters=l, kernel_size=3, strides=l,
padding='same', activation='tanh'),
])
generator.summary()
> Model: "sequential_l"
> ___________________________
> Layer (type) Output Shape Param #
> ----------------------------------------------------------------> dense_l (Dense) (None, 1568) 158368
> reshape (Reshape) (None, 7, 7, 32) 0
> conv2d_transpose (Conv2DTranspose (None, 14, 14, 64) 18496
> leaky_re_lu_2 (LeakyReLU) (None, 14, 14, 64) 0
> conv2d_transpose_l (Conv2DTranspose (None, 28, 28, 128) 73856
> leaky_re_lu_3 (LeakyReLU) (None, 28, 28, 128) 0
> conv2d_2 (Conv2D) (None, 28, 28, 1) 1153
> ----------------------------------------------------------------> Total params: 251,873
> Trainable params: 251,873
> Non-trainable params: 0
> ___________________________
Entrenamiento
Como hemos visto, la arquitectura de las GANs no es especialmente
complicada y se parece mucho a un VAE, pero la dificultad está en el entrenamiento.
Podemos entrenar el discriminador creando un set de entrenamiento donde algunas
de las imágenes son imágenes reales elegidas aleatoriamente y algunas son salidas
del generador. La etiqueta será 1 para las imágenes reales y O para las generadas. Si
tratamos esto como un problema supervisado, podemos entrenar el discriminador
para que nos diga la diferencia entre las imágenes originales y las generadas.
Entrenar el generador es más dificil, ya que no hay ningún set de
entrenamiento que nos diga a qué imagen debería estar mapeado un punto del
espacio latente. En su lugar, solo queremos que la imagen generada pueda engañar
300 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
al discriminador, es decir, queremos que cuando le pasemos una imagen generada
al discriminador, el resultado sea lo más cercano a 1 posible (que se piense que
es de verdad). Esto implica que, para entrenar el generador, primero tenemos que
conectarlo al discriminador. Podemos entrenar este modelo combinado creando
batches que consistan en vectores del espacio latente de etiqueta l. Como función de
coste utilizaremos la entropía cruzada entre la salida del discriminador y el vector de
etiquetas (que serán todo unos).
Es importante no actualizar los pesos del discriminador mientras entrenamos
el generador. Si los actualizásemos, se ajustarán para predecir las imágenes generadas
como reales, pero esto no es lo que queremos. Queremos que las prediga como reales
porque el generador sea bueno, no porque el discriminador sea malo.
Con todo esto, cada paso del entrenamiento tendrá que ser:
1. Generar muestras con el generador.
2. Juntarlas con las muestras reales.
3. Poner las etiquetas correspondientes real/generada.
4. Clasificarlas con el discriminador.
5. Propagar el error al discriminador.
6. Generar otra tanda de muestras con el generador.
7. Darle a todas las muestras generadas la etiqueta correspondiente a real.
8. Clasificarlas con el discriminador.
9. Propagar el error solamente al generador.
Aunque puede parecer extraño, en el paso 7 lo que estamos haciendo es
comprobar qué imágenes generadas son las que consiguen engañar al discriminador,
y utilizamos esta información para actualizar los pesos del generador hacia esta
dirección.
Ejercicio
En esta implementación barajamos los ejemplos antes de introducirlos
en el discriminador. ¿Qué efecto crees que tiene esto? ¿Sirve para algo?
Combinando las partes: Herencia de clases
Igual que hicimos en el VAE podríamos combinar el generador y el
discriminador utilizando la API Funcional, pero como hemos visto, la lógica del
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 301
entrenamiento es muy diferente de la de un problema supervisado usual. Para este tipo
de casos podemos definir nuestro modelo heredando de la clase tf. keras. Model, lo
que nos permite modificar lo que sucede durante el entrenamiento. Para personalizar
el entrenamiento tendremos que definir un método train_step() en nuestra clase.
Este método contiene las operaciones que se ejecutan cuando aplicamos el método
. iit(). No es necesario que devuelva nada, pero podemos devolver un diccionario
con distintas métricas que se almacenarán automáticamente en el objeto History
que hemos visto en otras prácticas. La parte negativa de modificar este método es
que también tendremos que encargarnos de calcular los gradientes y actualizar los
pesos. Para hacerlo tenemos que utilizar tf. GradientTape (): todas las operaciones
que ejecutemos dentro de su contexto almacenarán sus gradientes, y luego podremos
utilizarlos para actualizar los pesos de nuestro modelo.
Otro de los trucos que se recomienda para entrenar este tipo de modelos es
suavizar las etiquetas de forma que las etiquetas ya no sean un valor exacto (O o 1 ), y
estén dentro de un intervalo. Nosotros aplicaremos una variación de 0.3.
Ejercicio
Prueba diferentes intervalos de variac1on para las etiquetas. ¿Hay
alguno que funcione mejor que otro? ¿Por qué crees que es útil este truco?
Veámoslo:
class GAN(tf.keras.Model):
def _init_(self, generator, discriminator):
super(GAN, self)._init_()
self.generator = generator
self.discriminator = discriminator
self.loss_fn_discriminator = tf.losses.BinaryCrossentropy()
self.loss_fn_generator = tf.losses.BinaryCrossentropy()
self.optimizer_discriminator= tf.optimizers.Adam(learning_rate=le-4)
self.optimizer_generator = tf.optimizers.Adam(learning_rate=le-4)
def train_step(self, data):
## Entrenamiento del discriminador##
## Generar muestras con el generador
### Samplear vectores de 100 dims y pasarlos por el generador
samples = tf.random.normal(shape=(data.shape[0], 100))
generated = self.generator(samples)
## Crear las etiquetas
true_label = tf.ones(shape=(data.shape[0],1))
fake_label = tf.zeros(shape=(generated.shape[0],1))
302 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
## Juntarlas con las de verdad
data_together = tf.concat([data, generated], axis=0)
labels_together tf.concat([true_label, fake_label], axis=0)
labels_together = labels_together + 0.3*tf.random.uniform(tf.
shape(labels_together))
# Barajarlas
shuffle_idxs = np.random.permutation(data_together.shape[0])
data_together = tf.gather(data_together, shuffle_idxs)
labels_together = tf.gather(labels_together, shuffle_idxs)
## Pasarlas por el discriminador para clasificarlas
with tf.GradientTape() as tape:
labels_pred = self.discriminator(data_together)
loss_discriminator= self.loss_fn_discriminator(labels_together, la­
bels_pred)
grads = tape.gradient(loss_discriminator, self.discriminator.traina­
ble_weights)
self.optimizer_discriminator.apply_gradients(zip(grads, self.discriminator.trainable_weights))
## Entrenamiento del generador##
## Volvemos a generar imágenes con el generador
samples = tf.random.normal(shape=(data.shape[0], 100))
## Pero ahora las etiquetamos como verdaderas
fake_label = tf.ones(shape=(generated.shape[0], 1))
## Las pasamos por el discriminador para ver cuales
## han conseguido engañarle pero ahora utilizamos el
##
gradiente para actualizar únicamente el generador
with tf.GradientTape() as tape:
generated = self.generator(samples)
labels_pred = self.discriminator(generated)
loss_generator=self.loss_fn_generator(fake_label, labels_pred)
grads=tape.gradient(loss_generator, self.generator.trainable_weights)
self.optimizer_generator.apply_gradients(zip(grads, self.generator.trai­
nable_weights))
return {"Loss_Generator":loss_generator, "Loss_Discriminator":loss_dis­
criminator}
Gracias a que hemos definido el entrenamiento dentro de train_step(),
podemos utilizar el método . fit () para entrenar el modelo:
gan = GAN(generator=generator, discriminator=discriminator)
gan.compile()
history = gan.fit(dataset_rdy.batch(256, drop_remainder=True),
epochs=lS, verbose=l)
plt.figure()
plt.plot(history.history["Loss_Generator"], 'k-o',
label="Generator")
© RA-MA
Capítulo 5. MODELOS GENERATIVOS 303
plt. plot( history. history ["Loss_Discriminator"],
label="Discriminator")
'k- - *',
plt. legend ()
plt. show()
-+- Generator
-• - Discriminator
1.5
1.0
0.5
---.....................
o.o
-0.5
o
2
6
8
'
... ,.._ __ ...... -
_ .,¡(
12
10
,,"
14
Figura 5.26. Dinámicas de entrenamiento de la GAN. Se puede ver perfectamente el "tira y afloja" entre
el discriminador y el generador.
Vemos que las dinámicas de entrenamiento son justo las que esperábamos:
un juego de tira y afloja donde generador y discriminador intentan sobreponerse
constantemente. Para terminar, mostramos algunas imágenes generadas, aunque su
interpretación queda, en algunos casos, un poco condicionada a la imaginación:
..
o
5
L
10
15
20
�
25
o
5
10
15
20
Figura 5.27. Imagen generada por la GAN.
25
304 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Figura 5.28. Rejilla de imágenes generadas por la GAN.
5.8 BIBLIOGRAFÍA
Arjovsky, M., Chintala, S., & Bottou, L. (2017). Wasserstein GAN. https://arxiv.org/
abs/1701.07875v3.
Babcock, J., Bali, R. (2021). Generative Al with Python and TensorFlow 2: Create
images, text, and music with VAEs, GANs, LSTMs, Transformer models. Packt.
Bank, D., Koenigstein, N., & Giryes, R. (2020). Autoencoders. http://arxiv.org/
abs/2003.05991.
Foster, D. (2019). Generative Deep Learning: Teaching Machines to Paint, Write,
Compase, and Play. O'Reilly.
Goodfellow, l., Pouget-Abadie, J., Mirza, M., Xu, B., Warde-Farley, D., Ozair,
S., Courville, A., & Bengio, Y. (2014). Generative Adversaria! Networks.
Communications oftheACM, 63(11), 139-144. https://doi.org/10.1145/3422622.
Langr, J., Bok, V. (2019). GANs in Action: Deep learning with Genera tive Adversaria!
Networks. Manning.
Mao, X., Li, Q., Xie, H., Lau, R. Y. K., Wang, Z., & Smolley, S. P. (2016). Least
Squares Generative Adversaria! N etworks. Proceedings ofthe IEEE International
Conference on Computer Vision, 2017-October, 2813-2821. https://doi.
org/10.1109/ICCV.2017.304.
Mirza, M., & Osindero, S. (2014). Conditional Generative Adversaria! Nets. https://
arxiv.org/abs/1411.1784v 1.
Salimans, T., Goodfellow, l., Zaremba, W., Cheung, V., Radford, A., & Chen, X.
(2016). Improved Techniquesfar Training GANs. Adv anees in Neural Information
Processing Systems, 2234-2242. https://arxiv.org/abs/1606.03498vl.
Zhu, J. Y., Park, T., Isola, P., & Efros, A. A. (2017). Unpaired Image-to-Image
Translation using Cycle-Consistent Adversaria! Networks. Proceedings of the
IEEE International Conference on Computer Vision, 2017-0ctober, 2242-2251.
https://doi.org/10.l109/ICCV.2017.244.
6
APRENDIZAJE REFORZADO
Hasta ahora, hemos conocido los distintos conceptos del Aprendizaje
Profundo y su evolución hasta la actualidad. Se han visto sus aplicaciones en imágenes,
en datos con dependencias temporales o su aplicación en modelos generativos, todo
desde una visión práctica que permita resolver problemas reales. No obstante, existe
una disciplina en el campo de la Inteligencia Artificial que ha ido ganando relevancia
en los últimos años: el Aprendizaje Reforzado. Este tipo de aprendizaje trata de
resolver problemas donde la toma de decisiones es compleja, de una forma similar
a la que lo hacen los humanos: por medio de recompensas positivas y negativas
mientras se interactúa con el entorno. Este enfoque se ve repetido innumerables
ocasiones en la naturaleza: desde que un bebé aprende a caminar, hasta en la forma
en la que los humanos aprendemos a conducir. La interacción con el entorno e ir
adquiriendo experiencia es fundamental en todas estas tareas. Uno de los cambios
de paradigma en este tipo de aprendizaje es que, al elemento que irá aprendiendo
mediante interacciones con su entorno, no se le dice qué acciones debe realizar
previamente, sino que las irá aprendiendo, mediante repetición, e irá recibiendo una
recompensa mayor, o menor, en función de sus decisiones. El objetivo fundamental
de este tipo de problemas será encontrar la forma de maximizar la recompensa
que dicho elemento obtiene. Dicho de otro modo, el fin será encontrar aquellas
combinaciones de acciones que consigan anteponer el objetivo a largo plazo frente
a pequeñas acciones con mejores beneficios a corto plazo pero que no supongan la
consecución final del objetivo del problema.
Este capítulo se estructura de la siguiente forma. En primer lugar, se
tratarán los conceptos relacionados con los elementos que conforman un sistema
de Aprendizaje Reforzado, así como la relación que existe entre cada uno de ellos.
A continuación, se tratarán los Procesos de Decisión de Markov, fundamentales
para entender los procesos de toma de decisiones en entornos con incertidumbre.
306 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Tras ello, continuaremos con la ecuación de Bellman, la función de valor del estado
y la función de valor de la acción, Q función y Q-learning, que son la base para
el Aprendizaje Reforzado Profundo, empleado en el actual estado del arte del
Aprendizaje Reforzado, para la resolución de problemas más complejos, y que se
tratarán en el último punto de este capítulo.
6.1 INTRODUCCIÓN AL APRENDIZAJE REFORZADO
Antes de comenzar con la base teórica y matemática del Aprendizaje
Reforzado cabe definir primero una serie de elementos comunes los cuales están
presentes en todo sistema de Aprendizaje Reforzado, y son los siguientes:
,., Agente: El agente es el elemento del sistema que deberá tomar una serie
de decisiones (en la mayoría de los casos bajo incertidumbre) y deberá
aprender sobre ellas para ir ganando experiencia que le permitan resolver,
cada vez de forma más eficiente, el problema al que se enfrenta.
,., Entorno: El entorno modeliza todos aquellos elementos que envuelven
al agente y a partir de los cuales, mediante su interacción, deberá tomar
decisiones. Estas decisiones le permitirán variar sus estados dentro de
dicho entorno y recibir recompensas, tanto positivas como negativas.
Además, teniendo en cuenta la forma en la que el agente interacciona con
el entorno, podemos encontrar dos tipos de modelos:
• Model-based: El agente conoce el modelo del entorno, dispone de la
información completa de dicho entorno para tomar decisiones.
• Model-free: El agente no conoce todo el modelo de su entorno, debe
tomar decisiones sin conocer toda la información alrededor suyo.
,., Estado: Un estado hace referencia a la descripción del agente dentro de
todas las posibilidades del entorno. Cada uno de estos estados vienen
definidos por una o varias variables, que el agente tendrá en cuenta para
tomar las distintas decisiones.
,., Acciones: Por cada uno de los estados, el entorno permite al agente
realizar una serie de acciones (espacio de acciones), de entre las que el
agente debe elegir una de ellas. De tal forma que cada decisión (acción)
tomada, supondrá un cambio en la percepción del entorno por parte del
agente. Por lo general, las acciones pueden clasificarse en dos grandes
grupos:
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO
307
• Acciones continuas: cuando toman valores continuos, por ejemplo,
incrementar o disminuir la temperatura.
• Acciones discretas: cuando toman valores discretos, por ejemplo,
desplazarse por un laberinto, donde entre las acciones a elegir se
puede tomar "arriba", "abajo", "izquierda" o "derecha".
,.- Recompensa: Se asigna una recompensa por cada una de las acciones que
el agente toma sobre el conjunto de acciones del entorno. La recompensa
permite reforzar el comportamiento del agente y, cada una de ellas,
contribuyen proporcionalmente en la tarea que el agente debe resolver
y que debe conseguir maximizar. La función que mapea todas estas
recompensas se denomina/unción de recompensa.
,.- Time Steps: Ciclos a través de los cuales el agente deberá iterar. Por cada
uno de estos time steps el agente toma una decisión (acción) y adquiere
una recompensa para, posteriormente, llegar a un nuevo estado.
,.- Experiencia: A la información generada en cada uno de los time steps
del agente, compuesta por la acción realizada, el estado, la recompensa
obtenida y el siguiente estado se le llama experiencia.
,.- Episodio: Al conjunto de time steps desde el inicio de la tarea hasta el
final se le llama episodio.
,.- Retorno: A las recompensas acumuladas por cada uno de los distintos
episodios se le llama retorno, y es la medida de cómo de bien o mal va el
aprendizaje del agente.
,.- Política: La política (policy) es el conjunto de estrategias o reglas que el
agente utiliza para decidir las acciones a llevar a cabo. En otras palabras,
son aquellas normas que el agente debe aprender mientras interactúa con
el entorno con el objetivo de maximizar la recompensa obtenida.
Como se ha visto, el paradigma del Aprendizaje Reforzado supone un
cambio en la forma de entrenar los modelos. En este caso no existe un conjunto de
datos de entrenamiento y otro de prueba, sino que el agente siempre interactúa con el
mismo entorno. Entonces, ¿cómo se realiza la transición desde un entorno donde el
agente aprenda y otro dónde, una vez entrenado, comience a aplicar el conocimiento
previamente adquirido? Para ello se utilizan dos nuevos conceptos: exploración y
explotación.
308 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
En la fase de exploración el agente realiza acciones con el objetivo de ir
adquiriendo el conocimiento del entorno, probando y tomando distintas decisiones
(progresivamente mejores) mientras que, durante la fase de explotación el agente
utilizará las experiencias previamente aprendidas en la exploración para maximizar la
recompensa obtenida. Cuando el agente selecciona acciones que en el pasado ya han
resultado efectivas para maximizar su recompensa se conoce como estrategia greedy.
Por otro lado, para descubrir acciones que pudieran dar una mayor recompensa, el
agente debe explorar otras acciones no greedy. Una de las problemáticas que deben
tenerse en cuenta a la hora de resolver muchos de los problemas de Aprendizaje
Reforzado, es encontrar el equilibrio entre la fase de exploración y la fase de
explotación. Para conseguirlo, existen distintos métodos. Uno de los más conocidos
es el enfoque epsilon-greedy, donde el agente debe seguir mayoritariamente acciones
greedy (acciones ya conocidas), pero donde existirán momentos, con probabilidad e,
en los que el agente explorará nuevas acciones que hasta el momento desconocía. Por
lo general, en los inicios de las tareas a resolver, el agente optará mayoritariamente
por la exploración, con el objetivo de adquirir un mayor conocimiento del entorno. A
medida que este conocimiento aumente, e irá disminuyendo e irá aumentando la fase
de explotación, con probabilidad de ocurrencia 1-c.
Finalmente, tras explicar cada uno de los elementos que componen un sistema
de Aprendizaje Reforzado, a modo de ejemplo, en la figura 6.1 puede observarse
como interactúan cada uno de ellos.
Agente
Evaluación
de la política
'
/
\
(U
e:
VI 1
QJ 1
Recompensa (t=l)
... Q.1
Recompensa (t=O)
'
El
Recompensa (t=2)
:
\
UI
QJ
� I
Recompensa
acumulada
01
-·-·-·---·-·-·-·---
1
I
•
�
)>
�
E@
Entorno
Figura 6.1. Relación entre los elementos de un sistema de Aprendizaje Reforzado.
!:!.
o,
::::,
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO 309
En los últimos años el Aprendizaje Reforzado se está aplicando para, cada
vez, un mayor número y tipo de problemas. Donde más se ha desarrollado es en
el mundo de los juegos, de hecho, fue en el año 2015 cuando AlphaGo derrotó al
3 veces campeón europeo de Go (juego chino de tablero que se originó hace 4000
años), Fan Hui, siendo la primera vez en que una máquina superaba a un humano
en una partida que terminó 5-0. Pero como decimos hay muchas otras aplicaciones
en las que el Aprendizaje Reforzado se viene aplicando, entre las que destacamos:
11"'
11"'
11"'
11"'
11"'
11"'
Drones y coches inteligentes: En esta aplicación es importante nombrar
el trabajo de Microsoft con Airsim, simulador para coches y drones,
entre otros construido sobre Unreal Engine (motor de juego creado por
la compañía Epic Games). Lo cierto es que es muy útil porque al estar
creado para coches y drones captura toda la información necesaria para
una conducción o un pilotaje inteligente. También en este punto tenemos
el coche autónomo de Google. Es evidente que la política será evitar una
colisión manteniéndose dentro de las normas del Código de Circulación,
por ejemplo.
En el área de la Robótica se están empleando este tipo de técnicas para
facilitar los trabajos de programación, así como para permitir a los robots
adaptarse con mayor facilidad a cambios en el entorno (algunos de estos
no controlados). De este modo, se consigue que los robots sean más
flexibles a la vez que mejoran en la ejecución de las tareas.
En la gestión de recursos energéticos en centros de datos el Aprendizaje
por Refuerzo permite optimizar el consumo eléctrico. El caso más
relevante desarrollado hasta ahora fue, cómo no, de la empresa DeepMind
(propiedad de Google), que consiguió reducir en el año 2016 la factura
eléctrica en los centros de cómputo de la compañía en tomo un 40%
empleando este tipo de técnicas.
En ensayos de experimentación, cuando tratas de controlar la temperatura
o el pH, la política del agente puede ser útil para tratar de reducir los
tiempos del ensayo.
Sistemas de recomendación para intentar que esas recomendaciones se
traduzcan en un incremento de la probabilidad de venta.
Aplicaciones en medicina. El objetivo es optimizar la salud del paciente
a largo plazo estableciendo para ello determinadas recompensas que
tienen que ver con las características fisiológicas del paciente, siendo
las acciones las diferentes posibilidades de tratamiento que tienen los
© RA-MA
310 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
especialistas clínicos. Esta aproximación se ha mostrado especialmente
eficaz en la dosificación de fármacos.
En cuanto a posibilidades de simulación de este tipo de sistemas de
Aprendizaje Reforzado, lo cierto es que cada vez podemos disfrutar de más entornos
Open-Source para diseñar nuestros problemas, a continuación, se nombran algunos
de ellos:
11" OpenAI Gym: es el más conocido y permite resolver tanto juegos de
Atari (Space lnvaders por ejemplo, el cual se utilizará en uno de los
laboratorios del presente capítulo), como problemas de robots o juegos
de cartas como el blackjack.
11" Gym trading: La mejor opción para trabajar en problemas de trading,
con datos de mercados reales extraídos de Quandl (plataforma que tiene
datos financieros).
11" TensorTrade: Para trabajar con problemas de trading desde los más
sencillos con una simple CPU a los más complejos con ordenadores de
altas prestaciones HPC (High Performance Computing).
11" OpensielK: Este entorno contiene una colección de problemas simulados
que han sido utilizados por la compañíaDeepMind (ajedrez, backgammon
y Go).
11" RecoGym: Implementa entornos para desarrollar
recomendación basados en Aprendizaje Reforzado.
sistemas
de
6.2 ELEMENTOS MATEMÁTICOS A TENER EN CUENTA EN EL APRENDIZAJE
REFORZADO
Para llegar a los diferentes algoritmos del Aprendizaje Reforzado es necesario
comenzar con lo que se conoce como Procesos de Decisión de Markov (MDP por
sus siglas en inglés, Markov Decision Process) que están constituidos por una tupla
que contiene varios elementos:
11" Las acciones que el agente puede realizar y que, generalmente, están
definidas como A. Si pensamos en un ejemplo de navegación por un
laberinto estas acciones serían dirigirse al norte/sur/este/oeste en una
determinada situación de ese laberinto.
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO
311
11" Los estados que el agente puede visitar, codificados matemáticamente
como S. Estos estados en el ejemplo anterior serían las diferentes
situaciones fisicas del laberinto.
11" La modelización de las recompensas R obtenidas por el agente al realizar
una determinada acción. Esta función queda definida por la expresión
R(S, a) que define la recompensa obtenida si se encuentra el agente en
el estado s y realiza la acción a. Relacionado con estas recompensas se
encuentra lo que se conoce como retomo a partir de un cierto tiempo
t, definido como G t = Tt+l + Tt+ z + Tt+3 + · · · + Ty siendo rk las
recompensas que se obtienen en los diferentes instantes. Para asegurar
la convergencia de dicho retomo en situaciones donde se tienen bucles
entre estados se introduce un factor de descuento, y, siendo el retomo
igual a G t = Tt+l +y· Tt + z + y2 • Tt+3 + ... + yT -l • ry; de la anterior
- rt+l + y . Gt+l.
expresión se ve claramente que G t -
11" La llamada regla de transición T, que define la distribución de probabilidad
de los estados futuros en el tiempo t+ 1 dado el estado actual y la acción
ejecutada en el tiempo t. Esta probabilidad quedaría definida por
T{j = P(s t +l = j I St = í, a t = a) donde se da la probabilidad de llegar
en el instante t+ 1 al estado j a partir del estado i en el instante anterior
cuando se realiza la acción a. Es importante destacar la propiedad de
Markov que aparece en la expresión de p.a,
; el estado en el instante t+ 1
l]
sólo depende del estado en el instante anterior. Al ser una probabilidad se
cumple la propiedad L j T{j = L j P (st+1 = j I St = i, at = a) = 1.
Por todo lo comentado un MDP quedaría totalmente definido por: (A, S, R, 1).
Dentro de los diferentes conceptos matemáticos que el Aprendizaje
Reforzado utiliza se encuentran las funciones valor que permiten estimar los valores
de recompensa que tendremos a largo plazo para los estados y las acciones que el
agente elige. Dentro de las funciones valor podemos encontrar dos tipos: la función
valor del estado (función V) y la función valor de la acción (función Q). La función
valor del estado, conocida como función V, muestra cómo de bueno, o malo, es un
determinado estado al seguir una determinada política denominada rr. Obtiene el
valor esperado de la recompensa total que el agente obtendría desde el estado en
el que se encuentra hasta el objetivo final siguiendo esa política rr. Sucede en la
práctica que esta función V no siempre es eficaz, puesto que los valores calculados
para cada estado pueden variar mucho entre los distintos episodios dependiendo de
la política elegida por el agente (al calcularse de forma separada para cada episodio).
En general, para resolver esta problemática, se suele calcular la esperanza (promedio)
312 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
del retomo obtenido al final de muchos episodios. Matemáticamente se expresa de
la siguiente manera:
Vrr(s) = Err[Gt I St = s] = Err[LI=o y k · rt+k+1 1st = s] = Err[Gt+1 +Y· rc+1I St = s]
Ecuación 6.1
Donde Gtes el retomo con descuento desde el instante t dado un estado s. Es
decir, el objetivo del agente será maximizar la recompensa obtenida, priorizando el
largo plazo, frente a las recompensas inmediatas que se puedan tener. Como se ha
dicho anteriormente, rr hace referencia a la política y y ( con un valor entre O y 1) hace
referencia al factor de descuento. La política óptima sería, por tanto, la asignación
de estados a acciones que maximiza la suma de los refuerzos cuando se empieza en
un estado arbitrario y se realizan acciones hasta alcanzar un estado terminal; se tiene
pues que el valor de un estado depende de la política. La política óptima vendría
entonces dada por el máximo sobre todas las políticas, esto es:
V.(s) = maxVn(s)
Ecuación 6.2
La ecuación 6.2 queda definida para todos los posibles estados s. Destacar
que, en esta función V, no se considera la acción realizada por el agente, cuestión que
si considera la siguiente función que vamos a definir. La función valor de la acción,
conocida comofunción Q, muestra cómo de bueno o malo es tomar una determinada
acción dado un determinado estado en el que el agente se encuentra. De igual forma
que sucede para la función valor del estado, en la función valor de la acción surge
la misma problemática que en la variación de los valores Q en cada episodio. Para
solventarlo, se tiende a aplicar la misma solución que en el cálculo de la función V;
calcular la esperanza matemática o valor esperado de dicha función para diferentes
episodios. Por su parte, la función Q sigue una expresión matemática idéntica a la
función valor de los estados, teniendo en consideración tanto el estado en el que se
encuentra el agente como la acción seleccionada:
Qrr(s, a)= Err[Gt I St = s, ªt = a] = Err
[t ,l k=O
rt+k+l I St = s, ªt = a]
Ecuación 6.3
es el retomo con descuento en el instante t; en este caso se define comenzando desde
un estado s y tras realizar una acción a dada por la política rr· Siguiendo el mismo
razonamiento que para la función valor de estado la política óptima utilizando la
función valor de la acción quedará definida por:
Q.(s,a) = maxQ rc (s,a)
Ecuación 6.4
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO 313
Las ecuaciones 6.2 y 6.4 se relacionan mediante la igualdad
V.(s) = maxQ.(s, a).
Una vez conocida la función valor del estado y la función valor de la acción,
vamos a ver cómo se relacionan entre ellas. Definimos la función rr(a = a t ls = S t )
que proporciona la probabilidad de realizar la acción a, en el estado s1 (y,
evidentemente, ambas en el tiempo t). El nombre de la función, rt, hace referencia a
la política seguida. Con esta definición y las de las funciones de estado y acción se
tiene la relación definida por la figura 6.2.
Por otro lado, la función valor del estado se puede expresar como la suma de
todas las funciones valor de la acción que salen de un estado s, multiplicadas por la
probabilidad de ocurrencia de cada una de estas acciones:
-------', s '
:+······ Vrr (s)
: ◄-------- rr(als)
1
1
1
, _-
Figura 6.2. Relación entre la función valor del estado y la función valor de la acción.
La figura anterior pone de manifiesto que la aportación al retomo dentro
de cada estado debido a cada acción viene ponderada por la probabilidad de cada
acción (básicamente estamos aplicando la definición de valor esperado de cualquier
cantidad estadística), así pues estas dos funciones quedan relacionadas por la
siguiente expresión:
Vrr (s) =
¿ n(a Is)· Q (s, a)
a
rr
Ecuación 6. 5
Relacionado con todo lo visto anteriormente se encuentra el concepto de
programación dinámica, que fue introducido por Richard E. Bellman en la década
de 1950 y que proporciona un marco para resolver problemas dinámicos. Por un
lado, el concepto "dinámica" implica un proceso con dependencias temporales,
secuenciales. Por otro lado, "programación" se entiende como la búsqueda de una
política óptima definida por las ecuaciones 6.2 y 6.4. No obstante, esta forma de
interactuar con el entorno no está exenta de problemas. Una característica de la
314 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
programación dinámica es la necesidad de conocer el entorno al completo: tanto las
recompensas obtenidas como la función de transición. Otra de las características y
limitaciones de la programación dinámica es que la solución propuesta debe poder
descomponerse en problemas más simples. Esto implica que el número de dichas
particiones más sencillas debe ser finito y, en muchos casos, estas condiciones no se
cumplen.
La ecuación de Bellman queda definida de la siguiente forma (utilizando
funciones de valor):
Ecuación 6.6
V.(s) = max[R(s, a) +y· V.(s ' )]
Donde V.(s) es el valor óptimo de un determinado estado, mientras que
V.(s ') es el valor óptimo del siguiente estado después de tomar una determinada
acción. Por otro lado, como se ha comentado anteriormente, R(s,a) es la recompensa
que recibe el agente tras elegir una acción a desde un estado s. Básicamente lo que
nos indica la ecuación de Bellman es que tenemos que considerar aquellas acciones
que maximizan la suma de la función recompensa y la función valor en el siguiente
estado. La ecuación 6.6 se aplica en entornos deterministas, si se tienen entornos
estocásticos es necesario introducir las probabilidades de transición previamente
definidas llegándose a:
V.(s) = max¿s,TSS , · [R( s,a) + y ·V.(s') ]
ª
a
Ecuación 6. 7
En lo que sigue nos centraremos en la función valor de la acción y, en este
caso, la ecuación de Bellman en entornos deterministas queda definida por:
Q.(s,a) = [R(s,a)+y· �p:xQ.(s',a')]
Ecuación 6. 8
6.3 MÉTODOS DE APRENDIZAJE POR DIFERENCIAS TEMPORALES: SARSA Y
Q-LEARNING
Es importante resaltar que todo lo comentado anteriormente supone un
conocimiento completo del entorno, esto es, se necesita un conocimiento la tupla
(A,S,R,T). Sin embargo, hay muchos problemas donde esto no sucede y tendremos
que aplicar las técnicas de Aprendizaje Reforzado (RL) en las que el objetivo es
obtener una política que maximice la suma de recompensas a largo plazo mediante
una interacción directa (ensayo-error) con un entorno desconocido e incierto. Antes
de entrar a hablar de los modelos y las aplicaciones del Aprendizaje Reforzado
Profundo, tema principal de este capítulo, es importante entender las bases que
definen muchos de los modelos más empleados en la actualidad: los métodos de
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO
315
aprendizaje por diferencias temporales y, más concretamente, el método Q-learning.
Resaltar igualmente que ésta es una de las técnicas que se aplican en RL y que nos
centraremos en ella dado que se quieren dar ejemplos de uso de modelos neuronales
profundos en este campo, pero no es un libro especializado en RL. Se recomienda al
lector interesado consultar la bibliografía dada a final del capítulo. La parte positiva
de la elección de estas técnicas específicas es que son libres y no están asociadas a
modelos.
El aprendizaje por diferencias temporales fue propuesto en un artículo de
investigación por Richard S.Sutton en el año 1988, llamado Learning to Predict by
Methods of Temporal Differences. No obstante, para entender de forma correcta
cómo funciona el método de Q-learning es conveniente dar un paso atrás y conocer
primero el funcionamiento de SARSA debido a su funcionamiento similar. El método
SARSA viene de la repetición de la secuencia estado, acción, recompensa, estado,
acción, por sus siglas en inglés. El funcionamiento básico es el siguiente: el agente
se sitúa en un primer estado s 0 y toma una determinada acción a 0. Tras esta acción,
se sitúa en un nuevo estado, s 1, y recibe una recompensa r1. A partir de ahí, el agente
tomará, otra vez, una nueva acción a 1. Tras haber terminado esta primera secuencia
(s 0 , a 0,r1,s i,aJ, se actualiza la tabla Q para Q(s0 , a0 ), sin necesidad de esperar hasta
el final del episodio para su actualización. Es importante destacar que, en la elección
de la acción entra el dilema comentado al principio del capítulo de exploración­
explotación. Existen varios mecanismos para escoger la siguiente acción de forma
probabilística considerando el número de iteraciones que se tienen, el valor de la
recompensa en el estado actual, la evolución de la recompensa, etc. Este elemento de
búsqueda de nuevas acciones es esencial y crítico para obtener soluciones óptimas.
Para el cálculo de Q(st , at ) se utiliza la siguiente expresión matemática:
Donde Q(s t , at ) es el valor de Q asociado al estado s y la acción a en
el instante t, a es el parámetro que indica el tamaño del paso y y es el factor de
descuento considerado.
El método Q-learning es muy similar al método SARSA. La principal
diferencia que existe es que Q-learning selecciona aquella acción que maximiza el
valor Q. El funcionamiento es el siguiente; el agente se sitúa en un estado s 0 y toma
una acción a 0. A continuación, recibirá una recompensa r1, situándose en un nuevo
estado s 1. Para la selección de la siguiente acción a 1 por parte del agente se empleará
una política epsilon-greedy, al igual que sucede en el método SARSA. No obstante,
la principal diferencia con el método SARSA radica en la forma de actualizar los
Q-valores, pues la expresión matemática empleada en este caso introduce algunas
vanac10nes:
316 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Ecuación 6.10
Es instructivo escribir la ecuación 6.1 O de la siguiente forma:
Ecuación 6.11
Se observa en la ecuación 6.11 dos términos que corresponden al valor anterior
de Q(s, a) pesado por el valor a y, por otra parte, el término que se correspondería
con el valor óptimo según la ecuación de Bellman, ecuación 6.8.
En el caso de Q-learning la tabla Q-table se actualiza antes de tomar la
acción, tal y como se observa en el siguiente pseudocódigo, definido en la figura 6.3.
Definir a e (O, 1] y E> O
Inicializar Q (s, a) para todos los pares estado-acción de forma
arbitraria, excepto los Q asociados al estado final.
..--------------+1 Por cada episodio
Inicializas
Elige a des utilizando la política epsilon-greedy derivada de Q.
Por cada time step del episodio. 1+---------,
Tomar una acción a, almacenar ys1.
Selecciona una acción a' des' usando la política epsilon-greedy derivada de Q.
Calcula Q como:
Q(s, a) .- Q(s, a)+ a[r + ymaxa Q(s', a) - Q(s, a)]
SI
NO
Figura 6.3. Diagrama de flujo para el algoritmo Q-learning.
Una vez el algoritmo definido en la figura 6.3 converge, se aplica la siguiente
expresión para obtener la política óptima:
rr*(s) = arg maxQ(s, a)
Ecuación 6.12
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO 317
6.4 APRENDIZAJE REFORZADO PROFUNDO
El Aprendizaje Reforzado Profundo es el estado del arte dentro del campo
del Aprendizaje Reforzado. Estas nuevas técnicas tratan de combinar el campo de
las redes neuronales profundas con las metodologías de Aprendizaje Reforzado
descritas a lo largo de este capítulo. Pero realmente, ¿cuál es la diferencia en aplicar
modelos neuronales profundos al Aprendizaje Reforzado?, pues bien, a lo largo de
este capítulo hemos visto que en este tipo de aprendizaje un agente aprende por
ensayo-error (el famoso compromiso entre exploración-explotación) con el principal
objetivo de maximizar su recompensa; la incorporación de modelos profundos a
este tipo de aprendizaje facilita la codificación del conocimiento ya existente para
aplicarlo a nuevos datos. El modelo no necesitará guardar todos los pares estado­
acción, sino que el modelo podrá generalizar perfectamente a estados que no ha
visto anteriormente usando la modelización (y alta capacidad de generalización)
ofrecida por los modelos neuronales profundos. Además, combinando el algoritmo
Q-learning con redes neuronales profundas, se resuelven problemas en entornos con
acciones continuas y con un número de estados que puede tender a infinito. Así se
consigue subsanar la problemática presentada por los métodos Q-learning clásicos,
donde el número de estados y acciones debía ser mucho más limitado. Otra de las
ventajas de la aplicación de métodos neuronales profundos frente a los métodos
tabulares basados en Q-learning es que, en el caso de los primeros, y para un único
estado, la red neuronal es capaz de calcular todos los Q-valores asociados a todas
las posibles acciones en ese estado, mientras que el Q-learning clásico calculaba un
valor para cada par estado-acción por cada instante del tiempo.
En este apartado se tratarán algunos de los modelos más empleados en
el campo del Aprendizaje Reforzado Profundo derivados del Q-learning visto
anteriormente. Concretamente, se introducirá el algoritmo Deep Q-Network (DQN)
y sus variaciones; Doble DQN, Dueling DQN y DQN Rainbow.
En el primer algoritmo planteado, DQN, el objetivo es parametrizar
la función valor de la acción mediante un modelo neuronal profundo, esto es,
Q(s,a,w) � Q*(s,a). Su aproximación es usar la ecuación de Bellman para la
función valor de la acción (expresión 6.8); más concretamente usa el lado derecho
de la expresión como target de la red planteada y el lado izquierdo como salida de la
red planteando una función de pérdidas de tipo mínimos cuadrados. El objetivo será
calcular los Q-valores para cada par estado-acción mediante la ecuación de Bellman
y utilizarlos, posteriormente, para entrenar la red neuronal de forma supervisada.
No obstante, este método no está exento de problemas. Anteriormente a la aparición
del algoritmo DQN se habían utilizado ya modelos neuronales con el mismo papel
de aproximadores de funciones pero se tenían problemas de inestabilidad en el
entrenamiento que evitaban la convergencia del modelo neuronal. Estos problemas
de convergencia se debían a dos razones fundamentalmente:
© RA-MA
318 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
l. Correlación entre muestras. El algoritmo de descenso por gradiente
empleado en la actualización de los pesos de la red neuronal funciona de
mejor forma con datos procedentes de eventos independientes y, dada la
naturaleza de los problemas de Aprendizaje Reforzado donde el agente
sigue una serie de acciones y estados continuos en el tiempo (con alta
correlación), pueden existir problemas en el aprendizaje del agente. Para
solventar este problema, se propuso la técnica llamada experience replay.
Esta técnica trata de agrupar tuplas de experiencias con la estructura
estado-acción-recompensa-acción en un búfer de memoria a medida que
el agente va interactuando con el entorno. El objetivo es ir almacenando
experiencias en la memoria y luego seleccionar las tuplas de forma
aleatoria para el entrenamiento de la red; de esta forma se reduce la
dependencia entre muestras.
2. Valores deseados no estacionarios. Las dificultades en la generación y
evaluación de datos se deben a que las funciones que se intentan aprender
están estrechamente acopladas al bucle del proceso de decisión. El
intercambio de datos entre un agente y un entorno es interactivo y el
proceso está intrínsecamente limitado por el tiempo necesario para que
un agente actúe y para que un entorno haga una transición al tiempo que
se actualiza el modelizador. Para solventar esto, se propone introducir
una nueva red neuronal a la que se le llama Target Network. Esta segunda
red permite un entrenamiento más suave. Los valores Q que predice la
Target Network se emplearán para entrenar la red neuronal principal.
Además, esta segunda red no se entrenará, sino que, cada cierto número
de iteraciones tomará los pesos de la red neuronal principal.
En cuanto al funcionamiento, el agente comenzará desplazándose por el
entorno y se almacenarán experiencias (tuplas) del agente en el búfer de memoria
(experience replay) a través de la interacción del agente con el entorno. Una vez
lleno el búfer de memoria, comenzará la siguiente fase del algoritmo. En la segunda
fase se seleccionan tuplas de la memoria de forma aleatoria para entrenar la red
neuronal, de aquí se obtiene, para cada tupla, las predicciones Q proporcionadas
por la red; Q(si, ai) y, por otra parte, los valores Q esperados definidos como;
ri + ymax Q ( s' i, ai). Con estos dos valores, se calcula la función de pérdidas que,
ª'
en el caso del DQN, equivale a la diferencia entre los valores Q proporcionado por
la Target Network y los valores predichos por la red principal. Con esta diferencia,
se utiliza el algoritmo de retropropagación donde la red neuronal ajusta los pesos en
base a los errores calculados. Se tendría la siguiente función de pérdidas:
L(w) = !E [ (r + ym q.xQ(s', a', e-) - Q(s, a, 0)
ª
f]
Ecuación 6.13
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO
319
La figura 6.4 muestra el proceso seguido por el algoritmo DQN.
TARGET NETWORK
cada k iteraciones
Experiencias
r1 , · · · , r Nreplay
Figura 6.4. Esquema del algoritmo DQN. Los parámetros de la Target Network sólo se actualizan cada k
iteraciones con los parámetros de la red Q principal y se mantienen fijos entre actualizaciones de ésta.
El siguiente diagrama de flujo muestra todos los pasos del proceso explicados
anteriormente:
Inicializar red neuronal Q principal.
Inicializar red neuronal Q secundaria.
Inicializar memoria del experience replay D.
Generar un nuevo E (varía su valor en con técnica E·decay).
Desde el estado s, elegir la acción a tomar, obtener la recompensa y llegar al nuevo estado s'.
Almacenar la transición en la memoria del experience replay.
NO
Seleccionar batch de experiencias aleatorio
Calcular Q(s1, a 1)
Calcular max Q(s¡, a¡)
NO
SI
NO
Calcular función de pérdida.
Actualizar Q con gradiente descendente,
minimizando función de pérdida
Cada N iteraciones, copiar parámetros de red
principal Q a red secundaria Q
SI
Figura 6.5. Diagrama de flujo del algoritmo DQN.
NO
© RA-MA
320 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
Uno de los problemas generados por el algoritmo DQN, es la sobreestimación
de las recompensas obtenidas por el agente debido a las desviaciones en el cálculo
de las acciones, que generan inestabilidad en el entrenamiento y que condiciona
la política obtenida, tal y como se indica en el artículo de investigación donde se
publica el algoritmo Double DQN, Deep Reinforcement Learning With Double
Q-learning. Para conseguir resolver el problema, un grupo de investigación de la
empresa DeepMind (propiedad de Google) desarrollaron el algoritmo Double DQN.
Este modelo propone desacoplar la selección de las acciones de la evaluación de
éstas, consiguiendo reducir de este modo las sobreestimaciones. Aquí se plantea
como función de coste lo siguiente:
L(w) = IE [ (r + ymªqxQ (s', arg�axQ(s', a', 0), 0- )- Q(s, a, 0))
2
] Ecuación 6.14
Si comparamos las ecuaciones 6.13 y 6.14 se observa que el cambio del
Doble DQN con respecto a su predecesor es que las acciones son escogidas por la
red Q actual, pero son evaluadas por la Target Network.
DQN.
En el siguiente diagrama se resumen todos los pasos del algoritmo Doble
Inicializar la red neuronal principal
Inicializar la red neuronal objetivo
Definir el tamaño del buffer de memoria de experiencias.
Fijarr « 1
Observar el estados y seleccionar una acción a de la política rr.
Ejecutar la acción a y desplazarse el siguiente estados'. Tomar la
recompensa r.
Almacenar la experiencia en el buffer de memoria: (s,a,�s')
NO
,---->! Extraer tupla del buffer de memoria (s,a,r,s')
Calcula el Q valor como:
Q"(s,,a,) "'r, + yQ9(Si+1,ar9max.Qo,(s,+1,a'))
Calcula el siguiente paso del gradiente como:
(Q'(s,,a,) - Qo (s,, a,)) 2
Actualizar los parámetros de la red objetivo:
!J' <- T • IJ + (1 - T) • !J'
NO
NO
SI
Figura 6.6. Diagrama de flujo para el algoritmo Double DQN.
SI�
�
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO 321
Una mejora del algoritmo anterior es el Dueling DQN donde se utiliza lo que
se conoce como función de ventaja definida como:
Ecuación 6.15
Esta cantidad describe lo buena que es la acción a, en comparación con
el rendimiento esperado cuando se sigue directamente la política re. Esta nueva
magnitud aparece porque hay una diferencia entre el valor de un estado particular
s y las acciones que proceden de ese estado. A modo de ejemplo consideremos un
juego en el que, a partir de un determinado estado s', todas las acciones conducen
a que el agente muera y termine el juego. Este es un estado intrínsecamente de
bajo valor ya que se acaba en una situación no deseada independientemente de las
acciones realizadas. No tiene sentido invertir tiempo y recursos en encontrar las
mejores acciones. En ese estado, los valores Q deberían basarse únicamente en la
función de valor V y este estado debería evitarse. El caso inverso también habría que
considerarlo: algunos estados son muy valiosos, independientemente de los efectos
de las acciones posteriores. En este algoritmo se usa un modelo neuronal donde se
estiman dos cantidades:
l. La función valor del estado s, V(s), es decir, qué recompensa va a ser
capaz de obtener el agente.
2. La segunda cantidad estimada es la función de ventaja A, que indica
cómo de buena es una determinada acción si se compara con las otras
acciones posibles.
Tras estas dos divisiones, se vuelve a combinar la red para conseguir una
única capa de salida, la cual permitirá estimar los valores Q (se ve de la ecuación
6.15 que es la suma de ambas cantidades). La figura 6.7 es un esquema del modelo
inicial Dueling DQN.
CNN
Figura 6.7. Esquema del modelo Dueling DQN original; FC hace referencia a una etapa de "aplanado"
(flattened).
322 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
El artículo Dueling Network Architectures far Deep Reinforcement Learning
muestra una problemática a este procedimiento de suma ya que no es posible
conseguir un buen entrenamiento de la red sumando simplemente los valores V y
A, puesto que tendríamos infinitas posibles soluciones que, combinadas, den como
resultado el valor de Q. De la ecuación 6.15 se tiene:
maxA(s, a) = max [Q(s, a) - V(s)]
aEA
aEA
Ecuación 6.16
Anteriormente se ha visto que V(s) = maxQ(s, a) por lo que se llega a
ma x A( s, a) = O_ De esta manera se fuerza a Q � su valor más alto para que sea
aE A
igual al valor de V, consiguiendo con esto que el valor más alto de la función de
ventaja A sea cero. Con esta técnica se obtiene exactamente el valor de Vy posibilita
calcular todas las ventajas a partir de ahí, matemáticamente se expresa como:
Q (s, a; 0, a, /3) = V(s; 0, /3) + [A(s, a; 0, a) - max(A(s, a; 0, a))]
Ecuación 6.17
Donde a, f3 y 8 son los parámetros de los diferentes modelos neuronales
profundos implicados en el cálculo de las diferentes cantidades que nos interesan.
Sin embargo, para mejorar el modelo, el artículo propone un cambio en esta forma
de calcular los valores Q evitando la función no diferenciable máximo y aplicando
en su lugar el promedio como aparece en la siguiente ecuación:
Q (s, a; 0, a, /3) = V (s; 0, /3) + (A(s, a; 0, a) - : L a , A(s, a; 0, a))
Ecuación 6.18
1 1
Intuitivamente, esta arquitectura aprende qué estados son ( o no) valiosos, sin
tener que aprender el efecto de cada acción para cada estado. Esta estimación de Q
puede integrarse en los algoritmos anteriores.
Para finalizar esta sección vamos a describir un procedimiento que combina
diferentes algoritmos para llegar a un "super-algoritmo" que recoja las ventajas de
todos ellos; este macro-algoritmo se conoce como Rainbow DQN y considera los
siguientes algoritmos/modelos algunos de ellos ya comentados anteriormente:
11""
11""
11""
Doble DQN. Como se ha comentado anteriormente este algoritmo
desacopla la elección de las acciones con su evaluación.
Repetición prioritaria (Prioritized replay). En la DQN original
(anteriormente explicada) los elementos almacenados en la memoria de
experiencias se muestrean de forma uniforme. Esta variación propone
que se muestreen con mayor frecuencia aquellas transiciones que puedan
enseñar más y mejor al modelo.
Dueling DQN. También ya comentado, en este algoritmo se separa el cálculo
de la función Q en la suma de las funciones V y A para intentar centrarse
sólo en la búsqueda de los estados/acciones que más pueden aportar.
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO 323
11" Predicción mu/ti-paso. En esta aproximación se cambia la función de
coste definida en la ecuación 6.12 por la siguiente expresión:
L(w) = JE
[(r n + y n · maqxQ (s',arg�axQ(st+n,a',0),e-)- Q(st,a,e)f]
Ecuación 6.19
Se aprecia que los dos estados que aparecen en la ecuación 6.19 no son
consecutivos lo que impacta en la recompensa y en el factor de descuento.
11" Redes ruidosas (noisy nets). Se proponer sustituir la política de búsqueda
clásica (r-greedy) por el uso de este tipo de redes.
11" Retorno probabilístico. Toma un único valor de recompensa de salida y
lo sustituye por una distribución de recompensas entre N valores.
Este algoritmo, Rainbow DQN, ha demostrado ser una excelente alternativa
para ser aplicado en problemas de Aprendizaje Reforzado Profundo.
6.5 LABORATORIO
PRÁCTICA l. Resolución del problema CartPole de gym usando DQN
Introducción
El problema consiste en un poste unido por un brazo no accionado a un
carro, que se mueve sobre una pista sin fricción. El objetivo es mantener el brazo en
posición vertical. Esto se controla aplicando una fuerza al carro de +1 o -1. Por cada
paso de tiempo que el brazo se mantiene vertical, se proporciona una recompensa de
+ l. Cada episodio termina cuando el brazo está a más de 15 grados de inclinación o
se mueve más de 2,4 unidades desde el centro.
Eplsode 2
Figura 6.8. Estado ejemplo del entorno CartPole.
324 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
El objetivo es construir un modelo de inteligencia artificial basado en la
técnica de aprendizaje por refuerzo vista en este capítulo deDoubleDeep Q-Learning.
Definición del entorno, variables y sus valores
En este caso vamos a trabajar con un entorno importado de la librería Gym de
OpenAI. Estos son unos entornos predefinidos que nos ofrece OpenAI para probar
nuestros modelos de aprendizaje reforzado.
Implementación de la solución
En primer lugar cargamos las librerías que vamos a utilizar.
import random
from collections import deque
from IPython.display import clear_output
import numpy as np
import matplotlib.pyplot as plt
import tensorílow as tf
import tensorílow.keras.layers as layers
from tensorílow.keras.optimizers import Adam
import gym
A continuación, vamos a diseñar nuestro modelo deDoubleDeep Q-Learning.
Se utiliza una red neuronal para aproximar la función Q, la memoria de repetición
y la Q network del objetivo. El estado es la entrada a la red mientras que la salida
corresponde al valor Q de cada acción. Pasado un intervalo de tiempo, se realiza la
actualización del modelo del objetivo para que sea igual al modelo principal. Para
obtener la acción se utiliza la política épsilon-greedy. La clave del modelo Double
DQN es que la selección de la acción se realiza desde el modelo principal mientras
que la actualización es desde el modelo del objetivo.
Para realizar todo este proceso, creamos una clase llamada DDQNAgent.
class DDQNAgent:
def _init_(self, state_size, action_size):
self.state_size = state_size
self.action_size = action_size
self.memory = deque(maxlen=2000)
self.gamma = 0.95
# ratio de descuento
self.epsilon = 1.0 # ratio de exploración
self.epsilon_min = 0.01
self.epsilon_decay = 0.995
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO 325
self.learning_rate = 0.001
self.model = self._build_model()
self.target_model = self._build_model()
self.update_target_model()
def _build_model(self):
states = tf.keras.Input(shape = (self.state_size,))
x = layers.Dense(64, activation= 'sigmoid')(states)
y = layers.Dense(32, activation= 'sigmoid')(x)
q_values = layers.Dense(self.action_size, activation= 'linear')(y)
model = tf.keras.Model(inputs = states, outputs = q_values)
model.compile(loss='mse', optimizer=Adam(lr=self.learning_rate))
return model
def update_target_model(self):
self.target_model.set_weights(self.model.get_weights())
def action(self, state):
if np.random.rand() <= self.epsilon:
return random.randrange(self.action_size)
else:
act_values = self.model.predict(state)
return np.argmax(act_values[0])
def sample(self, state, action, reward, next_state, done):
self.memory.append((state, action, reward, next_state, done))
if self.epsilon > self.epsilon_min:
self.epsilon *= self.epsilon_decay
def experience_replay(self, batch_size):
minibatch = random.sample(self.memory, batch_size)
update_input = np.zeros((batch_size, self.state_size))
update_target = np.zeros((batch_size, self.state_size))
action, reward, done = [], [], []
far i in range(batch_size):
update_input[i] = minibatch[i][0]
action.append(minibatch[i][l])
reward.append(minibatch[i][2])
update_target[i] = minibatch[i][3]
done.append(minibatch[i][4])
target = self.model.predict(update_input)
target_next = self.model.predict(update_target)
target_val = self.target_model.predict(update_target)
far i in range(batch_size):
if done[i]:
target[i][action[i]] = reward[i]
else:
a = np.argmax(target_next[i])
target[i][action[i]] = reward[i]+self.gamma*(target_val[i][a])
self.model.fit(update_input, target,
batch_size=batch_size,
epochs=l,
verbose=0)
326 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Una vez definido nuestro modelo, pasamos a la etapa de entrenamiento. Para
ello importamos de la librería Gym el entorno CartPole-vl y definimos el agente,
que será la clase DDQNAgent creada anteriormente.
env = gym.make('CartPole-vl')
state_size = env.observation_spaee.shape[0]
aetion_size = env.aetion_spaee.n
agent = DDQNAgent(state_size, aetion_size)
done = False
seore = 0
bateh_size = 32
EPISODES=300
rewards = []
seores = []
A continuación, se entrena el modelo. Los pasos más importantes que hay
que tener en cuenta son los siguientes:
,. Obtenemos la acción para el estado actual.
,. Si una acción provoca el fin de un episodio se aplica una penalización
de -10.
,. En cada episodio se actualiza el modelo del target para que sea igual al
modelo principal.
env = gym.make('CartPole-vl')
state_size = env.observation_spaee.shape[0]
aetion_size = env.aetion_spaee.n
agent = DDQNAgent(state_size, aetion_size)
done = False
seore = 0
bateh_size = 32
EPISODES=300
rewards = []
seores = []
for e in range(EPISODES):
state = env.reset()
state = np.reshape(state, [1, state_size])
for time in range(100):
aetion = agent.aetion(state)
next_state, reward, done, _ = env.step(aetion)
reward = reward if not done else -10
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO 327
rewards.append(reward)
score += reward
scores.append(score)
next_state = np.reshape(next_state, [1, state_size])
agent.sample(state, action, reward, next_state, done)
state = next_state
if done:
agent.update_target_model()
clear_output(True)
fig = plt.figure()
ax = fig.add_subplot(lll)
plt.plot(np.arange(len(scores)), seores, 'k--')
plt.ylabel('score')
plt.xlabel('Episode #')
plt.show()
print("episode: {}/{}, score: {}, e: {:.2}"
.format(e, EPISODES, score, agent.epsilon))
break
if len(agent.memory) > batch_size:
agent.experience_replay(batch_size)
if e% 10 == 0:
agent.model.save_weights("cartpole_ddqn.hS")
Se obtiene la siguiente gráfica de puntuaciones según la recompensa obtenida
en cada uno de los episodios:
1500
1250
1000
750
500
250
o
o
1000
2000
Episode #
3000
4000
Figura 6.9. Evolución de la recompensa obtenida por el modelo.
328 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
PRÁCTICA 2. Jugando a Space Invaders con DQN
Introducción
En este ejercicio vamos a trabajar con un aprendizaje Deep Q Network (DQN)
para jugar a "Spaces Invaders-VO". El algoritmo Q-Learningpara el aprendizaje por
refuerzo se modifica para trabajar en estados que son (imágenes) de dimensiones
extremadamente altas utilizando una red neuronal convolucional.
En Space Invaders VO, un agente interactúa con un entorno E en una secuencia
de acciones, observaciones y recompensas. Durante el paso de tiempo, el agente
elige una acción del conjunto de posibles, siendo para cada estado A= {l,2 ...L}. El
emulador aplica la acción al estado actual, y lleva el juego a un nuevo estado. La
puntuación del juego se actualiza y se devuelve una recompensa r al agente.
Una explicación del funcionamiento de Space Invaders sería:
11" Estado s: una secuencia de observaciones, donde cada observación,
dependiendo del espacio de estados en el que se opera, es una matriz que
representa el marco de la imagen o un vector que representa el estado de
la RAM del emulador.
11" Acción a: un número entero en el rango de [1, L]. En el caso de Space
Invaders L = 6 y las acciones son 6: FIRE, RIGHT, LEFT, RIGHT FIRE,
LEFT FIRE, NOOP
11" r: Recompensa devuelta por el entorno, recortada para estar en el rango
[ -1, 1].
El objetivo es aprender una política para el agente que le permita tomar una
"buena" elección de acción para cada estado.
Explicación DQN en space invaders-ve
DQN supera el aprendizaje inestable principalmente mediante 4 técnicas:
11" Experiencia de repetición: Una vez la red neuronal está sobreajustada,
es difícil producir varias experiencias. Para resolver esto, se almacenan
experiencias que incluyen transiciones de estado, recompensas y acciones,
necesarias para realizar el aprendizaje Q.
11" Red de destino: Cada juego tiene diferentes escalas de puntuación. Por
ejemplo, en Pong, los jugadores pueden obtener 1 punto cuando ganan
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO 329
cada jugado, en cambio, si no ganan obtienen -1 punto. Sin embargo, en
Spacelnvaders, los jugadores obtienen entre 10 y 30 puntos al ganar a
los invasores. Esta diferencia haría que el entrenamiento fuera inestable.
Por lo tanto, la técnica Recompensas de recorte, recorta puntuaciones, en
las que todas las recompensas positivas se establecen en + 1 y todas las
recompensas negativas se establecen en -1.
11"'
Saltar frames: Consiste en calcular los valores de Q cada 4 frames y
usar los últimos 4 como entradas. Esto reduce el coste computacional
permitiendo juntar más experiencias.
Implementación y resultados
Una vez ya explicado cono funciona el Deep Q Network para este juego,
vamos a proceder a explicar los pasos a seguir en este proyecto, el cual ha sido
realizado con tensorflow 2x:
Instalar las librerías necesarias: tensorflow 2x, keras-rl2 y gym[atari]
try:
from google.colab import drive
%tensorftow_version 2.x
COLAS = True
print("Note: using Google CoLab")
except:
print("Note: not using Google CoLab")
COLAS = False
import keras
from tensorftow.python.client import device_lib
device_lib.list_local_devices()
print('Running! \nPlease dont interrupt this cell. It might cause serious is­
sues..')
!pip install gym keras-rl2 pyglet==l.2.4
!apt-get install -y cmake zliblg-dev libjpeg-dev xvfb ffmpeg xorg-dev python­
opengl libboost-all-dev libsdl2-dev swig
!pip install 'gym[atari]'
print('Done!')
Tras esto, necesitareis descargar los roms de Atari y buscar el
correspondiente a Space Invaders.
330 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
from google.colab import files
uploaded = files.upload()
!python -m atari_py.import_roms
Con la última instrucción los podéis cargar en vuestro Colab, si se
realizara en local, se debería sustituir el punto del final de la última
sentencia de código por la ruta donde se encuentra el rom. Tras esto ya
podemos cargar nuestro entorno y ver el número de acciones:
env = gym.make('Spacelnvaders-v0')
height, width, channels = env.observation_space.shape
actions = env.action_space.n
La instrucción de código para conocer las acciones concretas que
anteriormente hemos comentado sería:
env.unwrapped.get_action_meanings()
> ['NOOP', 'FIRE', 'RIGHT', 'LEFT', 'RIGHTFIRE', 'LEFTFIRE']
1. Construir la red neuronal: La red neuronal contendrá tres capas
convoluciones de dos dimensiones, por tanto el tamaño de la capa de
entrada es (3, height, width, channels). Tras la última Conv2D viene
una capa de aplanamiento Flatten para después conectar con 3 capas
densas, donde la última contendrá 6 unidades por las 6 acciones posibles
de Spaceinvaders.
def build_model(height, width, channels, actions):
model = tf.keras.Sequential([
layers.Conv2D(filters=32, kernel_size=8, strides=4,
activation='relu',
input_shape=(3,height, width, channels)),
layers.Conv2D(filters=64, kernel_size=4, strides=2,
activation='relu'),
layers.Conv2D(filters=64, kernel_size=4, strides=2,
activation='relu'),
layers.Conv2D(filters=64, kernel_size=3, strides=l,
activation='relu'),
layers.Flatten(),
layers.Dense(512, activation='relu'),
layers.Dense(256, activation='relu'),
layers.Dense(actions, activation='linear')
])
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO 331
return model
model = build_model(height, width, channels, actions)
model.summary()
> Model: "sequential"
> _____________________________
> Layer (type) Output Shape Param #
> ----------------------------------------------------------------> conv2d (Conv2D) (None, 3, 51, 39, 32) 6176
> _____________________________
> conv2d_l (Conv2D) (None, 3, 24, 18, 64) 32832
> _____________________________
> conv2d_2 (Conv2D) (None, 3, 22, 16, 64) 36928
> _____________________________
> flatten (Flatten) (None, 67584) 0
> _____________________________
> dense (Dense) (None, 512) 34603520
> _____________________________
> dense_l (Dense) (None, 256) 131328
> _____________________________
> dense_2 (Dense) (None, 6) 1542
> ----------------------------------------------------------------> Total params: 34,812,326
> Trainable params: 34,812,326
> Non-trainable params: 0
> _____________________________
2. Construir el agente y compilarlo: El agente tiene una [' greedy policy
que utilizaremos para equilibrar la exploración con la explotación, y,
en concreto, utilizaremos una LinearAnnealedPolicy para producir el
decaimiento del 8 (el número de steps será de 10000 con valores entre
0.1-1 ). Con el objetivo de almacenar las experiencias del agente usaremos
la función SequentialMemory de keras -rl. Usaremos un optimizador
Adam con un learning_rate de le-4.
def build_agent(model, actions):
policy = LinearAnnealedPolicy(EpsGreedyQPolicy(), attr='eps',
value_max=l., value_min=.1,
value_test=.2, nb_steps=10000)
memory = Sequentia1Memory(limit=1000, window_length=3)
dqn = DQNAgent(model=model, memory=memory, policy=policy,
enable_dueling_network=True, dueling_type='avg',
nb_actions=actions, nb_steps_warmup=l000)
return dqn
dqn = build_agent(model, actions)
dqn.compile(Adam(lr=le-4))
© RA-MA
332 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
3. Entrenar: Para el entrenamiento hemos usado nb_steps=10. 000 y ha
durado 1.75 horas.
history=dqn.fit(env, nb_steps=10000, visualize=False, verbose=2)
Como estamos en Colab ponemos visualize=False, pero trabajando
en local se puede activar para ver las partidas que se van disputando
durante el entrenamiento. No lo recomendamos porque si ya de por si el
entrenamiento es lento, aún va más despacio activando la visualización.
350
I
� 250
;:
I
I
I
! 200
"O
I
I
o
·c. 150
,'
'
\
\
\
:
:
,\
I
/
\ I,'
\/
\
,'
\
,'
,1
4
I
I
\
\ ,
,'
,'
\
\
\
',,/
\
2
1
\
\
1
1
/
I
I
I
/\
/\
/ \
\\
I
o
/\
/ \
/ \\
\
\
\
\
\
\
I
I
V)
50
,,
,\'
1
1
I
I
I
300
100
Average Episode Reward Training = 166.43
\
/
/
/
\
\
\
\
\
\
/
"
\
I
/\
\/
V
Steps
\
\
\
\
/
/
\1 /,'
\.'
6
\
.,
1 /
8
10
12
Figura 6.1 O. Evolución de la recompensa obtenida por el modelo.
La gráfica anterior corresponde al conjunto de recompensas en
entrenamiento. Comentar que con tan pocos pasos, todo resultado puede
ser fruto de la aleatorización de los pesos, pero sin una GPU potente toca
adaptarse a este tipo de entrenamientos.
4. Testear: Después de todo lo anterior llega el momento de probar los
resultados del modelo. Aunque primero vamos a crear una clase para
poder visualizar las partidas y que se reseteen cada vez que inicias una
nueva.
from tensorflow.keras.callbacks import Callback
class Render(Callback):
def on_step_end(self, step, logs={}):
plt.clf()
© RA-MA
Capítulo 6. APRENDIZAJE REFORZADO 333
plt.imshow(env.render(mode='rgb_array'))
display.display(plt.gcf())
display.clear_output(wait=True)
history2=dqn.test(env, nb_episodes=10, visualize=False, callbacks=[Render()])
o
25
50
75
100
115
150
175
200
o
50
100
150
Figura 6.11. Estado ejemplo del entorno Space lnvaders.
También podemos visualizar el history del promedio de las recompensas
del test.
Average Episode Reward Training = 240.50
600
I\
I \
I
I
\\
I
\
I
\
I
\
I
\
I
500
� 400
ii!
(lJ
"O
,'
I
I
I
300
-�
1
\
\
\1
1
1
1
o.
w
\
200
100
\
""
\.,.,,.,"
o
2
.,
., ....
,, ... ... ....
...........
.......
......
....
,,
........
4
,;
" ,,
,, ,
,,
✓
-------------------
' 6
8
Steps
Figura 6.12. Evolución de la recompensa episódica obtenida por el modelo.
334 INTELIGENCIA ARTIFICIAL. CASOS PRÁCTICOS CON APRENDIZAJE PROFUNDO
© RA-MA
Excepto un episodio, en el cual la recompensa sobresale del resto, podríamos
decir que el resto de steps tendrían un promedio sobre los 200, parecido al
entrenamiento.
Ejercicio
Modifica la arquitectura del agente para intentar mejorar los resultados.
6.6 BIBLIOGRAFÍA
Hessel, M., Modayil, J., VanHasselt,H., Schaul, T., Ostrovski, G.,Dabney, W.,Horgan,
D., Piot, B., Azar, M., & Silver, D. (2017). Rainbow: Combining lmprovements in
Deep Reinforcement Leaming. 32nd AAAI Conference on Artificial Intelligence,
AAAI 2018, 3215-3222. https://arxiv.org/abs/1710.02298v l.
Lapan, M. (2020). Deep Reinforcement Leaming Hands-On: Apply modem RL
methods to practica! problems of chatbots, robotics, discrete optimization, web
automation, and more. Packt.
Mnih, V., Kavukcuoglu, K., Silver, D., Graves, A., Antonoglou, l., Wierstra, D., &
Riedmiller, M. (2013). Playing Atari with Deep Reinforcement Learning. https://
arxiv.org/abs/1312.5602vl.
Morales, M. (2020). Grokking Deep Reinforcement Leaming. Manning Publications.
Ravichandiran, S. (2020). Deep Reinforcement Learning with Python: Master
classic RL, deep RL, distributional RL, inverse RL, and more with OpenAI Gym
and TensorFlow. Packt.
Sutton, R., A.G. Barto. (2018). Reinforcement Learning, second edition: An
lntroduction. MIT Press.
Sutton, R. (1988). Learning to predict by the methods of temporal diflerences.
Machine Learning 1988 3:1, 3(1), 9-44. https://doi.org/10.1007/BF00l15009.
Van Hasselt, H., Guez, A., & Silver, D. (2015). Deep Reinforcement Leaming with
Double Q-learning. 30th AAAI Conference on Artificial Intelligence, AAAI
2016, 2094-21OO. https://arxiv.org/abs/1509.06461v3.
Wang, Z., Schaul, T., Hessel, M., Van Hasselt, H., Lanctot, M., & De Frcitas, N.
(2015). Dueling Network Architectures far Deep Reinforcement Leaming. 33rd
Intemational Conference on Machine Leaming, ICML 2016, 4, 2939-2947.
https://arxiv.org/abs/1511.06581v3.
MATERIAL ADICIONAL
El material adicional de este libro puede descargarlo en nuestro portal web:
http://www.ra-ma.es.
Debe dirigirse a la ficha correspondiente a esta obra, dentro de la ficha
encontrará el enlace para poder realizar la descarga.
Cuando descomprima el fichero obtendrá los archivos que complementan al
libro para que pueda continuar con su aprendizaje.
INFORMACIÓN ADICIONAL Y GARANTÍA
,.- RA-MA EDITORIAL garantiza que estos contenidos han sido sometidos a un
riguroso control de calidad.
,., Los archivos están libres de virus, para comprobarlo se han utilizado las últimas
versiones de los antivirus líderes en el mercado.
,.- RA-MA EDITORIAL no se hace responsable de cualquier pérdida, daño o costes
provocados por el uso incorrecto del contenido descargable.
,.- Este material es gratuito y se distribuye como contenido complementario al
libro que ha adquirido, por lo que queda terminantemente prohibida su venta o
distribución.
lnteligenc:ia Artific:ial
Casos prácticos con Aprendizaie Profundo
Este libro tiene como objetívo acercar al lector, de una manera teórica y práctica,
a la lntelígencía Artificial moderna usando modelos neuronales artificiales
profundos que constituyen la base actual de esta tecnología.
Esta obra, dirigida a estudiantes y profesionales, nos brinda información clara y
concisa sobre la IA en la que se abordan desde el concepto de neurona artificial
planteado en 1943 hasta las últimas .aplicaciones de Modelos Generativos
y Aprendizaje Reforzado. Se tratan aplicaciones prácticas en el campo de
bioseñales, reconocimiento de imágenes, series temporales y sistemas de IA que
dirigen videojuegos, entre muchas otras cosas.
Cada capitulo contiene una parte de teoría e incluye actividades y ejemplos
prácticos con el propósito de facilitar la asimilación de los conocimientos
tratados. Está escrito con lenguaje claro y didáctico por lo que es muy adecuado
para impartir cursos sobre sistemas de IA o bien de Modelos Neuronales.
Además, el libro se acompaña de un repositorio de código con todas las prácticas
resueltas en Python, y listas para ejecutarse en entornos como Google Colab.
El libro contiene material adicional que podrá descargar
accediendo a la ficha del li.bro en www.ra-ma.es.
ISBN: 978-958,792-440
1
1 11 1
9 789587 924404
Ra-Ma
®
0
You can add this document to your study collection(s)
Sign in Available only to authorized usersYou can add this document to your saved list
Sign in Available only to authorized users(For complaints, use another form )