Módulo / 5. No supervisado
0%
Módulo 5 · Core

Aprendizaje no supervisado

Sin etiquetas, el objetivo es descubrir estructura: grupos, direcciones de máxima varianza, patrones raros. Veremos cómo medir la calidad de un agrupamiento y cuándo un método no supervisado aporta valor real.

8 lecciones 16 h estimadas Simuladores K-Means y PCA
0 de 8 lecciones completadas
Lección 5.1

Panorama del aprendizaje no supervisado

16 minMapa conceptualSin código todavía

Sin etiquetas no hay una respuesta correcta que copiar. El aprendizaje no supervisado busca estructura: grupos, direcciones de varianza, densidades, factores latentes o ejemplos atípicos. Es más difícil de evaluar y, por eso, exige más criterio humano.

Al terminar sabrás

Distinguir clustering, reducción de dimensionalidad y detección de anomalías.
Elegir una medida de distancia adecuada al tipo de dato.
Reconocer cuándo un resultado no supervisado es útil y cuándo es solo un dibujo bonito.
Evitar conclusiones causales a partir de patrones de agrupamiento.

Tres familias de problemas

Clustering

Agrupar

Encontrar subconjuntos de ejemplos similares: clientes con hábitos parecidos, documentos del mismo tema, imágenes semejantes.

Dimensionalidad

Comprimir

Representar cada ejemplo con menos variables conservando la información relevante: PCA, t-SNE, autoencoders.

Anomalías

Detectar lo raro

Modelar lo normal y marcar desviaciones: fraude, fallos industriales, intrusiones, errores de sensores.

Estructura

Descubrir

Estimar densidades, factores latentes o reglas de asociación que expliquen la distribución de los datos.

La distancia define el resultado

Todo algoritmo de clustering necesita una noción de similitud. Cambiar la distancia cambia los grupos. Las más comunes son:

Distancias habituales $$d_2(\mathbf{x},\mathbf{y})=\sqrt{\sum_j (x_j-y_j)^2}, \qquad d_1(\mathbf{x},\mathbf{y})=\sum_j |x_j-y_j|, \qquad d_{\cos}(\mathbf{x},\mathbf{y})=1-\frac{\mathbf{x}\cdot\mathbf{y}}{\|\mathbf{x}\|\|\mathbf{y}\|}$$ Euclidiana para variables continuas escaladas; Manhattan es más robusta a outliers; coseno es estándar en texto y embeddings.
Escalar no es opcional Si una variable se mide en miles y otra entre 0 y 1, la distancia euclidiana estará dominada por la primera. Estandariza o normaliza antes de aplicar K-Means, DBSCAN, PCA o KNN.

Un ejemplo numérico mínimo

1

Distancia euclidiana entre dos clientes

Cliente A: edad 30, gasto mensual 200. Cliente B: edad 40, gasto 260. Sin escalar, la distancia es $\sqrt{10^2+60^2}\approx 60.8$, dominada por el gasto. Si estandarizamos con media 35 y desviación 10 para edad, y media 230 y desviación 50 para gasto, las diferencias pasan a ser 1 y 0.6, y la distancia es $\sqrt{1+0.36}\approx 1.17$.

Conclusión: la misma pareja de clientes parece muy lejana o bastante cercana según la escala. La distancia no es una propiedad absoluta de los datos: es una decisión de modelado.

Pythondistancias.py
import numpy as np
from sklearn.preprocessing import StandardScaler

X = np.array([[30, 200], [40, 260], [29, 190], [55, 420]], dtype=float)

def distancias(X, escalar=True):
    Z = StandardScaler().fit_transform(X) if escalar else X
    D = np.sqrt(((Z[:, None, :] - Z[None, :, :]) ** 2).sum(axis=2))
    return D

print("sin escalar:\n", np.round(distancias(X, False), 2))
print("estandarizado:\n", np.round(distancias(X, True), 2))

Cómo se evalúa lo no supervisado

No hay etiquetas, así que no hay accuracy. Se usan dos familias de criterios:

  • Internos: silhouette, Davies–Bouldin, inercia, varianza explicada. Miden cohesión y separación, pero favorecen ciertas formas.
  • Externos: estabilidad ante remuestreo, coherencia con conocimiento del dominio, utilidad en una tarea posterior (por ejemplo, segmentación que mejora una campaña).

Un agrupamiento es valioso si es estable, interpretable y accionable. Si cambia por completo al repetir con otra semilla, no es un descubrimiento: es ruido.

Cuidado con la interpretación Que un algoritmo agrupe clientes en tres clusters no significa que existan tres tipos naturales de clientes. Significa que, con esa distancia, ese algoritmo y esos parámetros, esa partición minimiza cierto criterio. La validación con negocio es imprescindible.
Ejercicio 5.1 · Diseñar un estudio de segmentación

Una tienda online quiere segmentar clientes usando: número de compras al año, gasto total, días desde la última compra y porcentaje de devoluciones. Propón un pipeline y responde: ¿qué distancia usarías?, ¿escalarías?, ¿cómo validarías que los segmentos son útiles?

Orientación: estandarizar las cuatro variables (o aplicar transformación logarítmica al gasto si está muy sesgado), usar K-Means con varios valores de k y comparar silhouette y estabilidad; validar con negocio comprobando que los segmentos difieren en tasa de recompra o ticket medio. No interpretar los clusters como tipos psicológicos.

Lección 5.2

K-Means: asignar y actualizar

22 minAlgoritmo + simuladorImplementación

K-Means es el algoritmo de clustering más usado y el más malinterpretado. Es rápido, simple y funciona bien cuando los grupos son aproximadamente esféricos y tienen tamaño similar. Fuera de ese régimen puede producir resultados engañosos.

El objetivo

Inercia o suma de cuadrados intra-cluster $$J=\sum_{k=1}^{K}\sum_{\mathbf{x}\in C_k}\|\mathbf{x}-\boldsymbol{\mu}_k\|^2, \qquad \boldsymbol{\mu}_k=\frac{1}{|C_k|}\sum_{\mathbf{x}\in C_k}\mathbf{x}$$ K-Means alterna dos pasos: asignar cada punto al centroide más cercano y recalcular cada centroide como la media de su cluster. Ambos pasos nunca aumentan $J$.

El algoritmo paso a paso

  1. Elegir $K$ y inicializar $K$ centroides.
  2. Asignación: cada punto va al centroide con menor distancia al cuadrado.
  3. Actualización: cada centroide se mueve a la media de los puntos asignados.
  4. Repetir hasta que las asignaciones no cambien o se alcance un máximo de iteraciones.

El resultado depende de la inicialización. Una mala elección puede dejar dos centroides en el mismo grupo y otro capturando dos grupos reales. K-Means++ elige centroides iniciales separados con probabilidad proporcional a la distancia al centroide más cercano, lo que mejora mucho la convergencia.

◍

Simulador · K-Means

Cambia k, avanza iteración a iteración y observa cómo se mueven los centroides.

inercia = —
iteración = 0
Los círculos grandes son centroides. Prueba k = 3 y k = 6: la inercia siempre baja al aumentar k, pero eso no significa mejor clustering.

Ejemplo numérico en una dimensión

1

Dos iteraciones con K = 2

Datos: $\{1,2,3,8,9,10\}$. Centroides iniciales: $\mu_1=1$, $\mu_2=10$.

Asignación: $C_1=\{1,2,3\}$ y $C_2=\{8,9,10\}$ porque 3 está más cerca de 1 que de 10.

Actualización: $\mu_1=2$ y $\mu_2=9$. En la siguiente asignación nada cambia; el algoritmo converge.

Inercia final: $(1-2)^2+(2-2)^2+(3-2)^2+(8-9)^2+(9-9)^2+(10-9)^2=4$. Si hubiéramos inicializado $\mu_1=1$, $\mu_2=2$, el resultado sería distinto: dos clusters desbalanceados. La inicialización importa.

Implementación desde cero y con Scikit-learn

Pythonkmeans.py
import numpy as np
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler

X, _ = make_blobs(n_samples=500, centers=4, cluster_std=1.2, random_state=42)
X = StandardScaler().fit_transform(X)

def kmeans_desde_cero(X, k, iters=30, seed=0):
    rng = np.random.default_rng(seed)
    centros = X[rng.choice(len(X), k, replace=False)]
    for _ in range(iters):
        d = ((X[:, None, :] - centros[None, :, :]) ** 2).sum(axis=2)
        etiquetas = d.argmin(axis=1)
        nuevos = np.array([X[etiquetas == j].mean(axis=0) if np.any(etiquetas == j) else centros[j]
                           for j in range(k)])
        if np.allclose(nuevos, centros):
            break
        centros = nuevos
    inercia = ((X - centros[etiquetas]) ** 2).sum()
    return etiquetas, centros, inercia

etiq, centros, inercia = kmeans_desde_cero(X, 4)
print("inercia desde cero:", round(inercia, 2))

modelo = KMeans(n_clusters=4, n_init=10, random_state=42).fit(X)
print("inercia sklearn:", round(modelo.inertia_, 2))
print("silhouette:", round(__import__("sklearn.metrics", fromlist=["silhouette_score"]).silhouette_score(X, modelo.labels_), 3))

Ventajas y limitaciones

Ventajas

  • Muy rápido: $O(nKd)$ por iteración.
  • Fácil de implementar e interpretar.
  • Funciona bien con clusters compactos y esféricos.
  • Escala a millones de puntos con variantes mini-batch.

Limitaciones

  • Hay que elegir $K$.
  • Asume clusters convexos y de varianza similar.
  • Sensible a outliers y a la escala.
  • Resultado depende de la inicialización.
  • No detecta ruido: todo punto pertenece a un cluster.
No uses la inercia para elegir k La inercia siempre disminuye al aumentar $K$ y llega a cero cuando $K=n$. Es un criterio de ajuste, no de complejidad. Para elegir $K$ usa silhouette, estabilidad o conocimiento del dominio.
Ejercicios 5.2

(a) Implementa K-Means con inicialización aleatoria y con K-Means++ sobre el mismo dataset. Compara la inercia en 20 semillas. (b) Genera dos clusters alargados y observa por qué K-Means los corta mal. (c) ¿Qué ocurre si un cluster queda vacío durante la actualización?

Orientación: K-Means++ suele dar menor varianza de inercia entre semillas. Con clusters alargados, la frontera perpendicular a la dirección de máxima separación no respeta la forma real. Si un cluster queda vacío, se puede reasignar su centroide al punto más lejano o mantenerlo; Scikit-learn lo maneja internamente.

Lección 5.3

Elegir el número de clusters

18 minCriteriosCódigo

No existe un $K$ «verdadero» esperando ser descubierto. Existe un $K$ útil para una decisión. Los criterios cuantitativos ayudan, pero la última palabra la tiene el uso que se dará a los grupos.

Método del codo

Se calcula la inercia para varios valores de $K$ y se busca el punto donde la curva deja de caer abruptamente. Es una heurística visual, no una prueba estadística, y a menudo el «codo» es ambiguo.

Coeficiente de silhouette

Silhouette por punto y promedio $$s(i)=\frac{b(i)-a(i)}{\max\{a(i),b(i)\}}, \qquad S=\frac{1}{n}\sum_{i=1}^{n}s(i)$$ $a(i)$: distancia media a los puntos de su propio cluster. $b(i)$: distancia media al cluster vecino más cercano. $s(i)\in[-1,1]$; cerca de 1 indica punto bien asignado.

Un silhouette global alto indica clusters compactos y separados. Pero también favorece clusters convexos: un agrupamiento por densidad con formas arbitrarias puede tener silhouette mediocre y ser correcto.

1

Interpretar silhouette

Para un punto, $a(i)=2$ y $b(i)=7$. Entonces $s(i)=(7-2)/7\approx0.71$: el punto está mucho más cerca de su cluster que del vecino. Si $a(i)=6$ y $b(i)=5$, $s(i)=(5-6)/6\approx-0.17$: probablemente está mal asignado.

Un promedio global de 0.55 es razonable; por debajo de 0.25 conviene dudar de la estructura. No obstante, estos umbrales dependen del dominio.

Otras herramientas

  • Davies–Bouldin: menor es mejor; compara dispersión intra-cluster con separación entre centroides.
  • Calinski–Harabasz: mayor es mejor; cociente entre dispersión entre clusters y dentro de ellos.
  • Gap statistic: compara la inercia observada con la esperada bajo datos sin estructura.
  • Estabilidad: repite el clustering sobre submuestras y mide cuánto se conservan las asignaciones (por ejemplo, con adjusted Rand index).
Pythonelegir_k.py
import numpy as np
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, davies_bouldin_score, calinski_harabasz_score

for k in range(2, 9):
    modelo = KMeans(n_clusters=k, n_init=20, random_state=42).fit(X)
    sil = silhouette_score(X, modelo.labels_)
    db = davies_bouldin_score(X, modelo.labels_)
    ch = calinski_harabasz_score(X, modelo.labels_)
    print(f"k={k}: inercia={modelo.inertia_:.1f} silhouette={sil:.3f} DB={db:.3f} CH={ch:.1f}")
Regla práctica Prueba un rango de $K$, elimina los que tengan clusters de tamaño ridículo o no interpretables, y entre los restantes elige el que tenga mejor silhouette y estabilidad. Si dos soluciones son parecidas, prefiere la más simple.

Cuando K-Means no es la herramienta

Si los grupos tienen forma de media luna, densidades muy distintas o hay ruido, K-Means fuerza una partición incorrecta. En esos casos conviene DBSCAN, clustering jerárquico o modelos de mezcla gaussianos.

Ejercicio 5.3 · Elegir k con evidencia

Genera datos con `make_blobs` para 4 centros y calcula silhouette para $K=2,\dots,8$. ¿Coincide el máximo con 4? Repite con `cluster_std=3.0`. Explica qué cambia.

Orientación: con clusters bien separados, el máximo suele estar en 4. Con dispersión alta, los clusters se solapan y el criterio puede preferir menos grupos; la estructura real existe, pero ya no es separable de forma clara.

Lección 5.4

Clustering jerárquico y DBSCAN

22 minDos familiasComparación

K-Means impone una forma. El clustering jerárquico construye una jerarquía completa de grupos y DBSCAN descubre regiones densas de forma arbitraria. Ambos amplían el repertorio cuando los supuestos de K-Means no se cumplen.

Clustering aglomerativo

Se empieza con cada punto como un cluster y se fusionan iterativamente los dos más cercanos hasta formar uno solo. El resultado se visualiza como un dendrograma: cortar a cierta altura produce una partición.

Criterios de enlace $$d_{\text{single}}(A,B)=\min_{a\in A,b\in B}d(a,b), \quad d_{\text{complete}}=\max_{a,b}d(a,b), \quad d_{\text{average}}=\frac{1}{|A||B|}\sum_{a,b}d(a,b)$$ Single linkage puede encadenar puntos a través de puentes; complete produce clusters compactos; Ward minimiza el aumento de varianza intra-cluster y es el más usado con distancia euclidiana.

Su coste es mayor que K-Means ($O(n^2\log n)$ o $O(n^3)$ según implementación), pero para datasets medianos ofrece una visión global muy informativa.

DBSCAN: densidad

DBSCAN define un punto como núcleo si tiene al menos min_samples vecinos dentro de un radio eps. Los puntos núcleo conectados forman un cluster; los puntos alcanzables desde un núcleo se incorporan; el resto es ruido.

Ventaja clave DBSCAN no necesita elegir el número de clusters, detecta formas no convexas y etiqueta ruido. Su debilidad es que eps y min_samples son sensibles a la escala y a la densidad variable.
CriterioK-MeansJerárquicoDBSCAN
Número de clustersSe fija antesSe decide al cortarSe descubre
Forma de clustersEsférica/convexaDepende del enlaceArbitraria
RuidoNo lo modelaNo lo modelaLo etiqueta
EscalabilidadAltaMedia/bajaMedia (índice espacial)
InterpretabilidadCentroidesDendrogramaRegiones densas
Pythonjerarquico_dbscan.py
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import AgglomerativeClustering, DBSCAN
from scipy.cluster.hierarchy import dendrogram, linkage

X, _ = make_moons(n_samples=400, noise=0.06, random_state=42)
X = StandardScaler().fit_transform(X)

# Jerárquico con enlace Ward
jer = AgglomerativeClustering(n_clusters=2, linkage="ward").fit(X)
print("jerárquico:", np.bincount(jer.labels_))

# Dendrograma (sobre una submuestra para legibilidad)
Z = linkage(X[:120], method="ward")
plt.figure(figsize=(10, 4))
dendrogram(Z, no_labels=True)
plt.title("Dendrograma (Ward)")
plt.show()

# DBSCAN
db = DBSCAN(eps=0.25, min_samples=8).fit(X)
print("DBSCAN etiquetas:", np.unique(db.labels_, return_counts=True))

Elegir eps en DBSCAN

Una heurística útil es el gráfico de k-distancias: para cada punto se calcula la distancia a su $k$-ésimo vecino, se ordenan y se busca el codo. Ese valor sugiere un eps razonable. También se puede usar el silhouette ignorando los puntos de ruido, aunque no es una medida perfecta.

Densidad variable Si los clusters tienen densidades muy distintas, un único eps puede fusionar los densos y fragmentar los dispersos. HDBSCAN resuelve parcialmente este problema construyendo una jerarquía de densidades.
Ejercicio 5.4 · Comparar métodos

Con el dataset de dos lunas, compara K-Means, aglomerativo Ward y DBSCAN. Dibuja las etiquetas. ¿Cuál respeta mejor la forma? ¿Qué ocurre con K-Means y por qué?

Orientación: K-Means corta las lunas con una frontera aproximadamente lineal porque minimiza distancia a centroides. Ward suele mejorar algo, pero DBSCAN respeta la conectividad densa y asigna ruido a los puntos aislados.

Lección 5.5

PCA: reducir dimensión conservando varianza

24 minÁlgebra linealSimulador

PCA encuentra las direcciones ortogonales en las que los datos varían más. Es una técnica lineal, no supervisada y muy útil para visualizar, comprimir y eliminar redundancia antes de modelar.

La idea geométrica

Centramos los datos y buscamos una dirección unitaria $\mathbf{u}$ que maximice la varianza de las proyecciones $\mathbf{u}^\top\mathbf{x}_i$. La solución es el autovector principal de la matriz de covarianza.

Problema de autovalores $$\Sigma=\frac{1}{n-1}\sum_{i=1}^{n}(\mathbf{x}_i-\bar{\mathbf{x}})(\mathbf{x}_i-\bar{\mathbf{x}})^\top, \qquad \Sigma\mathbf{u}_j=\lambda_j\mathbf{u}_j$$ Los autovalores $\lambda_1\ge\lambda_2\ge\dots$ indican la varianza explicada por cada componente. Los autovectores son ortogonales.
↗

Simulador · PCA en 2D

Gira el eje de proyección y observa qué porcentaje de la varianza total captura.

varianza proyectada = —
varianza total = —
La varianza proyectada es máxima cuando el eje coincide con el componente principal. Gira el eje y compruébalo.

Ejemplo numérico

1

Dos variables correlacionadas

Supón que la matriz de covarianza es $\Sigma=\begin{pmatrix}4&3\\3&4\end{pmatrix}$. Sus autovalores son $7$ y $1$, con autovectores $(1,1)/\sqrt2$ y $(1,-1)/\sqrt2$.

El primer componente explica $7/(7+1)=87.5\%$ de la varianza. Proyectar sobre él reduce de dos dimensiones a una conservando casi toda la información. La segunda dirección, aunque ortogonal, solo aporta el 12.5%.

Varianza explicada y elección de componentes

Proporción acumulada $$r_k=\frac{\sum_{j=1}^{k}\lambda_j}{\sum_{j=1}^{d}\lambda_j}$$ Se suele elegir $k$ para alcanzar 90–95 % de varianza, o mirar el codo del scree plot. En visualización, $k=2$ o $3$ por razones obvias.
Pythonpca.py
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris

X, y = load_iris(return_X_y=True)
X_std = StandardScaler().fit_transform(X)  # PCA es sensible a la escala

pca = PCA().fit(X_std)
print("varianza explicada:", np.round(pca.explained_variance_ratio_, 3))
print("acumulada:", np.round(np.cumsum(pca.explained_variance_ratio_), 3))

Z = PCA(n_components=2).fit_transform(X_std)
plt.scatter(Z[:, 0], Z[:, 1], c=y, cmap="viridis", s=18)
plt.xlabel("PC1"); plt.ylabel("PC2")
plt.title("Iris proyectado en dos componentes")
plt.show()

Qué hace y qué no hace PCA

Hace

  • Elimina correlación entre componentes.
  • Reduce ruido de baja varianza si se descartan componentes menores.
  • Facilita visualización y compresión.
  • Puede acelerar modelos posteriores.

No hace

  • No selecciona variables originales: cada componente es una mezcla.
  • No garantiza mejorar la precisión predictiva.
  • No captura estructuras no lineales.
  • No es invariante a la escala: estandariza si las unidades difieren.
Fuga de información Ajusta PCA solo con el conjunto de entrenamiento dentro de la validación cruzada. Si calculas los componentes con todos los datos y luego divides, el test influye en la transformación y la estimación se vuelve optimista.
Ejercicio 5.5 · Varianza y reconstrucción

Con el dataset Iris estandarizado, ¿cuántos componentes necesitas para superar el 95% de varianza? Reconstruye los datos con esa cantidad y calcula el error cuadrático medio de reconstrucción. Compáralo con usar solo dos componentes.

Orientación: Iris suele necesitar 2–3 componentes para superar el 95%. El error de reconstrucción disminuye al añadir componentes; con dos se conserva la mayor parte de la estructura, pero se pierde parte de la información de las variables menos representadas.

Lección 5.6

t-SNE y UMAP: visualizar estructura

16 minNo linealPrecauciones

PCA es lineal. t-SNE y UMAP capturan estructura local no lineal y producen visualizaciones sorprendentes. También son las técnicas más malinterpretadas del curso: están diseñadas para mirar, no para medir distancias globales.

t-SNE en una frase

t-SNE convierte distancias entre puntos en probabilidades de vecindad en el espacio original y busca una configuración 2D/3D cuyas probabilidades de vecindad se parezcan lo más posible, minimizando la divergencia KL.

Divergencia KL entre distribuciones de vecindad $$C=\sum_{i}\sum_{j}p_{j|i}\log\frac{p_{j|i}}{q_{j|i}}$$ $p$ se construye con una gaussiana cuya anchura depende de la perplejidad; $q$ usa una distribución t de cola pesada en el espacio 2D.

La perplejidad controla el número efectivo de vecinos. Valores típicos: 5–50. Perplejidad baja enfatiza estructura local; alta, estructura más global, pero no garantiza distancias globales correctas.

Qué no concluir de un t-SNE

  • El tamaño de los grupos no significa nada: t-SNE expande grupos densos y comprime otros.
  • Las distancias entre grupos no son interpretables: dos clusters lejanos pueden ser similares en el espacio original.
  • La forma y orientación son arbitrarias: cambian con la semilla.
  • No es una reducción para alimentar un modelo: su objetivo es visual; para eso usa PCA o un autoencoder.

UMAP

UMAP construye un grafo de vecinos y lo proyecta preservando tanto estructura local como algo de global. Suele ser más rápido y más estable que t-SNE, y puede transformar puntos nuevos si se entrena con esa capacidad. Aun así, sigue siendo una visualización: no demuestres conclusiones solo con el mapa.

Pythontsne_umap.py
from sklearn.datasets import load_digits
from sklearn.preprocessing import StandardScaler
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt

X, y = load_digits(return_X_y=True)
X = StandardScaler().fit_transform(X)

Z = TSNE(n_components=2, perplexity=30, init="pca",
         learning_rate="auto", random_state=42).fit_transform(X)

plt.figure(figsize=(7, 6))
plt.scatter(Z[:, 0], Z[:, 1], c=y, cmap="tab10", s=12)
plt.title("t-SNE de dígitos (perplejidad 30)")
plt.axis("off")
plt.show()

# UMAP requiere: pip install umap-learn
# import umap
# Z2 = umap.UMAP(n_neighbors=15, min_dist=0.1, random_state=42).fit_transform(X)
Protocolo de visualización Prueba al menos tres semillas y dos perplejidades. Si la estructura que te interesa aparece y desaparece, probablemente sea un artefacto. Complementa siempre con métricas cuantitativas y con la interpretación de las variables originales.
Ejercicio 5.6 · t-SNE no es un mapa

Ejecuta t-SNE sobre dígitos con perplejidad 5, 30 y 100. Describe qué estructura se conserva y qué cambia. Luego selecciona dos puntos que parezcan lejanos y calcula su distancia euclidiana original. ¿Se corresponde con la distancia visual?

Orientación: con perplejidad baja se fragmentan grupos; con alta se mezclan vecindades. Las distancias visuales no son proporcionales a las originales: t-SNE preserva vecindad local, no geometría global.

Lección 5.7

Detección de anomalías

20 minMétodosCódigo

Una anomalía es un ejemplo que se desvía del patrón esperado. El reto es que «normal» es difícil de definir y las anomalías son raras por construcción: hay pocos ejemplos para aprender.

Enfoques según las etiquetas disponibles

SituaciónEnfoqueEjemplo
Solo datos normalesModelar la normalidad y medir desviaciónAutoencoder, Gaussian model, Isolation Forest
Datos normales y algunas anomalías etiquetadasClasificación con clases muy desbalanceadasFraude con histórico etiquetado
Sin etiquetasDetección no supervisada y revisión humanaIntrusiones, fallos de sensores

Métodos estadísticos

El z-score marca valores alejados de la media en unidades de desviación estándar. Es simple, pero asume una distribución aproximadamente normal y es sensible a outliers que contaminan la media.

z-score y rango intercuartílico $$z_i=\frac{x_i-\bar{x}}{s}, \qquad \text{IQR}=Q_3-Q_1, \qquad \text{atípico si } xQ_3+1.5\,\text{IQR}$$ El criterio IQR es más robusto que la media y la desviación, pero solo mira cada variable por separado.

Isolation Forest

Construye árboles que aíslan puntos mediante cortes aleatorios. Las anomalías, al estar en regiones poco densas, se aíslan con menos cortes. El score se basa en la profundidad media del punto en los árboles.

Local Outlier Factor

Compara la densidad local de un punto con la de sus vecinos. Un punto es anómalo si su densidad es mucho menor que la de su entorno, incluso si su distancia absoluta no es grande. Funciona bien con densidades heterogéneas, pero no está pensado para predecir puntos nuevos sin reajuste.

Pythonanomalias.py
import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.neighbors import LocalOutlierFactor
from sklearn.preprocessing import StandardScaler

# X: matriz de features; se asume que las anomalías son raras
X_std = StandardScaler().fit_transform(X)

iso = IsolationForest(contamination=0.02, random_state=42)
etiquetas_iso = iso.fit_predict(X_std)       # -1 = anomalía
scores_iso = iso.decision_function(X_std)    # menor = más anómalo

lof = LocalOutlierFactor(n_neighbors=20, contamination=0.02)
etiquetas_lof = lof.fit_predict(X_std)

print("Isolation Forest:", np.bincount(etiquetas_iso == -1))
print("LOF:", np.bincount(etiquetas_lof == -1))
print("Top 5 scores:", np.argsort(scores_iso)[:5])
El parámetro contamination no es magia contamination fija la proporción esperada de anomalías. Si no la conoces, no la inventes: usa el score continuo, ordena los casos y revisa manualmente los primeros. La etiqueta binaria es una decisión operativa, no una verdad del modelo.

Evaluar detección de anomalías

La exactitud es inútil con clases extremadamente desbalanceadas. Usa precisión en el top-k, recall a una tasa de alertas fija, curva precision–recall y coste de negocio (falso positivo vs. falso negativo). Además, valida con datos de un periodo distinto para detectar drift.

Ejercicio 5.7 · Anomalías en sensores

Tienes lecturas horarias de temperatura y presión de una máquina. Propón un detector no supervisado y un protocolo de validación. ¿Cómo elegirías el umbral de alerta?

Orientación: estandarizar, ajustar Isolation Forest o un autoencoder con datos de funcionamiento normal, ordenar por score y elegir el umbral según la capacidad del equipo de mantenimiento para revisar alertas. Validar con incidentes históricos y medir precisión en el top-k, no exactitud global.

Lección 5.8

Autoencoders y representaciones

20 minDeep learningCierre del módulo

Un autoencoder aprende a comprimir una entrada en un espacio latente y reconstruirla. Si solo se entrena con datos normales, aprende a reconstruir lo normal; cuando llega algo distinto, el error de reconstrucción sube. Esa es la base de su uso para anomalías y representaciones.

Arquitectura

Encoder, decoder y pérdida $$\mathbf{z}=f_\theta(\mathbf{x}), \qquad \hat{\mathbf{x}}=g_\phi(\mathbf{z}), \qquad L(\theta,\phi)=\frac{1}{n}\sum_{i=1}^{n}\|\mathbf{x}_i-\hat{\mathbf{x}}_i\|^2$$ El cuello de botella $\mathbf{z}$ tiene menos dimensiones que $\mathbf{x}$ (o está regularizado), lo que fuerza a conservar solo la información relevante.

Tipos y usos

  • Autoencoder bajo el cuello de botella: compresión para reducción de dimensionalidad o denoising.
  • Denoising autoencoder: se corrompe la entrada y se pide reconstruir la original; aprende representaciones robustas.
  • Variational autoencoder (VAE): modela una distribución latente y permite generar muestras nuevas.
  • Autoencoder para anomalías: se entrena solo con datos normales y se usa el error de reconstrucción como score.
Pythonautoencoder.py
import torch
import torch.nn as nn

class Autoencoder(nn.Module):
    def __init__(self, n_features, latent=8):
        super().__init__()
        self.encoder = nn.Sequential(
            nn.Linear(n_features, 32), nn.ReLU(),
            nn.Linear(32, latent)
        )
        self.decoder = nn.Sequential(
            nn.Linear(latent, 32), nn.ReLU(),
            nn.Linear(32, n_features)
        )

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

modelo = Autoencoder(n_features=X.shape[1], latent=8)
opt = torch.optim.Adam(modelo.parameters(), lr=1e-3)
loss_fn = nn.MSELoss()

for epoch in range(100):
    opt.zero_grad()
    reconstruccion, _ = modelo(X_train_tensor)
    loss = loss_fn(reconstruccion, X_train_tensor)
    loss.backward()
    opt.step()

# Score de anomalía = error de reconstrucción por ejemplo
with torch.no_grad():
    reconstruccion, _ = modelo(X_test_tensor)
    score = ((X_test_tensor - reconstruccion) ** 2).mean(dim=1)
Relación con PCA Un autoencoder lineal con activación identidad y pérdida cuadrática aprende un subespacio equivalente al de PCA. La diferencia es que las capas no lineales permiten representar variedades curvas que PCA no captura.

Riesgos prácticos

  • Si entrenas con datos que ya contienen anomalías, el modelo aprenderá a reconstruirlas y perderá sensibilidad.
  • El error de reconstrucción no es una probabilidad: hay que calibrar el umbral con validación.
  • Con datos de alta dimensión y pocos ejemplos, el autoencoder puede memorizar; usa regularización y aumenta datos.
Ejercicio 5.8 · Cierre del módulo

Diseña un pipeline completo para segmentar clientes y detectar comportamiento atípico: (1) limpieza y escalado, (2) clustering, (3) reducción de dimensión para visualizar, (4) detección de anomalías, (5) validación con negocio. Indica qué decisión tomarías en cada paso y qué métrica usarías.

Orientación: estandarizar, comparar K-Means con DBSCAN y elegir por estabilidad e interpretabilidad; usar PCA para visualizar y autoencoder o Isolation Forest para anomalías; validar los segmentos con diferencias reales en retención o ticket, y las anomalías con revisión manual y coste operativo.

Curso de Machine Learning · teoría, matemáticas y práctica en Python Progreso local · 0% completado · Hecho para estudiar en cualquier lugar