El segundo gran proyecto secuencial de TecnoMarket no trabaja con palabras sino con números fechados: las ventas diarias. Predecir la demanda de los próximos días decide cuánto stock pedir, cuánto personal reforzar en logística y qué promociones lanzar — equivocarse cuesta dinero en ambas direcciones (roturas de stock o almacenes llenos). En esta lección convertirás la predicción de demanda en un problema de aprendizaje supervisado mediante ventanas deslizantes, aprenderás a normalizar sin cometer el error clásico de la fuga de datos, construirás el imprescindible baseline ingenuo, entrenarás una LSTM sobre una serie sintética de ventas de TecnoMarket (con estacionalidad semanal y picos tipo Black Friday) y evaluarás honestamente en euros/unidades reales. Terminaremos con la predicción multi-paso y sus riesgos, y con el criterio para decidir cuándo una LSTM compensa frente a los métodos clásicos.
Contenido
- La demanda de TecnoMarket como serie temporal
- Componentes de una serie: tendencia, estacionalidad, ruido
- Ventanas deslizantes: de serie a dataset supervisado
- Normalización sin fuga de datos
- El baseline ingenuo: la vara de medir obligatoria
- LSTM sobre las ventas diarias de TecnoMarket
- Evaluación en unidades reales
- Predicción multi-paso: iterativa vs. directa
- ¿Cuándo compensa una LSTM frente a los métodos clásicos?
La demanda de TecnoMarket como serie temporal
Una serie temporal es una secuencia de observaciones tomadas a intervalos regulares: las unidades vendidas cada día, los pedidos por hora, las visitas por semana. Se diferencia de las secuencias de texto de 04-03 en tres aspectos prácticos:
- Los valores son continuos (no índices de vocabulario): no hay Embedding; el problema final es de regresión (MSE/MAE de 02-04), no de clasificación.
- El "futuro" existe de verdad: el modelo se usará para predecir valores que aún no han ocurrido, lo que impone reglas estrictas sobre qué datos puede ver durante el entrenamiento (y descarta
Bidirectional, como avisamos en 04-02). - El tiempo tiene estructura de calendario: los lunes se parecen a los lunes, noviembre no se parece a agosto.
Componentes de una serie: tendencia, estacionalidad, ruido
Casi cualquier serie de negocio se puede pensar como la suma de tres componentes:
| Componente | Qué es | En las ventas de TecnoMarket |
|---|---|---|
| Tendencia | Dirección de fondo a largo plazo | Crecimiento sostenido: la tienda gana clientes cada mes |
| Estacionalidad | Patrón que se repite con un periodo fijo | Semanal: picos en fin de semana; anual: Black Friday, Navidad, vuelta al cole |
| Ruido | Variación irregular no explicable | Un día de lluvia, un tuit viral, azar puro |
Generemos una serie sintética de 2 años de ventas diarias con estas tres componentes más eventos tipo Black Friday. Trabajar con datos sintéticos tiene una ventaja didáctica: conocemos la verdad que el modelo debe descubrir.
import numpy as np
rng = np.random.default_rng(7)
DIAS = 730 # 2 años
t = np.arange(DIAS)
tendencia = 200 + 0.15 * t # de ~200 a ~310 unidades/día
dia_semana = t % 7 # 0 = lunes ... 6 = domingo
estacional = np.where(dia_semana >= 5, 60, 0) + 10 * np.sin(2*np.pi*t/7)
ruido = rng.normal(0, 15, DIAS) # variación aleatoria (σ = 15)
ventas = tendencia + estacional + ruido
# Picos tipo Black Friday: día 328 y 693 (finales de noviembre de cada año)
for bf in (328, 693):
ventas[bf-3:bf+2] *= rng.uniform(2.2, 2.8) # unos días al x2.5
ventas = np.round(np.clip(ventas, 0, None)) # unidades enteras, no negativas
print(ventas[:10]) # p.ej. [214. 199. 217. 208. 224. 288. 273. 216. 204. 223.]Dibuja la serie (plt.plot(ventas)) y verás las tres capas: la pendiente suave, el dibujo semanal en sierra y dos agujas espectaculares en los Black Friday. La pregunta del módulo: ¿puede una LSTM aprender todo esto solo mirando los valores pasados?
Ventanas deslizantes: de serie a dataset supervisado
Una red supervisada necesita pares (entrada X, objetivo y), pero una serie es una sola tira de números. El truco universal es la ventana deslizante: usar los últimos V valores como entrada y el valor siguiente como objetivo, y deslizar la ventana a lo largo de toda la serie.
Ejemplo numérico con la mini-serie [10, 12, 15, 14, 18, 21, 19] y ventana V = 3:
| Ventana (X) | Objetivo (y) |
|---|---|
| [10, 12, 15] | 14 |
| [12, 15, 14] | 18 |
| [15, 14, 18] | 21 |
| [14, 18, 21] | 19 |
De 7 valores salen 7 − 3 = 4 ejemplos supervisados. Cada fila es una pregunta de examen: "vistos estos 3 días, ¿qué pasó al siguiente?". Es una tarea many-to-one (04-01): muchos pasos entran, un valor sale.
def crear_ventanas(serie, ventana):
"""Convierte una serie 1D en (X, y) supervisado con ventana deslizante."""
X, y = [], []
for i in range(len(serie) - ventana):
X.append(serie[i : i + ventana]) # V valores consecutivos
y.append(serie[i + ventana]) # el valor inmediatamente posterior
return np.array(X), np.array(y)
VENTANA = 28 # 4 semanas: suficiente para captar el patrón semanal varias vecesElegir V = 28 no es casual: la ventana debe ser más larga que el periodo estacional que quieres captar (aquí, 7 días) — idealmente conteniéndolo varias veces. Con V = 3, el modelo nunca podría "ver" que hoy es sábado comparándolo con el sábado anterior.
Normalización sin fuga de datos
Como con los píxeles de MNIST (los dividimos entre 255 en 02-05), las redes entrenan mejor con entradas en rangos pequeños; unas ventas entre 200 y 800 conviene escalarlas. Pero en series temporales acecha el error clásico: normalizar usando estadísticas de TODA la serie.
¿Por qué es grave? El máximo de la serie completa incluye los Black Friday futuros. Si escalas con él, cada dato de entrenamiento lleva incrustada una pista sobre el futuro ("las ventas llegarán a X") que en producción no tendrás. Es fuga de datos (data leakage): la evaluación saldrá optimista y el modelo decepcionará en el mundo real. Es el mismo principio que el adapt() solo-con-train de 04-03.
La regla de oro tiene dos partes:
- Divide primero, y por tiempo: en series temporales NUNCA se baraja para dividir train/test — el test debe ser el tramo final (el "futuro" respecto al train), porque así se usará el modelo.
- Ajusta el scaler solo con el train y aplícalo (sin reajustar) al test.
# 1. División temporal: primer 80 % para entrenar, último 20 % para evaluar
corte = int(DIAS * 0.8) # día 584
serie_train, serie_test = ventas[:corte], ventas[corte:]
# 2. Estadísticas SOLO del train
media, desv = serie_train.mean(), serie_train.std()
train_norm = (serie_train - media) / desv # el test se escala con las MISMAS
test_norm = (serie_test - media) / desv # media y desv del train
# 3. Ventanas sobre cada tramo ya normalizado
X_train, y_train = crear_ventanas(train_norm, VENTANA)
X_test, y_test = crear_ventanas(test_norm, VENTANA)
# 4. Tercer eje para la LSTM: (muestras, pasos, características)
X_train = X_train[..., np.newaxis]
X_test = X_test[..., np.newaxis]
print(X_train.shape, X_test.shape) # (556, 28, 1) (118, 28, 1)Guarda media y desv: son parte del modelo. En producción, cada predicción se des-normaliza con ellas (pred * desv + media) para volver a unidades reales.
El baseline ingenuo: la vara de medir obligatoria
Antes de entrenar nada sofisticado, calcula qué lograría una regla trivial. En series temporales, dos baselines canónicos:
- Ingenuo (naive): "mañana venderé lo mismo que hoy" →
ŷ_t = y_{t-1}. - Ingenuo estacional: "mañana venderé lo mismo que hace una semana" →
ŷ_t = y_{t-7}. En una serie con patrón semanal fuerte, es sorprendentemente difícil de batir.
# Baselines evaluados sobre el mismo tramo que evaluará la LSTM
reales = serie_test[VENTANA:] # objetivos en unidades reales
mae_naive = np.abs(reales - serie_test[VENTANA-1:-1]).mean() # ayer
mae_estacional = np.abs(reales - serie_test[VENTANA-7:-7]).mean() # hace 7 días
print(f"MAE naive: {mae_naive:.1f} u. | MAE estacional: {mae_estacional:.1f} u.")
# Típico con esta serie: naive ≈ 35 u., estacional ≈ 17 u.Esta es la regla más importante de la lección: un modelo de deep learning que no bate al baseline estacional no aporta nada, por muchas capas que tenga. El baseline es a las series lo que el "50 % de azar" era a IMDB: el suelo honesto de comparación.
LSTM sobre las ventas diarias de TecnoMarket
Con los datos en forma (muestras, 28, 1), el modelo es la aplicación directa de 04-02:
from tensorflow import keras
from tensorflow.keras import layers
modelo = keras.Sequential([
layers.Input(shape=(VENTANA, 1)),
layers.LSTM(64, return_sequences=True), # primera capa: pasa la secuencia entera
layers.LSTM(32), # segunda capa: resume en 32 números
layers.Dense(1) # regresión: sin activación (02-02)
])
modelo.compile(optimizer="adam", loss="mse", metrics=["mae"])
historia = modelo.fit(X_train, y_train,
epochs=40, batch_size=32,
validation_split=0.15, # el ÚLTIMO 15 % del train (Keras no baraja aquí)
shuffle=True, # barajar VENTANAS ya construidas sí es válido
verbose=0)Un matiz sutil e importante: una vez construidas las ventanas, sí se pueden barajar entre sí para entrenar (cada ventana es un ejemplo autocontenido); lo que no se baraja jamás es la serie antes de dividir train/test.
Evaluación en unidades reales
El MAE que reporta Keras está en unidades normalizadas — nadie en la reunión de logística entiende "0.21". Des-normaliza:
pred_norm = modelo.predict(X_test, verbose=0).ravel()
pred = pred_norm * desv + media # de vuelta a unidades/día
reales = y_test * desv + media
mae_lstm = np.abs(reales - pred).mean()
print(f"MAE LSTM: {mae_lstm:.1f} unidades/día")
print(f"vs. naive: {mae_naive:.1f} | vs. estacional: {mae_estacional:.1f}")
# Resultado típico: LSTM ≈ 13-15 u. — bate al estacional (~17), y de largo al naive (~35)Cómo leerlo en clave de negocio:
- Un MAE de ~14 unidades sobre ventas medias de ~300 es un error relativo del ~5 %: suficiente para dimensionar stock con margen razonable.
- La mejora frente al baseline estacional (~17 → ~14) parece modesta, pero es donde vive el valor: el patrón semanal ya lo daba gratis el baseline; la LSTM añade la tendencia y las interacciones. Grafica
predcontrareales: verás que sigue bien la sierra semanal y la pendiente. - ¿Y los Black Friday? Si caen en el tramo de test, la LSTM los suavizará o llegará tarde: un pico anual aparece 1-2 veces en todo el entrenamiento, insuficiente para aprenderlo solo de la serie. En la práctica esto se resuelve añadiendo características de calendario como entradas adicionales (día de la semana, "es campaña de Black Friday": el tercer eje de
Xpasa de 1 a varias características por paso) — el formato(muestras, pasos, características)de 04-01 estaba preparado para esto desde el principio.
Predicción multi-paso: iterativa vs. directa
Logística no quiere solo mañana: quiere los próximos 7 días. Dos estrategias:
| Estrategia | Cómo funciona | Ventajas | Riesgos |
|---|---|---|---|
| Iterativa | Predices t+1, metes esa predicción en la ventana y predices t+2, y así 7 veces | Un solo modelo; horizonte flexible | Acumulación de error: cada paso se apoya en predicciones, no en datos; el error crece con el horizonte |
| Directa | Un modelo entrenado para predecir el vector de 7 valores de golpe (Dense(7)), o un modelo por horizonte |
Cada horizonte se optimiza con datos reales; sin realimentación de errores | Más entrenamiento; no reutiliza la predicción de t+1 para t+2 |
def predecir_iterativo(modelo, ultima_ventana_norm, pasos):
"""Predicción multi-paso iterativa: realimenta cada predicción."""
ventana = ultima_ventana_norm.copy() # (VENTANA,)
futuro = []
for _ in range(pasos):
p = modelo.predict(ventana[np.newaxis, :, np.newaxis], verbose=0)[0, 0]
futuro.append(p)
ventana = np.append(ventana[1:], p) # desliza: entra la predicción
return np.array(futuro) * desv + media # a unidades reales
print(predecir_iterativo(modelo, test_norm[-VENTANA:], pasos=7))El riesgo de la iterativa se entiende con una imagen: es el teléfono estropeado — cada predicción hereda y amplifica los errores de la anterior. Para 2-3 pasos suele valer; para 14-30 días, la estrategia directa (o modelos clásicos de estacionalidad explícita) suele ser más segura. Evalúa siempre el MAE por horizonte (¿cuánto fallo a t+1?, ¿y a t+7?): verás la curva de degradación con tus propios ojos.
¿Cuándo compensa una LSTM frente a los métodos clásicos?
El deep learning no es siempre la respuesta en series temporales. Los métodos estadísticos clásicos (medias móviles, suavizado exponencial, ARIMA/SARIMA) llevan décadas prediciendo demanda:
| Situación | Mejor apuesta inicial |
|---|---|
| Una sola serie, corta (< 1-2 años), patrón regular | Clásicos (SARIMA, suavizado exponencial): más simples, interpretables y a menudo igual de precisos |
| Cientos/miles de series relacionadas (una por producto de TecnoMarket) | LSTM u otros modelos de DL: un solo modelo global aprende patrones compartidos entre productos |
| Muchas variables externas (precio, promociones, calendario, meteo) | DL: incorpora características por paso con naturalidad |
| Relaciones no lineales complejas (promoción × día × categoría) | DL |
| Necesitas explicar la predicción al negocio | Clásicos (o al menos, acompaña el DL de un baseline claro) |
| Datos muy escasos o muy ruidosos | Clásicos o baseline estacional; el DL sobreajustará |
La honestidad metodológica de esta lección —baseline primero, división temporal, evaluación en unidades reales— vale para ambos mundos, y es lo que distingue un prototipo serio de una demo.
Errores Comunes y Consejos
- Ajustar el scaler con toda la serie. El error clásico de fuga de datos: el test queda contaminado con estadísticas del futuro y las métricas se inflan.
fitdel scaler solo con train, siempre. - Dividir train/test barajando. En series, el test debe ser el tramo temporal final. Si barajas antes de dividir, el modelo "entrena con el futuro" y la evaluación es ficción. (Barajar las ventanas ya construidas del train, en cambio, es correcto.)
- No calcular el baseline. Sin baseline no sabes si tu MAE de 14 es bueno o vergonzoso. El ingenuo estacional se calcula en una línea y es la primera cifra que deberías apuntar.
- Ventana más corta que la estacionalidad. Con
V < 7en una serie semanal, el modelo no puede comparar "hoy" con "hace una semana". Ventana ≥ 2-4 periodos estacionales. - Reportar el error en unidades normalizadas. "MAE 0.21" no informa ninguna decisión. Des-normaliza y habla en unidades/día (o euros): es lo que el negocio puede juzgar.
- Fiarse de la predicción multi-paso iterativa a horizontes largos. El error se acumula paso a paso. Mide el MAE por horizonte y trunca donde deje de ser útil.
Ejercicios
-
Ventanas a mano: con la serie
[100, 110, 105, 120, 125, 118, 130]yV = 4, escribe todas las parejas (X, y) resultantes. ¿Cuántos ejemplos supervisados obtienes de una serie deNvalores con ventanaV? -
Caza la fuga: este código tiene dos errores de metodología — encuéntralos y corrígelos:
media, desv = ventas.mean(), ventas.std() norm = (ventas - media) / desv X, y = crear_ventanas(norm, 28) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=True) -
Baseline y veredicto: en el tramo de test, el baseline estacional logra MAE 17.2 unidades y tu LSTM 16.8. El entrenamiento de la LSTM tarda 20 minutos y el baseline es una línea de código. ¿Qué recomendarías al equipo de TecnoMarket y qué probarías antes de descartar la LSTM?
Soluciones
- Parejas:
([100,110,105,120] → 125),([110,105,120,125] → 118),([105,120,125,118] → 130). En general,N − Vejemplos: aquí7 − 4 = 3. - Error 1: el scaler se ajusta con toda la serie (
ventas.mean()) — debe calcularse solo sobre el tramo de train. Error 2:train_test_splitconshuffle=Truebaraja antes de dividir, mezclando futuro en el train — la división debe ser temporal (primer 80 % / último 20 %) y hacerse antes de crear las ventanas (si se hace después, además, hay ventanas que cruzan la frontera train/test). Versión correcta: dividir la serie por índice temporal, normalizar con estadísticas del train, y construir las ventanas dentro de cada tramo. - Con una mejora de 0.4 unidades (~2 %), la LSTM apenas justifica su coste: recomendaría desplegar el baseline estacional hoy mismo como sistema de referencia. Antes de descartar la LSTM probaría: añadir características de calendario (día de semana, festivos, campañas — donde el DL puede marcar diferencia real), ampliar la ventana, y entrenar un modelo global sobre muchos productos a la vez en lugar de una sola serie. Si tras eso la mejora sigue siendo marginal, el método simple gana: es la conclusión honesta que permite el baseline.
Conclusión
Has convertido la predicción de demanda de TecnoMarket en un problema de deep learning de principio a fin: descomponer la serie (tendencia + estacionalidad + ruido), transformarla en dataset supervisado con ventanas deslizantes, normalizar sin fuga de datos (scaler ajustado solo en train, división temporal sin barajar), exigir un baseline ingenuo antes de celebrar nada, entrenar una LSTM apilada que bate a ese baseline, y evaluar en unidades que el negocio entiende — más la predicción multi-paso con su acumulación de error y el criterio para elegir entre LSTM y métodos clásicos. Con esto se cierra el módulo de redes recurrentes: ya sabes modelar el orden, la memoria y el tiempo, tanto en texto como en números. El siguiente módulo cambia de pregunta: en lugar de clasificar o predecir, ¿puede una red crear — generar imágenes promocionales, comprimir representaciones, aprovechar modelos preentrenados? Empezamos con las redes generativas adversariales (GAN).
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
