R:如何将 foreach 用于预先指定的重复次数,例如在 while 循环中

问题描述

library(foreach)
library(doMC)


myfun <- function(threshold){
  val <- rnorm(1,mean = 0,sd = 1)
  if(val > threshold){
    stop("bad")
  }else return(val)
}

results <- vector("list",length = 10)
parallel_fun <- function(reps,threshold){
  registerDoMC(cores = 48)
  results = foreach (j = 1:reps,.combine = rbind) %dopar% {
    myfun(threshold)
  }
}

> parallel_fun(reps = 10,threshold = 0)
 Error in { : task 1 Failed - "bad" 

以上是一个简单的、可重现的示例。我想并行化 myfun 以获得总共 reps = 10 次复制。如果生成myfun 大于某个 valthreshold 可能会停止。在这种情况下,我想停止运行 myfun 而不是让它返回 val。最后,我希望我的 results 有 10 个 vals 大于某个 threshold。因此,我认为在这里使用 while 循环可能更合适,因为我想让它一直运行,直到我有 10 个满足 threshold 的值。是否可以将 foreach 重新用于并行化 while 循环?

解决方法

控制流程

通常不鼓励对控制流使用异常。理想情况下,

使用已经做你想做的功能

在此特定示例中,您正在模拟截断正态分布。因此,您可以使用 truncnorm 包中的 truncnorm 函数。

重写函数

或者,重写 myfun 以始终返回正确的值:

myfun = function(threshold){
    repeat{
        val = rnorm(1,1)
        if(val <= threshold)
            break
        }
    val 
    }

这只是可能的变体之一。在这里,我使用自定义 do-while 构造。

请注意,根据阈值,可能会发生大量或潜在无限次数的迭代,因此请谨慎行事,要么设置最大迭代次数,要么进行一些初步检查,如果 threshold 不在所讨论函数的最大范围,最好是两者。

有了这个,您应该能够像现在一样轻松地运行 foreach

编写包装器

如果您无法控制 myfun,则需要构造包装器,构造可能与上面的函数几乎相同:

wrap_myfun = function(threshold){
    repeat{
        val = try(myfun(threshold))
        if(is.numeric(val))
            break
        }
    val
    }

跟踪迭代:

如果您需要跟踪生成所述数字所需的迭代次数,您可以将 repeat 重写为 for 循环或仅添加计数器和另一个选项:>

wrap_myfun = function(threshold,.maxiter=10^9,.default=NA){
    iter = 1
    repeat{
        val = try(myfun(threshold))
        if(is.numeric(val))
            break

        if(iter >= .maxiter){
            val = .default 
            break
            }

        iter = iter + 1
        }
    list("value"=val,"iterations"=iter)
    }

或者,您可以使用`stop("最大迭代次数达到"),而不是分配默认值。这取决于问题的严重程度。

通过这种方式,您已将所有逻辑移到数据生成函数中,而不必管理在 foreach 中实现的队列。负载应该在内核之间平均分配(超过某些迭代的潜在随机长计算时间,但这是您无法影响的)。