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:

  1. Umbral. Conserva suficientes componentes para explicar el 90-95% de la varianza.
  2. Método del codo. Grafica la varianza explicada por componente. Busca una caida pronunciada.
  3. 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:

  1. Calcula la matriz de kernel K donde K_ij = k(x_i, x_j)
  2. Centra la matriz de kernel en el espacio de features
  3. Haz la descomposición en valores propios de la matriz de kernel centrada
  4. 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:

  1. Proyecta los datos a k dimensiones: X_reduced = X @ W_k
  2. Reconstruye: X_hat = X_reduced @ W_k^T
  3. 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

  1. 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.

  2. 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?

  3. 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

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).