代码之家  ›  专栏  ›  技术社区  ›  wojciesz

R中随机过程的10^9步快速生成

  •  5
  • wojciesz  · 技术社区  · 7 年前

    我要执行以下任务:

    生成10^9个由公式描述的过程步骤:

    X(0)=0
    X(t+1)=X(t)+Y(t)
    

    哪里 Y(t) 是具有分布的独立随机变量 N(0,1) . 计算指数的百分比 t 的价值 X(t) 为阴性。

    我尝试了以下代码:

      x<-c(0,0)
      z<-0
      loop<-10^9
      for(i in 2:loop) {
        x[1]<-x[2]
        x[2]<-x[1]+rnorm(1, 0, 1)
        if (x[2]<0) {z<-z+1}
      }
    

    然而,这是非常缓慢的。我怎样才能加快速度?

    5 回复  |  直到 7 年前
        1
  •  9
  •   Claus Wilke    7 年前

    通常,对于此类问题,可以使用Rcpp包将函数一对一转换为C++。这应该会大大加快速度。

    首先,R版本:

    random_sum <- function(loop = 1000) {
      x<-c(0,0)
      z<-0
      for(i in 2:loop) {
        x[1]<-x[2]
        x[2]<-x[1]+rnorm(1, 0, 1)
        if (x[2]<0) {z<-z+1}
      }
      z / loop
    }
    set.seed(123)
    random_sum()
    # [1] 0.134
    

    现在是C++版本:

    library("Rcpp")
    cppFunction("
      double random_sum_cpp(unsigned long loop = 1000) {
        double x1 = 0;
        double x2 = 0;
        double z = 0;
        for (unsigned long i = 2; i < loop; i++) {
          x1 = x2;
          x2 = x1 + Rcpp::rnorm(1)[0];
          if (x2 < 0) z = z+1;
        }
        return z/loop;
      }")
    
    set.seed(123)
    random_sum_cpp()
    # [1] 0.134
    

    为了完整性,我们还考虑一下所提出的矢量化版本:

    random_sum_vector <- function(loop = 1000) {
      Y = rnorm(loop)
      sum(cumsum(Y)<0)/loop
    }
    set.seed(123)
    random_sum_vector()
    # [1] 0.134
    

    我们看到,对于相同的随机种子,它给出了相同的结果,因此它似乎是一个可行的竞争者。

    在基准测试中,C++版本和矢量化版本的性能类似,矢量化版本显示出略高于C++版本的优势:

    > microbenchmark(random_sum(100000),
                     random_sum_vector(100000),
                     random_sum_cpp(100000))
    Unit: milliseconds
                         expr        min         lq       mean     median         uq       max neval
            random_sum(1e+05) 184.205588 199.859266 209.220232 205.137043 211.026740 274.47615   100
     random_sum_vector(1e+05)   6.320690   6.631704   7.273645   6.799093   7.334733  18.48649   100
        random_sum_cpp(1e+05)   8.950091   9.362303  10.663295   9.956996  11.079513  21.30898   100
    

    然而,矢量化版本在速度与内存和 will blow up your memory for long loops. C++版本几乎不使用内存。

    对于10^9个步骤,C++版本在我的机器上运行大约需要2分钟(110秒)。我没有尝试R版本。根据较短的基准,可能需要7个小时左右。

    > microbenchmark(random_sum_cpp(10^9), times = 1)
    Unit: seconds
                     expr      min       lq     mean   median       uq      max neval
     random_sum_cpp(10^9) 110.2182 110.2182 110.2182 110.2182 110.2182 110.2182     1
    
        2
  •  4
  •   G5W    7 年前

    这应该快得多,但任何事情都可能需要一段时间。用较小的长度值(如10^6)来测试这一点可能会更好。

    length = 10^9
    Y = rnorm(length)
    sum(cumsum(Y)<0)/length
    

    编辑

    根据@user3666197的评论,我对此进行了测试,他是正确的。 此解决方案适用于较小的数字,但一旦步骤数过大,它就会失败。

    我用OP的代码测试了我的“矢量化”版本。当随机游走的长度为10^8时,我的代码用了大约7秒,OP的代码用了131秒(在我的笔记本电脑上)。然而,当我将长度增加到10^9时(根据最初的问题),我的版本导致了大量磁盘交换,我不得不终止该进程。此解决方案在OP要求的规模下失败。

        3
  •  3
  •   Eric Watt    7 年前

    一种解决方案是使用@G5W提出的矢量化方法,但将其分解为更小的块以避免任何内存溢出问题。这为您提供了矢量化解决方案的速度,但通过管理块大小,您可以控制进程使用的内存量。

    下面将问题分解为1e+07块,循环100次,总共得到1e+09块。

    在第一个块的末尾,记录低于0的时间百分比和终点。然后将结束点输入到下一个块,并记录0以下的时间百分比和新的结束点。

    最后,平均100次跑步,使总时间低于零。呼叫 cat 在while循环中,可以监视进度并查看进度,这可以被注释掉。

    funky <- function(start, length = 1e+07) {
      Y <- rnorm(length)
      Z <- cumsum(Y)
      c(sum(Z<(-start))/length, (tail(Z, 1) + start))
    }
    
    starttime <- Sys.time()
    resvect <- vector(mode = "numeric", length = 100)
    result <- funky(0)
    resvect[1] <- result[1]
    i <- 2
    while (i < 101) {
      cat(result, "\n")
      result <- funky(result[2])
      resvect[i] <- result[1]
      i <- i + 1
    }
    mean(resvect)
    # [1] 0.1880392
    endtime <- Sys.time()
    elapsed <- endtime - starttime
    elapsed
    # Time difference of 1.207566 mins
    
        4
  •  0
  •   user3666197    7 年前

    如果随机性源在技术上被构造为其他确定性硬件的一种能力,以满足生成流的重复性要求以及通过某种伪随机生成器算法实现“生成”随机性的所有条件,那么这种随机性源不容易从纯- [SERIAL] 变成任何形式的“正义”- [CONCURRENT] 或者是真的- [PARALLEL] 作案手法。

    也就是说,PRG步骤是任何试图重新定义纯- [序列号] 代码执行。

    这不会改变(非)负的百分比 X(t) -值,但仅确定对于给定的PRG硬件实现,没有更短的方法,而是- [序列号] 相互(连续)依赖值的生成顺序。

    展开“慢速”循环或准-( 因为这些值仍然是连续相关的 )-矢量化过程(R语言实现的特点是利用但几乎是硬件CPU指令集级别的技巧,因此不是语言游戏规则的改变者,而是绕过一些故意缓慢的代码执行构造函数)是最可能发生的。

        5
  •  0
  •   AlphaDrivers    7 年前

    使用向量通常会产生比循环更好的性能。对于非常大的数字(即10^9),这里的问题是内存限制。由于您只对负指数的最终百分比感兴趣,因此以下内容将起作用(在10^9步骤中需要几分钟)。

    update_state <- function (curr_state, step_size) {
      n <- min(curr_state$counter, step_size)
      r <- rnorm(min(curr_state$counter, step_size))
      total <- curr_state$cum_sum + cumsum(r)
    
      list('counter' = curr_state$counter - n,
           'neg_count' = curr_state$neg_count + length(which(total < 0)),
           'cum_sum' = curr_state$cum_sum + sum(r))
    }
    
    
    n <- 10^9
    curr_state <- list('counter' = n, 'neg_count' = 0, 'cum_sum' = 0)
    
    step_size <- 10^8
    while (curr_state$counter > 0) {
      curr_state <- update_state(curr_state = curr_state, step_size = step_size)
    }
    
    print(curr_state)
    print(curr_state$neg_count/ n)