lapply vs for loop-Rendimiento R
performance (2)
A menudo se dice que uno debería preferir
lapply
for
bucles.
Hay alguna excepción como, por ejemplo, Hadley Wickham señala en su libro Advance R.
( http://adv-r.had.co.nz/Functionals.html ) (Modificación en el lugar, Recursión, etc.). El siguiente es uno de este caso.
Solo por aprender, intenté reescribir un algoritmo de perceptrón en una forma funcional para comparar el rendimiento relativo. fuente ( https://rpubs.com/FaiHas/197581 ).
Aquí está el código.
# prepare input
data(iris)
irissubdf <- iris[1:100, c(1, 3, 5)]
names(irissubdf) <- c("sepal", "petal", "species")
head(irissubdf)
irissubdf$y <- 1
irissubdf[irissubdf[, 3] == "setosa", 4] <- -1
x <- irissubdf[, c(1, 2)]
y <- irissubdf[, 4]
# perceptron function with for
perceptron <- function(x, y, eta, niter) {
# initialize weight vector
weight <- rep(0, dim(x)[2] + 1)
errors <- rep(0, niter)
# loop over number of epochs niter
for (jj in 1:niter) {
# loop through training data set
for (ii in 1:length(y)) {
# Predict binary label using Heaviside activation
# function
z <- sum(weight[2:length(weight)] * as.numeric(x[ii,
])) + weight[1]
if (z < 0) {
ypred <- -1
} else {
ypred <- 1
}
# Change weight - the formula doesn''t do anything
# if the predicted value is correct
weightdiff <- eta * (y[ii] - ypred) * c(1,
as.numeric(x[ii, ]))
weight <- weight + weightdiff
# Update error function
if ((y[ii] - ypred) != 0) {
errors[jj] <- errors[jj] + 1
}
}
}
# weight to decide between the two species
return(errors)
}
err <- perceptron(x, y, 1, 10)
### my rewriting in functional form auxiliary
### function
faux <- function(x, weight, y, eta) {
err <- 0
z <- sum(weight[2:length(weight)] * as.numeric(x)) +
weight[1]
if (z < 0) {
ypred <- -1
} else {
ypred <- 1
}
# Change weight - the formula doesn''t do anything
# if the predicted value is correct
weightdiff <- eta * (y - ypred) * c(1, as.numeric(x))
weight <<- weight + weightdiff
# Update error function
if ((y - ypred) != 0) {
err <- 1
}
err
}
weight <- rep(0, 3)
weightdiff <- rep(0, 3)
f <- function() {
t <- replicate(10, sum(unlist(lapply(seq_along(irissubdf$y),
function(i) {
faux(irissubdf[i, 1:2], weight, irissubdf$y[i],
1)
}))))
weight <<- rep(0, 3)
t
}
No esperaba ninguna mejora constante debido a los problemas mencionados anteriormente.
Pero, sin embargo, me sorprendió mucho cuando vi el empeoramiento agudo de usar
lapply
y
replicate
.
microbenchmark
estos resultados usando la función
microbenchmark
biblioteca
microbenchmark
¿Cuáles podrían ser las razones? ¿Podría ser alguna pérdida de memoria?
expr min lq mean median uq
f() 48670.878 50600.7200 52767.6871 51746.2530 53541.2440
perceptron(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10) 4184.131 4437.2990 4686.7506 4532.6655 4751.4795
perceptronC(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10) 95.793 104.2045 123.7735 116.6065 140.5545
max neval
109715.673 100
6513.684 100
264.858 100
La primera función es la función
lapply
/
replicate
La segunda es la función con bucles
for
La tercera es la misma función en
C++
usando
Rcpp
Aquí, según Roland, el perfil de la función. No estoy seguro de poder interpretarlo de la manera correcta. Me parece que la mayor parte del tiempo se dedica a la subconfiguración de perfiles de funciones
Actualmente,
Probé la diferencia con un problema que resolvió recientemente.
Solo inténtalo tú mismo.
En mi conclusión, no tengo ninguna diferencia, pero for loop to my case fue significativamente más rápido que lapply.
Ps: Trato principalmente de mantener la misma lógica en uso.
ds <- data.frame(matrix(rnorm(1000000), ncol = 8))
n <- c(''a'',''b'',''c'',''d'',''e'',''f'',''g'',''h'')
func <- function(ds, target_col, query_col, value){
return (unique(as.vector(ds[ds[query_col] == value, target_col])))
}
f1 <- function(x, y){
named_list <- list()
for (i in y){
named_list[[i]] <- func(x, ''a'', ''b'', i)
}
return (named_list)
}
f2 <- function(x, y){
list2 <- lapply(setNames(nm = y), func, ds = x, target_col = "a", query_col = "b")
return(list2)
}
benchmark(f1(ds2, n ))
benchmark(f2(ds2, n ))
Como puede ver, hice una rutina simple para construir una lista nombrada basada en un marco de datos, la función func realiza los valores de columna extraídos, el f1 usa un bucle for para recorrer el marco de datos y el f2 usa una función lapply.
En mi computadora obtengo estos resultados:
test replications elapsed relative user.self sys.self user.child
1 f1(ds2, n) 100 110.24 1 110.112 0 0
sys.child
1 0
&&
test replications elapsed relative user.self sys.self user.child
1 f1(ds2, n) 100 110.24 1 110.112 0 0
sys.child
1 0
En primer lugar, es un mito ya desacreditado que los bucles son más lentos que el
lapply
.
Los bucles
for
en R se han hecho mucho más
lapply
y actualmente son al menos tan rápidos como el
lapply
.
Dicho esto, tienes que repensar tu uso de
lapply
aquí.
Su implementación exige la asignación al entorno global, porque su código requiere que actualice el peso durante el ciclo.
Y esa es una razón válida para no considerar
lapply
.
lapply
es una función que debe usar para sus efectos secundarios (o falta de efectos secundarios).
La función
lapply
combina los resultados en una lista automáticamente y no se mete con el entorno en el que trabaja, a diferencia de un bucle
for
.
Lo mismo vale para
replicate
.
Ver también esta pregunta:
¿La familia R aplica más que el azúcar sintáctico?
La razón por la que su solución de
lapply
es mucho más lenta es porque su forma de usarla crea muchos más gastos generales.
-
replicate
no es otra cosa quesapply
internamente, por lo que en realidad combinasapply
ylapply
para implementar su doble bucle.sapply
crea una sobrecarga adicional porque tiene que probar si el resultado puede simplificarse o no. Por lo tanto, un buclefor
será realmente más rápido que usarreplicate
. -
dentro de su función
lapply
anónima, debe acceder al marco de datos para x e y para cada observación. Esto significa que, en contra de su bucle for, por ejemplo, la función$
debe llamarse cada vez. -
Debido a que utiliza estas funciones de gama alta, su solución ''lapply'' llama a 49 funciones, en comparación con su solución
for
que solo llama a 26. Estas funciones adicionales para la soluciónlapply
incluyen llamadas a funciones comomatch
,structure
,[[
,names
,%in%
,sys.call
,duplicated
, ... Todas las funciones no son necesarias para su buclefor
ya que esa no realiza ninguna de estas comprobaciones.
Si desea ver de dónde proviene esta sobrecarga adicional, mire el código interno de
replicate
,
unlist
,
sapply
y
simplify2array
.
Puede usar el siguiente código para tener una mejor idea de dónde pierde su rendimiento con el
lapply
.
¡Corre esta línea por línea!
Rprof(interval = 0.0001)
f()
Rprof(NULL)
fprof <- summaryRprof()$by.self
Rprof(interval = 0.0001)
perceptron(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10)
Rprof(NULL)
perprof <- summaryRprof()$by.self
fprof$Fun <- rownames(fprof)
perprof$Fun <- rownames(perprof)
Selftime <- merge(fprof, perprof,
all = TRUE,
by = ''Fun'',
suffixes = c(".lapply",".for"))
sum(!is.na(Selftime$self.time.lapply))
sum(!is.na(Selftime$self.time.for))
Selftime[order(Selftime$self.time.lapply, decreasing = TRUE),
c("Fun","self.time.lapply","self.time.for")]
Selftime[is.na(Selftime$self.time.for),]