Cómo funciona la propagación hacia atrás y cómo puede usar Python para construir una red neuronal

Las redes neuronales pueden resultar intimidantes, especialmente para las personas nuevas en el aprendizaje automático. Sin embargo, este tutorial desglosará cómo funciona exactamente una red neuronal y, al final, tendrá una red neuronal flexible que funcione. ¡Empecemos!

Entendiendo el proceso

Con aproximadamente 100 mil millones de neuronas, el cerebro humano procesa datos a velocidades de hasta 268 mph. En esencia, una red neuronal es una colección de neuronas conectadas por sinapsis .

Esta colección está organizada en tres capas principales: la entrada más tarde, la capa oculta y la capa de salida.

Puede tener muchas capas ocultas, que es donde entra en juego el término aprendizaje profundo . En una red neuronal artificial, hay varias entradas, que se denominan características , que producen al menos una salida, que se denomina etiqueta .

En el dibujo de arriba, los círculos representan neuronas mientras que las líneas representan sinapsis.

El papel de una sinapsis es tomar y multiplicar las entradas y los pesos .

Puede pensar en los pesos como la "fuerza" de la conexión entre las neuronas. Los pesos definen principalmente la salida de una red neuronal. Sin embargo, son muy flexibles. Después, se aplica una función de activación para devolver una salida.

Aquí hay una breve descripción general de cómo funciona una red neuronal de retroalimentación simple:

  1. Tome las entradas como una matriz (matriz 2D de números)
  2. Multiplique las entradas por un conjunto de pesos (esto se hace mediante la multiplicación de matrices, también conocido como tomando el 'producto escalar')
  3. Aplicar una función de activación
  4. Devuelve una salida
  5. El error se calcula tomando la diferencia entre la salida deseada del modelo y la salida prevista. Este es un proceso llamado descenso de gradiente, que podemos usar para alterar los pesos.
  6. A continuación, se ajustan los pesos de acuerdo con el error encontrado en el paso 5.
  7. Para entrenar, este proceso se repite más de 1000 veces. Cuanto más se entrenen los datos, más precisos serán nuestros resultados.

En esencia, las redes neuronales son simples.

Simplemente realizan la multiplicación de matrices con la entrada y los pesos, y aplican una función de activación.

Cuando los pesos se ajustan mediante la función de gradiente de pérdida, la red se adapta a los cambios para producir resultados más precisos.

Nuestra red neuronal modelará una sola capa oculta con tres entradas y una salida. En la red, estaremos prediciendo la puntuación de nuestro examen en función de las entradas de cuántas horas estudiamos y cuántas horas dormimos el día anterior. El resultado es la "puntuación de la prueba".

Estos son nuestros datos de muestra de en qué entrenaremos nuestra red neuronal:

Como habrás notado, ?en este caso representa lo que queremos que prediga nuestra red neuronal. En este caso, estamos prediciendo el puntaje de la prueba de alguien que estudió durante cuatro horas y durmió durante ocho horas en función de su desempeño anterior.

Propagación hacia adelante

¡Comencemos a codificar a este chico malo! Abra un nuevo archivo de Python. Querrá importar, numpyya que nos ayudará con ciertos cálculos.

Primero, importemos nuestros datos como matrices numpy usando np.array. También queremos normalizar nuestras unidades, ya que nuestras entradas están en horas, pero nuestra salida es una puntuación de prueba de 0 a 100. Por lo tanto, necesitamos escalar nuestros datos dividiendo por el valor máximo de cada variable.

A continuación, definamos una python classy escribamos una initfunción donde especificaremos nuestros parámetros, como las capas de entrada, oculta y de salida.

Es hora de nuestro primer cálculo. Recuerde que nuestras sinapsis realizan un producto escalar o una multiplicación matricial de la entrada y el peso. Tenga en cuenta que los pesos se generan aleatoriamente y entre 0 y 1.

Los cálculos detrás de nuestra red

En el conjunto de datos, nuestros datos de Xentrada`` es una matriz de 3x2. Nuestros datos de ysalida`` es una matriz de 3x1. Cada elemento de la matriz Xdebe multiplicarse por un peso correspondiente y luego sumarse con todos los demás resultados para cada neurona en la capa oculta. Así es como el primer elemento de datos de entrada (2 horas de estudio y 9 horas de sueño) calcularía una salida en la red:

Esta imagen desglosa lo que realmente hace nuestra red neuronal para producir una salida. Primero, los productos de los pesos generados aleatoriamente (.2, .6, .1, .8, .3, .7) en cada sinapsis y las entradas correspondientes se suman para llegar como los primeros valores de la capa oculta. Estas sumas están en una fuente más pequeña ya que no son los valores finales para la capa oculta.

Para obtener el valor final de la capa oculta, debemos aplicar la función de activación.

El papel de una función de activación es introducir la no linealidad. Una ventaja de esto es que la salida se asigna a partir de un rango de 0 y 1, lo que facilita la modificación de los pesos en el futuro.

Existen muchas funciones de activación para muchos casos de uso diferentes. En este ejemplo, nos ceñiremos a uno de los más populares: la función sigmoidea.

Ahora, necesitamos usar la multiplicación de matrices nuevamente, con otro conjunto de pesos aleatorios, para calcular nuestro valor de capa de salida.

Por último, para normalizar la salida, simplemente aplicamos la función de activación nuevamente.

¡Y ahí lo tienes! Teóricamente, con esos pesos, ¡ .85nuestra red neuronal se calculará como nuestra puntuación de prueba! Sin embargo, nuestro objetivo era .92. Nuestro resultado no fue pobre, simplemente no es el mejor que puede ser. Tuvimos un poco de suerte cuando elegí los pesos aleatorios para este ejemplo.

¿Cómo entrenamos a nuestro modelo para aprender? Bueno, lo averiguaremos muy pronto. Por ahora, sigamos codificando nuestra red.

Si aún está confundido, le recomiendo que vea este video informativo que explica la estructura de una red neuronal con el mismo ejemplo.

Implementando los cálculos

Ahora, generemos nuestros pesos al azar usando np.random.randn(). Recuerde, necesitaremos dos juegos de pesas. Una para ir de la capa de entrada a la capa oculta y la otra para ir de la capa oculta a la de salida.

Una vez que tenemos todas las variables configuradas, estamos listos para escribir nuestra forwardfunción de propagación. Pasemos nuestra entrada, Xy en este ejemplo, podemos usar la variable zpara simular la actividad entre las capas de entrada y salida.

Como se explicó, necesitamos tomar un producto escalar de las entradas y los pesos, aplicar una función de activación, tomar otro producto escalar de la capa oculta y el segundo conjunto de pesos y, por último, aplicar una función de activación final para recibir nuestra salida:

Por último, necesitamos definir nuestra función sigmoidea:

¡Y ahí lo tenemos! Una red neuronal (no capacitada) capaz de producir una salida.

Como habrás notado, necesitamos capacitar a nuestra red para calcular resultados más precisos.

Retropropagación: el "aprendizaje" de nuestra red

Dado que tenemos un conjunto aleatorio de pesos, necesitamos modificarlos para que nuestras entradas sean iguales a las salidas correspondientes de nuestro conjunto de datos. Esto se hace mediante un método llamado retropropagación.

La propagación hacia atrás funciona mediante el uso de una función de pérdida para calcular qué tan lejos estaba la red de la salida de destino.

Error de cálculo

Una forma de representar la función de pérdida es utilizando la función de pérdida de suma al cuadrado medio :

En esta función, oes nuestra salida prevista y yes nuestra salida real. Ahora que tenemos la función de pérdida, nuestro objetivo es acercarnos lo más posible a 0. Eso significa que tendremos que tener casi ninguna pérdida. Mientras capacitamos a nuestra red, todo lo que hacemos es minimizar la pérdida.

Para averiguar en qué dirección alterar los pesos, necesitamos encontrar la tasa de cambio de nuestra pérdida con respecto a nuestros pesos. En otras palabras, necesitamos usar la derivada de la función de pérdida para comprender cómo los pesos afectan la entrada.

En este caso, usaremos una derivada parcial para permitirnos tener en cuenta otra variable.

Este método se conoce como descenso de gradiente . Al saber de qué manera alterar nuestros pesos, nuestros resultados solo pueden ser más precisos.

Así es como calcularemos el cambio incremental en nuestros pesos:

  1. Encuentre el margen de error de la capa de salida (o) tomando la diferencia entre la salida prevista y la salida real (y)
  2. Aplique la derivada de nuestra función de activación sigmoidea al error de la capa de salida. Llamamos a este resultado la suma de salida delta .
  3. Utilice la suma de salida delta del error de la capa de salida para averiguar cuánto contribuyó nuestra capa z² (oculta) al error de salida al realizar un producto escalar con nuestra segunda matriz de peso. Podemos llamar a esto el error z².
  4. Calcule la suma de salida delta para la capa z² aplicando la derivada de nuestra función de activación sigmoidea (como en el paso 2).
  5. Ajuste los pesos de la primera capa realizando un producto escalar de la capa de entrada con la suma de salida delta oculta () . Para el segundo peso, realice un producto escalar de la capa oculta (z²) y la suma de salida delta de salida (o) .

Calcular la suma de salida delta y luego aplicar la derivada de la función sigmoidea son muy importantes para la retropropagación. La derivada del sigmoide, también conocida como sigmoide prima , nos dará la tasa de cambio, o pendiente, de la función de activación en la suma de salida.

Continuemos codificando nuestra Neural_Networkclase agregando una función sigmoidPrime (derivada de sigmoide):

Luego, queremos crear nuestra backwardfunción de propagación que hace todo lo especificado en los cuatro pasos anteriores:

Ahora podemos definir nuestra salida iniciando la propagación hacia adelante e iniciar la función hacia atrás llamándola en la trainfunción:

Para ejecutar la red, todo lo que tenemos que hacer es ejecutar la trainfunción. Por supuesto, queremos hacer esto varias, o quizás miles, de veces. Entonces, usaremos un forbucle.

Aquí están las 60 líneas completas de genialidad:

¡Ahí tienes! Una red neuronal en toda regla que puede aprender de las entradas y salidas.

Si bien pensamos en nuestras entradas como horas estudiando y durmiendo, y nuestras salidas como puntajes de prueba, siéntase libre de cambiarlos a lo que quiera y observe cómo se adapta la red.

Después de todo, todo lo que ve la red son los números. Los cálculos que hicimos, por complejos que parecieran ser, jugaron un papel importante en nuestro modelo de aprendizaje.

Si desea predecir un resultado basado en nuestros datos entrenados, como predecir el puntaje de la prueba si estudió durante cuatro horas y durmió durante ocho, consulte el tutorial completo aquí.

Demo y fuente

Referencias

Steven Miller

Welch Labs

Kabir Shah

Este tutorial se publicó originalmente en Enlight, un sitio web que alberga una variedad de tutoriales y proyectos para aprender construyendo. Compruébalo para ver más proyectos como estos :)