Redes neuronales desde cero (II): algo de matemáticas

En este segundo post de la serie nos adentraremos un poco en las matemáticas que hay detrás de las redes neuronales. Ya vimos algo de matemáticas en el anterior post, donde hablamos sobre las diferentes funciones de activación de las neuronas. Ahora veremos una de las partes más importantes de las redes neuronales: la asignación de los pesos para cada conexión neuronal. Entre los algoritmos más usados para asignar estos pesos se encuentra el llamado back propagation (propagación hacia atrás).

Introducción

Como ya intuimos por el post anterior, la programación clásica y las redes neuronales son aproximaciones distintas para resolver problemas. En la programación clásica, escribimos código fuente indicando al ordenador qué decisiones han de tomarse de forma absolutamente determinista para resolver un problema, una descripción detallada de todo lo que hay que hacer, similar a una receta. Por ejemplo, si lo que queremos es sumar dos números, tiene sentido programar en nuestro lenguaje favorito el algoritmo de la suma. Sin embargo, supongamos que nuestro problema es distinguir si una fotografía es un coche o no. En principio, no parece complicado cómo estructurar un algoritmo, un coche siempre tiene cuatro ruedas, ventanas, parabrisas, faros.

Pero algunas de estas características pueden no aparecer en la fotografía, la cual puede mostrar parte de coche, en lugar del coche entero. Además, diferenciar entre ruedas y ventanas, por ejemplo, puede ser igual de complicado que reconocer el propio coche. Por lo tanto, lo que hacemos en mostrarle a la red neuronal la mayor cantidad de coches diferentes posibles, y con estos ejemplos lo que la máquina aprende es el algoritmo por si mismo para identificar coches. Lo que hacemos es acudir a las redes neuronales como vimos en el post anterior. Lo único que nos falta ahora es asignar los pesos a cada una de las conexiones entre neuronas.

Asignación de pesos en Redes Neuronales

Una vez que tenemos el diseño de nuestra red neuronal, necesitamos asignar pesos a cada conexión neuronal, de manera que la salida de la función de activación de cada neurona se vea afectada por el paso de cada conexión. Existen varios algoritmos de asignación de pesos, como el gradiente de descenso o el back propagation. Veremos en detalle este último, pero primero echemos un vistazo a un problema general que tenemos al asignar los pesos, clave para entender la existencia de estos algoritmos.

Problema general de la asignación de pesos

Supongamos entonces que tenemos la red neuronal que de la siguiente imagen:

Red1
Red neuronal inicial

Sabemos que la información se introduce en la primera capa de neuronas por la izquierda, propagándose hacia delante atravesando las diferentes capas ocultas, hasta que llega a la capa de neuronas de salida, dando el resultado final. Este resultado se compara con los valores reales para calcular el error que se comete, y propagar este error hacia atrás a través de la red neuronal, lo que nos va a permitir ajustar los pesos durante el proceso de entrenamiento. Veremos algunas de las matemáticas involucradas en este proceso un poco más adelante, pero es muy importante recalcar que en la propagación hacia atrás todos los pesos se ajustan de manera simultánea.

Pero, ¿por qué complicarnos la vida con algoritmos de ajuste de pesos aparentemente tan difíciles, pudiendo asignar los pesos y probar con todas las combinaciones posibles para al final quedarnos con la que minimice el error? Esto es lo que se denomina ajuste de pesos por fuerza bruta. Veamos como esto no es viable, incluso teniendo una red neuronal muy simple, como la que usamos en el post anterior.

Algoritmo de fuerza bruta

Simple

Como vemos en la imagen, tenenos las cuatro neuronas de la capa de entrada conectadas con las cuatro neuronas de la capa oculta, lo que nos da un total de 16 conexiones. Además, las cuatro neuronas de la capa oculta están conectadas con la única neurona de salida, lo que nos da 4 conexiones más, por lo que en total tenemos 20 conexiones, es decir, debemos que calcular 20 pesos. En principio, Podemos pensar que estamos hablando de muy pocos pesos, únicamente 20, por lo que decidimos intentarlo por el método de la fuerza bruta, es decir, asignar pesos iniciales e ir probando todas las combinaciones obteniendo el error que cometemos, quedándonos con la combinación de pesos que minimice este error.

Imposible de realizar

Supongamos que tenemos 1000 valores posibles para cada uno de estos 20 pesos. Es decir, el número de combinaciones es:

1000 \times 1000 … \times 1000=1000^{20}=10^{60}


De todas estas combinaciones, tendríamos que escoger aquella que minimizara el error, por lo que tendríamos que probar absolutamente todas ellas.

El ordenador más potente que existe sobre la faz de la Tierra mientras usted lee este post, es Summit, capaz de realizar 200 cuatrillones de cálculos por segundo. Para probar todas las combinaciones, Summit tardaría miles de millones de veces la edad que tiene nuestro propio universo. Por ello, recurrimos a algoritmos complejos, como el back propagation o propagación hacia atrás.

Algoritmo back propagation

A continuación, antes de describir matemáticamente con un ejemplo el algoritmo, vemos cuáles son los pasos necesarios para implementar este algoritmo.

  1. Asignamos a cada conexión neuronal un peso con un valor pequeño, pero no nulo.
  2. Introducimos la primera observación de nuestro conjunto de entrenamiento por la capa inicial de la red neuronal.
  3. La información se propaga de izquierda a derecha, activando cada neurona que ahora es afectada por el peso de cada conexión, hasta llegar a la capa de neuronas de salida, obteniendo el resultado final para esa observación en concreto.
  4. Medimos el error que hemos cometido para esa observación.
  5. Comienza la propagación hacia atrás de derecha a izquierda, actualizando los pesos de cada conexión neuronal, dependiendo de la responsabilidad del peso actualizado en el error cometido.
  6. Repetimos los pasos desde el paso 2, actualizando todos los pesos para cada observación o conjunto de observaciones de nuestro conjunto de entrenamiento.
  7. Cuando todas las observaciones del conjunto de entrenamiento ha pasado por la red neuronal, hemos completado lo que se denomina un Epoch. Podemos realizar tantos Epochs como creamos convenientes.

Matemáticas del algoritmo back propagation

Veamos ahora con un ejemplo simple las matemáticas que hay detrás del algoritmo back propagation.

Cálculos preliminares

Partamos de una configuración inicial de una red neuronal compuesta por una capa de entrada de una neurona, una capa oculta de dos neuronas y una capa de salida de una única neurona.

red neuronal

Las neuronas se indican con la letra «a» y las conexiones neuronales con la letra «w». Para las neuronas, el superíndice entre paréntesis indica la capa a la que pertenece la neurona , y el índice indica el número de neurona dentro de la capa. Así, por ejemplo,

a_2^{(3)}

indica la neurona 2 de capa 3.

Sabemos que las conexiones neuronales conectan neuronas de una capa con las neuronas de la capa siguiente. El superíndice del peso indica el número de la capa inicial de la conexión. El subíndice está compuesto de dos números: el primero indica el número de la neurona de la capa inicial de la conexión y el segundo el número de neurona dentro de la capa al final de la conexión, es decir,

w_{21}^{(2)}

indica que la conexión empieza en la capa 2, y conecta la segunda neurona de esta capa con la neurona 1 de la capa siguiente.

La salida de la neurona 1 dentro de la capa 3 sería entonces:

a_1^{(3)}=f(u_1^{(3)}+w_{11}^{(2)}a_1^{(2)}+w_{21}^{(2)}a_2^{(2)})


Ahora, hacemos lo mismo con la salida de las neuronas de la capa oculta anterior, y tenemos que:

a_1^{(2)}=f(u_1^{(2)}+w_{11}^{(1)}a_1^{(1)}) \\
y \\
a_2^{(2)}=f(u_2^{(2)}+w_{12}^{(1)}a_1^{(1)})

Sustituimos estas dos últimas expresiones en la expresión inicial, y nos queda:

a_1^{(3)}=f(u_1^{(3)}+w_{11}^{(2)}f(u_1^{(2)}+w_{11}^{(1)}a_1^{(1)})+w_{21}^{(2)}f(u_2^{(2)}+w_{12}^{(1)}a_1^{(1)}))

Generalizando la fórmula

Por lo tanto, generalizando la fórmula anterior para cualquier numero de capas k y cualquier neurona i dentro de la capa, tenemos que:

a_i^{(k)}=f(u_i^{(k)}+ \sum_{j=1}^{n_k-1}a_j^{(k-1)}w_{ji}^{(k-1)}), i=1 \ldots n_k

Recordemos que las variables en toda esta expresión son los pesos, por lo que para hacer mínimo el error tenemos que derivar con respecto de cada uno de ellos, teniendo en cuenta que estamos usando una función de activación sigmoide, es decir, tenemos que hacer derivadas parciales con respecto a cada peso.

Una vez obtenido el valor mínimo, repetimos el proceso tantas veces como necesitemos para que las conexiones de la red queden ajustadas de manera óptima.

Resumen

En este post hemos visto la base de uno de los algoritmos mas utilizados para asignar pesos a las conexiones neuronales de nuestra red. Por supuesto, este es uno de entre muchos algoritmos que se usan para tal fin. Saber algo de las matemáticas básicas que se encuentran detrás de estos algoritmos nos dará una ventaja extra a la hora de elegir cual de ellos es mas adecuado usar en nuestra red neuronal. Por supuesto, aunque es un ejercicio muy conveniente y didáctico, no tenemos que programar explícitamente estos algoritmos al construir la red neuronal, prácticamente todas las librerías y frameworks que usamos para programar la red nos proporcionan métodos a los cuales únicamente hay que dotarles de los parámetros adecuados, y se encargaran de ejecutar el algoritmo por sí mismos.

Recursos

  • Tutorial de red neuronal perceptron con algoritmo back propagation.
  • Video fantástico (en ingles) sobre el algoritmo back propagation.

Créditos

Algunas de las imágenes de este artículo han sido reproducidas, con permiso, del Curso completo de Inteligencia Artificial con Python.

Acerca del autor

Este es un post invitado por José David Villanueva García. José David es Ingeniero Técnico en Informática de Sistema por la Universidad Rey Juan Carlos, Graduado en Matemáticas por la UNED, y Máster en Matemáticas Avanzadas por la UNED (Máster Thesis).

Actualmente trabaja como ingeniero en Darmstadt, Alemania, en diferentes proyectos para la ESA (European Space Agency) y EUMETSAT (European Organisation for the Exploitation of Meteorological Satellites).

4 comentarios en «Redes neuronales desde cero (II): algo de matemáticas»

  1. Buenos dias Jesus, gracias por el comentario.
    Intentare subir la tercera parte antes del verano.
    Por otro lado, el ejemplo estara escrito en el lenguaje de programacion Python, por lo que se necesitaran las librerias habituales de Python para temas de Machine Learning.
    Gracias y un saludo.
    Jose David.

    Responder

Deja un comentario