【问题标题】:Using R parallel to speed up bootstrap使用 R 并行加速引导程序
【发布时间】:2013-04-05 09:27:29
【问题描述】:

我想加快我的引导功能,它本身工作得很好。我读到自 R 2.14 以来有一个名为 parallel 的包,但我发现它对某人来说非常困难。计算机科学知识很少,无法真正实现它。也许有人可以帮忙。

所以这里我们有一个引导程序:

n<-1000
boot<-1000
x<-rnorm(n,0,1)
y<-rnorm(n,1+2*x,2)
data<-data.frame(x,y)
boot_b<-numeric()
for(i in 1:boot){
  bootstrap_data<-data[sample(nrow(data),nrow(data),replace=T),]
  boot_b[i]<-lm(y~x,bootstrap_data)$coef[2]
  print(paste('Run',i,sep=" "))
}

目标是使用并行处理/利用我 PC 的多个内核。我在 Windows 下运行 R。谢谢!

编辑(诺亚回复后)

以下语法可用于测试:

library(foreach)
library(parallel)
library(doParallel)
registerDoParallel(cores=detectCores(all.tests=TRUE))
n<-1000
boot<-1000
x<-rnorm(n,0,1)
y<-rnorm(n,1+2*x,2)
data<-data.frame(x,y)
start1<-Sys.time()
boot_b <- foreach(i=1:boot, .combine=c) %dopar% {
  bootstrap_data<-data[sample(nrow(data),nrow(data),replace=T),]
  unname(lm(y~x,bootstrap_data)$coef[2])
}
end1<-Sys.time()
boot_b<-numeric()
start2<-Sys.time()
for(i in 1:boot){
  bootstrap_data<-data[sample(nrow(data),nrow(data),replace=T),]
  boot_b[i]<-lm(y~x,bootstrap_data)$coef[2]
}
end2<-Sys.time()
start1-end1
start2-end2
as.numeric(start1-end1)/as.numeric(start2-end2)

但是,在我的机器上,简单的 R 代码更快。这是并行处理的已知副作用之一吗,即它会导致分叉进程的开销,从而增加像这样的“简单任务”中的时间?

编辑:在我的机器上,parallel 代码比“简单”代码耗时大约 5 倍。这个因素显然不会随着我增加任务的复杂性而改变(例如增加bootn)。所以也许代码或我的机器有问题(基于 Windows 的处理?)。

【问题讨论】:

    标签: r parallel-processing statistics-bootstrap


    【解决方案1】:

    这是一个古老的问题,但我认为使用data.table 可以提高其中的很多效率。在使用更大的数据集之前,不会真正注意到这些好处。把这个答案放在这里是为了帮助其他可能需要引导更大数据集的人

    library(data.table)
    setDT(data) # convert data.frame to data.table by reference
    system.time({
      b <- rbindlist(
        lapply(
          1:boot,
          function(i) {
            data.table(
              # store the statistic
              'statistic' = lm(y ~ x, data=data[sample(.N, .N, replace = T)])$coef[[2]],
              # store the iteration
              'iteration' = i
            )
          }
        )
      )
    })
    # 1.66 seconds on my system
    ggplot(b) + geom_density(aes(x = statistic))
    

    然后您可以通过使用parallel 包进一步提高性能。

    library(parallel)
    cl <- makeCluster(detectCores())            # use all cores on machine, can change this
    
    clusterExport(                              # give it the variables it needs #nolint
      cl,
      c(
        "data"
      ),
      envir = environment()
    )
    clusterEvalQ(                               # give it libraries needed #nolint
      cl,
      c(
        library(data.table)
      )
    )
    
    system.time({
      b <- rbindlist(
        parLapply(              # this is changed to be in parallel
          cl,                   # give it the cluster you created earlier
          1:boot,
          function(i) {
            data.table(
              'statistic' = lm(y ~ x, data=data[sample(.N, .N, replace = T)])$coef[[2]],
              'iteration' = i
            )
          }
        )
      )
    })
    
    stopCluster(cl)
    # .47 seconds on my machine
    

    【讨论】:

      【解决方案2】:

      我认为主要问题是你有很多小任务。在某些情况下,您可以通过使用 task chunking 来提高性能,这会导致 master 和 worker 之间的数据传输更少但更大,这通常更有效:

      boot_b <- foreach(b=idiv(boot, chunks=getDoParWorkers()), .combine='c') %dopar% {
        sapply(1:b, function(i) {
          bdata <- data[sample(nrow(data), nrow(data), replace=T),]
          lm(y~x, bdata)$coef[[2]]
        })
      }
      

      我喜欢为此使用idiv 函数,但如果您愿意,也可以使用b=rep(boot/detectCores(),detectCores())

      【讨论】:

        【解决方案3】:

        我没有在 Windows 上使用parallel 后端测试foreach,但我相信这对你有用:

        library(foreach)
        library(doSNOW)
        
        cl <- makeCluster(c("localhost","localhost"), type = "SOCK")
        registerDoSNOW(cl=cl)
        
        n<-1000
        boot<-1000
        x<-rnorm(n,0,1)
        y<-rnorm(n,1+2*x,2)
        data<-data.frame(x,y)
        boot_b <- foreach(i=1:boot, .combine=c) %dopar% {
          bootstrap_data<-data[sample(nrow(data),nrow(data),replace=T),]
          unname(lm(y~x,bootstrap_data)$coef[2])
        }
        

        【讨论】:

        • 谢谢,我将建议的语法提交给测试(上面编辑过的代码)。它现在使用了我 100% 的 CPU(即所有处理器)。但是,这比没有并行处理的情况下要慢,见上文。
        • 如果您能就时间问题提供任何其他建议会很棒,即为什么您的建议没有加快速度?谢谢。
        • 嗯。有趣的。在我的机器上(8 个 HT 内核,8 GB 内存,Ubuntu 12.04),我得到了大约 3.4 倍的加速,而 RAM 使用量很少。我对 Windows 环境中的多线程不太熟悉。以下是一些可以尝试的方法:
        • 1) 将一个数字传递给registerDoParallelcores 参数,而不是尝试自动检测它们。 2) 对任务管理器进行排序,降低 CPU 使用率,并查看进程何时开始启动。如果它们没有启动或挂起,则可能表明使用 parallel 包存在问题。你可以试试snow
        • 我会检查这个。同时,我读到多核在 Windows 机器上不起作用(好/根本不工作?)。也许这是相关的?只是为了确定:你给出的 3.4 的因子是从我上面的代码中计算出来的吗?因为as.numeric(start1-end1)/as.numeric(start2-end2) 会给你parallel 处理需要多长时间的因素。还是你反其道而行之?
        【解决方案4】:

        试试boot 包。它经过了很好的优化,并包含一个parallel 参数。这个包的棘手之处在于你必须编写新函数来计算你的统计数据,它接受你正在处理的数据和一个索引向量来重新采样数据。因此,从您定义data 的位置开始,您可以执行以下操作:

        # Define a function to resample the data set from a vector of indices
        # and return the slope
        slopeFun <- function(df, i) {
          #df must be a data frame.
          #i is the vector of row indices that boot will pass
          xResamp <- df[i, ]
          slope <- lm(y ~ x, data=xResamp)$coef[2] 
        } 
        
        # Then carry out the resampling
        b <- boot(data, slopeFun, R=1000, parallel="multicore")
        

        b$t 是重采样统计量的向量,boot 有很多很好的方法可以轻松处理它 - 例如plot(b)

        请注意,并行方法取决于您的平台。在您的 Windows 机器上,您需要使用 parallel="snow"

        【讨论】:

        • 您的解决方案是在我的机器上将其加速大约 25%。那很好。在我正在查看的实际应用程序中,问题要复杂得多,因为我的函数返回一个参数列表,所有这些都必须被引导。因此,我正在寻找并行的直接实现。
        • @tomka, boot 在这种情况下仍然可以正常工作。您编写的函数可以返回您感兴趣的参数向量。确实,这是 boot 的优势之一 - 能够自己编写任意函数。
        猜你喜欢
        • 2012-08-17
        • 2020-01-11
        • 1970-01-01
        • 2016-07-22
        • 2021-04-06
        • 1970-01-01
        • 1970-01-01
        • 2012-01-30
        • 1970-01-01
        相关资源
        最近更新 更多