
Random Forest explicado: cómo vota un bosque de árboles, medido con 42 años de clima de Chile
Qué es Random Forest, cómo funcionan el bootstrap y el sorteo de variables, y por qué sus árboles se entrenan en paralelo y no en cadena. Lo medí prediciendo lluvia en siete ciudades de Chile: 7,5 veces más rápido con 8 CPU, meseta pasados los 100 árboles, una columna de ruido que la importancia por defecto pone por encima de variables reales y un techo al extrapolar temperaturas.
Hace poco me apareció en Instagram un carrusel muy compartido con fichas de algoritmos de machine learning. La de Random Forest dibuja los árboles unidos por flechas: árbol 1, árbol 2, árbol T, uno detrás de otro. Ese dibujo describe otro algoritmo. Un bosque aleatorio entrena sus árboles sin que ninguno vea a los demás, y esa independencia es justamente lo que lo define.
En vez de explicarlo con otro dibujo, lo medí. Entrené bosques para predecir si llueve mañana en siete ciudades de Chile, de La Serena a Punta Arenas, con datos diarios desde 1984, dentro de un contenedor con límites de CPU y memoria. Todo lo que sigue sale de esas corridas.
¿Qué es Random Forest?
Random Forest es un algoritmo de aprendizaje supervisado que entrena muchos árboles de decisión, cada uno con una muestra distinta de los datos y un sorteo distinto de variables, y combina sus respuestas. Sirve para clasificar (¿llueve o no?) y para predecir números (¿cuánta temperatura?). Leo Breiman lo formalizó en 2001; antes, Tin Kam Ho ya había probado diversificar árboles entrenándolos con subconjuntos aleatorios de variables.
Un árbol solo aprende de memoria: sigue cada rareza de los datos hasta dejar hojas con un puñado de ejemplos. Cien árboles que se equivocan en cosas distintas, promediados, se equivocan menos. Para que se equivoquen en cosas distintas hay que obligarlos a ver datos distintos.
¿Cómo funciona un bosque aleatorio?
Hay tres pasos, y dos de ellos son sorteos:
- Bootstrap. Cada árbol recibe una muestra del mismo tamaño que los datos originales, sacada con reposición. Algunas filas se repiten y el 36,8 % no aparece (lo comprobé sorteando 36 mil filas veinte veces); con esas filas que quedaron fuera se calcula el error out-of-bag, una validación que sale gratis.
- Sorteo de variables. En cada división del árbol solo se consideran algunas variables al azar. En el clasificador de scikit-learn, por defecto, la raíz cuadrada del total; en el regresor, todas. Así ningún árbol puede apoyarse siempre en la misma variable dominante.
- Combinación. Para clasificar se juntan las respuestas de todos los árboles; para regresión se promedian.
Como ningún paso depende del resultado de otro árbol, los árboles se pueden entrenar al mismo tiempo. Boosting es lo contrario: cada árbol nuevo se entrena sobre los errores del anterior, así que la cadena es obligatoria.
- bosque, 200 árboles: 26,1 s con 1 CPU → 3,50 s con 8 (7,5×)
- boosting por histogramas, 200 rondas: 0,94 s → 0,34 s (2,8×), las rondas van en fila y solo parte del trabajo de cada una se reparte entre núcleos
La diferencia se mide. Con 200 árboles y 74 mil filas, el bosque tardó 26,1 segundos en una CPU y 3,50 segundos en ocho: 7,5 veces más rápido, el 93 % de lo que daría un escalado perfecto. El boosting por histogramas de scikit-learn, con 200 rondas, pasó de 0,939 a 0,341 segundos: 2,8 veces. Sus rondas van en fila, y solo algunas operaciones dentro de cada ronda se reparten entre núcleos.
A un cuarto del tiempo real. Cada vez que se doblan los núcleos, el tiempo casi se parte en dos.
Cinco veces más lento que el tiempo real. Doblar los núcleos rinde cada vez menos.
Con 8 CPU el bosque llega al 93 % de un escalado perfecto; el boosting, al 34 %. Aun así, el boosting con una CPU termina antes que el bosque con ocho.
Ojo con leer eso como “el bosque es más rápido”. En una sola CPU el boosting por histogramas fue 28 veces más rápido que el bosque, y acertó un poco más (AUC 0,882 contra 0,880). Lo que escala con núcleos es el bosque; lo que ya es rápido de partida es el boosting moderno. El GradientBoostingClassifier clásico, sin histogramas y sin paralelo, tardó 29,2 segundos.
Los datos: 42 años de clima diario en siete ciudades
Descargué de NASA POWER los datos diarios de La Serena, Valparaíso, Santiago, Concepción, Temuco, Puerto Montt y Punta Arenas: temperatura máxima y mínima, precipitación, viento (velocidad y dirección), radiación, humedad, presión y punto de rocío. Con eso, cada fila es un día en una ciudad, y la pregunta es si al día siguiente caerá al menos 1 mm.
Entrené con 1984 a 2012 (74.145 días) y probé con 2016 a 2026 (27.256 días). Los tres años del medio quedan fuera a propósito: el clima de un día se parece mucho al del siguiente, y sin ese hueco el modelo estaría probando con días que casi vio. Uso esos mismos años para todas las comparaciones del post, así que las cifras describen ese período; no son una prueba final guardada sin mirar.
- siete ciudades
- Santiago
- entrenamiento 1984–2012
- prueba 2016–2026
Entrenamiento 26,6 %, prueba 20,3 %. La baja aparece en las siete ciudades; puede venir del clima, de la composición del período o de los cambios de fuente de los datos, y este gráfico no las separa.
En entrenamiento llueve el 26,6 % de los días; en prueba, el 20,3 %. La baja aparece en las siete ciudades, así que no es un error al preparar una de ellas (Santiago pasa del 13,6 % al 9,7 % de los días, Puerto Montt del 52,9 % al 36,5 %). En Santiago la caída se ve sobre todo desde 2009, consistente con la megasequía que afecta a Chile central desde 2010, aunque también pueden pesar los cambios de fuente que explico abajo; no separé una cosa de la otra.
El tropiezo antes de medir
Empecé con Open-Meteo y la primera tabla ya salió rara: Arica, la ciudad más seca de Chile, aparecía con lluvia frecuente, y la tasa de lluvia se desplomaba entre los años de entrenamiento y los de prueba. Al pedir años sueltos apareció la causa. La opción por defecto de Open-Meteo, best_match, combina varios modelos según la fecha: Arica 2005 salía con 169 mm y Arica 2022 con 19 mm. El modelo habría aprendido el cambio de fuente, no el clima. Fijar ERA5 lo arreglaba, pero ERA5 le daba a Santiago 75 días de lluvia en 2005 y 50 en 2022, y el límite de cuota por hora terminó de sacarme de ahí. NASA POWER entrega la serie de cada ciudad en segundos y, en Santiago, 58 días de lluvia en 2005 y 25 en 2022. Tampoco es una sola fuente: usa MERRA-2 para el histórico y GEOS-IT para los meses más recientes, y la radiación pasa por varios productos satelitales. Lo que necesitaba es que la lluvia no cambiara de escala a mitad de la serie, como en Arica con Open-Meteo, y ese salto no lo vi en la tasa anual de Santiago. Arica quedó fuera: 50 días de lluvia en 2005 no son creíbles para el desierto costero.
Una advertencia que corre para todo el post: esto predice la precipitación de un reanálisis corregido (MERRA-2, con GEOS-IT en el tramo más reciente), no lo que marca un pluviómetro, el mismo tipo de dato con el que comparo pronósticos en mi marcador del clima. Para aprender cómo se comporta un bosque sirve igual; para decidir si sacar el paraguas, no.
¿Cuántos árboles hacen falta?
Antes del bosque, dos referencias tontas. Decir siempre “no llueve” acierta el 79,7 % de los días de prueba, así que la exactitud sola engaña. Decir “mañana llueve si hoy llovió” acierta el 81,2 % y su F1 de lluvia es 0,535. Cualquier modelo tiene que ganarles a esas dos.
AUC: un árbol 0,688 · bosque de 1 0,685 · bosque de 400 0,881.
F1 de lluvia: un árbol 0,494 · bosque de 1 0,490 · bosque de 400 0,596.
Un árbol de decisión solo llega a un AUC de 0,688. Un bosque de un único árbol queda igual o un poco peor, 0,685 (las bandas de semillas se tocan): ese árbol ve una muestra bootstrap y menos variables en cada corte, así que no hay por qué esperar que gane. Con 10 árboles el AUC sube a 0,849; con 100, a 0,879; con 400, a 0,881. Casi toda la ganancia ocurre antes de los 50 árboles, y entre 200 y 400 la diferencia es de menos de una milésima. Arriba de 25 árboles la banda de AUC casi desaparece (corrí cinco semillas hasta 50 árboles y dos desde 100); en F1 todavía se mueve un punto.
Hay un detalle raro en la pestaña de F1: con 2 árboles el F1 cae a 0,429, por debajo del árbol solo (0,494), aunque la exactitud sube de 0,774 a 0,815. Con árboles completos cada uno responde 0 o 1, y un empate 1 a 1 se resuelve como seco, así que un bosque de dos solo predice lluvia cuando los dos están de acuerdo: acierta más días secos y se le escapan más días de lluvia.
Contra las referencias, el bosque de 400 árboles acierta el 84,3 % y su F1 de lluvia es 0,596. Le gana a “mañana llueve si hoy llovió”, pero por 3 puntos de exactitud, no por 30. Predecir lluvia con variables de un solo punto es difícil, y un algoritmo no inventa información que los datos no traen.
La validación out-of-bag necesita cuidado en una serie temporal. Con 200 árboles dio un AUC de 0,894, contra 0,880 en los años de prueba: más optimista. La exactitud parece decir lo contrario (0,834 contra 0,840), pero no se puede comparar, porque en el período de entrenamiento llueve más y decir “siempre seco” acierta el 73,4 % ahí contra el 79,7 % en prueba. Una explicación es que los días vecinos se parecen, así que cada fila fuera de la muestra suele tener un gemelo dentro; otra, que los años de prueba simplemente son distintos. Este experimento no separa las dos causas. No lo usaría como única validación con datos que tienen fecha.
¿Cómo vota el bosque?
La explicación clásica dice que cada árbol vota y gana la mayoría. Breiman lo planteó así, pero scikit-learn no lo hace: su guía de usuario aclara que promedia la probabilidad que da cada árbol, y cada árbol entrega la proporción de días lluviosos de la hoja donde cae el ejemplo. Medí cuánto importa, y la respuesta depende de la profundidad. Con árboles que crecen hasta el final, como vienen por defecto, cada hoja queda pura y cada árbol responde 0 o 1: contar votos y promediar dan exactamente lo mismo en los 27.256 días de prueba. Con árboles limitados a profundidad 12, solo el 16 % de las respuestas de cada árbol son 0 o 1, y las dos reglas discrepan en 348 días, el 1,3 %.
scikit-learn no cuenta votos: promedia la probabilidad de cada árbol. Con árboles completos cada árbol responde 0 o 1, así que en los 27.256 días de prueba las dos reglas coinciden siempre (en 203 días el bosque quedó exactamente 50 a 50). Con árboles de profundidad 12 discrepan en 348.
La fracción de árboles que vota lluvia se comporta como una probabilidad razonable. En los días donde menos del 10 % de los árboles votó lluvia, llovió el 2 % de las veces; donde votó el 90 % o más, llovió el 90 %. Entre medio sube de forma ordenada, aunque en todos los tramos llovió algo menos de lo que la fracción sugiere (con 60 a 70 % de votos llovió el 59 % de las veces), como se espera si los años de prueba son más secos que los de entrenamiento. Eso muestra que la fracción ordena bien los días de más a menos probables; no demuestra que esté calibrada, pero sirve para saber cuándo el bosque duda.
Con menos del 10 % de los votos llovió el 2 % de las veces; con 90 % o más, el 90,3 %. En los diez tramos llovió menos que el centro del tramo. La mayoría de los días (11.694) cae en el tramo más bajo: el bosque rara vez está seguro de que va a llover.
- < 10 %
- 10–25 %
- 25–50 %
- 50–75 %
- ≥ 75 %
AUC en los años de prueba con solo estas dos variables: un árbol 0,664, 10 árboles 0,726, 200 árboles 0,734. El árbol solo dibuja escalones duros; el bosque los suaviza al promediar.
¿Qué variable importa? La trampa de la impureza
Todo bosque de scikit-learn trae feature_importances_, y es lo primero que la gente grafica. Esa medida suma cuánto reduce la impureza cada variable en los cortes de los árboles, calculado sobre los datos de entrenamiento. Para probarla, agregué una columna de números aleatorios que no tiene ninguna relación con la lluvia.
- máxima0,0580,0086 ± 0,0005
- mínima0,0490,0043 ± 0,0003
- lluvia de hoy0,1830,0983 ± 0,0015
- lluvia de ayer0,0570,0042 ± 0,0004
- velocidad del viento0,0500,0066 ± 0,0005
- viento este-oeste0,0480,0079 ± 0,0003
- viento norte-sur0,1250,0396 ± 0,0010
- radiación0,0540,0051 ± 0,0004
- humedad0,0560,0044 ± 0,0004
- presión0,0510,0065 ± 0,0003
- cambio de presión0,0600,0191 ± 0,0008
- punto de rocío0,0420,0030 ± 0,0002
- día del año (seno)0,0380,0013 ± 0,0003
- día del año (coseno)0,0380,0029 ± 0,0004
- latitud0,0490,0302 ± 0,0011
- ruido aleatorio0,041-0,0002 ± 0,0001
Por impureza, 13 de las 16 variables quedan entre 0,038 y 0,058 y el ruido aleatorio sale en el puesto 14. Por permutación solo 4 variables bajan el AUC en más de 0,01, y el ruido queda último.
Por impureza, el ruido quedó por encima de las dos variables del día del año, con una importancia de 0,041. La medida por permutación, que desordena una columna en los años de prueba y mide cuánto empeora el modelo, lo deja último y en cero. La guía de scikit-learn advierte las dos causas: la impureza se calcula con los datos de entrenamiento y favorece a las variables con muchos valores distintos, y una columna aleatoria continua tiene tantos valores distintos como filas.
Con las 16 variables a la vista aparece algo más: por impureza, 13 de las 16 quedan entre 0,038 y 0,058, así que todas parecen pesar algo; por permutación, solo 4 bajan el AUC en más de 0,01 (la lluvia de hoy, el viento norte-sur, la latitud y el cambio de presión). La impureza no solo infla el ruido: reparte la importancia casi pareja entre todo.
La permutación tampoco es un ranking causal. Mide cuánto depende este modelo de cada columna en estos datos, y cuando dos variables se parecen (la precipitación de hoy y la de ayer, la presión y su cambio) desordenar una deja a la otra cubriendo el hueco, así que ambas pueden salir más bajas de lo que pesan juntas.
Cuándo no usar Random Forest
Cuando hay que predecir fuera de lo que vio
Un árbol de regresión responde con el promedio de los ejemplos de una hoja, así que un bosque nunca puede predecir un valor más alto que el más alto que vio al entrenar. Para verlo sin ambigüedad armé un caso controlado: un bosque y una regresión lineal aprenden la máxima de mañana en Santiago solo con los meses de abril a septiembre de 1984 a 2012, y después predicen los veranos de 2016 a 2026. Son 5.307 días de ajuste y 962 de evaluación, sin un solo día compartido: al cambio de estación se suma el salto de años. Los dos modelos aprenden sin el día del año ni la latitud, porque en verano esas variables caen fuera del rango que vieron.
predicción perfecta
Día más caluroso del entrenamiento: 33,0 °C. Los veranos promediaron 29,63 °C; el bosque predijo 26,01 °C en promedio y nunca más de 28,9 °C, la recta 29,01 °C. Error absoluto medio: bosque 3,76 °C, recta 1,86 °C.
- La Serenavio 31,2 · máx. predicho 30,25 °C · 0 días de prueba por encima
- Valparaísovio 23,1 · máx. predicho 22,37 °C · 0 días de prueba por encima
- Santiagovio 38,4 · máx. predicho 34,43 °C · 0 días de prueba por encima
- Concepciónvio 34,9 · máx. predicho 31,09 °C · 2 días de prueba por encima
- Temucovio 39,5 · máx. predicho 34,28 °C · 5 días de prueba por encima
- Puerto Monttvio 36,0 · máx. predicho 27,96 °C · 0 días de prueba por encima
- Punta Arenasvio 22,2 · máx. predicho 19,91 °C · 1 día de prueba por encima
El bosque predijo en promedio 26,01 °C para unos veranos que promediaron 29,63 °C, y su valor más alto fue 28,9 °C. Había visto días de hasta 33,0 °C, pero promediar hojas lo dejó bastante más abajo de ese máximo: los días calurosos quedaron aplastados. La recta siguió subiendo con las variables del día y erró algo menos de la mitad, 1,86 °C de error absoluto medio contra 3,76 °C del bosque.
En el caso realista, con todos los meses y los años de prueba desde 2016, el bosque ganó (1,50 °C contra 1,70 °C), y casi ningún día de prueba superó el máximo de entrenamiento de su ciudad: 5 en Temuco, 2 en Concepción, 1 en Punta Arenas. En esos días el error medio del bosque fue de 4,7 a 8,6 °C según la ciudad, y el de la recta fue todavía mayor en las tres. La ventaja de la recta aparece cuando casi todo lo que se predice queda fuera del rango, como en el caso controlado; con unos pocos récords sueltos, los dos modelos fallan.
Corregido el 16 de septiembre de 2026. La primera versión de este caso controlado juntaba los años de entrenamiento y los de prueba antes de separar las estaciones. El ajuste quedó con inviernos de 2016-2026 dentro y el “verano que ningún modelo vio” incluía veranos de 1984-2012: las cifras publicadas entonces, 3,43 °C para el bosque y 1,81 °C para la recta, salían de un experimento contaminado. Con los períodos separados el techo del bosque sigue ahí y la distancia entre los dos modelos es algo mayor.
Cuando el modelo tiene que viajar
Un bosque sin límite de profundidad guarda todos sus nodos, y con muchos datos crece rápido. Medí el tamaño serializado con pickle y la latencia de predicción con un solo hilo:
| Bosque | Tamaño | Nodos | Una fila (p50) | 27.256 filas de una vez | AUC |
|---|---|---|---|---|---|
| 10 árboles, sin límite | 12,3 MB | 153.570 | 0,48 ms | 0,019 s | 0,848 |
| 100 árboles, sin límite | 122,9 MB | 1.535.624 | 1,73 ms | 0,182 s | 0,878 |
| 100 árboles, profundidad 12 | 26,0 MB | 325.084 | 1,74 ms | 0,103 s | 0,880 |
| 400 árboles, profundidad 12 | 104,1 MB | 1.299.310 | 5,73 ms | 0,408 s | 0,882 |
Dos lecturas. Limitar la profundidad a 12 dejó el bosque de 100 árboles 4,7 veces más liviano sin perder nada de AUC, así que la configuración por defecto, que deja crecer los árboles hasta el final, sale cara de mover y de cargar en memoria. Y predecir fila por fila cuesta muchísimo más que en lote: 1,74 ms por una fila contra 0,103 s por 27 mil, unos 3,8 microsegundos por fila. El costo de la llamada individual crece con los árboles, de 0,48 ms con 10 a 5,73 ms con 400, porque scikit-learn despacha cada árbol por separado aunque la consulta sea una sola fila.
Dónde vive un Random Forest en un sistema real
Un bosque aleatorio casi nunca es el producto. Es una pieza entre la tabla de variables y la decisión, y en la práctica aparece en tres lugares:
- Puntuación en lote. Un proceso nocturno lee la tabla de clientes, cuentas o sensores, calcula una probabilidad por fila y la guarda. Es donde el bosque brilla: el de 100 árboles y profundidad 12 procesó el lote a 3,8 microsegundos por fila, así que un millón de filas tomaría unos cuatro segundos en un hilo si ese ritmo se mantiene. En lote se diluye el costo fijo de cada llamada, aunque los árboles y la profundidad siguen pesando: 400 árboles tardaron cuatro veces más que 100.
- Detrás de una API, con cuidado. Si la decisión se toma en línea (aprobar un pago, marcar un correo), cada petición paga el costo por llamada y el modelo tiene que estar cargado en memoria en cada réplica. Ahí conviene limitar la profundidad, bajar el número de árboles hasta donde la curva deja de subir, o agrupar peticiones.
- Como línea base y como detector de variables. Antes de probar algo más complejo, un bosque con los valores por defecto da un número honesto en minutos. Para saber de qué variables depende, la importancia por permutación sobre datos de evaluación, no la que viene incluida.
El patrón de datos que le acomoda es la tabla: filas con columnas numéricas (y categóricas una vez codificadas como números, porque scikit-learn no acepta texto), unos cientos de miles de ejemplos, relaciones no lineales. Necesita menos preparación que otros modelos: no hay que escalar las variables. Para imágenes, audio o texto crudo no es la herramienta. Para detectar anomalías sin etiquetas existe un pariente con nombre parecido y mecánica distinta, el Random Cut Forest, que medí en Flink sobre eventos en streaming.
Cuándo lo elegiría: datos tabulares, necesidad de un buen resultado sin ajustar mucho, entrenamiento en varias CPU, y predicción en lote. Cuándo no: si hay que extrapolar fuera del rango visto, si el modelo tiene que ser pequeño y responder en microsegundos por petición, o si cada punto de precisión importa y hay tiempo para ajustar un boosting por histogramas, que en estos mismos datos igualó o superó por poco al bosque con una fracción del tiempo en una sola CPU. Y con tablas chicas vale la pena mirar más allá de los árboles: TabPFN, un modelo que ni siquiera entrena sobre la tabla, le ganó a XGBoost ajustado en las catorce tablas que medí.
Fuentes
- Breiman, L. (2001). «Random Forests». Machine Learning, 45(1), 5–32. DOI 10.1023/A:1010933404324.
- Ho, T. K. (1995). «Random decision forests». Proceedings of the 3rd International Conference on Document Analysis and Recognition, pp. 278–282. DOI 10.1109/ICDAR.1995.598994.
- scikit-learn, guía de usuario, «Ensembles: Gradient boosting, random forests, bagging, voting, stacking»: la nota de que su implementación promedia probabilidades en vez de contar votos, y la advertencia sobre la importancia por impureza.
- NASA POWER, Daily API y fuentes de datos: de ahí salen los datos diarios. La precipitación es
PRECTOTCORR, que la propia API define como precipitación total de MERRA-2 con corrección de sesgo. - Open-Meteo, Historical Weather API: la selección
best_match, que combina IFS HRES, ERA5 y ERA5-Land, y que me hizo descartar esa fuente.
Comentarios
Todavía no hay comentarios. El primero es tuyo.