Saltar al contenido

# machine-learning-clasico-con-python

Machine Learning con scikit-learn y XGBoost

Volver a la portada del curso
Lectura (Markdown)

Ensambles: bagging, Random Forest y gradient boosting

Sección 5 · Árboles, boosting con XGBoost y ajuste de hiperparámetros

Ensambles: bagging, Random Forest y gradient boosting

Un solo árbol de decisión es fácil de leer, pero inestable: crecido sin límite memoriza el entrenamiento y en validación cruzada su AUC cae a 0.687 con los datos de PyRitmo. Los ensambles combinan muchos árboles para que sus errores se compensen. Hay dos familias: bagging, que entrena árboles en paralelo y promedia, y boosting, que los entrena en serie y cada uno corrige al anterior. El notebook arboles-y-ensambles reproduce todas las cifras.

Bagging y Random Forest

Bagging entrena cada árbol sobre una muestra bootstrap: tantas filas como hay en train, sorteadas con reemplazo. Cada muestra contiene alrededor del 63 % de canciones distintas. El Random Forest agrega un segundo azar: en cada corte solo puede elegir entre unas pocas columnas al azar (la raíz cuadrada de las 14 que produce pre: 3). Así los árboles son distintos entre sí, y el bosque promedia sus probabilidades.

from sklearn.ensemble import RandomForestClassifier

rf = make_pipeline(pre, RandomForestClassifier(n_estimators=300, min_samples_leaf=3,
                                               random_state=42))
rf.fit(X_tr, y_tr)
print(round(roc_auc_score(y_te, rf.predict_proba(X_te)[:, 1]), 3))

AUC test 0.856. Agregar árboles no sobreajusta: con 1, 10, 50 y 300 árboles el AUC da 0.737, 0.841, 0.857 y 0.856.

Gradient boosting paso a paso

El boosting arranca con una predicción constante (la media) y en cada iteración entrena un árbol pequeño sobre los residuos: lo que todavía falta corregir. La predicción avanza solo una fracción $\eta$ (el learning_rate) de lo que dice cada árbol. Tres iteraciones sobre popularidad con árboles de profundidad 2 y $\eta = 0.5$, para las cinco primeras canciones (popularidad real 46, 47, 48, 47 y 41):

pasoresiduoslo que dice el árbolpredicciónMAE (1500 canciones)
inicio——48.4 para todas9.36
1−2.4, −1.4, −0.4, −1.4, −7.4−10.1, 1.6, 1.6, −10.1, −10.143.3, 49.2, 49.2, 43.3, 43.37.92
22.7, −2.2, −1.2, 3.7, −2.30.1, 0.1, 7.4, 0.1, 7.443.4, 49.2, 52.9, 43.4, 47.16.86
32.6, −2.2, −4.9, 3.6, −6.1−3.3, 3.1, −3.3, −3.3, −3.341.7, 50.8, 51.3, 41.7, 45.46.23

Cada árbol predice el residuo promedio de su hoja, no el de cada canción: algunas canciones mejoran y otras empeoran en un paso, pero el error total baja en cada iteración (9.36, 7.92, 6.86, 6.23). GradientBoostingRegressor(n_estimators=3, learning_rate=0.5, max_depth=2) da exactamente las mismas predicciones.

¿Por qué «gradiente»? Con error cuadrático, el residuo es justamente el gradiente negativo de la pérdida: cada árbol da un paso de descenso de gradiente en el espacio de las predicciones. En clasificación se usa la log-loss, y los árboles se ajustan a su gradiente sobre la escala del logit. La predicción final es $F(x) = F_0(x) + \eta \sum_{m=1}^{M} h_m(x)$.

learning_rate, n_estimators y sobreajuste

learning_rate funciona como shrinkage: con pasos más cortos hacen falta más árboles. Por validación cruzada sobre train, 100 árboles con 0.1 dan AUC 0.849, pero 300 con 0.1 bajan a 0.840; 300 con 0.03 dan 0.851 y 1000 con 0.03 bajan a 0.838. Con 1.0, el modelo memoriza (0.805). Por eso los dos se ajustan juntos.

Para ver dónde empieza el sobreajuste, staged_predict_proba devuelve las probabilidades después de cada árbol:

gb = GradientBoostingClassifier(n_estimators=500, random_state=42)
gb.fit(pre.fit_transform(X_tr), y_tr)
auc = [roc_auc_score(y_te, p[:, 1]) for p in gb.staged_predict_proba(pre.transform(X_te))]

El AUC de test sube hasta 0.871 con 85 árboles y luego baja: 0.852 con 300 y 0.846 con 500, mientras el de train llega a 1.000. Esa curva se mira en validación para decidir cuántos árboles usar; aquí se muestra en test solo como ilustración. Con subsample=0.8 (boosting estocástico) cada árbol ve el 80 % de las filas y el AUC CV sube de 0.849 a 0.854; con 0.5 baja a 0.841.

HistGradientBoosting

HistGradientBoostingClassifier agrupa cada columna en a lo más 255 intervalos (binning) antes de buscar cortes, lo que lo hace mucho más rápido con tablas grandes. Maneja faltantes sin imputar, acepta columnas categóricas con categorical_features y, con early_stopping=True, aparta parte de train como validación y se detiene cuando deja de mejorar. Con genero como categoría nativa se detiene en 34 árboles con AUC test 0.845.

Random Forest frente a gradient boosting

Random ForestGradient boosting
Entrenamientoárboles en paraleloárboles en serie
Árbolesprofundos, se promediancortos, se suman
Sensibilidad a hiperparámetrosbajaalta (tasa, árboles, profundidad)
Más árbolesno sobreajustapuede sobreajustar

Trampas comunes

  • Subir n_estimators sin bajar learning_rate.
  • Elegir el número de árboles mirando el test.
  • Leer feature_importances_ como causa: favorece a las columnas continuas con muchos cortes.

XGBoost lleva esta misma idea más lejos: agrega regularización a la función objetivo, usa la segunda derivada de la pérdida para elegir cortes y trae early stopping con un conjunto de validación. Es el tema del video siguiente.

Recursos