Ir al contenido principal

Modelos LSTM: guía completa de las redes de memoria a corto y largo plazo

Domina el funcionamiento interno de las redes LSTM, la base de los LLM modernos. Explora los mecanismos de compuertas, los gradientes y crea un clasificador de sentimiento con PyTorch.
Actualizado 17 sept 2026  · 15 min leer

Explora con IA

ChatGPTClaudePerplexity

Las redes neuronales recurrentes estándar tienen problemas de memoria. A medida que las secuencias superan los 20-30 pasos temporales, los gradientes o bien se encogen hasta casi cero o bien explotan durante la retropropagación, y la red pierde la capacidad de conectar partes distantes de la entrada. Hochreiter y Schmidhuber abordaron este problema en su artículo de 1997 al introducir las redes de memoria a corto y largo plazo (LSTM).

En esta guía, repasaré el funcionamiento interno de un LSTM antes de pasar a su implementación práctica en Python. En las secciones finales comparo LSTM con Transformers para que puedas elegir la arquitectura adecuada para tu caso de uso.

Te recomiendo encarecidamente nuestro curso Introduction to Deep Learning in PyTorch para conseguir experiencia práctica con PyTorch entrenando redes neuronales, que son la base de los modelos LSTM.

¿Qué es un modelo LSTM?

LSTM significa Long Short-Term Memory, un tipo de red neuronal recurrente (RNN) diseñada para manejar secuencias donde el contexto de mucho antes en la entrada sigue siendo relevante.

La idea central de los LSTM es un mecanismo de compuertas que regula el flujo de información a través de la red. En lugar de dejar pasar todas las señales por igual, las compuertas aprenden cuándo conservar el contexto y cuándo soltarlo. 

Esta memoria selectiva permite a los LSTM seguir dependencias a lo largo de cientos de pasos temporales, algo muy útil en modelado del lenguaje, reconocimiento de voz y predicción de series temporales. Para entender por qué existe esta arquitectura, conviene ver qué falla en las RNN estándar.

El problema del desvanecimiento del gradiente en RNN

Las redes recurrentes transmiten información de un paso temporal al siguiente a través de un estado oculto. En cada paso se multiplica este estado por pesos aprendidos, se actualiza y se pasa adelante. Para entrenar, hay que enviar las señales de error hacia atrás a través de todos esos pasos, y en cada pasada hacia atrás la señal se encoge un poco. Tras suficientes pasos, no queda nada.

Es como hacer una fotocopia de una fotocopia. Unas pocas veces y la copia se ve bien. Cincuenta veces y apenas se puede leer. Las RNN chocan con este muro alrededor de 20-30 pasos temporales, por eso les cuestan las secuencias largas.

Cómo resuelven los LSTM las dependencias a largo plazo

Los LSTM añaden una vía de memoria separada, llamada estado de celda, que corre en paralelo al estado oculto estándar. La diferencia es estructural: la información en el estado de celda fluye por suma en lugar de por multiplicaciones repetidas, así que no se degrada del mismo modo.

Para controlar qué entra y sale de esta memoria, los LSTM usan compuertas. Cada compuerta es una pequeña red neuronal que aprende a dar valores entre 0 y 1, actuando como filtro. Un valor cercano a 0 bloquea información; uno cercano a 1 la deja pasar. La red aprende durante el entrenamiento qué patrones merece la pena recordar a lo largo de secuencias largas y cuáles se pueden descartar.

Este mecanismo de compuertas es lo que separa a los LSTM de las RNN básicas. En lugar de forzar toda la información por el mismo camino, los LSTM pueden preservar selectivamente el contexto durante cientos de pasos. En la siguiente sección desglosamos exactamente cómo funcionan estas compuertas y cómo se combinan para actualizar el estado de celda.

Casos de uso de los modelos LSTM

Los LSTM aparecen allá donde importan los datos secuenciales. Algunos ejemplos más allá de las aplicaciones típicas de PLN y forecasting:

  • Los bancos analizan secuencias de transacciones con LSTM para detectar fraudes que se escapan a sistemas basados en reglas
  • Los hospitales monitorizan trazas de ECG con LSTM bidireccionales, detectando anomalías cardiacas más rápido que las alertas por umbral
  • Las plataformas de smart city predicen tráfico y demanda energética a partir de sensores, ajustando recursos antes de que lleguen los picos
  • Las fábricas siguen la vibración de equipos a lo largo del tiempo para programar mantenimiento antes de que algo falle

Arquitectura LSTM: compuertas y celdas de memoria

Esta sección cubre en detalle el interior de un LSTM. Si prefieres ir directo a la implementación, salta a la siguiente sección. Los detalles de arquitectura no son imprescindibles para usar LSTM, pero ayudan al depurar o ajustar.

Para entender cómo funcionan los LSTM, seguiremos un ejemplo concreto: procesar la secuencia "The clouds are dark..." para predecir la siguiente palabra. Cada componente tiene más sentido cuando se ve qué hace con datos reales.

Estado de celda y estado oculto en LSTM

Un LSTM mantiene dos vectores separados que fluyen por la red: el estado de celda y el estado oculto.

El estado de celda actúa como almacenamiento a largo plazo. Transporta información entre pasos temporales con modificaciones controladas en cada paso. Al procesar nuestra secuencia, el estado de celda podría guardar "apareció un sustantivo relacionado con el tiempo" tras ver "clouds" y mantener ese contexto durante "are", hasta que "dark" refuerza el patrón.

El estado oculto es la salida de la celda en cada paso. Alimenta las predicciones y los cálculos del siguiente paso. Tras procesar "dark", el estado oculto codifica algo como "adjetivo que describe condiciones meteorológicas" que puede usar una capa de predicción.

LSTM Cell

¿Por qué dos vías separadas? Un único estado tendría que sostener a la vez el contexto a largo plazo y producir salidas útiles. Estos objetivos chocan: la memoria estable requiere mínimos cambios, mientras que generar salidas útiles requiere transformar. Separarlos permite que cada vía optimice su propósito.

En cada paso temporal, la celda recibe tres entradas y produce dos salidas:

Entradas:

  • Estado de celda previo (C_{t-1}): lo que la red recuerda hasta ahora

  • Estado oculto previo (h_{t-1}): la salida del último paso

  • Entrada actual (x_t): el nuevo token, como "clouds" o "dark"

Salidas:

  • Estado de celda actualizado (C_t): memoria tras procesar este token

  • Nuevo estado oculto (h_t): la salida de este paso

Cómo calculan las compuertas de un LSTM

Cuatro componentes controlan lo que ocurre dentro de la celda. Tres son compuertas que devuelven valores entre 0 y 1, actuando como filtros. El cuarto es el candidato, que propone contenido nuevo.

Cuando llega "are" después de "clouds", cada componente tiene un cometido:

  • Compuerta de olvido (f_t): ¿Debemos mantener el contexto de "clouds"? Los sustantivos meteorológicos seguidos de verbos copulativos suelen importar, así que la compuerta de olvido da valores cercanos a 1 para preservar la mayor parte del estado de celda.

  • Compuerta de entrada (i_t): ¿Cuánto debe aportar "are" a la memoria? Los verbos copulativos llevan menos señal predictiva, así que la compuerta de entrada devuelve valores más bajos.

  • Candidato (C̃_t): ¿Qué nueva información podría añadir "are"? El candidato propone contenido, pero la compuerta de entrada lo atenúa.

  • Compuerta de salida (o_t): ¿Qué debe mostrar el estado oculto? La compuerta de salida filtra el estado de celda para producir h_t.

How LSTM gates compute

Cada componente calcula su salida del mismo modo:

  1. Concatenar h_{t-1} y x_t en un único vector

  2. Multiplicar por una matriz de pesos aprendida

  3. Sumar un término de sesgo

  4. Aplicar una función de activación

Las compuertas usan activación sigmoide, que comprime los valores al rango 0-1. El candidato usa tanh, devolviendo valores entre -1 y 1, para poder proponer aumentos y disminuciones al estado de celda.

Las ecuaciones

Compuerta de olvido (sigmoide → 0 a 1):

Compuerta de entrada (sigmoide → 0 a 1):

Compuerta de salida (sigmoide → 0 a 1):

Candidato (tanh → -1 a +1):

Cada componente tiene su propia matriz de pesos (W_f, W_i, W_c, W_o), por lo que aprenden a responder a distintos patrones en la entrada.

Cómo funciona el filtrado

Las salidas de las compuertas se multiplican contra vectores posición por posición (multiplicación elemento a elemento, escrita como ⊙). Cada dimensión se filtra de forma independiente.

Si una compuerta da 0,9 para la posición 1, esa dimensión conserva el 90% de su valor. Si da 0,0 para la posición 4, esa dimensión se borra. Al procesar "dark" tras "clouds are", la compuerta de olvido podría dar valores altos para dimensiones que guardan el contexto de sustantivo/tiempo y valores más bajos para dimensiones con contexto previo irrelevante.

Flujo de información entre compuertas de olvido, adición y salida

Los componentes se combinan en tres pasos para actualizar el estado de celda y producir el estado oculto.

Paso 1: olvidar

La compuerta de olvido filtra el estado de celda previo:

Procesando "dark", la compuerta de olvido preserva el contexto meteorológico de "clouds" mientras limpia las dimensiones que seguían el artículo "The".

Paso 2: añadir

La compuerta de entrada escala la propuesta del candidato y luego la suma al estado de celda filtrado:

El candidato podría proponer "adjetivo que modifica a sustantivo meteorológico", y la compuerta de entrada decide con qué fuerza escribirlo en memoria. Esta actualización aditiva (no multiplicativa) es lo que ayuda a que los gradientes fluyan durante el entrenamiento.

Paso 3: salida

El estado de celda pasa por tanh y después la compuerta de salida filtra lo que se convierte en estado oculto:

La tanh mantiene los valores acotados. La compuerta de salida selecciona qué aspectos de la memoria acumulada deben ser visibles para las predicciones en este paso.

La secuencia completa:

Aquí tienes un seguimiento simplificado con un estado de celda de 4 dimensiones:

Paso

Entrada

Estado de celda (simplificado)

Qué ocurre

1

"The"

[0.1, 0.0, 0.2, 0.0]

Almacenamiento mínimo; los artículos aportan poca señal predictiva

2

"clouds"

[0.1, 0.7, 0.2, 0.3]

La compuerta de entrada se abre en las dimensiones 2 y 4; guarda patrón de sustantivo

3

"are"

[0.1, 0.6, 0.2, 0.3]

La compuerta de olvido ≈ 0.9 preserva el contexto; el verbo copulativo aporta poco

4

"dark"

[0.1, 0.8, 0.5, 0.6]

El adjetivo refuerza la señal en las dimensiones 2, 3 y 4

En el paso 4, las dimensiones 2-4 han acumulado un patrón. El estado oculto h_4 alimenta una capa de predicción (no parte del LSTM), que produce probabilidades sobre el vocabulario. Ese patrón acumulado ayuda al modelo a favorecer palabras como "rain" o "sky".

La red no entiende de meteorología. Aprende correlaciones estadísticas: ciertas secuencias de entrada tienden a preceder a ciertas salidas.

Flujo de gradientes durante el entrenamiento

La estructura aditiva del estado de celda proporciona una vía de gradiente más directa que las RNN estándar. Si no estás familiarizado con cómo se propagan los gradientes por las redes neuronales, el tutorial de backpropagation cubre los fundamentos.

En las RNN estándar, los gradientes fluyen a través de matrices de pesos en cada paso. La multiplicación repetida se compone: tras 100 pasos con pesos que promedian 0,9, el gradiente se reduce a 0.9^100 ≈ 0.00003.

Los LSTM ofrecen una vía alternativa a lo largo del estado de celda. La derivada de C_t con respecto a C_{t-1}:

∂C_t/∂C_{t-1} = f_t

Este es el valor de la compuerta de olvido, no una matriz de pesos. Cuando las compuertas de olvido se mantienen cerca de 1, los gradientes pasan con menos decaimiento. Si las compuertas promedian 0,95, tras 100 pasos: 0.95^100 ≈ 0.006. Aún reducido, pero útil.

Aspecto

RNN

LSTM

Vía de gradiente

Solo matrices de pesos

Vía adicional a lo largo del estado de celda

Gradiente a 100 pasos

~10^-5 (desvanecido)

~10^-2 a 10^-1 (utilizable)

Memoria práctica

~10-20 pasos temporales

~50-200 pasos temporales

Nota práctica: a menudo se inicializan los sesgos de la compuerta de olvido en 1.0 o más, empujando las salidas iniciales hacia "recuerda todo". Esto ayuda a la red a aprender patrones a largo plazo al inicio del entrenamiento.

Los LSTM mitigan el problema del desvanecimiento del gradiente más que eliminarlo. La mejora viene de la actualización aditiva del estado de celda, que proporciona una autopista de gradientes junto a las vías estándar.

Cómo implementar modelos LSTM en Python

La sección anterior cubrió lo que ocurre dentro de una celda LSTM: compuertas filtrando información, estado de celda preservando contexto y estado oculto produciendo salidas. Ahora veremos cómo nn.LSTM de PyTorch envuelve todo eso en un único módulo y lo usa para clasificar reseñas de películas como positivas o negativas.

Consejo para ejecutar el código: entrenar 50 épocas en una CPU tarda bastante. Usa Google Colab o Kaggle Notebooks con aceleración GPU para resultados más rápidos. El script completo está disponible como GitHub Gist.

Construir un clasificador LSTM con PyTorch

La clasificación de texto con LSTM sigue un patrón: convertir palabras en vectores, dejar que el LSTM las procese secuencialmente y después usar la salida final para predecir.

Convertir palabras en vectores

Las redes neuronales operan con números continuos, pero las palabras son símbolos discretos. No podemos meter "terrible" directamente en una multiplicación de matrices. La solución es una capa de embeddings: una tabla de búsqueda en la que cada palabra se asigna a un vector aprendible.

self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=padding_idx)

Si eliges embedding_dim=32, cada palabra se convierte en un vector de 32 dimensiones. Al principio del entrenamiento, estos vectores son aleatorios. A medida que el modelo aprende, palabras con sentimiento similar se acercan en ese espacio vectorial. "Terrible", "awful" y "boring" podrían acabar cerca, mientras que "excellent" y "amazing" formarían otro grupo.

El argumento padding_idx indica a la capa que mantenga los tokens de relleno como vectores cero, para que no influyan en el modelo.

Definir la arquitectura LSTM

self.lstm = nn.LSTM(
	input_size=embedding_dim,
	hidden_size=hidden_size,
	num_layers=num_layers,
	batch_first=True,
	dropout=dropout if num_layers > 1 else 0,
	bidirectional=bidirectional,
)

Con esta sola línea creas toda la maquinaria de compuertas de la sección anterior. El hidden_size determina cuánta información pueden transportar el estado de celda y el estado oculto. Valores mayores permiten seguir más patrones, pero exigen más cómputo.

Configurar batch_first=True implica que las entradas tienen forma (batch, sequence_length, embedding_dim). El LSTM procesa cada posición de izquierda a derecha, actualizando sus estados internos en cada paso.

Procesar la secuencia

Tras procesar "This movie was absolutely terrible", el LSTM ha visto cada palabra. Su estado oculto final ha acumulado lo que el modelo ha aprendido a extraer de esa secuencia. Para sentimiento, esto podría codificar algo como "adjetivo fuertemente negativo al final, que anula contenido neutro anterior".

def forward(self, x):
	embedded = self.embedding(x)
	lstm_out, (h_n, c_n) = self.lstm(embedded)
	h_final = h_n[-1]
	return self.fc(h_final)

El LSTM devuelve salidas por paso (lstm_out) y estados finales (h_n, c_n). Para clasificar solo necesitamos h_n[-1], el estado oculto tras el último token. Una capa lineal lo mapea a puntuaciones de clase.

Trazar una reseña a través del modelo

Cuando "This movie was terrible" entra en el clasificador:

  1. Cada palabra obtiene su embedding: cuatro vectores de forma (32,)

  2. El LSTM los procesa secuencialmente. "This" inicializa cierto estado de celda. "Movie" lo actualiza ligeramente. "Was" es neutra y cambia poco. "Terrible" hace que la compuerta de olvido borre contexto irrelevante y que la compuerta de entrada escriba una señal negativa fuerte.

  3. El estado oculto final captura el patrón acumulado.

  4. La capa lineal lo mapea a dos puntuaciones: una para positivo y otra para negativo

El modelo no "entiende" el lenguaje. Aprende patrones estadísticos: ciertas secuencias de palabras se correlacionan con ciertas etiquetas.

La clase completa del modelo

A continuación puedes ver la clase LSTMClassifier completa que hemos creado paso a paso:

import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
from datasets import load_dataset
from collections import Counter
import numpy as np
 
torch.manual_seed(42)
np.random.seed(42)
 
 
class LSTMClassifier(nn.Module):
    def __init__(
        Self,
        Vocab_size,
        Embedding_dim,
        Hidden_size,
        Output_size,
        num_layers=1,
        dropout=0.0,
        bidirectional=False,
        padding_idx=0,
    ):
        super().__init__()
        self.hidden_size = hidden_size
    	self.num_layers = num_layers
    	self.bidirectional = bidirectional
 
    	self.embedding = nn.Embedding(
	    vocab_size, embedding_dim, padding_idx=padding_idx
        )
    	self.lstm = nn.LSTM(
            input_size=embedding_dim,
            hidden_size=hidden_size,
            num_layers=num_layers,
            batch_first=True,
            dropout=dropout if num_layers > 1 else 0,
            bidirectional=bidirectional,
    	)	
 
    	fc_input_size = hidden_size * 2 if bidirectional else hidden_size
    	self.fc = nn.Linear(fc_input_size, output_size)
 
    def forward(self, x):
    	embedded = self.embedding(x)
    	lstm_out, (h_n, c_n) = self.lstm(embedded)
 
    	if self.bidirectional:
            h_final = torch.cat([h_n[-2], h_n[-1]], dim=1)
    	else:
            h_final = h_n[-1]
 
    	return self.fc(h_final)

La opción bidireccional ejecuta dos LSTM: uno de izquierda a derecha y otro de derecha a izquierda. Sus estados ocultos finales se concatenan, dando al modelo contexto de ambas direcciones. Esto ayuda cuando el significado depende de lo que viene después, como "not bad", donde "not" solo cobra sentido con "bad".

Preparar el texto para el modelo

Los LSTM necesitan secuencias de enteros, no cadenas de texto. El preprocesamiento convierte el texto bruto en tres pasos: 

  1. Construir un vocabulario que mapee palabras a índices
  2. Convertir cada reseña en una secuencia de índices
  3. Rellenar (pad) las secuencias hasta una longitud uniforme
def build_vocab(texts, max_vocab_size=10000):
	counter = Counter()
	for text in texts:
    		counter.update(text.lower().split())
 
	vocab = {"<pad>": 0, "<unk>": 1}
	for word, _ in counter.most_common(max_vocab_size - 2):
    		vocab[word] = len(vocab)
	return vocab

Reservamos el índice 0 para el relleno y el 1 para palabras desconocidas. Limitar el tamaño del vocabulario filtra palabras raras que no tienen suficientes ejemplos para aprender embeddings significativos.

def tokenize_and_pad(texts, vocab, max_length=200):
	sequences = []
	for text in texts:
    		tokens = text.lower().split()
    		ids = [vocab.get(t, vocab["<unk>"]) for t in tokens[:max_length]]
    		ids = ids + [vocab["<pad>"]] * (max_length - len(ids))
    		sequences.append(ids)
	return torch.tensor(sequences, dtype=torch.long)

Truncar a max_length limita cuánto atrás debe recordar el LSTM. Secuencias más largas preservan más contexto, pero aumentan el coste computacional y de memoria. Para reseñas de cine, 100-200 tokens suelen capturar las señales clave de sentimiento.

El relleno garantiza que cada secuencia tenga la misma longitud, permitiendo a PyTorch agruparlas en un único tensor. Los tokens de relleno se convierten en vectores cero tras el embedding y aportan poco al estado final.

def load_imdb_data(max_samples=10000, max_length=200, max_vocab=10000):
	dataset = load_dataset("imdb")
 
	train_data = dataset["train"].shuffle(seed=42)
	test_data = dataset["test"].shuffle(seed=42)
 
	train_texts = train_data["text"][:max_samples]
	train_labels = train_data["label"][:max_samples]
	test_texts = test_data["text"][: max_samples // 4]
	test_labels = test_data["label"][: max_samples // 4]
 
	vocab = build_vocab(train_texts, max_vocab)
 
	X_train = tokenize_and_pad(train_texts, vocab, max_length)
	X_test = tokenize_and_pad(test_texts, vocab, max_length)
	y_train = torch.tensor(train_labels, dtype=torch.long)
	y_test = torch.tensor(test_labels, dtype=torch.long)
 
	return X_train, y_train, X_test, y_test, vocab

Construir el vocabulario solo con datos de entrenamiento evita fugas de información. Si una palabra rara aparece solo en reseñas de test, el modelo debería tratarla como desconocida en lugar de haber aprendido su embedding durante el entrenamiento.

Entrenamiento de LSTM e hiperparámetros

El bucle de entrenamiento actualiza los pesos del modelo en función de cuánto se desvían sus predicciones de los valores correctos. En LSTM, un detalle importa más de lo habitual: el recorte de gradientes.

def train_model(model, train_loader, test_loader, n_epochs=5, lr=0.001):
	criterion = nn.CrossEntropyLoss()
	optimizer = torch.optim.Adam(model.parameters(), lr=lr)
 
	for epoch in range(n_epochs):
    		model.train()
    		total_loss, correct, total = 0, 0, 0
 
    		for batch_texts, batch_labels in train_loader:
        		optimizer.zero_grad()
        		outputs = model(batch_texts)
        		loss = criterion(outputs, batch_labels)
        		loss.backward()
            		torch.nn.utils.clip_grad_norm_(
	model.parameters(), 
	max_norm=5.0)
        		optimizer.step()
 
        		total_loss += loss.item()
        		_, predicted = outputs.max(1)
        		correct += (predicted == batch_labels).sum().item()
        		total += batch_labels.size(0)

Por qué importa el recorte de gradientes en LSTM

Antes explicamos cómo los LSTM mitigan los gradientes que se desvanecen mediante actualizaciones aditivas del estado de celda. Pero los gradientes que explotan siguen siendo un riesgo. Cuando los gradientes crecen demasiado, las actualizaciones de pesos se pasan de rosca y desestabilizan el entrenamiento.

clip_grad_norm_ limita la magnitud total del gradiente. Si los gradientes fueran a superar max_norm=5.0, se escalan proporcionalmente a la baja. Así se mantienen actualizaciones estables sin eliminar por completo la señal de gradiente.

    	model.eval()
    	test_correct, test_total = 0, 0
    	with torch.no_grad():
        	for batch_texts, batch_labels in test_loader:
            		outputs = model(batch_texts)
            		_, predicted = outputs.max(1)
            		test_correct += (predicted == batch_labels).sum().item()
            		test_total += batch_labels.size(0)
 
    	test_acc = test_correct / test_total
    	print(f"Epoch {epoch+1}: Loss={total_loss/len(train_loader):.4f}, "
          	f"Train={correct/total:.3f}, Test={test_acc:.3f}")
 
    return test_acc

model.eval() desactiva el dropout durante la evaluación. torch.no_grad() evita calcular gradientes cuando no vamos a actualizar pesos, lo que ahorra memoria y acelera la inferencia.

Elegir hiperparámetros

X_train, y_train, X_test, y_test, vocab = load_imdb_data(
	max_samples=10000, max_length=100, max_vocab=5000
)
 
train_loader = DataLoader(TensorDataset(X_train, y_train), batch_size=64, shuffle=True)
test_loader = DataLoader(TensorDataset(X_test, y_test), batch_size=64)
 
model = LSTMClassifier(
	vocab_size=len(vocab),
	embedding_dim=32,
	hidden_size=32,
	output_size=2,
	padding_idx=vocab["<pad>"],
)
 
final_acc = train_model(model, train_loader, test_loader, n_epochs=50, lr=0.001)

Estos valores reflejan equilibrios:

  • hidden_size=32: Lo bastante pequeño para iterar rápido. En producción se suelen usar 128-512, pero a medida que crece el tamaño los beneficios disminuyen y el entrenamiento se ralentiza.

  • embedding_dim=32: Igualar el tamaño oculto es común pero no obligatorio. Embeddings más grandes capturan matices más finos a costa de más parámetros.

  • max_length=100: La mayoría de señales de sentimiento aparecen pronto en las reseñas. Secuencias más largas ayudan en casos sutiles, pero cuadruplican la memoria al doblar max_length.

  • batch_size=64: Lotes mayores dan estimaciones de gradiente más estables. Lotes más pequeños pueden ayudar a salir de mínimos locales. 32-128 es típico.

  • lr=0.001: El valor por defecto de Adam funciona en la mayoría de tareas con LSTM. Si la pérdida oscila, prueba 0.0001. Si el aprendizaje se estanca, prueba 0.01.

  • n_epochs=50: Suficiente para que el modelo converja. Vigila si la precisión en test se estanca o baja mientras la de train sigue subiendo: indica sobreajuste.

Qué aprende el modelo

Con esta configuración, verás algo como:

...
Epoch 46: Loss=0.4126, Train=0.812, Test=0.789
Epoch 47: Loss=0.4054, Train=0.815, Test=0.791
Epoch 48: Loss=0.3998, Train=0.818, Test=0.788
Epoch 49: Loss=0.3921, Train=0.821, Test=0.793
Epoch 50: Loss=0.3867, Train=0.824, Test=0.795

La precisión de entrenamiento alcanza alrededor del 82% y la de test se estabiliza cerca del 80%. El modelo aprende qué patrones de palabras predicen sentimiento. Los adjetivos fuertes al final pesan más. Negaciones como "not" invierten la polaridad. Ciertas frases ("waste of time", "highly recommend") se vuelven señales claras. Nada de esto está programado: emerge de las correlaciones del conjunto de entrenamiento.

Con un ajuste mínimo y un modelo pequeño, un 80% de exactitud es razonable. La pequeña brecha entre training y test muestra que el modelo generaliza bien sin sobreajustar en exceso. Añadir dropout, usar embeddings preentrenados como GloVe o entrenar con más datos podría llevar la precisión a finales de los 80.

Cómo mejorar el rendimiento de los modelos LSTM

La arquitectura LSTM básica de las secciones anteriores funciona, pero varias técnicas pueden aumentar la precisión sin cambiar de familia de modelos. Estas aproximaciones han sido validadas en miles de despliegues en producción y artículos de investigación.

LSTM bidireccionales

Los LSTM estándar procesan secuencias de izquierda a derecha, construyendo contexto solo desde el pasado. Los LSTM bidireccionales (BiLSTM) ejecutan dos pasadas en paralelo: una hacia adelante y otra hacia atrás. La representación final en cada paso combina lo que vino antes y lo que viene después.

self.lstm = nn.LSTM(
	input_size=embedding_dim,
	hidden_size=hidden_size,
	bidirectional=True,  # Run both directions
	batch_first=True,
)
# Output size doubles: hidden_size * 2

En análisis de sentimiento, esto importa cuando la negación aparece después de la palabra que modifica: "the movie was not bad" necesita contexto hacia adelante para saber que "bad" existe, y hacia atrás para saber que "not" lo precede. 

Los BiLSTM superan de forma consistente a los modelos unidireccionales en reconocimiento de entidades (NER), clasificación de sentimiento y reconocimiento de voz, con mejoras típicas del 5-15%.

La contrapartida: necesitas la secuencia completa en inferencia. Las aplicaciones en streaming que reciben tokens uno a uno no pueden usar procesamiento bidireccional.

Añadir atención

La atención permite al modelo ponderar qué pasos temporales importan más para una predicción. En lugar de comprimir toda una secuencia en un solo estado oculto final, la atención calcula una combinación ponderada de todos los estados ocultos.

class AttentionLayer(nn.Module):
	def __init__(self, hidden_size):
    		super().__init__()
    		self.attention = nn.Linear(hidden_size, 1)
	
	def forward(self, lstm_outputs):
    	# lstm_outputs: (batch, seq_len, hidden_size)
    		scores = self.attention(lstm_outputs)  # (batch, seq_len, 1)
    		weights = torch.softmax(scores, dim=1)
    		context = torch.sum(weights * lstm_outputs, dim=1)
    		return context

En una reseña como "The acting was wooden, the plot made no sense, but the cinematography was stunning", la atención aprende a centrarse en las frases que portan sentimiento en lugar de tratar todas las palabras por igual. El modelo asigna pesos más altos a "wooden", "no sense" y "stunning" y reduce "the" y "was".

La atención añade poca sobrecarga computacional y suele aportar mejoras del 2-5% en clasificación. En problemas secuencia a secuencia como traducción, la atención entre codificador y decodificador es casi imprescindible para un buen rendimiento.

Apilar capas LSTM

Una sola capa LSTM capta patrones a un nivel de abstracción. Al apilar varias capas, la red aprende características jerárquicas: la primera capa puede detectar patrones locales de frases, mientras que las superiores combinan esos patrones en comprensión a nivel de documento.

self.lstm = nn.LSTM(
	input_size=embedding_dim,
	hidden_size=hidden_size,
	num_layers=3,  # Stack three layers
	dropout=0.3,   # Dropout between layers (not within)
	batch_first=True,
)

Pautas para la profundidad:

  • 1 capa: Tareas sencillas, conjuntos pequeños, iteración rápida
  • 2 capas: La mayoría de aplicaciones en producción
  • 3-4 capas: Dependencias temporales complejas, conjuntos grandes
  • Más de 4: Rendimientos decrecientes, inestabilidad en entrenamiento

Cada capa adicional aproximadamente dobla el tiempo de entrenamiento. Empieza con poca profundidad y añádela solo si las métricas de validación se estancan.

Técnicas de regularización

Los LSTM sobreajustan con facilidad, especialmente en conjuntos pequeños. Tres técnicas funcionan bien juntas:

  • Dropout
  • Recorte de gradientes
  • Early stopping

Dropout

El dropout pone a cero neuronas aleatoriamente durante el entrenamiento, obligando a la red a distribuir el aprendizaje por múltiples vías. Aplícalo entre capas LSTM, no dentro de las conexiones recurrentes (puede estropear el aprendizaje temporal).

# PyTorch aplica dropout entre capas apiladas automáticamente
self.lstm = nn.LSTM(..., num_layers=2, dropout=0.3)
 
# Para LSTM de una sola capa, añade dropout después
self.dropout = nn.Dropout(0.3)

Tasas de 0.2-0.5 funcionan en la mayoría de tareas. Valores más altos ayudan con sobreajuste severo pero ralentizan la convergencia.

Recorte de gradientes

El recorte de gradientes limita su magnitud durante la retropropagación. Aunque los LSTM mitigan los gradientes que se desvanecen, los que explotan siguen ocurriendo, sobre todo con secuencias largas o redes profundas.

torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)

Valores entre 1.0 y 5.0 son típicos. Si la pérdida de entrenamiento se dispara o produce NaN, baja el umbral.

Early stopping

El early stopping monitoriza la pérdida de validación y detiene el entrenamiento cuando deja de mejorar. Así evitas que el modelo memorice el entrenamiento a costa de la generalización.

patience = 5
best_val_loss = float('inf')
epochs_without_improvement = 0
 
for epoch in range(max_epochs):
	train_loss = train_one_epoch(model, train_loader)
	val_loss = evaluate(model, val_loader)
	
	if val_loss < best_val_loss:
    		best_val_loss = val_loss
    		epochs_without_improvement = 0
    		torch.save(model.state_dict(), 'best_model.pt')
	else:
    		epochs_without_improvement += 1
	if epochs_without_improvement >= patience:
        	break

Para un recorrido completo por bucles de entrenamiento, métricas de evaluación y depuración de problemas comunes en LSTM, consulta la guía de NLP con PyTorch.

Embeddings preentrenados

Inicializar embeddings al azar funciona, pero embeddings preentrenados como GloVe o Word2Vec le dan al modelo una ventaja inicial. Estos embeddings codifican relaciones semánticas aprendidas de miles de millones de palabras: "king" y "queen" acaban cerca, igual que "terrible" y "awful".

import numpy as np
 
def load_glove_embeddings(glove_path, vocab, embedding_dim):
	embeddings = np.random.randn(len(vocab), embedding_dim) * 0.01
	
	with open(glove_path, 'r') as f:
    	for line in f:
        	parts = line.split()
        	word = parts[0]
        	if word in vocab:
            		embeddings[vocab[word]] = np.array(parts[1:], dtype=np.float32)
	
	return torch.tensor(embeddings, dtype=torch.float32)
 
# Load and freeze or fine-tune
pretrained = load_glove_embeddings('glove.6B.100d.txt', vocab, 100)
model.embedding.weight.data.copy_(pretrained)
model.embedding.weight.requires_grad = True  # Fine-tune during training

Los embeddings preentrenados ayudan sobre todo cuando los datos de entrenamiento son limitados. Con millones de ejemplos, los embeddings aleatorios acaban alcanzando. En aplicaciones prácticas con 10k-100k muestras, espera mejoras del 3-8% con inicialización preentrenada.

Combinar técnicas

Estas técnicas se pueden apilar. Un clasificador de texto de producción podría usar:

  • BiLSTM para contexto bidireccional
  • 2 capas apiladas para rasgos jerárquicos
  • Pooling con atención en lugar del estado oculto final
  • Embeddings GloVe ajustados durante el entrenamiento
  • Dropout de 0.3 entre capas
  • Recorte de gradiente a 5.0
  • Early stopping con paciencia de 5 épocas

Cada añadido incrementa la complejidad y el tiempo de entrenamiento. Empieza con un LSTM sencillo de una capa, mide el rendimiento y luego añade técnicas una a una para ver qué te aporta en tu tarea concreta.

LSTM vs Transformers: cómo elegir la arquitectura adecuada

Los LSTM dominaron el modelado secuencial desde finales de los 90 hasta mediados de los 2010. El artículo de 2017 "Attention Is All You Need" cambió eso. Los Transformers impulsan la mayoría de LLM actuales, pero los LSTM no han desaparecido. Cubren otros nichos.

Limitaciones de LSTM

Ya vimos cómo los LSTM procesan secuencias de un token en un token y por qué su mecanismo de compuertas funciona. A gran escala, esa dependencia secuencial se convierte en cuello de botella. Tu GPU puede tener miles de núcleos, pero la mayoría quedan ociosos mientras se completa la cadena del estado oculto. Una entrada de 500 tokens implica 500 operaciones en serie.

Los Transformers no tienen esta restricción. La autoatención calcula relaciones entre todas las posiciones simultáneamente, así que esa secuencia de 500 tokens se procesa en paralelo. En hardware moderno con lotes grandes, esto se traduce en aceleraciones de entrenamiento de 10 a 100 veces.

También importa el límite de longitud de secuencia. Vimos cómo los LSTM preservan mejor los gradientes que las RNN básicas, pero "mejor" no significa "perfecto". El rendimiento sigue cayendo en torno a 200-500 tokens, según la tarea. 

Los Transformers calculan atención entre cada par de posiciones directamente, así que el token 1 atiende al 1000 con la misma facilidad. La contrapartida es la memoria: las matrices de atención crecen cuadráticamente con la longitud de la secuencia.

Las activaciones de las compuertas también dificultan la interpretación. Los valores van de 0 a 1 sin límites claros. Los pesos de atención en Transformers tampoco son explicaciones perfectas, pero dan una señal más clara de qué tokens de entrada importaron para cada salida.

Cuándo usar LSTM o Transformers

Tiene sentido usar Transformers cuando:

  • Los datos de entrenamiento superan 100K ejemplos y tienes GPU
  • La tarea implica dependencias de cientos de tokens
  • La velocidad de entrenamiento importa para iterar
  • Puedes ajustar modelos preentrenados como GPT a tu caso

Los LSTM encajan cuando:

  • El dispositivo objetivo tiene memoria muy limitada (edge, embebido, móvil)
  • Hay pocos datos (menos de 10K ejemplos) y un Transformer sobreajustaría
  • Las predicciones deben hacerse con secuencias parciales antes de recibir toda la entrada
  • Los requisitos de latencia exigen modelos ligeros

La previsión de series temporales queda en terreno intermedio. Benchmarks recientes muestran un rendimiento similar en datos univariantes con patrones consistentes. Los Transformers destacan cuando interactúan múltiples variables de forma compleja, pero los LSTM aguantan bien cuando faltan datos o el cómputo es ajustado.

Los diseños híbridos combinan ambos enfoques. Algunas arquitecturas apilan capas LSTM con mecanismos de atención. xLSTM, publicado en 2024, revisita el diseño original con compuertas exponenciales y memoria matricial, mostrando resultados competitivos con Transformers a escalas más pequeñas.

Aspecto

LSTM

Transformer

Procesamiento

Secuencial (token a token)

Paralelo (todas las posiciones a la vez)

Velocidad de entrenamiento

Más lenta (paralelización limitada)

Más rápida en GPU

Dependencias de largo alcance

Degradan más allá de 200-500 tokens

Maneja miles de tokens

Escalado de memoria

O(n) con la longitud

O(n²) por matrices de atención

Rendimiento con pocos datos

Bueno (menos parámetros)

Propenso al sobreajuste

Despliegue en edge

Adecuado (menor huella de memoria)

Complicado (modelos grandes)

Streaming/tiempo real

Soporte nativo

Requiere la secuencia completa

Ecosistema preentrenado

Limitado

Extenso (BERT, GPT, etc.)

La decisión suele depender de las restricciones. ¿Cómputo y datos sin límite? Los Transformers ganan en la mayoría de tareas de PLN. ¿Un microcontrolador con 10K ejemplos de entrenamiento? Los LSTM siguen siendo prácticos.

Conclusión

Los LSTM resolvieron un problema real cuando llegaron en 1997. El mecanismo de compuertas introducido por Hochreiter y Schmidhuber permitió que las redes por fin aprendieran de secuencias más largas que unas pocas decenas de tokens. Esa arquitectura impulsó la traducción automática, el reconocimiento de voz y la predicción de series temporales durante casi dos décadas.

Hoy, los Transformers manejan la mayoría de tareas de PLN a gran escala, pero los LSTM no han quedado obsoletos. Siguen teniendo sentido para despliegues en edge, aplicaciones en streaming y situaciones con datos de entrenamiento limitados.

Si vas a empezar un proyecto nuevo de modelado secuencial, prueba primero con un LSTM sencillo. Mide su rendimiento, entiende sus puntos débiles y decide luego si el problema realmente requiere un Transformer. Los conceptos de esta guía (compuertas, estado de celda, flujo de gradientes) te ayudarán a entender arquitecturas más complejas.

Para practicar, te recomiendo cursar nuestro curso de Recurrent Neural Networks, que recorre más aplicaciones de LSTM con ejercicios guiados.

Preguntas frecuentes sobre LSTM

¿Cuál es la diferencia entre un LSTM y una RNN normal?

Las RNN normales tienen dificultades con secuencias largas porque los gradientes se encogen durante la retropropagación y pierden la capacidad de aprender de contexto distante. Los LSTM añaden un estado de celda con mecanismos de compuertas que controlan el flujo de información, permitiendo a la red conservar contexto relevante a lo largo de cientos de pasos en lugar de solo 20-30.

¿Cuándo debería usar LSTM en lugar de Transformers?

Los LSTM funcionan mejor cuando tienes pocos datos de entrenamiento (menos de 10K ejemplos), necesitas ejecutar en dispositivos con memoria limitada como móviles o sistemas embebidos, requieres predicciones en tiempo real sobre datos en streaming antes de recibir la secuencia completa, o tienes presupuestos de latencia estrictos que descartan modelos más grandes.

¿Qué significa hidden_size en el LSTM de PyTorch?

El parámetro hidden_size determina la dimensionalidad de los vectores de estado oculto y de celda. Valores mayores permiten al modelo rastrear más patrones, pero aumentan el cómputo y la memoria. Entre 32 y 512 es común; 128-256 es típico en texto en producción.

¿Cómo evito que mi LSTM sobreajuste?

Usa dropout entre capas LSTM (tasas de 0.2-0.5), aplica recorte de gradiente para evitar explosiones, implementa early stopping para detener el entrenamiento cuando la validación deje de mejorar y valora usar embeddings preentrenados como GloVe si tu conjunto es pequeño.

¿Qué es un LSTM bidireccional y cuándo debo usarlo?

Un LSTM bidireccional lanza dos pasadas sobre la secuencia: una hacia adelante y otra hacia atrás. Así cada posición tiene contexto de ambas direcciones, lo que ayuda cuando el significado depende de tokens futuros (como "not bad", donde "not" necesita ver "bad"). Úsalo para clasificación y etiquetado cuando tengas la secuencia completa en inferencia, pero evítalo en streaming.


Bexruz (Bex) Tuychiev's photo
Author
Bexruz (Bex) Tuychiev
LinkedIn

Soy creador de contenidos sobre ciencia de datos con más de 2 años de experiencia y uno de los mayores seguimientos en Medium. Me gusta escribir artículos detallados sobre IA y ML con un toque sarcástico, porque hay que darle algo de vidilla al tema. He publicado más de 130 artículos y un curso en DataCamp, y tengo otro en marcha. Mis contenidos han sido vistos por más de 5 millones de personas; 20.000 de ellas se convirtieron en seguidores tanto en Medium como en LinkedIn. 

Temas
Aprendizaje profundo
Aprendizaje automático

Cursos de machine learning

Curso

Redes neuronales recurrentes (RNN) para el modelado del lenguaje con Keras

4 h
16.4K
Aprende a utilizar las RNN para clasificar el sentimiento del texto, generar frases y traducir texto entre idiomas.
Ver detallesRight Arrow
Empezar Curso
Ver másRight Arrow
Relacionado

Tutorial

Predicciones bursátiles con LSTM en Python

Descubra las redes de memoria larga a corto plazo (LSTM) en Python y cómo puede utilizarlas para hacer predicciones bursátiles.
Thushan Ganegedara's photo

Thushan Ganegedara

15 min

Tutorial

Cómo formar a un LLM con PyTorch

Domine el proceso de entrenamiento de grandes modelos lingüísticos con PyTorch, desde la configuración inicial hasta la implementación final.
Zoumana Keita 's photo

Zoumana Keita

8 min

Tutorial

Tutorial sobre cómo crear aplicaciones LLM con LangChain

Explore el potencial sin explotar de los grandes modelos lingüísticos con LangChain, un marco Python de código abierto para crear aplicaciones avanzadas de IA.
Moez Ali's photo

Moez Ali

12 min

Tutorial

Ajuste fino de LLaMA 2: Guía paso a paso para personalizar el modelo de lenguaje grande

Aprende a ajustar Llama-2 en Colab utilizando nuevas técnicas para superar las limitaciones de memoria y computación y hacer más accesibles los grandes modelos lingüísticos de código abierto.
Abid Ali Awan's photo

Abid Ali Awan

12 min

Tutorial

Guía introductoria para el ajuste preciso de los LLM

El ajuste preciso de los grandes modelos lingüísticos (LLM) ha revolucionado el procesamiento del lenguaje natural (PLN) y ofrece capacidades sin precedentes en tareas como la traducción lingüística, el análisis del sentimiento y la generación de textos. Este enfoque transformador aprovecha modelos preentrenados como el GPT-2 y mejora su rendimiento en dominios específicos mediante el proceso de ajuste preciso.
Josep Ferrer's photo

Josep Ferrer

11 min

Tutorial

Introducción a las redes neuronales profundas

Comprender las redes neuronales profundas y su importancia en el mundo moderno del aprendizaje profundo de la inteligencia artificial
Bharath K's photo

Bharath K

13 min

Ver MásVer Más