Autoencoders
De la compresión inteligente a la generación creativa: fundamentos matemáticos del autoencoder, el espacio latente, variantes (denoising, sparse, convolucional), el Autoencoder Variacional (VAE) con ELBO, KL divergence y reparameterization trick, y su papel como piedra fundacional de la IA generativa moderna.
🔬 ¿Qué es un Autoencoder?
Un autoencoder es una red neuronal que aprende a copiar su entrada en su salida — pero pasando por un cuello de botella que la obliga a aprender una representación comprimida de los datos. Parece trivial, pero ese cuello de botella es lo que lo hace poderoso.
Definición formal: Un autoencoder es una función \( f: \mathbb{R}^n \to \mathbb{R}^n \) compuesta por un encoder \( g_\phi: \mathbb{R}^n \to \mathbb{R}^d \) y un decoder \( f_\theta: \mathbb{R}^d \to \mathbb{R}^n \), donde \( d < n \), tal que:
El objetivo es minimizar la diferencia entre \( x \) y \( \hat{x} \), forzando a la red a aprender las características más importantes de los datos.
El autoencoder fue introducido por Rumelhart, Hinton y Williams en 1986 como parte de su trabajo seminal sobre backpropagation. La idea original era utilizar una red con un cuello de botella para aprender representaciones internas útiles de los datos de forma no supervisada — sin necesitar etiquetas. Aunque puede parecer una tarea trivial («copiar la entrada»), el bottleneck obliga a la red a descubrir la estructura latente de los datos: patrones, correlaciones y factores de variación que definen la distribución subyacente.
Esta idea resultó ser extraordinariamente influyente. Hoy, los autoencoders y sus variantes son la base conceptual de modelos como Stable Diffusion (que opera en el espacio latente de un autoencoder), DALL-E (que usa VQ-VAE para tokenizar imágenes), y numerosas aplicaciones en detección de anomalías, compresión aprendida y drug discovery.
🏗️ Arquitectura Encoder-Decoder
Todo autoencoder tiene dos partes simétricas conectadas por el espacio latente. La arquitectura encoder-decoder es una de las ideas más influyentes del deep learning: aparece no solo en autoencoders, sino en modelos de traducción (Seq2Seq), segmentación (U-Net), generación de imágenes (Stable Diffusion), y prácticamente en cualquier tarea que requiera transformar una representación en otra.
Comprime la entrada \( x \in \mathbb{R}^n \) a una representación \( z \in \mathbb{R}^d \) donde \( d \ll n \). Aprende a extraer las características esenciales.
Representación comprimida de baja dimensión. Captura la estructura esencial de los datos. Es el «corazón» del autoencoder.
Reconstruye la entrada original a partir de \( z \). Si la reconstrucción es buena, el espacio latente captura la información relevante.
📉 Función de Pérdida: Reconstrucción
El autoencoder se entrena minimizando la pérdida de reconstrucción: la diferencia entre la entrada \( x \) y la salida \( \hat{x} \). Las dos opciones más comunes:
| Loss | Fórmula | Caso de uso |
|---|---|---|
| MSE (Mean Squared Error) | $$ \mathcal{L}_\text{MSE} = \frac{1}{n}\sum_{i=1}^{n}(x_i - \hat{x}_i)^2 $$ | Datos continuos (imágenes normalizadas [0,1], señales) |
| BCE (Binary Cross-Entropy) | $$ \mathcal{L}_\text{BCE} = -\frac{1}{n}\sum_{i=1}^{n}\left[x_i\log\hat{x}_i + (1-x_i)\log(1-\hat{x}_i)\right] $$ | Datos binarios o valores en [0,1] (imágenes binarizadas) |
La loss no mide «calidad visual»: MSE penaliza diferencias pixel a pixel, pero una imagen puede ser perceptualmente buena con MSE alto (o viceversa). Por eso existen losses perceptuales, pero para empezar, MSE y BCE funcionan bien.
📐 Undercomplete vs Overcomplete
La relación entre la dimensión de entrada \( n \) y la dimensión latente \( d \) define el tipo de autoencoder:
El bottleneck fuerza a la red a aprender las características más importantes. No puede simplemente copiar la entrada. Este es el caso estándar.
Sin restricciones adicionales, la red puede aprender la identidad (copiar todo sin aprender nada útil). Se necesita regularización (sparsity, denoising, etc.) para que sea útil.
🔗 Conexión con PCA
Si usamos un autoencoder lineal (sin funciones de activación) con loss MSE, la solución óptima es exactamente PCA (Principal Component Analysis):
Sea \( W_e \in \mathbb{R}^{d \times n} \) la matriz del encoder y \( W_d \in \mathbb{R}^{n \times d} \) la del decoder. La reconstrucción es:
Minimizar \( \|x - W_d W_e x\|^2 \) sobre todos los datos equivale a encontrar la proyección de rango \( d \) que maximiza la varianza explicada. Por el teorema de Eckart-Young, la solución óptima son los primeros \( d \) componentes principales de la matriz de covarianza de los datos.
Pero: al añadir no-linealidades (ReLU, sigmoid...), el autoencoder puede capturar manifolds no lineales que PCA no puede ver. Esa es su ventaja.
PCA vs Autoencoder no lineal:
- PCA: encuentra el mejor subespacio lineal. Rápido, interpretable, limitado.
- AE no lineal: encuentra un manifold no lineal. Más expresivo, puede capturar estructuras complejas.
- El AE es una generalización no lineal de PCA.
Referencias
- Rumelhart et al. (1986). Learning representations by back-propagating errors. Nature 323 — Trabajo original con autoencoders.
- Baldi & Hornik (1989). Neural networks and principal component analysis. Neural Networks 2(1) — Demostración de la equivalencia AE lineal ↔ PCA.
- Goodfellow et al. (2016). Deep Learning, Capítulo 14: Autoencoders. deeplearningbook.org — Referencia completa sobre autoencoders.
🌌 El Espacio Latente: el corazón del autoencoder
El espacio latente es la representación de baja dimensión que el autoencoder aprende internamente. Es el punto donde la red ha destilado la información esencial, descartando redundancia y ruido.
Intuición: Imagina que tienes 10.000 fotos de caras. Cada imagen tiene 784 píxeles (28×28), pero la «esencia» de cada cara se puede describir con muchos menos números: forma de la nariz, color de ojos, ángulo de la cabeza... El espacio latente captura esos factores de variación en un vector compacto \( z \).
⏳ El Bottleneck y la Hipótesis del Manifold
La hipótesis del manifold dice que los datos reales de alta dimensión viven en un subespacio (manifold) de dimensión mucho menor. El autoencoder aprende este manifold:
La hipótesis del manifold es uno de los supuestos más importantes en deep learning. Proporciona la justificación teórica de por qué las redes neuronales profundas pueden funcionar tan bien con datos de alta dimensión: si los datos realmente viven en un manifold de dimensión mucho menor, la tarea de la red no es «entender» todas las \( 2^{784} \) posibles configuraciones de píxeles, sino solo el pequeño subconjunto que corresponde a imágenes válidas.
🗺️ Visualización del Espacio Latente
Cuando \( d = 2 \), podemos visualizar directamente el espacio latente como un scatter plot. Para \( d > 2 \), usamos técnicas como t-SNE o UMAP para proyectar a 2D.
Si entrenas un autoencoder con \( d = 2 \) sobre MNIST, cada dígito forma un cluster en el espacio latente. Los dígitos similares (como 4 y 9) estarán cerca. Esto demuestra que el espacio latente captura la semántica de los datos.
🔄 Interpolación en el Espacio Latente
Una propiedad fascinante: si tomamos dos puntos \( z_A \) y \( z_B \) en el espacio latente y generamos puntos intermedios, el decoder produce transiciones suaves entre las imágenes correspondientes:
Esta interpolación es clave: demuestra que el espacio latente es continuo y tiene estructura semántica. Es el fundamento de la generación de contenido nuevo.
Problema: En un autoencoder estándar, la interpolación no siempre funciona bien. Los puntos intermedios pueden caer en «zonas muertas» del espacio latente que nunca se vieron durante el entrenamiento. Este es uno de los problemas que el VAE resuelve.
🧪 Explorador interactivo: Espacio Latente 2D
Experimenta con un espacio latente bidimensional simulado. Mueve el punto en el espacio latente y observa cómo cambia la «reconstrucción»:
💡 Nota: esta es una simulación didáctica. z₁ controla la «forma» y z₂ el «estilo». En un autoencoder real, los ejes latentes suelen no estar tan perfectamente alineados con conceptos humanos (a menos que uses β-VAE).
🗂️ Tipos de Autoencoders
El autoencoder «vanilla» es solo el punto de partida. Existen variantes que añaden regularización o estructura para mejorar las representaciones aprendidas. Aquí están las más importantes:
🧹 Denoising Autoencoder (DAE)
Idea: Corrompe la entrada con ruido y entrena al autoencoder a reconstruir la versión limpia. Esto fuerza a la red a aprender la estructura real de los datos, no artefactos del ruido.
¿Por qué funciona? Al ver muchas versiones ruidosas del mismo dato, la red aprende la distribución subyacente, no los detalles superficiales. Es equivalente a aprender el score function de la distribución — ¡la misma idea que subyace a los modelos de difusión!
✨ Sparse Autoencoder
Idea: Permite un espacio latente overcomplete (\( d \geq n \)), pero añade una penalización de sparsity para que solo unas pocas neuronas estén activas a la vez. Cada entrada activa un subconjunto diferente de neuronas.
Donde \( \hat{\rho}_j \) es la activación media de la neurona \( j \) y \( \rho \) es el nivel de sparsity objetivo (típicamente \( \rho = 0.05 \), es decir, solo el 5% de las neuronas activas).
Una alternativa más suave es usar la divergencia KL entre la distribución de activaciones y una Bernoulli con parámetro \( \rho \):
Esto penaliza suavemente las neuronas que se desvían del nivel de sparsity deseado.
Aplicación moderna: Los sparse autoencoders se están usando hoy (2024-2025) para interpretar redes neuronales grandes (LLMs). Anthropic y OpenAI los usan para descomponer las activaciones internas de modelos de lenguaje en features interpretables. ¡Un uso que nadie anticipó!
📄 Bricken et al., 2023 — Towards Monosemanticity (Anthropic)
📏 Contractive Autoencoder (CAE)
Idea: Añade una penalización sobre el jacobiano del encoder, forzando a que la representación latente sea insensible a pequeñas perturbaciones en la entrada:
La norma de Frobenius del jacobiano mide cuánto cambia \( z \) cuando \( x \) cambia un poco. Minimizarla hace que el encoder sea «suave» — resistente a variaciones irrelevantes.
| Criterio | DAE | CAE |
|---|---|---|
| Regularización | Implícita (ruido) | Explícita (jacobiano) |
| Coste computacional | Bajo (solo añadir ruido) | Alto (calcular jacobiano) |
| Conexión teórica | Score matching | Manifold tangente |
| En la práctica | Más usado | Más elegante, menos práctico |
Rifai et al. (2011) demostró que ambos aprenden representaciones similares cuando están bien ajustados. DAE es más práctico; CAE tiene garantías teóricas más fuertes. 📄 Rifai et al., ICML 2011
🔲 Autoencoder Convolucional
Para imágenes, usar capas densas es ineficiente. Los autoencoders convolucionales usan Conv2D en el encoder y ConvTranspose2D (o upsampling) en el decoder:
import torch
import torch.nn as nn
class ConvAutoencoder(nn.Module):
def __init__(self, latent_dim=32):
super().__init__()
# Encoder: 1x28x28 → 32x7x7 → latent
self.encoder = nn.Sequential(
nn.Conv2d(1, 16, 3, stride=2, padding=1), # 16x14x14
nn.BatchNorm2d(16), nn.ReLU(),
nn.Conv2d(16, 32, 3, stride=2, padding=1), # 32x7x7
nn.BatchNorm2d(32), nn.ReLU(),
nn.Flatten(),
nn.Linear(32*7*7, latent_dim)
)
# Decoder: latent → 32x7x7 → 1x28x28
self.decoder = nn.Sequential(
nn.Linear(latent_dim, 32*7*7),
nn.Unflatten(1, (32, 7, 7)),
nn.ConvTranspose2d(32, 16, 3, stride=2, padding=1, output_padding=1),
nn.BatchNorm2d(16), nn.ReLU(),
nn.ConvTranspose2d(16, 1, 3, stride=2, padding=1, output_padding=1),
nn.Sigmoid()
)
def forward(self, x):
z = self.encoder(x)
return self.decoder(z), z
model = ConvAutoencoder(latent_dim=16)
print(f"Parámetros: {sum(p.numel() for p in model.parameters()):,}")
# Loss: nn.MSELoss() o nn.BCELoss()
import tensorflow as tf
from tensorflow.keras import layers, Model
def build_conv_autoencoder(latent_dim=32):
# Encoder
encoder_input = layers.Input(shape=(28, 28, 1))
x = layers.Conv2D(16, 3, strides=2, padding='same', activation='relu')(encoder_input)
x = layers.BatchNormalization()(x)
x = layers.Conv2D(32, 3, strides=2, padding='same', activation='relu')(x)
x = layers.BatchNormalization()(x)
x = layers.Flatten()(x)
z = layers.Dense(latent_dim, name='latent')(x)
encoder = Model(encoder_input, z, name='encoder')
# Decoder
decoder_input = layers.Input(shape=(latent_dim,))
x = layers.Dense(7 * 7 * 32, activation='relu')(decoder_input)
x = layers.Reshape((7, 7, 32))(x)
x = layers.Conv2DTranspose(16, 3, strides=2, padding='same', activation='relu')(x)
x = layers.BatchNormalization()(x)
x = layers.Conv2DTranspose(1, 3, strides=2, padding='same', activation='sigmoid')(x)
decoder = Model(decoder_input, x, name='decoder')
# Full autoencoder
ae_output = decoder(encoder(encoder_input))
autoencoder = Model(encoder_input, ae_output, name='autoencoder')
autoencoder.compile(optimizer='adam', loss='mse')
return autoencoder, encoder, decoder
ae, enc, dec = build_conv_autoencoder(latent_dim=16)
ae.summary()
📋 Resumen comparativo
| Tipo | Regularización | Fórmula clave | Ventaja |
|---|---|---|---|
| Vanilla | Solo bottleneck | \( d < n \) | Simple, línea base |
| Denoising | Ruido en la entrada | \( \tilde{x} = x + \epsilon \) | Robusto, aprende distribución |
| Sparse | Penalización activaciones | \( \lambda \sum |\hat{\rho}_j - \rho| \) | Features interpretables |
| Contractive | Penalización jacobiano | \( \lambda \|J\|_F^2 \) | Insensible a perturbaciones |
| Convolucional | Estructura espacial | Conv2D + ConvTranspose2D | Eficiente para imágenes |
| VAE → | Probabilístico | \( \text{KL}(q_\phi \| p) \) | Generativo, espacio continuo |
🌟 VAE: de reconstruir a generar
El autoencoder estándar aprende a reconstruir, pero no a generar. ¿Por qué? Porque su espacio latente no tiene estructura probabilística: los puntos se distribuyen de forma irregular, con «huecos» donde el decoder no sabe qué producir.
El problema fundamental: Si tomo un punto aleatorio \( z \) del espacio latente de un AE estándar y lo paso por el decoder, el resultado suele ser basura. El espacio latente no es continuo ni está regularizado.
La solución del VAE: Forzar al espacio latente a seguir una distribución conocida (Gaussiana estándar). Así, cualquier punto muestreado de \( \mathcal{N}(0, I) \) produce una salida coherente.
La diferencia visual es clara: el espacio latente del AE estándar tiene «agujeros» — regiones donde ningún dato del entrenamiento fue codificado, y por lo tanto el decoder no tiene información sobre qué producir. El VAE, al forzar que todo el espacio latente se parezca a una gaussiana estándar, garantiza que cualquier punto muestreado producirá una salida coherente. Este es el salto conceptual que convierte a un autoencoder en un modelo generativo.
📐 Formulación Probabilística del VAE
El VAE (Kingma & Welling, 2013) reformula el autoencoder como un modelo generativo probabilístico. En lugar de aprender una función determinista encoder-decoder, el VAE aprende una distribución sobre el espacio latente. Esta reformulación, que se apoya en la inferencia variacional — una de las técnicas más potentes del ML bayesiano — es lo que permite al VAE generar muestras nuevas de forma principled.
El marco conceptual tiene cuatro elementos:
Clave: El encoder no produce un punto \( z \), sino los parámetros de una distribución (\( \mu \) y \( \sigma \)). Cada entrada \( x \) se mapea a una «nube» gaussiana en el espacio latente, no a un punto fijo.
📊 ELBO: la función objetivo del VAE
El objetivo del VAE es maximizar la Evidence Lower BOund (ELBO), que es una cota inferior de la log-verosimilitud de los datos:
«¿Qué tan bien reconstruye el decoder?» Equivale a la loss de reconstrucción (MSE o BCE). Queremos maximizar esto.
«¿Qué tan cerca está la distribución latente de N(0,I)?» Penaliza desviaciones. Queremos minimizar esto.
En la práctica, la loss del VAE es:
Para dos gaussianas univariadas \( q = \mathcal{N}(\mu, \sigma^2) \) y \( p = \mathcal{N}(0, 1) \):
Desarrollando con las densidades gaussianas:
Para \( d \) dimensiones independientes, simplemente sumamos. Esta fórmula cerrada es una de las razones por las que el VAE usa gaussianas: la KL se calcula analíticamente, sin necesidad de Monte Carlo.
🔧 Reparameterization Trick
El problema: En el forward pass, necesitamos muestrear \( z \sim q_\phi(z|x) = \mathcal{N}(\mu, \sigma^2) \). Pero el muestreo es una operación no diferenciable — no podemos hacer backpropagation a través de él.
La solución: Reparametrizar el muestreo separando la parte estocástica:
Genial truco: En vez de muestrear de \( \mathcal{N}(\mu, \sigma^2) \), muestreamos \( \epsilon \) de \( \mathcal{N}(0, 1) \) (fijo) y luego hacemos \( z = \mu + \sigma \cdot \epsilon \). Los gradientes fluyen a través de \( \mu \) y \( \sigma \) sin problemas. El \( \epsilon \) es solo ruido externo, no parte del grafo computacional.
🎨 Generación con VAE
Una vez entrenado, generar nuevas muestras es trivial:
El trade-off reconstrucción vs regularización:
- Si priorizas reconstrucción: El espacio latente puede ser irregular, pero las reconstrucciones son nítidas.
- Si priorizas KL: El espacio latente es suave y regular, pero las reconstrucciones pueden ser borrosas.
- Las imágenes VAE tienden a ser ligeramente borrosas — este es el «precio» de un espacio latente bien regularizado. Los GANs y modelos de difusión abordan esto de otras formas.
🧪 Explorador interactivo: VAE
Experimenta con los parámetros de un VAE y observa el trade-off entre reconstrucción y regularización:
Referencias clave del VAE
- Kingma & Welling (2013). Auto-Encoding Variational Bayes. arXiv:1312.6114 — El paper original del VAE.
- Doersch (2016). Tutorial on Variational Autoencoders. arXiv:1606.05908 — Tutorial excelente y accesible.
- Blei, Kucukelbir & McAuliffe (2017). Variational Inference: A Review for Statisticians. arXiv:1601.00670 — Contexto de inferencia variacional.
📖 Ver también: GANs | Modelos de Difusión
🎛️ β-VAE: Representaciones Disentangled
Higgins et al. (2017) propusieron una modificación simple pero poderosa: multiplicar el término KL por un factor \( \beta \). Aunque parezca un cambio trivial, tiene implicaciones profundas sobre la estructura del espacio latente y la calidad de las representaciones aprendidas.
| β | Efecto | Trade-off |
|---|---|---|
| β = 0 | Autoencoder estándar (sin regularización) | Buena reconstrucción, espacio latente irregular |
| β = 1 | VAE estándar | Balance reconstrucción / regularización |
| β > 1 | Mayor presión hacia N(0,I) | Representaciones disentangled, reconstrucción más borrosa |
| β ≫ 1 | El latente colapsa a N(0,I) | «Posterior collapse»: pierde toda la información |
¿Qué significa «disentangled»? Que cada dimensión del espacio latente controla un factor de variación independiente. Por ejemplo, en caras: z₁ = sonrisa, z₂ = orientación, z₃ = gafas... Cambiar un eje cambia solo un atributo.
Con β > 1, el VAE está más «presionado» a distribuir la información en dimensiones ortogonales e independientes.
La KL con \( p(z) = \mathcal{N}(0, I) \) puede descomponerse (Chen et al. 2018) en:
Con β alto, se penaliza especialmente la información mutua, forzando al modelo a usar solo la información mínima necesaria en cada dimensión latente. Esto induce independencia estadística entre las dimensiones → disentanglement.
🏷️ Conditional VAE (CVAE)
El CVAE (Sohn et al., 2015) condiciona tanto el encoder como el decoder en una etiqueta \( y \) (clase, atributo, texto...). Esta extensión es fundamental: permite pasar de generar al azar a generar con control, una capacidad esencial en aplicaciones reales.
Esto permite generar con control: «genera un dígito 7» o «genera una cara con gafas».
🧊 VQ-VAE: Cuantización Vectorial
Van den Oord et al. (2017) propusieron el VQ-VAE, que usa un espacio latente discreto en lugar de continuo. Esta idea rompe con la tradición de los VAE gaussianos y abre la puerta a combinar autoencoders con modelos autoregresivos como Transformers. El codebook de embeddings actúa como un diccionario aprendido de «palabras visuales».
Donde \( \text{sg}[\cdot] \) es «stop gradient». Los tres términos son: reconstrucción, commitment loss (acercar encoder a embeddings), y codebook loss (acercar embeddings a encoder).
¿Por qué importa? VQ-VAE es el ancestro directo de DALL-E (OpenAI, 2021), que usa VQ-VAE para tokenizar imágenes y luego un Transformer para generar secuencias de tokens de imagen. También inspira Stable Diffusion, que opera en el espacio latente de un autoencoder (de ahí «Latent Diffusion Model»).
💻 Implementación completa: VAE
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
class VAE(nn.Module):
def __init__(self, input_dim=784, hidden_dim=400, latent_dim=20):
super().__init__()
# Encoder
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc_mu = nn.Linear(hidden_dim, latent_dim)
self.fc_logvar = nn.Linear(hidden_dim, latent_dim)
# Decoder
self.fc3 = nn.Linear(latent_dim, hidden_dim)
self.fc4 = nn.Linear(hidden_dim, input_dim)
def encode(self, x):
h = F.relu(self.fc1(x))
return self.fc_mu(h), self.fc_logvar(h)
def reparameterize(self, mu, logvar):
"""El famoso reparameterization trick"""
std = torch.exp(0.5 * logvar) # σ = exp(logσ²/2)
eps = torch.randn_like(std) # ε ~ N(0, I)
return mu + std * eps # z = μ + σ·ε
def decode(self, z):
h = F.relu(self.fc3(z))
return torch.sigmoid(self.fc4(h))
def forward(self, x):
mu, logvar = self.encode(x.view(-1, 784))
z = self.reparameterize(mu, logvar)
return self.decode(z), mu, logvar
def vae_loss(x_recon, x, mu, logvar):
"""ELBO loss = reconstrucción + KL"""
# Reconstrucción (BCE porque sigmoid en decoder)
recon = F.binary_cross_entropy(x_recon, x.view(-1, 784), reduction='sum')
# KL divergence con N(0,I) — fórmula cerrada
kl = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
return recon + kl
# Entrenamiento
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = VAE(latent_dim=20).to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
train_loader = DataLoader(
datasets.MNIST('./data', train=True, download=True,
transform=transforms.ToTensor()),
batch_size=128, shuffle=True
)
for epoch in range(10):
model.train()
total_loss = 0
for x, _ in train_loader:
x = x.to(device)
x_recon, mu, logvar = model(x)
loss = vae_loss(x_recon, x, mu, logvar)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch {epoch+1}, Loss: {total_loss/len(train_loader.dataset):.2f}")
# Generar nuevas muestras
model.eval()
with torch.no_grad():
z = torch.randn(16, 20).to(device) # Muestrear de N(0,I)
samples = model.decode(z) # Decodificar
# samples tiene forma (16, 784) → reshape a (16, 1, 28, 28)
import tensorflow as tf
from tensorflow.keras import layers, Model
import numpy as np
class Sampling(layers.Layer):
"""Reparameterization trick como capa de Keras"""
def call(self, inputs):
mu, log_var = inputs
eps = tf.random.normal(shape=tf.shape(mu))
return mu + tf.exp(0.5 * log_var) * eps
class VAE(Model):
def __init__(self, latent_dim=20, **kwargs):
super().__init__(**kwargs)
self.latent_dim = latent_dim
# Encoder
self.encoder_dense = layers.Dense(400, activation='relu')
self.mu_layer = layers.Dense(latent_dim)
self.logvar_layer = layers.Dense(latent_dim)
self.sampling = Sampling()
# Decoder
self.decoder_dense1 = layers.Dense(400, activation='relu')
self.decoder_out = layers.Dense(784, activation='sigmoid')
def encode(self, x):
h = self.encoder_dense(x)
return self.mu_layer(h), self.logvar_layer(h)
def decode(self, z):
h = self.decoder_dense1(z)
return self.decoder_out(h)
def call(self, x):
x_flat = tf.reshape(x, [-1, 784])
mu, logvar = self.encode(x_flat)
z = self.sampling([mu, logvar])
x_recon = self.decode(z)
# Añadir KL loss
kl_loss = -0.5 * tf.reduce_sum(
1 + logvar - tf.square(mu) - tf.exp(logvar), axis=1
)
self.add_loss(tf.reduce_mean(kl_loss))
return x_recon
# Entrenamiento
(x_train, _), _ = tf.keras.datasets.mnist.load_data()
x_train = x_train.astype('float32') / 255.0
vae = VAE(latent_dim=20)
vae.compile(optimizer='adam', loss='binary_crossentropy')
vae.fit(x_train.reshape(-1, 784), x_train.reshape(-1, 784),
epochs=10, batch_size=128, validation_split=0.1)
# Generar
z_sample = np.random.normal(size=(16, 20)).astype('float32')
generated = vae.decode(z_sample).numpy().reshape(-1, 28, 28)
Referencias del VAE avanzado
- Higgins et al. (2017). beta-VAE: Learning Basic Visual Concepts with a Constrained Variational Framework. ICLR 2017 — β-VAE y disentanglement.
- Sohn, Lee & Yan (2015). Learning Structured Output Representation using Deep Conditional Generative Models. NeurIPS 2015 — CVAE.
- van den Oord, Vinyals & Kavukcuoglu (2017). Neural Discrete Representation Learning. arXiv:1711.00937 — VQ-VAE.
- Chen et al. (2018). Isolating Sources of Disentanglement in VAEs. arXiv:1802.04942 — Descomposición TC de la KL.
📖 Ver también: GANs (VAE-GAN) | Difusión (Latent Diffusion)
🌍 Aplicaciones de los Autoencoders
🧪 Widget: Detector de anomalías con AE
Simula cómo un autoencoder detecta anomalías basándose en el error de reconstrucción:
🌐 Los Autoencoders en el Ecosistema Generativo
Los autoencoders no son un modelo aislado — son el fundamento conceptual de gran parte de la IA generativa moderna:
Conexiones clave con otros modelos (que verás en los siguientes submódulos):
- GANs: Resuelven el problema de las imágenes borrosas del VAE usando un discriminador adversarial. El VAE-GAN combina ambos.
- Difusión: El DAE es el ancestro conceptual de los modelos de difusión (denoising iterativo). Stable Diffusion opera en el espacio latente de un autoencoder.
- Transformers/LLMs: VQ-VAE tokeniza imágenes para que los Transformers las procesen como secuencias. Los tokenizers de texto son, en cierto sentido, encoders.
🏛️ El Legado de los Autoencoders
| Concepto del AE | Dónde aparece hoy |
|---|---|
| Espacio latente | Latent Diffusion, CLIP, embeddings en general |
| Encoder-Decoder | Seq2Seq, U-Net, Transformers (encoder-decoder) |
| Bottleneck | Information bottleneck theory, compression |
| Reparameterization trick | Gumbel-Softmax, normalizing flows, score models |
| ELBO | Inferencia variacional en todo el ML bayesiano |
| Denoising | DDPM, score matching, data augmentation |
| VQ (cuantización) | DALL-E, Codex, tokenización de imágenes/audio |
Lo que has aprendido en este submódulo:
- Qué es un autoencoder y cómo funciona la arquitectura encoder-decoder
- El espacio latente: compresión, manifolds, interpolación
- Variantes: denoising, sparse, contractive, convolucional
- El VAE: formulación probabilística, ELBO, KL divergence, reparameterization trick
- VAE avanzado: β-VAE (disentanglement), CVAE, VQ-VAE
- Implementación completa en PyTorch y TensorFlow
- Aplicaciones prácticas y la conexión con el resto de la IA generativa
Con estos fundamentos, estás preparado para entender GANs, modelos de difusión y Transformers — todos ellos construyen sobre las ideas que acabas de aprender.
Referencias generales
- Goodfellow, Bengio & Courville (2016). Deep Learning, Cap. 14 — Autoencoders. deeplearningbook.org
- Kingma & Welling (2019). An Introduction to Variational Autoencoders. arXiv:1906.02691 — Monografía completa.
- Rombach et al. (2022). High-Resolution Image Synthesis with Latent Diffusion Models. arXiv:2112.10752 — Stable Diffusion (usa un AE latente).
- Ramesh et al. (2021). Zero-Shot Text-to-Image Generation. arXiv:2102.12092 — DALL-E (basado en VQ-VAE).
📖 Submódulos relacionados: GANs | Modelos de Difusión | Transformers / LLMs