加快循环中指数随机变量的重复生成
Speeding up repeated generation of Exponential random variables in loop
我正在实施一种算法,作为其中的一部分,我需要生成指数随机变量。不幸的是,我无法真正避免循环,因为每个生成的随机变量都依赖于前一个随机变量,所以我认为矢量化是不可能的。我围绕一代做了一些计算,但瓶颈(目前)是一代。在这一点上,我假设 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()
调用?
我们可以利用 if X ~ Exp(λ) then 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
暴力 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;
}
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
我正在实施一种算法,作为其中的一部分,我需要生成指数随机变量。不幸的是,我无法真正避免循环,因为每个生成的随机变量都依赖于前一个随机变量,所以我认为矢量化是不可能的。我围绕一代做了一些计算,但瓶颈(目前)是一代。在这一点上,我假设 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()
调用?
我们可以利用 if X ~ Exp(λ) then 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
暴力 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;
}
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