71  t-SNE, UMAP y reducción de dimensionalidad no lineal

Autor/a

Diego Villalba

Fecha de publicación

16 de octubre de 2026

Los datos modernos suelen vivir en espacios de decenas, cientos o miles de dimensiones. Aunque matemáticamente bien definidos, estos espacios son difíciles de visualizar e interpretar. La reducción de dimensionalidad busca representar los datos en un espacio de baja dimensión —típicamente 2D o 3D— preservando la estructura relevante: distancias, vecindades, o topología local.

PCA (Análisis de Componentes Principales) es el método lineal clásico: encuentra la proyección ortogonal de menor dimensión que maximiza la varianza conservada (Jolliffe 2002). Sin embargo, PCA asume que la estructura relevante se puede capturar mediante combinaciones lineales de las variables originales. Muchas variedades de datos reales —como manifolds curvos en alta dimensión— requieren métodos no lineales.

t-SNE y UMAP son los dos métodos no lineales más usados en la práctica para visualización de datos de alta dimensión. Ambos intentan preservar la estructura local de vecindades, pero difieren en sus fundamentos teóricos, velocidad de cómputo y capacidad de preservar estructura global (Maaten y Hinton 2008; McInnes et al. 2018).

1 Limitaciones de PCA para visualización

PCA proyecta los datos sobre los \(d\) autovectores de la matriz de covarianza con mayores autovalores. Esta proyección es óptima para preservar la varianza, pero no necesariamente preserva la estructura de grupos o la topología local.

Mostrar código
import numpy as np
from sklearn.datasets import make_swiss_roll
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

X, color = make_swiss_roll(n_samples=1000, noise=0.05, random_state=42)

pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax1 = fig.add_subplot(121, projection="3d")
ax1.scatter(X[:, 0], X[:, 1], X[:, 2], c=color, cmap="Spectral", s=5, alpha=0.7)
ax1.set_title("Swiss Roll (3D original)")
ax1.set_xlabel("x"); ax1.set_ylabel("y"); ax1.set_zlabel("z")
ax2 = fig.add_subplot(122)
ax2.scatter(X_pca[:, 0], X_pca[:, 1], c=color, cmap="Spectral", s=5)
ax2.set_title("PCA (2D) — estructura doblada colapsada")
ax2.set_xlabel("PC1"); ax2.set_ylabel("PC2")
plt.tight_layout()
plt.show()

2 SNE y t-SNE

t-SNE fue propuesto por van der Maaten y Hinton en 2008 como mejora de Stochastic Neighbor Embedding (SNE) (Maaten y Hinton 2008). La idea central es modelar la estructura de vecindades en el espacio original mediante una distribución de probabilidad, construir una distribución análoga en el espacio de baja dimensión, y minimizar la divergencia entre ambas.

2.1 Probabilidades de similitud en el espacio original

Para cada par de puntos \((i,j)\), se define una probabilidad \(p_{j|i}\) que refleja qué tan probable es que el punto \(j\) sea vecino del punto \(i\) si los vecinos se eligieran proporcionalmente a una distribución gaussiana centrada en \(i\).

Definición. La similitud condicional asimétrica en el espacio original es: \[ p_{j|i} = \frac{\exp\left(-\|\mathbf{x}_i-\mathbf{x}_j\|^2/(2\sigma_i^2)\right)}{\sum_{k\neq i}\exp\left(-\|\mathbf{x}_i-\mathbf{x}_k\|^2/(2\sigma_i^2)\right)}, \tag{1}\] y la similitud simétrica es \(p_{ij} = (p_{j|i}+p_{i|j})/(2n)\).

El ancho \(\sigma_i\) se determina para que la entropía de \(p_{\cdot|i}\) sea \(\log_2(\text{Perp})\), donde Perp es la perplejidad, un hiperparámetro que controla el número efectivo de vecinos considerados.

2.2 Distribución en el espacio de baja dimensión

En el espacio de baja dimensión, t-SNE usa una distribución \(t\) de Student con un grado de libertad (distribución de Cauchy) en vez de una gaussiana. Esta elección resuelve el problema del crowding: en el espacio original los puntos moderadamente distantes pueden distribuirse en muchas direcciones, pero en 2D no hay suficiente “espacio” para acomodarlos todos, lo que comprime artificialmente los grupos.

Definición. La similitud entre los puntos en el espacio de baja dimensión \(\mathbf{y}_i, \mathbf{y}_j\) usando la distribución \(t\) de Student es: \[ q_{ij} = \frac{(1+\|\mathbf{y}_i-\mathbf{y}_j\|^2)^{-1}}{\sum_{k\neq l}(1+\|\mathbf{y}_k-\mathbf{y}_l\|^2)^{-1}}. \tag{2}\]

2.3 Función objetivo

t-SNE minimiza la divergencia KL entre la distribución en el espacio original y la distribución en el espacio reducido:

Definición. El objetivo de t-SNE es: \[ C = \text{KL}(P\|Q) = \sum_{i\neq j} p_{ij}\log\frac{p_{ij}}{q_{ij}}. \tag{3}\]

La minimización se realiza mediante descenso de gradiente estocástico, típicamente con aceleración por momentos y un término de exageración inicial que amplifica los \(p_{ij}\) en las primeras iteraciones para facilitar la formación de grupos.

2.4 Interpretación de la perplejidad

La perplejidad controla cuántos vecinos efectivos se consideran al construir las distribuciones de similitud. Valores bajos (5-10) priorizan la estructura local muy fina; valores altos (30-100) capturan estructura más global. En la práctica se recomienda probar Perp \(\in \{5, 10, 30, 50, 100\}\) y comparar visualmente.

t-SNE: efecto de la perplejidad y las iteraciones
Simula cómo la perplejidad afecta la distribución de similitudes y la separación entre grupos


Mostrar código
from sklearn.datasets import load_digits
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import numpy as np

digits = load_digits()
X = StandardScaler().fit_transform(digits.data)

tsne = TSNE(n_components=2, perplexity=30, n_iter=1000, random_state=42, learning_rate="auto", init="pca")
X_tsne = tsne.fit_transform(X)

plt.figure(figsize=(8, 6))
scatter = plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=digits.target,
                      cmap="tab10", s=8, alpha=0.8)
plt.colorbar(scatter, label="Dígito")
plt.title("t-SNE (perp=30) — dataset de dígitos MNIST")
plt.xlabel("t-SNE 1"); plt.ylabel("t-SNE 2")
plt.tight_layout()
plt.show()

3 UMAP

UMAP (Uniform Manifold Approximation and Projection) fue propuesto por McInnes et al. en 2018 (McInnes et al. 2018) con fundamentos teóricos basados en geometría diferencial y topología algebraica. A diferencia de t-SNE, UMAP tiene una mejor preservación de la estructura global y es significativamente más rápido, lo que lo hace aplicable a conjuntos de datos de millones de puntos.

3.1 Fundamentos teóricos

La base de UMAP es la teoría de conjuntos simpliciales difusos (fuzzy simplicial sets). Los datos se modelan como una variedad de Riemann, se construye una representación topológica local de esa variedad usando grafos ponderados de vecinos más cercanos, y luego se busca una representación de baja dimensión que preserve esa topología.

Definición. UMAP construye un grafo de vecindad ponderado donde la conectividad entre \(\mathbf{x}_i\) y \(\mathbf{x}_j\) se modela mediante \[ v_{j|i} = \exp\left(-\frac{d(\mathbf{x}_i,\mathbf{x}_j)-\rho_i}{\sigma_i}\right), \tag{4}\] donde \(\rho_i\) es la distancia al vecino más cercano de \(\mathbf{x}_i\) y \(\sigma_i\) se ajusta para que la suma de conectividades sea \(\log_2(k)\) (con \(k\) el número de vecinos).

La simetrización es \(v_{ij} = v_{j|i}+v_{i|j}-v_{j|i}\cdot v_{i|j}\).

En el espacio de baja dimensión, UMAP modela la similitud con una curva de familia parametrizable que se ajusta para aproximar una distribución \(t\) de Student. El objetivo es minimizar la divergencia cruzada binaria entre las similitudes en alta y baja dimensión.

3.2 Hiperparámetros principales

n_neighbors: número de vecinos en el grafo de alta dimensión. Similar a la perplejidad de t-SNE. Valores pequeños capturan estructura local fina; valores grandes capturan estructura global.

min_dist: distancia mínima entre puntos en el espacio reducido. Controla qué tan compactos son los grupos en la visualización. Valores pequeños producen grupos apretados; valores grandes los esparcen.

Mostrar código
try:
    import umap
    from sklearn.datasets import load_digits
    from sklearn.preprocessing import StandardScaler
    import matplotlib.pyplot as plt

    digits = load_digits()
    X = StandardScaler().fit_transform(digits.data)

    reducer = umap.UMAP(n_neighbors=15, min_dist=0.1, random_state=42)
    X_umap = reducer.fit_transform(X)

    plt.figure(figsize=(8, 6))
    scatter = plt.scatter(X_umap[:, 0], X_umap[:, 1], c=digits.target,
                          cmap="tab10", s=8, alpha=0.8)
    plt.colorbar(scatter, label="Dígito")
    plt.title("UMAP (n_neighbors=15, min_dist=0.1) — dataset de dígitos")
    plt.xlabel("UMAP 1"); plt.ylabel("UMAP 2")
    plt.tight_layout()
    plt.show()

except ImportError:
    print("Instalar umap-learn: pip install umap-learn")

4 Comparación t-SNE vs UMAP

Característica t-SNE UMAP
Fundamento Divergencia KL, distribución \(t\) Topología difusa, manifold learning
Velocidad \(O(n^2)\) ingenuo, \(O(n\log n)\) con BH \(O(n^{1.14})\) aproximado
Preservación global Limitada Mejor que t-SNE
Reproducibilidad Estocástico, varía entre corridas Más estable con random_state
Hiperparámetros Perplejidad, tasa aprendizaje n_neighbors, min_dist
Escalabilidad Hasta ~100k puntos Hasta millones de puntos
Uso en clustering Solo visualización Sí, como preprocessing
Mostrar código
from sklearn.datasets import make_blobs
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import numpy as np

X_high, y = make_blobs(n_samples=500, n_features=20, centers=5,
                       cluster_std=2.0, random_state=42)
X_std = StandardScaler().fit_transform(X_high)

tsne = TSNE(n_components=2, perplexity=30, random_state=42,
            learning_rate="auto", init="pca")
X_tsne = tsne.fit_transform(X_std)

fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap="tab10", s=10, alpha=0.8)
axes[0].set_title("t-SNE — datos en 20D")

try:
    import umap
    reducer = umap.UMAP(n_neighbors=15, min_dist=0.1, random_state=42)
    X_umap = reducer.fit_transform(X_std)
    axes[1].scatter(X_umap[:, 0], X_umap[:, 1], c=y, cmap="tab10", s=10, alpha=0.8)
    axes[1].set_title("UMAP — datos en 20D")
except ImportError:
    axes[1].text(0.5, 0.5, "Instalar umap-learn", ha="center", va="center")
    axes[1].set_title("UMAP (no instalado)")

plt.tight_layout()
plt.show()

5 Autoencoder básico para reducción de dimensionalidad

Un autoencoder es una red neuronal que aprende una representación comprimida de los datos. Consiste en un codificador que mapea la entrada a un espacio latente de dimensión reducida, y un decodificador que reconstruye la entrada desde ese espacio latente. El entrenamiento minimiza el error de reconstrucción.

Autoencoder — compresión y reconstrucción
Ajusta la dimensión del espacio latente y observa cómo cambia el error de reconstrucción
Mostrar código
import numpy as np
from sklearn.datasets import load_digits
from sklearn.preprocessing import MinMaxScaler

# Autoencoder con PyTorch (si disponible) o simulado con sklearn
try:
    import torch
    import torch.nn as nn

    digits = load_digits()
    X = MinMaxScaler().fit_transform(digits.data).astype(np.float32)
    X_tensor = torch.tensor(X)

    class Autoencoder(nn.Module):
        def __init__(self, latent_dim):
            super().__init__()
            self.encoder = nn.Sequential(
                nn.Linear(64, 32), nn.ReLU(),
                nn.Linear(32, latent_dim)
            )
            self.decoder = nn.Sequential(
                nn.Linear(latent_dim, 32), nn.ReLU(),
                nn.Linear(32, 64), nn.Sigmoid()
            )
        def forward(self, x):
            return self.decoder(self.encoder(x))

    ae = Autoencoder(latent_dim=2)
    optimizer = torch.optim.Adam(ae.parameters(), lr=1e-3)
    criterion = nn.MSELoss()

    for epoch in range(200):
        out = ae(X_tensor)
        loss = criterion(out, X_tensor)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    with torch.no_grad():
        Z = ae.encoder(X_tensor).numpy()

    print(f"Error de reconstrucción final (MSE): {loss.item():.6f}")
    print(f"Espacio latente: {Z.shape}")

except ImportError:
    print("PyTorch no disponible. Usando PCA como aproximación.")
    from sklearn.decomposition import PCA
    digits = load_digits()
    X = MinMaxScaler().fit_transform(digits.data)
    pca = PCA(n_components=2)
    Z = pca.fit_transform(X)
    rec = pca.inverse_transform(Z)
    mse = ((X - rec)**2).mean()
    print(f"PCA 2 componentes — MSE de reconstrucción: {mse:.6f}")
    print(f"Varianza explicada: {pca.explained_variance_ratio_.sum():.4f}")
Error de reconstrucción final (MSE): 0.063913
Espacio latente: (1797, 2)

6 Precauciones y buenas prácticas

Las técnicas de reducción no lineal son herramientas de visualización, no de inferencia estadística. Varias advertencias son importantes:

No interpretar distancias como absolutas. Las distancias en el espacio 2D de t-SNE o UMAP no son proporcionales a las distancias originales. Dos puntos cercanos en la visualización no necesariamente son similares en el espacio original.

Resultados estocásticos. t-SNE produce resultados distintos en cada corrida salvo que se fije la semilla aleatoria. UMAP es más estable pero sigue siendo estocástico.

La estructura global no se preserva en t-SNE. Los grupos lejanos en la visualización de t-SNE no necesariamente están más separados en el espacio original. UMAP preserva mejor la estructura global.

No usar como único criterio para clustering. Una apariencia de grupos en 2D no implica que existan grupos reales en el espacio original. Confirmar con métricas internas o externas.

Mostrar código
from sklearn.manifold import TSNE
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
import numpy as np

# Demostración: correr t-SNE múltiples veces con distintas semillas
X, y = make_blobs(n_samples=200, centers=3, cluster_std=0.8, random_state=0)
X_std = StandardScaler().fit_transform(X)

for seed in [0, 42, 123]:
    tsne = TSNE(n_components=2, perplexity=30, random_state=seed,
                learning_rate="auto", init="pca", n_iter=500)
    Z = tsne.fit_transform(X_std)
    spread = Z.std(axis=0).mean()
    print(f"Semilla {seed:3d} — dispersión media en 2D: {spread:.3f}")
Semilla   0 — dispersión media en 2D: 8.040
Semilla  42 — dispersión media en 2D: 8.040
Semilla 123 — dispersión media en 2D: 8.172

7 Cuándo usar cada método

Escenario Recomendación
Visualización rápida, <50k puntos t-SNE con perplejidad 30
Visualización de millones de puntos UMAP
Se necesita reproducibilidad UMAP con random_state
Reducción para clustering posterior UMAP (preserva mejor estructura global)
Reconstrucción aproximada Autoencoder
Interpretabilidad de los ejes PCA
Alta dimensión con manifold no lineal UMAP o t-SNE después de PCA inicial