Es de sobra conocido que el tiempo es oro. No es lo mismo que un análisis en R tarde una hora que un día. Aquí os vamos a mostrar cómo se realizan los análisis a nivel computacional y cómo podemos hacer que vayan más rápido con la computación paralela en R.
El paquete caret es realmente una obra de arte. Nos permite integrar una cantidad enorme de modelos estadísticos y realizar con ellos validaciones cruzadas y un sinfín de otros análisis de optimización. Pero esto implica, normalmente, una elevada carga de trabajo y de análisis. Y cuanto más carga, más tiempo.
Para ello, en esta entrada os voy a mostrar cómo poder editar el código del paquete caret para aprovechar la computación paralela en R que ofrece. Así los análisis serán más rápidos sin perder un ápice de fiabilidad. Y os adelante que en el ejemplo, el análisis ha ido un 60% más rápido con computación paralela que sin ella. ¿Queréis saber cómo? Pues seguid leyendo.
Quiero leer...
Red neuronal como ejemplo
Sin entrar en detalles de cómo puede ejecutarse una red neuronal en R, que ya adelanto que el paquete neuralnet es maravilloso, vamos ejecutar el siguiente código de R:
tune.grid.neuralnet <- expand.grid( layer1 = c(1:5), layer2 = c(0:5), layer3 = c(0:5) ) nn <- function(formula, train, pre_mdl) { caret::train(formula, data = predict(pre_mdl, train), method = "neuralnet", tuneGrid = tune.grid.neuralnet, trControl = trainControl ( method = "cv", number = 10, verboseIter = FALSE, savePredictions = TRUE)) } nn(formula = formula(DC1 ~.), train = pk_dc_only$I, pre_mdl = pre_mdl_I)
Para poner un poco de contexto del código anterior, diremos que tune.grid.neuralnet
define el número de capas ocultas y neuronas que podría haber en cada una. Después hemos creado una función, nn()
con los parámetros de la red neuronal. Finalmente hemos ejecutado la función con la red neuronal.
¿Cuántos procesadores se emplean en ejecutar códigos de R?
En R se ejecutan códigos empleando solo 1 procesador. Esto es extensible, como es obvio, a RStudio. ¿Pero solo un procesador? ¿Y si tengo 16 CPUs? Pues nada, solamente 1. En esta entrada encontraréis un resumen estupendo sobre esto.
Por lo tanto, al margen del número de CPUs o procesadores que tengamos, de modo predeterminado solo tirará de uno. Y esto es un problema enorme porque ante demandas computacionales altas, es un factor limitante. En lugar de utilizar todo el potencial del ordenador, se encuentra limitado por esta estructura.
Y de aquí surge el concepto de computación paralela en R. Esto implica emplear el número de procesadores que queramos a la hora de ejecutar código de R de un modo paralelo. Como podréis intuir rápidamente, los tiempos de ejecución se van a reducir. Y esta reducción dependerá del número de procesadores que decidamos poner manos a la obra.
Comparación de cuando hay computación paralela en R y cuando no hay computación paralela
Vamos a ver las diferencias de tiempo que existe al ejecutar código de R cuando se emplea computación paralela en R y cuando no se emplea. Lógicamente hemos ejecutado el mismo.
Pero antes de continuar, es necesario puntualizar de que mi ordenador tiene 4 procesadores (4 CPUs).
Sin computación paralela
Si no usamos la computación paralela en R, mi único procesador empleado para ejecutar el código ha tardado 112 segundos en ejecutar la red neuronal.
proc.time() - t user system elapsed 111.746 0.001 112.256
Tal y como se puede ver en la imagen siguiente, sólo tengo un procesador trabajando al 100% (CPU 1). Es cierto que con el tiempo de ejecución se van alternando los procesadores, pero siempre es uno.

Con computación paralela
Ahora bien, si empleamos la computación paralela, vemos que el tiempo se ha reducido considerablemente, requiriendo solo 45 segundos en ejecutarse. ¡Esto es casi alrededor de un 60% más rápido! Y eso que solo he empleado 3 procesadores en lugar de los 4 que tengo. Si seguís leyendo podréis ver cómo seleccionar el número de CPUs dedicados a R.
proc.time() - t user system elapsed 3.006 0.065 44.981
Como podemos ver en la imagen siguiente, tengo 3 procesadores funcionando a pleno rendimiento (CPU 1, CPU 2 y CPU 4). Aunque algunas veces se van alternando, siempre son 3 los que están a pleno pulmón.

Cómo aplicar la computación paralela en R
Ya hemos visto la enorme diferencia de tiempo que hay entre emplear computación paralela en R y no. ¡Un 60% más rápido en nuestro ejemplo!
Nos toca ahora explicar cómo emplear la computación paralela en R y RStudio.
Necesitamos solo dos pequeños pasos que implican muy pocas líneas de código extra.
allowParallel
Dentro de la función trainControl()
tenemos que añadir un argumento extra especificando que haga computación paralela: allowParallel = TRUE
. El código de esa función quedaría así:
trControl = trainControl ( method = "cv", number = 10, verboseIter = FALSE, savePredictions = TRUE, allowParallel = TRUE) # esta es la línea importante
Paquete doFuture
Para que sea efectivo allowParallel
tenemos que escribir las siguientes líneas antes del código a ejecutar de un modo paralelo relacionadas con el paquete doFuture.
library(doFuture) registerDoFuture() plan(multiprocess, workers = availableCores() - 1)
En las líneas anteriores vamos a quedarnos con la última, ya que las dos primeras simplemente son cargar el paquete y ejecutar una función para que aplique a todo el script.
La última línea es la función plan()
en la que indicamos que sea una ejecución multiproceso o paralela (multiprocess
) y el número de procesadores a utilizar (workers
).
En relación con este último argumento, detectamos los procesadores de nuestro ordenador con la función availableCores()
y le restamos 1. Esta es una buena práctica, reservar 1 procesador para hacer otras tareas que necesitemos y no colapsar el ordenador. Es por esto que mi análisis se ha hecho con 3 procesadores en lugar de 4.
Finalmente, el código completo quedaría del siguiente modo:
library(doFuture) registerDoFuture() plan(multiprocess, workers = availableCores() - 1) tune.grid.neuralnet <- expand.grid( layer1 = c(1:5), layer2 = c(0:5), layer3 = c(0:5) ) nn <- function(formula, train, pre_mdl) { caret::train(formula, data = predict(pre_mdl, train), method = "neuralnet", tuneGrid = tune.grid.neuralnet, trControl = trainControl ( method = "cv", number = 10, verboseIter = FALSE, savePredictions = TRUE, allowParallel = TRUE)) } nn(formula = formula(DC1 ~.), train = pk_dc_only$I, pre_mdl = pre_mdl_I)
Muy buen artículo. A cualquiera que nos interese el tema nos será de gran utilidad. Gracias por tu trabajo.
Muy buena información, gran aporte al blog.