在 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 是进度条的样子。这看起来有点奇怪,因为每个进度条都会打印一个新进度条,而且工作人员可能会滞后一点,这会导致进度条偶尔来回走动。
【讨论】:
建议的改进(我认为它与您的想法足够接近,无需单独回答):基本上,每次迭代都用cat
向tempfile
写一个换行符,然后计算换行符(我使用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
,但使用multicore
的mclapply
,使用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 [====>-----------------------------] 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()”函数时如何创建进度条?的主要内容,如果未能解决你的问题,请参考以下文章
使用 foreach 函数和 doParallel 库在 R 中嵌套 for 循环
在Stata中,foreach x的R等价函数是什么?[关闭]