Si bien las redes neuronales ahora se pueden crear, entrenar y probar en solo tres líneas de código, es crucial comprender su funcionamiento interno. Aunque la mayoría de la gente tiene un conocimiento básico de los pases hacia adelante, los pases en pendiente y los pases hacia atrás, solo unos pocos están familiarizados con las técnicas esenciales, como el producto vectorial jacobiano o el truco log-sum-expque evitan que las redes neuronales experimenten desbordamiento de memoria e inestabilidad numérica.
En este tutorial, lo guiaremos a través de la implementación de una red neuronal desde cero usando Python en solo 100 líneas de código. Nuestro enfoque está en la implementación y no profundizaremos en los tecnicismos detrás de cada ecuación. Sin embargo, si está interesado en obtener una comprensión más profunda de las redes neuronales, tengo un curso de cinco horas en Udemy que cubre este tema en detalle.
La implementación del MLP es un proceso sencillo que involucra dos conjuntos de parámetros que se pueden aprender, los pesos W y sesgo b. Para garantizar una inicialización adecuada, el Inicialización de Xavier comúnmente se utiliza el método. El pase hacia adelante es una implementación directa de la ecuación MLP. Para el pase hacia atrás, necesitamos calcular el gradiente de pérdida con respecto a los pesos y el sesgo, lo que nos permite actualizarlos con descenso de gradiente. Podemos lograr esto multiplicando el jacobiano de la transformación MLP con el gradiente de pérdida con respecto a las salidas de MLP. En lugar de calcular y almacenar el jacobiano, lo que requeriría una cantidad significativa de memoria, podemos calcular directamente los resultados usando el Producto vectorial jacobiano. Esta técnica es esencial en la implementación de redes neuronales, y una implementación ingenua sin este truco no escalaría debido a la explosión de la memoria. El jacobiano suele ser escaso, y el producto vectorial jacobiano se puede reescribir analíticamente y calcular directamente.
Después de calcular el gradiente de pérdida con respecto a los pesos y sesgos, podemos determinar el gradiente de las salidas con respecto a las entradas. Esto permite que los módulos anteriores lo utilicen para calcular el gradiente de la pérdida con respecto a sus parámetros, siguiendo el regla de la cadena de la derivada.
Las redes neuronales constan de varios componentes, incluidos MLP, funciones de activación y capas complejas. Para combinar estos bloques, podemos crear una clase de red neuronal secuencial que toma una lista de módulos como entrada y los envuelve para verlos como un solo bloque.
Durante el paso hacia adelante, las entradas pasan a través de cada capa para obtener las salidas del NN secuencial. De manera similar, durante el paso hacia atrás, partimos del gradiente de la pérdida y lo propagamos hacia atrás a través de los módulos, desde la última capa hasta la primera.
Las activaciones no lineales son esenciales para las redes neuronales, ya que introducen la no linealidad y les permiten funcionar como aproximadores de funciones universales.
ReLU es una función de activación popular que es fácil de implementar. Implica establecer todos los valores negativos en 0 durante el pase hacia adelante. Para el paso hacia atrás, podemos calcular analíticamente el producto vectorial jacobiano e implementarlo directamente.
Al realizar la clasificación, las funciones de activación de softmax o log softmax son cruciales (puede consultar mi curso si está interesado en la regresión). Convierte las salidas logit sin restricciones de la red neuronal en probabilidades mayores que cero para cada clase, que suman uno.
Si bien es posible implementar la función log softmax aplicando la ecuación softmax y luego tomando su logaritmo, este enfoque puede generar inestabilidad numérica debido al logaritmo. La mejor manera de implementarlo es reescribiendo la ecuación usando el truco log-sum-exp. Una vez que el producto vectorial jacobiano se ha calculado analíticamente, el paso hacia atrás se puede implementar directamente.
La función de pérdida es el componente final en la canalización secuencial y, a diferencia de los otros módulos, toma dos valores como entrada durante su paso hacia adelante: las predicciones y el objetivo.
En las tareas de clasificación, el pérdida de probabilidad logarítmica negativa se usa comúnmente. Para calcular esta pérdida, nuestro objetivo es maximizar la probabilidad logarítmica total de los datos, que consiste en sumar las probabilidades logarítmicas predichas por el modelo para las clases objetivo.
En la dirección hacia atrás, la pérdida es la primera capa, por lo que no toma un gradiente como entrada. En cambio, devuelve el gradiente de la pérdida con respecto a la predicción, que luego se alimenta a las capas anteriores.
El optimizador recibe una red neuronal secuencial como entrada. Cada vez que se llama a la función de paso, realiza un descenso de gradiente simple para actualizar los pesos de la red neuronal. Alternativamente, se podrían utilizar métodos de optimización más avanzados como Adam (sígame para mi próxima publicación sobre su implementación).
Sin duda, el ciclo de entrenamiento es la sección más sencilla. No requiere conocimientos matemáticos y simplemente implica invocar los módulos anteriores. Dado que estamos realizando aprendizaje supervisado, durante cada época, seleccionamos aleatoriamente valores de entrada y objetivo de un tamaño de lote específico, utilizamos el modelo para hacer predicciones y calculamos la pérdida entre las predicciones y el objetivo. Luego, retropropagamos la pérdida y actualizamos los pesos de la NN con el optimizador.
Habiendo implementado todos los componentes necesarios, ahora podemos integrarlos sin problemas. Al cargar y preprocesar los datos, podemos inicializar una red neuronal, un optimizador y lanzar el proceso de entrenamiento de la red neuronal. Se necesitan solo 20 segundos de entrenamiento en la CPU para alcanzar una precisión de prueba de hasta el 98 %.
[post_relacionado id=»1723″]