Red Neuronal Recurrente con Puertas desde Cero en Julia

RNN con Puertas desde Cero en Julia

Vamos a explorar Julia para construir una RNN con celdas GRU desde cero

Imagen por el autor.

1. Introducción

Hace algún tiempo, comencé a aprender Julia para programación científica y ciencia de datos. La adopción continua de Julia resulta de combinar el poder estadístico de R, la sintaxis expresiva y clara de Python, y el alto rendimiento de los lenguajes compilados como C++.

La mejor manera de aprender algo es practicarlo constantemente. Esta “simple” receta es evidentemente efectiva en el campo de la tecnología. Solo a través de la codificación y la práctica un programador puede comprender y explorar la sintaxis, los tipos de datos, las funciones, los métodos, las variables, la gestión de memoria, el flujo de control, el manejo de errores y las bibliotecas, incluidas las mejores prácticas y convenciones.

Estrechamente ligado a esta creencia, comencé un proyecto personal para construir una Red Neuronal Recurrente (RNN) que utiliza la arquitectura de las Unidades Recurrentes con Puertas (GRUs) de última generación. Para agregar un poco más de sabor y aumentar mi comprensión de Julia, construí esta RNN desde cero. La idea era utilizar la RNN con GRUs para predecir series temporales relacionadas con el mercado de valores.

Algoritmo de Agrupamiento Basado en Densidad desde Cero en Julia

Programemos en Julia como una alternativa a Python en ciencia de datos

pub.towardsai.net

El esquema de este post será el siguiente:

  1. Comprender la arquitectura de las GRUs
  2. Configurar el proyecto
  3. Implementar la red GRU
  4. Resultados e ideas
  5. Conclusión

Comienza, bifurca, comparte y lo más importante, experimenta con el repositorio de GitHub creado para este proyecto 👇.

GitHub – jodhernandezbe/post-gru-julia: Este es un repositorio que contiene códigos en Julia para crear desde…

Este es un repositorio que contiene códigos en Julia para crear desde cero una Red Neuronal Recurrente con Puertas para acciones…

github.com

2. Comprendiendo la arquitectura GRU

La idea de esta sección no es dar una descripción exhaustiva de la arquitectura GRU, sino presentar los elementos necesarios para codificar una RNN con celdas GRU desde cero. Para los recién llegados, puedo decir que las RNN pertenecen a una familia de modelos que permiten manejar datos secuenciales como texto, precios de acciones y datos de sensores.

Descubriendo el Modelo de Markov Oculto: Conceptos, Matemáticas y Aplicaciones en la Vida Real

Vamos a explorar la Cadena de Markov Oculta

VoAGI.com

La idea detrás de las GRUs es superar el problema del gradiente desvaneciente de las RNN tradicionales. El artículo escrito por Chi-Feng Wang puede darte una explicación sencilla de este problema 👇. En caso de que desees profundizar en las GRUs, te animo a leer los siguientes papers de fácil lectura y de código abierto:

  1. Sobre las Propiedades de la Traducción Automática Neural: Enfoques Codificador-Decodificador
  2. Evaluación Empírica de Redes Neuronales Recurrentes con Puertas en Modelado de Secuencias

El Problema del Gradiente Desvaneciente

El Problema, Sus Causas, Su Importancia y Sus Soluciones

towardsdatascience.com

Este artículo implementa una RNN que no es ni profunda ni bidireccional. Las funciones integradas en Julia deben ser capaces de capturar este comportamiento. Como se ilustra en la Figura 1, una RNN con celdas GRU consta de una serie de fases secuenciales. En cada etapa t, proporciona un elemento correspondiente al estado oculto de la etapa inmediatamente anterior (hₜ₋₁). De manera similar, un elemento representa el elemento tᵗʰ de una secuencia de muestras (es decir, xₜ). La salida de cada celda GRU corresponde al estado oculto de ese paso de tiempo que se alimentará a la siguiente fase (es decir, hₜ). Además, hₜ puede pasar por una función como Softmax para obtener la salida deseada (por ejemplo, si una palabra en un texto es un adjetivo).

Figura 1. RNN con celdas GRU (Imagen por el Autor).

La Figura 2 muestra cómo se forma una celda GRU y cómo fluye la información y las operaciones matemáticas que ocurren en su interior. La celda en el paso de tiempo t contiene una puerta de actualización (zₜ) para determinar qué parte de la información anterior se pasará al siguiente paso y una puerta de reinicio (rₜ) para determinar qué parte de la información anterior se debe olvidar. Con rₜ, hₜ₋₁ y xₜ, se calcula un estado oculto candidato (ĥₜ) para el paso actual. Posteriormente, utilizando zₜ, hₜ₋₁ y ĥₜ, se calcula el estado oculto actual (hₜ₋₁). Todas estas operaciones componen el pase hacia adelante en una celda GRU y se resumen en las ecuaciones presentadas en la Figura 3, donde Wᵣₕ, Wᵣₓ, Wₕₕ, Wₕₓ, W₂ₓ, W₂ₕ, bᵣ, bₕ y b₂ son los parámetros aprendibles. El ” * ” indica multiplicación de matrices, mientras que “・” indica multiplicación de elementos.

Figura 2. Celda GRU (Imagen por el Autor).

En la literatura, es común encontrar las ecuaciones de pase hacia adelante como se muestra en la Figura 4. En esta figura, se utiliza la concatenación de matrices para acortar las expresiones presentadas en la Figura 3. Wᵣ, Wₕ y W₂ son las concatenaciones verticales entre Wᵣₕ y Wᵣₓ, Wₕₕ y Wₕₓ, y W₂ₓ y W₂ₕ, respectivamente. Los corchetes indican que los elementos contenidos dentro de ellos se concatenan horizontalmente. Ambas representaciones son útiles, siendo la de la Figura 4 buena para acortar las fórmulas y la de la Figura 3 útil para comprender las ecuaciones de retropropagación.

Figura 3. Pase hacia adelante de GRU sin concatenación (Imagen por el Autor).
Figura 4. Pase hacia adelante de GRU con concatenación (Imagen por el Autor).

La Figura 4 muestra las ecuaciones de retropropagación que deben incluirse en el programa Julia para el entrenamiento del modelo. En las ecuaciones, ” T ” indica que se transponen las matrices. Estas ecuaciones se pueden obtener utilizando la definición de la derivada total para una función multivariable y la regla de la cadena. Además, puedes guiarte utilizando un enfoque gráfico 👇:

Pase hacia adelante y retropropagación en GRUs – Derivado | Aprendizaje Profundo

Una explicación de las unidades recurrentes con compuertas (GRUs) con las matemáticas detrás de cómo la pérdida se retropropaga a través del tiempo.

VoAGI.com

Unidades GRU

Para realizar la BPTT con una unidad GRU, tenemos el error proveniente de la capa superior (\(\delta 1\)), el oculto futuro…

cran.r-project.org

Figura 4. Ecuaciones de retropropagación GRU (Imagen del autor).

3. Configuración del proyecto

Para ejecutar el proyecto, instala Julia en tu computadora siguiendo las instrucciones de la documentación:

Instrucciones específicas de la plataforma para binarios oficiales

El sitio web oficial del lenguaje Julia. Julia es un lenguaje rápido, dinámico, fácil de usar y de código abierto…

julialang.org

Al igual que Python, puedes usar Jupyter Notebooks con kernels de Julia. Si deseas hacerlo, consulta el siguiente artículo escrito por el Dr. Martin McGovern, PhD FIA:

Cómo usar mejor Julia con Jupyter

Cómo agregar código de Julia a tus cuadernos de Jupyter y también permitirte usar Python y Julia simultáneamente en el mismo…

towardsdatascience.com

3.1. Estructura del proyecto

El proyecto dentro del repositorio de GitHub tiene la siguiente estructura de árbol:

.├── data│   ├── AAPL.csv│   ├── GOOG.csv│   └── IBM.csv├── plots│   ├── residual_plot.png│   └── sequence_plot.png├── Project.toml├── .pre-commit-config.yaml├── src│   ├── data_preprocessing.jl│   ├── main.jl│   ├── prediction_plots.jl│   └── scratch_gru.jl└── tests (unit testing)    ├── test_data_preprocessing.jl    ├── test_main.jl    └── test_scratch_gru.jl

Carpetas:

  • data: En esta carpeta encontrarás archivos .csv que contienen los datos para entrenar el modelo. Aquí se almacenan los archivos con los precios de las acciones.
  • plots: Carpeta utilizada para almacenar las gráficas que se obtienen después del entrenamiento del modelo.
  • src: Esta carpeta es el núcleo del proyecto y contiene los archivos .jl necesarios para preprocesar los datos, entrenar el modelo, construir la arquitectura de la RNN, crear las celdas GRU y realizar las gráficas.
  • tests: Esta carpeta contiene las pruebas unitarias construidas con Julia para asegurar la corrección del código y detectar errores. La explicación del contenido de esta carpeta está fuera del alcance de este artículo. Puedes utilizarla como referencia y avísame si te gustaría un artículo que explore el paquete Test.

Pruebas unitarias

Base.runtests(tests=[“all”]; ncores=ceil(Int, Sys.CPU_THREADS / 2), exit_on_error=false, revise=false, [seed]) Ejecuta las…

docs.julialang.org

3.2. Paquetes necesarios

Aunque comenzaremos desde cero, se requieren los siguientes paquetes:

  • CSV(0.10.11): CSV es un paquete en Julia para trabajar con archivos de valores separados por comas (CSV).
  • DataFrames (1.5.0): DataFrames es un paquete en Julia para trabajar con datos tabulares.
  • LinearAlgebra (standard): LinearAlgebra es un paquete estándar en Julia que proporciona una colección de rutinas de álgebra lineal.
  • Base (standard): Base es el módulo estándar en Julia que proporciona funcionalidad fundamental y tipos de datos básicos.
  • Statistics (standard): Statistics es un módulo estándar en Julia que proporciona funciones y algoritmos estadísticos para el análisis de datos.
  • ArgParse (1.1.4): ArgParse es un paquete en Julia para analizar argumentos de línea de comandos. Proporciona una forma fácil y flexible de definir interfaces de línea de comandos para scripts y aplicaciones de Julia.
  • Plots (1.38.16): Plots es un paquete popular de trazado en Julia que proporciona una interfaz de alto nivel para crear visualizaciones de datos.
  • Random (standard): Random es un módulo estándar en Julia que proporciona funciones para generar números aleatorios y trabajar con procesos aleatorios.
  • Test (standard, solo pruebas unitarias): Test es un módulo estándar en Julia que proporciona utilidades para escribir pruebas unitarias (fuera del alcance de este artículo).

Usando Project.toml, uno puede crear un entorno que contenga los paquetes mencionados anteriormente. Este archivo es similar a requirements.txt en Python o a environment.yml en Conda. Ejecute el siguiente comando para instalar las dependencias:

julia --project=. -e 'using Pkg; Pkg.instantiate()'

3.3. Precios de las acciones

Como practicante de ciencia de datos, entiendes que los datos son el combustible que impulsa cada modelo de aprendizaje automático o estadístico. En nuestro ejemplo, los datos específicos del dominio provienen del mercado de valores. Yahoo Finance proporciona estadísticas del mercado de valores de acceso público. Específicamente, analizaremos estadísticas históricas para Google Inc. (GOOG). No obstante, también puedes buscar y descargar datos de otras empresas, como IBM y Apple.

Precios históricos de las acciones de Alphabet Inc. (GOOG) y datos – Yahoo Finance

Descubre los precios históricos de las acciones de GOOG en Yahoo Finance. Ver el formato diario, semanal o mensual desde que Alphabet…

finance.yahoo.com

4. Implementando la red GRU

Dentro de la carpeta src, puedes sumergirte en los archivos utilizados para generar las gráficas que se presentarán en la Sección 5 (prediction_plots.jl), procesar los precios de las acciones antes del entrenamiento del modelo (data_preprocessing.jl), entrenar y construir la red GRU (scratch_gru.jl), e integrar todos los archivos mencionados anteriormente en uno solo (main.jl). En esta sección, profundizaremos en las cuatro funciones que componen el corazón de la arquitectura de la red GRU y que se utilizan para implementar el pase hacia adelante y la retropropagación durante el entrenamiento.

4.1. Función gru_cell_forward

El fragmento de código presentado a continuación corresponde a la función gru_cell_forward. Esta función recibe la entrada actual (x), el estado oculto anterior (prev_h) y un diccionario de parámetros como entradas (parameters). Con los parámetros mencionados anteriormente, esta función permite un paso hacia adelante de la propagación hacia adelante de la celda GRU y calcula la compuerta de actualización (z), la compuerta de reinicio (r), el nuevo estado de memoria o estado oculto candidato (h_tilde) y el siguiente estado oculto (next_h), utilizando las funciones sigmoid y tanh. También calcula la predicción de la celda GRU (y_pred). Dentro de esta función, se implementan las ecuaciones presentadas en las Figuras 3 y 4.

4.2. Función gru_forward

A diferencia de gru_cell_forward, gru_forward realiza el pase hacia adelante de la red GRU, es decir, la propagación hacia adelante para una secuencia de pasos de tiempo. Esta función recibe el tensor de entrada (x), el estado oculto inicial (ho) y un diccionario como entrada (parameters).

Si eres nuevo en modelos secuenciales, no confundas un paso de tiempo con las iteraciones para minimizar el error del modelo.

No confundas el x que recibe gru_cell_forward con el que recibe gru_forward. En gru_forward, x tiene tres dimensiones en lugar de dos. La tercera dimensión corresponde al total de celdas GRU que tiene la capa RNN. En resumen, gru_cell_forward está relacionado con la Figura 2, mientras que gru_forward está relacionado con la Figura 1.

gru_forward itera sobre cada paso de tiempo en la secuencia, llamando a la función gru_cell_forward para calcular next_h y y_pred. Los resultados se almacenan en h y y, respectivamente.

4.3. Función gru_cell_backward

gru_cell_backward realiza el paso hacia atrás para una sola celda GRU. gru_cell_forward recibe el gradiente del estado oculto (dh) como entrada, acompañado de la cache que contiene los elementos necesarios para calcular las derivadas en la Figura 4 (es decir, next_h, prev_h, z, r, h_tilde, x y parameters). De esta manera, gru_cell_backward calcula los gradientes para las matrices de pesos (es decir, Wz, Wr y Wh) y los sesgos (es decir, bz, br y bh). Todos los gradientes se almacenan en un diccionario de Julia (gradients).

4.4. Función gru_backward

gru_backward realiza la retropropagación para la red GRU completa, es decir, para la secuencia completa de pasos de tiempo. Esta función recibe el gradiente del tensor de estado oculto (dh) y las caches. A diferencia del caso de gru_cell_backward, dh para gru_backward tiene una tercera dimensión correspondiente al número total de pasos de tiempo en la secuencia o celdas GRU en la capa de la red GRU. Esta función itera sobre los pasos de tiempo en orden inverso, llamando a gru_cell_backward para calcular los gradientes para cada paso de tiempo, acumulándolos en el bucle.

Es crucial señalar en esta etapa que este proyecto solo utiliza el descenso de gradiente para actualizar los parámetros de la red GRU y no incluye ninguna funcionalidad que afecte la tasa de aprendizaje o introduzca momento. Además, la implementación se creó teniendo en cuenta una preocupación de regresión. No obstante, debido a la modularización implementada, se requieren solo pequeños cambios para adquirir un comportamiento diferente.

5. Resultados e ideas

Ahora ejecutemos el código para entrenar la red GRU. Debido a la integración del paquete ArgParse, podemos usar argumentos de línea de comandos para ejecutar el código. El procedimiento es el mismo si estás familiarizado con Python. Realizaremos el experimento utilizando una división de entrenamiento del 0.7 (split_ratio), una longitud de secuencia de 10 (seq_length), un tamaño oculto de 70 (hidden_size), 1000 épocas (num_epochs) y una tasa de aprendizaje de 0.00001 (learning_rate) porque el propósito de este proyecto no es optimizar los hiperparámetros (lo cual implicaría el uso de módulos adicionales). Ejecuta el siguiente comando para comenzar el entrenamiento:

julia --project src/main.jl --data_file GOOG.csv --split_ratio 0.7 --seq_length 10 --hidden_size 70 --num_epochs 1000 --learning_rate 0.00001

Aunque el modelo se entrena durante 1000 épocas, hay un control de flujo en la función train_gru que almacena el mejor valor para los parámetros.

La Figura 5 muestra el costo de las iteraciones de entrenamiento. Como se puede observar, la curva tiene una tendencia descendente y el modelo parece converger alrededor de las últimas iteraciones. Debido a la curvatura de la curva, es posible que se pueda obtener una mejora adicional aumentando el número de épocas para entrenar la red GRU. La evaluación externa del conjunto de prueba produce un error cuadrático medio (MSE) de aproximadamente 6.57. Aunque este valor puede no ser cercano a cero, no se puede sacar una conclusión final debido a la falta de un valor de referencia para comparación.

Figura 5. Valor del costo a lo largo de las iteraciones de entrenamiento (Imagen del autor).

La Figura 6 muestra los valores reales para los conjuntos de datos de entrenamiento y prueba como puntos dispersos y los valores predichos como líneas continuas (ver la leyenda de la figura para más detalles). Es claro que el modelo coincide con la tendencia de los puntos reales; sin embargo, se requiere más entrenamiento para mejorar el rendimiento de la red GRU. Aun así, es posible observar que en algunas partes de la imagen, especialmente en el lado de entrenamiento, el modelo sobreajustó algunas muestras; dado que el MSE para el conjunto de entrenamiento fue alrededor de 1.70, es posible que el modelo presente algo de sobreajuste.

Figura 6. Valores predichos y reales para las acciones de Google Inc. (Imagen del autor).

La fluctuación o inestabilidad del error puede ser un problema en el análisis de regresión, incluyendo la predicción de series de tiempo. En ciencia de datos y estadística, esto se conoce como heterocedasticidad (para más información, consulta el siguiente artículo 👇). El gráfico de residuos es un método para detectar la heterocedasticidad. La Figura 7 ilustra el gráfico de residuos, donde el eje x representa el valor predicho y el eje y representa el residuo.

Heterocedasticidad y Homocedasticidad en el Aprendizaje de Regresión

Variabilidad del residuo en el análisis de regresión

pub.towardsai.net

Los puntos espaciados uniformemente alrededor del valor cero indican la presencia de homocedasticidad (es decir, residuos estables). Esta figura muestra evidencia de heterocedasticidad en este escenario, lo que requeriría el uso de métodos para corregir el problema (por ejemplo, la transformación logarítmica) para crear un modelo de alto rendimiento. La Figura 7 muestra que la presencia de heterocedasticidad es evidente por encima de los 120 USD, independientemente de si la muestra proviene del conjunto de entrenamiento o de prueba. La Figura 6 ayuda a reforzar este punto. La Figura 6 indica que los valores mayores a 120 se desvían significativamente del número real.

Figura 7. Gráfico de residuos para las predicciones (Imagen del autor).

Conclusión

En esta publicación, construimos una red GRU desde cero utilizando el lenguaje de programación Julia. Comenzamos investigando las ecuaciones matemáticas que el programa necesitaba considerar, así como los problemas teóricos más críticos para que la implementación de GRU sea exitosa. Repasamos cómo crear la configuración inicial para que los programas de Julia manejen los datos, establecer la arquitectura GRU, entrenar el modelo y evaluarlo. Repasamos los fragmentos de código más importantes utilizados en el diseño de la arquitectura del modelo. Ejecutamos los programas para analizar los resultados. Detectamos la presencia de heterocedasticidad en este proyecto en particular, recomendando la investigación de estrategias para superar este problema y crear una red GRU de alto rendimiento.

Te invito a examinar el código en GitHub y hacernos saber si deseas que analicemos algo más en Julia o en otro tema de ciencia de datos o programación. Tus ideas y comentarios son extremadamente útiles para mí 🚀…

Material adicional

  1. Unidad Recurrente con Compuertas (GRU)
  2. Curso Completo de Modelos de Secuencia

Si disfrutas de mis publicaciones, sígueme en VoAGI para estar al tanto de más contenido estimulante y comparte este material con tus colegas.

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Inteligencia Artificial

Google Research explora ¿Puede la retroalimentación de IA reemplazar la entrada humana para un aprendizaje por refuerzo efectivo en modelos de lenguaje grandes?

La retroalimentación humana es esencial para mejorar y optimizar los modelos de aprendizaje automático. En los último...

Inteligencia Artificial

Nuevo curso técnico de inmersión profunda Fundamentos de IA generativa en AWS

Generative AI Foundations en AWS es un nuevo curso de inmersión técnica que te proporciona los fundamentos conceptual...

Inteligencia Artificial

DALL·E 3 está aquí con integración de ChatGPT

Adéntrate en cómo el nuevo generador de imágenes de OpenAI, DALL·E 3, está empujando los límites y descubre cómo está...

Inteligencia Artificial

AI diseña un nuevo robot desde cero en segundos

Un equipo de investigación liderado por científicos de la Universidad Northwestern creó una inteligencia artificial c...

Inteligencia Artificial

¡Atención Industria del Gaming! No más espejos extraños con Mirror-NeRF

Las NeRF o Campos de Radiancia Neurales utilizan una combinación de RNN y CNN para capturar las características físic...