#1.- ABSTRACT

Los bosques aleatorios son una modificación de los árboles de decisión en bagging que crean una gran colección de árboles descorrelacionados para mejorar aún más el rendimiento predictivo. Se han convertido en un algoritmo de aprendizaje listo para usar muy popular que disfruta de un buen rendimiento predictivo con relativamente poco ajuste de hiperparámetros. Existen muchas implementaciones modernas de bosques aleatorios; sin embargo, se menciona el algoritmo de Leo Breiman (Breiman 2001).

#2.- INTRODUCCIÓN

Se emplearán la siguiente paquetería con el énfasis en cómo implementar bosques aleatorios con ranger (Wright and Ziegler 2017). Otra implementación de bosques se lleva cabo con h2o (LeDell et al. 2021). El resto de este material se basa fuertemente en el cap 11 de Boehmke and Greenwell (2020).

#3.- BIBLIOTECAS

library(mlbench)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.3     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.0
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(tidymodels)
## ── Attaching packages ────────────────────────────────────── tidymodels 1.1.1 ──
## ✔ broom        1.0.5     ✔ rsample      1.2.0
## ✔ dials        1.2.0     ✔ tune         1.1.2
## ✔ infer        1.0.5     ✔ workflows    1.1.3
## ✔ modeldata    1.3.0     ✔ workflowsets 1.0.1
## ✔ parsnip      1.1.1     ✔ yardstick    1.3.0
## ✔ recipes      1.0.8     
## ── Conflicts ───────────────────────────────────────── tidymodels_conflicts() ──
## ✖ scales::discard() masks purrr::discard()
## ✖ dplyr::filter()   masks stats::filter()
## ✖ recipes::fixed()  masks stringr::fixed()
## ✖ dplyr::lag()      masks stats::lag()
## ✖ yardstick::spec() masks readr::spec()
## ✖ recipes::step()   masks stats::step()
## • Search for functions across packages at https://www.tidymodels.org/find/
library(rpart.plot)
## Loading required package: rpart
## 
## Attaching package: 'rpart'
## 
## The following object is masked from 'package:dials':
## 
##     prune
library(vip)
## 
## Attaching package: 'vip'
## 
## The following object is masked from 'package:utils':
## 
##     vi
# bibliotecas auxiliares
library(dplyr) # manejo de datos
library(ggplot2) # mejores gráficas

# bibliotecas de modelación
library(ranger)  # implementación en c++ de random forest
library(h2o)     # implementación en jave-based de random forest
## 
## ----------------------------------------------------------------------
## 
## Your next step is to start H2O:
##     > h2o.init()
## 
## For H2O package documentation, ask for help:
##     > ??h2o
## 
## After starting H2O, you can use the Web UI at http://localhost:54321
## For more information visit https://docs.h2o.ai
## 
## ----------------------------------------------------------------------
## 
## 
## Attaching package: 'h2o'
## 
## The following objects are masked from 'package:lubridate':
## 
##     day, hour, month, week, year
## 
## The following objects are masked from 'package:stats':
## 
##     cor, sd, var
## 
## The following objects are masked from 'package:base':
## 
##     %*%, %in%, &&, ||, apply, as.factor, as.numeric, colnames,
##     colnames<-, ifelse, is.character, is.factor, is.numeric, log,
##     log10, log1p, log2, round, signif, trunc

~4.- MUESTRA (SAMPLE)

data("BreastCancer")
BCrec <- recipe(Class ~ ., data = BreastCancer) %>%
  step_naomit(all_predictors()) %>%
  prep(BreastCancer, verbose = FALSE) %>%
  bake(new_data = NULL)
BCrec2 <- BCrec %>% select(-Class,-Id) %>% mutate_all(as.numeric)
BCrec4 <- tibble(BCrec2,BCrec %>% select(Class))
set.seed(1234)
BCrec_split <- initial_split(BCrec4)

BCrec_train <- training(BCrec_split)
BCrec_test <- testing(BCrec_split)
BCrec4
## # A tibble: 683 × 10
##    Cl.thickness Cell.size Cell.shape Marg.adhesion Epith.c.size Bare.nuclei
##           <dbl>     <dbl>      <dbl>         <dbl>        <dbl>       <dbl>
##  1            5         1          1             1            2           1
##  2            5         4          4             5            7          10
##  3            3         1          1             1            2           2
##  4            6         8          8             1            3           4
##  5            4         1          1             3            2           1
##  6            8        10         10             8            7          10
##  7            1         1          1             1            2          10
##  8            2         1          2             1            2           1
##  9            2         1          1             1            2           1
## 10            4         2          1             1            2           1
## # ℹ 673 more rows
## # ℹ 4 more variables: Bl.cromatin <dbl>, Normal.nucleoli <dbl>, Mitoses <dbl>,
## #   Class <fct>
BCrec_train
## # A tibble: 512 × 10
##    Cl.thickness Cell.size Cell.shape Marg.adhesion Epith.c.size Bare.nuclei
##           <dbl>     <dbl>      <dbl>         <dbl>        <dbl>       <dbl>
##  1           10         4          4             6            2          10
##  2            4         1          2             1            2           1
##  3            4         1          1             1            2           1
##  4            1         1          1             1            2           1
##  5            8         5          6             2            3          10
##  6            7         5          6            10            5          10
##  7           10        10         10            10           10           1
##  8            3         1          1             1            2           1
##  9            8         5          5             5            2          10
## 10            2         2          2             1            1           1
## # ℹ 502 more rows
## # ℹ 4 more variables: Bl.cromatin <dbl>, Normal.nucleoli <dbl>, Mitoses <dbl>,
## #   Class <fct>
BCrec_test
## # A tibble: 171 × 10
##    Cl.thickness Cell.size Cell.shape Marg.adhesion Epith.c.size Bare.nuclei
##           <dbl>     <dbl>      <dbl>         <dbl>        <dbl>       <dbl>
##  1            3         1          1             1            2           2
##  2            4         1          1             3            2           1
##  3            8        10         10             8            7          10
##  4            2         1          2             1            2           1
##  5            2         1          1             1            2           1
##  6            7         4          6             4            6           1
##  7            4         1          1             1            2           1
##  8            7         3          2            10            5          10
##  9            1         1          1             1            2           1
## 10            3         2          1             1            1           1
## # ℹ 161 more rows
## # ℹ 4 more variables: Bl.cromatin <dbl>, Normal.nucleoli <dbl>, Mitoses <dbl>,
## #   Class <fct>
summary(BCrec4)
##   Cl.thickness      Cell.size        Cell.shape     Marg.adhesion  
##  Min.   : 1.000   Min.   : 1.000   Min.   : 1.000   Min.   : 1.00  
##  1st Qu.: 2.000   1st Qu.: 1.000   1st Qu.: 1.000   1st Qu.: 1.00  
##  Median : 4.000   Median : 1.000   Median : 1.000   Median : 1.00  
##  Mean   : 4.442   Mean   : 3.151   Mean   : 3.215   Mean   : 2.83  
##  3rd Qu.: 6.000   3rd Qu.: 5.000   3rd Qu.: 5.000   3rd Qu.: 4.00  
##  Max.   :10.000   Max.   :10.000   Max.   :10.000   Max.   :10.00  
##   Epith.c.size     Bare.nuclei      Bl.cromatin     Normal.nucleoli
##  Min.   : 1.000   Min.   : 1.000   Min.   : 1.000   Min.   : 1.00  
##  1st Qu.: 2.000   1st Qu.: 1.000   1st Qu.: 2.000   1st Qu.: 1.00  
##  Median : 2.000   Median : 1.000   Median : 3.000   Median : 1.00  
##  Mean   : 3.234   Mean   : 3.545   Mean   : 3.445   Mean   : 2.87  
##  3rd Qu.: 4.000   3rd Qu.: 6.000   3rd Qu.: 5.000   3rd Qu.: 4.00  
##  Max.   :10.000   Max.   :10.000   Max.   :10.000   Max.   :10.00  
##     Mitoses            Class    
##  Min.   :1.000   benign   :444  
##  1st Qu.:1.000   malignant:239  
##  Median :1.000                  
##  Mean   :1.583                  
##  3rd Qu.:1.000                  
##  Max.   :9.000
summary(BCrec_train)
##   Cl.thickness      Cell.size        Cell.shape     Marg.adhesion   
##  Min.   : 1.000   Min.   : 1.000   Min.   : 1.000   Min.   : 1.000  
##  1st Qu.: 2.000   1st Qu.: 1.000   1st Qu.: 1.000   1st Qu.: 1.000  
##  Median : 4.000   Median : 1.000   Median : 2.000   Median : 1.000  
##  Mean   : 4.484   Mean   : 3.211   Mean   : 3.281   Mean   : 2.906  
##  3rd Qu.: 6.000   3rd Qu.: 5.000   3rd Qu.: 5.000   3rd Qu.: 4.000  
##  Max.   :10.000   Max.   :10.000   Max.   :10.000   Max.   :10.000  
##   Epith.c.size     Bare.nuclei     Bl.cromatin     Normal.nucleoli 
##  Min.   : 1.000   Min.   : 1.00   Min.   : 1.000   Min.   : 1.000  
##  1st Qu.: 2.000   1st Qu.: 1.00   1st Qu.: 2.000   1st Qu.: 1.000  
##  Median : 2.000   Median : 1.00   Median : 3.000   Median : 1.000  
##  Mean   : 3.279   Mean   : 3.68   Mean   : 3.508   Mean   : 2.979  
##  3rd Qu.: 4.000   3rd Qu.: 7.00   3rd Qu.: 5.000   3rd Qu.: 4.000  
##  Max.   :10.000   Max.   :10.00   Max.   :10.000   Max.   :10.000  
##     Mitoses            Class    
##  Min.   :1.000   benign   :322  
##  1st Qu.:1.000   malignant:190  
##  Median :1.000                  
##  Mean   :1.641                  
##  3rd Qu.:1.000                  
##  Max.   :9.000
summary(BCrec_test)
##   Cl.thickness      Cell.size        Cell.shape     Marg.adhesion   
##  Min.   : 1.000   Min.   : 1.000   Min.   : 1.000   Min.   : 1.000  
##  1st Qu.: 2.000   1st Qu.: 1.000   1st Qu.: 1.000   1st Qu.: 1.000  
##  Median : 4.000   Median : 1.000   Median : 1.000   Median : 1.000  
##  Mean   : 4.316   Mean   : 2.971   Mean   : 3.018   Mean   : 2.602  
##  3rd Qu.: 5.000   3rd Qu.: 4.000   3rd Qu.: 4.000   3rd Qu.: 3.000  
##  Max.   :10.000   Max.   :10.000   Max.   :10.000   Max.   :10.000  
##   Epith.c.size     Bare.nuclei     Bl.cromatin     Normal.nucleoli 
##  Min.   : 1.000   Min.   : 1.00   Min.   : 1.000   Min.   : 1.000  
##  1st Qu.: 2.000   1st Qu.: 1.00   1st Qu.: 1.000   1st Qu.: 1.000  
##  Median : 2.000   Median : 1.00   Median : 2.000   Median : 1.000  
##  Mean   : 3.099   Mean   : 3.14   Mean   : 3.257   Mean   : 2.544  
##  3rd Qu.: 4.000   3rd Qu.: 4.00   3rd Qu.: 4.000   3rd Qu.: 3.000  
##  Max.   :10.000   Max.   :10.00   Max.   :10.000   Max.   :10.000  
##     Mitoses            Class    
##  Min.   :1.000   benign   :122  
##  1st Qu.:1.000   malignant: 49  
##  Median :1.000                  
##  Mean   :1.409                  
##  3rd Qu.:1.000                  
##  Max.   :9.000

#5.- EXTENSIÓN BAGGING

El ensacado o bagging de árboles introduce un componente aleatoria en el proceso de construcción de árboles al construir muchos árboles en copias de arranque de los datos de entrenamiento. El bagging luego agrega las predicciones en todos los árboles.

Los bosques aleatorios ayudan a reducir la correlación de árboles al inyectar más aleatoriedad en el proceso de crecimiento de los árboles. Más específicamente, mientras crece un árbol de decisión durante el proceso de baggging, los bosques aleatorios realizan una aleatorización de variables divididas donde cada vez que se realiza una división, la búsqueda de la variable dividida se limita a un subconjunto aleatorio de de las características . Los valores predeterminados típicos son mtry=p3 (regresión) y mtry=p‾√ (clasificación), pero esto debe considerarse un parámetro de ajuste.

El algoritmo básico para un bosque aleatorio de regresión o clasificación se puede generalizar de la siguiente manera:

Given a training data set Select number of trees to build (n_trees) for i = 1 to n_trees do Generate a bootstrap sample of the original data Grow a regression/classification tree to the bootstrapped data for each split do | Select m_try variables at random from all p variables | Pick the best variable/split-point among the m_try | Split the node into two child nodes end Use typical tree model stopping criteria to determine when a tree is complete (but do not prune) end Output ensemble of trees Dado que el algoritmo selecciona aleatoriamente una muestra de arranque para entrenar y una muestra aleatoria de características para usar en cada división, se produce un conjunto más diverso de árboles que tiende a disminuir la correlación de árboles más allá de los árboles en bolsas y, a menudo, aumenta drásticamente el poder predictivo.

#6.- RENDIMIENTO OOB

Los bosques aleatorios se han vuelto populares porque tienden a proporcionar un rendimiento muy bueno desde el primer momento. Aunque tienen varios hiperparámetros que se pueden ajustar, los valores predeterminados tienden a producir buenos resultados.

Por ejemplo, al entrenar un modelo de bosque aleatorio con todos los hiperparámetros configurados en sus valores predeterminados, obtenemos un OOB rmse que es mejor que cualquier modelo hasta ahora.

#7.- MODELO BÁSICO

# number of features
n_features <- length(setdiff(names(BCrec4), "Cell.size"))

# train a default random forest model
BC_rf1 <- ranger(
  Cell.size ~ ., 
  data = BCrec_train,
  mtry = floor(n_features / 3),
  respect.unordered.factors = "order",
  seed = 123
)

# get OOB RMSE
(default_rmse <- sqrt(BC_rf1$prediction.error))
## [1] 1.240171

#8.- HIPER PARAMETROS

Existen varios hiperparámetros ajustables a considerar al entrenar un modelo. Los principales incluyen:

La cantidad de árboles en el bosque El número de funciones a considerar en cualquier división determinada: mtry La complejidad de cada árbol El esquema de muestreo La regla de división que se debe usar durante la construcción del árbol.

#9.- AJUSTE ÓPTIMO

# cuadrícula de hiperparámetros
hyper_grid <- expand.grid(
  mtry = floor(n_features * c(.05, .15, .25, .333, .4)),
  min.node.size = c(1,3,5,10),
  replace = c(TRUE, FALSE),
  sample.fraction = c(.5, .63, .8),
  rmse = NA
)
# búsqueda cartesiana de cuadrícula
for(i in seq_len(nrow(hyper_grid))) {
  # fit model for ith hyperparameter combination
  fit <- ranger(
    formula         = Cell.size ~ ., 
    data            = BCrec_train, 
    num.trees       = n_features * 10,
    mtry            = hyper_grid$mtry[i],
    min.node.size   = hyper_grid$min.node.size[i],
    replace         = hyper_grid$replace[i],
    sample.fraction = hyper_grid$sample.fraction[i],
    verbose         = FALSE,
    seed            = 123,
    respect.unordered.factors = 'order',
  )
  
  # exportar error OOB  
  hyper_grid$rmse[i] <- sqrt(fit$prediction.error)
}

# modelos top
hyper_grid %>%
  arrange(rmse) %>%
  mutate(perc_gain = (default_rmse - rmse) / default_rmse * 100) %>%
  head(10)
##    mtry min.node.size replace sample.fraction     rmse   perc_gain
## 1     0             5    TRUE            0.63 1.240676 -0.04078014
## 2     3             5    TRUE            0.63 1.240676 -0.04078014
## 3     0            10    TRUE            0.63 1.243332 -0.25491073
## 4     3            10    TRUE            0.63 1.243332 -0.25491073
## 5     0            10   FALSE            0.80 1.245002 -0.38956505
## 6     3            10   FALSE            0.80 1.245002 -0.38956505
## 7     0             3    TRUE            0.50 1.247997 -0.63104506
## 8     3             3    TRUE            0.50 1.247997 -0.63104506
## 9     0            10   FALSE            0.50 1.249266 -0.73337182
## 10    3            10   FALSE            0.50 1.249266 -0.73337182
h2o.no_progress()
h2o.init(max_mem_size = "5g")
##  Connection successful!
## 
## R is connected to the H2O cluster: 
##     H2O cluster uptime:         5 minutes 16 seconds 
##     H2O cluster timezone:       Europe/Paris 
##     H2O data parsing timezone:  UTC 
##     H2O cluster version:        3.42.0.2 
##     H2O cluster version age:    6 months and 3 days 
##     H2O cluster name:           H2O_started_from_R_Usuario_dmg384 
##     H2O cluster total nodes:    1 
##     H2O cluster total memory:   4.09 GB 
##     H2O cluster total cores:    20 
##     H2O cluster allowed cores:  20 
##     H2O cluster healthy:        TRUE 
##     H2O Connection ip:          localhost 
##     H2O Connection port:        54321 
##     H2O Connection proxy:       NA 
##     H2O Internal Security:      FALSE 
##     R Version:                  R version 4.3.2 (2023-10-31 ucrt)
## Warning in h2o.clusterInfo(): 
## Your H2O cluster version is (6 months and 3 days) old. There may be a newer version available.
## Please download and install the latest version from: https://h2o-release.s3.amazonaws.com/h2o/latest_stable.html
# vip basada en impurezas
rf_impurity <- ranger(
  formula = Cell.size ~ ., 
  data = BCrec_train, 
  num.trees = 2000,
  mtry = 3,
  min.node.size = 1,
  sample.fraction = .80,
  replace = FALSE,
  importance = "impurity",
  respect.unordered.factors = "order",
  verbose = FALSE,
  seed  = 123
)

# vip basada en permutaciones
rf_permutation <- ranger(
  formula = Cell.size ~ ., 
  data = BCrec_train, 
  num.trees = 2000,
  mtry = 3,
  min.node.size = 1,
  sample.fraction = .80,
  replace = FALSE,
  importance = "permutation",
  respect.unordered.factors = "order",
  verbose = FALSE,
  seed  = 123
)
p1 <- vip::vip(rf_impurity, num_features = 25, bar = FALSE)
p2 <- vip::vip(rf_permutation, num_features = 25, bar = FALSE)

gridExtra::grid.arrange(p1, p2, nrow = 1)

#10.- CONCLUSIÓN

Los bosques aleatorios proporcionan un algoritmo que a menudo tiene una gran precisión predictiva. Vienen con todos los beneficios de los árboles de decisión (con la excepción de las divisiones sustitutas) y el bagging, pero reducen en gran medida la inestabilidad y la correlación entre árboles. Y debido al atributo de selección de variable de división agregado, los bosques aleatorios también son más rápidos que el bagging, ya que tienen un espacio de búsqueda de características más pequeño en cada división de árbol. Sin embargo, los bosques aleatorios todavía sufren de una velocidad computacional lenta a medida que las bases de datos se hacen grandes, pero, al igual que el bagging, el algoritmo se basa en pasos independientes y la mayoría de las implementaciones modernas (ranger, h2o) permiten la paralelización para mejorar el tiempo de entrenamiento.