Al cerrar el módulo de CNN dejamos una puerta abierta: las redes convolucionales dominan las imágenes del catálogo de TecnoMarket, pero dos de sus problemas más valiosos —analizar las reseñas de los clientes y predecir la demanda diaria— no son imágenes. Son secuencias: datos donde el orden importa y donde cada elemento depende de los anteriores. En esta lección descubrirás por qué las redes densas y las CNN se quedan cortas con este tipo de datos, y conocerás la arquitectura diseñada específicamente para ellos: la red neuronal recurrente (RNN). Entenderás su idea central (un estado oculto que actúa como memoria), su formulación matemática con un ejemplo numérico a mano, sus topologías típicas y su primera implementación en Keras, además de su gran limitación, que motivará la siguiente lección.
Contenido
- Datos secuenciales: cuando el orden lo es todo
- Por qué las densas y las CNN no modelan el orden
- La idea de recurrencia: un estado oculto como memoria
- Despliegue temporal de una RNN
- La fórmula de la RNN con un ejemplo numérico a mano
- Topologías: one-to-many, many-to-one, many-to-many
- SimpleRNN en Keras sobre una secuencia sintética
- La limitación: memoria a corto plazo
Datos secuenciales: cuando el orden lo es todo
Un dato secuencial es una colección ordenada de elementos donde la posición y el contexto importan. Si barajas los elementos, destruyes la información.
Ejemplos en TecnoMarket:
| Dato | Elementos de la secuencia | ¿Qué pasa si desordenas? |
|---|---|---|
| Reseña de cliente | Palabras: "no", "es", "nada", "malo" | "es nada malo no" pierde el sentido; peor aún: "no es nada malo" (positivo) y "es malo, no es nada" (negativo) usan casi las mismas palabras |
| Demanda diaria | Ventas por día: 120, 135, 128, 410... | Pierdes la tendencia, la estacionalidad semanal y el pico del Black Friday |
| Clics de un usuario | portada → móviles → iPhone → carrito | El orden distingue "compró tras comparar" de navegación aleatoria |
| Audio de atención telefónica | Muestras de sonido en el tiempo | El habla desordenada es ruido |
Fíjate en el primer ejemplo: en español, "no es nada malo" es un elogio. Un modelo que solo cuente palabras ("no", "malo" → ¡negativa!) fallará sistemáticamente. Necesitamos un modelo que lea en orden y recuerde lo que ha leído.
Por qué las densas y las CNN no modelan el orden
Ya conoces dos familias de arquitecturas. Veamos por qué ninguna encaja del todo:
- Red densa (como la 784-128-64-10 de MNIST):
- Espera una entrada de tamaño fijo. Las reseñas tienen longitudes distintas (5 palabras o 200) y una serie de ventas crece cada día.
- Trata cada posición de entrada de forma independiente: no hay nada en la arquitectura que diga que la palabra 3 va después de la palabra 2. Para la red, la entrada es una "bolsa" de números.
- Si entrenas una densa para reconocer "envío rápido" en las posiciones 1-2, no sabrá reconocerlo en las posiciones 7-8: tendría que aprender el patrón en cada posición por separado (el mismo problema de la explosión de parámetros que vimos en 03-01 con las imágenes).
- CNN:
- Resuelven parte del problema: sus filtros con pesos compartidos detectan un patrón local esté donde esté (de hecho, existen CNN 1D para texto). Pero su "campo de visión" es local y de tamaño fijo: un filtro de tamaño 3 ve 3 palabras seguidas.
- Una dependencia larga —"El televisor que compré hace dos meses y que llegó con la pantalla rota no funciona"— exige conectar "televisor" con "no funciona" a 15 palabras de distancia. Apilar muchas capas convolucionales lo amplía, pero de forma rígida y costosa.
Lo que necesitamos es un mecanismo que procese los elementos uno a uno, en orden, y que arrastre un resumen de lo visto hasta el momento. Eso es exactamente la recurrencia.
La idea de recurrencia: un estado oculto como memoria
Una RNN procesa la secuencia paso a paso con una única célula (un pequeño bloque de red neuronal) que se aplica repetidamente. En cada paso de tiempo t, la célula recibe dos cosas:
- La entrada actual
x_t(la palabra de hoy, la venta de hoy). - El estado oculto
h_{t-1}: un vector que resume todo lo procesado hasta el paso anterior.
Y produce un nuevo estado oculto h_t, que pasa al siguiente paso. El estado oculto es la memoria de la red: un resumen comprimido, de tamaño fijo, de toda la secuencia leída hasta ahora.
Piensa en el comité de compras de TecnoMarket del módulo 1, pero leyendo una reseña en voz alta palabra a palabra: tras cada palabra, el comité actualiza una nota mental ("de momento suena positivo... ojo, ha dicho 'pero'... ahora se queja del envío"). Esa nota mental es h_t. Al final de la reseña, la nota resume la opinión completa.
Dos propiedades clave:
- La misma célula en todos los pasos: los pesos no cambian de un paso de tiempo a otro. Es el paralelismo exacto de las CNN: donde una CNN comparte pesos en el espacio (el mismo filtro recorre toda la imagen), una RNN comparte pesos en el tiempo (la misma célula recorre toda la secuencia). Por eso una RNN puede procesar secuencias de cualquier longitud con un número fijo de parámetros.
- El estado tiene tamaño fijo: elijas reseñas de 10 o de 300 palabras,
h_tes siempre un vector de, digamos, 64 números. La red aprende a decidir qué merece la pena guardar en ese resumen.
Despliegue temporal de una RNN
Aunque la RNN es una sola célula con un bucle, para entenderla (y para entrenarla) conviene "desenrollarla" en el tiempo, como si fuera una red profunda con una capa por paso:
graph LR
subgraph "RNN desplegada en el tiempo"
x1["x₁: 'no'"] --> C1[Célula RNN]
x2["x₂: 'es'"] --> C2[Célula RNN]
x3["x₃: 'nada'"] --> C3[Célula RNN]
x4["x₄: 'malo'"] --> C4[Célula RNN]
h0["h₀ = 0"] --> C1
C1 -- "h₁" --> C2
C2 -- "h₂" --> C3
C3 -- "h₃" --> C4
C4 -- "h₄" --> S["Salida: sentimiento"]
end
Puntos importantes del diagrama:
- Las cuatro cajas "Célula RNN" son la misma célula con los mismos pesos, dibujada cuatro veces.
- El estado inicial
h₀suele ser un vector de ceros: al empezar, la red no ha leído nada. - La información fluye de izquierda a derecha por la cadena de estados: para que "no" (paso 1) influya en la decisión final, su efecto debe sobrevivir en
h₁ → h₂ → h₃ → h₄.
Ese último punto, que ahora parece un detalle, será la clave de la limitación que veremos al final.
La fórmula de la RNN con un ejemplo numérico a mano
La célula RNN básica (la que Keras llama SimpleRNN) calcula:
Donde:
x_t: vector de entrada en el pasot.h_{t-1}: estado oculto anterior.Wx: matriz de pesos entrada→estado (se aprende).Wh: matriz de pesos estado→estado (se aprende). Es la matriz de la recurrencia: decide cuánto y cómo se conserva la memoria.b: sesgo (se aprende).tanh: la activación que ya conoces de 02-02; mantiene el estado entre -1 y 1, lo que evita que la memoria crezca sin control paso tras paso.
Hagamos el cálculo a mano con dimensiones minúsculas: entrada de 1 número, estado de 2 números.
Supongamos estos pesos ya entrenados:
Y la secuencia de entrada x = [1, 0, 1] (tres pasos de tiempo). Partimos de h₀ = [0, 0].
Paso 1 (x₁ = 1):
Paso 2 (x₂ = 0; toda la información viene de la memoria):
z₂ = Wx·0 + Wh·h₁ = [0.8·0.462 + (-0.2)·(-0.291), 0.1·0.462 + 0.6·(-0.291)] = [0.428, -0.129] h₂ = tanh([0.428, -0.129]) = [0.404, -0.128]
Paso 3 (x₃ = 1):
z₃ = Wx·1 + Wh·h₂ = [0.5 + 0.8·0.404 + (-0.2)·(-0.128), -0.3 + 0.1·0.404 + 0.6·(-0.128)] = [0.849, -0.336] h₃ = tanh([0.849, -0.336]) = [0.690, -0.324]
Observa dos cosas: en el paso 2, aunque la entrada era 0, el estado no se vació — la memoria Wh·h₁ mantuvo viva la información del paso 1 (atenuada: de 0.462 a 0.404). Y en el paso 3, la salida no es la misma que en el paso 1 pese a que la entrada es idéntica (x=1): el contexto acumulado cambia el resultado. Eso es exactamente lo que una densa no puede hacer.
En numpy, el mismo cálculo generalizado:
import numpy as np
def simple_rnn_paso_a_paso(x_seq, Wx, Wh, b):
"""Ejecuta una SimpleRNN a mano sobre una secuencia.
x_seq: (pasos, dim_entrada), devuelve todos los estados h_t."""
h = np.zeros(Wh.shape[0]) # h0 = vector de ceros
estados = []
for x_t in x_seq: # un paso de tiempo por iteración
h = np.tanh(Wx @ x_t + Wh @ h + b) # la fórmula de la célula
estados.append(h)
return np.array(estados)
Wx = np.array([[0.5], [-0.3]])
Wh = np.array([[0.8, -0.2], [0.1, 0.6]])
b = np.zeros(2)
x = np.array([[1.0], [0.0], [1.0]]) # secuencia de 3 pasos
print(simple_rnn_paso_a_paso(x, Wx, Wh, b))
# [[ 0.462 -0.291]
# [ 0.404 -0.128]
# [ 0.690 -0.324]]El bucle for es literal: una RNN es un bucle sobre el tiempo con los mismos pesos en cada vuelta.
Recuento de parámetros
Con entrada de dimensión d y estado de dimensión u, la célula tiene d·u (Wx) + u·u (Wh) + u (b) parámetros. Para d=1, u=2: 2 + 4 + 2 = 8. Fíjate en que no depende de la longitud de la secuencia: 8 parámetros procesan igual 3 pasos que 3.000, gracias a los pesos compartidos en el tiempo.
Topologías: one-to-many, many-to-one, many-to-many
Según cuántas entradas y salidas tenga la secuencia, hay varias formas de usar una RNN:
| Topología | Entrada | Salida | Ejemplo TecnoMarket |
|---|---|---|---|
| Many-to-one | Secuencia | Un valor | Leer una reseña completa → una etiqueta (positiva/negativa) |
| One-to-many | Un valor | Secuencia | Una foto de producto (vector CNN) → generar su descripción palabra a palabra |
| Many-to-many (sincronizada) | Secuencia | Secuencia de igual longitud | Para cada palabra de una reseña, etiquetar si es nombre de producto o no |
| Many-to-many (desfasada / seq2seq) | Secuencia | Secuencia de otra longitud | Traducir una reseña del inglés al español |
En este módulo trabajaremos sobre todo la many-to-one: es la forma natural del análisis de sentimiento (04-03) y de la predicción de demanda con ventanas (04-04): muchas observaciones entran, una predicción sale. La generación de texto (one-to-many) la desarrollaremos como proyecto en 07-02, y el seq2seq lo veremos conceptualmente en 04-03.
SimpleRNN en Keras sobre una secuencia sintética
Vamos a comprobar que una RNN aprende de verdad una dependencia temporal. Tarea sintética: dada una secuencia de 10 números, predecir la suma de los 3 últimos. Una tarea imposible sin saber qué posición ocupa cada número.
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers
# 1. Generamos el dataset sintético
rng = np.random.default_rng(42)
X = rng.uniform(0, 1, size=(2000, 10, 1)) # 2000 secuencias, 10 pasos, 1 valor por paso
y = X[:, -3:, 0].sum(axis=1) # objetivo: suma de los 3 últimos pasos
# 2. Modelo: SimpleRNN many-to-one + salida de regresión
modelo = keras.Sequential([
layers.Input(shape=(10, 1)), # (pasos, características por paso)
layers.SimpleRNN(16), # devuelve solo el ÚLTIMO estado h_10 (16 números)
layers.Dense(1) # del resumen final, a la predicción
])
modelo.compile(optimizer="adam", loss="mse", metrics=["mae"])
modelo.summary()
# 3. Entrenamos
historia = modelo.fit(X, y, epochs=30, batch_size=32,
validation_split=0.2, verbose=0)
print(f"MAE final de validación: {historia.history['val_mae'][-1]:.3f}")
# Típico: ~0.05 (sobre sumas que rondan 1.5, un error muy pequeño)Claves de lectura para principiantes:
- La forma de entrada de una RNN en Keras siempre es
(muestras, pasos, características). Aquí: 2000 secuencias, de 10 pasos, con 1 número por paso. Este tercer eje despista al principio: aunque cada paso sea un solo número, hay que declararlo. SimpleRNN(16)crea una célula con estado de 16 dimensiones. Parámetros:1·16 + 16·16 + 16 = 288(compruébalo en elsummary()).- Por defecto la capa devuelve solo el último estado
h_10: perfecto para many-to-one. (El parámetroreturn_sequences=True, que devuelve todos los estados, lo usaremos en 04-02 para apilar capas.) - La red aprende sola a "ignorar" los 7 primeros números y "recordar" los 3 últimos: nadie le ha dicho qué posiciones importan.
La limitación: memoria a corto plazo
Prueba mental: cambia la tarea anterior a "predecir la suma de los 3 primeros números de una secuencia de 100". Ahora la información relevante debe sobrevivir 97 pasos en el estado oculto... y la SimpleRNN fracasará.
¿Por qué? El entrenamiento de una RNN usa retropropagación a través del tiempo (BPTT, Backpropagation Through Time): conceptualmente, es la misma retropropagación de 02-03 aplicada a la red desplegada — el "reparto de culpa" recorre la cadena de células hacia atrás, paso a paso. Y ahí reaparece un viejo conocido: en cada paso hacia atrás, el gradiente se multiplica por Wh (y por la derivada de la tanh, que es como mucho 1). Tras muchos pasos:
- Si esos factores son < 1, el gradiente se desvanece exponencialmente: la culpa nunca llega a los primeros pasos, así que la red no aprende dependencias largas. Es el vanishing gradient de 02-03, pero a lo largo del tiempo en vez de a lo largo de capas.
- Si son > 1, el gradiente explota y el entrenamiento se desestabiliza.
Consecuencia práctica: una SimpleRNN recuerda bien unos 5-10 pasos; más allá, su memoria se difumina. Para las reseñas de TecnoMarket ("El televisor que compré... [20 palabras] ...no funciona") o para la demanda con estacionalidad semanal y anual, eso es insuficiente.
En 03-03 vimos que las skip connections de ResNet crearon "atajos" para que el gradiente fluyera por redes muy profundas. Las secuencias necesitan su propia versión de esa idea: una célula con memoria protegida. Es exactamente lo que hacen las LSTM y GRU de la siguiente lección.
Errores Comunes y Consejos
- Pasar los datos con forma incorrecta. El error más frecuente al empezar:
expected ndim=3, found ndim=2. Keras exige(muestras, pasos, características); si cada paso es un escalar, añade el tercer eje conX.reshape(n, pasos, 1)oX[..., np.newaxis]. - Creer que hay una célula distinta por paso de tiempo. No: es la misma célula (mismos
Wx,Wh,b) aplicada en bucle. El despliegue es solo una forma de dibujarla. Por eso el número de parámetros no depende de la longitud de la secuencia. - Usar una RNN cuando el orden no importa. Si tus características son independientes (edad del cliente, provincia, gasto medio), una red densa es más simple y funciona mejor. La RNN solo aporta valor cuando hay dependencia secuencial real.
- Esperar memoria larga de una SimpleRNN. Si tu dependencia relevante está a más de ~10 pasos, no pelees con la SimpleRNN: es una limitación estructural, no de hiperparámetros. La solución llega en 04-02.
- Olvidar que
tanhsatura. Si el estado se llena de valores ±1, los gradientes por esa vía tienden a cero (lo vimos en 02-02). Es otra cara del mismo problema de memoria corta.
Ejercicios
- A mano: con los pesos del ejemplo (
Wx = [0.5, -0.3]ᵀ,Wh = [[0.8, -0.2], [0.1, 0.6]],b = 0), calculah₁yh₂para la secuenciax = [0, 1]. Antes de calcular, razona: ¿qué valdráh₁y por qué? - Recuento de parámetros: ¿cuántos parámetros tiene
SimpleRNN(32)con entradas de 8 características por paso? ¿Y si la secuencia pasa de 20 a 200 pasos de tiempo? - Clasificación de topologías: clasifica estos casos de TecnoMarket como one-to-many, many-to-one o many-to-many: (a) predecir la venta de mañana a partir de las últimas 30 ventas diarias; (b) marcar, palabra a palabra, qué partes de una reseña mencionan el envío; (c) generar una respuesta automática de disculpa a partir de la categoría de la queja.
Soluciones
- Con
x₁ = 0yh₀ = [0,0]:z₁ = Wx·0 + Wh·[0,0] = [0,0], así queh₁ = tanh([0,0]) = [0, 0]— sin entrada y sin memoria previa, el estado sigue vacío. Conx₂ = 1:z₂ = Wx·1 + Wh·[0,0] = [0.5, -0.3], luegoh₂ = [0.462, -0.291](igual que el paso 1 del ejemplo, porque el contexto previo era nulo). Wx: 8·32 = 256,Wh: 32·32 = 1024,b: 32. Total: 1312 parámetros. Con 200 pasos en vez de 20: exactamente los mismos 1312 — los pesos se comparten en el tiempo; la longitud de la secuencia no añade parámetros (aunque sí coste de cómputo y más dificultad para el gradiente).- (a) Many-to-one: 30 valores entran, 1 predicción sale. (b) Many-to-many sincronizada: una etiqueta por palabra. (c) One-to-many: una categoría entra, una secuencia de palabras sale (este tipo de generación la practicaremos en el proyecto 07-02).
Conclusión
Las reseñas y la demanda de TecnoMarket son secuencias, y las secuencias necesitan una arquitectura que respete el orden: la RNN lo consigue con una única célula que se aplica paso a paso, arrastrando un estado oculto que funciona como memoria — pesos compartidos en el tiempo, igual que las CNN los comparten en el espacio. Has visto su fórmula (h_t = tanh(Wx·x_t + Wh·h_{t-1} + b)) funcionando a mano, sus topologías y una SimpleRNN en Keras aprendiendo una dependencia temporal real. Pero también su talón de Aquiles: el gradiente que viaja hacia atrás a través del tiempo se desvanece, y con él la memoria a largo plazo. En la próxima lección conoceremos las células que resolvieron este problema y que llevan décadas impulsando las aplicaciones reales de secuencias: LSTM y GRU.
Curso de Deep Learning
Módulo 1: Introducción a Deep Learning
- ¿Qué es Deep Learning?
- Historia y evolución del Deep Learning
- Aplicaciones de Deep Learning
- Conceptos básicos de redes neuronales
- Preparación del entorno de trabajo
Módulo 2: Fundamentos de Redes Neuronales
- Perceptrón y Perceptrón Multicapa
- Función de activación
- Propagación hacia adelante y hacia atrás
- Optimización y función de pérdida
- Tu primera red neuronal completa
Módulo 3: Redes Neuronales Convolucionales (CNN)
- Introducción a las CNN
- Capas convolucionales y de pooling
- Arquitecturas populares de CNN
- Aplicaciones de CNN en reconocimiento de imágenes
Módulo 4: Redes Neuronales Recurrentes (RNN)
- Introducción a las RNN
- LSTM y GRU
- Aplicaciones de RNN en procesamiento del lenguaje natural
- Secuencias y series temporales
Módulo 5: Técnicas Avanzadas en Deep Learning
- Redes Generativas Adversariales (GAN)
- Autoencoders
- Transfer Learning
- Regularización y técnicas de mejora
- Mecanismos de atención y Transformers
Módulo 6: Herramientas y Frameworks
- Introducción a TensorFlow
- Introducción a PyTorch
- Comparación de frameworks
- Entornos de desarrollo y recursos adicionales
- Guardar, cargar y desplegar modelos
Módulo 7: Proyectos Prácticos
- Clasificación de imágenes con CNN
- Generación de texto con RNN
- Detección de anomalías con Autoencoders
- Creación de una GAN para generación de imágenes
- Fine-tuning de un modelo preentrenado
