# machine-learning-clasico-con-python
Machine Learning con scikit-learn y XGBoost
Volver a la portada del cursoEnsambles: 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):
| paso | residuos | lo que dice el árbol | predicción | MAE (1500 canciones) |
|---|---|---|---|---|
| inicio | — | — | 48.4 para todas | 9.36 |
| 1 | −2.4, −1.4, −0.4, −1.4, −7.4 | −10.1, 1.6, 1.6, −10.1, −10.1 | 43.3, 49.2, 49.2, 43.3, 43.3 | 7.92 |
| 2 | 2.7, −2.2, −1.2, 3.7, −2.3 | 0.1, 0.1, 7.4, 0.1, 7.4 | 43.4, 49.2, 52.9, 43.4, 47.1 | 6.86 |
| 3 | 2.6, −2.2, −4.9, 3.6, −6.1 | −3.3, 3.1, −3.3, −3.3, −3.3 | 41.7, 50.8, 51.3, 41.7, 45.4 | 6.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 Forest | Gradient boosting | |
|---|---|---|
| Entrenamiento | árboles en paralelo | árboles en serie |
| Árboles | profundos, se promedian | cortos, se suman |
| Sensibilidad a hiperparámetros | baja | alta (tasa, árboles, profundidad) |
| Más árboles | no sobreajusta | puede sobreajustar |
Trampas comunes
- Subir
n_estimatorssin bajarlearning_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.