首页 > 解决方案 > 如何在“R”中使用“parallel::parSapply”在“FORK”集群上生成随机数?

问题描述

我正在尝试从FORK集群的子进程中生成随机数。我发现史蒂夫韦斯顿的答案概述了三种方式。但是,该答案主要针对使用parallel::mclapply. 我感兴趣的是我是否可以使用手动创建的FORK集群来实现类似的效果,以及下面的方法是否有任何副作用。

问题。

# Ensure the `.Random.seed` exists in the main process.
rnorm(1)

# Print the seed.
print(.Random.seed)
# [1] 10407 -1622412906  1293611072  -102526474  -427981783  -110857513  1641877986

# Generate random numbers on the cluster (the problem).
for(i in 1:2) {
    # Feedback for clarity.
    cat("Run number:", i, "\n")

    # Create the `FORK` cluster.
    backend <- parallel::makeCluster(2, "FORK")

    # Check the values of `.Random.seed` on the child processes.
    print(parallel::clusterEvalQ(backend, { .Random.seed }))

    # Generate random numbers.
    print(parallel::parSapply(backend, 1:2, function(x) rnorm(1)))

    # Stop the cluster.
    parallel::stopCluster(backend)

    # Feedback for clarity.
    cat(rep("-", 30), "\n\n")
}

# Run number: 1 
# [1] 10407 -1622412906  1293611072  -102526474  -427981783  -110857513  1641877986
# [1] 10407 -1622412906  1293611072  -102526474  -427981783  -110857513  1641877986
# [1] 0.4935776 0.4935776
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
#
# Run number: 2 
# [1] 10407 -1622412906  1293611072  -102526474  -427981783  -110857513  1641877986
# [1] 10407 -1622412906  1293611072  -102526474  -427981783  -110857513  1641877986
# [1] 0.4935776 0.4935776
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

# ...

如您所见,根据史蒂夫韦斯顿的回答,子进程是使用.Random.seed主进程中存在的副本创建的。因此,如果没有任何东西使.Random.seed流向前移动(或者它没有被删除),那么每次我创建一个新集群时,我都会得到相同的数字(例如,-110857513 1641877986)。

一种方法(也许是幼稚的)。

文档mcparallel说明如果RNG存在,它将由子进程复制,否则每个子进程将RNG根据时钟时间和进程 ID 启动:

如果mc.set.seed = FALSE,则子进程具有与当前 R 会话相同的初始随机数生成器 (RNG) 状态。如果已使用 RNG(或 .Random.seed 已从保存的工作区恢复),则子项将在与当前会话相同的点开始绘制随机数。如果尚未使用 RNG,则子节点将在第一次使用 RNG 时根据时间和进程 ID 设置种子:这几乎可以保证提供与当前会话和任何其他子节点不同的随机数流过程。

因此,.Random.seed从主进程中删除副本并让子进程根据时钟时间和进程 ID 初始化自己的进程是否安全?例如,如下所示:

# ...

# Generate random numbers on the cluster (a potential approach)?
for(i in 1:2) {
    # Feedback for clarity.
    cat("Run number:", i, "\n")

    # Create the `FORK` cluster.
    backend <- parallel::makeCluster(2, "FORK")

    # Check the inherited values of `.Random.seed` on the child processes.
    print(parallel::clusterEvalQ(backend, { .Random.seed }))

    # Remove the inherited `.Random.seed`.
    parallel::clusterEvalQ(backend, rm(list = ls(all.names = TRUE)))

    # Generate random numbers and let each child process create a new `.Random.seed`.
    print(parallel::parSapply(backend, 1:2, function(x) rnorm(1)))

    # Check the values of `.Random.seed`.
    print(parallel::clusterEvalQ(backend, { .Random.seed }))

    # Stop the cluster.
    parallel::stopCluster(backend)

    # Feedback for clarity.
    cat(rep("-", 30), "\n\n")
}

# Which results in the following output.
# Run number: 1 
# [1] 10407 -1622412906  1293611072  -102526474  -427981783  -110857513  1641877986
# [1] 10407 -1622412906  1293611072  -102526474  -427981783  -110857513  1641877986
# [1] -0.5715241  1.1185240
# [1] 10407   526289805   353889827  1059054224 -1056295096  -865117501  2032268800
# [1] 10407  154241933 -599585698 -497710325 -694601912  -34060352 -398990459
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
# 
# Run number: 2 
# [1] 10407 -1622412906  1293611072  -102526474  -427981783  -110857513  1641877986
# [1] 10407 -1622412906  1293611072  -102526474  -427981783  -110857513  1641877986
# [1] -1.1293323 -0.8339863
# [1] 10407  1114672013  -219668828  1458973439 -2063321272  -775346660  1678610177
# [1] 10407 -1649177715 -1213192943 -1982050350  1287862088 -2081396332   185873261
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

快速提一下——我尝试使用parallel::clusterSetRNGStream,但运气不佳,因为.Random.seed它存在于主进程中,它会被复制到子进程中。

标签: rrandomparallel-processingrandom-seed

解决方案


推荐阅读