在 R 中使用“foreach()”函数时如何创建进度条?

Posted

技术标签:

【中文标题】在 R 中使用“foreach()”函数时如何创建进度条?【英文标题】:How do you create a progress bar when using the "foreach()" function in R? 【发布时间】:2011-07-22 09:06:24 【问题描述】:

有一些关于如何在 R 程序中为循环创建计数器的信息性文章。但是,当使用带有“foreach()”的并行版本时,如何创建类似的函数?

【问题讨论】:

你知道如何在 Stack Overflow 上接受答案吗?如果没有,请阅读常见问题解答并返回之前的问题。 ParallelR 博客here 中有一个foreach 的示例,我认为值得一读:) 【参考方案1】:

编辑:在 doSNOW 包中添加 update 后,使用 %dopar% 时显示漂亮的进度条变得非常简单,并且它适用于 Linux、Windows 和 OS X

doSNOW 现在通过.options.snow 参数正式支持进度条。

library(doSNOW)
cl <- makeCluster(2)
registerDoSNOW(cl)
iterations <- 100
pb <- txtProgressBar(max = iterations, style = 3)
progress <- function(n) setTxtProgressBar(pb, n)
opts <- list(progress = progress)
result <- foreach(i = 1:iterations, .combine = rbind, 
                  .options.snow = opts) %dopar%

    s <- summary(rnorm(1e6))[3]
    return(s)

close(pb)
stopCluster(cl) 

如果您牢记迭代总数,另一种跟踪进度的方法是设置 .verbose = T,因为这将在控制台打印出哪些迭代已完成。

以前适用于 Linux 和 OS X 的解决方案

在 Ubuntu 14.04(64 位)和 OS X(El Capitan)上,如果在 makeCluster 函数中设置了 oufile = "",即使使用 %dopar% 也会显示进度条。它似乎在 Windows 下不起作用。来自makeCluster的帮助:

outfile:将工作人员的 stdout 和 stderr 连接输出定向到何处。 "" 表示没有重定向(这可能只对本地机器上的工作人员有用)。默认为“/dev/null”(Windows 上为“nul:”)。

示例代码:

library(foreach)
library(doSNOW)
cl <- makeCluster(4, outfile="") # number of cores. Notice 'outfile'
registerDoSNOW(cl)
iterations <- 100
pb <- txtProgressBar(min = 1, max = iterations, style = 3)
result <- foreach(i = 1:iterations, .combine = rbind) %dopar% 

      s <- summary(rnorm(1e6))[3]
      setTxtProgressBar(pb, i) 
      return(s)

close(pb)
stopCluster(cl) 

This 是进度条的样子。这看起来有点奇怪,因为每个进度条都会打印一个新进度条,而且工作人员可能会滞后一点,这会导致进度条偶尔来回走动。

【讨论】:

建议的改进(我认为它与您的想法足够接近,无需单独回答):基本上,每次迭代都用cattempfile 写一个换行符,然后计算换行符(我使用wc,因为我在 Linux 上,但还有其他适用于 Windows 的解决方案)并使用它来更新进度条。这具有单调递增的优点。缺点是你必须在每次迭代中读取一个文件——不确定这有多慢。 感谢@MichaelChirico 的建议,但现在有一种“官方”的方式来做这件事。我已经更新了答案。 我似乎无法在函数中使用它。 doSNOW 软件包现已被取代。【参考方案2】:

此代码是 doRedis example 的修改版本,即使使用带有并行后端的 %dopar% 也会生成进度条:

#Load Libraries
library(foreach)
library(utils)
library(iterators)
library(doParallel)
library(snow)

#Choose number of iterations
n <- 1000

#Progress combine function
f <- function()
  pb <- txtProgressBar(min=1, max=n-1,style=3)
  count <- 0
  function(...) 
    count <<- count + length(list(...)) - 1
    setTxtProgressBar(pb,count)
    Sys.sleep(0.01)
    flush.console()
    c(...)
  


#Start a cluster
cl <- makeCluster(4, type='SOCK')
registerDoParallel(cl)

# Run the loop in parallel
k <- foreach(i = icount(n), .final=sum, .combine=f()) %dopar% 
  log2(i)


head(k)

#Stop the cluster
stopCluster(cl)

你要提前知道迭代次数和组合函数。

【讨论】:

嗯,这很奇怪。在实际计算完成后,我的功能似乎一次更新进度条...... 此方法可能仅适用于 doRedis 后端。我将不得不研究如何使它与 doParallel 后端一起工作。 doParallel 不能很好地工作,因为 doParallel 只是在所有结果返回后才调用 combine 函数,因为它是通过调用并行 clusterApplyLB 函数来实现的。这种技术仅适用于动态调用组合函数的后端,例如 doRedis、doMPI、doNWS 和(已失效?)doSMP。 @Steve Weston 感谢您的澄清。这对我来说很有意义,现在我明白了为什么我的函数适用于 doRedis,但不适用于 doParallel。 您可以尝试刷新控制台...未经测试。【参考方案3】:

现在可以使用parallel 包来实现。在 OSX 10.11 上使用 R 3.2.3 进行测试,在 RStudio 中运行,使用 "PSOCK"-type 集群。

library(doParallel)

# default cluster type on my machine is "PSOCK", YMMV with other types
cl <- parallel::makeCluster(4, outfile = "")
registerDoParallel(cl)

n <- 10000
pb <- txtProgressBar(0, n, style = 2)

invisible(foreach(i = icount(n)) %dopar% 
    setTxtProgressBar(pb, i)
)

stopCluster(cl)

奇怪的是,它只有在 style = 3 时才能正确显示。

【讨论】:

R 3.2.2 on Windows 10 似乎没有使用此代码生成任何进度条...这是特定于 >= 3.2.3 的吗? @IainS 我宁愿将差异归咎于操作系统的不一致性,而不是 R 版本。 这似乎偶尔会下降。它可能无法处理迭代的异步性质(i = 15 可能在 i = 10 之前完成)。【参考方案4】:

您也可以使用progress 包来实现它。

# loading parallel and doSNOW package and creating cluster ----------------
library(parallel)
library(doSNOW)

numCores<-detectCores()
cl <- makeCluster(numCores)
registerDoSNOW(cl)

# progress bar ------------------------------------------------------------
library(progress)

iterations <- 100                               # used for the foreach loop  

pb <- progress_bar$new(
  format = "letter = :letter [:bar] :elapsed | eta: :eta",
  total = iterations,    # 100 
  width = 60)

progress_letter <- rep(LETTERS[1:10], 10)  # token reported in progress bar

# allowing progress bar to be used in foreach -----------------------------
progress <- function(n)
  pb$tick(tokens = list(letter = progress_letter[n]))
 

opts <- list(progress = progress)

# foreach loop ------------------------------------------------------------
library(foreach)

foreach(i = 1:iterations, .combine = rbind, .options.snow = opts) %dopar% 
  summary(rnorm(1e6))[3]


stopCluster(cl) 

【讨论】:

但我不知道迭代次数 - 因为 foreach 中有一个嵌套循环,我不知道如何计算迭代次数。这些真的需要吗? 如果您查看progress_bar 的帮助文件,您可以设置total=NA,尽管您不再获得进度条。我很乐意帮助您找出确定迭代次数的方法。 如果我将迭代次数更改为 10000,我会收到“警告:进度函数失败:无效的 'times' 参数”我该如何解决这个问题? 如果您只将迭代次数更改为 10000(假设您运行的代码与上述完全相同),则还需要更改 progress_letter 变量。【参考方案5】:

在循环之前使用Sys.time() 保存开始时间。循环遍历行或列或您知道总数的东西。然后,在循环内,您可以计算到目前为止运行的时间(请参阅difftime)、完成百分比、速度和估计剩余时间。每个进程都可以使用message 函数打印这些进度线。你会得到类似的输出

1/1000 complete @ 1 items/s, ETA: 00:00:45
2/1000 complete @ 1 items/s, ETA: 00:00:44

显然,循环顺序会极大地影响它的效果。不知道foreach,但使用multicoremclapply,使用mc.preschedule=FALSE 会得到很好的结果,这意味着项目将按照先前项目完成的顺序一个接一个地分配给进程。

【讨论】:

您是在使用某种全局计数器,还是依赖于被循环的索引 (i)? @C8H10N4O2:索引循环了。使用 mclapply 时,使用 mc.preschedule=FALSE 时效果很好,有时会出错,但通常与默认值(通常更快)mc.preschedule=TRUE 足够接近。【参考方案6】:

此代码使用doMC 后端并使用R 中出色的progress 包实现跟踪并行化foreach 循环的进度条。它假定由numCores 指定的所有内核执行的工作量大致相同。

library(foreach)
library(doMC)
library(progress)

iterations <- 100
numCores <- 8

registerDoMC(cores=numCores)

pbTracker <- function(pb,i,numCores) 
    if (i %% numCores == 0) 
        pb$tick()
    


pb <- progress_bar$new(
  format <- " progress [:bar] :percent eta: :eta",
  total <- iterations / numCores, clear = FALSE, width= 60)


output = foreach(i=1:iterations) %dopar% 
    pbTracker(pb,i,numCores)
    Sys.sleep(1/20)

【讨论】:

如果你真的注册了多个核心,这是行不通的。 以上示例在我的 MacBook Pro 2017, R v. 3.5.1 上似乎可以正常工作。我相信如果循环内的实际工作很小,则上述与并行性相关的软件包之一会阻止多个内核启动。尝试在循环中添加一些更费力的东西 - 它应该可以工作。 但是上面甚至没有注册内核?我认为它实际上并没有将任务外包出去。需要明确的是,上述方法对我有用,但是当我实际注册多个工作人员时,它只会在最后返回完成的跟踪器。尝试在 %dopar% 调用之前添加 registerDoMC(2) @luke.sonnet,感谢您指出缺失的行。在包含registerDoMC(cores=numCores) 之后,当我在我的 Mac 上查看活动监视器时,我正在启动多个内核。给你一个想法,progress [====&gt;-----------------------------] 15% eta: 12s,这就是我在此期间看到的。【参考方案7】:

以下代码将在 R 中为foreach 控制结构生成一个漂亮的进度条。它还可以通过将txtProgressBar 替换为所需的进度条对象来处理图形进度条。

# Gives us the foreach control structure.
library(foreach)
# Gives us the progress bar object.
library(utils)
# Some number of iterations to process.
n <- 10000
# Create the progress bar.
pb <- txtProgressBar(min = 1, max = n, style=3)
# The foreach loop we are monitoring. This foreach loop will log2 all 
# the values from 1 to n and then sum the result. 
k <- foreach(i = icount(n), .final=sum, .combine=c) %do% 
    setTxtProgressBar(pb, i)
    log2(i)

# Close the progress bar.
close(pb)

虽然上面的代码以最基本的形式回答了您的问题,但一个更好且更难回答的问题是,您是否可以创建一个 R 进度条来监控与 %dopar% 并行化的 foreach 语句的进度。不幸的是,我认为不可能以这种方式监控并行化 foreach 的进度,但我希望有人能证明我错了,因为这将是非常有用的功能。

【讨论】:

这个答案没有解决与并行化相关的 OP 问题,%dopar%

以上是关于在 R 中使用“foreach()”函数时如何创建进度条?的主要内容,如果未能解决你的问题,请参考以下文章

如何将自定义函数加载到 R 中的 foreach 循环中?

如何在“R”中的foreach循环中导出多个函数或包

使用 foreach 函数和 doParallel 库在 R 中嵌套 for 循环

在Stata中,foreach x的R等价函数是什么?[关闭]

如何使用 SwiftUI 在 Xcode 中消除 foreach 循环的歧义

R中的并行foreach共享内存