Módulo 3 · Aprendizaje supervisado

Lección 7 de 9

Bagging y boosting

En esta páginaEl problema

El problema

Una tienda en línea registró 3000 pedidos sintéticos. Cada pedido tiene cinco columnas numéricas, una zona de entrega y la etiqueta tarde, que vale 1 si el pedido llegó después de la hora prometida.

columnaunidadcontenido
distancia_kmkmdistancia del almacén al domicilio
peso_kgkgpeso del paquete
horahhora de salida del almacén, de 8 a 21
carga_pct%ocupación del almacén al salir
lluvia_mmmmlluvia registrada durante la entrega
zona—una de 25 zonas, de Z01 a Z25
tarde—1 si llegó tarde y 0 si llegó a tiempo

La celda plegada genera la tabla pedidos con una semilla fija.

Mostrar códigoOcultar códigoPython
Python 3 · ejemplo reproducible
import numpy as np
import pandas as pd

rng = np.random.default_rng(0)
n = 3000
zonas = [f"Z{i:02d}" for i in range(1, 26)]
pedidos = pd.DataFrame(
    {
        "distancia_km": np.round(rng.gamma(2.0, 4.0, n), 1),
        "peso_kg": np.round(rng.gamma(1.5, 2.0, n), 1),
        "hora": rng.integers(8, 22, n),
        "carga_pct": rng.integers(20, 101, n),
        "lluvia_mm": np.round(
            np.where(rng.random(n) < 0.7, 0, rng.exponential(6, n)), 1
        ),
        "zona": pd.Categorical(rng.choice(zonas, n), categories=zonas),
    }
)
efecto_zona = rng.normal(0, 1.2, len(zonas))
logit = (
    -2.5
    + 0.12 * pedidos["distancia_km"]
    + 2.0 * pedidos["hora"].isin([13, 14, 18, 19])
    + 2.5 * (pedidos["carga_pct"] > 80)
    + 0.15 * pedidos["distancia_km"] * (pedidos["lluvia_mm"] > 3)
    + efecto_zona[pedidos["zona"].cat.codes]
)
pedidos["tarde"] = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)

pedidos tiene 3000 filas, una por pedido, con las siete columnas de la tabla. zona tiene dtype category.

Python 3 · ejemplo reproducible
print(pedidos.head(4))
Salida esperada

La salida esperada está oculta.

Dos llamadas a train_test_split con test_size=600 dividen la tabla en entrenamiento, validación y prueba. stratify mantiene en cada parte la misma proporción de pedidos tarde. La prueba queda apartada.

Python 3 · ejemplo reproducible
from sklearn.model_selection import train_test_split

X = pedidos.drop(columns="tarde")
y = pedidos["tarde"]
X_resto, X_prueba, y_resto, y_prueba = train_test_split(
    X, y, test_size=600, stratify=y, random_state=0
)
X_ent, X_val, y_ent, y_val = train_test_split(
    X_resto, y_resto, test_size=600, stratify=y_resto, random_state=0
)
print(len(X_ent), len(X_val), len(X_prueba), round(y.mean(), 3))
Salida esperada

La salida esperada está oculta.

Los modelos de esta lección combinan muchos árboles de decisión. Unos promedian árboles entrenados con muestras distintas de las filas. Otros suman árboles en secuencia, y cada uno corrige lo que dejaron los anteriores.

Bootstrap y fuera de bolsa

El bosque aleatorio de «Árboles, vecinos y ensembles» entrenó cada árbol con una muestra con reemplazo de las filas. Esa muestra se llama muestra bootstrap: toma m filas al azar de las m filas de entrenamiento, y cada fila puede salir varias veces o ninguna. El bagging (de bootstrap aggregating) entrena un modelo con cada muestra bootstrap y promedia sus predicciones. Las filas que una muestra no incluyó quedan fuera de bolsa para el modelo entrenado con ella.

Con NumPy, rng.integers(0, m, m) toma m índices entre 0 y m − 1, con reemplazo: es una muestra bootstrap de m filas.

Pregunta. Una muestra bootstrap de 10 filas toma 10 índices entre 0 y 9. ¿Cuántos índices distintos esperas? En una muestra bootstrap de las 1800 filas de entrenamiento, ¿qué fracción de las filas saldrá al menos una vez?

Ver resultado

La columna «medida» dice qué se contó en las muestras bootstrap y la columna «valor» da el resultado. La primera fila usa la muestra de 10 índices; las cuatro siguientes, muestras de las 1800 filas de entrenamiento; la última es la fórmula que anticipa la fracción fuera de bolsa. Por ejemplo, la fila «veces que sale la fila más repetida» con 5 dice que, en esa muestra de 1800 índices, una misma fila aparece 5 veces.

medidavalor
10 índices entre 0 y 9, ordenados[0 0 0 1 2 3 5 6 8 8]
filas distintas en una muestra de las 18001125
veces que sale la fila más repetida5
fracción dentro, promedio de 1000 muestras0.632
fracción fuera de bolsa, promedio de 1000 muestras0.368
(1 − 1/m) ** m con m = 18000.368
Lectura. De 10 índices salieron 7 distintos: el 0 aparece tres veces, el 8 dos veces, y el 4, el 7 y el 9 no aparecen. Con 1800 filas, la muestra contiene 1125 filas distintas y una de ellas sale 5 veces. En promedio, una muestra bootstrap contiene el 63.2 % de las filas y deja fuera de bolsa el 36.8 %. La cuenta lo explica: en cada extracción, una fila dada no sale con probabilidad 1 − 1/m. Tras m extracciones independientes, no sale nunca con probabilidad (1 − 1/m) ** m, que con m grande se acerca a 1/e ≈ 0.368.

DecisionTreeClassifier y BaggingClassifier reciben solo números. make_column_transformer aplica OneHotEncoder a zona y la convierte en 25 columnas de ceros y unos. remainder="passthrough" deja las cinco columnas numéricas como están. El resultado son X_ent_num y X_val_num.

Python 3 · ejemplo reproducible
from sklearn.compose import make_column_transformer
from sklearn.preprocessing import OneHotEncoder

a_numeros = make_column_transformer(
    (OneHotEncoder(sparse_output=False), ["zona"]),  # matriz densa, no dispersa
    remainder="passthrough",
    verbose_feature_names_out=False,  # nombres sin prefijo del transformador
)
X_ent_num = a_numeros.fit_transform(X_ent)
X_val_num = a_numeros.transform(X_val)
print("columnas:", X_ent_num.shape[1])
Salida esperada

La salida esperada está oculta.

BaggingClassifier entrena 100 copias de DecisionTreeClassifier() sin límite de profundidad, cada una con su muestra bootstrap, y promedia sus probabilidades. estimators_samples_ guarda los índices de cada muestra. Con oob_score=True, cada fila de entrenamiento se predice solo con los árboles que no la vieron, y oob_score_ es la exactitud de esas predicciones.

Pregunta. ¿La exactitud de entrenamiento del bagging quedará cerca de 1? ¿oob_score_ quedará más cerca de la exactitud de entrenamiento o de la de validación?

Python 3 · ejemplo reproducible
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier

bolsa = BaggingClassifier(
    estimator=DecisionTreeClassifier(),
    n_estimators=100,
    oob_score=True,
    random_state=0,
).fit(X_ent_num, y_ent)
distintas = [np.unique(s).size / len(X_ent) for s in bolsa.estimators_samples_]

print(f"fracción de filas distintas por árbol: {np.mean(distintas):.3f}")
print(f"exactitud de entrenamiento: {bolsa.score(X_ent_num, y_ent):.3f}")
print(f"oob_score_: {bolsa.oob_score_:.3f}")
print(f"exactitud de validación: {bolsa.score(X_val_num, y_val):.3f}")
Salida esperada

La salida esperada está oculta.

Varianza y sesgo

En «Underfitting y overfitting», el sobreajuste y el subajuste se diagnosticaron comparando el error de entrenamiento con el de validación. La varianza de un modelo mide cuánto cambian sus predicciones si se entrena con otro conjunto de filas del mismo origen; las muestras bootstrap simulan esos conjuntos.

Los 100 árboles de bolsa son versiones del mismo árbol entrenadas con muestras distintas. bolsa.estimators_ los guarda. La función exactitudes devuelve la exactitud de un modelo en entrenamiento y en validación. La función unanimes reúne la predicción de cada árbol de estimators_ y cuenta en qué fracción de los pedidos de validación todos los árboles de un ensemble predicen la misma clase.

Mostrar códigoOcultar códigoPython
Python 3 · ejemplo reproducible
def exactitudes(modelo):
    return modelo.score(X_ent_num, y_ent), modelo.score(X_val_num, y_val)


def unanimes(ensemble):
    # una fila por árbol y una columna por pedido
    votos = np.array([t.predict(X_val_num) for t in ensemble.estimators_])
    # mínimo igual al máximo: todos los árboles votaron la misma clase
    return (votos.min(axis=0) == votos.max(axis=0)).mean()

La tabla compara bolsa con un solo árbol sin límite de profundidad, DecisionTreeClassifier(random_state=0), entrenado con todas las filas.

Pregunta. El bagging ya dio 0.737 en validación. ¿El árbol solo quedará arriba o abajo, y por cuántos pedidos? ¿En qué fracción de los pedidos de validación coincidirán los 100 árboles?

Ver resultado

Cada fila es un modelo. Las columnas dan su exactitud en las 1800 filas con las que se entrenó y en los 600 pedidos de validación. Por ejemplo, la fila del árbol profundo dice que acierta todas sus filas de entrenamiento y 0.642 de los pedidos de validación. La frase de abajo cuenta en cuántos pedidos de validación votan igual los 100 árboles del bagging.

modeloexactitud de entrenamientoexactitud de validación
árbol profundo1.0000.642
bagging de profundos1.0000.737

Los 100 árboles de bolsa predicen la misma clase en el 2.2 % de los pedidos de validación.

Lectura. Los dos modelos aciertan todas las filas de entrenamiento, así que esa columna no los distingue. En validación, el árbol solo acierta 0.642 y el bagging 0.737: 57 pedidos más de 600. Que los 100 árboles casi nunca coincidan indica que cada muestra bootstrap produce un árbol muy distinto: el árbol profundo tiene varianza alta. Cada árbol falla en pedidos distintos, y el promedio cancela buena parte de esos errores. Promediar reduce la varianza sin cambiar el árbol base.

Un árbol de profundidad 1 hace una sola pregunta. GradientBoostingClassifier(max_depth=1) suma 100 árboles de profundidad 1 en secuencia, y cada uno se entrena para corregir lo que dejaron los anteriores. La celda entrena el árbol de profundidad 1 solo, un bagging de 100 árboles de profundidad 1 y ese boosting.

Python 3 · ejemplo reproducible
from sklearn.ensemble import GradientBoostingClassifier

arbol_d1 = DecisionTreeClassifier(max_depth=1, random_state=0).fit(X_ent_num, y_ent)
bolsa_d1 = BaggingClassifier(
    estimator=DecisionTreeClassifier(max_depth=1), n_estimators=100, random_state=0
).fit(X_ent_num, y_ent)
boosting_d1 = GradientBoostingClassifier(max_depth=1, random_state=0).fit(
    X_ent_num, y_ent
)

La tabla compara sus exactitudes y cuenta cuántas columnas distintas usan los cortes de cada modelo.

Pregunta. ¿La exactitud de validación del árbol de profundidad 1 subirá con bagging? ¿Y con boosting? ¿Qué ensemble usará más columnas?

Ver resultado

Cada fila es un modelo. Las dos columnas de exactitud se miden en los 1800 pedidos de entrenamiento y en los 600 de validación, y «columnas» cuenta cuántas columnas distintas de X_ent_num usan los cortes de sus árboles. Por ejemplo, la fila del boosting dice que acierta 0.733 en validación y que sus 100 árboles cortan en 11 columnas distintas.

modeloexactitud de entrenamientoexactitud de validacióncolumnas
árbol profundidad 10.6840.6581
bagging profundidad 10.6840.6581
boosting profundidad 10.7690.73311
Lectura. El árbol de profundidad 1 acierta 0.658 en validación, con un entrenamiento apenas mayor: subajusta. El bagging de 100 árboles de profundidad 1 da las mismas exactitudes y, como el árbol solo, corta en una sola columna. El boosting sube la validación a 0.733 con árboles del mismo tamaño. Sus cortes usan 11 columnas: cada árbol ve lo que faltó y hace otra pregunta.

En un árbol ajustado, tree_.feature[0] es la posición de la columna del primer corte y tree_.threshold[0] es su umbral. get_feature_names_out da el nombre de cada posición. El boosting guarda sus árboles en estimators_, una matriz con una fila por ronda; en regresión y en clasificación binaria cada fila tiene un solo árbol, en la columna 0. La celda muestra el corte del árbol solo, las columnas que usa el bagging, en qué fracción de los pedidos de validación votan igual sus 100 árboles y las columnas que usa el boosting.

Python 3 · ejemplo reproducible
nombres = a_numeros.get_feature_names_out()
corte = arbol_d1.tree_
print("corte del árbol solo:", nombres[corte.feature[0]], corte.threshold[0])
columnas_bolsa = {nombres[t.tree_.feature[0]] for t in bolsa_d1.estimators_}
print("columnas del bagging:", sorted(columnas_bolsa))
print(f"pedidos de validación con los 100 árboles de acuerdo: {unanimes(bolsa_d1):.3f}")
columnas_boosting = {nombres[t.tree_.feature[0]] for t in boosting_d1.estimators_[:, 0]}
print("columnas del boosting:", sorted(columnas_boosting))
Salida esperada

La salida esperada está oculta.

Las dos formas de combinar árboles atacan errores distintos:

baggingboosting
ordenen paraleloen secuencia
relaciónárboles independientescada árbol depende de los anteriores
árbol baseprofundopequeño
error que reducevarianzasesgo
predicciónpromedio de los árbolessuma de las correcciones

El bosque aleatorio de «Árboles, vecinos y ensembles» es bagging de árboles profundos que, además, sortea un subconjunto de columnas en cada división.

Árboles sobre residuos

El gradient boosting construye la predicción como una suma. Empieza con una constante y agrega árboles pequeños, uno por ronda. Cada árbol nuevo no predice la etiqueta: predice lo que le falta al modelo actual. En regresión con error cuadrático, eso es el residuo y − F, donde F es la predicción acumulada. La tasa de aprendizaje (learning_rate) multiplica la salida de cada árbol antes de sumarla.

Ocho estudiantes registraron sus horas de práctica semanal y su puntaje en un simulacro. La constante inicial es la media de los puntajes: np.full la repite en un arreglo de ocho valores. DecisionTreeRegressor es la versión de regresión del árbol de decisión, y aquí se ajusta a los residuos.

Pregunta. ¿Qué signo tendrán los residuos de los cuatro estudiantes con menos horas? Un árbol de profundidad 1 hace una sola pregunta: ¿entre qué horas pondrá el corte?

Python 3 · ejemplo reproducible
from sklearn.tree import DecisionTreeRegressor

horas = np.arange(1, 9, dtype=float).reshape(-1, 1)  # forma (8, 1): scikit-learn espera una matriz de columnas
puntaje = np.array([52, 55, 61, 60, 74, 78, 77, 90], dtype=float)

f0 = np.full(len(puntaje), puntaje.mean())
residuo = puntaje - f0
arbol_1 = DecisionTreeRegressor(max_depth=1, random_state=0).fit(horas, residuo)

print(f0[0])
print(residuo.round(2))
print(arbol_1.tree_.threshold[0], np.unique(arbol_1.predict(horas)).round(2))
Salida esperada

La salida esperada está oculta.

La ronda se repite: se calculan los residuos de la predicción nueva y otro árbol de profundidad 1 los ajusta.

Pregunta. ¿El RMSE de entrenamiento bajará con cada árbol?

Python 3 · ejemplo reproducible
tasa = 0.5
f = f0.copy()
print(f"árbol 0: rmse {np.sqrt(np.mean((puntaje - f) ** 2)):.2f}")
for ronda in range(1, 4):
    arbol = DecisionTreeRegressor(max_depth=1, random_state=0).fit(horas, puntaje - f)
    f = f + tasa * arbol.predict(horas)
    rmse = np.sqrt(np.mean((puntaje - f) ** 2))
    print(f"árbol {ronda}: corte en {arbol.tree_.threshold[0]}  rmse {rmse:.2f}")
print(f.round(2))
Salida esperada

La salida esperada está oculta.

GradientBoostingRegressor de scikit-learn construye n_estimators árboles de profundidad max_depth. La celda siguiente lo entrena con 3 árboles, tasa 0.5 y profundidad 1, y compara su predicción con f. np.allclose compara dos arreglos y tolera diferencias de redondeo.

Pregunta. ¿La suma manual de tres árboles coincidirá con GradientBoostingRegressor(n_estimators=3, learning_rate=0.5, max_depth=1)?

Python 3 · ejemplo reproducible
from sklearn.ensemble import GradientBoostingRegressor

gbr = GradientBoostingRegressor(
    n_estimators=3, learning_rate=0.5, max_depth=1, random_state=0
).fit(horas, puntaje)
print(gbr.predict(horas).round(2))
print(np.allclose(gbr.predict(horas), f))
Salida esperada

La salida esperada está oculta.

El residuo tiene otra lectura. Con la pérdida ½(y − F)², el gradiente negativo respecto a F es y − F. Cada árbol ajusta el gradiente negativo de la pérdida, y la tasa fija el tamaño del paso. Con otra pérdida cambia lo que ajusta cada árbol, pero el procedimiento es el mismo.

Tasa y parada temprana

En clasificación binaria, el boosting suma árboles en la escala del logit y convierte esa suma en probabilidad con la sigmoide. La pérdida es la pérdida logarítmica: el promedio de −log de la probabilidad que el modelo dio a la clase real. Un modelo que siempre dice 0.5 tiene pérdida 0.693. Un modelo seguro y equivocado tiene pérdida grande. Con esta pérdida, el gradiente negativo respecto al logit es y − p: cada árbol nuevo ajusta la diferencia entre la etiqueta y la probabilidad actual.

El preprocesador de la regresión logística aplica one-hot a zona y escala las columnas numéricas con StandardScaler; make_pipeline lo encadena con el modelo. HistGradientBoostingClassifier acepta X_ent tal como viene: reconoce zona por su dtype category; la lección siguiente explica cómo la trata.

Python 3 · ejemplo reproducible
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

numericas = ["distancia_km", "peso_kg", "hora", "carga_pct", "lluvia_mm"]
preproceso = make_column_transformer(
    (OneHotEncoder(), ["zona"]), (StandardScaler(), numericas)
)
logistica = make_pipeline(preproceso, LogisticRegression(max_iter=1000))
por_defecto = HistGradientBoostingClassifier(random_state=0)

log_loss calcula esa pérdida. La función perdida se la aplica a la probabilidad de la clase 1, la segunda columna de predict_proba.

Python 3 · ejemplo reproducible
from sklearn.metrics import log_loss


def perdida(modelo, X_, y_):
    return log_loss(y_, modelo.predict_proba(X_)[:, 1])

roc_auc_score mide el ordenamiento, como en «Baseline y métricas». DummyClassifier() ignora las columnas y da a todos los pedidos la misma probabilidad. Después de fit, n_iter_ guarda cuántas rondas entrenó el boosting.

Pregunta. Ordena el baseline, la regresión logística y el boosting por defecto según su pérdida de validación. ¿La pérdida de entrenamiento del boosting quedará cerca de su pérdida de validación?

Python 3 · ejemplo reproducible
from sklearn.dummy import DummyClassifier
from sklearn.metrics import roc_auc_score

print("modelo        auc_val  pérdida_ent  pérdida_val")
for nombre, modelo in [
    ("baseline", DummyClassifier()),
    ("logística", logistica),
    ("boosting", por_defecto),
]:
    modelo.fit(X_ent, y_ent)
    auc = roc_auc_score(y_val, modelo.predict_proba(X_val)[:, 1])
    ent, val = perdida(modelo, X_ent, y_ent), perdida(modelo, X_val, y_val)
    print(f"{nombre:<12}  {auc:7.3f}  {ent:11.3f}  {val:11.3f}")
print("rondas del boosting:", por_defecto.n_iter_)
Salida esperada

La salida esperada está oculta.

Cada árbol corrige una fracción de lo que falta. staged_predict_proba devuelve las probabilidades después de cada ronda, así que una sola corrida permite medir la pérdida de validación en todas las rondas. Los árboles de esta celda tienen max_depth=3, y early_stopping=False obliga a entrenar todas las rondas de max_iter.

Pregunta. Con tasas 0.3, 0.1 y 0.03 y 500 rondas, ¿qué tasa necesitará más rondas para llegar a su mínimo de validación? Si sigues entrenando hasta la ronda 500, ¿cuál de las tres empeorará más?

Python 3 · ejemplo reproducible
print("tasa  mejor_ronda  pérdida_mínima  pérdida_ronda_500")
for tasa in [0.3, 0.1, 0.03]:
    modelo = HistGradientBoostingClassifier(
        learning_rate=tasa,
        max_iter=500,
        max_depth=3,
        early_stopping=False,
        random_state=0,
    ).fit(X_ent, y_ent)
    curva = [log_loss(y_val, p[:, 1]) for p in modelo.staged_predict_proba(X_val)]
    mejor = int(np.argmin(curva)) + 1  # la ronda 1 está en la posición 0
    print(f"{tasa:4}  {mejor:11d}  {min(curva):14.3f}  {curva[-1]:17.3f}")
Salida esperada

La salida esperada está oculta.

La parada temprana convierte esa curva en una regla. Después de cada ronda mide la pérdida en un conjunto de validación y se detiene si no mejora durante n_iter_no_change rondas seguidas. HistGradientBoostingClassifier acepta ese conjunto con X_val y y_val en fit. validation_score_ guarda la pérdida de validación con signo negativo; su posición 0 corresponde al modelo antes del primer árbol.

Pregunta. Con tasa 0.1, tope de 1000 rondas y n_iter_no_change=20, ¿en qué ronda se detendrá respecto al mínimo de la celda anterior? ¿La pérdida de validación del modelo final será igual al mínimo?

Python 3 · ejemplo reproducible
parada = HistGradientBoostingClassifier(
    learning_rate=0.1,
    max_iter=1000,
    max_depth=3,
    early_stopping=True,
    n_iter_no_change=20,
    random_state=0,
).fit(X_ent, y_ent, X_val=X_val, y_val=y_val)
mejor_ronda = int(np.argmax(parada.validation_score_))

print("rondas entrenadas:", parada.n_iter_)
print("mejor ronda:", mejor_ronda)
print("pérdida de validación:", round(perdida(parada, X_val, y_val), 3))
Salida esperada

La salida esperada está oculta.

Explorador

Bagging, boosting y parada temprana

Elige profundidad 1, agrega unos 30 árboles en modo Boosting y luego en modo Bagging, y compara la forma de la predicción F y el RMSE de validación.

Qué probar

Después prueba Bagging con árboles sin límite y compara su RMSE de validación con el de un solo árbol. En Boosting con profundidad 3, marca en qué ronda llega a su mínimo el RMSE de validación con tasa 0.3 y con tasa 1.

Parámetros
Modo
Profundidad del árbol

Cambiar el modo, el learning rate o la profundidad vuelve a la ronda 0, con F igual a la media de entrenamiento.

Ronda 0 con tasa 0.30 y profundidad 1: F es la media de entrenamiento, 2.87, en todo el eje. RMSE de entrenamiento 0.93 y de validación 0.84. El árbol 1, abajo, corta en x = 4.47.

Práctica

Traza guiada

Calcula a mano un bagging y un boosting de árboles de profundidad 1 con los mismos cuatro puntos.

Python 3 · ejemplo reproducible
x_traza = np.array([1.0, 2.0, 3.0, 4.0]).reshape(-1, 1)
y_traza = np.array([2.0, 2.0, 6.0, 10.0])
muestras_traza = [[0, 0, 1, 3], [1, 2, 3, 3]]

Para el bagging, cada lista de muestras_traza es una muestra bootstrap de índices. Escribe las filas fuera de bolsa de cada muestra, ajusta un árbol de regresión de profundidad 1 a cada una y promedia las dos predicciones. Para el boosting, calcula dos rondas con tasa 0.5 sobre los cuatro puntos: residuos, corte, valores de hoja y predicción nueva.

Resultado esperado: los cortes del bagging quedan en 3.0 y 3.5, la fila 2 queda fuera de la primera muestra y la fila 0 fuera de la segunda, y el promedio es [3, 3, 3, 10]. El boosting corta en 2.5 y en 3.5 y predice [2.917, 2.917, 5.917, 8.25]. Comprueba el bagging con DecisionTreeRegressor(max_depth=1).fit(x_traza[i], y_traza[i]) para cada lista i, y el boosting con GradientBoostingRegressor(n_estimators=2, learning_rate=0.5, max_depth=1).fit(x_traza, y_traza), predict y estimators_. Si un corte no coincide, compara la suma de residuos al cuadrado de las divisiones posibles, contando cada fila repetida tantas veces como aparece.

Paciencia de la parada temprana

Repite la celda de parada temprana con n_iter_no_change igual a 5 y a 100. Antes de procesar, predice para cada valor la mejor ronda, las rondas entrenadas y si la pérdida de validación del modelo final sube o baja respecto a la paciencia de 20. Resultado esperado: con paciencia 5 se detiene en la ronda 65, su mejor ronda es 60 y la pérdida de validación sube a 0.481; con 100 entrena 193 rondas, su mejor ronda sigue siendo 93 y la pérdida sube a 0.485. Explica ambos resultados con la curva de la tasa 0.1 y con las rondas extra que conserva HistGradientBoostingClassifier.

Varianza y sesgo en dos lunas

Genera make_moons(n_samples=600, noise=0.3, random_state=0) y separa la mitad como validación con stratify y random_state=0. Produce:

  1. la exactitud de entrenamiento y de validación de un árbol sin límite, un bagging de 100 árboles sin límite, un árbol de profundidad 1, un bagging de 100 árboles de profundidad 1 y GradientBoostingClassifier(max_depth=1), con random_state=0 en todos los modelos
  2. el oob_score_ del bagging de árboles sin límite
  3. las columnas y los umbrales de los cortes del bagging de profundidad 1
  4. una frase que diga qué ensemble reduce varianza y cuál reduce sesgo en estos datos

Resultado esperado en validación: 0.867 para el árbol sin límite y 0.897 para su bagging, 0.760 para el árbol de profundidad 1, 0.800 para su bagging y 0.893 para el boosting. El oob_score_ del bagging sin límite es 0.923. Aquí el bagging de profundidad 1 sí mejora un poco. Explica la diferencia con los pedidos a partir de las columnas y los umbrales del punto 3.

Reto opcional

Programa el boosting de clasificación a mano con X_ent_num y X_val_num. Empieza con el logit de la proporción de positivos de y_ent. En cada una de 100 rondas, ajusta un DecisionTreeRegressor(max_depth=3) a y − p, suma 0.1 veces su predicción al logit y recalcula p con la sigmoide. Compara la pérdida de validación con la de HistGradientBoostingClassifier entrenado con X_ent_num, la misma tasa, profundidad y rondas, y early_stopping=False. Explica por qué no coinciden exactamente: revisa en la guía de scikit-learn cómo calcula el valor de cada hoja, qué hace min_samples_leaf y cómo agrupa los valores en intervalos (max_bins).

Errores que revisar

  • Si oob_score_ no existe después de fit, faltó oob_score=True. Con oob_score=True y bootstrap=False, ninguna fila queda fuera de bolsa y fit lanza un ValueError.
  • Si comparas un árbol profundo y su bagging con la exactitud de entrenamiento, los dos dan 1.000. La reducción de varianza solo se ve en filas que el modelo no vio: validación o fuera de bolsa.
  • Si un bagging de árboles poco profundos no supera al árbol solo, el error es sesgo y más árboles no lo corrigen. Usa árboles más profundos o boosting.
  • Si HistGradientBoostingClassifier sobreajusta con 10 000 filas o menos, revisa n_iter_: con early_stopping="auto" no hubo parada temprana. Pasa early_stopping=True y un conjunto de validación.
  • Si reportas la pérdida del mismo conjunto que eligió la ronda de parada, el número es optimista. Reporta un conjunto que ninguna decisión usó.

Cierre

Un compañero entrenó dos árboles con los mismos pedidos. Uno, de profundidad 2, acierta 0.70 en entrenamiento y 0.69 en validación. El otro, sin límite, acierta 1.00 en entrenamiento y 0.64 en validación. Escribe a cuál le aplicarías bagging y a cuál boosting, y qué medición esperas que cambie en cada caso.

Lista de comprobación

  • ¿Qué fracción de las filas queda fuera de una muestra bootstrap y por qué oob_score_ estima la exactitud sin un conjunto de validación?
  • ¿Por qué promediar árboles profundos sube la exactitud de validación y en los pedidos promediar árboles de profundidad 1 no?
  • ¿Qué ajusta cada árbol nuevo de un boosting con pérdida cuadrática y con pérdida logarítmica?
  • ¿Por qué una tasa menor necesita más rondas y qué pasa con la pérdida de validación después del mínimo?
  • ¿Qué agrega un bosque aleatorio al bagging de árboles?

Documentación oficial

Cargando progreso…