Gradient boosting y XGBoost

El gradient boosting construye un modelo aditivo ajustando secuencialmente árboles de decisión superficiales al gradiente negativo de la función de pérdida. Cada árbol corrige los errores del ensemble actual. XGBoost refina esto con una expansión de Taylor de segundo orden de la pérdida, regularización explícita sobre los pesos de las hojas y submuestreo de columnas que reduce aún más el sobreajuste. Juntos son el algoritmo dominante en competiciones de datos tabulares.

La idea central: descenso por gradiente funcional

En lugar de minimizar la pérdida actualizando parámetros (como en el descenso por gradiente), el boosting minimiza la pérdida añadiendo funciones. La predicción en el paso \(m\) es:

\[F_m(\mathbf{x}) = F_{m-1}(\mathbf{x}) + \eta \cdot h_m(\mathbf{x})\]

donde \(h_m\) es el nuevo árbol y \(\eta\) es la tasa de aprendizaje (shrinkage). El árbol \(h_m\) se ajusta al gradiente negativo de la pérdida evaluado en las predicciones actuales:

\[r_i^{(m)} = -\left[\frac{\partial \mathcal{L}(y_i, F(\mathbf{x}_i))}{\partial F(\mathbf{x}_i)}\right]_{F=F_{m-1}}\]

Para la pérdida de error cuadrático \(\mathcal{L} = (y_i - F(\mathbf{x}_i))^2/2\): \(r_i^{(m)} = y_i - F_{m-1}(\mathbf{x}_i)\). Los árboles se ajustan a los residuos, que es la razón por la que las primeras descripciones del boosting hablaban de “ajustar residuos”. La visión del gradiente generaliza esto a cualquier pérdida diferenciable: log-loss para clasificación, error absoluto para regresión robusta, etc.

La analogía con el descenso por gradiente: el descenso por gradiente se mueve en la dirección del gradiente negativo en el espacio de parámetros; el descenso por gradiente funcional se mueve en la dirección del gradiente negativo en el espacio de funciones, añadiendo un árbol que apunta en esa dirección.

El algoritmo de gradient boosting

  1. Inicializa \(F_0(\mathbf{x}) = \arg\min_\gamma \sum_i \mathcal{L}(y_i, \gamma)\) (p. ej., la media de \(y\) para regresión).
  2. Para \(m = 1, \ldots, M\):
    • Calcula los pseudo-residuos \(r_i^{(m)} = -\partial\mathcal{L}/\partial F(\mathbf{x}_i)|_{F_{m-1}}\).
    • Ajusta un árbol superficial \(h_m\) a \(\{(\mathbf{x}_i, r_i^{(m)})\}\).
    • Actualiza \(F_m = F_{m-1} + \eta \cdot h_m\).
  3. Devuelve \(F_M\).

Cuatro paneles mostrando cómo el gradient boosting ajusta secuencialmente árboles a los residuos y mejora la predicción del ensemble

Con solo 1 árbol (arriba izquierda) el ajuste es muy brusco. A medida que se añaden más árboles, el ensemble sigue progresivamente la función real (gris discontinuo). La curva roja es la predicción actual del ensemble.

Regularización: los tres controles

El gradient boosting puede sobreajustar gravemente sin regularización. Tres controles clave:

Tasa de aprendizaje \(\eta\) (shrinkage)

Cada árbol contribuye \(\eta \cdot h_m\) en lugar de \(h_m\). Un \(\eta\) más pequeño requiere más árboles pero produce mejor generalización. La combinación óptima es \(\eta\) pequeño + \(M\) grande con parada temprana. Valores típicos: 0,01 a 0,3.

Gradient boosting estocástico (submuestreo de filas)

En cada iteración, solo se usa una fracción aleatoria \(s\) de las observaciones de entrenamiento (sin reemplazamiento) para ajustar el árbol. Esto introduce aleatoriedad que reduce el sobreajuste y acelera el entrenamiento. Valores típicos: \(s = 0,5\) a \(0,8\).

Profundidad del árbol

Los árboles superficiales (profundidad 1-6) son el estándar. Un árbol de profundidad 1 (tocón) ajusta solo efectos principales; los de profundidad 2 ajustan interacciones de pares; los más profundos ajustan interacciones de orden superior. El boosting suele funcionar mejor con árboles superficiales: la mayor parte de la complejidad proviene de combinar muchos árboles, no de árboles individuales profundos.

Error de entrenamiento y test vs número de árboles para diferentes tasas de aprendizaje mostrando el efecto del shrinkage en el sobreajuste

\(\eta=0{,}5\) grande (rojo) ajusta los datos de entrenamiento rápidamente pero el error de test sube pronto: sobreajuste. \(\eta=0{,}01\) pequeño (verde) desciende lentamente pero alcanza un error de test más bajo con más árboles. \(\eta=0{,}1\) medio (azul) equilibra ambos.

XGBoost: optimización de segundo orden

El gradient boosting estándar usa solo la primera derivada (gradiente) para ajustar cada árbol. XGBoost usa una expansión de Taylor de segundo orden de la pérdida:

\[\mathcal{L}^{(m)} \approx \sum_i \left[g_i f_m(\mathbf{x}_i) + \frac{1}{2}h_i f_m(\mathbf{x}_i)^2\right] + \Omega(f_m)\]

donde \(g_i = \partial\mathcal{L}/\partial \hat{y}_i\) y \(h_i = \partial^2\mathcal{L}/\partial \hat{y}_i^2\) son las derivadas de primer y segundo orden de la pérdida en la predicción actual, y:

\[\Omega(f_m) = \gamma T + \frac{1}{2}\lambda\sum_{j=1}^T w_j^2\]

penaliza el número de hojas \(T\) y los pesos cuadráticos de las hojas \(w_j\) con parámetros \(\gamma\) y \(\lambda\).

Esta formulación da un peso óptimo de hoja en forma cerrada para cada hoja \(j\):

\[w_j^* = -\frac{\sum_{i \in \text{hoja}_j} g_i}{\sum_{i \in \text{hoja}_j} h_i + \lambda}\]

y una ganancia exacta para cada candidato de división, haciendo la construcción de árboles más rápida y mejor calibrada que los métodos de primer orden.

Mejoras adicionales de XGBoost sobre GBDT clásico:

  • Submuestreo de columnas (características) a nivel de árbol y de división (como random forest).
  • Búsqueda de divisiones consciente de la dispersión: maneja valores faltantes y datos dispersos de forma nativa.
  • Búsqueda aproximada de divisiones con bocetos de cuantiles: más rápida que la búsqueda exacta para \(n\) grande.
  • Validación cruzada y parada temprana integradas.

Random forest vs gradient boosting

Random forest Gradient boosting
Construcción de árboles Paralela (independiente) Secuencial (dependiente)
Profundidad del árbol Profunda (completa) Superficial (profundidad 3-6)
Sesgo Bajo Decrece con \(M\)
Varianza Reducida por promediado Controlada por \(\eta\), profundidad, submuestreo
Riesgo de sobreajuste Bajo (regularizado naturalmente) Alto sin ajuste cuidadoso
Complejidad de ajuste Baja (principalmente mtry) Mayor (muchos hiperparámetros)
Velocidad de entrenamiento Rápida (paralelizable) Más lenta (secuencial)
Precisión típica Muy buena Mejor en datos tabulares

Random forest es un valor predeterminado más seguro: menos ajuste, más difícil de sobreajustar. El gradient boosting con XGBoost suele lograr mayor precisión pero requiere una selección cuidadosa de hiperparámetros.

⚠️ Usa siempre parada temprana con gradient boosting

Sin parada temprana, el gradient boosting acabará sobreajustando a medida que \(M\) aumenta: el error de entrenamiento sigue bajando mientras el error de test sube. Reserva siempre un conjunto de validación o usa validación cruzada con parada temprana:

  • Monitoriza la métrica de validación en cada ronda.
  • Para cuando el error de validación no haya mejorado durante \(k\) rondas consecutivas (típicamente \(k=10\) a \(50\)).
  • Usa el modelo en la mejor ronda de validación, no la última.

Con \(\eta\) pequeño (p. ej., 0,01), puede que necesites miles de árboles antes de que se active la parada temprana: este es el comportamiento esperado.

💡 Gradient boosting en R

library(xgboost)

dtrain <- xgb.DMatrix(X_train, label=y_train)
dval   <- xgb.DMatrix(X_val,   label=y_val)

params <- list(
  eta           = 0.05,
  max_depth     = 5,
  subsample     = 0.8,
  colsample_bytree = 0.8,
  lambda        = 1,       # L2 sobre pesos de hojas
  gamma         = 0,       # ganancia mínima para dividir
  objective     = "binary:logistic",
  eval_metric   = "auc"
)

fit <- xgb.train(
  params    = params,
  data      = dtrain,
  nrounds   = 1000,
  watchlist = list(train=dtrain, val=dval),
  early_stopping_rounds = 30,
  verbose   = 1
)

# Importancia de características
xgb.importance(model=fit)
xgb.plot.importance(xgb.importance(model=fit), top_n=15)

# Validación cruzada (sin conjunto de validación separado)
cv <- xgb.cv(params=params, data=dtrain, nfold=5,
              nrounds=1000, early_stopping_rounds=30)
best_nrounds <- cv$best_iteration