Phase 01 - Lesson 10
Reducción de Dimensionalidad
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Los datos de alta dimensión tienen estructura. La encuentras mirando desde el ángulo correcto.
Tipo: Build Lenguaje: Python Requisitos previos: Fase 1, Lecciones 01 (Intuición de Álgebra Lineal), 02 (Vectores, Matrices y Operaciones), 03 (Valores y Vectores Propios), 06 (Probabilidad y Distribuciones) Tiempo: ~90 minutos
Objetivos de Aprendizaje
- Implementar PCA desde cero: centrar los datos, calcular la matriz de covarianza, hacer la descomposición en valores propios y proyectar
- Usar la razón de varianza explicada y el método del codo para elegir el número de componentes principales
- Comparar PCA, t-SNE y UMAP para visualizar dígitos de MNIST en 2D y explicar sus tradeoffs
- Aplicar kernel PCA con un kernel RBF para separar estructuras de datos no lineales que el PCA estándar no puede manejar
El Problema
Tienes un conjunto de datos con 784 features por muestra. Quizas sean valores de píxeles de dígitos escritos a mano. Quizas sean niveles de expresión génica. Quizas sean señales de comportamiento de usuario. No puedes visualizar 784 dimensiones. No puedes graficarlas. Ni siquiera puedes pensar en ellas.
Pero la mayoría de esas 784 features son redundantes. La información real vive en una superficie mucho más pequeña. Un "7" escrito a mano no necesita 784 números independientes para describirse. Necesita unos pocos: el ángulo del trazo, la longitud de la barra, cuanto se inclina. El resto es ruido.
La reducción de dimensionalidad encuentra esa superficie más pequeña. Toma tus datos de 784 dimensiones y los comprime a 2, 10 o 50 dimensiones manteniendo la estructura que importa.
El Concepto
La maldición de la dimensionalidad
Los espacios de alta dimensión son contraintuitivos. Tres cosas se rompen a medida que crecen las dimensiones.
La distancia pierde sentido. En altas dimensiones, la distancia entre dos puntos aleatorios cualesquiera converge al mismo valor. Si cada punto esta aproximadamente a la misma distancia de todos los demas, la búsqueda de vecinos más cercanos deja de funcionar.
Dimension Avg distance ratio (max/min between random points)
2 ~5.0
10 ~1.8
100 ~1.2
1000 ~1.02
El volumen se concentra en las esquinas. Un hipercubo unitario en d dimensiones tiene 2^d esquinas. En 100 dimensiones, casi todo el volumen está en las esquinas, lejos del centro. Los puntos de datos se dispersan hacia los bordes y tus modelos se quedan sin datos en el interior.
Necesitas exponencialmente más datos. Para mantener la misma densidad de muestras en un espacio, pasar de 2D a 20D significa que necesitas 10^18 veces más datos. Nunca tienes suficiente. Reducir dimensiones devuelve la densidad de los datos a algo manejable.
PCA: encuentra las direcciones que importan
El Análisis de Componentes Principales (PCA) encuentra los ejes a lo largo de los cuales tus datos varían más. Rota tu sistema de coordenadas para que el primer eje capture la mayor varianza, el segundo capture la siguiente mayor, y así sucesivamente.
El algoritmo:
1. Center the data (subtract the mean from each feature)
2. Compute covariance (how features move together)
3. Eigendecomposition (find the principal directions)
4. Sort by eigenvalue (biggest variance first)
5. Project (keep top k eigenvectors, drop the rest)
¿Por qué descomposición en valores propios? La matriz de covarianza es simétrica y positiva semidefinida. Sus vectores propios son direcciones ortogonales en el espacio de features. Los valores propios te dicen cuanta varianza captura cada dirección. El vector propio con el mayor valor propio apunta a lo largo de la dirección de varianza máxima.
graph LR
A["Original data (2D)\nData spread in both\nx and y directions"] -->|"PCA rotation"| B["After PCA\nPC1 captures the elongated spread\nPC2 captures the narrow spread\nDrop PC2 and you lose little info"]
- Antes de PCA: La nube de datos se dispersa diagonalmente por los ejes x e y
- Después de PCA: El sistema de coordenadas se rota para que PC1 se alinee con la dirección de varianza máxima (dispersión alargada) y PC2 se alinee con la dirección de varianza mínima (dispersión estrecha)
- Reducción de dimensionalidad: Descartar PC2 proyecta los datos sobre PC1, perdiendo muy poca información
Razón de varianza explicada
Cada componente principal captura una fracción de la varianza total. La razón de varianza explicada te dice cuanto.
Component Eigenvalue Explained ratio Cumulative
PC1 4.73 0.473 0.473
PC2 2.51 0.251 0.724
PC3 1.12 0.112 0.836
PC4 0.89 0.089 0.925
...
Cuando la varianza explicada acumulada alcanza 0.95, sabes que esa cantidad de componentes captura el 95% de la información. Todo lo posterior es mayormente ruido.
Eligiendo el número de componentes
Tres estrategias:
- Umbral. Conserva suficientes componentes para explicar el 90-95% de la varianza.
- Método del codo. Grafica la varianza explicada por componente. Busca una caida pronunciada.
- Rendimiento downstream. Usa PCA como preprocesamiento. Barre k y mide la exactitud de tu modelo. El mejor k es donde la exactitud se estabiliza.
t-SNE: preserva vecindarios
El t-Distributed Stochastic Neighbor Embedding (t-SNE) está diseñado para visualización. Mapea datos de alta dimensión a 2D (o 3D) preservando que puntos están cerca unos de otros.
La intuición: en el espacio original, calcula una distribución de probabilidad sobre pares de puntos basada en sus distancias. Los puntos cercanos reciben alta probabilidad. Los puntos lejanos reciben baja probabilidad. Luego encuentra una disposición 2D donde se mantiene la misma distribución de probabilidad. Los puntos que eran vecinos en 784 dimensiones siguen siendo vecinos en 2D.
Propiedades clave del t-SNE:
- No lineal. Puede desplegar variedades complejas que el PCA no puede.
- Estocástico. Distintas ejecuciones producen distintos layouts.
- El parámetro de perplejidad controla cuantos vecinos considerar (rango típico: 5-50).
- Las distancias entre clusters en la salida no tienen significado. Solo los clusters en si lo tienen.
- Lento en grandes conjuntos de datos. O(n^2) por defecto.
UMAP: más rápido, mejor estructura global
El Uniform Manifold Approximation and Projection (UMAP) funciona de forma similar al t-SNE pero con dos ventajas:
- Más rápido. Usa grafos aproximados de vecinos más cercanos en lugar de calcular todas las distancias por pares.
- Mejor estructura global. Las posiciones relativas de los clusters en la salida tienden a ser más significativas que en el t-SNE.
El UMAP construye un grafo ponderado en el espacio de alta dimensión (la "representación topológica difusa") y luego encuentra un layout de baja dimensión que preserva ese grafo lo mejor posible.
Parámetros clave:
n_neighbors: cuantos vecinos definen la estructura local (similar a la perplejidad). Valores más altos preservan más estructura global.min_dist: que tan apretados se agrupan los puntos en la salida. Valores más bajos crean clusters más densos.
Cuando usar cual
| Method | Use case | Preserves | Speed |
|---|---|---|---|
| PCA | Preprocesamiento antes del entrenamiento | Varianza global | Rápido (exacto), funciona en millones de muestras |
| PCA | Visualización exploratoria rápida | Estructura lineal | Rápido |
| t-SNE | Gráficos 2D de calidad de publicación | Vecindarios locales | Lento (< 10k muestras ideal) |
| UMAP | Visualización 2D a escala | Estructura local + algo global | Medio (maneja millones) |
| PCA | Reducción de features para modelos | Features clasificadas por varianza | Rápido |
| t-SNE / UMAP | Entender la estructura de clusters | Separación de clusters | Medio a lento |
Regla practica: usa PCA para preprocesamiento y compresión de datos. Usa t-SNE o UMAP cuando necesites visualizar estructura en 2D.
Kernel PCA
El PCA estándar encuentra subespacios lineales. Rota tu sistema de coordenadas y descarta ejes. Pero que pasa si los datos yacen en una variedad no lineal? Un circulo en 2D no puede separarse por ninguna línea. El PCA estándar no ayudará.
El kernel PCA aplica PCA en un espacio de features de alta dimensión inducido por una función de kernel, sin calcular explícitamente las coordenadas en ese espacio. Este es el truco del kernel -- la misma idea detrás de las SVMs.
El algoritmo:
- Calcula la matriz de kernel K donde K_ij = k(x_i, x_j)
- Centra la matriz de kernel en el espacio de features
- Haz la descomposición en valores propios de la matriz de kernel centrada
- Los vectores propios principales (escalados por 1/sqrt(valor propio)) son las proyecciones
Funciones de kernel comunes:
| Kernel | Fórmula | Good for |
|---|---|---|
| RBF (Gaussian) | exp(-gamma * ||x - y||^2) | La mayoría de datos no lineales, variedades suaves |
| Polynomial | (x . y + c)^d | Relaciones polinomiales |
| Sigmoid | tanh(alpha * x . y + c) | Mapeos del tipo red neuronal |
Cuando usar kernel PCA vs PCA estándar:
| Criterion | Standard PCA | Kernel PCA |
|---|---|---|
| Estructura de datos | Subespacio lineal | Variedad no lineal |
| Velocidad | O(min(n^2 d, d^2 n)) | O(n^2 d + n^3) |
| Interpretabilidad | Los componentes son combinaciones lineales de features | Los componentes carecen de interpretación directa de features |
| Escalabilidad | Funciona en millones de muestras | La matriz de kernel es n x n, limitada por memoria |
| Reconstrucción | Transformación inversa directa | Requiere aproximación de preimagen |
El ejemplo clásico: circulos concéntricos en 2D. Dos anillos de puntos, uno dentro del otro. El PCA estándar proyecta ambos sobre la misma línea -- inútil para clasificación. El kernel PCA con un kernel RBF mapea el circulo interno y el externo a regiones distintas, haciendolos linealmente separables.
Error de Reconstrucción
¿Qué tan buena es tu reducción de dimensionalidad? Comprimiste 784 dimensiones a 50. ¿Qué perdiste?
Mide el error de reconstrucción:
- Proyecta los datos a k dimensiones: X_reduced = X @ W_k
- Reconstruye: X_hat = X_reduced @ W_k^T
- Calcula el MSE: mean((X - X_hat)^2)
Para PCA, el error de reconstrucción tiene una relación limpia con la varianza explicada:
Reconstruction error = sum of eigenvalues NOT included
Total variance = sum of ALL eigenvalues
Fraction lost = (sum of dropped eigenvalues) / (sum of all eigenvalues)
La razón de varianza explicada para cada componente es:
explained_ratio_k = eigenvalue_k / sum(all eigenvalues)
Graficar la varianza explicada acumulada contra el número de componentes te da la curva del "codo". El número correcto de componentes es donde:
- La curva se aplana (rendimientos decrecientes)
- La varianza acumulada cruza tu umbral (generalmente 0.90 o 0.95)
- El rendimiento de la tarea downstream se estabiliza
El error de reconstrucción es útil más allá de elegir k. Puedes usarlo para detección de anomalías: las muestras con alto error de reconstrucción son outliers que no encajan en el subespacio aprendido. Esta es la base de la detección de anomalías basada en PCA en sistemas de producción.
Construye
Paso 1: PCA desde cero
import numpy as np
class PCA:
def __init__(self, n_components):
self.n_components = n_components
self.components = None
self.mean = None
self.eigenvalues = None
self.explained_variance_ratio_ = None
def fit(self, X):
self.mean = np.mean(X, axis=0)
X_centered = X - self.mean
cov_matrix = np.cov(X_centered, rowvar=False)
eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix)
sorted_idx = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[sorted_idx]
eigenvectors = eigenvectors[:, sorted_idx]
self.components = eigenvectors[:, :self.n_components].T
self.eigenvalues = eigenvalues[:self.n_components]
total_var = np.sum(eigenvalues)
self.explained_variance_ratio_ = self.eigenvalues / total_var
return self
def transform(self, X):
X_centered = X - self.mean
return X_centered @ self.components.T
def fit_transform(self, X):
self.fit(X)
return self.transform(X)
Paso 2: Prueba con datos sintéticos
np.random.seed(42)
n_samples = 500
t = np.random.uniform(0, 2 * np.pi, n_samples)
x1 = 3 * np.cos(t) + np.random.normal(0, 0.2, n_samples)
x2 = 3 * np.sin(t) + np.random.normal(0, 0.2, n_samples)
x3 = 0.5 * x1 + 0.3 * x2 + np.random.normal(0, 0.1, n_samples)
X_synthetic = np.column_stack([x1, x2, x3])
pca = PCA(n_components=2)
X_reduced = pca.fit_transform(X_synthetic)
print(f"Original shape: {X_synthetic.shape}")
print(f"Reduced shape: {X_reduced.shape}")
print(f"Explained variance ratios: {pca.explained_variance_ratio_}")
print(f"Total variance captured: {sum(pca.explained_variance_ratio_):.4f}")
Paso 3: Dígitos de MNIST en 2D
from sklearn.datasets import fetch_openml
mnist = fetch_openml("mnist_784", version=1, as_frame=False, parser="auto")
X_mnist = mnist.data[:5000].astype(float)
y_mnist = mnist.target[:5000].astype(int)
pca_mnist = PCA(n_components=50)
X_pca50 = pca_mnist.fit_transform(X_mnist)
print(f"50 components capture {sum(pca_mnist.explained_variance_ratio_):.2%} of variance")
pca_2d = PCA(n_components=2)
X_pca2d = pca_2d.fit_transform(X_mnist)
print(f"2 components capture {sum(pca_2d.explained_variance_ratio_):.2%} of variance")
Paso 4: Compara con sklearn
from sklearn.decomposition import PCA as SklearnPCA
from sklearn.manifold import TSNE
sklearn_pca = SklearnPCA(n_components=2)
X_sklearn_pca = sklearn_pca.fit_transform(X_mnist)
print(f"\nOur PCA explained variance: {pca_2d.explained_variance_ratio_}")
print(f"Sklearn PCA explained variance: {sklearn_pca.explained_variance_ratio_}")
diff = np.abs(np.abs(X_pca2d) - np.abs(X_sklearn_pca))
print(f"Max absolute difference: {diff.max():.10f}")
tsne = TSNE(n_components=2, perplexity=30, random_state=42)
X_tsne = tsne.fit_transform(X_mnist)
print(f"\nt-SNE output shape: {X_tsne.shape}")
Paso 5: Comparación con UMAP
try:
from umap import UMAP
reducer = UMAP(n_components=2, n_neighbors=15, min_dist=0.1, random_state=42)
X_umap = reducer.fit_transform(X_mnist)
print(f"UMAP output shape: {X_umap.shape}")
except ImportError:
print("Install umap-learn: pip install umap-learn")
Usalo
PCA como preprocesamiento antes de un clasificador:
from sklearn.decomposition import PCA as SklearnPCA
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
X_train, X_test, y_train, y_test = train_test_split(
X_mnist, y_mnist, test_size=0.2, random_state=42
)
results = {}
for k in [10, 30, 50, 100, 200]:
pca_k = SklearnPCA(n_components=k)
X_tr = pca_k.fit_transform(X_train)
X_te = pca_k.transform(X_test)
clf = LogisticRegression(max_iter=1000, random_state=42)
clf.fit(X_tr, y_train)
acc = accuracy_score(y_test, clf.predict(X_te))
var_captured = sum(pca_k.explained_variance_ratio_)
results[k] = (acc, var_captured)
print(f"k={k:>3d} accuracy={acc:.4f} variance={var_captured:.4f}")
El rendimiento se estabiliza mucho antes de las 784 dimensiones. Esa meseta es tu punto de operación.
Entregalo
Esta lección produce:
outputs/skill-dimensionality-reduction.md- una skill para elegir la técnica de reducción de dimensionalidad correcta para una tarea dada
Ejercicios
Modifica la clase PCA para soportar
inverse_transform. Reconstruye dígitos de MNIST a partir de 10, 50 y 200 componentes. Imprime el error de reconstrucción (diferencia cuadrática media respecto al original) para cada uno.Ejecuta t-SNE sobre el mismo subconjunto de MNIST con valores de perplejidad de 5, 30 y 100. Describe como cambia la salida. ¿Por qué la perplejidad afecta el apretamiento de los clusters?
Toma un conjunto de datos con 50 features donde solo 5 son informativas (genera uno con
sklearn.datasets.make_classification). Aplica PCA y verifica si la curva de varianza explicada identifica correctamente que los datos son efectivamente de 5 dimensiones.
Términos Clave
| Term | What people say | What it actually means |
|---|---|---|
| Curse of dimensionality | "Demasiadas features" | Las distancias, los volumenes y la densidad de datos se comportan de forma contraintuitiva a medida que crecen las dimensiones. Los modelos necesitan exponencialmente más datos para compensar. |
| PCA | "Reducir dimensiones" | Rota tu sistema de coordenadas para que los ejes se alineen con las direcciones de varianza máxima, luego descarta los ejes de baja varianza. |
| Principal component | "Una dirección importante" | Un vector propio de la matriz de covarianza. La dirección en el espacio de features a lo largo de la cual los datos varían más. |
| Explained variance ratio | "Cuanta info tiene este componente" | La fracción de la varianza total capturada por un componente principal. Suma las top k razones para ver cuanto preservan k componentes. |
| Covariance matrix | "Como se correlacionan las features" | Una matriz simétrica donde la entrada (i,j) mide como la feature i y la feature j se mueven juntas. Las entradas diagonales son las varianzas individuales. |
| t-SNE | "Ese gráfico de clusters" | Un método no lineal que mapea datos de alta dimensión a 2D preservando probabilidades de vecindario por pares. Bueno para visualización, no para preprocesamiento. |
| UMAP | "t-SNE más rápido" | Un método no lineal basado en análisis topológico de datos. Preserva tanto estructura local como algo de estructura global. Escala mejor que el t-SNE. |
| Perplexity | "Una perilla del t-SNE" | Controla el número efectivo de vecinos que cada punto considera. Baja perplejidad se enfoca en estructura muy local. Alta perplejidad capta patrones más amplios. |
| Manifold | "La superficie en la que viven los datos" | Una superficie de baja dimensión embebida en un espacio de alta dimensión. Una hoja de papel arrugada en 3D es una variedad 2D. |
Lectura Adicional
- A Tutorial on Principal Component Analysis (Shlens) - derivación clara del PCA desde la base
- How to Use t-SNE Effectively (Wattenberg et al.) - guia interactiva de las trampas del t-SNE y la elección de parámetros
- UMAP documentation - teoría y orientación practica de los autores de UMAP