使用 Snow/doSNOW 重置内部循环的 R 随机数生成器 (rlecuyer)

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 向量),然后在所有内部实例中使用此常量数据。这不是最佳选择,因为随机数据总量变得非常大,并且最好以较小的块即时(重新)生成它。

编辑

最重要的是任何设置/重置都保持并行流独立。据我目前了解,这需要以下机制:

对于每个并行进程并在每个并行进程内:

  1. 检测进程正在处理的相关子流。
  2. 在方便的时候:查找并存储该子流的随机种子。
  3. 在方便的时候:使用 2) 中的种子重置 1) 中的子流,并保持所有其他流不受干扰。

Ale*_*xis 2

这里有一些事情值得一提。\n首先,您要保存.Random.seed然后将其直接传递给set.seed,\n即使您的 RNG 没有问题,也不会给出您想要的结果。\n原因因为,正如记录的那样,\nset.seedseed参数只关心单个整数,\n因此,如果您传递一个向量,\n 仍将仅使用第一个值,\n以及.Random.seed状态的文档:

\n
\n

.Random.seed 是一个整数向量,其第一个元素编码 RNG 和正常生成器的类型。

\n
\n

因此,所有并行流最终都会得到相同的整数,因为.Random.seed对于相同的 RNG 类型, 的第一个值始终相同。

\n

其次,文档中还指出:

\n
\n

.Random.seed 保存统一随机数生成器的种子集,至少是系统生成器的种子集。它不一定保存其他生成器的状态,特别是不保存 Box\xe2\x80\x93Muller 普通生成器的状态。如果您想稍后重现工作,请调用 set.seed (最好使用 kind 和 normal.kind 的显式值)而不是 set .Random.seed。

\n
\n

这意味着如果您要操纵更多内部值,您绝对应该知道您正在使用什么 RNG。\n如果您在调用clusterEvalQ(cl, RNGkind()) clusterSetupRNGstream调用,\n您将看到它返回:

\n
[1] "user-supplied" "Inversion"     "Rejection"\n
Run Code Online (Sandbox Code Playgroud)\n

所以你不能假设这.Random.seed足以保存 RNG 的状态。\n事实上,我什至不确定它是否能很好地与 配合使用doSNOW,\n看看这个差异:

\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\n
Run Code Online (Sandbox Code Playgroud)\n

最后,从您的示例中可以清楚地看出,set.seed在这种情况下并没有真正起作用。 \n 的文档表明使用了clusterSetupRNGstreamrlecuyer包,\n并且我不知道有关该包的足够详细信息来说明它是否支持set.seed,\n但我假设没有。

\n

我可以给你的唯一替代方案是我之前使用过的,它有点冗长:

\n
RNGkind("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\n
Run Code Online (Sandbox Code Playgroud)\n

AFAIK,R 自己的 L'Ecuyer-CMRG 的状态确实保存在 中.Random.seed

\n