• Blog
  • IA
  • ¿Qué es una RNN (Recurrent Neuronal Network)?

¿Qué es una RNN (Recurrent Neuronal Network)?

18min

Hoy nos centramos vamos a hablar de una especialización de las redes neuronales, que añaden a IA algo que las redes tradicionales no tienen: memoria. Por eso, se denominan redes neuronales recurrentes. Esto facilita funcionalidades como  el reconocimiento facial del teléfono hasta los traductores automáticos y los asistentes de voz y se pueden implementar en Python con librerías como TensorFlow y PyTorch.

Índice

¿Qué son las redes neuronales recurrentes?

Una RNN, siglas de Recurrent Neural Network o red neuronal recurrente, es un tipo de red neuronal diseñado para trabajar con datos que por su naturaleza tienen un orden o una dependencia entre elementos consecutivos. A diferencia de las redes neuronales tradicionales, que suele tratar cada entrada como un caso independiente, una RNN conserva información de los pasos anteriores de una secuencia mediante un estado interno o estado oculto.

Este mecanismo la hace adecuada para problemas de lo más variado, como el procesamiento de texto, el reconocimiento de voz, la predicción de series temporales o la generación de secuencias. Por ejemplo, al procesar una frase palabra a palabra, una RNN puede usar la información ya leída para interpretar el significado de la siguiente palabra, algo que, como te imaginarás, necesitan los sistemas de reconocimiento de textos o de voz.

La clave del funcionamiento de la RNN es que procesa cada elemento junto con la memoria producida hasta ese momento. Así, cuando llega a cualquier elemento dado, no solo analiza el dato actual, sino también el resumen de lo que ha visto hasta el momento.

Esta propiedad permite entender por ejemplo el sentido de una palabra, que puede depender de varias palabras anteriores. O entender mejor de manera genérica cualquier elemento analizando tendencias, ciclos o cambios producidos mucho antes.

Estructura matemática y arquitectura de las redes neuronales recurrentes

Una RNN puede entenderse como una red donde también se guarda información temporal, habiendo en su estructura una parte dedicada a la propia entrada actual, pero también al resumen del conjunto anterior.

Para resumir la estructura, cada copia recibe tres elementos principales. Por un lado, la entrada actual. Por otro, el estado oculto anterior. Y, además, un conjunto de pesos compartidos con todos los demás pasos temporales.

En la RNN la celda calcula un nuevo estado oculto que resume la información previa y el contenido de la entrada actual.

La ecuación del estado oculto y la actualización del vector de memoria

El estado oculto es como una memoria del trabajo que se viene realizando en la red, que se actualiza a medida que la secuencia guarda información en cada paso.

Esa nueva memoria necesita filtrar y mezclar lo que acaba de entrar, uniendo lo que ya recordaba. Para realizar ese cálculo se utiliza una ecuación del estado oculto característica de las RNN. Las librerías más habituales para realizar RNN ya implementan esta ecuación como veremos más adelante en Python.

Algoritmo de retropropagación a través del tiempo (Backpropagation Through Time)

Las RNN se entrenan habitualmente con el algoritmo de retropropagación a través del tiempo, conocido como Backpropagation Through Time o por sus siglas BPTT. Este algoritmo sirve para aprender de sus errores a lo largo de la secuencia.

Podemos entenderlo como una serie de pasos temporales en una secuencia que permiten almacenar información de todo un proceso, de modo que si se encuentra un fallo por el camino es posible volver hacia atrás para encontrar el momento exacto en el que empezó la desviación.

Más técnicamente, consiste en desplegar la red recurrente a lo largo de los pasos temporales. De este modo, una vez desplegada, se puede aplicar retropropagación de forma similar a una red profunda convencional, pero teniendo en cuenta que los parámetros son los mismos en cada instante.

Mapeos de entrada y salida: Arquitecturas One-to-Many, Many-to-One y Many-to-Many

Las RNN pueden resolver distintos tipos de problemas y para ello se pueden hacer distintos tipos de mapeo según la tarea. Estos mapeos son las entradas que tú vas a aportar a la red neuronal y las salidas que quieres obtener de ella.

Los tipos de mapeo se definen en tres bloques:

  • One-to-Many: Un único dato de entrada y salida en todos los pasos de la secuencia. Puede servir para generación de texto desde una etiqueta o generación de subtítulos desde una imagen.
  • Many-to-One: donde entregamos una entrada con una secuencia y deseamos un único resultado de salida. Por ejemplo se da en el análisis de sentimiento o la  detección de fraude en una secuencia de eventos.
  • Many-to-Many: donde damos una entrada en secuencia y queremos también la salida de la secuencia. Esto puede darse para traducción, resúmenes, transcripciones de audio, reconocimiento de entidades, etc.

Variantes avanzadas y tipos de redes neuronales recurrentes

Las redes neuronales recurrentes tradicionales tenían un problema grave: sufrían de «amnesia a corto plazo». Esto quiere decir que, si la secuencia de datos era muy larga, la red olvidaba lo que había pasado al principio. Para solucionar esto y hacerlas mucho más capaces, se crearon versiones evolucionadas con mejores mecanismos de memoria, que tienen estructuras organizadas en varios niveles.

Entre estas mejoras las más conocidas son las redes LSTM y GRU. Vamos a ver de qué se tratan.

Redes LSTM (Long Short-Term Memory)

Las redes LSTM, siglas de Memoria a Largo y Corto Plazo, son redes que incluyen un sistema inteligente de filtros llamados «puertas». Estos filtros deciden de forma constante qué información vale la pena guardar a largo plazo y cuál es mejor desechar.

  • Puerta de olvido, que equivale a borrar la información. Decide qué datos antiguos ya no son útiles y se pueden desechar.
  • Puerta de entrada, que equivale a Guardar. En este caso se decide que la información nueva que acaba de leer es lo suficientemente importante como para guardarla en la memoria.
  • Puerta de salida, que dice qué conviene usar. Es el mecanismo que permite decidir qué parte de todo lo memorizado necesita utilizar en ese momento preciso.

Piensa en una red LSTM como un estudiante subrayando un libro de texto y organizando todo de ese modo. Gracias a las puertas, consigue recordar un detalle mencionado en la primera página de un libro y usarlo correctamente en el capítulo final.

Redes GRU (Gated Recurrent Unit)

Luego tenemos un tipo de red, GRU (algo como Unidad Recurrente Regulada por Puertas) , que es una versión simplificada y rápida de la LSTM. Cumple la misma función pero lo consigue usando menos piezas internas.

En la práctica reduce las puertas de 3 a 2, eliminando la puerta de salida. En vez de ello comparte su memoria directamente sin ese filtro extra. Como resultado requiere menos capacidad de cómputo y aprende más rápido. Es ideal cuando no hay tantos datos disponibles o se necesita que el sistema funcione con mayor velocidad sin perder precisión.

Redes recurrentes bidireccionales (BiRNN) y modelos profundos apiladas (Stacked RNN)

Una RNN convencional procesa la secuencia en una única dirección, normalmente desde el primer elemento hasta el último, lo que significa que solo conoce el contexto que ha aparecido antes.

Luego tenemos un tipo de redes recurrentes que resuelven esta limitación y que son bidireccionales. Para ello usan más de una red recurrente independiente. Con todo consiguen mejorar la manera en que la red procesa la información para entender conceptos más complejos.

Una redes recurrente bidireccional BiRNN incluye dos RNN, una que mira hacia atrás, al pasado, y otra que mira hacia el futuro Mientras que una stacked RNN incluye varias capas de análisis, apiladas una encima de otra, siendo una responsable de una parte del razonamiento.

Estas variantes bidireccionales y apiladas no son excluyentes. De hecho, es posible construir una BiLSTM o una BiGRU con varias capas, combinando el contexto de ambas direcciones con una mayor profundidad. Estas arquitecturas son muy importantes en la actualidad, ya que se usan para procesamiento de lenguaje natural o reconocimiento de voz.

Diferencias entre una RNN y una CNN

Aunque ambas son arquitecturas fundamentales en el aprendizaje profundo, sus diseños se usan para resolver problemas completamente diferentes. Mientras que las redes convolucionales (CNN) destacan analizando estructuras estáticas de múltiples dimensiones, las redes recurrentes (RNN) están optimizadas para la dimensión temporal:

  • Naturaleza de los datos (Patrones espaciales vs. Secuencias temporales): Las CNN están diseñadas para extraer patrones espaciales en datos estáticos (como píxeles en imágenes). Las RNN procesan datos donde el orden secuencial y la dimensión temporal son críticos (como texto, audio o series numéricas).
  • Mecanismo de procesamiento (Campos receptivos vs. Memoria de estado): Las CNN aplican filtros matemáticos sobre áreas locales de la entrada sin recordar las anteriores. En cambio, las RNN utilizan bucles de realimentación interna para mantener una memoria de estado de la información procesada previamente.
  • Paralelización y rendimiento en hardware: En las CNN, la matriz de la entrada se procesa en paralelo de forma masiva en la GPU. Por su parte, en las RNN, como el cálculo de un instante depende del resultado del instante anterior, el flujo es secuencial y resulta más difícil de paralelizar.
  • Invariancia espacial vs. Dependencia contextual: Las CNN buscan invariancia a la traslación (detectar un objeto en la imagen sin importar su posición). En cambio, las RNN dependen del contexto previo para interpretar el significado de un elemento dentro de la cadena.

Criterios de selección según la naturaleza de los datos: Archivos audiovisuales frente a series de tiempo

En resumen, las CNN son más adecuadas si trabajas con archivos audiovisuales estáticos como imágenes de satélite o escaneos médicos.

En el caso que tengas una entrada de longitud variable y la historia pasada condicione el resultado de la secuencia completa, será más adecuado usar una RNN. Por ejemplo para audio en directo, reconocer texto, traducción, etc.

Casos prácticos de las redes neuronales recurrentes

Como hemos dicho, las redes neuronales recurrentes (RNN) son ideales para procesar datos secuenciales y mantener una memoria del contexto previo. Esto las convierte en una herramienta muy potente para tareas donde el orden de la información es crítico. Estos son sus casos de uso más destacados:

  • Traducción automática y Procesamiento del Lenguaje Natural (PLN): Permiten interpretar la estructura de una oración completa en lugar de traducir palabra por palabra, logrando equivalencias contextuales precisas.
  • Predicción de series temporales y finanzas: Analizan secuencias históricas de precios, volumen de ventas o indicadores macroeconómicos para proyectar cotizaciones bursátiles y tendencias de mercado.
  • Reconocimiento continuo del habla (Speech-to-Text): Se usan para procesar frecuencias de audio en tiempo real, para convertirlas en texto escrito, sirviendo de motor en asistentes de voz y sistemas de transcripción en directo.
  • Generación de descripciones para imágenes (Image Captioning): Combinadas con redes convolucionales (CNN) que analizan la imagen, las RNN redactan subtítulos y descripciones textuales coherentes sobre el contenido visual.
  • Análisis de sentimiento: Evalúan el flujo de palabras en reseñas de clientes o redes sociales para determinar el tipo de sentimiento del texto y su enfoque positivo, negativo o neutral, captando negaciones y matices contextuales.
  • Composición musical automatizada: Modelan la secuencia temporal de notas, ritmos y tiempos para componer piezas musicales, tomando en consideración estructuras armónicas previas.
  • Detección de anomalías en IoT y mantenimiento predictivo: Se usan para monitorizar las lecturas continuas de sensores industriales para identificar patrones atípicos y anticipar fallos mecánicos antes de que sucedan.

¿Cómo implementar Recurrent Neuronal NetWork en Python?

Entre todos los lenguajes de programación que podemos utilizar, Python es uno de los más adecuados para implementar una RNN. Como te imaginarás, no es un problema trivial y requiere resolver varias fases, como preparar las secuencias, convertirlas a una representación numérica, además de definir la arquitectura, entrenarla y evaluar si realmente funciona sobre datos no vistos en su entrenamiento.

TensorFlow con Keras y PyTorch son dos de los ecosistemas más habituales para realizar estas tareas. Keras permite construir prototipos de forma rápida con capas de alto nivel, mientras que PyTorch ofrece un control más explícito sobre la definición del modelo, el flujo de entrenamiento y la manipulación de tensores. Ambas alternativas incluyen implementaciones preparadas de RNN, LSTM y GRU.

Configuración del entorno y preprocesamiento de secuencias con NumPy y Pandas

El primer paso consiste en preparar un entorno de trabajo con Python con las dependencias necesarias. Para un proyecto introductorio centrado en datos tabulares, texto o series temporales, normalmente bastan NumPy, Pandas y uno de los principales frameworks de aprendizaje profundo.

pip install numpy pandas tensorflow torch scikit-learn

NumPy se utiliza principalmente para trabajar con arrays multidimensionales y realizar operaciones numéricas vectorizadas. Pandas se usa para cargar, inspeccionar, limpiar y transformar conjuntos de datos estructurados, por ejemplo desde archivos CSV, bases de datos o API.

Tokenización, padding y creación de word embeddings para datos de texto

El siguiente paso consiste en la tokenización, que resulta esencial porque una RNN no puede trabajar directamente con palabras o caracteres. La tokenización consiste en dividir un texto en unidades, que pueden ser palabras, caracteres, fragmentos de palabras o subpalabras. Luego, cada token se convierte en un identificador entero dentro de un vocabulario, que básicamente será un vector como [12, 25, 8] o [4, 19, 21, 9, 3].

Ese vector no incluye un significado por sí mismo, sino que identifica al texto que hay detrás. Sin embargo, el sistema no es capaz de trabajar con vectores de distintas dimensiones por lo que se tiene que realizar un padding.

El padding es necesario porque los modelos se entrenan habitualmente por lotes y dentro de un lote todas las muestras deben tener una forma compatible. Es por ello que el padding transforma las secuencias para que sean compatibles, tal que así: [12, 25, 8, 0, 0], [4, 19, 21, 9, 3]. Para eso librerías como Keras tienen funciones que consiguen hacer ese trabajo por nosotros.

Finalmente, la capa embedding convierte cada identificador entero en un vector denso. Por ejemplo, un token representado por el número 27 puede pasar a convertirse en un vector de 128 valores decimales.

Construcción de una capa LSTM o GRU utilizando TensorFlow y Keras

Keras permite construir una red recurrente mediante una secuencia de capas. En un caso básico de clasificación de textos, el modelo puede incluir una capa de embeddings, una capa LSTM o GRU y una capa densa de salida.

El siguiente ejemplo utiliza una LSTM para clasificar textos en dos categorías:

import tensorflow as tf
modelo = tf.keras.Sequential([
    tf.keras.layers.Embedding(
        input_dim=10000,
        output_dim=128,
        mask_zero=True
    ),
    tf.keras.layers.LSTM(64),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

modelo.compile(
    optimizer='adam',
    loss='binary_crossentropy',
    metrics=['accuracy']
)

Sería imposible entrar en el detalle de este código en este artículo pero puedes ver que no es demasiado largo para la complejidad que hay detrás de estas operaciones.

Aquí tenemos otro ejemplo de código en el que tenemos implementada una GRU:

modelo = tf.keras.Sequential([
    tf.keras.layers.Embedding(
        input_dim=10000,
        output_dim=128,
        mask_zero=True
    ),
    tf.keras.layers.GRU(64),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

Definición e implementación de un modelo recurrente desde cero con PyTorch

PyTorch permite también implementar RNN desde cero con un código de muy alto nivel. Para ello se definen modelos mediante clases de Python que heredan de torch.nn.Module.

Se deben declarar las capas en el constructor y se define el flujo de datos en el método forward. Además hay módulos separados para implementar las ecuaciones de una RNN, una LSTM o una GRU.

Para que sirva de ejemplo, a continuación tienes un código mínimo de clasificador textual con una LSTM:

import torch
import torch.nn as nn
class ClasificadorLSTM(nn.Module):
    def __init__(
        self,
        vocabulario_size,
        embedding_dim,
        hidden_dim,
        num_clases
    ):
        super().__init__()
        self.embedding = nn.Embedding(
            num_embeddings=vocabulario_size,
            embedding_dim=embedding_dim,
            padding_idx=0
        )
        self.lstm = nn.LSTM(
            input_size=embedding_dim,
            hidden_size=hidden_dim,
            batch_first=True
        )
        self.salida = nn.Linear(hidden_dim, num_clases)
    def forward(self, x):
        x = self.embedding(x)
        salida_lstm, (estado_oculto, estado_celda) = self.lstm(x)
        ultima_capa = estado_oculto[-1]
        return self.salida(ultima_capa)

Entrenamiento del modelo, ajuste de hiperparámetros y evaluación de resultados

Una vez construido el modelo, hay que definir una función de pérdida, un optimizador y un bucle de entrenamiento. En Keras, la mayor parte de este proceso se simplifica mediante los métodos compile y fit. Por su parte, en PyTorch, el proceso se expresa de forma más explícita, lo que requiere algo más de código, pero permite controlar cada fase del entrenamiento con mayor detalle.

Para el ajuste de hiperparámetros se tienen que tomar decisiones en función de factores como tamaño del vocabulario, longitud máxima de las secuencias o dimensión de los embeddings.

Desafíos técnicos y estrategias de optimización

Entrenar redes neuronales recurrentes presenta retos únicos debido a la propagación del error a lo largo del tiempo. Para mitigar problemas habituales como el desvanecimiento o la explosión del gradiente y asegurar una convergencia estable, diversas técnicas como estas:

  • Recorte de gradientes (Gradient Clipping): Esta técnica previene la explosión del gradiente reescalando o limitando la norma del vector de gradientes si este supera un umbral determinado, evitando que los pesos del modelo se desestabilicen numéricamente durante la actualización.
  • Recurrent Dropout y Zoneout: Se trata de adaptaciones de regularización para datos secuenciales. El Recurrent Dropout apaga conexiones de forma idéntica a lo largo de los pasos temporales para no destruir la memoria retenida, mientras que Zoneout obliga a la red a mantener de forma aleatoria los valores del estado oculto anterior.
  • Optimizadores adaptativos (Adam y RMSprop frente a SGD): En datos secuenciales, la magnitud de las actualizaciones puede fluctuar violentamente. Para estos casos Adam y RMSprop adaptan la tasa de aprendizaje de forma individual para cada parámetro, gestionando los gradientes ruidosos o dispersos mucho mejor que el descenso de gradiente estocástico (SGD) tradicional.
  • Truncamiento temporal (Truncated BPTT): En lugar de hacer la retropropagación a través del tiempo (Backpropagation Through Time) por la secuencia completa, la cadena se divide en bloques temporales más pequeños, reduciendo sustancialmente el coste computacional y el uso de memoria RAM/VRAM.

Preprocesamiento de datos secuenciales y preparación de series temporales

Antes de alimentar una red neuronal recurrente, los datos deben transformarse para reflejar su naturaleza secuencial. En series temporales numéricas, esto implica aplicar técnicas de normalización, desestacionalización y la creación de ventanas deslizantes, conocidas habitualmente con el término sliding windows. Estas series sirven para convertir secuencias pasadas en datos de entrada y el valor siguiente en la etiqueta a predecir.

En el caso del lenguaje natural, el texto se somete a tokenización e incrustación de palabras para representar conceptos en espacios vectoriales densos, finalizando con procesos de relleno y enmascaramiento, padding y masking, que igualan la longitud de las secuencias para su procesamiento matricial sin alterar el contexto.

A continuación se muestra un ejemplo básico con Keras/TensorFlow donde preprocesamos una secuencia de texto corta (tokenización y padding) y construimos un modelo con una capa LSTM para clasificación:

import tensorflow as tf
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, LSTM, Dense

# 1. Datos de ejemplo y Preprocesamiento
corpus = [
    'las redes neuronales son geniales',
    'el procesamiento de lenguaje natural es fascinante',
    'las rnn procesan datos secuenciales'
]
labels = [1, 1, 1]  # Clases/Etiquetas

# Tokenización y Padding (igualar longitudes de secuencia)
tokenizer = Tokenizer(num_words=100)
tokenizer.fit_on_texts(corpus)
sequences = tokenizer.texts_to_sequences(corpus)
padded_sequences = pad_sequences(sequences, padding='post')


# 2. Construcción de la arquitectura RNN (LSTM)
model = Sequential([
    Embedding(input_dim=100, output_dim=16, input_length=padded_sequences.shape[1]),
    LSTM(units=32, dropout=0.2, recurrent_dropout=0.2), # Capa recurrente principal
    Dense(1, activation='sigmoid') # Capa de salida
])

# 3. Compilación y resumen del modelo
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
model.summary()

Espero que este artículo te haya resultado útil para entender la base de las redes neuronales recurrentes y comprender cómo manejan la dimensión temporal en los datos. Aunque hoy en día arquitecturas más recientes como los Transformers han tomado el relevo en tareas complejas de lenguaje, las RNN siguen siendo una pieza fundamental en el aprendizaje profundo.

Fernán García de Zúñiga

Productos relacionados: