我正在实现一种算法,作为其中的一部分,我需要生成指数随机变量。不幸的是,我无法真正避免循环,因为每个生成的随机变量都取决于前一个,所以我认为矢量化是不可能的。我在这一代人中做了一些计算,但瓶颈(目前)是一代。此时我假设N将很大(N> = 1,000,000)。
以下是一些示例代码:
N <- 1e7
#Preallocate
x <- rep(0, times=N)
#Set a starting seed
x[1] <- runif(1)
for(i in 2:N) {
#Do some calculations
x[i] <- x[i-1] + rexp(1, x[i-1]) #Bottleneck
#Do some more calculations
}
我怎样才能加快速度?我曾尝试在Rcpp中实施,但在这种情况下它似乎没有做太多。在每次迭代中,我是否有另一种聪明的方法可以绕过rexp()
调用?
答案 0 :(得分:3)
我们可以使用如果X~Exp(λ)然后kX_Exp(λ/ k) (source: Wikipedia)来加速代码的事实。通过这种方式,我们可以预先使用rate = 1
进行所有随机绘制,然后在循环内进行划分以适当地缩放它们。
draws = rexp(N, rate = 1)
x <- rep(0, times = N)
x[1] <- runif(1)
for(i in 2:N) {
#Do some calculations
x[i] <- x[i-1] + draws[i] / x[i-1]
#Do some more calculations
}
N = 1e6
值的微基准测试表明这个速度提高了约14倍:
N <- 1e6
draws = rexp(N, rate = 1)
x <- rep(0, times = N)
x[1] <- runif(1)
microbenchmark::microbenchmark(
draw_up_front = {
draws = rexp(N, rate = 1)
for (i in 2:N)
x[i] <- x[i - 1] + draws[i] / x[i - 1]
},
draw_one_at_time = {
for (i in 2:N)
x[i] <- x[i - 1] + rexp(1, x[i - 1])
},
times = 10
)
# Unit: milliseconds
# expr min lq mean median uq max neval cld
# draw_up_front 153.9547 156.6552 159.9622 160.1901 161.9803 167.2831 10 a
# draw_one_at_time 2207.1997 2212.0460 2280.1265 2236.5197 2332.9913 2478.5104 10 b
答案 1 :(得分:3)
蛮力Rcpp解决方案:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
NumericVector genExp(int N) {
NumericVector res(N);
double prev;
res[0] = unif_rand();
for (int i = 1; i < N; i++) {
prev = res[i-1];
res[i] = prev + exp_rand() / prev;
}
return res;
}
Microbenchmark与N = 1e6
:
Unit: milliseconds
expr min lq mean median uq max neval
draw_up_front 167.17031 168.57345 170.62292 170.18072 171.73782 175.46868 20
draw_one_at_time 1415.01898 1465.57139 1510.81220 1502.15753 1550.07829 1623.70831 20
rcpp 28.25466 29.33682 33.52528 29.89636 30.74908 94.38009 20
使用N = 1e7
:
Unit: milliseconds
expr min lq mean median uq max neval
draw_up_front 1698.730 1708.739 1737.8633 1716.1345 1752.3276 1923.3940 20
rcpp 297.142 319.794 338.6755 327.6626 364.6308 398.1554 20