使用 R 的多线程计算:如何获得所有不同的随机数?

Multithread computation with R: how to get all different random numbers?

谁知道如何在下面的代码中让所有的随机数都不同?例如。使用 doRNG 包? 我不关心再现性。

编辑: 接受纯属偶然的重复。

rm(list = ls())
set.seed(666)
cat("4")
library(plyr)
library(dplyr)
library(doRNG)

# ====== Data Preparation ======
dt = data.frame(id = 1:10,
                part = rep("dt",10),
                HG = c(1,3,6,NA,NA,2,NA,NA,NA,NA),
                random = NA)

# ====== Set Parallel Computing ======
library(foreach)
library(doParallel)

cl = makeCluster(3, outfile = "")
registerDoParallel(cl)

# ====== SIMULATION ======
nsim = 1000                # number of simulations
iterChunk = 100            # split nsim into this many chunks
out = data.frame()    # prepare output DF
for(iter in 1:ceiling(nsim/iterChunk)){
  strt = Sys.time()
  
  out_iter = 
    foreach(i = 1:iterChunk, .combine = rbind, .multicombine = TRUE, .maxcombine = 100000, .inorder = FALSE, .verbose = FALSE,
            .packages = c("plyr", "dplyr")) %dopar% {
              
              # simulation number
              id_sim = iterChunk * (iter - 1) + i

              ## Generate random numbers
              tmp_sim = is.na(dt$HG) # no results yet
              dt$random[tmp_sim] = runif(sum(tmp_sim))
              dt$HG[tmp_sim] = 3

              # Save Results
              dt$id_sim = id_sim
              dt$iter = iter
              dt$i = i
              
              print(Sys.time())
              return(dt)
            }#i;sim_forcycle
  
  out = rbind.data.frame(out,subset(out_iter, !is.na(random)))
  
  fnsh = Sys.time()
  cat(" [",iter,"] ",fnsh - strt, sep = "")
}#iter

# ====== Stop Parallel Computing ======
stopCluster(cl)

# ====== Distinct Random Numbers ======
length(unique(out$random))              # expectation: 6000

我已经为此苦苦挣扎了 2 天。我 asked this question 早些时候只有关于随机数的一般回应。

在这里我想寻求一个解决方案(如果有人知道的话)如何设置 doRNG 包选项(或类似包),使 所有随机数都不同。遍历所有循环。

我已经尝试了很多 doRNG 设置,但仍然无法正常工作。在两台不同的计算机上尝试了 R 版本 3.5.3 和 3.6.3。


更新 与@Limey

讨论后

代码的目的是模拟足球比赛。由于模拟很大,我使用 iterChunk 将模拟“拆分”为可管理的部分,并且在每个 iter 之后将数据发送到 PostgreSQL 数据库,这样模拟就不会使 RAM 过载。一些比赛已经有了真实世界的结果,并填写了 HG(主场进球)。我想模拟其余的。

iterChunk设置为1时一切正常。增加 iterChunk 会导致在 iter 内生成相同的数字。例如,当我将 nsim 设置为 100 并将 iterChunk 设置为 10 时。 (所有比赛模拟100次,10次循环10次)。我期望 600 个随机数(每个匹配在所有循环中独立模拟)。但是我只得到 180 - 按照逻辑:3 个核心 * 6 个匹配项 * 10 个 iterChunks。)使用 2 个工作人员我得到 120 个不同的随机数 (2 * 6 * 10)

此外:排除 dt$HG[tmp_sim] = 3 无论设置如何,我都会得到不同的随机数。

为了理解问题,我建议:

  1. 运行 代码原样。 (可能设置 nsim100iterChunk10)你会得到 180 个不同的随机数。使用较少数量的 nsim 和 iterChunk,事情可能会按预期工作。
  2. 注释掉 dt$HG[tmp_sim] = 3。 您将获得 6000 个不同的随机数(如果更改 nsimiterChunk 则为 600 个)

第二步中的代码分配主队进球数。它看起来像某种我无法克服的错误。即使有人得到相同结果但不知道为什么的信息也会有所帮助 - 它会减轻我自己愚蠢的负担。

谢谢,我非常感谢任何努力。

修改“doParallel入门”中的“Hello World”示例vignette生成随机数,我想出了:

library(doParallel)

cl <- makeCluster(2)
registerDoParallel(cl)
myFunc <- function(n) {runif(n)}
foreach(i=1:3) %dopar% myFunc(10)
[[1]]
 [1] 0.18492375 0.13388278 0.65455450 0.93093066 0.41157625 0.89479764 0.14736529 0.47935995 0.03062963 0.16110714

[[2]]
 [1] 0.89245145 0.20980791 0.83828019 0.04411547 0.38184303 0.48110619 0.51509058 0.93732055 0.40159834 0.81414140

[[3]]
 [1] 0.74393129 0.66999730 0.44411989 0.85040773 0.80224527 0.72483644 0.64566262 0.22546420 0.14526819 0.05931329

建议跨线程获取随机数很简单。事实上,doRNG reference manual 第 2 页和第 3 页的示例说明了同样的事情。

事实上,如果我理解正确的话,doRNG 的目的恰恰与您想要的相反:使随机进程可跨线程重现。

当然,这并不能保证所有线程中的所有数字都不同。但这使得复制的可能性很小。保证没有重复意味着过程中一定程度的确定性:完全随机的过程可能偶然产生重复。

更新 继我们在评论中的对话之后...

我们确定问题出在您的程序逻辑上,而不是并行化本身。所以我们需要重新关注这个问题:你做什么。恐怕我一点也不清楚。所以意味着我们需要简化。

我将 nsim 设置为 5,将 iterChunk 设置为 1。我得到 5 个数据框,看起来像

   id part HG    random id_sim iter i
1   1   dt  1        NA      1    1 1
2   2   dt  3        NA      1    1 1
3   3   dt  6        NA      1    1 1
4   4   dt  3 0.6919744      1    1 1
5   5   dt  3 0.5413398      1    1 1
6   6   dt  2        NA      1    1 1
7   7   dt  3 0.3983175      1    1 1
8   8   dt  3 0.3342174      1    1 1
9   9   dt  3 0.6126020      1    1 1
10 10   dt  3 0.4185468      1    1 1

在每个中,id_simiter的值总是相同的,并且运行从第一个数据帧中的15在第五。 i 对于所有数据框中的所有行都是 1random 中的值似乎是随机的,并且在数据帧之间有所不同。但是 NA 在每个数据框中都位于相同的位置:第 1、2、3 和 6 行。 HG 的值如上所示所有五个数据帧。

这是您所期望的吗?如果没有,你期望什么?鉴于我们知道问题不是并行化,您需要向我们提供更多信息。

更新 2

你知道Arduan吗?他们在周末发布了一个相关问题...

我不会告诉你你的代码有什么问题。我将向您展示我将如何解决您的问题。如果没有别的,我希望你会同意它更具可读性。

所以,我们正在模拟一些足球比赛。我假设它是一种联赛形式,并以英超联赛为例。首先生成单个赛季的赛程表。

library(tidyverse)

teams <- c("Arsenal", "Aston Villa", "Bournemouth", "Brighton & Hove Albion", 
           "Burnley", "Chelsea", "Crystal Palace", "Everton", "Leicester City", 
           "Liverpool", "Manchester City", "Manchester United", "Newcastle United", 
           "Norwich City", "Sheffield United", "Southampton", "Tottenham Hotspur", 
           "Watford", "West Ham United", "Wolverhampton Wanderers")

fixtures <- tibble(HomeTeam=teams, AwayTeam=teams) %>% 
              complete(HomeTeam, AwayTeam) %>% 
              filter(HomeTeam != AwayTeam) # A team can't play itself
fixtures %>% head(5)
# A tibble: 5 x 2
  HomeTeam AwayTeam              
  <chr>    <chr>                 
1 Arsenal  Aston Villa           
2 Arsenal  Bournemouth           
3 Arsenal  Brighton & Hove Albion
4 Arsenal  Burnley               
5 Arsenal  Chelsea     

假设我们知道一些结果。以昨天的比赛为例

knownResults <- tribble(~HomeTeam,          ~AwayTeam,         ~HomeGoals, ~AwayGoals,
                        "Burnley",          "Sheffield United",         1,          1,
                        "Newcastle United", "West Ham United",          2,          2,
                        "Liverpool",        "Aston Villa",              2,          0,
                        "Southampton",      "Manchester City",          1,          0)

resultsSoFar <- fixtures %>% 
             left_join(knownResults, by=c("HomeTeam", "AwayTeam"))
resultsSoFar %>% filter(!is.na(HomeGoals))
# A tibble: 4 x 4
  HomeTeam         AwayTeam         HomeGoals AwayGoals
  <chr>            <chr>                <dbl>     <dbl>
1 Burnley          Sheffield United         1         1
2 Liverpool        Aston Villa              2         0
3 Newcastle United West Ham United          2         2
4 Southampton      Manchester City          1         0

现在一些实用函数。您当然可以将它们结合起来,但我认为将它们分开会更清楚,这样您就可以准确地看到每个人在做什么。

首先,模拟所有未知比赛结果的功能。您如何模拟分数的细节完全是任意的。我假设主队平均每场进 1.5 个球,客队平均每场进 1.2 个球。稍后,我将使用它来一次性模拟多个季节,因此我将添加一个变量(Iteration)来索引季节。

simulateResults <- function(i=NA, data) {
  n <- nrow(data)
  data %>%
    add_column(Iteration=i, .before=1) %>% 
    mutate(
      # Give the home team a slight advantage
      HomeGoals=ifelse(is.na(HomeGoals), floor(rexp(n, rate=1/1.5)), HomeGoals),
      AwayGoals=ifelse(is.na(AwayGoals), floor(rexp(n, rate=1/1.2)), AwayGoals)
    )
}

使用它,并检查我们是否没有覆盖已知结果:

simulateResults(1, resultsSoFar) %>% filter(HomeTeam=="Burnley", AwayTeam=="Sheffield United")
# A tibble: 1 x 5
  Iteration HomeTeam AwayTeam         HomeGoals AwayGoals
      <dbl> <chr>    <chr>                <dbl>     <dbl>
1         1 Burnley  Sheffield United         1         1

我要并行化整个模拟,所以现在让我们使用一个函数来模拟大量模拟。同样,创建一个索引列来标识块。

simulateChunk <- function(chunkID=NA, n) {
  bind_rows(lapply(1:n, simulateResults, data=resultsSoFar)) %>% 
    add_column(Chunk=chunkID, .before=1)
}
simulateChunk(chunkID=1, n=3)
# A tibble: 1,140 x 6
   Chunk Iteration HomeTeam AwayTeam               HomeGoals AwayGoals
   <dbl>     <int> <chr>    <chr>                      <dbl>     <dbl>
 1     1         1 Arsenal  Aston Villa                    2         0
 2     1         1 Arsenal  Bournemouth                    0         0
 3     1         1 Arsenal  Brighton & Hove Albion         2         0
 4     1         1 Arsenal  Burnley                        2         0
 5     1         1 Arsenal  Chelsea                        1         0
 6     1         1 Arsenal  Crystal Palace                 0         0
 7     1         1 Arsenal  Everton                        2         3
 8     1         1 Arsenal  Leicester City                 2         0
 9     1         1 Arsenal  Liverpool                      0         1
10     1         1 Arsenal  Manchester City                4         0

好的。现在我已准备好进行主要的模拟工作。我将 运行 10 个 100 个模拟 eash 块,总共给出 1000 个模拟季节,和你一样。

library(doParallel)

cl <- makeCluster(3)
registerDoParallel(cl)

chunkSize <- 100
nChunks <- 10

startedAt <- Sys.time()
x <- bind_rows(foreach(i=1:nChunks, .packages=c("tidyverse")) %dopar% simulateChunk(i, n=chunkSize))
finishedAt <- Sys.time()
print(finishedAt - startedAt)
Time difference of 6.772928 secs
stopCluster(cl)
> x
# A tibble: 380,000 x 6
   Chunk Iteration HomeTeam AwayTeam               HomeGoals AwayGoals
   <int>     <int> <chr>    <chr>                      <dbl>     <dbl>
 1     1         1 Arsenal  Aston Villa                    2         0
 2     1         1 Arsenal  Bournemouth                    3         1
 3     1         1 Arsenal  Brighton & Hove Albion         0         1
 4     1         1 Arsenal  Burnley                        3         0
 5     1         1 Arsenal  Chelsea                        1         0
 6     1         1 Arsenal  Crystal Palace                 0         0
 7     1         1 Arsenal  Everton                        1         2
 8     1         1 Arsenal  Leicester City                 0         0
 9     1         1 Arsenal  Liverpool                      0         0
10     1         1 Arsenal  Manchester City                0         0

让我们检查一下我得到了合理的结果。作为基本检查,我将查看阿森纳与阿斯顿维拉的比赛结果:

x %>% 
  filter(HomeTeam == "Arsenal", AwayTeam=="Aston Villa") %>% 
  group_by(HomeGoals, AwayGoals) %>% 
  summarise(N=n(), .groups="drop") %>% 
  pivot_wider(
    values_from="N", names_prefix="AwayGoals", 
    names_sep="", names_from=AwayGoals
  )
# A tibble: 8 x 10
  HomeGoals AwayGoals0 AwayGoals1 AwayGoals2 AwayGoals3 AwayGoals4 AwayGoals5 AwayGoals6 AwayGoals8 AwayGoals7
      <dbl>      <int>      <int>      <int>      <int>      <int>      <int>      <int>      <int>      <int>
1         0        299        129         57         19         12          7         NA         NA         NA
2         1        135         63         25          6          4          4          1          2         NA
3         2         75         21         12          9          4          1         NA         NA          1
4         3         30         13         10          1         NA         NA         NA         NA         NA
5         4         21          7          1          1         NA         NA         NA         NA         NA
6         5         11          2          1         NA          2         NA         NA         NA         NA
7         6          4          2          2         NA         NA         NA         NA         NA         NA
8         7          4          1          1         NA         NA         NA         NA         NA         NA

这看起来很合理。现在确认具有已知结果的匹配没有变化。例如:

x %>% 
  filter(HomeTeam == "Liverpool", AwayTeam=="Aston Villa") %>% 
  group_by(HomeGoals, AwayGoals) %>% 
  summarise(N=n(), .groups="drop") %>% 
  pivot_wider(values_from="N", names_prefix="AwayGoals", names_sep="", names_from=AwayGoals)
  HomeGoals AwayGoals0
      <dbl>      <int>
1         2       1000

一切顺利。

所以,这是 23 个语句来生成固定装置、考虑已知结果、模拟剩余的比赛并进行一些基本的健全性检查。如果必须的话,我可以很容易地将它减少到 20 个语句以下。这比您仅用于尝试模拟未知结果的数量少了大约三分之一。 [实际模拟不到 10 条语句。]我认为我的方法更容易理解:通过使用 tidy 动词,代码几乎是自我记录的。

我在洗澡的时候意识到了 OP 代码的问题所在。这很简单,回想起来也很明显:所有循环和并行进程都在同一个对象上工作——dt 数据框。所以他们不断地覆盖每个所做的更改,并且在外循环结束时,您只需要完成上一个循环所做更改的多个副本。解决方案同样简单:处理 dt 数据框的副本。

为了尽量减少更改,我将 dt 重命名为 baseDT

# ====== Data Preparation ======
baseDT = data.frame(id = 1:10,
                part = rep("dt",10),
                HG = c(1,3,6,NA,NA,2,NA,NA,NA,NA),
                random = NA)

然后在 foreach 循环

的顶部复制了一份
  out_iter = foreach(i = 1:iterChunk, 
               .combine = rbind, .multicombine = TRUE, .maxcombine = 100000, 
               .inorder = FALSE, .verbose = FALSE,
               .packages = c("plyr", "dplyr")) %dopar% {
    dt <- baseDT

这给

> length(unique(out$random))              # expectation: 6000
[1] 6000

符合预期。