
Gradient boosting explicado: 594 árboles en cadena que entrenan 9 veces más rápido que un bosque
Qué es gradient boosting, cómo cada árbol corrige a los anteriores y cuándo falla, medido con clima diario de siete ciudades de Chile entre 1984 y 2026. Con tasa 1,0 se desestabilizó y a las 3.000 rondas quedó en AUC 0,681; con tasa 0,03 y 594 rondas elegidas en validación llegó a 0,883, sin diferencia concluyente con un bosque de 200 árboles.
Con tasa de aprendizaje 1,0, gradient boosting encontró su mejor punto en la ronda 7 y después se desestabilizó. A las 3.000 rondas, en los días de 2016-2026 quedó en AUC 0,681, menos que un árbol de decisión sin límite de profundidad. El mismo algoritmo con tasa 0,03 y 594 rondas, elegidas en un período de validación, llegó a 0,883.
Es la sexta entrega de la serie, con los mismos datos diarios de NASA POWER de siete ciudades de Chile que usé en Random Forest, regresión lineal, K-Means, regresión logística y árbol de decisión. La pregunta sigue siendo si lloverá al menos 1 mm mañana. Entrené con 1984-2012 y evalué en 2016-2026, en un contenedor con 8 CPU y scikit-learn 1.7.2. La tasa y las rondas las elegí ajustando con 1984-2009 y midiendo en 2010-2012; lo demás quedó en los valores por defecto o fijo en cada comparación.
¿Qué es gradient boosting?
Gradient boosting es una suma de árboles de decisión pequeños. El primero hace una predicción gruesa. El segundo usa las mismas variables, pero no se ajusta a la etiqueta original sino a lo que le faltó al primero. El tercero, a lo que les faltó a los dos anteriores, y así por cientos de rondas. Cada árbol suma su aporte multiplicado por una tasa de aprendizaje.
Un bosque aleatorio hace lo contrario: entrena árboles grandes e independientes, cada uno con otra muestra de filas, y promedia. Los árboles de un bosque se pueden entrenar a la vez; los del boosting van en fila, porque cada uno depende de los anteriores.
Una formulación general muy influyente es la de Jerome Friedman, en «Greedy Function Approximation: A Gradient Boosting Machine» (2001): cada árbol se ajusta al gradiente negativo de la función de pérdida. Si la pérdida es la mitad del error cuadrático, ese gradiente es exactamente el residuo, lo que falta para llegar al valor real. Con la pérdida logarítmica de un problema de lluvia o no lluvia, es la diferencia entre lo que pasó (1 o 0) y la probabilidad que el modelo daba. Un antecedente es AdaBoost, de Freund y Schapire (1997), que en vez de ajustar residuos daba más peso a los ejemplos mal clasificados.
Cada árbol ajusta lo que falta
Con una sola variable se ve el mecanismo. La máxima de hoy predice la de mañana, con árboles de una sola pregunta, llamados tocones, y error cuadrático.
El primer tocón corta en 17,3 °C: resta 4,69 °C a los días por debajo y suma 5,29 °C a los de arriba. Con tasa 0,3 el mismo tocón suma solo 0,3 veces eso (-1,41 y +1,59). El error cuadrático medio de entrenamiento baja de 42,27 sin árboles a 5,52 tras 100 rondas con tasa 1,0 y a 5,64 con 0,3; en 2016-2026 erran por 1,86 y 1,88 °C en promedio. Con la mitad del error cuadrático como pérdida, el residuo es exactamente el gradiente negativo; con la pérdida logarítmica de la lluvia es la etiqueta menos la probabilidad, como explica el texto.
Sin árboles, la predicción es el promedio: 17,0 °C para todos los días. El primer tocón corta en 17,3 °C, resta 4,69 °C a los días más fríos y suma 5,29 °C a los más cálidos. El segundo sigue usando la temperatura de hoy como entrada, pero su objetivo ya son los residuos que dejó el primero, y busca el corte que más los reduce. Tras 100 rondas con tasa 1,0, el error cuadrático medio de entrenamiento pasó de 42,27 a 5,52.
Con tasa 0,3, cada tocón suma solo 0,3 veces su valor, así que el mismo primer corte mueve la predicción 1,41 °C hacia abajo y 1,59 °C hacia arriba. Tras 100 rondas quedó en 5,64. El camino es más lento, y en la tarea de lluvia las tasas bajas coincidieron con los mejores resultados.
La tasa de aprendizaje compra rondas
Para la lluvia usé HistGradientBoostingClassifier con árboles de hasta 31 hojas, que es su valor por defecto, y 3.000 rondas sin parada automática. Registré la pérdida logarítmica en validación ronda por ronda con cuatro tasas.
- tasa 1,0 · mejor 7
- tasa 0,3 · mejor 30
- tasa 0,1 · mejor 188
- tasa 0,03 · mejor 594
la tasa 1,0 se desestabiliza y ya en la ronda 1.000 está clavada en 3,25
Con tasa 1,0 la mejor ronda en validación es la 7; si sigue hasta 3.000 rondas, en 2016-2026 cae a AUC 0,681, por debajo de un árbol de decisión sin límite de profundidad (0,689 en el post anterior). La tasa 0,3 toca fondo en la ronda 30, la 0,1 en la 188 y la 0,03 en la 594, con la pérdida más baja. Esa elección, reentrenada con 1984-2012, da en 2016-2026 AUC 0,883 y pérdida 0,332; la misma tasa con 3.000 rondas, 0,8805 y 0,338.
Con tasa 1,0 la pérdida en validación tocó fondo en la ronda 7 y después el ajuste se desestabilizó: subió a saltos y ya en la ronda 1.000 estaba clavada en 3,25. La de entrenamiento también: bajó hasta 0,33 en la ronda 15 y terminó en 3,16. Eso no es el sobreajuste de siempre, donde el entrenamiento sigue mejorando; es un paso tan grande que el modelo deja de converger. Con las otras tres tasas la pérdida de entrenamiento bajó hasta la ronda 3.000. La 0,3 tocó fondo en validación en la ronda 30, la 0,1 en la 188 y la 0,03 en la 594, con la menor pérdida de las cuatro (0,347). Elegí esa combinación y reentrené con 1984-2012 completo: en 2016-2026 dio AUC 0,883 y pérdida 0,332.
Pasarse de rondas con una tasa baja costó poco. Con tasa 0,03 y 3.000 rondas, cinco veces más de las elegidas, el AUC en 2016-2026 bajó a 0,8805 y la pérdida subió a 0,338. Con tasa 1,0 y las mismas 3.000 rondas, a 0,681 y 3,15.
scikit-learn trae una parada automática que se activa sola con más de 10.000 filas: aparta al azar el 10 % de los datos de entrenamiento y detiene el ajuste cuando 10 rondas seguidas no mejoran. Con datos diarios, apartar días al azar deja en validación días vecinos de los que se usan para aprender, así que esperaba que se pasara de largo. No pasó: con cinco semillas se detuvo entre 335 y 466 rondas, y la misma regla aplicada sobre 2010-2012 se detuvo en 344. En 2016-2026 el AUC quedó entre 0,8817 y 0,8826 con la parada automática y en 0,8824 con la temporal. En estos datos la parada automática sirvió; con series más autocorrelacionadas conviene medirlo antes de confiar en ella.
Qué tan grande conviene cada árbol
La tasa no es lo único que decide cuántas rondas hacen falta. También pesa el tamaño de cada árbol.
Los tocones, árboles de una sola pregunta, no pueden combinar dos variables dentro de un mismo árbol: su mejor ronda en validación fue la 19.968, cerca del tope de 20.000, y se quedaron en AUC 0,869. Con cuatro hojas bastó para llegar a 0,882 con 5.571 rondas, y con 63 hojas, a 0,883 con 427. Árboles más grandes piden menos rondas; pasadas las cuatro hojas el resultado casi no cambia.
Un tocón solo puede preguntar por una variable, así que un boosting de tocones es una suma de efectos de una variable a la vez, sin combinaciones. Su mejor ronda en validación fue la 19.968, cerca del tope de 20.000 que le puse, así que con más rondas podía mejorar algo; quedó en AUC 0,869. Con árboles de cuatro hojas, que ya pueden combinar dos preguntas, llegó a 0,882 con 5.571 rondas. Desde ahí, agrandar el árbol recortó rondas casi sin mover el resultado: 0,883 con 31 hojas y 594 rondas, 0,883 con 63 hojas y 427. Me quedé con las 31 hojas por defecto; en validación, las 63 quedaron apenas 0,0007 por debajo en pérdida.
Por qué la versión por histogramas es rápida
scikit-learn tiene dos implementaciones. GradientBoostingClassifier busca cortes entre todos los valores de cada variable y trabaja con un hilo. HistGradientBoostingClassifier, que su documentación describe como inspirada en LightGBM, reparte primero cada variable en cajas y reparte el trabajo entre hilos.
AUC 2016-2026 con 200 rondas de árboles de profundidad 3
cajas por variable
Con 255 cajas, el valor por defecto de scikit-learn, el AUC fue 0,8802; con 64, 0,8799; con 16, 0,8796; con solo 4 cajas por variable, 0,8736. Lo que se pierde por agrupar valores es poco aquí, y es lo que abarata cada ronda.
A un cuarto del tiempo real.
El clásico trabaja con un hilo por diseño; el de histogramas se midió limitando sus hilos de OpenMP. Con la configuración principal de este experimento (hasta 31 hojas y 200 rondas), el de histogramas tardó 0,94 s con un hilo y 0,34 s con ocho.
Con un hilo, la versión por histogramas fue 46,5 veces más rápida que la clásica, con un AUC apenas mayor (0,880 contra 0,879). Usar la mitad de las filas en cada ronda, la variante estocástica que propuso Friedman en 2002, bajó el clásico de 29,7 a 16,1 s sin cambiar el AUC en tres decimales. Con ocho hilos, el de histogramas pasó de 0,64 a 0,17 s: los árboles van en fila, pero dentro de cada árbol la búsqueda de cortes se reparte por variable.
Etiquetas equivocadas
Si cada árbol se ajusta a lo que los anteriores erraron, una etiqueta mal puesta es un error que el modelo va a intentar corregir. Lo medí volteando al azar el 5, el 10 y el 20 % de las etiquetas de entrenamiento, con cinco semillas por nivel (y una sola corrida sin ruido). Las rondas del boosting las elegí en una validación 2010-2012 sin tocar, y medí en 2016-2026.
- boosting, rondas en validación · 20 %: 0,868
- bosque, 200 árboles · 20 %: 0,867
- boosting, 3.000 rondas · 20 %: 0,851
Con el 10 % volteado, el bosque promedió 0,876 y el boosting 0,871, que varió entre 0,868 y 0,877 según la semilla. Con el 20 %, quedaron muy cerca en promedio (0,868 y 0,867), pero el boosting fue de 0,854 a 0,874: la validación eligió entre 632 y 2.982 rondas. Dejado en 3.000 rondas, el boosting fue el peor en todos los niveles con ruido.
Con el 5 % volteado, boosting y bosque quedaron muy cerca en promedio: AUC 0,880 y 0,879. Con el 10 %, el bosque quedó arriba, 0,876 contra 0,871, y además se movió poco entre semillas (de 0,875 a 0,876), mientras el boosting fue de 0,868 a 0,877. Con el 20 % volvieron a quedar muy cerca en promedio (0,868 y 0,867), pero el boosting dependió de la semilla: de 0,854 a 0,874.
Lo que cambia entre semillas es cuántas rondas elige la validación. Con el 20 % de ruido eligió entre 632 y 2.982, y la corrida de 2.982 fue la peor. Dejado en 3.000 rondas sin elegir nada, el boosting fue el peor de los tres en todos los niveles con ruido: 0,851 con el 20 %. En pérdida logarítmica, en cambio, el boosting con rondas elegidas quedó por debajo del bosque en los tres niveles (0,434 contra 0,443 con el 20 %). Con etiquetas sucias, el bosque dio un AUC más predecible entre semillas; el del boosting dependió de cuántas rondas eligió la validación.
Boosting, bosque, árbol y logística
A un cuarto del tiempo real.
Serializados con pickle, el árbol ocupa 21,6 KB, la logística 1,8 KB, el boosting 2,1 MB y el bosque 124 MB. Es tamaño en disco, no memoria en uso.
En AUC, boosting y bosque quedaron a 0,0006 de distancia. Para ver si esa diferencia significa algo, remuestreé 1.000 veces los 77 bloques ciudad-año de 2016-2026 y recalculé la diferencia: el intervalo del 95 % fue de −0,0007 a +0,0021 y cruza el cero, así que la diferencia no es concluyente. En pérdida logarítmica el boosting sí quedó mejor, por poco: −0,0025, con intervalo de −0,0042 a −0,0008.
Donde se separaron fue en el costo. Con un hilo, el boosting entrenó en 2,6 s y el bosque en 24,0 s. Al predecir, la ventaja se da vuelta a medias: puntuar un día tomó 3,0 ms al boosting y 3,1 ms al bosque, pero los 27.256 días de prueba de una vez tomaron 0,58 s al boosting y 0,32 s al bosque. No medí de dónde sale esa diferencia.
Los dos predijeron más lluvia de la que cayó: en promedio, 23,4 % el boosting y 23,8 % el bosque, contra 20,3 % observado. Esa sobreestimación coincidió con una caída de la frecuencia de lluvia entre períodos: en entrenamiento llovió el 26,6 % de los días y en prueba el 20,3 %, como conté en el post de Random Forest. En el tramo de días a los que el boosting dio entre 50 y 60 %, llovió en el 46 %; en el mismo tramo del bosque, en el 48 %. Si la probabilidad se va a usar tal cual, conviene recalibrarla con datos recientes, como se vio en el post de regresión logística.
Para números, el mismo techo que el bosque
Con la máxima de mañana como objetivo y todas las variables, un boosting con tasa 0,1 y 933 rondas elegidas en validación erró por 1,44 °C en promedio en 2016-2026. El bosque de 200 árboles, por 1,50 °C, y la regresión lineal, por 1,70 °C.
Repetí también la prueba de extrapolación del post de Random Forest, aquí con las quince variables, incluidos el día del año y la latitud: entrené con los meses de abril a septiembre de Santiago entre 1984 y 2012 y predije los veranos de 2016-2026. El día más caluroso de entrenamiento tuvo 32,97 °C y los veranos promediaron 29,63 °C. El boosting no predijo más de 29,56 °C, promedió 24,2 °C y erró por 5,48 °C; el bosque, por 4,30 °C; la regresión lineal, por 1,72 °C. Allá las cifras del bosque y de la recta son 3,76 y 1,86 °C porque ese caso controlado descarta el día del año y la latitud; el orden entre los tres modelos no cambia. Más allá de los cortes que aprendió, cada árbol devuelve un valor constante, así que la suma no sigue la tendencia hacia temperaturas que no vio. Es el mismo techo que medí en el bosque.
De fábrica: monotonía y valores faltantes
Probabilidad media de lluvia mañana al fijar la lluvia de hoy en cada valor, sobre 500 días reales de entrenamiento.
Sin restricción, la curva bajó en 4 de 40 tramos; con restricción solo puede subir o quedarse plana, y el AUC en 2016-2026 fue 0,883.
Humedad y presión borradas al azar en el 20 % de los días, cada una por su lado; después, borradas en todos los días de prueba.
El boosting aprende en cada corte hacia qué lado mandar los días con el valor faltante, sin imputar: 0,883 sin faltantes, 0,882 con 20 % faltante y 0,880 con ambas variables ausentes en todos los días de prueba. El bosque, que en esta versión de scikit-learn también acepta faltantes: 0,881 y 0,879. Los huecos son sintéticos y al azar; los de un sensor real pueden no serlo.
A veces se sabe de antemano que una relación va en una sola dirección. Sin restricción, la probabilidad media de lluvia mañana bajó en 4 de los 40 tramos al subir la lluvia de hoy de 0 a 20 mm; con monotonic_cst sobre la lluvia de hoy y la humedad, solo puede subir o quedarse, y el AUC en 2016-2026 fue 0,883, igual que sin restricción.
Borré al azar la humedad y la presión en el 20 % de los días, cada una por su lado. HistGradientBoostingClassifier aprende en cada corte hacia qué rama mandar los días con el valor faltante: el AUC bajó de 0,883 a 0,882, y a 0,880 con las dos variables ausentes en todos los días de prueba. El bosque de scikit-learn, que desde la versión 1.4 también acepta faltantes, dio 0,881 y 0,879. Los huecos de este experimento son al azar; los de un sensor que se cae con mal tiempo pueden no serlo, y ahí el resultado puede cambiar.
Dónde vive gradient boosting en un sistema real
Medido con un hilo: el boosting de 594 rondas entrena en 2,62 s, serializado ocupa 2,1 MB, puntúa un día en 3,03 ms y los 27.256 días de prueba en 0,58 s. El bosque de 200 árboles: 24,0 s, 124,3 MB, 3,08 ms y 0,32 s. Como referencia, el árbol de profundidad 7 puntúa un día en 0,25 ms.
- Puntuación de tablas. Riesgo, demanda, fraude, cualquier tabla con decenas de columnas. Es el primer modelo que probaría ahí: entrena en segundos, acepta faltantes sin imputar y puede tratar columnas categóricas si vienen marcadas como tales.
- Puntuación por lotes. Un trabajo nocturno que puntúa millones de filas. Conviene medir el lote completo: aquí 27.256 días tomaron 0,58 s con un hilo, casi el doble que el bosque.
- API en línea. Una fila tomó 3,0 ms con scikit-learn, parecido al bosque. Si eso no alcanza, conviene probar menos rondas con árboles más grandes o exportar el modelo a un formato compilado, y medir de nuevo.
- Reentrenamiento vigilado. Las rondas se eligen con un tramo de tiempo aparte, y ese tramo tiene que moverse cuando llegan datos nuevos. La calibración también se desplaza: aquí la sobreestimación de lluvia coincidió con una caída de la frecuencia de lluvia entre períodos.
Cuándo lo elegiría: para datos en tabla donde importan la precisión y el costo de entrenar, y cuando hace falta imponer que una variable empuje en una sola dirección. Cuándo no: cuando hay que extrapolar fuera del rango visto, cuando las etiquetas tienen muchos errores y no hay datos limpios para elegir rondas, o cuando una regla legible vale más que dos a cuatro centésimas de AUC, donde un árbol de decisión chico o una regresión logística bastan. Con GPU y tablas de hasta 32.000 filas, un modelo tabular preentrenado le ganó a XGBoost ajustado en las catorce tablas que probé en TabPFN contra XGBoost.
Fuentes
- Friedman, J. H. (2001). «Greedy function approximation: A gradient boosting machine». The Annals of Statistics, 29(5). DOI 10.1214/aos/1013203451.
- Friedman, J. H. (2002). «Stochastic gradient boosting». Computational Statistics & Data Analysis, 38(4), 367–378. DOI 10.1016/S0167-9473(01)00065-2.
- Freund, Y. y Schapire, R. E. (1997). «A Decision-Theoretic Generalization of On-Line Learning and an Application to Boosting». Journal of Computer and System Sciences, 55(1), 119–139. DOI 10.1006/jcss.1997.1504.
- Chen, T. y Guestrin, C. (2016). «XGBoost: A Scalable Tree Boosting System». KDD ‘16, 785–794. DOI 10.1145/2939672.2939785.
- Ke, G. et al. (2017). «LightGBM: A Highly Efficient Gradient Boosting Decision Tree». Advances in Neural Information Processing Systems 30.
- scikit-learn 1.7, gradient boosting e histogramas y
HistGradientBoostingClassifier. - NASA POWER, Daily API y fuentes de datos.
Comentarios
Todavía no hay comentarios. El primero es tuyo.