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

机器算法验证 r 模拟 随机生成 指数分布 分位数
2022-03-28 00:14:53

我的问题受到R的内置指数随机数生成器 function 的启发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.;

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

  • IT更容易编码,
  • IT 速度更快,并且
  • IT 和 AD 都可能失去数值准确性。

如果有人能解释为什么 R 仍然将 AD 作为rexp().

3个回答

在我的电脑上(请原谅我的法语!):

> 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

因此,里程仍然会有所不同,具体取决于规模!

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

在 FORTRAN 中,算法最好直接编程为 X=ALOG(REGOL(IR))避免任何子程序调用。成绩504μ每个样本秒。这次361μsec 由制造商的对数例程占用,并且 105μ秒由生成器生成均匀分布变量的 REGOLu. 因此,试图通过编写一个必须使用相同的两个子程序的汇编函数来提高速度是没有意义的。

所以看起来作者选择了其他方法来避免这种慢对数的“制造商”限制。也许这个问题最好转移到stackoverflow,在那里有R知识的人可以发表评论。

只需运行它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()

在此处输入图像描述