Módulo 3 · Aprendizaje supervisado
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.
| columna | unidad | contenido |
|---|---|---|
distancia_km | km | distancia del almacén al domicilio |
peso_kg | kg | peso del paquete |
hora | h | hora de salida del almacén, de 8 a 21 |
carga_pct | % | ocupación del almacén al salir |
lluvia_mm | mm | lluvia 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ódigo
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.
print(pedidos.head(4))
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.
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))
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.
| medida | valor |
|---|---|
| 10 índices entre 0 y 9, ordenados | [0 0 0 1 2 3 5 6 8 8] |
| filas distintas en una muestra de las 1800 | 1125 |
| veces que sale la fila más repetida | 5 |
| fracción dentro, promedio de 1000 muestras | 0.632 |
| fracción fuera de bolsa, promedio de 1000 muestras | 0.368 |
(1 − 1/m) ** m con m = 1800 | 0.368 |
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.
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])
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?
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}")
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ódigo
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.
| modelo | exactitud de entrenamiento | exactitud de validación |
|---|---|---|
| árbol profundo | 1.000 | 0.642 |
| bagging de profundos | 1.000 | 0.737 |
Los 100 árboles de bolsa predicen la misma clase en el 2.2 % de los pedidos de validación.
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.
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.
| modelo | exactitud de entrenamiento | exactitud de validación | columnas |
|---|---|---|---|
| árbol profundidad 1 | 0.684 | 0.658 | 1 |
| bagging profundidad 1 | 0.684 | 0.658 | 1 |
| boosting profundidad 1 | 0.769 | 0.733 | 11 |
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.
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))
La salida esperada está oculta.
Las dos formas de combinar árboles atacan errores distintos:
| bagging | boosting | |
|---|---|---|
| orden | en paralelo | en secuencia |
| relación | árboles independientes | cada árbol depende de los anteriores |
| árbol base | profundo | pequeño |
| error que reduce | varianza | sesgo |
| predicción | promedio de los árboles | suma 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?
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))
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?
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))
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)?
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))
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.
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.
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?
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_)
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?
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}")
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?
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))
La salida esperada está oculta.
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.
Cambiar el modo, el learning rate o la profundidad vuelve a la ronda 0, con F igual a la media de entrenamiento.
Práctica
Traza guiada
Calcula a mano un bagging y un boosting de árboles de profundidad 1 con los mismos cuatro puntos.
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:
- 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), conrandom_state=0en todos los modelos - el
oob_score_del bagging de árboles sin límite - las columnas y los umbrales de los cortes del bagging de profundidad 1
- 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 defit, faltóoob_score=True. Conoob_score=Trueybootstrap=False, ninguna fila queda fuera de bolsa yfitlanza unValueError. - 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
HistGradientBoostingClassifiersobreajusta con 10 000 filas o menos, revisan_iter_: conearly_stopping="auto"no hubo parada temprana. Pasaearly_stopping=Truey 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
- Bagging en scikit-learn: muestras bootstrap,
max_samples,max_featuresy estimación fuera de bolsa. BaggingClassifier:estimator,n_estimators,oob_score,oob_score_yestimators_samples_.- Gradient boosting en scikit-learn: árboles sobre el gradiente de la pérdida,
learning_rate,HistGradientBoostingClassifiery parada temprana.