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

  1. Datos secuenciales: cuando el orden lo es todo
  2. Por qué las densas y las CNN no modelan el orden
  3. La idea de recurrencia: un estado oculto como memoria
  4. Despliegue temporal de una RNN
  5. La fórmula de la RNN con un ejemplo numérico a mano
  6. Topologías: one-to-many, many-to-one, many-to-many
  7. SimpleRNN en Keras sobre una secuencia sintética
  8. 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:

  1. La entrada actual x_t (la palabra de hoy, la venta de hoy).
  2. 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_t es 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:

h_t = tanh(Wx · x_t + Wh · h_{t-1} + b)

Donde:

  • x_t: vector de entrada en el paso t.
  • 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:

Wx = [ 0.5 ]      Wh = [ 0.8  -0.2 ]      b = [ 0 ]
     [-0.3 ]           [ 0.1   0.6 ]          [ 0 ]

Y la secuencia de entrada x = [1, 0, 1] (tres pasos de tiempo). Partimos de h₀ = [0, 0].

Paso 1 (x₁ = 1):

z₁ = Wx·1 + Wh·[0,0] + b = [0.5, -0.3]
h₁ = tanh([0.5, -0.3]) = [0.462, -0.291]

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 el summary()).
  • Por defecto la capa devuelve solo el último estado h_10: perfecto para many-to-one. (El parámetro return_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 con X.reshape(n, pasos, 1) o X[..., 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 tanh satura. 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

  1. A mano: con los pesos del ejemplo (Wx = [0.5, -0.3]ᵀ, Wh = [[0.8, -0.2], [0.1, 0.6]], b = 0), calcula h₁ y h₂ para la secuencia x = [0, 1]. Antes de calcular, razona: ¿qué valdrá h₁ y por qué?
  2. 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?
  3. 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

  1. Con x₁ = 0 y h₀ = [0,0]: z₁ = Wx·0 + Wh·[0,0] = [0,0], así que h₁ = tanh([0,0]) = [0, 0] — sin entrada y sin memoria previa, el estado sigue vacío. Con x₂ = 1: z₂ = Wx·1 + Wh·[0,0] = [0.5, -0.3], luego h₂ = [0.462, -0.291] (igual que el paso 1 del ejemplo, porque el contexto previo era nulo).
  2. 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).
  3. (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

Módulo 2: Fundamentos de Redes Neuronales

Módulo 3: Redes Neuronales Convolucionales (CNN)

Módulo 4: Redes Neuronales Recurrentes (RNN)

Módulo 5: Técnicas Avanzadas en Deep Learning

Módulo 6: Herramientas y Frameworks

Módulo 7: Proyectos Prácticos

Módulo 8: Consideraciones Éticas y Futuro del Deep Learning

© Copyright 2026. Todos los derechos reservados