En la lección anterior vimos que el generador de una GAN mapea un espacio latente a imágenes, pero ese espacio se construye "a ciegas", empujado por un adversario. El autoencoder toma el camino directo: es una red que aprende a comprimir sus entradas y a reconstruirlas, y en el proceso construye un espacio latente explícito sin necesitar ni etiquetas ni adversarios. Esta idea aparentemente modesta —copiar la entrada a la salida pasando por un embudo— es una de las más fecundas del deep learning: da lugar a reducción de dimensionalidad, limpieza de ruido y, sobre todo para TecnoMarket, detección de anomalías y fraude, que es la aplicación que dejaremos planteada aquí y desarrollaremos como proyecto en 07-03.
Contenido
- Aprendizaje no supervisado de representaciones
- Anatomía: encoder, cuello de botella, decoder
- Qué aprende el espacio latente
- Un autoencoder denso sobre MNIST en Keras
- Interpretación del error de reconstrucción
- Aplicaciones: reducción de dimensionalidad y denoising
- Detección de anomalías: por qué funciona
- El autoencoder variacional (VAE): puente hacia la generación
Aprendizaje no supervisado de representaciones
Todos los modelos que has entrenado hasta ahora eran supervisados: cada ejemplo llevaba su etiqueta (el dígito correcto, el sentimiento de la reseña, las ventas del día siguiente). Pero etiquetar es caro. TecnoMarket tiene millones de transacciones, fotos y registros sin etiquetar, y ahí es donde entra el aprendizaje no supervisado: extraer estructura de los datos sin que nadie diga qué es cada cosa.
El truco del autoencoder es convertir un problema sin etiquetas en uno supervisado con un giro elegante: la etiqueta de cada ejemplo es el propio ejemplo. La red recibe una imagen y debe producir... esa misma imagen. Parece trivial (bastaría copiar), pero le ponemos un obstáculo: en medio de la red hay un cuello de botella, una capa mucho más pequeña que la entrada. Para atravesarlo, la red se ve obligada a decidir qué información es esencial y cuál es prescindible. Eso —decidir qué importa— es aprender una representación.
Anatomía: encoder, cuello de botella, decoder
Un autoencoder tiene tres partes:
flowchart LR
X[Entrada<br/>784 valores] --> E1[Dense 128]
E1 --> E2[Dense 64]
E2 --> Z[Cuello de botella<br/>32 valores]
Z --> D1[Dense 64]
D1 --> D2[Dense 128]
D2 --> XR[Reconstruccion<br/>784 valores]
subgraph Encoder
E1
E2
end
subgraph Decoder
D1
D2
end
- Encoder: comprime progresivamente la entrada hasta el cuello de botella. Es como el camino de una CNN hacia su embedding (03-04), pero aquí sin etiquetas que guíen.
- Cuello de botella (código o vector latente
z): la representación comprimida. Su tamaño es una decisión de diseño: demasiado grande y la red "copia" sin aprender nada; demasiado pequeño y pierde información esencial. - Decoder: reconstruye la entrada a partir del código. ¿Te suena? Es estructuralmente lo mismo que el generador de la GAN de 05-01: un mapa de vector latente → dato. La diferencia es cómo se entrena.
La pérdida de reconstrucción mide cuánto se parece la salida a la entrada. Para imágenes normalizadas suele usarse el MSE que conociste en 02-04 (o BCE por píxel):
perdida = MSE(x, x_reconstruida) = promedio de (x_i - x̂_i)²
No hay adversario, no hay juego minimax: es una optimización normal con Adam y backpropagation, tan estable como las del módulo 2.
Qué aprende el espacio latente
Comprimir 784 píxeles en 32 números obliga a una compresión con pérdida, y esa pérdida es selectiva: la red conserva lo que ayuda a reconstruir muchos ejemplos y descarta el ruido idiosincrático. La analogía útil es el resumen: si resumes una reseña de 500 palabras en 20, conservas "cliente insatisfecho, batería dura poco, pide devolución" y descartas los giros de estilo. El cuello de botella hace lo mismo con los datos.
El resultado es un espacio latente con las propiedades que ya conoces de los embeddings (03-04, 04-03) y del espacio latente de las GAN (05-01):
- Entradas parecidas → códigos cercanos (medibles con similitud coseno o distancia euclídea).
- Los ejes latentes capturan factores de variación (inclinación del trazo, grosor, forma, en el caso de MNIST).
- Se puede usar el código como "huella" compacta del dato para búsquedas o clustering.
Hay una diferencia importante con la GAN: el espacio latente de un autoencoder clásico no está pensado para generar: si le das al decoder un z inventado al azar, lo más probable es que salga un borrón, porque la red solo ha aprendido a reconstruir códigos que provienen de datos reales. Volveremos a esto al final con el VAE.
Un autoencoder denso sobre MNIST en Keras
Siguiendo la metodología del curso —primero el dataset público, luego TecnoMarket—, construyamos el autoencoder del diagrama sobre MNIST:
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers
# 1. Datos: solo necesitamos las imagenes, NO las etiquetas (no supervisado)
(x_train, _), (x_test, _) = keras.datasets.mnist.load_data()
x_train = x_train.astype("float32") / 255.0 # normalizamos a [0, 1]
x_test = x_test.astype("float32") / 255.0
x_train = x_train.reshape(-1, 784) # aplanamos 28x28 -> 784
x_test = x_test.reshape(-1, 784)
DIM_LATENTE = 32 # el cuello de botella: 784 -> 32 (compresion ~24:1)
# 2. Encoder: embudo descendente
encoder = keras.Sequential([
layers.Dense(128, activation="relu", input_shape=(784,)),
layers.Dense(64, activation="relu"),
layers.Dense(DIM_LATENTE, activation="relu"), # el codigo z
], name="encoder")
# 3. Decoder: embudo ascendente, espejo del encoder
decoder = keras.Sequential([
layers.Dense(64, activation="relu", input_shape=(DIM_LATENTE,)),
layers.Dense(128, activation="relu"),
# sigmoide final porque los pixeles estan en [0, 1]
layers.Dense(784, activation="sigmoid"),
], name="decoder")
# 4. Autoencoder = encoder + decoder encadenados
autoencoder = keras.Sequential([encoder, decoder], name="autoencoder")
# 5. La "etiqueta" es la propia entrada: fit(x_train, x_train)
autoencoder.compile(optimizer="adam", loss="mse")
historia = autoencoder.fit(
x_train, x_train, # <- entrada y objetivo son lo mismo
epochs=20,
batch_size=256,
validation_data=(x_test, x_test),
)Los detalles que importan:
fit(x_train, x_train): aquí está toda la idea del autoencoder condensada en una línea. El objetivo es la propia entrada.- Sigmoide en la última capa porque los píxeles normalizados viven en [0, 1]; recuerda de 02-02 que la activación de salida debe casar con el rango del objetivo.
- Simetría encoder/decoder: no es obligatoria, pero es el diseño habitual y facilita razonar sobre la red.
Y ahora, lo más instructivo: mirar las reconstrucciones.
import matplotlib.pyplot as plt
reconstruidas = autoencoder.predict(x_test[:10])
fig, ejes = plt.subplots(2, 10, figsize=(14, 3))
for i in range(10):
ejes[0, i].imshow(x_test[i].reshape(28, 28), cmap="gray")
ejes[0, i].set_title("original", fontsize=8)
ejes[0, i].axis("off")
ejes[1, i].imshow(reconstruidas[i].reshape(28, 28), cmap="gray")
ejes[1, i].set_title("reconstruida", fontsize=8)
ejes[1, i].axis("off")
plt.show()Verás dígitos reconocibles pero ligeramente suavizados: los trazos finos se difuminan y las rarezas caligráficas desaparecen. Esa suavidad es la compresión con pérdida en acción — la red reconstruye el dígito "típico" que resume al tuyo.
Interpretación del error de reconstrucción
Además de mirar las imágenes, podemos medir el error de cada ejemplo:
errores = np.mean((x_test - autoencoder.predict(x_test)) ** 2, axis=1)
print(f"Error medio: {errores.mean():.4f}")
print(f"Peor ejemplo: {errores.max():.4f} | Mejor: {errores.min():.4f}")Este número por ejemplo es la clave de casi todas las aplicaciones prácticas:
- Error bajo → el ejemplo se parece a lo que la red vio en entrenamiento: es "normal".
- Error alto → el ejemplo tiene algo que el resumen aprendido no sabe expresar: es raro, ruidoso... o anómalo.
Ordena los ejemplos de test por error descendente y mira los primeros: encontrarás los dígitos más extraños del dataset (trazos ilegibles, estilos atípicos). El autoencoder acaba de hacer, sin que se lo pidiéramos, un detector de rarezas.
Aplicaciones
Reducción de dimensionalidad (vs. PCA)
El encoder por sí solo (encoder.predict(x)) es un reductor de dimensionalidad: 784 → 32. La técnica clásica para esto es PCA (análisis de componentes principales); comparemos:
| Aspecto | PCA | Autoencoder |
|---|---|---|
| Tipo de transformación | Lineal (proyección) | No lineal (tan flexible como la red) |
| Capacidad | Limitada a estructura lineal | Captura relaciones curvas y complejas |
| Coste de entrenamiento | Muy bajo (álgebra directa) | Entrenamiento por gradiente |
| Interpretabilidad | Alta (componentes ordenadas por varianza) | Baja (el código no tiene orden natural) |
| Cuándo elegirlo | Baseline rápido, datos ~lineales | Datos complejos (imágenes, señales) |
La regla práctica es la misma que con los baselines de 04-04: empieza por PCA (barato y honesto) y pasa al autoencoder si la estructura de tus datos lo justifica. De hecho, un autoencoder con activaciones lineales y MSE aprende esencialmente el mismo subespacio que PCA — la ganancia viene de las no linealidades.
Denoising autoencoder
Variante con un cambio mínimo y gran utilidad: entrenar con entrada ruidosa y objetivo limpio.
# Añadimos ruido gaussiano a las entradas
factor_ruido = 0.4
x_train_ruido = x_train + factor_ruido * np.random.normal(size=x_train.shape)
x_test_ruido = x_test + factor_ruido * np.random.normal(size=x_test.shape)
x_train_ruido = np.clip(x_train_ruido, 0.0, 1.0) # mantenemos [0, 1]
x_test_ruido = np.clip(x_test_ruido, 0.0, 1.0)
# Misma arquitectura; cambia SOLO el par (entrada, objetivo):
# entrada ruidosa -> objetivo limpio
autoencoder.fit(x_train_ruido, x_train,
epochs=20, batch_size=256,
validation_data=(x_test_ruido, x_test))Como la red no puede memorizar el ruido (es aleatorio y distinto cada vez), se ve forzada a aprender la estructura subyacente del dígito y a descartarlo. Para TecnoMarket, la misma receta limpia fotos de producto subidas por vendedores con mala iluminación o compresión agresiva antes de pasarlas al clasificador de 03-04.
Detección de anomalías: por qué funciona
Esta es la aplicación estrella para TecnoMarket, y el razonamiento merece enunciarse con precisión:
- Entrenas el autoencoder solo con datos normales (p. ej., cientos de miles de transacciones legítimas).
- La red aprende a resumir y reconstruir bien lo normal: sus regularidades caben en el cuello de botella.
- Llega un ejemplo anómalo (una transacción fraudulenta con una combinación insólita de importe, hora, dispositivo y dirección de envío). El resumen aprendido no tiene vocabulario para esa rareza: al comprimirla, la deforma hacia lo normal, y la reconstrucción sale mal.
- Error de reconstrucción alto → candidata a anomalía. Fijando un umbral sobre el error (otra vez el patrón de umbral + cola de revisión humana de 03-04), las transacciones sospechosas van a revisión.
La elegancia del enfoque: no necesitas ejemplos de fraude para entrenar — solo normalidad abundante, que es justo lo que sobra. Esto importa porque el fraude es raro, cambiante y caro de etiquetar. En 07-03 construiremos este sistema completo sobre datos ficticios de transacciones de TecnoMarket: generación del dataset, elección del umbral, métricas adecuadas para clases desequilibradas y puesta en marcha. Aquí basta con que la lógica te haya quedado clara.
El autoencoder variacional (VAE)
Cerremos el círculo con la GAN de 05-01. Dijimos que el decoder de un autoencoder clásico no sirve para generar: su espacio latente tiene "huecos" — regiones sin datos donde la reconstrucción es basura. El autoencoder variacional (VAE) arregla exactamente eso, con dos cambios conceptuales (te ahorramos la matemática pesada):
- El encoder no produce un punto
z, sino una pequeña nube de probabilidad (una media y una varianza) de la que se muestreaz. Cada ejemplo ocupa una región, no un punto. - Se añade a la pérdida un término que empuja todas esas nubes hacia una distribución normal estándar, obligándolas a solaparse y rellenar el espacio sin huecos.
El resultado es un espacio latente continuo y navegable: muestrea cualquier z de la normal, pásalo por el decoder y obtendrás un dato nuevo plausible. Es decir, un VAE es un autoencoder que sí genera:
| Autoencoder clásico | VAE | GAN | |
|---|---|---|---|
| Entrena con | Reconstrucción | Reconstrucción + regularización del latente | Juego adversarial |
| Estabilidad | Alta | Alta | Baja (05-01) |
| ¿Genera muestras nuevas? | No (huecos en el latente) | Sí (algo borrosas) | Sí (nítidas) |
| Uso típico | Compresión, anomalías | Generación controlada, interpolación | Generación de alta fidelidad |
El VAE es el puente conceptual entre las dos lecciones: reconstruir (autoencoder) y crear (GAN) son dos caras del mismo espacio latente.
Errores Comunes y Consejos
- Hacer el cuello de botella demasiado grande. Con
DIM_LATENTE=784la red puede aprender la identidad y el error será bajísimo sin haber aprendido nada útil. Si tus reconstrucciones son perfectas, sospecha: reduce el código hasta que la compresión "duela" un poco. - Entrenar el detector de anomalías con datos contaminados. Si el conjunto de entrenamiento incluye fraudes sin saberlo, la red también aprenderá a reconstruirlos y dejarán de destacar. Cura el dataset de entrenamiento lo mejor posible.
- Olvidar que el error de reconstrucción no es una probabilidad. Es una distancia sin escala universal: el umbral de anomalía debe calibrarse sobre un conjunto de validación, no fijarse "a ojo" (lo haremos con rigor en 07-03).
- Usar el mismo ruido fijo en el denoising. Si generas el ruido una sola vez, la red puede memorizarlo. Idealmente el ruido se regenera en cada época (con un generador de datos), o al menos se acepta la versión simple sabiendo su limitación.
- Comparar autoencoder contra nada. Como con las series de 04-04: ten un baseline (PCA para reducción, distancia a la media para anomalías) y exige que el autoencoder lo supere.
Ejercicios
Ejercicio 1. Explica con la analogía del resumen por qué un autoencoder entrenado con transacciones legítimas de TecnoMarket reconstruye mal una transacción fraudulenta, y por qué esto es una ventaja frente a entrenar un clasificador supervisado fraude/no-fraude.
Ejercicio 2. Modifica el autoencoder de MNIST para que el cuello de botella sea de 2 dimensiones, entrena, y escribe el código para pintar un scatter plot de encoder.predict(x_test) coloreado por el dígito real (las etiquetas, que no usamos para entrenar, sí podemos usarlas para visualizar). ¿Qué esperas ver?
Ejercicio 3. Verdadero o falso, justificando: (a) un autoencoder necesita etiquetas para entrenarse; (b) muestrear un z aleatorio y pasarlo por el decoder de un autoencoder clásico produce ejemplos nuevos de calidad; (c) el denoising autoencoder usa como objetivo la entrada ruidosa; (d) un VAE añade a la reconstrucción un término que organiza el espacio latente.
Soluciones
Solución 1.
El autoencoder aprende un "resumen" de lo normal: las combinaciones habituales de importe, horario, dispositivo y destino caben en el cuello de botella porque se repiten en cientos de miles de ejemplos. Una transacción fraudulenta contiene combinaciones que el resumen no sabe expresar (p. ej., importe alto + cuenta recién creada + envío a dirección nunca vista); al comprimirla, la red la "normaliza" y la reconstrucción difiere mucho del original → error alto → alarma. La ventaja frente al clasificador supervisado es doble: (1) no hacen falta ejemplos etiquetados de fraude, que son escasos y caros; (2) detecta fraudes de tipos nuevos, mientras que un clasificador solo reconoce patrones parecidos a los fraudes que vio entrenando.
Solución 2.
DIM_LATENTE = 2 # y reentrenar encoder/decoder/autoencoder como antes
(_, _), (x_test_img, y_test) = keras.datasets.mnist.load_data()
codigos = encoder.predict(x_test) # forma (10000, 2)
plt.figure(figsize=(8, 8))
plt.scatter(codigos[:, 0], codigos[:, 1], c=y_test, cmap="tab10", s=3)
plt.colorbar(label="digito")
plt.xlabel("z1"); plt.ylabel("z2")
plt.show()Cabe esperar agrupaciones por dígito: aunque la red nunca vio etiquetas, ejemplos del mismo dígito se parecen entre sí y acaban en regiones cercanas del plano latente (con solapes entre dígitos visualmente afines, como 4/9 o 3/8). La reconstrucción con solo 2 dimensiones será notablemente peor que con 32: es el precio de una compresión tan agresiva.
Solución 3.
- (a) Falso: usa la propia entrada como objetivo (
fit(x, x)); es aprendizaje no supervisado de representaciones. - (b) Falso en general: el espacio latente clásico tiene huecos sin datos; un
zarbitrario suele decodificarse como un borrón. Justo eso motiva el VAE. - (c) Falso: la entrada es la versión ruidosa, pero el objetivo es la versión limpia; de ahí que aprenda a eliminar ruido.
- (d) Verdadero: el VAE suma a la pérdida de reconstrucción un término que empuja los códigos hacia una distribución normal, dejando el espacio latente continuo y apto para generar.
Conclusión
El autoencoder convierte la ausencia de etiquetas en virtud: usando cada dato como su propio objetivo, aprende un espacio latente que resume lo esencial, y de ese resumen nacen sus aplicaciones — reducir dimensionalidad mejor que una proyección lineal, limpiar ruido, y señalar como anómalo todo lo que se reconstruye mal, que es la base del detector de fraude que TecnoMarket construirá en 07-03. El VAE añade el ingrediente probabilístico que convierte la reconstrucción en generación, tendiendo el puente de vuelta a las GAN.
Queda una constante incómoda en todo lo que llevamos de módulo: entrenar desde cero cuesta datos, tiempo y GPU. En la próxima lección aprenderemos el atajo más rentable del deep learning práctico: transfer learning, o cómo apoyarse en las arquitecturas preentrenadas que catalogamos en 03-03 para resolver problemas de TecnoMarket con unas pocas decenas de imágenes.
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
