¿Cuáles son las ventajas de un generador aleatorio exponencial que utiliza el método de Ahrens y Dieter (1972) en lugar de la transformación inversa?

11

Mi pregunta está inspirada en el generador de números aleatorios exponencial incorporado de R , la función rexp(). Al intentar generar números aleatorios distribuidos exponencialmente, muchos libros de texto recomiendan el método de transformación inversa como se describe en esta página de Wikipedia . Soy consciente de que hay otros métodos para realizar esta tarea. En particular, el código fuente de R utiliza el algoritmo descrito en un artículo de Ahrens y Dieter (1972) .

Me he convencido de que el método Ahrens-Dieter (AD) es correcto. Aún así, no veo el beneficio de usar su método en comparación con el método de transformación inversa (IT). AD no solo es más complejo de implementar que TI. Tampoco parece haber un beneficio de velocidad. Aquí está mi código R para comparar ambos métodos seguidos de los resultados.

invTrans <- function(n)
    -log(runif(n))
print("For the inverse transform:")
print(system.time(invTrans(1e8)))
print("For the Ahrens-Dieter algorithm:")
print(system.time(rexp(1e8)))

Resultados:

[1] "For the inverse transform:" 
user     system     elapsed
4.227    0.266      4.597 
[1] "For the Ahrens-Dieter algorithm:"
user     system     elapsed
4.919    0.265      5.213

Al comparar el código de los dos métodos, AD dibuja al menos dos números aleatorios uniformes (con la función Cunif_rand() ) para obtener un número aleatorio exponencial. Solo necesita un número aleatorio uniforme. Presumiblemente, el equipo central de R decidió no implementar TI porque asumió que tomar el logaritmo puede ser más lento que generar números aleatorios más uniformes. Entiendo que la velocidad de tomar logaritmos puede depender de la máquina, pero al menos para mí lo contrario es cierto. ¿Quizás haya problemas en torno a la precisión numérica de TI que tiene que ver con la singularidad del logaritmo en 0? Pero entonces, el código fuente R sexp.crevela que la implementación de AD también pierde algo de precisión numérica porque la siguiente parte del código C elimina los bits iniciales del número aleatorio uniforme u .

double u = unif_rand();
while(u <= 0. || u >= 1.) u = unif_rand();
for (;;) {
    u += u;
    if (u > 1.)
        break;
    a += q[0];
}
u -= 1.;

u más tarde se recicla como un número aleatorio uniforme en el resto de sexp.c . Hasta ahora, parece que

  • Es más fácil codificar,
  • Es más rápido y
  • tanto IT como AD posiblemente pierdan precisión numérica.

Realmente agradecería si alguien pudiera explicar por qué R todavía implementa AD como la única opción disponible para rexp().

Pratyush Más
fuente
44
Con los generadores de números aleatorios, ¡"más fácil de codificar" no es realmente una consideración a menos que sea usted quien lo haga! La velocidad y la precisión son las dos únicas consideraciones. (Para generadores uniformes, también existe el período del generador). En los viejos tiempos, AD era más rápido. En mi caja de Linux, AD se ejecuta en aproximadamente la mitad del tiempo que su función invTrans, y en mi computadora portátil en aproximadamente 2/3 del tiempo. Es posible que también desee utilizar microbenchmark para tiempos más completos.
jbowman
55
Sugeriría que no lo migremos. Esto me parece sobre el tema.
ameba dice Reinstate Monica
1
Dado que no puedo pensar en un solo escenario en el que rexp(n)sería el cuello de botella, la diferencia de velocidad no es un argumento fuerte para el cambio (al menos para mí). Podría estar más preocupado por la precisión numérica, aunque no me queda claro cuál sería más confiable numéricamente.
Cliff AB
1
@amoeba Creo que "¿Cuáles serían las ventajas de ..." sería una reformulación que sería claramente sobre el tema aquí, y no afectaría las respuestas existentes. Supongo que "¿Por qué las personas que hicieron que R decidiera hacer ...?" Es realmente (a) una pregunta específica de software, (b) requiere evidencia en la documentación o telepatía, por lo que podría decirse que está fuera de tema aquí. Personalmente, prefiero que la pregunta se reformule para que sea más clara dentro del alcance del sitio, pero no veo esto como una razón lo suficientemente fuerte como para cerrarlo.
Silverfish
1
@amoeba He tenido una oportunidad. No estoy convencido de que mi nuevo título sugerido sea especialmente gramatical, y quizás algunas otras partes del texto de la pregunta podrían cambiar. Pero espero que esto sea más claramente sobre el tema, al menos, y no creo que invalide o requiera cambios para responder.
Silverfish

Respuestas:

9

En mi computadora (¡perdón por mi francés!):

> print(system.time(rexp(1e8)))
utilisateur     système      écoulé 
      4.617       0.320       4.935 
> print(system.time(rexp(1e8)))
utilisateur     système      écoulé 
      4.589       2.045       6.629 
> print(system.time(-log(runif(1e8))))
utilisateur     système      écoulé 
      7.455       1.080       8.528 
> print(system.time(-log(runif(1e8))))
utilisateur     système      écoulé 
      9.140       1.489      10.623

la transformación inversa empeora. Pero debes tener cuidado con la variabilidad. La introducción de un parámetro de velocidad conduce a una variabilidad aún mayor para la transformación inversa:

> print(system.time(rexp(1e8,rate=.01)))
utilisateur     système      écoulé 
      4.594       0.456       5.047 
> print(system.time(rexp(1e8,rate=.01)))
utilisateur     système      écoulé 
      4.661       1.319       5.976 
> print(system.time(-log(runif(1e8))/.01))
utilisateur     système      écoulé 
     15.675       2.139      17.803 
> print(system.time(-log(runif(1e8))/.01))
utilisateur     système      écoulé 
      7.863       1.122       8.977 
> print(system.time(rexp(1e8,rate=101.01)))
utilisateur     système      écoulé 
      4.610       0.220       4.826 
> print(system.time(rexp(1e8,rate=101.01)))
utilisateur     système      écoulé 
      4.621       0.156       4.774 
> print(system.time(-log(runif(1e8))/101.01))
utilisateur     système      écoulé 
      7.858       0.965       8.819 > 
> print(system.time(-log(runif(1e8))/101.01))
utilisateur     système      écoulé 
     13.924       1.345      15.262 

Aquí están las comparaciones usando rbenchmark:

> benchmark(x=rexp(1e6,rate=101.01))
  elapsed user.self sys.self
  4.617     4.564    0.056
> benchmark(x=-log(runif(1e6))/101.01)
  elapsed user.self sys.self
  14.749   14.571    0.184
> benchmark(x=rgamma(1e6,shape=1,rate=101.01))
  elapsed user.self sys.self
  14.421   14.362    0.063
> benchmark(x=rexp(1e6,rate=.01))
  elapsed user.self sys.self
  9.414     9.281    0.136
> benchmark(x=-log(runif(1e6))/.01)
  elapsed user.self sys.self
  7.953     7.866    0.092
> benchmark(x=rgamma(1e6,shape=1,rate=.01))
  elapsed user.self sys.self
  26.69    26.649    0.056

¡Entonces el kilometraje todavía varía, dependiendo de la escala!

Xi'an
fuente
2
En mi computadora portátil, los tiempos coinciden con los OP tan estrechamente que sospecho que tenemos la misma máquina (o al menos el mismo procesador). Pero creo que su punto aquí es que la ventaja de velocidad observada depende de la plataforma, y ​​dada la mínima diferencia, no hay una ventaja clara para uno sobre el otro con respecto a la velocidad.
Cliff AB
44
¿Podrías quizás realizar un microbenchmarken su lugar?
Firebug
2
Los tiempos del sistema parecen medir una sobrecarga muy variable, quizás debido a interrupciones y paginación de memoria. Es interesante, como señala @Cliff, ver diferencias relativas considerables en el rendimiento entre sistemas. En un Xeon con mucha RAM, por ejemplo, casi no incurro en tiempo de sistema (0.05 a 0.32 segundos), aproximadamente 12% más de tiempo de usuario para rexp, 3% tiempo de usuario más corto para -log(runif()), y sin variabilidad con un parámetro de velocidad ( segundos totales). Todos estamos asumiendo implícitamente que está logrando tiempos y es comparable a lo que uno obtendría con una subrutina Fortran. 5.27±0.02Rlogrunif
whuber
7

Esto es solo citando el artículo en la sección "Algoritmo LG: (Método de logaritmo)":

En FORTRAN, el algoritmo se programa mejor directamente como evitando cualquier llamada de subprograma. El rendimiento fue de 504 seg por muestra. De este tiempo, 361 seg. Fue tomada por la rutina de logaritmo del fabricante y 105 seg. Por el generador para REGOL de la variable uniformemente distribuida . Por lo tanto, no tenía sentido tratar de mejorar la velocidad escribiendo una función de ensamblador que tendría que usar los mismos dos subprogramas.μ μ μ uX=ALOG(REGOL(IR))μμμu

Por lo tanto, parece que los autores optaron por otros métodos para evitar esta limitación "fabricante" de logaritmos lentos. Quizás esta pregunta se traslade mejor a stackoverflow donde alguien con conocimiento en las entrañas de R pueda comentar.

Alex R.
fuente
6

Solo ejecuta esto con microbenchmark; en mi máquina, el enfoque nativo de R es uniformemente más rápido:

library(microbenchmark)
microbenchmark(times = 10L,
               R_native = rexp(1e8),
               dir_inv = -log(runif(1e8)))
# Unit: seconds
#      expr      min       lq     mean   median       uq      max neval
#  R_native 3.643980 3.655015 3.687062 3.677351 3.699971 3.783529    10
#   dir_inv 5.780103 5.783707 5.888088 5.912384 5.946964 6.050098    10

Por el bien de la novedad, aquí nos aseguramos de que no se deba totalmente a tener :λ=1

lambdas = seq(0, 10, length.out = 25L)[-1L]
png("~/Desktop/micro.png")
matplot(lambdas, 
        ts <- 
          t(sapply(lambdas, function(ll)
            print(microbenchmark(times = 50L,
                                 R_native = rexp(5e5, rate = ll),
                                 dir_inv = -log(runif(5e5))/ll),
                  unit = "relative")[ , "median"])),
        type = "l", lwd = 3L, xlab = expression(lambda),
        ylab = "Relative Timing", lty = 1L,
        col = c("black", "red"), las = 1L,
        main = paste0("Direct Computation of Exponential Variates\n",
                      "vs. R Native Generator (Ahrens-Dieter)"))
text(lambdas[1L], ts[1L, ], c("A-D", "Direct"), pos = 3L)
dev.off()

ingrese la descripción de la imagen aquí

MichaelChirico
fuente