Módulo / 4. Aprendizaje supervisado
0%
Módulo 4 · Core

Aprendizaje supervisado

El corazón del ML aplicado: predecir un valor continuo o una clase a partir de ejemplos etiquetados. Estudiaremos cada familia de algoritmos con su intuición, su formulación, su implementación y sus limitaciones reales.

11 lecciones 26 h estimadas 2 simuladores
0 de 11 lecciones completadas
Lección 4.1

Panorama y criterios de elección

14 minMapa mentalSin código todavía

No existe un algoritmo universal. Existen supuestos, sesgos inductivos y compromisos. Elegir bien requiere responder cuatro preguntas: ¿qué tipo de salida?, ¿cuántos datos?, ¿qué relación esperas?, ¿cuánto necesitas interpretar?

Árbol de decisión para elegir

Si tu situación es…Empieza con…Alternativa fuerte
Salida continua, relación aproximadamente linealRegresión lineal / RidgeGradient boosting
Clasificación binaria, necesitas probabilidadesRegresión logísticaRandom Forest, XGBoost
Datos tabulares medianos, rendimiento máximoGradient boostingRandom Forest
Datos tabulares pequeños, interpretabilidadÁrbol de decisiónRegresión logística
Fronteras no lineales, datos densos y escaladosSVM RBFKNN, redes
Texto con muchas features dispersasNaive Bayes / linealSVM lineal, transformers
Pocos datos, distancia significativaKNNSVM
Imágenes, audio, texto sin features manualesDeep LearningTransfer learning

Los compromisos que nunca desaparecen

Sesgo–varianza

Flexibilidad vs. estabilidad

Modelos flexibles capturan patrones complejos pero son sensibles al ruido. Modelos rígidos son estables pero pueden no capturar la señal.

Interpretabilidad

Explicar vs. predecir

Una regresión logística se explica con coeficientes; un ensemble de 500 árboles requiere SHAP u otras técnicas post-hoc.

Cómputo

Entrenamiento vs. inferencia

KNN no entrena pero predice lento; un modelo lineal entrena rápido y predice rápido; los transformers son costosos en ambos.

Datos

Cantidad vs. calidad

Un modelo simple con datos limpios supera a uno complejo con datos sesgados o etiquetas ruidosas.

La línea base siempre primero

Antes de probar XGBoost, mide una baseline: la media para regresión, la clase mayoritaria para clasificación, o una regresión logística simple. Sin baseline no sabes si tu modelo aporta valor. En muchos problemas industriales, la diferencia entre un modelo simple y uno complejo es menor de lo que se supone.

Estrategia recomendada Empieza simple y añade complejidad solo cuando el análisis de errores lo justifique. Documenta cada experimento. Un modelo que no puedes mantener ni explicar es un pasivo, no un activo.

Notación común

$$\hat{y}=f(\mathbf{x};\theta), \qquad L(\theta)=\frac1n\sum_{i=1}^{n}\ell\left(y_i,\hat{y}_i\right)+\lambda R(\theta)$$ Todos los algoritmos de este módulo minimizan una pérdida $\ell$ con alguna forma de regularización $R$, aunque el procedimiento de optimización cambie.
Ejercicio 4.1 · Elegir modelo

Justifica una elección inicial para: (a) predecir el precio de un departamento con 500 filas y 12 variables; (b) detectar spam con 5 000 correos; (c) clasificar dígitos manuscritos con 60 000 imágenes; (d) predecir cancelación con 50 000 clientes y 200 variables.

Orientación: (a) Ridge o gradient boosting con validación; (b) regresión logística o Naive Bayes con TF-IDF; (c) CNN o SVM RBF; (d) gradient boosting, con regresión logística como baseline interpretable.

Lección 4.2

Regresión lineal simple

24 minTeoría + derivaciónSimulador

La regresión lineal modela una salida continua como una combinación lineal de las entradas. Es el modelo más simple, el más interpretable y la base conceptual de casi todo lo demás.

Modelo

Regresión lineal simple $$\hat{y}=w x+b, \qquad y=wx+b+\varepsilon$$ $w$: pendiente (cuánto cambia $y$ por unidad de $x$); $b$: intercepto (valor esperado cuando $x=0$); $\varepsilon$: error irreducible.

Cómo se ajusta: mínimos cuadrados ordinarios

Buscamos $w,b$ que minimicen la suma de errores al cuadrado:

$$L(w,b)=\sum_{i=1}^{n}\left(y_i-wx_i-b\right)^2$$ Derivando respecto a $w$ y $b$ e igualando a cero se obtienen las ecuaciones normales.
Solución cerrada $$\hat{w}=\frac{\sum_i(x_i-\bar{x})(y_i-\bar{y})}{\sum_i(x_i-\bar{x})^2}=\frac{\operatorname{Cov}(x,y)}{\operatorname{Var}(x)}, \qquad \hat{b}=\bar{y}-\hat{w}\bar{x}$$
1 Ajuste paso a paso

Datos: $x=(1,2,3,4)$, $y=(2,4,5,4)$.

1
$\bar{x}=2.5$, $\bar{y}=3.75$.
2
Desviaciones de $x$: $-1.5,-0.5,0.5,1.5$. De $y$: $-1.75,0.25,1.25,0.25$.
3
Productos: $2.625,-0.125,0.625,0.375$; suma $=3.5$.
4
Cuadrados de $x$: $2.25+0.25+0.25+2.25=5$.
5
$\hat{w}=3.5/5=0.7$; $\hat{b}=3.75-0.7(2.5)=2.0$.
6
Modelo: $\hat{y}=0.7x+2$. Predicciones: $2.7,3.4,4.1,4.8$. Residuos: $-0.7,0.6,0.9,-0.8$.
7
$SSE=2.3$; $SST=4.75$; $R^2=1-2.3/4.75=0.516$. El modelo explica el 51.6% de la variabilidad.

Coeficiente de determinación $R^2$

$$R^2=1-\frac{SSE}{SST}=1-\frac{\sum_i(y_i-\hat{y}_i)^2}{\sum_i(y_i-\bar{y})^2}$$ $R^2=1$: ajuste perfecto; $R^2=0$: no mejor que predecir la media; puede ser negativo en test si el modelo generaliza muy mal.

Supuestos del modelo

  1. Linealidad: la relación esperada es lineal en los parámetros.
  2. Independencia: las observaciones son independientes (no series temporales correlacionadas).
  3. Homocedasticidad: la varianza del error es constante.
  4. Normalidad de los residuos: necesaria para inferencia exacta, no para predecir.
  5. No multicolinealidad: en el caso múltiple, las features no deben ser casi redundantes.

Si los supuestos fallan, la predicción puntual puede seguir siendo útil, pero los intervalos de confianza y los contrastes de significancia dejan de ser válidos.

↗

Simulador · Regresión lineal

Arrastra los puntos o haz doble clic para añadir. Observa cómo cambia la recta de mínimos cuadrados.

ŷ = —
R² = —
n = —
Doble clic en el área para añadir un punto. Arrastra un punto para moverlo. Un outlier desplaza la recta.

Implementación desde cero y con Scikit-learn

Pythonregresion_lineal.py
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

x = np.array([1., 2., 3., 4.])
y = np.array([2., 4., 5., 4.])

# Desde cero (ecuaciones normales)
x_mean, y_mean = x.mean(), y.mean()
w = ((x - x_mean) * (y - y_mean)).sum() / ((x - x_mean) ** 2).sum()
b = y_mean - w * x_mean
print(f"desde cero: y = {w:.3f}x + {b:.3f}")

# Con Scikit-learn
X = x.reshape(-1, 1)
modelo = LinearRegression().fit(X, y)
print(f"sklearn:    y = {modelo.coef_[0]:.3f}x + {modelo.intercept_:.3f}")

y_pred = modelo.predict(X)
print("R²:", r2_score(y, y_pred))
print("RMSE:", mean_squared_error(y, y_pred) ** 0.5)

plt.scatter(x, y, color="#3355c8", zorder=3)
plt.plot(x, y_pred, color="#b42318")
for xi, yi, yp in zip(x, y, y_pred):
    plt.plot([xi, xi], [yi, yp], color="gray", linestyle=":", lw=.8)
plt.xlabel("x"); plt.ylabel("y"); plt.title("Mínimos cuadrados")
plt.grid(alpha=.3); plt.show()

Ventajas y limitaciones

Ventajas

  • Rápida, interpretable y estable.
  • Base para modelos regularizados.
  • Funciona bien con relaciones aproximadamente lineales.

Limitaciones

  • No captura curvas ni interacciones sin ingeniería de features.
  • Sensible a outliers.
  • Extrapola de forma peligrosa fuera del rango observado.
Correlación no es causalidad Un coeficiente positivo indica asociación condicional, no que aumentar $x$ cause un aumento de $y$. Para inferencia causal se necesitan diseño experimental o supuestos causales explícitos.
Ejercicios 4.2
  1. Con $\bar{x}=10$, $\bar{y}=50$, $\hat{w}=2.5$, calcula $\hat{b}$: $50-2.5(10)=25$.
  2. Si $R^2=0.8$ y $SST=100$, ¿cuánto vale $SSE$? $SSE=20$.
  3. Añade un outlier a los datos del ejemplo y recalcula mentalmente la dirección del cambio de la pendiente. Si el outlier está arriba a la derecha, la pendiente aumenta.
  4. ¿Por qué no se debe extrapolar una regresión? Porque el supuesto lineal puede dejar de valer fuera del rango observado.
Lección 4.3

Regresión múltiple y polinómica

20 minÁlgebra + featuresMulticolinealidad

Regresión lineal múltiple

$$\hat{y}=w_1x_1+w_2x_2+\cdots+w_dx_d+b=\mathbf{w}^\top\mathbf{x}+b$$ Cada $w_j$ es el efecto esperado de $x_j$ manteniendo las demás variables constantes.

Con $X$ aumentada con una columna de unos, la solución de mínimos cuadrados es $\hat{\mathbf{w}}=(X^\top X)^{-1}X^\top\mathbf{y}$. En la práctica se resuelve con QR o SVD para evitar problemas numéricos.

Multicolinealidad

Si dos features están muy correlacionadas, $X^\top X$ se vuelve casi singular: los coeficientes se vuelven inestables y difíciles de interpretar. El modelo puede predecir bien, pero la atribución de efectos es frágil.

  • Detectar: matriz de correlación, VIF (Variance Inflation Factor). VIF > 10 sugiere colinealidad seria.
  • Resolver: eliminar una de las variables, combinar features, usar regularización (Ridge), o PCA.
VIF $$\text{VIF}_j=\frac{1}{1-R_j^2}$$ $R_j^2$ proviene de regresar $x_j$ sobre las demás features. VIF=1: sin correlación; VIF=5: moderada; VIF>10: alta.

Regresión polinómica

La regresión sigue siendo lineal en los parámetros, aunque las features sean potencias:

$$\hat{y}=w_0+w_1x+w_2x^2+\cdots+w_px^p$$ Ajustar un polinomio es una regresión lineal sobre las features $(x,x^2,\dots,x^p)$. El modelo es lineal en $w$, no en $x$.

Al aumentar el grado, la capacidad crece y aparece el overfitting. Es útil para tendencias suaves, pero inestable en los extremos (fenómeno de Runge) y muy sensible a la escala. Casi siempre es mejor usar splines, gradient boosting o modelos no paramétricos.

Importante «Lineal» en regresión lineal se refiere a la linealidad en los coeficientes, no a una recta. Un polinomio, una interacción $x_1x_2$ o una transformación $\log x$ siguen siendo modelos lineales si se escriben como combinación lineal de parámetros.
Pythonregresion_multiple.py
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import r2_score
from sklearn.datasets import fetch_california_housing

# --- Regresión múltiple ---
datos = fetch_california_housing(as_frame=True)
X, y = datos.data, datos.target
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=.2, random_state=42)

modelo = LinearRegression().fit(X_tr, y_tr)
print("R² test:", modelo.score(X_te, y_te))
for nombre, coef in zip(X.columns, modelo.coef_):
    print(f"{nombre:>12}: {coef: .4f}")

# --- Multicolinealidad: VIF ---
from statsmodels.stats.outliers_influence import variance_inflation_factor
X_num = X_tr.select_dtypes("number").copy()
vif = pd.DataFrame({
    "feature": X_num.columns,
    "VIF": [variance_inflation_factor(X_num.values, i)
            for i in range(X_num.shape[1])],
})
print(vif.sort_values("VIF", ascending=False))

# --- Polinómica: comparar grados ---
x = np.linspace(-3, 3, 40)
y_poly = 0.5 * x**3 - x**2 + 2 + np.random.default_rng(0).normal(0, 1.2, 40)

for grado in [1, 2, 3, 6, 12]:
    pipe = make_pipeline(PolynomialFeatures(grado, include_bias=False), LinearRegression())
    scores = cross_val_score(pipe, x.reshape(-1, 1), y_poly, cv=5, scoring="r2")
    print(f"grado {grado:2d}: R² CV = {scores.mean(): .3f}")

Ingeniería de features

  • Interacciones: $x_1x_2$ captura efectos conjuntos (por ejemplo, superficie × ubicación).
  • Transformaciones: $\log$, raíz, inversa para reducir asimetría.
  • Variables de calendario: día de la semana, mes, festivos.
  • Agregaciones: medias por grupo, frecuencias, lags temporales.
  • Ratios: ingreso/deuda, clics/impresiones.

La ingeniería de features suele aportar más que cambiar de algoritmo. Pero cada feature nueva debe validarse: puede introducir fuga, ruido o sobreajuste.

Ejercicios 4.3
  1. Si dos features tienen correlación 0.98, ¿qué esperas del VIF? Muy alto, probablemente >20; los coeficientes serán inestables.
  2. ¿Un modelo con $x$ y $x^2$ es lineal? Sí, lineal en los parámetros; no lineal en $x$.
  3. ¿Qué grado elegirías con 30 puntos ruidosos? Bajo (1–3), validado con CV; grados altos sobreajustan.
  4. Añade una interacción a un modelo y comprueba si mejora la validación cruzada. Si no mejora, no la incluyas.
Lección 4.4

Regresión logística

26 minClasificaciónDesde cero

A pesar del nombre, la regresión logística es un modelo de clasificación. Estima la probabilidad de pertenecer a una clase mediante una transformación logística de una combinación lineal de features.

De la recta a la probabilidad

Una regresión lineal puede producir valores fuera de $[0,1]$, inválidos como probabilidad. La función sigmoide los comprime:

Modelo logístico $$p=P(y=1\mid\mathbf{x})=\sigma(\mathbf{w}^\top\mathbf{x}+b)=\frac{1}{1+e^{-(\mathbf{w}^\top\mathbf{x}+b)}}$$ $\sigma(z)\in(0,1)$, monótona creciente. La frontera de decisión es $\mathbf{w}^\top\mathbf{x}+b=0$.

El logit o log-odds es la inversa:

$$\log\frac{p}{1-p}=\mathbf{w}^\top\mathbf{x}+b$$ Cada coeficiente $w_j$ indica cuánto cambian los log-odds de la clase positiva al aumentar $x_j$ en una unidad.

Función de pérdida: entropía cruzada

$$L(\mathbf{w},b)=-\frac1n\sum_{i=1}^{n}\left[y_i\log p_i+(1-y_i)\log(1-p_i)\right]$$ Es la log-verosimilitud negativa. Convexа en $\mathbf{w}$, por lo que el descenso del gradiente converge al óptimo global.
Gradiente $$\nabla_{\mathbf{w}}L=\frac1n\sum_{i=1}^{n}(p_i-y_i)\mathbf{x}_i=\frac1n X^\top(\mathbf{p}-\mathbf{y}), \qquad \frac{\partial L}{\partial b}=\frac1n\sum_i(p_i-y_i)$$

La forma del gradiente es idéntica a la de la regresión lineal: error por feature. Cambia la función que produce $\hat{y}$.

2 Un paso de entrenamiento

Un ejemplo con $x=2$, $y=1$, pesos iniciales $w=1$, $b=-3$, $\eta=0.1$.

1
$z=1(2)-3=-1$; $p=\sigma(-1)=0.269$.
2
Pérdida: $-\log(0.269)=1.313$. El modelo da poca probabilidad a la clase correcta.
3
Gradiente respecto a $w$: $(p-y)x=(0.269-1)\cdot2=-1.462$.
4
Actualización: $w\leftarrow1-0.1(-1.462)=1.146$; $b\leftarrow-3-0.1(-0.731)=-2.927$.
5
Nuevo $z=1.146(2)-2.927=-0.635$; $p=0.347$; pérdida $=1.059$. Mejoró.

Umbral de decisión

Por defecto se predice clase 1 si $p\geq0.5$. Pero el umbral óptimo depende del costo de los errores. En diagnóstico, quizá prefieras umbral 0.3 para no perder enfermos (mayor recall) a costa de más falsos positivos. En spam, quizá 0.7 para no bloquear correos legítimos.

Regularización y desbalance

  • L2 (por defecto): estabiliza los coeficientes, útil con muchas features correlacionadas.
  • L1: selecciona features; útil en texto.
  • class_weight="balanced": repondera las clases inversamente a su frecuencia.
  • Umbral: ajustarlo con la curva precision-recall, no con accuracy.
Pythonlogistica.py
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, roc_auc_score
from sklearn.datasets import load_breast_cancer

# --- Desde cero: un paso de gradiente ---
def sigmoide(z):
    return 1 / (1 + np.exp(-z))

x, y, w, b, lr = 2.0, 1.0, 1.0, -3.0, 0.1
p = sigmoide(w * x + b)
loss = -(y * np.log(p) + (1 - y) * np.log(1 - p))
grad_w = (p - y) * x
grad_b = (p - y)
w, b = w - lr * grad_w, b - lr * grad_b
print(f"p={p:.3f} loss={loss:.3f} -> w={w:.3f} b={b:.3f}")

# --- Con Scikit-learn ---
datos = load_breast_cancer(as_frame=True)
X, y = datos.data, datos.target
X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=.2, random_state=42, stratify=y
)

modelo = make_pipeline(
    StandardScaler(),
    LogisticRegression(C=1.0, max_iter=5000, class_weight="balanced")
).fit(X_tr, y_tr)

y_pred = modelo.predict(X_te)
y_prob = modelo.predict_proba(X_te)[:, 1]
print(classification_report(y_te, y_pred, target_names=datos.target_names))
print("AUC:", roc_auc_score(y_te, y_prob))

# Coeficientes en escala estandarizada
coefs = modelo[-1].coef_[0]
for nombre, c in sorted(zip(X.columns, coefs), key=lambda t: abs(t[1]), reverse=True)[:5]:
    print(f"{nombre:>20}: {c: .3f}")

Ventajas y limitaciones

Ventajas

  • Probabilidades calibradas y coeficientes interpretables.
  • Rápida, convexa y robusta con muchas features.
  • Excelente baseline en clasificación.

Limitaciones

  • Frontera lineal: no captura interacciones sin features explícitas.
  • Sensible a outliers y a la escala (regularización).
  • Puede fallar con relaciones muy no lineales.
Odds ratios $e^{w_j}$ es el factor por el que se multiplican los odds al aumentar $x_j$ en una unidad. Si $w_j=0.693$, $e^{0.693}=2$: los odds se duplican. Es una forma muy útil de comunicar el modelo a personas no técnicas.
Ejercicios 4.4
  1. Con $w=(2,-1)$, $b=0.5$, $x=(1,3)$: $z=2-3+0.5=-0.5$; $p=\sigma(-0.5)\approx0.378$.
  2. ¿Qué umbral usarías para detectar fraude si un falso negativo cuesta 20 veces más que un falso positivo? Bajo el umbral (por ejemplo 0.1–0.2) para aumentar recall.
  3. Calcula la pérdida para $y=0$, $p=0.9$: $-\log(0.1)=2.303$. Es alta: el modelo está muy equivocado.
  4. ¿Por qué estandarizar antes de regularizar? Porque la penalización trata todos los coeficientes igual; sin escala, las features con mayor magnitud son penalizadas de forma distinta.
Lección 4.5

Ridge, Lasso y Elastic Net

18 minRegularizaciónSelección de features

La regularización añade una penalización a la pérdida para limitar la magnitud de los coeficientes. Es la herramienta más eficaz contra el overfitting en modelos lineales y una excelente primera opción en datos tabulares.

Las tres variantes

Objetivos $$L_{\text{ridge}}=\|y-Xw\|_2^2+\lambda\|w\|_2^2$$ $$L_{\text{lasso}}=\|y-Xw\|_2^2+\lambda\|w\|_1$$ $$L_{\text{EN}}=\|y-Xw\|_2^2+\lambda\left(\alpha\|w\|_1+(1-\alpha)\|w\|_2^2\right)$$ $\lambda\geq0$ controla la fuerza; $\alpha\in[0,1]$ reparte entre L1 y L2 en Elastic Net.
ModeloEfectoVentajaLimitación
Ridge (L2)Encoge coeficientes, no los anulaEstable con features correlacionadasNo selecciona variables
Lasso (L1)Anula coeficientesModelos dispersos e interpretablesInestable con features correlacionadas
Elastic NetCombina ambosLo mejor de los dosDos hiperparámetros que ajustar

Por qué Lasso anula coeficientes

Geométricamente, la restricción L1 tiene forma de rombo con vértices sobre los ejes. La elipse de la pérdida suele tocar el rombo en un vértice, donde una o más coordenadas son exactamente cero. La restricción L2 es un círculo: el punto de contacto rara vez cae en un eje, así que los coeficientes se encogen pero no desaparecen.

Solución cerrada de Ridge $$\hat{w}_{\text{ridge}}=(X^\top X+\lambda I)^{-1}X^\top y$$ La suma de $\lambda I$ estabiliza la inversión cuando $X^\top X$ es casi singular. Lasso no tiene solución cerrada general; se resuelve con coordenadas o proximal.
Escala obligatoria La penalización depende de la magnitud de los coeficientes. Si una feature se mide en millones y otra en fracciones, la regularización afecta de forma desigual. Estandariza siempre antes de Ridge/Lasso (y no penalices el intercepto).
Pythonridge_lasso.py
import numpy as np
from sklearn.linear_model import RidgeCV, LassoCV, ElasticNetCV
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_california_housing
from sklearn.metrics import mean_squared_error

datos = fetch_california_housing(as_frame=True)
X, y = datos.data, datos.target
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=.2, random_state=42)

alphas = np.logspace(-3, 3, 30)

ridge = make_pipeline(StandardScaler(), RidgeCV(alphas=alphas, cv=5))
lasso = make_pipeline(StandardScaler(), LassoCV(alphas=alphas, cv=5, max_iter=20_000))
enet  = make_pipeline(StandardScaler(), ElasticNetCV(
    alphas=alphas, l1_ratio=[.2, .5, .8, 1.0], cv=5, max_iter=20_000))

for nombre, modelo in [("ridge", ridge), ("lasso", lasso), ("elastic", enet)]:
    modelo.fit(X_tr, y_tr)
    pred = modelo.predict(X_te)
    rmse = mean_squared_error(y_te, pred) ** .5
    print(f"{nombre:>8}: RMSE={rmse:.3f}  alpha={modelo[-1].alpha_:.4f}")

coefs = lasso[-1].coef_
print("coeficientes cero:", (coefs == 0).sum(), "de", len(coefs))
print(dict(zip(X.columns, coefs.round(3))))

Interpretación y uso

  • Ridge cuando todas las features aportan y están correlacionadas: estabiliza sin descartar.
  • Lasso cuando sospechas que muchas features son irrelevantes y quieres un modelo compacto.
  • Elastic Net cuando hay grupos de features correlacionadas: Lasso tiende a elegir una al azar, Elastic Net las conserva juntas.
Sobre la interpretación Un coeficiente grande no implica causalidad ni importancia absoluta: depende de la escala y de las correlaciones. Para importancia, usa validación o métodos como permutation importance y SHAP con cautela.
Ejercicios 4.5
  1. ¿Qué ocurre con Ridge cuando $\lambda\to\infty$? Los pesos tienden a cero; el modelo predice la media.
  2. ¿Qué modelo elegirías con 200 features y sospecha de que solo 20 importan? Lasso o Elastic Net.
  3. ¿Por qué no se penaliza el intercepto? Porque desplazar la predicción no aumenta la complejidad; penalizarlo sesgaría la media.
  4. Dos features idénticas: ¿qué hace Lasso? Tiende a quedarse con una y anular la otra, de forma inestable entre ejecuciones.
Lección 4.6

K-Nearest Neighbors (KNN)

18 minBasado en instanciasDistancias

KNN no construye una función explícita: guarda los datos y, para predecir, busca los $k$ ejemplos más cercanos. Es el algoritmo más intuitivo de ML y un excelente banco de pruebas para entender distancia, escala y sesgo–varianza.

Algoritmo

  1. Estandariza las features.
  2. Calcula la distancia entre el nuevo punto y todos los de entrenamiento.
  3. Selecciona los $k$ más cercanos.
  4. Clasificación: voto mayoritario (o promedio ponderado por distancia).
  5. Regresión: promedio de los valores de los vecinos.
Predicción $$\hat{y}(\mathbf{x})=\frac{1}{k}\sum_{i\in\mathcal{N}_k(\mathbf{x})}y_i \quad\text{(regresión)}, \qquad \hat{p}_c=\frac{1}{k}\sum_{i\in\mathcal{N}_k(\mathbf{x})}\mathbb{1}[y_i=c] \quad\text{(clasificación)}$$ $\mathcal{N}_k(\mathbf{x})$: índices de los $k$ vecinos más cercanos según la métrica elegida.

El papel de $k$ y de la distancia

$k$SesgoVarianzaFrontera
1BajoAltaIrregular, pasa por cada punto
5–15MedioMediaSuave pero flexible
$n$AltoBajaConstante (clase mayoritaria)

La distancia euclidiana es la opción por defecto, pero en texto se usa coseno y en datos con escalas muy distintas, Manhattan. La elección de métrica es un hiperparámetro más.

Maldición de la dimensionalidad En dimensiones altas, las distancias entre puntos tienden a igualarse: «cerca» y «lejos» pierden significado. KNN sufre especialmente. Si tienes cientos de features, reduce dimensionalidad o usa otro modelo.
Pythonknn.py
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
from sklearn.metrics import pairwise_distances

# --- KNN desde cero (versión simple) ---
def knn_predict(X_train, y_train, x, k=3):
    distancias = np.linalg.norm(X_train - x, axis=1)
    idx = np.argsort(distancias)[:k]
    valores, conteos = np.unique(y_train[idx], return_counts=True)
    return valores[np.argmax(conteos)]

# --- Con Scikit-learn ---
iris = load_iris()
X, y = iris.data, iris.target

for k in [1, 3, 5, 9, 15, 25]:
    modelo = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=k))
    scores = cross_val_score(modelo, X, y, cv=5, scoring="accuracy")
    print(f"k={k:2d}: {scores.mean():.3f} ± {scores.std():.3f}")

# Matriz de distancias
D = pairwise_distances(X[:5], X[:5])
print(D.round(2))

Ventajas y limitaciones

Ventajas

  • Sin entrenamiento: el costo está en predecir.
  • Naturalmente multclase y no lineal.
  • Fácil de explicar: «se parece a estos vecinos».

Limitaciones

  • Predicción lenta con millones de ejemplos.
  • Sensible a escala, ruido y dimensionalidad.
  • No extrapola: en regresión, nunca predice fuera del rango de los vecinos.
Uso moderno KNN aparece en sistemas de recomendación, búsqueda por similitud (con embeddings) y como baseline. Para vectores densos de alta dimensión se usan índices aproximados (FAISS, HNSW) en lugar de fuerza bruta.
Ejercicios 4.6
  1. Con $k=1$, el error de entrenamiento es 0. ¿Por qué no es deseable? Porque memoriza el ruido y generaliza peor.
  2. ¿Por qué estandarizar? Para que ninguna feature domine la distancia por su escala.
  3. Diseña un caso donde KNN falle: datos de alta dimensión con muchas features irrelevantes; la distancia se vuelve uniforme.
  4. ¿Cómo ponderarías por distancia? Peso $1/(d+\epsilon)$; los vecinos más cercanos influyen más.
Lección 4.7

Árboles de decisión

24 minReglas + impurezaSimulador

Un árbol de decisión divide el espacio de features con preguntas sucesivas del tipo «¿$x_j\leq t$?». Cada hoja asigna una predicción. Son muy interpretables y la base de los ensembles más potentes en datos tabulares.

Estructura

  • Nodo raíz: primera división, la más informativa.
  • Nodos internos: preguntas sobre una feature y un umbral.
  • Ramas: resultados de la pregunta (verdadero/falso).
  • Hojas: predicción (clase mayoritaria o media).
  • Profundidad: número de divisiones desde la raíz hasta la hoja.

Cómo elige una división: impureza

Gini y entropía $$\text{Gini}(S)=1-\sum_{c=1}^{K}p_c^2, \qquad H(S)=-\sum_{c=1}^{K}p_c\log_2 p_c$$ $p_c$: proporción de la clase $c$ en el nodo. Gini=0 y entropía=0 indican nodo puro.
Ganancia de información $$\Delta I=I(S)-\frac{|S_L|}{|S|}I(S_L)-\frac{|S_R|}{|S|}I(S_R)$$ Se elige la división que maximiza la reducción de impureza. En regresión, se minimiza el MSE de los hijos.
3 Gini de un nodo

Un nodo tiene 6 ejemplos: 4 de la clase A y 2 de la clase B.

1
$p_A=4/6=0.667$, $p_B=2/6=0.333$.
2
$\text{Gini}=1-(0.667^2+0.333^2)=1-(0.444+0.111)=0.444$.
3
Entropía: $H=-(0.667\log_2 0.667+0.333\log_2 0.333)\approx0.918$ bits.
4
Si una división produce hijos (4A,0B) y (0A,2B), ambos puros: ganancia máxima. Si produce (2A,2B) y (2A,0B), la ganancia es menor.

Árboles de regresión

En regresión, la impureza es la varianza (o MSE) dentro del nodo. La predicción de una hoja es la media de los $y$ de entrenamiento que caen en ella. La división se elige para minimizar la suma de MSE de los hijos.

Poda y control de complejidad

  • max_depth: limita la profundidad.
  • min_samples_split / min_samples_leaf: evita divisiones con pocos datos.
  • ccp_alpha: poda por complejidad de costo (post-poda).
  • max_features: limita las features evaluadas por división.

Sin límites, un árbol crece hasta que cada hoja es pura: error de train cero, varianza altísima. La poda es esencial.

⑂

Simulador · Árbol de decisión

Cambia la profundidad y observa cómo se divide el espacio en regiones de decisión.

accuracy train = —
hojas = —
Con profundidad 1 la frontera es una sola recta. Aumenta la profundidad para capturar formas complejas y observa el sobreajuste.
Pythonarboles.py
from sklearn.tree import DecisionTreeClassifier, export_text, plot_tree
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt

iris = load_iris(as_frame=True)
X, y = iris.data, iris.target
X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=.2, random_state=42, stratify=y)

for depth in [1, 2, 3, 5, None]:
    arbol = DecisionTreeClassifier(max_depth=depth, random_state=42)
    arbol.fit(X_tr, y_tr)
    train = arbol.score(X_tr, y_tr)
    test = arbol.score(X_te, y_te)
    print(f"depth={str(depth):>4}: train={train:.3f} test={test:.3f} hojas={arbol.get_n_leaves()}")

arbol = DecisionTreeClassifier(max_depth=3, random_state=42).fit(X_tr, y_tr)
print(export_text(arbol, feature_names=list(X.columns)))

plt.figure(figsize=(12, 6))
plot_tree(arbol, feature_names=X.columns, class_names=iris.target_names,
          filled=True, rounded=True, fontsize=8)
plt.show()

# Importancia de features
for n, imp in sorted(zip(X.columns, arbol.feature_importances_), key=lambda t: -t[1]):
    print(f"{n:>14}: {imp:.3f}")

Ventajas y limitaciones

Ventajas

  • Muy interpretables: se pueden leer como reglas.
  • No requieren escalado ni supuestos distribucionales.
  • Capturan no linealidades e interacciones.

Limitaciones

  • Alta varianza: pequeños cambios en datos cambian el árbol.
  • Sesgados hacia features con muchos valores únicos.
  • Fronteras axis-aligned, poco eficientes en diagonales.
De árbol a bosque La alta varianza del árbol se combate promediando muchos árboles entrenados con perturbaciones distintas. Eso es exactamente Random Forest, la siguiente lección.
Ejercicios 4.7
  1. Nodo con 3 A y 1 B: Gini $=1-(0.75^2+0.25^2)=0.375$.
  2. ¿Qué división es mejor: una que separa perfectamente 2 y 2, o una que deja 3/1 y 1/3? La primera; produce hijos puros.
  3. ¿Por qué un árbol sin límite tiene error de train 0? Porque puede crear una hoja por ejemplo.
  4. Dibuja a mano un árbol de profundidad 2 para el problema XOR y observa que necesita al menos dos divisiones.
Lección 4.8

Random Forest y bagging

20 minEnsemblesVarianza

Un bosque aleatorio es un conjunto de árboles entrenados sobre muestras bootstrap y subconjuntos aleatorios de features. Promediar árboles descorrelacionados reduce la varianza sin aumentar mucho el sesgo.

Bagging (bootstrap aggregating)

  1. Genera $B$ muestras bootstrap del train (con reemplazo, mismo tamaño).
  2. Entrena un árbol en cada muestra.
  3. Promedia las predicciones (regresión) o vota (clasificación).
Reducción de varianza $$\operatorname{Var}\left(\frac1B\sum_{b=1}^{B}\hat{f}_b\right)=\rho\sigma^2+\frac{1-\rho}{B}\sigma^2$$ $\rho$: correlación media entre árboles; $\sigma^2$: varianza individual. Más árboles reduce el segundo término, pero no el primero: hay que descorrelacionar.

Random Forest añade aleatoriedad en las features: en cada división solo considera un subconjunto aleatorio (por defecto $\sqrt{d}$ en clasificación y $d/3$ en regresión). Esto reduce $\rho$ y mejora el ensemble.

Out-of-bag (OOB)

Cada árbol deja fuera ≈37% de los ejemplos (los que no aparecen en su bootstrap). Esos ejemplos actúan como validación gratuita. El error OOB es una estimación honesta del rendimiento y evita reservar un conjunto adicional.

$$P(\text{no seleccionado})=\left(1-\frac1n\right)^n\approx e^{-1}\approx0.368$$
Pythonrandom_forest.py
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.datasets import load_breast_cancer
from sklearn.metrics import classification_report, roc_auc_score
import pandas as pd

datos = load_breast_cancer(as_frame=True)
X, y = datos.data, datos.target
X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=.2, random_state=42, stratify=y)

rf = RandomForestClassifier(
    n_estimators=500,
    max_features="sqrt",
    min_samples_leaf=2,
    oob_score=True,
    n_jobs=-1,
    random_state=42,
).fit(X_tr, y_tr)

print("OOB score:", round(rf.oob_score_, 3))
print("Test AUC:", round(roc_auc_score(y_te, rf.predict_proba(X_te)[:, 1]), 3))
print(classification_report(y_te, rf.predict(X_te)))

# Importancia por impureza (con cautela)
imp = pd.Series(rf.feature_importances_, index=X.columns).sort_values(ascending=False)
print(imp.head(10))

# Búsqueda de hiperparámetros
parametros = {
    "n_estimators": [300, 600],
    "max_depth": [None, 8, 16],
    "min_samples_leaf": [1, 2, 4],
    "max_features": ["sqrt", 0.5],
}
busqueda = GridSearchCV(
    RandomForestClassifier(random_state=42, n_jobs=-1),
    parametros, cv=5, scoring="roc_auc", n_jobs=-1,
).fit(X_tr, y_tr)
print(busqueda.best_params_, round(busqueda.best_score_, 3))

Importancia de features: precauciones

La importancia por impureza favorece features con muchos valores únicos y se calcula sobre el train. Para decisiones críticas, usa permutation importance sobre validación o SHAP, y comprueba la estabilidad entre folds. Una feature importante para el modelo no es necesariamente causal.

Bosques no extrapolan En regresión, un Random Forest predice promediando hojas; no puede predecir un valor fuera del rango observado en entrenamiento. Si necesitas extrapolar, usa un modelo lineal o un enfoque híbrido.

Ventajas y limitaciones

Ventajas

  • Robusto, poco sensible a hiperparámetros y a outliers moderados.
  • Maneja bien datos tabulares y relaciones no lineales.
  • OOB y importancia de features integradas.

Limitaciones

  • Menos interpretable que un árbol.
  • Más lento y pesado en inferencia.
  • No extrapola y puede sesgarse con categorías de alta cardinalidad.
Ejercicios 4.8
  1. ¿Por qué promediar árboles reduce la varianza pero no el sesgo? Porque el promedio de estimadores insesgados sigue siendo insesgado, pero su dispersión disminuye.
  2. ¿Qué pasa si todos los árboles son idénticos? $\rho=1$ y el promedio no reduce nada.
  3. ¿Cómo cambia max_features el compromiso? Valores bajos descorrelacionan más pero cada árbol es más débil; valores altos se acercan a bagging puro.
  4. Compara OOB con validación cruzada en un dataset. Deberían ser cercanos; una gran discrepancia sugiere problemas.
Lección 4.9

Support Vector Machines

24 minMáximo margenKernels

Una SVM busca el hiperplano que separa las clases con el máximo margen. En lugar de cualquier frontera que acierte, elige la que deja más espacio a ambos lados. Con kernels, esta idea se extiende a fronteras no lineales.

Margen y hiperplano

Hiperplano y margen $$\mathbf{w}^\top\mathbf{x}+b=0, \qquad \text{margen}=\frac{2}{\|\mathbf{w}\|_2}$$ Maximizar el margen equivale a minimizar $\|\mathbf{w}\|^2$ sujeto a que todos los puntos estén correctamente clasificados y fuera del margen.
Problema de margen duro $$\min_{\mathbf{w},b}\frac12\|\mathbf{w}\|^2 \quad \text{s.a.}\quad y_i(\mathbf{w}^\top\mathbf{x}_i+b)\geq1,\;\; i=1,\dots,n$$ Los puntos que tocan el margen ($y_i(\mathbf{w}^\top\mathbf{x}_i+b)=1$) son los vectores de soporte: solo ellos determinan la frontera.

Margen suave: el parámetro $C$

En datos reales las clases se solapan. Introducimos variables de holgura $\xi_i\geq0$ que permiten violaciones del margen, penalizadas por $C$:

$$\min_{\mathbf{w},b,\xi}\frac12\|\mathbf{w}\|^2+C\sum_{i=1}^{n}\xi_i \quad \text{s.a.}\quad y_i(\mathbf{w}^\top\mathbf{x}_i+b)\geq1-\xi_i,\;\;\xi_i\geq0$$ $C$ pequeño: margen amplio, más errores tolerados (mayor sesgo). $C$ grande: margen estrecho, menos errores (mayor varianza).

La formulación equivalente con pérdida hinge es $\sum_i\max(0,1-y_i f(\mathbf{x}_i))+\frac{1}{2C}\|\mathbf{w}\|^2$.

El truco del kernel

Si los datos no son separables linealmente, los proyectamos a un espacio de mayor dimensión donde sí lo sean. El kernel calcula el producto punto en ese espacio sin calcular la transformación explícita:

Kernels comunes $$K_{\text{lineal}}=\mathbf{x}^\top\mathbf{z}, \qquad K_{\text{poly}}=(\gamma\mathbf{x}^\top\mathbf{z}+r)^d, \qquad K_{\text{RBF}}=e^{-\gamma\|\mathbf{x}-\mathbf{z}\|^2}$$ $\gamma$ controla el radio de influencia en RBF: alto → frontera muy local y sobreajuste; bajo → frontera suave.
Por qué funciona La solución solo depende de productos punto entre ejemplos. Si sustituimos el producto punto por un kernel, obtenemos el mismo algoritmo en un espacio implícito de dimensión alta. Es una de las ideas más elegantes del ML clásico.
Pythonsvm.py
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.datasets import load_breast_cancer
from sklearn.metrics import classification_report, roc_auc_score

datos = load_breast_cancer(as_frame=True)
X, y = datos.data, datos.target
X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=.2, random_state=42, stratify=y)

parametros = [
    {"svc__kernel": ["linear"], "svc__C": [0.01, 0.1, 1, 10, 100]},
    {"svc__kernel": ["rbf"], "svc__C": [0.1, 1, 10, 100],
     "svc__gamma": [0.001, 0.01, 0.1, 1]},
]

pipe = make_pipeline(StandardScaler(), SVC(probability=True, random_state=42))
busqueda = GridSearchCV(pipe, parametros, cv=5, scoring="roc_auc", n_jobs=-1)
busqueda.fit(X_tr, y_tr)

print("mejores params:", busqueda.best_params_)
print("mejor AUC CV:", round(busqueda.best_score_, 3))

y_pred = busqueda.predict(X_te)
y_prob = busqueda.predict_proba(X_te)[:, 1]
print(classification_report(y_te, y_pred))
print("AUC test:", round(roc_auc_score(y_te, y_prob), 3))
print("vectores de soporte:", busqueda.best_estimator_[-1].n_support_)

Ventajas y limitaciones

Ventajas

  • Eficaz en espacios de alta dimensión y con pocos ejemplos.
  • Margen máximo: buena generalización teórica.
  • Los kernels capturan no linealidades sin diseñar features.

Limitaciones

  • Coste $O(n^2)$–$O(n^3)$: no escala a millones de filas.
  • Sensible a escala y a la elección de $C$ y $\gamma$.
  • No da probabilidades calibradas de forma nativa.
  • Difícil de interpretar con kernels.
Escala primero SVM con kernel RBF sin estandarizar es casi siempre un desastre: la distancia está dominada por la feature de mayor magnitud. Estandariza y luego busca $C$ y $\gamma$ en escala logarítmica.
Ejercicios 4.9
  1. ¿Qué puntos determinan la frontera? Solo los vectores de soporte; mover un punto lejano no la cambia.
  2. Si aumentas $C$ de 0.01 a 100, ¿qué esperas? Menos errores de train, margen más estrecho y más riesgo de overfitting.
  3. ¿Qué hace $\gamma$ alto en RBF? Cada punto influye solo en su vecindad; frontera muy irregular.
  4. ¿Por qué SVM no es ideal para 10 millones de filas? El entrenamiento escala de forma superlineal con $n$.
Lección 4.10

Naive Bayes

18 minProbabilísticoTexto

Naive Bayes aplica la regla de Bayes con una suposición fuerte —y a menudo falsa—: las features son condicionalmente independientes dada la clase. Aun así, es rápido, funciona muy bien en texto y es un baseline excelente.

Modelo

Clasificador $$\hat{y}=\arg\max_{c}\; P(c)\prod_{j=1}^{d}P(x_j\mid c)$$ Se elige la clase con mayor probabilidad posterior. El denominador $P(\mathbf{x})$ es común y no afecta al argmax.

En la práctica se trabaja en escala logarítmica para evitar underflow con muchas features:

$$\hat{y}=\arg\max_c\left[\log P(c)+\sum_{j=1}^{d}\log P(x_j\mid c)\right]$$

Variantes según el tipo de feature

VarianteSupuestoUso
GaussianNB$x_j\mid c\sim\mathcal{N}(\mu_{jc},\sigma_{jc}^2)$Features continuas
MultinomialNBConteos de palabrasBolsa de palabras, TF-IDF
BernoulliNBPresencia/ausencia binariaTexto con vocabulario fijo
ComplementNBCorrección para clases desbalanceadasTexto desbalanceado

Suavizado de Laplace

Si una palabra no apareció en una clase durante el entrenamiento, $P(x_j\mid c)=0$ y todo el producto se anula. El suavizado añade un pseudo-conteo $\alpha$:

$$P(x_j\mid c)=\frac{N_{jc}+\alpha}{N_c+\alpha d}$$ $\alpha=1$ es el suavizado de Laplace; $\alpha<1$ (por ejemplo 0.1) es Lidstone y suele funcionar mejor en texto.
4 Clasificar un correo simplificado

Vocabulario: «oferta», «reunión». Priors: $P(\text{spam})=0.6$, $P(\text{normal})=0.4$. Verosimilitudes: $P(\text{oferta}\mid\text{spam})=0.8$, $P(\text{oferta}\mid\text{normal})=0.1$; $P(\text{reunión}\mid\text{spam})=0.1$, $P(\text{reunión}\mid\text{normal})=0.7$.

1
Correo con «oferta» y «reunión». Spam: $0.6(0.8)(0.1)=0.048$.
2
Normal: $0.4(0.1)(0.7)=0.028$.
3
Normalizando: $P(\text{spam}\mid x)=0.048/(0.048+0.028)\approx0.632$.
4
Se predice spam, con incertidumbre. La independencia es dudosa (las palabras correlacionan), pero el ranking suele ser útil.
Pythonnaive_bayes.py
from sklearn.naive_bayes import MultinomialNB, GaussianNB
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_20newsgroups
from sklearn.metrics import classification_report

# --- Texto: 4 categorías de 20 Newsgroups ---
categorias = ["alt.atheism", "comp.graphics", "sci.med", "soc.religion.christian"]
datos = fetch_20newsgroups(subset="train", categories=categorias,
                           remove=("headers", "footers", "quotes"))
X_tr, X_te, y_tr, y_te = train_test_split(
    datos.data, datos.target, test_size=.25, random_state=42, stratify=datos.target)

modelo = make_pipeline(
    TfidfVectorizer(stop_words="english", max_features=20_000, ngram_range=(1, 2)),
    MultinomialNB(alpha=0.1),
).fit(X_tr, y_tr)

y_pred = modelo.predict(X_te)
print(classification_report(y_te, y_pred, target_names=datos.target_names))

# --- Continuo: GaussianNB ---
from sklearn.datasets import load_iris
iris = load_iris()
Xtr, Xte, ytr, yte = train_test_split(iris.data, iris.target,
                                      test_size=.2, random_state=42, stratify=iris.target)
gnb = GaussianNB().fit(Xtr, ytr)
print("GaussianNB accuracy:", gnb.score(Xte, yte))

Ventajas y limitaciones

Ventajas

  • Extremadamente rápido y con pocos datos.
  • Funciona bien con alta dimensionalidad y texto.
  • Probabilidades interpretables (aunque a menudo mal calibradas).

Limitaciones

  • La independencia condicional rara vez se cumple.
  • Probabilidades sobreconfiadas; no las uses sin calibrar.
  • Peor que modelos discriminativos cuando hay muchos datos y correlaciones fuertes.
Por qué funciona pese a la suposición falsa Para clasificar solo importa el orden de las probabilidades, no su valor exacto. La independencia sesga las probabilidades, pero a menudo preserva el argmax. Es un caso interesante de modelo «mal especificado» pero útil.
Ejercicios 4.10
  1. ¿Qué pasa si $\alpha=0$ y una palabra no aparece en una clase? La probabilidad condicional es 0 y anula todo el producto.
  2. ¿Por qué usar logaritmos? Para convertir productos de muchos números pequeños en sumas y evitar underflow.
  3. ¿Qué variante usarías para TF-IDF? MultinomialNB (aunque TF-IDF no son conteos puros, funciona) o ComplementNB.
  4. ¿Por qué las probabilidades de Naive Bayes suelen estar mal calibradas? Por la suposición de independencia, que sobreestima la confianza.
Lección 4.11

Gradient Boosting y XGBoost

26 minEstado del arte tabularRegularización

Gradient Boosting construye un modelo aditivo de árboles débiles, donde cada árbol corrige los errores del anterior. Es, junto con Random Forest, el rey de los datos tabulares y la base de XGBoost, LightGBM y CatBoost.

La idea aditiva

Modelo por etapas $$F_0(\mathbf{x})=\arg\min_\gamma\sum_i L(y_i,\gamma), \qquad F_m(\mathbf{x})=F_{m-1}(\mathbf{x})+\nu\,h_m(\mathbf{x})$$ $h_m$: árbol ajustado a los errores (gradientes) de la etapa anterior; $\nu\in(0,1]$: shrinkage o tasa de aprendizaje.

En cada etapa, el nuevo árbol se ajusta a los gradientes negativos de la pérdida respecto a la predicción actual. Para MSE, el gradiente negativo es simplemente el residuo $y-F_{m-1}(x)$. Para otras pérdidas, es una pseudo-respuesta.

Por qué funciona

Es descenso del gradiente en el espacio de funciones: en lugar de actualizar un vector de parámetros, añadimos una función (un árbol) que reduce la pérdida en la dirección del gradiente. Cada árbol es débil (pocas hojas), pero la suma de cientos es muy potente.

XGBoost: mejoras clave

  • Regularización explícita: penaliza número de hojas y magnitud de sus pesos.
  • Aproximación de segundo orden: usa gradiente y hessiano para elegir divisiones.
  • Manejo de valores faltantes: aprende una dirección por defecto.
  • Subsample y colsample: aleatoriedad para reducir overfitting.
  • Paralelización y compresión: eficiencia en datos grandes.
  • Early stopping: detiene cuando la validación deja de mejorar.
Objetivo regularizado de XGBoost $$\mathcal{L}=\sum_{i=1}^{n}L(y_i,\hat{y}_i)+\sum_{m=1}^{M}\left(\gamma T_m+\frac12\lambda\sum_{j=1}^{T_m}w_{mj}^2\right)$$ $T_m$: número de hojas del árbol $m$; $w_{mj}$: peso de la hoja $j$. $\gamma$ penaliza complejidad; $\lambda$ penaliza pesos grandes.
Pythonxgboost_demo.py
# pip install xgboost
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score, classification_report
from sklearn.datasets import load_breast_cancer

datos = load_breast_cancer(as_frame=True)
X, y = datos.data, datos.target
X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=.2, random_state=42, stratify=y)

modelo = xgb.XGBClassifier(
    n_estimators=1000,
    learning_rate=0.03,
    max_depth=4,
    min_child_weight=3,
    subsample=0.8,
    colsample_bytree=0.8,
    reg_lambda=1.0,
    reg_alpha=0.0,
    objective="binary:logistic",
    eval_metric="auc",
    early_stopping_rounds=50,
    random_state=42,
    n_jobs=-1,
)

modelo.fit(
    X_tr, y_tr,
    eval_set=[(X_tr, y_tr), (X_te, y_te)],
    verbose=False,
)

print("mejor iteración:", modelo.best_iteration)
print("AUC test:", roc_auc_score(y_te, modelo.predict_proba(X_te)[:, 1]))
print(classification_report(y_te, modelo.predict(X_te)))

# Importancia de features
import pandas as pd
imp = pd.Series(modelo.feature_importances_, index=X.columns).sort_values(ascending=False)
print(imp.head(10))

Guía práctica de hiperparámetros

HiperparámetroEfecto si aumentaRango típico
learning_rate ($\nu$)Aprende más rápido, más riesgo de overfitting0.01–0.1
n_estimatorsMás capacidad; usar early stopping500–5000
max_depthMás interacciones, más overfitting3–8
min_child_weightDivisiones más conservadoras1–10
subsampleMás aleatoriedad, menos overfitting0.6–0.9
colsample_bytreeMenos features por árbol0.6–0.9
reg_lambdaMás regularización L20.1–10

Estrategia: fija una tasa de aprendizaje baja (0.03–0.05), usa early stopping para encontrar el número de árboles, y ajusta profundidad y regularización. No optimices todos los hiperparámetros a la vez: usa búsqueda aleatoria o bayesiana y valida con cuidado.

Ventajas y limitaciones

Ventajas

  • Rendimiento excelente en tabular.
  • Maneja no linealidades, interacciones y faltantes.
  • Early stopping y regularización integradas.

Limitaciones

  • Más hiperparámetros y riesgo de sobreajuste si no se valida.
  • Menos interpretable; requiere SHAP.
  • No extrapola y puede ser costoso en entrenamiento.
No es magia XGBoost con datos con fuga de información o con un split mal hecho dará resultados espectaculares y falsos. La metodología del Módulo 3 importa más que el algoritmo. Siempre compara contra una baseline y valida en datos no vistos.
Resumen del módulo Lineales para interpretabilidad y baselines; KNN para similitud; árboles para reglas; Random Forest para robustez; SVM para márgenes y kernels; Naive Bayes para texto; boosting para rendimiento tabular. La elección se valida empíricamente, no se decide por intuición.
Ejercicios 4.11
  1. ¿Por qué un learning rate bajo requiere más árboles? Porque cada árbol aporta una corrección pequeña; se necesitan más etapas para reducir la pérdida.
  2. ¿Qué hace early stopping? Detiene el entrenamiento cuando la métrica de validación no mejora durante un número de iteraciones, evitando overfitting.
  3. ¿Por qué XGBoost maneja valores faltantes? Aprende, en cada división, hacia qué rama enviar los faltantes según los datos de entrenamiento.
  4. Compara XGBoost con Random Forest en tu dataset: reporta AUC, tiempo de entrenamiento e interpretabilidad. No asumas que uno siempre gana.
Curso de Machine Learning · teoría, matemáticas y práctica en Python Progreso local · 0% completado · Hecho para estudiar en cualquier lugar