如何使用foreach和doMC软件包为随机模拟设置种子?

26

我需要进行一些模拟,并且出于调试目的,我想使用 set.seed 来获得相同的结果。下面是我尝试做的一个例子:

library(foreach)
library(doMC)
registerDoMC(2)

set.seed(123)
a <- foreach(i=1:2,.combine=cbind) %dopar% {rnorm(5)}
set.seed(123)
b <- foreach(i=1:2,.combine=cbind) %dopar% {rnorm(5)}

对象ab应该是相同的,也就是说,sum(abs(a-b))应该为零,但情况并非如此。我做错了什么,还是我碰到了某个特性?

我能够在两个不同的R 2.13和R 2.14系统上再现这个问题。

4个回答

21

我的默认答案过去常常是“那就别这样做”(使用foreach),因为snow包可以(可靠地!)为您完成此操作。

但正如@Spacedman所指出的,Renaud的新doRNG是你要寻找的,如果你想留在doFoo/foreach系列中。

然而,真正的关键是一个clusterApply-style调用来设置所有节点上的种子。以协调跨流进行。哦,我提到了吗?Tierney、Rossini、Li和Sevcikova的snow已经为您完成了这个任务近十年?

编辑:虽然你没有问过snow,但为了完整起见,这里有一个命令行的例子:

edd@max:~$ r -lsnow -e'cl <- makeSOCKcluster(c("localhost","localhost"));\
         clusterSetupRNG(cl);\
         print(do.call("rbind", clusterApply(cl, 1:4, \
                                             function(x) { stats::rnorm(1) } )))'
Loading required package: utils
Loading required package: utils
Loading required package: rlecuyer
           [,1]
[1,] -1.1406340
[2,]  0.7049582
[3,] -0.4981589
[4,]  0.4821092
edd@max:~$ r -lsnow -e'cl <- makeSOCKcluster(c("localhost","localhost"));\
         clusterSetupRNG(cl);\
         print(do.call("rbind", clusterApply(cl, 1:4, \
                                             function(x) { stats::rnorm(1) } )))'
Loading required package: utils
Loading required package: utils
Loading required package: rlecuyer
           [,1]
[1,] -1.1406340
[2,]  0.7049582
[3,] -0.4981589
[4,]  0.4821092
edd@max:~$ 

编辑: 为了完整起见,这里结合了你的示例以及doRNG文档中的内容。

> library(foreach)
R> library(doMC)
Loading required package: multicore

Attaching package: ‘multicore’

The following object(s) are masked from ‘package:parallel’:

    mclapply, mcparallel, pvec

R> registerDoMC(2)
R> library(doRNG)
R> set.seed(123)
R> a <- foreach(i=1:2,.combine=cbind) %dopar% {rnorm(5)}
R> set.seed(123)
R> b <- foreach(i=1:2,.combine=cbind) %dopar% {rnorm(5)}
R> identical(a,b)
[1] FALSE                     ## ie standard approach not reproducible
R>
R> seed <- doRNGseed()
R> a <- foreach(i=1:2,combine=cbind) %dorng% { rnorm(5) }
R> b <- foreach(i=1:2,combine=cbind) %dorng% { rnorm(5) }
R> doRNGseed(seed)
R> a1 <- foreach(i=1:2,combine=cbind) %dorng% { rnorm(5) }
R> b1 <- foreach(i=1:2,combine=cbind) %dorng% { rnorm(5) }
R> identical(a,a1) && identical(b,b1)
[1] TRUE                      ## all is well now with doRNGseed()
R> 

感谢提供关于snow的示例。我在R中的并行编程细节方面不是很精通,所以我开始使用foreach,因为它可以从非并行代码无痛过渡到并行代码。但我知道我错过了一些东西。 - mpiktas
2
嗯,这就是为什么我们多年前都从snow开始,因为从标准的*apply()函数过渡到并行函数非常容易 :) - Dirk Eddelbuettel

8

使用 set.seed(123, kind = "L'Ecuyer-CMRG") 也可以实现相同的效果,而且不需要额外的包:

set.seed(123, kind = "L'Ecuyer-CMRG")
a <- foreach(i=1:2,.combine=cbind) %dopar% {rnorm(5)}

set.seed(123, kind = "L'Ecuyer-CMRG")
b <- foreach(i=1:2,.combine=cbind) %dopar% {rnorm(5)}

identical(a,b)
# TRUE

这个答案比Dirk Eddulbuettel的答案简单得多。它有什么缺点吗? - generic_user
2
我也被这个解决方案的简单性所吸引,但是我无法复制结果(在Windows 7上使用R 3.4.0,doParallel 1.0.11和foreach 1.4.3)。 - whopper510
这显然不起作用。 随机数生成器需要在调用之间重新播种。我已经擅自修复了这个答案(尽管我改变了它的意义),因为它已经误导了人们。 - Konrad Rudolph
好的,显然它在2017年“曾经能够工作”!感谢您更新代码。 - enricoferrero

5

谢谢你的回答,我真的很想把两个答案都标记为最佳答案,但是Dirk的回答更详细。尽管如此,我还是给你的回答点了赞,因为它包含足够的信息来解决我的问题。 - mpiktas

4

对于更复杂的循环,您可能需要在for循环中包含set.seed() 内部:

library(foreach)
library(doMC)
registerDoMC(2)
library(doRNG)

set.seed(123)
a <- foreach(i=1:2,.combine=cbind) %dopar% {
  create_something <- c(1, 2, 3)
  rnorm(5)
}
set.seed(123)
b <- foreach(i=1:2,.combine=cbind) %dopar% {
  create_something  <- c(4, 5, 6)
  rnorm(5)
}
identical(a, b)
# FALSE

对比

a <- foreach(i=1:2,.combine=cbind) %dopar% {
  create_something  <- c(1, 2, 3)
  set.seed(123)
  rnorm(5)
}
b <- foreach(i=1:2,.combine=cbind) %dopar% {
  create_something  <- c(4, 5, 6)
  set.seed(123)
  rnorm(5)
}
identical(a, b)
# TRUE

你可以使用 set.seed(i) 在每个循环中设置不同的种子。 - user16263

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接