En 05-03 entrenamos un MLP y empató con la regresión logística en el predictor de devoluciones. No es una derrota de las redes: es la señal de que su terreno es otro. Esta lección explica qué es exactamente el deep learning, por qué despegó en 2012 y no antes, y cuáles son las arquitecturas que incorporan la estructura de los datos y por eso ven, oyen y leen donde el MLP y los modelos clásicos no llegan: las redes convolucionales para imágenes (con una convolución calculada a mano y un mini-ejemplo en PyTorch sobre fotos sintéticas de paquetes dañados y correctos, el caso de las incidencias de NovaMarket), las redes recurrentes para secuencias, los autoencoders para detectar anomalías y la transferencia de aprendizaje, la idea que ha cambiado la práctica profesional: no entrenar desde cero. Cerraremos con el panorama de aplicaciones y con sus costes y límites. Es importante porque es aquí donde las redes justifican su complejidad, y donde Marta encontrará las herramientas para las fotos de incidencias.csv y, en la siguiente lección, para las reseñas.

Contenido

  1. Qué hace "profundo" al deep learning y por qué despegó
  2. Aprendizaje de representaciones jerárquicas
  3. Redes convolucionales: convolución, filtros, pooling y mapas de características
  4. Código: una CNN para las fotos de incidencias de NovaMarket
  5. Redes recurrentes: secuencias, estado oculto, LSTM y GRU
  6. Autoencoders y detección de anomalías
  7. Transferencia de aprendizaje y modelos preentrenados
  8. Panorama de aplicaciones
  9. Costes y límites
  10. Errores Comunes y Consejos
  11. Ejercicios
  12. Conclusión

  1. Qué hace "profundo" al deep learning y por qué despegó

Deep learning es aprendizaje automático con redes neuronales de muchas capas (de unas pocas a cientos). Lo "profundo" no es solo un número: es que la red aprende una jerarquía de representaciones (sección 2) en lugar de recibir características diseñadas a mano. En el módulo 4 Marta creó importe_por_articulo o tasa_devolucion_previa con su conocimiento del negocio; una red profunda inventa sus propias "columnas" a partir de píxeles o palabras.

La teoría existía desde los 80 (retropropagación, 05-03) y las convolucionales desde 1989 (LeNet de LeCun, para leer códigos postales). ¿Por qué el despegue en 2012? Confluyeron cuatro cosas:

Ingrediente Antes Desde ~2010 Por qué importa
Datos Miles de ejemplos ImageNet: 1,2 millones de imágenes etiquetadas en 1.000 clases; la web entera para texto Las redes profundas tienen millones de parámetros; sin datos, sobreajustan (04-06)
Cómputo CPU GPU (tarjetas gráficas): miles de núcleos que multiplican matrices en paralelo Entrenar en días lo que llevaría meses; las capas densas y convolucionales son multiplicaciones de matrices
Algoritmos Sigmoide, redes de 2-3 capas ReLU (05-02), mejores inicializaciones, dropout, normalización por lotes (05-03) Sin ReLU el gradiente se desvanece y las capas profundas no aprenden
Software Código a mano Frameworks con diferenciación automática (Theano, luego TensorFlow y PyTorch; 07-03) backward() gratis; experimentar es cuestión de horas

El momento simbólico es ImageNet 2012 (01-01): AlexNet, una convolucional de 8 capas y 60 millones de parámetros entrenada en dos GPU, bajó el error de clasificación del 26 % al 16 % de golpe, cuando las mejoras anuales eran de un punto. Desde entonces, cada dominio (visión, voz, texto) fue cayendo ante la misma receta: red profunda, muchos datos, GPU.

  1. Aprendizaje de representaciones jerárquicas

La idea central: cada capa transforma la salida de la anterior en algo más abstracto. En una red que reconoce fotos de paquetes:

flowchart LR
    P["Píxeles<br/>(valores 0-1)"] --> C1["Capa 1<br/>bordes, manchas,<br/>cambios de brillo"]
    C1 --> C2["Capa 2<br/>esquinas, líneas,<br/>texturas"]
    C2 --> C3["Capa 3<br/>partes: solapa,<br/>cinta, grieta"]
    C3 --> C4["Capa 4<br/>objeto: caja<br/>intacta / dañada"]
    C4 --> S["Salida<br/>probabilidad"]

Nadie programa "detector de grieta": aparece porque es útil para minimizar la pérdida. Lo mismo ocurre en texto (letras → palabras → frases → sentido) y en audio (ondas → fonemas → palabras). Esta es la respuesta a la pregunta de 05-01: por qué las redes pueden leer y mirar. Aprenden la conversión de datos brutos a características, la parte que en el módulo 4 hacía Marta a mano y que para píxeles y palabras nadie sabe hacer bien a mano. Y explica también por qué la profundidad rinde más que la anchura (05-02): componer transformaciones simples es más eficiente que aproximar la función de golpe.

  1. Redes convolucionales: convolución, filtros, pooling y mapas de características

Un MLP sobre una imagen de 16×16 la aplana en 256 números sin saber que el píxel 17 está debajo del 1. Las redes convolucionales (CNN) incorporan dos supuestos ciertos para las imágenes: los patrones son locales (una grieta ocupa unos pocos píxeles vecinos) y se pueden dar en cualquier posición (la grieta puede estar arriba o abajo). Lo consiguen con tres piezas:

3.1 La convolución y los filtros

Un filtro (o kernel) es una matriz pequeña de pesos, típicamente 3×3. La convolución desliza el filtro sobre la imagen y, en cada posición, multiplica elemento a elemento y suma. El resultado es un mapa de características que dice "cuánto se parece este trozo de imagen al patrón del filtro". Ejemplo a mano: imagen 5×5 con un cuadrado claro (valor 9) sobre fondo 0, y un filtro que detecta bordes verticales (columna izquierda −1, centro 0, derecha +1):

Imagen (5×5)          Filtro (3×3)
0 0 0 0 0             -1  0  1
0 9 9 9 0             -1  0  1
0 9 9 9 0             -1  0  1
0 9 9 9 0
0 0 0 0 0

Colocamos el filtro en la esquina superior izquierda (filas 0-2, columnas 0-2 de la imagen): (0·−1 + 0·0 + 0·1) + (0·−1 + 9·0 + 9·1) + (0·−1 + 9·0 + 9·1) = 18. Lo desplazamos una columna a la derecha (columnas 1-3): (0 + 0 + 0) + (−9 + 0 + 9) + (−9 + 0 + 9) = 0. Una más (columnas 2-4): (0 + 0 + 0) + (−9 + 0 + 0) + (−9 + 0 + 0) = −18. Repitiendo en las 3 × 3 posiciones posibles:

Posición (fila, col) Cálculo Salida
(0, 0) 0 + 9 + 9 18
(0, 1) 0 + (−9+9) + (−9+9) 0
(0, 2) 0 − 9 − 9 −18
(1, 0) 9 + 9 + 9 27
(1, 1) 0 0
(1, 2) −27 −27
(2, 0) 18 18
(2, 1) 0 0
(2, 2) −18 −18

Mapa de características resultante (3×3): la columna izquierda "se enciende" (borde izquierdo del cuadrado, paso oscuro→claro), la derecha se enciende en negativo (claro→oscuro) y el centro, sin cambios horizontales, queda a 0. El filtro traspuesto (filas −1, 0, +1) detecta bordes horizontales y da [[18, 27, 18], [0, 0, 0], [−18, −27, −18]]. Detalles: la salida se encoge (5×5 → 3×3) salvo que se añada un borde de ceros (padding), y el filtro puede saltar de 2 en 2 (stride). Lo esencial: los 9 pesos del filtro se aprenden por retropropagación, igual que los de una capa densa, y una capa convolucional tiene muchos filtros (8, 16, 64...) que producen otros tantos mapas. Con 9 pesos por filtro y los mismos pesos en toda la imagen, una capa convolucional tiene muchísimos menos parámetros que una densa equivalente y detecta el patrón esté donde esté.

3.2 Pooling

El pooling reduce el tamaño de los mapas quedándose con un resumen de cada bloque, casi siempre el máximo (max pooling) de bloques 2×2. Sobre [[1, 3, 2, 0], [4, 6, 1, 1], [0, 2, 7, 5], [3, 1, 8, 2]] da [[6, 2], [3, 8]]. Aporta invariancia a pequeños desplazamientos (si la grieta se mueve un píxel, el máximo del bloque no cambia) y divide el cómputo por cuatro en cada nivel.

3.3 Arquitectura típica

Bloques convolución → ReLU → pooling repetidos (cada uno con más filtros y mapas más pequeños: la jerarquía de la sección 2), seguidos de Flatten y una o dos capas densas que clasifican. Es la estructura de LeNet (1989), AlexNet (2012) y, con atajos residuales para llegar a cientos de capas, de las ResNet (2015).

  1. Código: una CNN para las fotos de incidencias de NovaMarket

Las fotos reales de incidencias.csv no las tenemos, así que generamos con numpy imágenes sintéticas de 16×16 en escala de grises: una caja clara sobre fondo oscuro, y en la mitad de ellas un daño (una grieta diagonal o una abolladura oscura). El objetivo es ver la CNN funcionar y compararla con un MLP; en 09-03 se hará un proyecto completo con imágenes reales.

import numpy as np, torch, torch.nn as nn

def generar_fotos(n=600, semilla=42, tam=16):
    """Fotos sintéticas 16x16: caja clara sobre fondo oscuro. 1 = dañada, 0 = correcta."""
    rng = np.random.default_rng(semilla)
    X = np.zeros((n, 1, tam, tam), dtype=np.float32)          # (n, canales, alto, ancho)
    y = np.zeros(n, dtype=np.int64)
    for i in range(n):
        img = rng.normal(0.1, 0.05, (tam, tam))                # fondo oscuro con ruido
        x0, y0 = rng.integers(1, 5, size=2)                    # esquina superior izquierda de la caja
        x1, y1 = rng.integers(tam - 5, tam - 1, size=2)        # esquina inferior derecha
        img[y0:y1, x0:x1] = rng.normal(0.7, 0.05, (y1 - y0, x1 - x0))   # la caja, clara
        if rng.random() < 0.5:                                 # la mitad, dañadas
            y[i] = 1
            if rng.random() < 0.5:                             # grieta: diagonal oscura
                r0 = rng.integers(y0, y1 - 4); c0 = rng.integers(x0, x1 - 4)
                largo = rng.integers(4, min(y1 - r0, x1 - c0) + 1)
                for k in range(largo):
                    img[r0 + k, c0 + k] = 0.05
            else:                                              # abolladura: cuadrado oscuro
                r0 = rng.integers(y0, y1 - 3); c0 = rng.integers(x0, x1 - 3)
                lado = rng.integers(2, 4)
                img[r0:r0 + lado, c0:c0 + lado] = rng.normal(0.15, 0.05, (lado, lado))
        X[i, 0] = np.clip(img, 0, 1)
    return X, y

X, y = generar_fotos()
print(X.shape, " dañadas:", y.mean())
np.set_printoptions(linewidth=120)
print((X[1, 0] * 9).astype(int))        # una foto, con los valores 0-1 escalados a 0-9 para verla
print("etiqueta:", y[1])

Salida (recortada):

(600, 1, 16, 16)  dañadas: 0.51
[[1 0 1 1 1 0 1 1 1 1 1 0 0 0 1 1]
 [0 0 1 0 0 0 1 0 1 1 1 1 1 0 0 1]
 [0 0 1 0 0 0 0 1 1 1 0 0 0 0 1 1]
 [0 6 6 6 5 6 5 6 5 6 5 5 6 0 0 0]
 [0 6 6 6 6 6 6 6 6 6 6 6 5 0 0 1]
 [1 5 7 7 6 5 6 6 6 5 6 6 5 0 1 1]
 [1 6 7 6 6 6 6 5 6 6 7 6 7 1 0 1]
 [0 5 6 6 5 0 6 6 5 6 5 6 6 0 0 1]
 [0 5 5 6 6 5 0 6 5 5 6 6 7 0 1 1]
 [1 6 6 6 6 6 6 0 6 6 6 6 6 0 0 1]
 [1 6 6 6 6 5 5 5 0 5 5 6 6 0 0 1]
 [0 6 5 6 6 5 5 6 6 0 6 6 5 0 1 0]
 ...
etiqueta: 1

Se ve la caja (valores 5-7) y la grieta diagonal de ceros que baja de la fila 7 a la 11. Ahora la CNN, un MLP para comparar, y el entrenamiento (el bucle es el de 05-03, con CrossEntropyLoss porque tenemos 2 logits de salida):

Xtr, ytr, Xte, yte = X[:450], y[:450], X[450:], y[450:]         # 450 para entrenar, 150 para test
Xtr_t, ytr_t = torch.tensor(Xtr), torch.tensor(ytr)
Xte_t, yte_t = torch.tensor(Xte), torch.tensor(yte)

torch.manual_seed(0)
cnn = nn.Sequential(
    nn.Conv2d(1, 8, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(2),    # 1x16x16 -> 8x16x16 -> 8x8x8
    nn.Conv2d(8, 16, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(2),   # 8x8x8 -> 16x8x8 -> 16x4x4
    nn.Flatten(),                                                              # 16*4*4 = 256 valores
    nn.Linear(256, 32), nn.ReLU(),
    nn.Linear(32, 2))                                                          # 2 logits: correcta / dañada
mlp = nn.Sequential(nn.Flatten(), nn.Linear(256, 64), nn.ReLU(), nn.Linear(64, 2))
print("Parámetros CNN:", sum(p.numel() for p in cnn.parameters()), " MLP:", sum(p.numel() for p in mlp.parameters()))

def entrenar(red, epocas=15, tasa=0.003, lote=32):
    opt = torch.optim.Adam(red.parameters(), lr=tasa)
    perdida_fn = nn.CrossEntropyLoss()                # softmax + entropía cruzada (05-02)
    for ep in range(1, epocas + 1):
        red.train()
        perm = torch.randperm(len(Xtr_t))             # barajar los índices
        for i in range(0, len(perm), lote):           # mini-lotes de 32
            idx = perm[i:i + lote]
            opt.zero_grad()
            L = perdida_fn(red(Xtr_t[idx]), ytr_t[idx])
            L.backward()
            opt.step()
        red.eval()
        with torch.no_grad():
            acc_tr = (red(Xtr_t).argmax(1) == ytr_t).float().mean().item()   # argmax: clase con mayor logit
            acc_te = (red(Xte_t).argmax(1) == yte_t).float().mean().item()
        if ep in (1, 3, 5, 10, 15):
            print(f"época {ep:2d}  acierto train {acc_tr:.3f}  test {acc_te:.3f}")

print("CNN:"); torch.manual_seed(0); entrenar(cnn)
print("MLP:"); torch.manual_seed(0); entrenar(mlp)

Salida (varía con la semilla y la versión):

Parámetros CNN: 9538  MLP: 16578
CNN:
época  1  acierto train 0.578  test 0.567
época  3  acierto train 0.680  test 0.680
época  5  acierto train 0.962  test 0.960
época 10  acierto train 0.991  test 0.973
época 15  acierto train 0.998  test 0.987
MLP:
época  1  acierto train 0.553  test 0.480
época  3  acierto train 0.616  test 0.533
época  5  acierto train 0.531  test 0.540
época 10  acierto train 0.547  test 0.513
época 15  acierto train 0.613  test 0.567

Lectura, línea a línea del modelo: nn.Conv2d(1, 8, kernel_size=3, padding=1) aprende 8 filtros de 3×3 sobre 1 canal (72 pesos + 8 sesgos) y, gracias al padding, mantiene el tamaño 16×16; MaxPool2d(2) lo baja a 8×8; la segunda convolución produce 16 mapas de 8×8 con filtros que miran los 8 mapas anteriores (16 × 8 × 3 × 3 = 1.152 pesos); tras el segundo pooling quedan 16 mapas de 4×4, es decir 256 valores que Flatten convierte en vector para las densas. El resultado es contundente: la CNN acierta el 98,7 % de las fotos de test (matriz de confusión: 75 correctas bien clasificadas, 73 dañadas detectadas, 2 dañadas que se escapan) con menos parámetros (9.538) que el MLP (16.578), que se queda en un 57 %, poco mejor que el azar, porque para él cada píxel es una columna independiente y la misma grieta en otra posición es un patrón nuevo. Los mapas de la primera capa (cnn[1](cnn[0](Xte_t[:1])) tiene forma [1, 8, 16, 16]) son 8 versiones "filtradas" de la foto; los pesos aprendidos (cnn[0].weight[0, 0]) forman detectores de bordes parecidos al de la sección 3.1, sin que nadie los haya diseñado. Para Diego: con 450 fotos etiquetadas y un minuto de CPU tenemos un clasificador de paquetes dañados que funciona; con fotos reales harían falta más datos, aumento de datos (ejercicio 2) y probablemente transferencia de aprendizaje (sección 7).

  1. Redes recurrentes: secuencias, estado oculto, LSTM y GRU

Las series temporales (la demanda semanal del NovaClean, 04-04) y el texto son secuencias en las que el orden importa y la longitud varía. Una red recurrente (RNN) procesa la secuencia elemento a elemento manteniendo un estado oculto h, un vector que resume lo visto hasta ahora: en cada paso, h_t = f(W·x_t + U·h_{t−1} + b), es decir, la misma neurona de siempre pero alimentada también con su propio estado anterior. Los mismos pesos W y U se aplican en todos los pasos (como el filtro de la CNN se aplica en todas las posiciones), y el estado final, o la secuencia de estados, se pasa a una capa de salida.

flowchart LR
    x1["x₁ (sem. 1)"] --> H1["h₁"]
    x2["x₂ (sem. 2)"] --> H2["h₂"]
    x3["x₃ (sem. 3)"] --> H3["h₃"]
    x4["x₄ (sem. 4)"] --> H4["h₄"]
    H1 --> H2 --> H3 --> H4
    H4 --> Y["ŷ (sem. 5)"]

El problema de la RNN simple es que, al entrenarla, el gradiente atraviesa tantos pasos como elementos tenga la secuencia, multiplicándose en cada uno: el gradiente que se desvanece (05-02) en versión temporal. En la práctica, olvida lo que pasó hace más de unas decenas de pasos. Las LSTM (long short-term memory, 1997) y las GRU (gated recurrent unit, 2014) lo resuelven con compuertas: pequeñas neuronas sigmoides que deciden, en cada paso, qué parte del estado se olvida, qué parte de la entrada se escribe y qué se lee para la salida. Un "cinturón transportador" de memoria por el que la información y el gradiente viajan sin degradarse. No hace falta memorizar las ecuaciones; sí saber que nn.LSTM y nn.GRU de PyTorch (por ejemplo, nn.GRU(input_size=1, hidden_size=8, batch_first=True), con 264 parámetros, devuelve para una secuencia de 12 semanas 12 estados de 8 valores) son la herramienta cuando la dependencia es larga. Aplicaciones: previsión de demanda con las 8-12 semanas anteriores (frente al MLP de la solución 3 de 05-02, la GRU respeta el orden), clasificación de texto palabra a palabra, reconocimiento de voz. En texto, desde 2018 los transformers (05-05) han sustituido a las recurrentes; en series temporales siguen siendo competitivas. 09-03 incluye un proyecto guiado con una red recurrente sobre la demanda del NovaClean.

  1. Autoencoders y detección de anomalías

Un autoencoder es una red entrenada para reproducir su entrada: un codificador comprime los datos a un vector pequeño (el "cuello de botella" o representación latente) y un decodificador intenta reconstruir el original; la pérdida es el error de reconstrucción (MSE). Parece inútil (¿para qué copiar?), pero al pasar por el cuello de botella la red se ve obligada a aprender lo esencial de los datos, sin etiquetas: es aprendizaje no supervisado (04-02).

Uso estrella en NovaMarket: detección de anomalías (caso 3, fraude). Se entrena el autoencoder solo con pedidos normales; los pedidos raros, al no parecerse a nada de lo aprendido, se reconstruyen mal. Con un autoencoder mínimo (nn.Sequential(nn.Linear(4, 2), nn.Tanh(), nn.Linear(2, 4)), cuello de 2) sobre las columnas estandarizadas importe, num_articulos, dias_entrega y cliente_nuevo de los 3.000 pedidos, 200 épocas de Adam dejan el error medio de reconstrucción en 0,51. Si generamos 30 pedidos anómalos (importe altísimo, muchos artículos, entrega en un día, cliente nuevo), su error medio es 8,0; fijando el umbral en el percentil 99 de los pedidos normales (2,61) se detectan los 30 anómalos con un 1 % de falsas alarmas. Otras aplicaciones: reducción de dimensión para visualizar clientes (alternativa no lineal al PCA), eliminación de ruido en imágenes, y la variante variacional (VAE), primer modelo generativo de imágenes moderno.

  1. Transferencia de aprendizaje y modelos preentrenados

La idea que más ha cambiado la práctica: no entrenar desde cero. Una CNN entrenada en ImageNet ha aprendido en sus primeras capas detectores de bordes, texturas y formas que sirven para cualquier imagen, también para fotos de paquetes. La transferencia de aprendizaje consiste en tomar ese modelo preentrenado, quitarle la capa de salida (que clasificaba 1.000 clases de ImageNet), poner una nueva para nuestro problema (2 clases) y entrenar solo esa capa (extracción de características) o toda la red con una tasa de aprendizaje pequeña (ajuste fino, fine-tuning). Resultado: buenos modelos con cientos de ejemplos en lugar de millones, y en minutos en lugar de días.

Ejemplo ilustrativo, no ejecutable en este entorno (requiere torchvision y descargar los pesos), del patrón con una ResNet-18:

# ILUSTRATIVO: patrón de transferencia de aprendizaje con torchvision (no ejecutado aquí)
import torchvision
modelo = torchvision.models.resnet18(weights="IMAGENET1K_V1")   # CNN preentrenada en ImageNet
for p in modelo.parameters():
    p.requires_grad = False                                       # congelar todo lo aprendido
modelo.fc = nn.Linear(modelo.fc.in_features, 2)                   # nueva salida: correcta / dañada
# A partir de aquí, el bucle de entrenamiento de 05-03 con las fotos reales de incidencias
# (redimensionadas a 224x224 y normalizadas como espera la ResNet) entrena SOLO modelo.fc.

Lo mismo existe para texto (BERT, GPT y compañía, 05-05), audio (Whisper) y casi cualquier dominio, distribuido en repositorios como Hugging Face. La consecuencia estratégica para NovaMarket la resume Marta: "no vamos a entrenar una red de visión desde cero; vamos a adaptar una que ya ve".

  1. Panorama de aplicaciones

Arquitectura Problema Ejemplo real Ejemplo NovaMarket
CNN Clasificación y detección en imágenes Diagnóstico por imagen médica; inspección visual en fábricas; coches autónomos Fotos de incidencias (paquete dañado / correcto); búsqueda visual de productos
CNN / transformers Reconocimiento de voz Asistentes de voz, transcripción de llamadas Transcribir las llamadas de atención al cliente
RNN / transformers Series temporales Previsión de consumo eléctrico, tráfico Previsión de demanda del NovaClean (caso 2)
RNN / transformers Procesamiento del lenguaje Traducción automática, resumen, chatbots Sentimiento de reseñas (caso 4); asistente (caso 7), en 05-05
Embeddings + MLP Recomendación Netflix, Spotify, Amazon "Los clientes que compraron el NovaClean también..." (caso 1)
Autoencoder Anomalías, compresión Fraude con tarjeta, fallos en máquinas Pedidos anómalos (caso 3)
Redes profundas + refuerzo Juegos, control AlphaGo (01-01), robótica, control de centros de datos Políticas de asignación de pedidos a almacenes (caso 6), a largo plazo
CNN / transformers Ciencia AlphaFold (estructura de proteínas), meteorología, descubrimiento de materiales (Fuera del negocio)

En recomendación vale la pena detenerse: cada cliente y cada producto reciben un embedding (05-02) y una red predice la afinidad a partir de ambos; los 12.000 productos de NovaMarket quedan en un espacio donde el NovaClean está cerca de las bolsas de aspirador y lejos de las cafeteras, aprendido solo de qué se compra junto. Y en aprendizaje por refuerzo profundo, la red aproxima el valor de cada acción (03-03, 04-02); AlphaGo combinó una CNN que evaluaba tableros con búsqueda en árbol.

  1. Costes y límites

Diego, que ha visto el 98,7 % de las fotos, pregunta cuánto cuesta y qué puede salir mal. Respuesta honesta:

  • Datos etiquetados: la CNN necesitó 450 fotos etiquetadas; una real, miles, o transferencia de aprendizaje. Etiquetar cuesta tiempo de personas y arrastra sesgos de etiquetado (02-03).
  • Cómputo y energía: entrenar modelos grandes cuesta de miles a millones de euros en GPU y una huella energética considerable; la inferencia también cuenta si se ejecuta millones de veces al día. Elegir el modelo más pequeño que resuelve el problema es una decisión económica y ambiental.
  • Opacidad: no podemos leer los 9.538 pesos de la CNN como leíamos los coeficientes de la logística. Existen técnicas de explicación (mapas de saliencia que resaltan qué píxeles pesaron, SHAP), pero son aproximadas. Con el AI Act (02-04), un sistema que decida sobre personas necesita explicabilidad y supervisión humana; para clasificar fotos de paquetes el riesgo es bajo, para negar una devolución no.
  • Fragilidad: las redes se equivocan con confianza ante datos distintos de los de entrenamiento (una foto con otra iluminación, otro tipo de caja) y son vulnerables a ejemplos adversarios (perturbaciones imperceptibles que cambian la predicción). Hay que monitorizar en producción (08-01).
  • Sesgo: aprenden lo que hay en los datos, incluido lo indeseable (02-04). Una CNN de "producto en buen estado" entrenada solo con cajas marrones fallará con cajas blancas.
  • Deuda de mantenimiento: versiones de frameworks, GPU, reentrenamientos. Cada modelo en producción es un sistema que envejece.

Errores Comunes y Consejos

  • Aplanar imágenes para un MLP. Se pierde la estructura espacial y el modelo necesita muchos más datos y parámetros para un resultado peor, como acabamos de ver (57 % frente a 98,7 %). Para imágenes, convoluciones (o un modelo preentrenado).
  • Entrenar desde cero por defecto. Con pocos datos, la transferencia de aprendizaje casi siempre gana. Antes de diseñar una red, busca un modelo preentrenado del dominio.
  • Olvidar el orden de las dimensiones. PyTorch espera (lote, canales, alto, ancho) en las CNN y (lote, tiempo, características) en las recurrentes con batch_first=True. Un error aquí no da excepción: da resultados absurdos.
  • Confundir la forma de la salida de la CNN. Antes de la primera capa densa hay que saber cuántos valores salen del último pooling (aquí 16 × 4 × 4 = 256); calcúlalo o imprímelo con un tensor de prueba.
  • Sin aumento de datos en imágenes. Girar, recortar y cambiar el brillo es la regularización más barata en visión (ejercicio 2).
  • Vender el deep learning como magia. Cuesta datos, GPU y mantenimiento, y es opaco. Para tablas pequeñas, la logística o el bosque del módulo 4 siguen siendo la primera opción, como comprobamos en 05-03.

Ejercicios

Ejercicio 1. Calcula a mano la convolución de la imagen 5×5 de la sección 3.1 con el filtro [[1, 1, 1], [0, 0, 0], [−1, −1, −1]] (detector de bordes horizontales). Comprueba el resultado con la función conv2d(img, k) que recorra las posiciones con dos bucles y (img[i:i+3, j:j+3] * k).sum(). ¿Qué borde de la caja se enciende en positivo y cuál en negativo?

Ejercicio 2. Aumento de datos. Con la CNN ya entrenada de la sección 4, evalúa el acierto sobre el test espejado horizontalmente (torch.flip(Xte_t, dims=[3])), donde las grietas diagonales pasan a tener la orientación contraria. Después reentrena una CNN nueva añadiendo al entrenamiento las versiones espejadas (torch.cat([Xtr_t, torch.flip(Xtr_t, dims=[3])]) y duplicando las etiquetas) y vuelve a medir sobre el test normal y el espejado. Explica el resultado.

Ejercicio 3. En el autoencoder de la sección 6, ¿qué ocurre con las falsas alarmas y con la detección de los 30 anómalos si el umbral se fija en el percentil 95 en lugar del 99? ¿Y si se entrena el autoencoder con los pedidos anómalos mezclados entre los normales? Razona sin ejecutar y, si quieres, compruébalo replicando el código descrito.

Soluciones

Solución 1. El resultado es [[−18, −27, −18], [0, 0, 0], [18, 27, 18]]. En la fila superior de salida el filtro cae con sus pesos +1 sobre el fondo (0) y con los −1 sobre la caja (9): el borde superior de la caja se enciende en negativo; la fila central da 0 (dentro de la caja no hay cambio vertical); en la inferior los +1 caen sobre la caja y los −1 sobre el fondo: el borde inferior se enciende en positivo. Es el mapa del filtro traspuesto de la sección 3.1 con el signo cambiado (aquel tenía los −1 arriba). Cada filtro "ve" una orientación y un sentido, y por eso una capa tiene muchos filtros.

Solución 2. En nuestra ejecución, la CNN entrenada sin espejos baja del 98,7 % al 96,0 % sobre el test espejado (6 dañadas se escapan en lugar de 2): ha aprendido en parte "grieta = diagonal que baja hacia la derecha" y la orientación contraria le resulta menos familiar. Con los espejos añadidos al entrenamiento (900 imágenes), la CNN nueva alcanza el 99,3 % en el test normal y el 98,7 % en el espejado: el aumento de datos le ha enseñado la invariancia correcta (una grieta es una grieta en cualquier orientación) sin necesidad de etiquetar ni una foto más. En un caso real se añadirían rotaciones, recortes y cambios de brillo, y se aplicarían al vuelo en cada época en lugar de duplicar el conjunto.

Solución 3. Con el percentil 95, el umbral baja (por construcción, el 5 % de los pedidos normales queda por encima), así que las falsas alarmas suben del 1 % al 5 % (unos 150 pedidos de 3.000) y los 30 anómalos siguen detectándose (su error, en torno a 8, está muy por encima de cualquiera de los dos umbrales). Es el intercambio precisión-recall de 04-05: el umbral se elige por coste, y aquí un 5 % de revisiones manuales sería probablemente excesivo para el beneficio nulo. Si los anómalos se mezclan en el entrenamiento, el autoencoder aprende también a reconstruirlos (son solo 30 entre 3.000, así que los reconstruirá algo peor que a los normales, pero mejor que si nunca los hubiera visto): su error baja, algunos caen por debajo del umbral y la detección empeora. Por eso los autoencoders de anomalías se entrenan con datos lo más "limpios" posible, o se acepta que solo detectan lo muy raro.

Conclusión

Hemos definido el deep learning como redes de muchas capas que aprenden representaciones jerárquicas (bordes → partes → objetos), y explicado su despegue en 2012 por la confluencia de datos (ImageNet), GPU, ReLU y frameworks. Hemos abierto las redes convolucionales: la convolución como filtro deslizante (calculada a mano: el detector de bordes verticales sobre la caja de 5×5), el pooling y los mapas de características, y las hemos visto ganar en las fotos sintéticas de incidencias de NovaMarket (98,7 % de acierto con 9.538 parámetros, frente al 57 % de un MLP con más parámetros). Hemos presentado las recurrentes con su estado oculto y las compuertas de LSTM/GRU para secuencias largas, los autoencoders que detectan pedidos anómalos por su error de reconstrucción, y la transferencia de aprendizaje, la práctica de adaptar modelos preentrenados en lugar de entrenar desde cero. El panorama de aplicaciones y la lista de costes y límites (datos etiquetados, cómputo, opacidad, fragilidad, sesgo) completan el cuadro que Marta necesita para decidir con Diego dónde merece la pena una red profunda.

Queda el dato más importante para NovaMarket, el texto: las reseñas de resenas.csv y el asistente de atención al cliente. Las recurrentes lo leían palabra a palabra y olvidaban; desde 2017 lo hace una arquitectura distinta, basada en atención, que ha acabado dominando también imágenes y audio y ha dado lugar a los grandes modelos de lenguaje. Es el tema de la última lección del módulo: Transformers, Grandes Modelos de Lenguaje e IA Generativa.

Fundamentos de Inteligencia Artificial (IA)

Módulo 1: Introducción a la Inteligencia Artificial

Módulo 2: Principios Básicos de la IA

Módulo 3: Algoritmos en IA

Módulo 4: Aprendizaje Automático (Machine Learning)

Módulo 5: Redes Neuronales y Deep Learning

Módulo 6: Lógica y Sistemas Expertos

Módulo 7: Herramientas y Lenguajes de Programación en IA

Módulo 8: Proyectos y Casos de Estudio

Módulo 9: Ejercicios y Prácticas

Módulo 10: Recursos Adicionales

© Copyright 2026. Todos los derechos reservados