57  Árboles de regresión y ensambles: bagging, bosques aleatorios y AdaBoost

Autor/a

Diego Villalba

Los dos capítulos anteriores terminaron con la misma advertencia: un árbol de decisión es inestable. Basta cambiar unos cuantos ejemplos para que cambie la raíz y, con ella, todo el árbol. En el lenguaje del capítulo introductorio, los árboles profundos tienen poco sesgo y mucha varianza.

Este capítulo convierte esa debilidad en una fortaleza. Si un árbol es ruidoso, entrenemos muchos árboles distintos y combinemos sus respuestas: los errores de unos compensan los de otros. Esa es la idea de los métodos de ensamble (Dietterich 2000), que con árboles como base están entre los clasificadores más eficaces para datos tabulares. Estudiamos dos familias:

Como varios de estos métodos se entienden mejor con una respuesta numérica, empezamos con los árboles de regresión, que además son la pieza básica del gradient boosting.

57.1 Árboles de regresión

Un árbol de regresión parte el espacio de predictoras igual que CART, pero en cada hoja predice un número: el promedio de las respuestas de los ejemplos que llegan a ella. El modelo es una función escalonada

\[ \hat{f}(\mathbf{x}) = \sum_{h=1}^{J} c_h\,\mathbb{1}[\mathbf{x} \in R_h], \qquad c_h = \frac{1}{|R_h|}\sum_{\mathbf{x}_i \in R_h} y_i, \tag{57.1}\]

donde \(R_1, \ldots, R_J\) son las regiones (hojas). El promedio es la constante que minimiza el error cuadrático dentro de cada hoja, y la impureza de un nodo es su suma de cuadrados \(\text{SSE} = \sum_i (y_i - \bar{y})^2\). Cada corte se elige para reducirla lo más posible.

57.1.1 Ejemplo manual: precio de laptops según la RAM

Laptop A B C D E F
RAM (GB) 4 4 8 8 16 16
Precio ($) 6 500 7 200 10 500 12 000 21 000 24 000
Tabla 57.1: Seis laptops.

Sin dividir, la predicción es el promedio, 13 533.33, y la suma de cuadrados es \(266.43 \times 10^6\). Hay dos cortes posibles:

Corte Izquierda (promedio) Derecha (promedio) SSE después Reducción
RAM ≤ 6 A, B (6 850) C, D, E, F (16 875) \(132.43 \times 10^6\) 50.3 %
RAM ≤ 12 A–D (9 050) E, F (22 500) \(\mathbf{25.23 \times 10^6}\) 90.5 %
Tabla 57.2: Los dos cortes candidatos.

Por ejemplo, para RAM ≤ 12: \((6500 - 9050)^2 + (7200 - 9050)^2 + (10500 - 9050)^2 + (12000 - 9050)^2 + (21000 - 22500)^2 + (24000 - 22500)^2 = 25.23 \times 10^6\). El árbol de profundidad 1 dice: «si la RAM es de 12 GB o menos, 9 050; si no, 22 500».

Todo lo visto en CART se aplica sin cambios: el crecimiento voraz, la poda de costo-complejidad (ahora con la suma de cuadrados como \(R(T)\)) y la tendencia a sobreajustar si se deja crecer el árbol hasta tener una observación por hoja. El laboratorio de bagging muestra este sobreajuste con 40 puntos.

57.2 ¿Por qué funciona combinar modelos?

57.2.1 Intuición: el voto de cinco jueces

Supongamos cinco clasificadores que aciertan cada uno el 70 % de las veces y cuyos errores son independientes. Si decidimos por mayoría, el ensamble acierta cuando al menos tres de los cinco aciertan:

\[ P(\text{mayoría correcta}) = \sum_{k=3}^{5}\binom{5}{k} 0.7^k\, 0.3^{5-k} = 0.309 + 0.360 + 0.168 = 0.837. \]

Con 25 clasificadores así, la exactitud de la mayoría sube a 0.98. Es el teorema del jurado de Condorcet: si cada juez es mejor que el azar y sus errores son independientes, la mayoría se vuelve casi infalible. Dos condiciones hacen el trabajo: que cada modelo sea mejor que el azar y que sus errores sean diversos. Si los cinco cometieran exactamente los mismos errores, votar no serviría de nada.

57.2.2 Formalización: la varianza de un promedio

Para predicciones numéricas, la misma idea se expresa con varianzas. Si \(B\) modelos tienen cada uno varianza \(\sigma^2\) y correlación \(\rho\) entre sí, la varianza de su promedio es

\[ \operatorname{Var}\Big(\frac{1}{B}\sum_{b=1}^{B} \hat{f}_b(\mathbf{x})\Big) = \rho\,\sigma^2 + \frac{1 - \rho}{B}\,\sigma^2. \tag{57.2}\]

Al aumentar \(B\), el segundo término desaparece, pero el primero no: la correlación entre modelos pone un piso a la reducción de varianza. Toda la estrategia de los bosques aleatorios consiste en bajar \(\rho\) sin subir demasiado \(\sigma^2\). Como promediar no cambia el valor esperado, el sesgo del ensamble es el de sus modelos: por eso se promedian árboles profundos, de poco sesgo y mucha varianza.

57.3 Bagging

Si tuviéramos muchos conjuntos de entrenamiento independientes, entrenaríamos un árbol en cada uno y los promediaríamos. Solo tenemos uno. Bagging (bootstrap aggregating), propuesto por Breiman (1996), simula esos conjuntos con muestras bootstrap: muestras de tamaño \(n\) tomadas con reemplazo del conjunto original.

57.3.1 Construcción paso a paso

Paso 1. Una muestra bootstrap. Con las seis laptops, un sorteo con reemplazo podría dar \(\{A, A, C, D, F, F\}\): la laptop A y la F aparecen dos veces, y la B y la E no aparecen.

Paso 2. Un árbol con esa muestra. El mejor corte vuelve a ser RAM ≤ 12, pero las hojas cambian: a la izquierda \((6500 + 6500 + 10500 + 12000)/4 = 8\,875\) y a la derecha \(24\,000\).

Paso 3. Los ejemplos fuera de la bolsa. B y E no participaron en este árbol, así que sirven para evaluarlo sin trampa. Sus errores son \(7\,200 - 8\,875 = -1\,675\) y \(21\,000 - 24\,000 = -3\,000\). Son los ejemplos fuera de la bolsa (out-of-bag, OOB).

Paso 4. Repetir y promediar. Con \(B\) muestras bootstrap se obtienen \(B\) árboles distintos. La predicción de bagging es el promedio (en regresión) o el voto mayoritario (en clasificación):

\[ \hat{f}_{\text{bag}}(\mathbf{x}) = \frac{1}{B}\sum_{b=1}^{B} \hat{f}^{*b}(\mathbf{x}). \tag{57.3}\]

57.3.2 El error fuera de la bolsa

La probabilidad de que una observación concreta no salga en ninguna de las \(n\) extracciones es

\[ \Big(1 - \frac{1}{n}\Big)^n \xrightarrow[n \to \infty]{} e^{-1} \approx 0.368. \tag{57.4}\]

Con \(n = 6\) es \((5/6)^6 = 0.335\). Es decir, cada árbol deja fuera alrededor de un tercio de los datos. Prediciendo cada observación solo con los árboles que no la vieron y promediando esos errores se obtiene el error OOB, una estimación del error de generalización casi gratuita, comparable a una validación cruzada, sin entrenar modelos adicionales.

57.4 Bosques aleatorios

En bagging los árboles son distintos, pero no tanto: si hay una variable muy predictiva, casi todos la usan en la raíz y sus predicciones están muy correlacionadas, de modo que el piso \(\rho\sigma^2\) de la Ecuación 57.2 es alto. Los bosques aleatorios de Breiman (2001) añaden una segunda fuente de azar: en cada nodo, el mejor corte se busca solo entre \(m\) variables elegidas al azar de las \(p\) disponibles. Así, otras variables tienen oportunidad de ocupar las preguntas importantes, los árboles se parecen menos y su promedio es más estable.

Los hiperparámetros principales son:

Hiperparámetro scikit-learn Valor habitual Efecto
Número de árboles \(B\) n_estimators 200–1000 Más árboles nunca sobreajustan; solo cuestan tiempo
Variables por nodo \(m\) max_features \(\sqrt{p}\) (clasificación), \(p/3\) (regresión) Menor \(m\): árboles menos correlacionados pero más débiles
Tamaño de hoja min_samples_leaf 1–5 Hojas más grandes: menos varianza por árbol
Profundidad max_depth sin límite Normalmente se dejan crecer
Tabla 57.3: Hiperparámetros de un bosque aleatorio.

Con \(m = p\) el bosque es exactamente bagging. Una propiedad práctica importante: aumentar \(B\) no produce sobreajuste; el error converge y basta elegir \(B\) suficientemente grande.

57.4.1 Importancia de las variables

Un bosque pierde la interpretabilidad de un árbol único, pero ofrece dos medidas de importancia:

  • Disminución media de impureza (MDI): la suma de las reducciones de Gini de cada variable en todos los árboles. Es gratuita, pero está sesgada a favor de variables numéricas o con muchas categorías, que tienen más cortes donde «encontrar» reducciones por azar (Strobl et al. 2007).
  • Importancia por permutación: se desordena al azar una columna en los datos de prueba (o en los OOB) y se mide cuánto empeora el desempeño. Si la variable importa, desordenarla destruye información; si no, el error casi no cambia. Es más fiable, aunque reparte la importancia entre variables muy correlacionadas.

57.5 Laboratorio: bagging de árboles de regresión

Cuarenta puntos generados con \(y = 2\sin x + 0.3x + \varepsilon\), con ruido de desviación 0.6 (varianza 0.36, el error irreducible). El estado inicial es un único árbol sin límite de profundidad: memoriza los datos. Al pasar a Bootstrap y aumentar el número de árboles, el promedio se suaviza.

Laboratorio · Bagging

¿Cuántos árboles ruidosos hacen falta para una buena predicción?

Cada línea tenue es un árbol entrenado con una muestra bootstrap; la línea gruesa es su promedio. Compara el error en prueba con el de un solo árbol y con el error irreducible.

Muestreo
Azar
ECM entrenamiento
ECM prueba (300 puntos)
ECM fuera de la bolsa
Error irreducible σ²0.360

Árboles y su promedio

entrenamientofunción realárbolespromedio

Error en prueba según el número de árboles

promedio de B árbolesun árbol (todos los datos)σ² irreducible

Guía del laboratorio.

  1. En el estado inicial, ¿por qué el ECM de entrenamiento es casi 0 y el de prueba ronda 0.62? Compara la escalera con la función real (verde).
  2. Predice antes de mover: con Sin remuestreo y \(B = 50\), ¿mejorará el error de prueba? Compruébalo y explica.
  3. Cambia a Bootstrap y sube \(B\) de 1 a 100. ¿Dónde se aplana la curva? ¿Llega al error irreducible?
  4. Con \(B = 100\), baja la profundidad a 1. ¿Por qué ahora el bagging casi no mejora al árbol único? Relaciónalo con la Ecuación 57.2 y con el sesgo.
  5. Compara el error fuera de la bolsa con el de prueba para varias semillas. ¿Es una buena estimación?

57.6 Boosting y AdaBoost

Bagging entrena modelos en paralelo y los trata por igual. Boosting los entrena en secuencia: cada modelo nuevo se concentra en los casos que los anteriores clasificaron mal, y al final se combinan con pesos según su calidad. La pregunta que lo originó fue teórica (Schapire 1990): ¿se puede convertir un clasificador débil, apenas mejor que el azar, en uno arbitrariamente preciso? AdaBoost (Freund y Schapire 1997) fue la respuesta práctica.

57.6.1 Construcción paso a paso

Diez puntos sobre una recta con etiquetas \(+1\) y \(-1\):

\(x\) 1 2 3 4 5 6 7 8 9 10
\(y\) + + + − − − + + + −
Tabla 57.4: Diez puntos que ningún corte único separa.

El clasificador débil será un tocón: un solo corte «\(x \le t\) ⇒ clase». Ningún tocón separa estos datos; el mejor comete 3 errores.

Ronda 1. Todos los pesos valen \(w_i = 1/10\). El mejor tocón es «\(x \le 3.5\) ⇒ +, si no −», que falla en \(x = 7, 8, 9\). Su error ponderado es \(\varepsilon_1 = 0.3\) y su peso en el ensamble es

\[ \alpha_1 = \tfrac{1}{2}\ln\frac{1 - \varepsilon_1}{\varepsilon_1} = \tfrac{1}{2}\ln\frac{0.7}{0.3} = 0.424. \]

Ahora se repesan los ejemplos: los mal clasificados se multiplican por \(e^{\alpha_1} = 1.528\) y los bien clasificados por \(e^{-\alpha_1} = 0.655\); después se normaliza para que sumen 1. Los tres errores pasan a pesar \(0.1667\) cada uno y los siete aciertos \(0.0714\). Observa que los mal clasificados suman ahora exactamente \(0.5\): para el siguiente tocón, el anterior es tan bueno como lanzar una moneda.

Ronda 2. Con esos pesos, el mejor tocón es «\(x \le 9.5\) ⇒ +, si no −», que falla en \(x = 4, 5, 6\): \(\varepsilon_2 = 3 \times 0.0714 = 0.214\) y \(\alpha_2 = \frac{1}{2}\ln(0.786/0.214) = 0.650\). Ese tocón casi no tendría sentido por sí solo; tiene sentido porque los puntos 7, 8 y 9 ahora pesan mucho.

Ronda 3. El mejor tocón es «\(x \le 6.5\) ⇒ −, si no +», que falla en \(x = 1, 2, 3\) y \(10\), ahora livianos: \(\varepsilon_3 = 0.182\) y \(\alpha_3 = 0.752\).

El ensamble. La predicción es el signo de la suma ponderada de los tres tocones:

\[ F(x) = 0.424\,h_1(x) + 0.650\,h_2(x) + 0.752\,h_3(x), \qquad \hat{y} = \operatorname{signo}(F(x)). \]

\(x\) \(h_1\) \(h_2\) \(h_3\) \(F(x)\) \(\hat{y}\) \(y\)
1–3 + + − \(0.424 + 0.650 - 0.752 = 0.321\) + +
4–6 − + − \(-0.424 + 0.650 - 0.752 = -0.526\) − −
7–9 − + + \(-0.424 + 0.650 + 0.752 = 0.978\) + +
10 − − + \(-0.424 - 0.650 + 0.752 = -0.321\) − −
Tabla 57.5: El ensamble de tres tocones clasifica bien los diez puntos.

Tres clasificadores que fallan, cada uno, tres o cuatro de diez puntos producen juntos un clasificador perfecto en entrenamiento. Ninguno de ellos podía dibujar la frontera con tres cambios de clase; su suma sí.

57.6.2 Formalización

Con etiquetas \(y_i \in \{-1, +1\}\), pesos iniciales \(w_i = 1/n\) y \(M\) rondas, AdaBoost repite:

  1. Entrenar el clasificador débil \(h_m\) minimizando el error ponderado \(\varepsilon_m = \sum_i w_i\,\mathbb{1}[h_m(\mathbf{x}_i) \neq y_i]\).
  2. Calcular \(\alpha_m = \frac{1}{2}\ln\frac{1 - \varepsilon_m}{\varepsilon_m}\), positivo si \(\varepsilon_m < 0.5\).
  3. Actualizar \(w_i \leftarrow w_i \exp\!\big(-\alpha_m\, y_i\, h_m(\mathbf{x}_i)\big)\) y normalizar.

La predicción es \(\operatorname{signo}\big(\sum_m \alpha_m h_m(\mathbf{x})\big)\). Friedman et al. (2000) mostraron que AdaBoost minimiza, paso a paso, la pérdida exponencial \(\sum_i e^{-y_i F(\mathbf{x}_i)}\) sobre modelos aditivos \(F\); cada ronda agrega el término que más la reduce. Esta lectura como optimización por etapas es la que generaliza el gradient boosting del capítulo siguiente a cualquier pérdida.

Dos consecuencias prácticas:

  • El error de entrenamiento cae exponencialmente rápido mientras cada \(\varepsilon_m\) esté por debajo de 0.5. Pero la pérdida exponencial castiga sin límite a los puntos muy mal clasificados, de modo que AdaBoost es sensible a etiquetas erróneas y atípicos: termina concentrando todo el peso en ellos.
  • Boosting reduce sobre todo el sesgo (combina modelos débiles, de mucho sesgo) y puede sobreajustar si se usan demasiadas rondas o modelos base demasiado fuertes. El número de rondas y la complejidad del modelo base se eligen con validación.

scikit-learn implementa la variante SAMME, que usa \(\alpha_m = \ln\frac{1 - \varepsilon_m}{\varepsilon_m}\) (el doble) y solo aumenta el peso de los errores; tras normalizar, los pesos y las predicciones son idénticos a los de la versión clásica.

57.7 Laboratorio: AdaBoost ronda por ronda

El tamaño de cada punto es proporcional a su peso al inicio de la ronda elegida. El modo Diez puntos reproduce las tablas anteriores. El modo Círculo usa 150 puntos de entrenamiento y 300 de prueba en el plano; la clase \(+1\) está dentro de un círculo y un 3 % de las etiquetas están invertidas; los tocones solo pueden hacer cortes verticales u horizontales.

Laboratorio · Boosting

¿Cómo aprenden juntos clasificadores que por separado fallan?

Avanza las rondas. Observa qué puntos ganan peso, qué tocón se elige y cómo cambia la frontera del ensamble.

Datos
Tocón de la ronda
Error ponderado ε
Peso α
Errores del ensamble

Pesos y ensamble

clase +1clase −1tocón de la rondaF(x) del ensamble

Error del ensamble por ronda

entrenamientopruebaronda actual

Rondas

Guía del laboratorio.

  1. Con Diez puntos, recorre las rondas 1 a 3 y comprueba \(\varepsilon\), \(\alpha\) y los pesos contra la construcción paso a paso.
  2. En la ronda 2, ¿por qué el tocón elegido deja mal a los puntos 4, 5 y 6? ¿Cuánto pesan en ese momento?
  3. Predice antes de mover: en la ronda 4, ¿qué tocón se elegirá? Pista: compara los pesos de los grupos.
  4. Cambia a Círculo. ¿Cuántas rondas hacen falta para que la región se parezca al círculo? ¿El error de prueba sigue bajando todo el tiempo?
  5. Localiza un punto con etiqueta invertida (un punto azul lejos del círculo o naranja dentro) y observa su tamaño en rondas avanzadas. ¿Qué dice esto de AdaBoost con datos ruidosos?

57.8 Bagging frente a boosting

Bagging y bosques aleatorios Boosting
Entrenamiento En paralelo, independiente En secuencia, dependiente
Modelo base Fuerte e inestable (árbol profundo) Débil (tocón, árbol poco profundo)
Qué reduce Varianza Sesgo (y algo de varianza)
Combinación Promedio o voto simple Suma ponderada
Riesgo de sobreajuste al agregar modelos No Sí: se controla el número de rondas
Ruido en las etiquetas Robusto Sensible (AdaBoost)
Ajuste de hiperparámetros Poco necesario Importante
Tabla 57.6: Comparación de las dos familias de ensambles.

Como regla práctica, un bosque aleatorio con valores por defecto es una referencia excelente y difícil de batir con poco esfuerzo; el boosting bien ajustado (capítulo siguiente) suele ganar por un margen pequeño cuando hay tiempo para validar sus hiperparámetros.

57.9 Implementación en Python

57.9.1 Árbol de regresión y bagging

Mostrar código
import numpy as np
import pandas as pd
from sklearn.tree import DecisionTreeRegressor, export_text

ram = pd.DataFrame({"RAM": [4, 4, 8, 8, 16, 16]}, index=list("ABCDEF"))
precio = np.array([6500, 7200, 10500, 12000, 21000, 24000])
arbol = DecisionTreeRegressor(max_depth=1).fit(ram, precio)
print(export_text(arbol, feature_names=["RAM"]))

# La muestra bootstrap del texto: {A, A, C, D, F, F}; fuera de la bolsa: B y E
boot = ["A", "A", "C", "D", "F", "F"]
arbol_b = DecisionTreeRegressor(max_depth=1).fit(ram.loc[boot], pd.Series(precio, index=ram.index).loc[boot])
oob = ["B", "E"]
print("Predicciones OOB:", dict(zip(oob, arbol_b.predict(ram.loc[oob]))))
print(f"Probabilidad de quedar fuera de una muestra bootstrap: n=6 → {(5/6)**6:.3f}; n grande → {np.exp(-1):.3f}")
|--- RAM <= 12.00
|   |--- value: [9050.00]
|--- RAM >  12.00
|   |--- value: [22500.00]

Predicciones OOB: {'B': np.float64(8875.0), 'E': np.float64(24000.0)}
Probabilidad de quedar fuera de una muestra bootstrap: n=6 → 0.335; n grande → 0.368

Los datos del laboratorio y el efecto de promediar:

Mostrar código
import plotly.graph_objects as go
from sklearn.ensemble import BaggingRegressor
from sklearn.metrics import mean_squared_error

def f_real(x):
    return 2 * np.sin(x) + 0.3 * x

def generar_reg(n, semilla):
    rng = np.random.default_rng(semilla)
    x = np.sort(rng.uniform(0, 10, n)).round(2)
    return x, (f_real(x) + rng.normal(0, 0.6, n)).round(2)

x_tr, y_tr = generar_reg(40, 11)
x_te, y_te = generar_reg(300, 12)
X_tr, X_te = x_tr.reshape(-1, 1), x_te.reshape(-1, 1)

for prof in [1, 2, 4, None]:
    t = DecisionTreeRegressor(max_depth=prof, random_state=0).fit(X_tr, y_tr)
    print(f"Árbol único (profundidad {prof}): ECM entrenamiento {mean_squared_error(y_tr, t.predict(X_tr)):.3f}, "
          f"prueba {mean_squared_error(y_te, t.predict(X_te)):.3f}")

Bs = [1, 2, 5, 10, 20, 50, 100, 200]
ecm_bag = [mean_squared_error(y_te, BaggingRegressor(DecisionTreeRegressor(), n_estimators=B, random_state=0)
                              .fit(X_tr, y_tr).predict(X_te)) for B in Bs]
bag_oob = BaggingRegressor(DecisionTreeRegressor(), n_estimators=200, oob_score=True, random_state=0).fit(X_tr, y_tr)
print(f"Bagging con 200 árboles: ECM prueba {ecm_bag[-1]:.3f}, ECM fuera de la bolsa "
      f"{mean_squared_error(y_tr, bag_oob.oob_prediction_):.3f}")

fig = go.Figure(go.Scatter(x=Bs, y=ecm_bag, mode="lines+markers", name="bagging", line=dict(color="#7c3aed")))
fig.add_hline(y=mean_squared_error(y_te, DecisionTreeRegressor(random_state=0).fit(X_tr, y_tr).predict(X_te)),
              line=dict(color="gray", dash="dash"), annotation_text="árbol único")
fig.add_hline(y=0.36, line=dict(color="#16a34a", dash="dot"), annotation_text="error irreducible")
fig.update_layout(xaxis=dict(type="log", title="número de árboles"), yaxis_title="ECM en prueba",
                  height=340, margin=dict(l=10, r=10, t=20, b=40))
fig.show()
Árbol único (profundidad 1): ECM entrenamiento 1.326, prueba 1.317
Árbol único (profundidad 2): ECM entrenamiento 0.552, prueba 0.772
Árbol único (profundidad 4): ECM entrenamiento 0.118, prueba 0.554
Árbol único (profundidad None): ECM entrenamiento 0.002, prueba 0.621
Bagging con 200 árboles: ECM prueba 0.498, ECM fuera de la bolsa 0.541
Figura 57.1: Error cuadrático medio en prueba de bagging y de un bosque aleatorio según el número de árboles, frente a un árbol único y al error irreducible (0.36). En una sola variable, el bosque aleatorio coincide con bagging.

57.9.2 AdaBoost: el ejemplo manual

AdaBoostClassifier con tocones reproduce las tres rondas. Sus estimator_weights_ son el doble de los \(\alpha_m\) clásicos (variante SAMME).

Mostrar código
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier

x10 = np.arange(1, 11).reshape(-1, 1)
y10 = np.array([1, 1, 1, -1, -1, -1, 1, 1, 1, -1])
ada = AdaBoostClassifier(DecisionTreeClassifier(max_depth=1), n_estimators=3, random_state=0).fit(x10, y10)

pd.DataFrame({
    "corte": [e.tree_.threshold[0] for e in ada.estimators_],
    "ε": ada.estimator_errors_,
    "α clásico (½ ln)": ada.estimator_weights_ / 2,
    "errores del ensamble": [int((p != y10).sum()) for p in ada.staged_predict(x10)],
}, index=[1, 2, 3]).round(4)
corte ε α clásico (½ ln) errores del ensamble
1 3.5 0.3000 0.4236 3
2 9.5 0.2143 0.6496 3
3 6.5 0.1818 0.7520 0

57.9.3 Comparación en un problema real

Comparamos un árbol, bagging, un bosque aleatorio, AdaBoost y gradient boosting en el conjunto breast cancer (569 tumores, 30 variables) con validación cruzada estratificada repetida.

Mostrar código
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import BaggingClassifier, RandomForestClassifier, GradientBoostingClassifier
from sklearn.model_selection import RepeatedStratifiedKFold, cross_validate, train_test_split

X, y = load_breast_cancer(return_X_y=True, as_frame=True)
cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=3, random_state=0)
modelos = {
    "Árbol (sin poda)": DecisionTreeClassifier(random_state=0),
    "Bagging (300 árboles)": BaggingClassifier(DecisionTreeClassifier(), n_estimators=300, random_state=0, n_jobs=-1),
    "Bosque aleatorio (300)": RandomForestClassifier(n_estimators=300, random_state=0, n_jobs=-1),
    "AdaBoost (300 tocones)": AdaBoostClassifier(DecisionTreeClassifier(max_depth=1), n_estimators=300, random_state=0),
    "Gradient boosting": GradientBoostingClassifier(random_state=0),
}
filas = []
for nombre, m in modelos.items():
    r = cross_validate(m, X, y, cv=cv, scoring=["accuracy", "roc_auc"])
    filas.append({"modelo": nombre, "exactitud": r["test_accuracy"].mean(), "desv.": r["test_accuracy"].std(),
                  "AUC": r["test_roc_auc"].mean()})
pd.DataFrame(filas).set_index("modelo").round(3)
exactitud desv. AUC
modelo
Árbol (sin poda) 0.927 0.019 0.923
Bagging (300 árboles) 0.954 0.017 0.990
Bosque aleatorio (300) 0.960 0.012 0.991
AdaBoost (300 tocones) 0.972 0.012 0.996
Gradient boosting 0.960 0.013 0.993

57.9.4 Error fuera de la bolsa e importancia de variables

Mostrar código
from sklearn.inspection import permutation_importance
from plotly.subplots import make_subplots

X_tr2, X_te2, y_tr2, y_te2 = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)
rf = RandomForestClassifier(n_estimators=500, oob_score=True, random_state=0, n_jobs=-1).fit(X_tr2, y_tr2)
print(f"Exactitud fuera de la bolsa: {rf.oob_score_:.3f} · exactitud en prueba: {rf.score(X_te2, y_te2):.3f}")

mdi = pd.Series(rf.feature_importances_, index=X.columns)
perm = pd.Series(permutation_importance(rf, X_te2, y_te2, n_repeats=20, random_state=0).importances_mean, index=X.columns)
top = mdi.sort_values(ascending=False).index[:10][::-1]
fig = make_subplots(rows=1, cols=2, subplot_titles=("MDI (impureza)", "Permutación (prueba)"), shared_yaxes=True)
fig.add_trace(go.Bar(x=mdi[top], y=top, orientation="h", marker_color="#2563eb"), row=1, col=1)
fig.add_trace(go.Bar(x=perm[top], y=top, orientation="h", marker_color="#ea580c"), row=1, col=2)
fig.update_layout(height=420, showlegend=False, margin=dict(l=10, r=10, t=40, b=30))
fig.show()
Exactitud fuera de la bolsa: 0.965 · exactitud en prueba: 0.953
Figura 57.2: Diez variables más importantes del bosque aleatorio según la disminución de impureza (MDI, en el entrenamiento) y según la permutación (en prueba). Ambas coinciden en las variables principales, pero la MDI reparte importancia entre variables muy correlacionadas que la permutación considera casi intercambiables.

57.10 Resumen

  • Un árbol de regresión predice el promedio de cada hoja y elige cortes que reducen la suma de cuadrados.
  • Combinar modelos funciona si cada uno es mejor que el azar y sus errores son diversos; la correlación entre modelos limita la reducción de varianza (\(\rho\sigma^2\)).
  • Bagging promedia árboles entrenados con muestras bootstrap; cada árbol deja fuera ≈ 36.8 % de los datos, lo que da el error fuera de la bolsa sin costo adicional.
  • Los bosques aleatorios eligen cada corte entre \(m\) variables al azar para decorrelacionar los árboles; más árboles no sobreajustan. La importancia por permutación es más fiable que la MDI.
  • AdaBoost entrena clasificadores débiles en secuencia, repesando los errores y combinándolos con pesos \(\alpha_m = \frac{1}{2}\ln\frac{1 - \varepsilon_m}{\varepsilon_m}\); minimiza la pérdida exponencial y es sensible al ruido.
  • Bagging reduce varianza; boosting, sesgo. El capítulo siguiente generaliza el boosting a cualquier pérdida con el gradient boosting y su implementación más popular, XGBoost.

Referencias