g g*_*g g 5 random parallel-processing r montecarlo snow
我有一个包含内部循环的外部 foreach/dopar 并行循环。内部循环的每个实例都应该处理同一组随机数。其余部分,即外部主体的其余部分和并行实例应照常工作,即具有独立的随机数。
我可以在非并行实现中实现这一点,方法是在内循环开始之前保存 RNG 的状态,并在执行内循环的每个实例之后恢复该状态。请参见以下示例:
library(doSNOW)
seed = 4711
cl = makeCluster(2)
registerDoSNOW(cl)
clusterSetupRNGstream (cl, seed=rep(seed,6))
erg = foreach(irun = 1:3,.combine = rbind) %dopar% {
#do some random stuff in outer loop
smp = runif(1)
# save current state of RNG
s = .Random.seed
# inner loop, does some more random stuff
idx = numeric(5)
for(ii in seq.int(5)) {
idx[ii] = sample.int(10, 1)
# reset RNG for next loop iteration
set.seed(s)
}
c(smp,idx)
}
> print(erg)
[,1] [,2] [,3] [,4] [,5] [,6]
result.1 0.5749162 7 6 2 3 7
result.2 0.1208910 4 3 6 8 9
result.3 0.3491315 7 2 7 6 10
Run Code Online (Sandbox Code Playgroud)
我想要的输出是每行的常量整数,每行都不同。所以这不能并行工作。原因很清楚:snow 使用不同的随机生成器 (rlecuyer) 并且必须处理并行流。
问题是:如何使用随机数生成器 rlecuyer 在雪中实现种子重置?对我来说,挑战之一是确定应重置种子时所在的正确子流。
我当前的解决方法是为内部循环预先计算一次所有随机内容(在示例中为 idx 向量),然后在所有内部实例中使用此常量数据。这不是最佳选择,因为随机数据总量变得非常大,并且最好以较小的块即时(重新)生成它。
最重要的是任何设置/重置都保持并行流独立。据我目前了解,这需要以下机制:
对于每个并行进程并在每个并行进程内:
这里有一些事情值得一提。\n首先,您要保存.Random.seed然后将其直接传递给set.seed,\n即使您的 RNG 没有问题,也不会给出您想要的结果。\n原因因为,正如记录的那样,\nset.seed的seed参数只关心单个整数,\n因此,如果您传递一个向量,\n 仍将仅使用第一个值,\n以及.Random.seed状态的文档:
\n\n.Random.seed 是一个整数向量,其第一个元素编码 RNG 和正常生成器的类型。
\n
因此,所有并行流最终都会得到相同的整数,因为.Random.seed对于相同的 RNG 类型, 的第一个值始终相同。
其次,文档中还指出:
\n\n\n.Random.seed 保存统一随机数生成器的种子集,至少是系统生成器的种子集。它不一定保存其他生成器的状态,特别是不保存 Box\xe2\x80\x93Muller 普通生成器的状态。如果您想稍后重现工作,请调用 set.seed (最好使用 kind 和 normal.kind 的显式值)而不是 set .Random.seed。
\n
这意味着如果您要操纵更多内部值,您绝对应该知道您正在使用什么 RNG。\n如果您在调用clusterEvalQ(cl, RNGkind()) 后clusterSetupRNGstream调用,\n您将看到它返回:
[1] "user-supplied" "Inversion" "Rejection"\nRun Code Online (Sandbox Code Playgroud)\n所以你不能假设这.Random.seed足以保存 RNG 的状态。\n事实上,我什至不确定它是否能很好地与 配合使用doSNOW,\n看看这个差异:
clusterSetupRNGstream(cl, seed = rep(seed, 6))\nfoo = foreach(irun = 1:2, .combine = list) %dopar% {\n list(\n .Random.seed,\n get(".Random.seed", .GlobalEnv)\n )\n}\n> str(foo)\nList of 2\n $ :List of 2\n ..$ : int [1:626] 10403 1 -921191862 -372998484 563067311 -15494811 985677596 1278354290 1696669677 -1382461401 ...\n ..$ : int 10405\n $ :List of 2\n ..$ : int [1:626] 10403 1 -921191862 -372998484 563067311 -15494811 985677596 1278354290 1696669677 -1382461401 ...\n ..$ : int 10405\nRun Code Online (Sandbox Code Playgroud)\n最后,从您的示例中可以清楚地看出,set.seed在这种情况下并没有真正起作用。 \n 的文档表明使用了clusterSetupRNGstream该rlecuyer包,\n并且我不知道有关该包的足够详细信息来说明它是否支持set.seed,\n但我假设没有。
我可以给你的唯一替代方案是我之前使用过的,它有点冗长:
\nRNGkind("L'Ecuyer-CMRG")\n\ncl = makeCluster(2)\nregisterDoSNOW(cl)\n\nseed = 4711L\nset.seed(seed)\n# x is a vector of length irun - 1\nseeds = Reduce(x = 1:2, init = .Random.seed, accumulate = TRUE, f = function(x, ignored) {\n parallel::nextRNGStream(x)\n})\n\nerg = foreach(pseed = seeds, .combine = rbind) %dopar% {\n RNGkind("L'Ecuyer-CMRG")\n assign(".Random.seed", pseed, .GlobalEnv)\n\n # do some random stuff in outer loop\n smp = runif(1)\n \n # save current state of RNG\n s = get(".Random.seed", .GlobalEnv)\n \n # inner loop, does some more random stuff\n idx = numeric(5)\n for(ii in seq.int(5)) {\n idx[ii] = sample.int(10, 1)\n # reset RNG for next loop iteration\n assign(".Random.seed", s, .GlobalEnv)\n }\n \n c(smp, idx)\n}\n\n> print(erg)\n [,1] [,2] [,3] [,4] [,5] [,6]\nresult.1 0.9482966 2 2 2 2 2\nresult.2 0.1749918 3 3 3 3 3\nresult.3 0.3263343 1 1 1 1 1\nRun Code Online (Sandbox Code Playgroud)\nAFAIK,R 自己的 L'Ecuyer-CMRG 的状态确实保存在 中.Random.seed。