使用Ahrens和Dieter(1972)的方法而不是通过逆变换的指数随机发生器的优点是什么?


11

我的问题是受R的内置指数随机数生成器函数启发的rexp()。当尝试生成指数分布的随机数时,许多教科书建议使用此Wikipedia页面中概述的逆变换方法。我知道还有其他方法可以完成此任务。特别是,R的源代码使用Ahrens&Dieter(1972)论文中概述的算法。

我已经说服自己,Ahrens-Dieter(AD)方法是正确的。不过,与逆变换(IT)方法相比,我看不出使用它们的方法的好处。AD不仅比IT实施更复杂。似乎也没有速度上的好处。这是我的R代码,用于对两种方法及其结果进行基准测试。

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)))

结果:

[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

比较这两种方法的代码,AD至少绘制两个统一的随机数(使用C函数unif_rand())以获得一个指数随机数。IT只需要一个统一的随机数。大概是R核心团队决定不实施IT,因为它假设采用对数可能比生成更统一的随机数慢。我了解对数的获取速度可能与机器有关,但至少对我而言是相反的。也许IT的数值精度与对数为0的奇异性有关吗?但是然后,R 源代码sexp.c揭示了AD的实现也失去了一些数值精度,因为C代码的以下部分从统一随机数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.;

稍后,在sexp.c的其余部分中将u作为统一的随机数回收。到目前为止,似乎

  • IT更容易编码,
  • IT更快,并且
  • IT和AD都可能会丢失数值精度。

如果有人能解释为什么R仍将AD作为实现的唯一可用选项,我将不胜感激rexp()


4
对于随机数生成器,“易编码”并不是真正的考虑因素,除非您是这样做的人!速度和准确性是仅有的两个考虑因素。(对于统一发电机,还有发电机的周期。)在过去,AD速度更快。在我的Linux机器上,AD的运行时间是invTrans函数的1/2倍,而在笔记本电脑上的运行时间是2/3。您可能也想将microbenchmark用于更全面的时间安排。
jbowman

5
我建议我们不要迁移它。对我来说,这似乎是话题。
变形虫说莫妮卡(Monica)恢复

1
鉴于我无法考虑出现rexp(n)瓶颈的单一情况,因此速度差异并不是改变的强烈理由(至少对我而言)。我可能会更关心数值精度,尽管我不清楚哪个数值上更可靠。
Cliff AB

1
@amoeba我认为“ ...的优点是什么”将是一个明确的表述,不会影响任何现有的答案。我猜想“为什么让R的人决定要做...”确实是(a)一个软件特定的问题,(b)需要文档中的证据或心灵感应,因此在这里可以说是不合时宜的。就我个人而言,我希望将问题改写为使其在网站范围内更清楚,但我认为这不是关闭它的足够有力的理由。
银鱼

1
@amoeba我去了。不敢相信我建议的新标题特别语法,问题文本的其他部分可能与更改有关。但是,我希望至少在主题上更明确,并且我不认为这无效或需要更改任一答案。
银鱼

Answers:


9

在我的计算机上(对不起,我的法语!):

> 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

逆变换的效果更糟。但是您应该注意可变性。引入速率参数会导致逆变换的更多可变性:

> 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 

以下是使用的比较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

因此,里程仍然取决于规模!


2
在我的笔记本电脑上,时间与OP的匹配时间非常接近,以至于我怀疑我们拥有相同的计算机(或至少具有相同的处理器)。但是我认为您的意思是观察到的速度优势取决于平台,并且鉴于最小的差异,在速度方面,没有一个相对于另一个优势明显。
悬崖AB

4
您可以microbenchmark代替吗?
Firebug

2
系统时间似乎衡量了高度可变的开销,这可能是由于中断和内存分页导致的。就像@Cliff指出的那样,有趣的是看到系统之间在性能方面存在相当大的相对差异。上有很多的RAM至强,例如,我招致几乎没有系统时间(0.05〜0.32秒)时,约长12%的用户的时间rexp,3%更短的用户的时间-log(runif()),并且与速率参数没有变化( 秒)。我们所有人都隐含地假设实现时间并与使用Fortran子例程所获得的时间相当。5.27±0.02Rlogrunif
Whuber

7

这只是引用“ LG算法:(对数方法)”部分下的文章:

在FORTRAN中,最好将算法直接编程为 避免调用任何子程序。表现得504每个样品秒。此时的361秒,就由制造商的对数例程和105秒由所述发电机为均匀分布的变量的REGOL。因此,试图通过编写必须使用相同的两个子程序的汇编器函数来提高速度没有任何意义。μ μ μ ùX=ALOG(REGOL(IR))μμμu

因此,似乎作者选择了其他方法来避免这种对数慢的“制造商”限制。也许最好将这个问题移到stackoverflow上,使对R胆识有所了解的人可以发表评论。


6

只是用这个运行microbenchmark; 在我的机器上,R的本机方法一致地更快:

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

为了新颖起见,这里要确保它不是完全由于:λ=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()

在此处输入图片说明

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.