En la lección anterior dejamos presentado el boosting como la familia secuencial de los ensembles: modelos que se entrenan uno tras otro, cada cual corrigiendo los errores del anterior. Esta lección la dedicamos por completo a su representante estrella, el gradient boosting, probablemente el algoritmo más ganador de la última década en datos tabulares: domina las competiciones de Kaggle y resuelve en producción problemas exactamente como el churn y la demanda de MercaFresh. Vas a entender su mecánica con un ejemplo numérico a mano, aprenderás a manejar sus dos mandos principales (learning rate y número de estimadores), conocerás las implementaciones que importan — sklearn, HistGradientBoosting, XGBoost, LightGBM — y lo enfrentarás al Random Forest de la lección anterior.

Contenido

  1. La idea: aprender de los residuos
  2. Ejemplo numérico a mano: 3 iteraciones
  3. Learning rate y n_estimators: el equilibrio
  4. Gradient boosting en scikit-learn
  5. HistGradientBoosting: la versión moderna
  6. XGBoost y LightGBM: los dominadores del tablero
  7. Boosting sobre el churn de MercaFresh: duelo con Random Forest
  8. El talón de Aquiles: overfitting y sus frenos
  9. Interpretación: feature importance y SHAP

La idea: aprender de los residuos

Random Forest entrena árboles independientes y promedia. Gradient boosting hace lo contrario: entrena árboles pequeños y débiles en cadena, donde cada uno tiene una única misión: predecir el error que dejaron los anteriores.

En regresión, con error cuadrático, el proceso es muy intuitivo:

  1. Empieza con una predicción trivial: la media de y (nuestro viejo conocido DummyRegressor de 06-02).
  2. Calcula los residuos: residuo = y_real - predicción_actual.
  3. Entrena un árbol pequeño que prediga esos residuos a partir de las features.
  4. Actualiza: predicción_nueva = predicción_actual + η · predicción_del_árbol, donde η (eta) es el learning rate, un factor que encoge cada corrección.
  5. Vuelve al paso 2, tantas veces como diga n_estimators.

El nombre "gradient" viene de que los residuos son, matemáticamente, el gradiente (con signo cambiado) de la función de pérdida cuadrática: cada árbol da un paso de descenso de gradiente — el mismo que usamos en 04-01 para ajustar la regresión lineal — pero en el espacio de las predicciones en lugar del espacio de los coeficientes. Con otras pérdidas (p. ej. logística para clasificación) se sigue el mismo esquema sustituyendo el residuo por el "pseudo-residuo" correspondiente; por eso el método generaliza a clasificación sin cambiar de filosofía.

flowchart LR
    M0[Predicción inicial: media] --> R1[Residuos 1]
    R1 --> A1[Árbol 1 aprende residuos]
    A1 --> M1[Predicción += eta x árbol 1]
    M1 --> R2[Residuos 2 más pequeños]
    R2 --> A2[Árbol 2]
    A2 --> M2[Predicción += eta x árbol 2]
    M2 --> R3[Residuos 3 aún menores]

Ejemplo numérico a mano: 3 iteraciones

Tomemos 4 clientes de MercaFresh y su gasto mensual (en €). Para poder seguir las cuentas usaremos learning rate η = 1 (sin encogimiento) y "árboles" simplificados que predicen la media del residuo de su grupo (clientes de frecuencia alta vs. baja):

Cliente Frecuencia Gasto real (y)
Ana alta 120
Bruno alta 100
Carla baja 40
David baja 60

Iteración 0. Predicción inicial = media global = (120+100+40+60)/4 = 80 para todos.

Cliente Predicción Residuo (y − pred)
Ana 80 +40
Bruno 80 +20
Carla 80 −40
David 80 −20

Iteración 1. El árbol 1 divide por frecuencia y predice la media del residuo de cada grupo: +30 para frecuencia alta, −30 para baja. Actualizamos:

Cliente Predicción nueva Residuo nuevo
Ana 80 + 30 = 110 +10
Bruno 80 + 30 = 110 −10
Carla 80 − 30 = 50 −10
David 80 − 30 = 50 +10

El error se ha reducido drásticamente: de residuos ±40/±20 a ±10.

Iteración 2. Dentro de cada grupo los residuos ya suman cero, así que un árbol que solo viera la frecuencia predice 0; supongamos que el árbol 2 encuentra otra feature (p. ej. antigüedad) que separa a Ana de Bruno y a David de Carla, y predice +10/−10 según corresponda:

Cliente Predicción final Residuo
Ana 110 + 10 = 120 0
Bruno 110 − 10 = 100 0
Carla 50 − 10 = 40 0
David 50 + 10 = 60 0

Tres pasos (predicción base + 2 árboles) y el train está clavado. Y aquí está la moraleja doble: la suma de correctores débiles alcanza una precisión que ninguno lograría solo… pero también acabamos de ver lo rápido que este método puede memorizar el conjunto de entrenamiento — con residuo cero en train, todo lo que quede es ajuste al ruido. En la práctica, η = 1 casi nunca se usa: encoger cada paso es el primer freno contra el overfitting.

Learning rate y n_estimators: el equilibrio

Los dos mandos principales están acoplados:

  • learning_rate (η): cuánto aporta cada árbol. Valores típicos: 0.01–0.3.
  • n_estimators: cuántos árboles se encadenan.

La relación es de compensación: si reduces η a la mitad, necesitas aproximadamente el doble de árboles para llegar al mismo punto. ¿Por qué molestarse entonces en bajar η? Porque muchos pasos pequeños generalizan mejor que pocos pasos grandes: cada corrección suave deja margen a los árboles siguientes para enmendar el rumbo, mientras que un paso grande puede perseguir ruido irreversiblemente.

Configuración Comportamiento
η alto (0.3) + pocos árboles Rápido de entrenar; riesgo de overfitting y de pasos bruscos
η bajo (0.01–0.05) + muchos árboles Más lento; mejor generalización; necesita early stopping para no pasarse
η alto + muchos árboles Receta casi segura de overfitting

A diferencia del Random Forest — donde más árboles solo estabilizaba —, en boosting n_estimators sí es un mando de complejidad: cada árbol adicional sigue reduciendo el error de train, y a partir de cierto punto empieza a subir el de validación. Es exactamente la silueta de las curvas de 06-05, y su antídoto natural será el early stopping que veremos enseguida.

Gradient boosting en scikit-learn

La implementación clásica: GradientBoostingRegressor y GradientBoostingClassifier.

from sklearn.ensemble import GradientBoostingClassifier

gb = GradientBoostingClassifier(
    n_estimators=300,     # árboles encadenados
    learning_rate=0.05,   # aporte de cada árbol
    max_depth=3,          # árboles deliberadamente pequeños (débiles)
    subsample=0.8,        # cada árbol ve el 80% de las filas (toque de bagging)
    random_state=42,
)

Fíjate en max_depth=3: en boosting los árboles son enanos a propósito (1–4 niveles). El aprendiz débil es una decisión de diseño: la potencia viene de la secuencia, no del individuo. subsample < 1 añade aleatoriedad al estilo bagging ("stochastic gradient boosting"), que suele mejorar la generalización.

HistGradientBoosting: la versión moderna

Desde scikit-learn 0.21 existe una reimplementación mucho más rápida: HistGradientBoostingClassifier/Regressor. Su truco es discretizar cada feature en un histograma de a lo sumo 255 contenedores, de modo que buscar la mejor división ya no requiere ordenar valores continuos: se recorren los contenedores. En datasets de decenas de miles de filas es órdenes de magnitud más rápida, y además:

  • Maneja valores faltantes de forma nativa (aprende hacia qué rama enviar los NaN, sin imputación previa — aunque lo aprendido en 03-02 sigue siendo necesario para los demás modelos).
  • Incorpora early stopping integrado (early_stopping=True) con una fracción de validación interna.
  • Soporta features categóricas nativas (categorical_features).
from sklearn.ensemble import HistGradientBoostingClassifier

hgb = HistGradientBoostingClassifier(
    learning_rate=0.05,
    max_iter=1000,          # equivalente a n_estimators
    early_stopping=True,    # para de añadir árboles cuando validación deja de mejorar
    validation_fraction=0.15,
    n_iter_no_change=20,    # paciencia: 20 iteraciones sin mejora
    random_state=42,
)

Regla práctica actual: para gradient boosting dentro del ecosistema sklearn, usa la versión Hist por defecto; la clásica queda para datasets pequeños o por compatibilidad.

XGBoost y LightGBM: los dominadores del tablero

Fuera de sklearn viven las dos bibliotecas que popularizaron el gradient boosting moderno (se instalan aparte: pip install xgboost lightgbm):

XGBoost (2014) LightGBM (2017)
Aportación clave Regularización L1/L2 en los árboles, manejo de faltantes, paralelización eficiente Crecimiento por hojas (leaf-wise) e histogramas: aún más rápido y con menos memoria
Fama El algoritmo que "ganaba todos los Kaggle" a mediados de los 2010 Estándar actual en industria para tabular grande
API Propia + wrapper compatible con sklearn (XGBClassifier) Ídem (LGBMClassifier)

¿Por qué esta familia domina en datos tabulares (tablas de clientes, transacciones, sensores…)? Porque los árboles capturan sin esfuerzo interacciones y no-linealidades, ignoran la escala de las features, digieren mezclas de variables numéricas y categóricas, y el boosting exprime hasta la última gota de señal — todo con tiempos de entrenamiento de segundos o minutos. En este terreno suelen batir incluso a las redes profundas, como discutiremos con honestidad en 07-04.

Ambas bibliotecas ofrecen early stopping contra un conjunto de validación explícito, la forma profesional de fijar n_estimators:

# Ejemplo con la API sklearn de XGBoost
from xgboost import XGBClassifier

xgb = XGBClassifier(n_estimators=2000, learning_rate=0.05, max_depth=3,
                    early_stopping_rounds=50, eval_metric="logloss")
xgb.fit(X_train, y_train, eval_set=[(X_val, y_val)], verbose=False)
print(xgb.best_iteration)   # cuántos árboles se usaron realmente

Se pide un n_estimators deliberadamente alto y se deja que la validación decida dónde parar: si tras 50 rondas la métrica no mejora, se detiene y se queda con la mejor iteración. Observa que reaparece la disciplina de 06-01: el conjunto de validación usado para parar no puede ser el test final.

Boosting sobre el churn de MercaFresh: duelo con Random Forest

Enfrentemos al campeón de la lección anterior con el nuevo aspirante, sobre el mismo dataset de churn con features RFM y la misma validación cruzada estratificada de 06-03.

import numpy as np
import pandas as pd
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.ensemble import (RandomForestClassifier,
                              HistGradientBoostingClassifier)

# Dataset de churn de MercaFresh (idéntico al de 07-02)
rng = np.random.default_rng(42)
n = 1000
recencia = rng.gamma(2, 15, n)
frecuencia = rng.poisson(5, n) + 1
monetario = rng.gamma(3, 40, n)
antiguedad = rng.uniform(1, 60, n)
incidencias = rng.poisson(0.5, n)
logits = 0.05 * recencia - 0.4 * frecuencia + 0.6 * incidencias - 0.01 * antiguedad - 0.5
y = (rng.random(n) < 1 / (1 + np.exp(-logits))).astype(int)
X = pd.DataFrame({"recencia": recencia, "frecuencia": frecuencia,
                  "monetario": monetario, "antiguedad": antiguedad,
                  "incidencias": incidencias})

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

modelos = {
    "Random Forest (07-02)": RandomForestClassifier(
        n_estimators=300, random_state=42, n_jobs=-1),
    "HistGradientBoosting": HistGradientBoostingClassifier(
        learning_rate=0.05, max_iter=500, max_depth=3,
        early_stopping=True, random_state=42),
}

for nombre, modelo in modelos.items():
    f1 = cross_val_score(modelo, X, y, cv=cv, scoring="f1")
    print(f"{nombre:24s} F1: {f1.mean():.3f} ± {f1.std():.3f}")

En datasets pequeños y con señal sencilla como este, ambos suelen quedar muy parejos (a veces gana el bosque). El patrón general en la práctica es:

  • Random Forest: excelente resultado con cero ajuste; difícil de estropear. La baseline fuerte.
  • Gradient boosting: techo más alto — con sus hiperparámetros bien elegidos suele arañar unos puntos más de métrica, especialmente en datasets grandes con interacciones complejas — pero exige ese ajuste (que automatizaremos en 07-05) y vigilancia del overfitting.

Para MercaFresh la lectura de negocio es concreta: si el bosque da F1 0.82 sin esfuerzo y el boosting bien afinado da 0.85, esos tres puntos son decenas de clientes recuperables cada mes correctamente priorizados por la campaña de retención — normalmente merece el esfuerzo.

El talón de Aquiles: overfitting y sus frenos

El boosting persigue residuos, y los residuos acaban siendo ruido: si lo dejas correr, memoriza el train (lo vimos en el ejemplo a mano, que llegó a residuo cero). Es el alumno aventajado de las patologías de 06-05, y por eso viene equipado con más frenos que ningún otro algoritmo:

Freno Parámetro típico Efecto
Learning rate bajo learning_rate=0.01–0.1 Pasos pequeños, correcciones suaves
Early stopping early_stopping / early_stopping_rounds Corta la secuencia cuando validación deja de mejorar
Árboles pequeños max_depth=2–4 (o num_leaves bajo en LightGBM) Limita la complejidad de cada corrector
Submuestreo de filas subsample<1 Aleatoriedad estilo bagging, descorrelaciona errores
Submuestreo de columnas colsample_bytree<1 (XGBoost/LightGBM) Feature bagging, como en Random Forest
Regularización en hojas reg_alpha, reg_lambda (XGBoost/LightGBM) El L1/L2 de 07-01 aplicado a los valores de las hojas

Fíjate en la última fila: la regularización de 07-01 reaparece dentro de los árboles. Las técnicas de este módulo no son compartimentos estancos, son piezas combinables. La receta defensiva estándar: learning rate bajo + early stopping + árboles poco profundos, y validar siempre con la CV del módulo 6.

Interpretación: feature importance y SHAP

Como el Random Forest, los modelos de boosting ofrecen feature_importances_ (en sklearn clásico y en XGBoost/LightGBM) con las mismas virtudes y los mismos sesgos que discutimos en 07-02: útiles como ranking global, peligrosas con features correlacionadas.

Para explicaciones serias, el estándar actual es SHAP (SHapley Additive exPlanations, biblioteca shap): asigna a cada feature de cada predicción individual una contribución con signo — "a este cliente el modelo le da un 78% de churn: la recencia de 45 días aporta +0.20, sus 2 incidencias +0.12 y su antigüedad de 5 años −0.08". Eso es oro para el equipo de retención de MercaFresh, que necesita saber no solo quién se va sino por qué. No lo desarrollamos aquí — es materia de interpretabilidad avanzada y reaparecerá en el contexto ético de 08-04 —, pero conviene que sepas que existe y que se lleva especialmente bien con los modelos de árboles.

Errores Comunes y Consejos

  • Subir n_estimators "porque más es mejor". Cierto en Random Forest, falso en boosting: aquí cada árbol extra añade complejidad. Fija un tope alto y deja que el early stopping decida.
  • Ajustar el learning rate sin tocar el número de árboles. Van acoplados: si bajas η de 0.1 a 0.01, multiplica el presupuesto de árboles por ~10 o el modelo quedará infraentrenado.
  • Usar árboles profundos como base. max_depth=10 en boosting suele ser una receta de sobreajuste; la fuerza está en la secuencia de correctores débiles, no en correctores fuertes.
  • Hacer early stopping contra el conjunto de test. El conjunto que decide cuándo parar participa en el entrenamiento a todos los efectos: es una fuga de las de 06-01. Usa una partición de validación o la validación interna del propio algoritmo, y reserva el test intacto.
  • Escalar las features "por si acaso". No hace daño, pero es innecesario: como todos los métodos de árboles, el boosting es insensible a la escala. Ahorra ese paso del pipeline (y recuerda que sí es obligatorio para SVM, K-NN o los modelos regularizados de 07-01).
  • Consejo: en proyectos reales, entrena primero un Random Forest (baseline fuerte, 07-02) y después un HistGradientBoosting/LightGBM con early stopping. Si el boosting no supera claramente al bosque, quédate con el bosque: menos hiperparámetros que mantener.

Ejercicios

  1. Boosting a mano, cuarta iteración. Repite el ejemplo numérico de la lección pero con learning rate η = 0.5: recalcula predicciones y residuos de las iteraciones 1 y 2 y añade una iteración 3 (asume que cada árbol sigue prediciendo la media del residuo de su grupo, alternando la división por frecuencia y la que separa dentro de cada grupo). ¿Se llega a residuo cero? ¿Qué ilustra esto sobre η?
  2. El acoplamiento η–n_estimators. Sobre el churn de MercaFresh, entrena GradientBoostingClassifier(max_depth=3, subsample=0.8) con las combinaciones (η=0.3, 50 árboles), (η=0.1, 150), (η=0.03, 500) y (η=0.3, 500). Evalúa F1 con la CV estratificada. ¿Confirman los resultados la tabla de la sección 3?
  3. Curva de early stopping. Con train_test_split (estratificado) separa un 20% de validación del churn. Entrena GradientBoostingClassifier(n_estimators=400, learning_rate=0.1, max_depth=3) y usa staged_predict_proba para calcular la log-loss en train y en validación tras cada árbol. Dibuja ambas curvas: ¿en qué iteración habría parado un early stopping con paciencia 20? ¿Qué forma de 06-05 reconoces?

Soluciones

  1. Con η = 0.5, la iteración 1 suma la mitad de la corrección: predicciones 95/95/65/65 y residuos +25/+5/−25/−5. La iteración 2 (corrección por grupos de frecuencia: media de residuos ±15, aplicada al 50%) deja predicciones 102.5/102.5/57.5/57.5 y residuos +17.5/−2.5/−17.5/+2.5; la iteración 3 sigue reduciendo pero no llega a cero. Ilustración: con η < 1 el modelo se aproxima asintóticamente al ajuste perfecto en train — nunca lo clava de golpe —, y eso da margen para detener la secuencia (early stopping) en el punto de mejor generalización antes de memorizar el ruido.
  2. Las tres primeras combinaciones (presupuesto equilibrado η·n_estimators ≈ 15) dan F1 similares, con ligera ventaja habitual para η bajo + muchos árboles y menor desviación entre pliegues. La cuarta (η=0.3 con 500 árboles, presupuesto 150) rinde peor en validación pese a ser la que menor error de train tiene: overfitting puro. Confirma la tabla: lo que importa es el producto equilibrado, y pasarse de presupuesto pasa factura.
  3. La log-loss de train desciende monótonamente (el boosting siempre puede seguir limando residuos). La de validación baja, toca mínimo — típicamente entre las iteraciones 80 y 200 con estos datos — y luego asciende suavemente: la silueta clásica de la curva de validación de 06-05, ahora con "número de árboles" en el eje x. El early stopping con paciencia 20 habría cortado unas 20 iteraciones después del mínimo, quedándose con el modelo de la mejor iteración. Este ejercicio es exactamente lo que early_stopping=True automatiza por dentro.

Conclusión

El gradient boosting materializa la promesa del boosting: una predicción inicial trivial más una cadena de árboles enanos, cada uno entrenado sobre los residuos del conjunto anterior, con el learning rate encogiendo cada paso — descenso de gradiente en el espacio de las predicciones. Has visto sus dos mandos acoplados (η y n_estimators), sus implementaciones por orden de modernidad (clásica de sklearn, HistGradientBoosting, XGBoost y LightGBM), su duelo con Random Forest sobre el churn de MercaFresh y, sobre todo, su carácter: el algoritmo más potente en datos tabulares es también el más propenso a memorizar, y por eso viaja rodeado de frenos — learning rate bajo, early stopping, árboles pequeños, submuestreo y la regularización de 07-01 aplicada a sus hojas. Con árboles y ensembles hemos llegado muy lejos partiendo de modelos sencillos; la siguiente lección explora la otra gran vía hacia la potencia: apilar capas de neuronas hasta construir redes profundas, el deep learning que la MLP de 04-07 dejó anunciado.

Curso de Machine Learning

Módulo 1: Introducción al Machine Learning

Módulo 2: Fundamentos de Estadística y Probabilidad

Módulo 3: Preprocesamiento de Datos

Módulo 4: Algoritmos de Machine Learning Supervisado

Módulo 5: Algoritmos de Machine Learning No Supervisado

Módulo 6: Evaluación y Validación de Modelos

Módulo 7: Técnicas Avanzadas y Optimización

Módulo 8: Implementación y Despliegue de Modelos

Módulo 9: Proyectos Prácticos

Módulo 10: Recursos Adicionales

© Copyright 2026. Todos los derechos reservados