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.
🏗️ Arquitectura Encoder-Decoder
Todo autoencoder tiene dos partes simétricas conectadas por el espacio latente:
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.
🌌 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:
🗺️ 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ó!
📏 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.
🔲 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.
📐 Formulación Probabilística del VAE
El VAE (Kingma & Welling, 2013) reformula el autoencoder como un modelo generativo probabilístico:
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:
🎛️ β-VAE: Representaciones Disentangled
Higgins et al. (2017) propusieron una modificación simple pero poderosa: multiplicar el término KL por un factor \( \beta \):
| β | 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...):
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:
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)
🌍 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.