PyTorch Autoencoder

El autoencoder (Autoencoder, AE)es una red neuronal de aprendizaje no supervisado que comprime los datos de entrada a un espacio latente de baja dimensión y reconstruye los datos originales a partir de la representación comprimida.

Los autoencoders se utilizan ampliamente en escenarios como reducción de dimensionalidad de datos, extracción de características, detección de anomalías, denoising de imágenes y modelos generativos.


1. Principios básicos del autoencoder

La estructura básica de un autoencoder incluye tres partes:

  • Codificador (Encoder): mapea los datos de entrada \(x\) a una representación latente de baja dimensión \(z\)
  • Espacio latente (Latent Space): el vector de baja dimensión que genera el codificador, también conocido como capa de cuello de botella
  • Decodificador (Decoder): reconstruye la representación latente \(z\) en la salida \(\hat{x}\)

1.1 Estructura de la red

El objetivo del autoencoder es que la salida \(\hat{x}\) sea lo más cercana posible a la entrada \(x\):

\[ \min_{\theta, \phi} \frac{1}{n} \sum_{i=1}^{n} \| x_i - D_\phi(E_\theta(x_i)) \|^2 \]

donde \(\theta\) son los parámetros del codificador y \(\phi\) son los parámetros del decodificador.

1.2 Efecto de reducción de dimensionalidad

El autoencoder aprende una representación comprimida de los datos al forzarlos a pasar por una capa de cuello de botella con menor dimensión que la entrada. Esta compresión conserva la información principal de los datos.

En comparación con el Análisis de Componentes Principales (PCA), el autoencoder puede aprender una reducción de dimensionalidad no lineal y capturar estructuras de datos más complejas.


2. Implementación básica del autoencoder

2.1 Autoencoder simple

Ejemplo

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset

# ── Modelo de autoencoder ─────────────────────────────────
class Autoencoder(nn.Module):
    """
Autoencoder básico: estructura simétrica
    """

    def __init__(self, input_dim, hidden_dim, latent_dim):
        super().__init__()

        # Codificador
        self.encoder = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, latent_dim),  # Capa de cuello de botella
        )

        # Decodificador
        self.decoder = nn.Sequential(
            nn.Linear(latent_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, input_dim),
        )

    def forward(self, x):
        z = self.encoder(x)
        x_recon = self.decoder(z)
        return x_recon

    def encode(self, x):
        """Codificación: obtener la representación latente"""
        return self.encoder(x)

    def decode(self, z):
        """Decodificación: reconstruir desde la representación latente"""
        return self.decoder(z)


# ── Ejemplo de uso ──────────────────────────────────────
INPUT_DIM = 784   # Por ejemplo, después de aplanar la imagen MNIST
HIDDEN_DIM = 256
LATENT_DIM = 32   # La dimensión del espacio latente, mucho menor que la dimensión de entrada

model = Autoencoder(INPUT_DIM, HIDDEN_DIM, LATENT_DIM)
print(f"Dimensión de entrada: {INPUT_DIM}")
print(f"Dimensión latente: {LATENT_DIM}")
print(f"Relación de compresión: {INPUT_DIM / LATENT_DIM:.1f}x")

# Ver cantidad de parámetros
total_params = sum(p.numel() for p in model.parameters())
print(f"Total de parámetros: {total_params:,}")

2.2 Autoencoder convolucional

Para datos de imagen, los autoencoders que usan capas convolucionales funcionan mejor:

Ejemplo

import torch
import torch.nn as nn

class ConvAutoencoder(nn.Module):
    """
Autoencoder convolucional: adecuado para imágenes
    """

    def __init__(self, channels=3, latent_dim=128):
        super().__init__()

        # Codificador: reduce gradualmente el tamaño, aumenta el número de canales
        # Entrada: (batch, channels, 64, 64)
        self.encoder = nn.Sequential(
            # 32 -> 16
            nn.Conv2d(channels, 32, kernel_size=3, stride=2, padding=1),
            nn.ReLU(),
            # 16 -> 8
            nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1),
            nn.ReLU(),
            # 8 -> 4
            nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1),
            nn.ReLU(),
            # 4 -> 2
            nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1),
            nn.ReLU(),
        )

        # Mapeo del espacio latente
        self.to_latent = nn.AdaptiveAvgPool2d((1, 1))

        # Decodificador: aumenta gradualmente el tamaño
        # Entrada: (batch, 256, 2, 2)
        self.from_latent = nn.Sequential(
            nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
            nn.Conv2d(256, 128, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
            nn.Conv2d(128, 64, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
            nn.Conv2d(64, 32, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
            nn.Conv2d(32, channels, kernel_size=3, padding=1),
            nn.Sigmoid()  # Salida [0, 1]
        )

    def forward(self, x):
        z = self.encode(x)
        x_recon = self.decode(z)
        return x_recon

    def encode(self, x):
        """Codificar"""
        features = self.encoder(x)
        z = self.to_latent(features)
        z = z.view(z.size(0), -1)  # (batch, 256)
        return z

    def decode(self, z):
        """Decodificar"""
        # Reestructurar el vector a un mapa de características
        batch_size = z.size(0)
        z = z.view(batch_size, 256, 1, 1)
        z = z.expand(-1, -1, 2, 2)  # Sobremuestrear a 2x2
        x_recon = self.from_latent(z)
        return x_recon


# Prueba
model = ConvAutoencoder(channels=3, latent_dim=128)
x = torch.randn(4, 3, 64, 64)
x_recon = model(x)

print(f"Forma de entrada: {x.shape}")
print(f"Forma de salida: {x_recon.shape}")
print(f"Forma del vector latente: {model.encode(x).shape}")

2.3 Entrenamiento y reconstrucción

Ejemplo

import torch
import torch.nn as nn
import torch.optim as optim

# ── Configuración de entrenamiento ────────────────────────────
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = ConvAutoencoder(channels=3, latent_dim=128).to(device)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)

# ── Bucle de entrenamiento ────────────────────────────────────
def train_autoencoder(model, dataloader, criterion, optimizer, num_epochs=10):
    model.train()
    for epoch in range(num_epochs):
        total_loss = 0
        for batch in dataloader:
            images = batch[0].to(device)

            # Propagación hacia adelante
            outputs = model(images)
            loss = criterion(outputs, images)

            # Propagación hacia atrás
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            total_loss += loss.item()

        avg_loss = total_loss / len(dataloader)
        print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.6f}")

    return model


# Suponiendo que ya existe un cargador de datos
# train_autoencoder(model, train_loader, criterion, optimizer, num_epochs=10)
print("¡Entrenamiento del autoencoder completado!")

3. Autoencoder de denoising (DAE)

El autoencoder de denoising (Denoising Autoencoder, DAE)añade ruido a la entrada durante el entrenamiento y luego aprende a eliminar el ruido para recuperar la entrada original. Esto permite que el modelo aprenda representaciones de características más robustas.

3.1 Implementación del autoencoder de denoising

Ejemplo

import torch
import torch.nn as nn

class DenoisingAutoencoder(nn.Module):
    """
Autoencoder de denoising
    """

    def __init__(self, input_dim, hidden_dim, latent_dim):
        super().__init__()

        self.encoder = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, latent_dim),
        )

        self.decoder = nn.Sequential(
            nn.Linear(latent_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, input_dim),
            nn.Sigmoid()  # Salida [0, 1]
        )

    def forward(self, x):
        z = self.encode(x)
        return self.decode(z)

    def encode(self, x):
        return self.encoder(x)

    def decode(self, z):
        return self.decoder(z)


def add_noise(x, noise_factor=0.3):
    """
Añadir ruido gaussiano
    """

    noise = torch.randn_like(x) * noise_factor
    noisy_x = x + noise
    return torch.clamp(noisy_x, 0.0, 1.0)


# Entrenar el autoencoder de denoising
def train_dae(model, dataloader, noise_factor=0.3, lr=1e-3):
    criterion = nn.MSELoss()
    optimizer = optim.Adam(model.parameters(), lr=lr)

    model.train()
    for epoch in range(10):
        for batch in dataloader:
            images = batch[0]

            # Añadir ruido
            noisy_images = add_noise(images, noise_factor)
            noisy_images = noisy_images.to(next(model.parameters()).device)
            images = images.to(next(model.parameters()).device)

            # Propagación hacia adelante
            outputs = model(noisy_images)
            loss = criterion(outputs, images)  # Comparar con la imagen original, no con la imagen con ruido

            # Propagación hacia atrás
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

    return model

3.2 Otros tipos de ruido

Ejemplo

def salt_pepper_noise(x, prob=0.1):
    """Ruido de sal y pimienta"""
    random_mask = torch.rand_like(x)
    noisy = x.clone()
    noisy[random_mask < prob / 2] = 0.0
    noisy[random_mask > 1 - prob / 2] = 1.0
    return noisy


def mask_noise(x, prob=0.1):
    """Ruido de enmascaramiento (puesta a cero aleatoria)"""
    mask = torch.rand_like(x) > prob
    return x * mask.float()


def dropout_noise(x, rate=0.2):
    """Ruido Dropout"""
    mask = torch.rand_like(x) > rate
    return x * mask.float() / (1 - rate)

4. Autoencoder variacional (VAE)

El autoencoder variacional (Variational Autoencoder, VAE)es un modelo generativo que codifica los datos como una distribución de probabilidad en el espacio latente, en lugar de un vector fijo. Esto nos permite muestrear del espacio latente para generar nuevos datos.

4.1 Principios fundamentales del VAE

La innovación clave del VAE es aprender la distribución de probabilidad de las variables latentes:

  • El codificador genera la media \(\mu\) y la desviación estándar \(\sigma\)
  • Se muestrea un vector latente \(z\) de la distribución normal \(\mathcal{N}(\mu, \sigma)\)
  • El decodificador reconstruye los datos a partir de \(z\)

Para lograr un proceso de muestreo diferenciable, se utiliza elTruco de reparametrización (Reparameterization Trick):

\[ z = \mu + \sigma \cdot \epsilon, \quad \epsilon \sim \mathcal{N}(0, 1) \]

4.2 Implementación del VAE

Ejemplo

import torch
import torch.nn as nn
import torch.optim as optim

class VAE(nn.Module):
    """
Autoencoder variacional
    """

    def __init__(self, input_dim, hidden_dim, latent_dim):
        super().__init__()

        # Codificador: genera media y varianza
        self.encoder = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
        )
        self.fc_mu = nn.Linear(hidden_dim, latent_dim)
        self.fc_logvar = nn.Linear(hidden_dim, latent_dim)

        # Decodificador
        self.decoder = nn.Sequential(
            nn.Linear(latent_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, input_dim),
            nn.Sigmoid()
        )

    def encode(self, x):
        h = self.encoder(x)
        mu = self.fc_mu(h)
        logvar = self.fc_logvar(h)
        return mu, logvar

    def reparameterize(self, mu, logvar):
        """Truco de reparametrización"""
        std = torch.exp(0.5 * logvar)
        eps = torch.randn_like(std)
        return mu + eps * std

    def decode(self, z):
        return self.decoder(z)

    def forward(self, x):
        mu, logvar = self.encode(x)
        z = self.reparameterize(mu, logvar)
        x_recon = self.decode(z)
        return x_recon, mu, logvar


def vae_loss(x_recon, x, mu, logvar, beta=1.0):
    """
Función de pérdida del VAE
Pérdida de reconstrucción + divergencia KL
    """

    # Pérdida de reconstrucción
    recon_loss = nn.functional.mse_loss(x_recon, x, reduction='sum')

    # Divergencia KL: -0.5 * sum(1 + log(sigma^2) - mu^2 - sigma^2)
    kl_loss = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())

    return recon_loss + beta * kl_loss, recon_loss, kl_loss


# Ejemplo de uso
INPUT_DIM = 784
HIDDEN_DIM = 256
LATENT_DIM = 2  # Espacio latente bidimensional para facilitar la visualización

model = VAE(INPUT_DIM, HIDDEN_DIM, LATENT_DIM)

# Prueba
x = torch.randn(32, 784)
x_recon, mu, logvar = model(x)

print(f"Forma de entrada: {x.shape}")
print(f"Forma de reconstrucción: {x_recon.shape}")
print(f"Forma de la media: {mu.shape}")       # (32, 2)
print(f"Forma de la varianza: {logvar.shape}")   # (32, 2)

4.3 Generación y visualización con VAE

Ejemplo

import matplotlib.pyplot as plt

def visualize_latent_space(model, dataloader, device):
    """Visualizar el espacio latente"""
    model.eval()
    all_mu = []
    all_labels = []

    with torch.no_grad():
        for batch in dataloader:
            images, labels = batch[0].to(device), batch[1]
            mu, _ = model.encode(images)
            all_mu.append(mu.cpu())
            all_labels.append(labels)

    all_mu = torch.cat(all_mu, dim=0).numpy()
    all_labels = torch.cat(all_labels, dim=0).numpy()

    plt.figure(figsize=(10, 8))
    scatter = plt.scatter(all_mu[:, 0], all_mu[:, 1], c=all_labels,
                          cmap='tab10', alpha=0.5, s=10)
    plt.colorbar(scatter)
    plt.xlabel('Latent Dimension 1')
    plt.ylabel('Latent Dimension 2')
    plt.title('VAE Latent Space')
    plt.show()


def generate_from_latent(model, z, device):
    """Generar imágenes desde vectores latentes"""
    model.eval()
    with torch.no_grad():
        z = z.to(device)
        generated = model.decode(z)
    return generated


def interpolate_latent(model, z1, z2, steps=10, device):
    """Generación por interpolación en el espacio latente"""
    model.eval()

    # Interpolación lineal
    alphas = torch.linspace(0, 1, steps)
    interpolated = []

    with torch.no_grad():
        for alpha in alphas:
            z = z1 * (1 - alpha) + z2 * alpha
            generated = model.decode(z)
            interpolated.append(generated)

    return torch.cat(interpolated, dim=0)


# Generar nuevas imágenes
def generate_new_images(model, num_images, latent_dim, device):
    """Generar nuevas imágenes desde vectores latentes aleatorios"""
    model.eval()
    with torch.no_grad():
        # Muestrear de una distribución normal estándar
        z = torch.randn(num_images, latent_dim).to(device)
        generated = model.decode(z)
    return generated

El espacio latente de un VAE es continuo, por lo que se puede interpolar en el espacio latente para generar imágenes con transiciones suaves. Sin embargo, las imágenes generadas por un VAE suelen ser borrosas, porque optimiza un límite inferior en lugar de la verosimilitud logarítmica exacta.


5. Autoencoder disperso

Autoencoder disperso (Sparse Autoencoder)Se añade una restricción de dispersión a la función de pérdida para limitar el número de activaciones en el vector latente. Esto permite al modelo aprender características más significativas.

5.1 Implementación del autoencoder disperso

< h2 class="example">Ejemplo
import torch
import torch.nn as nn
import torch.nn.functional as F

class SparseAutoencoder(nn.Module):
    """
Autoencoder disperso
    """

    def __init__(self, input_dim, hidden_dim, latent_dim):
        super().__init__()

        # Codificador
        self.encoder = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
        )
        # Capa latente
        self.bottleneck = nn.Linear(hidden_dim, latent_dim)

        # Decodificador
        self.decoder = nn.Sequential(
            nn.Linear(latent_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, input_dim),
            nn.Sigmoid()
        )

    def forward(self, x):
        h = self.encoder(x)
        z = self.bottleneck(h)
        z_activated = F.relu(z)  # Activación dispersa
        x_recon = self.decoder(z_activated)
        return x_recon, z_activated


def sparse_loss(z, rho=0.05, beta=1.0):
    """
Pérdida de dispersión: divergencia KL
rho: dispersión objetivo (por ejemplo, 0.05 significa que solo el 5% de las neuronas deberían activarse)
beta: peso del término de dispersión
    """

    # Calcular la activación media
    rho_hat = torch.mean(z, dim=0)

    # Divergencia KL
    kl = rho * torch.log(rho / (rho_hat + 1e-8)) + \
         (1 - rho) * torch.log((1 - rho) / (1 - rho_hat + 1e-8))

    return beta * torch.sum(kl)


def total_sparse_loss(x_recon, x, z, rho=0.05, beta=1.0):
    """Pérdida total = pérdida de reconstrucción + pérdida de dispersión"""
    recon_loss = F.mse_loss(x_recon, x)
    sparsity = sparse_loss(z, rho, beta)
    return recon_loss + sparsity

6. Autoencoder secuencia a secuencia

Para datos secuenciales (como texto o series temporales), se utilizan RNN/LSTM como codificador y decodificador.

6.1 Implementación del autoencoder de secuencias

Ejemplo

import torch
import torch.nn as nn

class Seq2SeqAutoencoder(nn.Module):
    """
Autoencoder secuencia a secuencia: para datos secuenciales
    """

    def __init__(self, input_size, hidden_size, latent_size, num_layers=2):
        super().__init__()

        # Codificador LSTM
        self.encoder = nn.LSTM(
            input_size=input_size,
            hidden_size=hidden_size,
            num_layers=num_layers,
            batch_first=True,
            bidirectional=True
        )

        # Mapeo al espacio latente
        # La salida del LSTM bidireccional es hidden_size * 2
        self.to_latent = nn.Linear(hidden_size * 2, latent_size)
        self.from_latent = nn.Linear(latent_size, hidden_size * 2)

        # Decodificador LSTM
        self.decoder = nn.LSTM(
            input_size=hidden_size,
            hidden_size=hidden_size,
            num_layers=num_layers,
            batch_first=True,
            bidirectional=True
        )

        # Mapeo de salida
        self.output_proj = nn.Linear(hidden_size * 2, input_size)

    def forward(self, x):
        # Codificación
        _, (h_n, _) = self.encoder(x)

        # Combinar el último estado oculto bidireccional
        # h_n: (num_layers * 2, batch, hidden_size)
        h_forward = h_n[-2]
        h_backward = h_n[-1]
        h_combined = torch.cat([h_forward, h_backward], dim=-1)

        # Mapear al espacio latente
        z = self.to_latent(h_combined)

        # Mapear de vuelta desde el espacio latente
        decoder_init = self.from_latent(z)
        decoder_init = decoder_init.view(2, decoder_init.size(0), -1)  # (2, batch, hidden_size*2)

        # Decodificar (usando la longitud de entrada original)
        decoder_output, _ = self.decoder(
            x,
            (decoder_init, torch.zeros_like(decoder_init))
        )

        # Mapeo de salida
        output = self.output_proj(decoder_output)
        return output, z


# Ejemplo de uso
model = Seq2SeqAutoencoder(
    input_size=128,   # Dimensión de características de entrada
    hidden_size=256,  # Dimensión oculta del LSTM
    latent_size=64,   # Dimensión del espacio latente
    num_layers=2
)

# Prueba
x = torch.randn(8, 20, 128)  # (batch, seq_len, input_size)
output, z = model(x)

print(f"Forma de entrada: {x.shape}")        # (8, 20, 128)
print(f"Forma de salida: {output.shape}")  # (8, 20, 128)
print(f"Forma del vector latente: {z.shape}")    # (8, 64)

7. Escenarios de aplicación del autoencoder

7.1 Detección de anomalías

Un autoencoder puede utilizarse para detectar datos anómalos. El error de reconstrucción es pequeño para datos normales y grande para datos anómalos:

Ejemplo

import torch
import torch.nn as nn

def detect_anomalies(model, data_loader, threshold=None, device='cpu'):
    """
Detección de anomalías usando un autoencoder
    """

    model.eval()
    reconstruction_errors = []

    with torch.no_grad():
        for batch in data_loader:
            images = batch[0].to(device)
            outputs = model(images)

            # Calcular el error de reconstrucción (error cuadrático medio)
            errors = torch.mean((outputs - images) ** 2, dim=(1, 2, 3))
            reconstruction_errors.extend(errors.cpu().numpy())

    reconstruction_errors = torch.tensor(reconstruction_errors)

    # Si no se da un umbral, usar un método estadístico
    if threshold is None:
        # Usar el percentil 95
        threshold = torch.quantile(reconstruction_errors, 0.95).item()

    # Marcar anomalías
    anomalies = reconstruction_errors > threshold

    return anomalies, reconstruction_errors, threshold


# Entrenar modelo de detección de anomalías
def train_anomaly_detector(normal_data_loader):
    """Entrenar el autoencoder solo con datos normales"""
    model = ConvAutoencoder(channels=1, latent_dim=32)
    criterion = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

    model.train()
    for epoch in range(10):
        for batch in normal_data_loader:
            images = batch[0]
            outputs = model(images)
            loss = criterion(outputs, images)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

    return model

7.2 Coloreado de imágenes y transferencia de estilo

Ejemplo

class ColorizationAutoencoder(nn.Module):
    """
Autoencoder para colorización de imágenes
Entrada: imagen en escala de grises (batch, 1, H, W)
Salida: imagen en color (batch, 2, H, W) (espacio de color ab)
    """

    def __init__(self):
        super().__init__()

        # Codificador: extraer características gradualmente
        self.encoder = nn.Sequential(
            nn.Conv2d(1, 64, kernel_size=4, stride=2, padding=1),   # H/2
            nn.ReLU(),
            nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1),  # H/4
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.Conv2d(128, 256, kernel_size=4, stride=2, padding=1), # H/8
            nn.BatchNorm2d(256),
            nn.ReLU(),
        )

        # Decodificador: aumentar la resolución para generar color
        self.decoder = nn.Sequential(
            nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.ConvTranspose2d(64, 32, kernel_size=4, stride=2, padding=1),
            nn.ReLU(),
            nn.Conv2d(32, 2, kernel_size=3, padding=1),  # Salida de canales ab
            nn.Sigmoid()  # Rango de canales ab [0, 1]
        )

    def forward(self, x):
        z = self.encoder(x)
        color = self.decoder(z)
        return color

7.3 Visualización de reducción de dimensionalidad de datos

Ejemplo

import matplotlib.pyplot as plt

def visualize_latent_2d(model, dataloader, device, num_samples=1000):
    """
Usar un autoencoder para reducir los datos a 2 dimensiones y visualizarlos
    """

    model.eval()
    all_latents = []
    all_labels = []

    with torch.no_grad():
        count = 0
        for batch in dataloader:
            if count >= num_samples:
                break
            images, labels = batch[0], batch[1]
            images = images.to(device)

            # Si es un AE 2D, usar directamente
            # Si la dimensión es mayor, primero proyectar a 2D
            z = model.encode(images)

            all_latents.append(z.cpu())
            all_labels.append(labels)
            count += images.size(0)

    latents = torch.cat(all_latents, dim=0)[:num_samples].numpy()
    labels = torch.cat(all_labels, dim=0)[:num_samples].numpy()

    plt.figure(figsize=(10, 8))
    scatter = plt.scatter(latents[:, 0], latents[:, 1], c=labels,
                          cmap='tab10', alpha=0.6, s=20)
    plt.colorbar(scatter)
    plt.xlabel('Latent Dimension 1')
    plt.ylabel('Latent Dimension 2')
    plt.title('Autoencoder 2D Latent Space Visualization')
    plt.show()

8. Referencia rápida de API

8.1 Tipos comunes de autoencoders

Tipo Características Escenarios de aplicación
Autoencoder básico Estructura simétrica simple Reducción de dimensionalidad, extracción de características
Autoencoder convolucional Usa capas convolucionales para preservar la estructura espacial Procesamiento de imágenes
Autoencoder de denoising Aprende a eliminar el ruido Eliminación de ruido en imágenes, características robustas
Autoencoder variacional Aprende una distribución de probabilidad, puede generar nuevos datos Modelo generativo, generación de datos
Autoencoder disperso Restricción de dispersión, aprende características interpretables Desacoplamiento de características, interpretabilidad
Autoencoder secuencial Usa RNN/LSTM para procesar secuencias Texto, series temporales

8.2 Selección de la función de pérdida

Tarea Función de pérdida recomendada
Reconstrucción de imágenes MSELoss、SSIMLoss
Imágenes binarias BCELoss、BCEWithLogitsLoss
Reconstrucción de texto CrossEntropyLoss
VAE MSE + KL Divergence
Detección de anomalías MSE、MAE

8.3 Selección de la dimensión latente

数据维度低(<100维)
    -> 潜在维度设为 2~10

数据维度中等(100~1000维)
    -> 潜在维度设为 10~50

数据维度高(>1000维)
    -> 潜在维度设为 50~200

生成任务(VAE)
    -> 潜在维度 2~32(便于采样和可视化)

异常检测
    -> 潜在维度 16~64(保留足够信息检测异常)
Otras extensiones