Una guía visual para ajustar hiperparámetros de bosques aleatorios

En mi publicación anterior Observé el impacto de diferentes hiperparametros en los árboles de decisión, tanto su rendimiento como cómo aparecen visualmente.

El siguiente paso natural, entonces, son los bosques aleatorios, usando sklearn.ensemble.RandomForestRegressor.

Nuevamente, no entraré en cómo funcionan los bosques aleatorios, áreas como el bote y la selección de características y la votación mayoritaria. Fundamentalmente, un bosque aleatorio es una gran cantidad de árboles que trabajan juntos (de ahí un bosque), y eso es todo lo que nos importa.

Usaré los mismos datos (el conjunto de datos de viviendas de California a través de Scikit-Learn, CC-by) y el mismo proceso general, por lo que si no ha visto mi publicación anterior, sugeriría leer eso primero, ya que pasa sobre algunas de las funciones y métricas que estoy usando aquí.

El código para esto está en el mismo repositorio que antes: https://github.com/jamesdeluk/data-projects/tree/main/visualising-trees

Como antes, todas las imágenes a continuación son creadas por mí.

Un bosque básico

Primero, veamos cómo funciona un bosque aleatorio básico, es decir rf = RandomForestRegressor(random_state=42). El modelo predeterminado tiene una profundidad máxima ilimitada y 100 árboles. Usando el método promedio de diez, tardó ~ 6 segundos en encajarse y ~ 0.1 segundos para predecir, dado que es un bosque y no un solo árbol, no es sorprendente que haya llevado de 50 a 150 veces más que el árbol de decisión profundo. ¿Y los puntajes?

Métrico max_depth = ninguno
Mae 0.33
Mape 0.19
MSE 0.26
RMSE 0.51
0.80

Predijo 0.954 para mi fila elegida, en comparación con el valor real de 0.894.

Sí, ¡el bosque aleatorio listo para usar funcionó mejor que el árbol de decisión de Bayes-Search de mi publicación anterior!

Visualizante

Hay algunas formas de visualizar un bosque aleatorio, como los árboles, las predicciones y los errores. Las importantes de características también se pueden utilizar para comparar los árboles individuales en un bosque.

Tiras de árbol individuales

Obviamente, puede trazar un árbol de decisión individual. Se puede acceder a ellos usando rf.estimators_. Por ejemplo, este es el primero:

Este tiene una profundidad de 34, 9,432 hojas y 18,863 nodos. ¡Y este bosque aleatorio tiene 100 árboles similares!

Predicciones individuales

Una forma en que me gusta visualizar bosques aleatorios es trazar las predicciones individuales para cada árbol. Por ejemplo, puedo hacerlo para mi fila elegida con [tree.predict(chosen[features].values) for tree in rf.estimators_]y traza los resultados en una dispersión:

Como Un recordatorio, el valor verdadero es 0.894. Puede ver fácilmente cómo, mientras algunos árboles estaban muy lejos, la media de todas las predicciones es bastante cercana, similar al teorema del límite central (CLT). Esta es mi forma favorita de ver la magia de los bosques al azar.

Errores individuales

Dando esto un paso más allá, puede iterar a través de todos los árboles, hacer que hagan predicciones para todo el conjunto de datos, luego calcule una estadística de error. En este caso, para MSE:

La MSE media fue ~ 0.30, tan ligeramente más alta que el bosque aleatorio general nuevamente mostrando la ventaja de un bosque sobre un solo árbol. El mejor árbol fue el número 32, con un MSE de 0.27; el peor, 74, fue 0.34 Aunque todavía es bastante decente. Ambos tienen profundidades de 34 ± 1, con ~ 9400 hojas y ~ 18000 nodos Entonces, estructuralmente, muy similar.

Importaciones de características

Claramente, una trama con todos los árboles sería difícil de ver, por lo que estas son las importantes para el bosque en general, con el mejor y peor árbol:

Los mejores y peores árboles todavía tienen importantes similares para las diferentes características. aunque el orden no es necesariamente el mismo. El ingreso mediano es, con mucho, el factor más importante basado en este análisis.

Ajuste de hiperparameter

Los mismos hiperparámetros que se aplican a los árboles de decisión individuales, por supuesto, se aplican a los bosques aleatorios compuestos por árboles de decisión. En aras de la comparación, creé algunos RF con los valores que había usado en la publicación anterior:

Métrico max_depth = 3 ccp_alpha = 0.005 min_samples_split = 10 min_samples_leaf = 10 max_leaf_nodes = 100
Tiempo para encajar (s) 1.43 25.04 3.84 3.77 3.32
Tiempo para predecir (s) 0.006 0.013 0.028 0.029 0.020
Mae 0.58 0.49 0.37 0.37 0.41
Mape 0.37 0.30 0.22 0.22 0.25
MSE 0.60 0.45 0.29 0.30 0.34
RMSE 0.78 0.67 0.54 0.55 0.58
0.54 0.66 0.78 0.77 0.74
Predicción elegida 1.208 1.024 0.935 0.920 0.969

Lo primero que vemos ninguno funcionó mejor que el árbol predeterminado (max_depth=None) arriba. Esto es diferente de los árboles de decisión individuales, donde los que tienen restricciones mejoran Una vez más, demostrando que el poder de un bosque imperfecto impulsado por CLT sobre un árbol “perfecto”. Sin embargo, similar a antes, ccp_alpha Toma mucho tiempo, y los árboles poco profundos son bastante basura.

Más allá de estos, hay algunos hiperparámetros que las RF tienen que los DT no lo hacen. El mas importante es n_estimators En otras palabras, ¡el número de árboles!

n_jobs

Pero primero, n_jobs. Esto es cuántos trabajos ejecutar en paralelo. Hacer las cosas en paralelo es típicamente más rápido que en serie/secuencialmente. El RF resultante será el mismo, con el mismo error, etc. puntajes (suponiendo random_state está configurado), pero debe hacerse más rápido! Para probar esto, agregué n_jobs=-1 a la RF predeterminada En este contexto, -1 significa “todo”.

¿Recuerdas cómo el predeterminado tardó casi 6 segundos en encajar y 0.1 para predecir? Paralelo, tardó solo 1.1 segundos y 0.03 para predecir una mejora de 3 ~ 6x. ¡Definitivamente haré esto de ahora en adelante!

n_estimadores

Ok, volviendo a la cantidad de árboles. El RF predeterminado tiene 100 estimadores; Probemos 1000. Tomó ~ 10 veces más largas (9.7 segundos para encajar, 0.3 para predecir, cuando es paralelo), como uno podría haber predicho. Los puntajes?

Métrico n_estimators = 1000
Mae 0.328
Mape 0.191
MSE 0.252
RMSE 0.502
0.807

Muy poca diferencia; MSE y RMSE son 0.01 más bajos y R² es 0.01 más alto. ¿Mejor, pero vale la pena la inversión de 10 veces?
Validemos cruzados, solo para verificar.

En lugar de usar mi bucle personalizado, usaré sklearn.model_selection.cross_validatecomo se tocó en la publicación anterior:

cross_validate(
    rf, X, y,
    cv=RepeatedKFold(n_splits=5, n_repeats=20, random_state=42),
    n_jobs=-1,
    scoring={
        "neg_mean_absolute_error": "neg_mean_absolute_error",
        "neg_mean_absolute_percentage_error": "neg_mean_absolute_percentage_error",
        "neg_mean_squared_error": "neg_mean_squared_error",
        "root_mean_squared_error": make_scorer(
            lambda y_true, y_pred: np.sqrt(mean_squared_error(y_true, y_pred)),
            greater_is_better=False,
        ),
        "r2": "r2",
    },
)

Estoy usando RepeatedKFold como la estrategia de división, que es más estable pero más lenta que KFold; Como el conjunto de datos no es tan grande, no estoy demasiado preocupado por el tiempo adicional que tomará.
Como no hay anotador RMSE estándar, tuve que crear uno con sklearn.metrics.make_scorer y una función lambda.

Para los árboles de decisión, hice 1000 bucles. Sin embargo, dado que el bosque aleatorio predeterminado contiene 100 árboles, 1000 bucles serían un lote de árboles, y por lo tanto tomar un lote de tiempo. Probaré 100 (20 repeticiones de 5 divisiones) Todavía mucho, pero gracias a la paralelización no fue también malo La versión de 100 árboles tomó 2 minutos (1304 segundos de tiempo sin igual), y el 1000 uno tomó 18 minutos (¡10254s!) Casi 100% de CPU en todos los núcleos, y se puso bastante tostado No es frecuente que mis fanáticos de MacBook se enciendan, ¡pero esto los alcanzó!

¿Cómo se comparan? El 100 árbol:

Métrico Significar Std
Mae -0.328 0.006
Mape -0.184 0.005
MSE -0.253 0.010
RMSE -0.503 0.009
0.810 0.007

y el de 1000 árboles:

Métrico Significar Std
Mae -0.325 0.006
Mape -0.183 0.005
MSE -0.250 0.010
RMSE -0.500 0.010
0.812 0.006

Muy poca diferencia Probablemente no valga la pena el tiempo/poder extra.

Bayes Búsqueda

Finalmente, hagamos una búsqueda en Bayes. Usé una amplia gama de hiperparameter.

search_spaces = {
    'n_estimators': (50, 500),
    'max_depth': (1, 100),
    'min_samples_split': (2, 100),
    'min_samples_leaf': (1, 100),
    'max_leaf_nodes': (2, 20000),
    'max_features': (0.1, 1.0, 'uniform'),
    'bootstrap': [True, False],
    'ccp_alpha': (0.0, 1.0, 'uniform'),
}

El único hiperparámetro que no hemos visto hasta ahora es bootstrap; Esto determina si utilizar todo el conjunto de datos al construir un árbol o usar un enfoque basado en bootstrap (muestra con reemplazo). Más comúnmente esto se establece en Truepero intentemos False de todos modos.

Hice 200 iteraciones, que tomaron 66 (!!) minutos. Dio:

Best Parameters: OrderedDict({
    'bootstrap': False,
    'ccp_alpha': 0.0,
    'criterion': 'squared_error',
    'max_depth': 39,
    'max_features': 0.4863711682589259,
    'max_leaf_nodes': 20000,
    'min_samples_leaf': 1,
    'min_samples_split': 2,
    'n_estimators': 380
})

Ver cómo max_depth era similar a los simples anteriores, pero n_estimators y max_leaf_nodes fueron muy altos (nota max_leaf_nodes no es el número real de nodos de hoja, solo el valor máximo permitido; El número medio de hojas fue de 14,954). min_samples_ ambos fueron el mínimo Similar a antes cuando comparamos los bosques restringidos con el sin restricciones. También es interesante cómo no fue bootstrap.

¿Qué nos da eso (la prueba rápida, no la cruzada validada)?

Métrico Valor
Mae 0.313
Mape 0.181
MSE 0.229
RMSE 0.478
0.825

Lo mejor hasta ahora, aunque solo. Para consistencia, también validada:

Métrico Significar Std
Mae -0.309 0.005
Mape -0.174 0.005
MSE -0.227 0.009
RMSE -0.476 0.010
0.830 0.006

Está funcionando muy bien. Comparando los errores absolutos para el mejor árbol de decisión (el Bayes Search One), la RF predeterminada y el Bayes buscó RF, nos da:

Conclusión

En la última publicación, el árbol de decisión de Bayes parecía bueno, especialmente en comparación con el árbol de decisión básico; ¡Ahora parece terrible, con errores más altos, R² más bajos y variaciones más amplias! Entonces, ¿por qué no usar siempre un bosque aleatorio?

Bueno, los bosques aleatorios tardan mucho más en encajar (y predecir), y esto se vuelve aún más extremo con conjuntos de datos más grandes. Hacer miles de iteraciones de sintonización en un bosque con cientos de árboles y un conjunto de datos de millones de filas y cientos de características … incluso con paralelización, puede llevar mucho tiempo. Deja bastante claro por qué las GPU, que se especializan en el procesamiento paralelo, se han vuelto esenciales para el aprendizaje automático. Aun así, tienes que preguntarte ¿Qué es lo suficientemente bueno? ¿La mejora de ~ 0.05 en MAE realmente importa para su caso de uso?

Cuando se trata de visualización, como con los árboles de decisión, trazar árboles individuales puede ser una buena manera de tener una idea de la estructura general. Además, trazar las predicciones y errores individuales es una excelente manera de ver la varianza de un bosque aleatorio y comprender mejor cómo funcionan.

¡Pero hay más variantes de árbol! A continuación, el gradiente aumentó los.