原文地址:http://blog.163.com/yugao1986@126/blog/static/6922850820145303931857/
FROM:《Using The foreach Package》
foreach包提供了一种新的循环运行R脚本的循环结构,它支持并行运算。
#1 简介
require(foreach)
## Loading required package: foreach
#利用foreach重复运行sqrt函数
foreach(i=1:3) %do% sqrt(i)
## [[1]]
## [1] 1
##
## [[2]]
## [1] 1.414
##
## [[3]]
## [1] 1.732
foreach返回的是list格式值,list格式是默认的数据格式。
上例中,我们将循环值赋予i,进而将i作为函数sqrt的参数。类似,我们可以指定多个循环:
foreach(a=1:3, b=rep(10, 3)) %do% {
a + b
}
## [[1]]
## [1] 11
##
## [[2]]
## [1] 12
##
## [[3]]
## [1] 13
上面a和b被赋予同样数量的值,如果只赋予两个值给b,而给a赋1000个值,我们得到的值同样只有2个:
foreach(a=1:1000, b=rep(10, 2)) %do% (a + b)
## [[1]]
## [1] 11
##
## [[2]]
## [1] 12
#2 “.combine”选项
foreach返回的数据格式是list格式,有时我们需要向量格式的数据,此时可以使用.combine选项:
foreach(i=1:3, .combine="c") %do% exp(i)
## [1] 2.718 7.389 20.086
这里.combine选项连接了“c”函数,该函数的功能是连接所有返回值组成向量。此外,我们可以使用“cbind”将生成的多个向量组合成矩阵,例如生成四组随机数向量,进而按列合并成矩阵:
foreach(i=1:4, .combine="cbind") %do% rnorm(4)
## result.1 result.2 result.3 result.4
## [1,] 0.26634 -0.73193 -0.25927 0.8632
## [2,] 0.54132 0.08586 1.46398 -0.6995
## [3,] -0.15619 0.85427 -0.47997 0.2160
## [4,] 0.02697 -1.40507 -0.06972 0.2252
我们还可以使用“+”或“*”等运算符作为.combine选项的值:
foreach(i=1:4, .combine="+") %do% rnorm(4)
## [1] 1.203 1.354 1.793 -2.245
也可以使用自定义函数:
cfun <- function(a, b) NULL
foreach(i=1:4, .combine="cfun") %do% rnorm(4)
## NULL
如果自定义函数涉及多个参数,我们需要使用.multicombine
cfun <- function(...) NULL
foreach(i=1:4, .combine="cfun", .multicombine=TRUE) %do% rnorm(4)
## NULL
.maxcombine可以设置最大参数个数的上线
cfun <- function(...) NULL
foreach(i=1:4, .combine='cfun', .multicombine=TRUE, .maxcombine=10) %do% rnorm(4)
## NULL
在进行并行计算时候,.inorder选项将非常重要。该参数是逻辑型的,默认为TRUE,某些情况下可以改为FALSE。
foreach(i=4:1, .combine="c") %dopar% {
Sys.sleep(3*i)
i
}
## Warning: executing %dopar% sequentially: no parallel backend registered
## [1] 4 3 2 1
foreach(i=4:1, .combine='c', .inorder=FALSE) %dopar% {
Sys.sleep(3 * i)
i
}
## [1] 4 3 2 1
#3 迭代器
迭代循环值不是必须设定位向量或者list格式,我们可以结合"iterators"包使用迭代器设置其格式。iterators包中的irnorm函数在每次调用时都可以返回指定数量的随机数:
require(iterators)
## Loading required package: iterators
foreach(a=irnorm(4,count=4), .combine='cbind') %do% a
## result.1 result.2 result.3 result.4
## [1,] -0.8766 -1.5256 1.1133 0.2166
## [2,] 0.1950 0.7027 0.4458 0.2943
## [3,] -1.0098 -0.5317 -0.6918 -0.3026
## [4,] 1.0034 -0.1961 -0.6325 0.9973
在处理大数据时候,该方法相当有用,因为iterators按照我们的命令动态生成随机数,而不是在一开始就生成随机数再进行运算操作。例如,计算数千个随机数向量的汇总:
set.seed(123)
system.time(foreach(a=irnorm(4, count=1000), .combine='+') %do% a)
## user system elapsed
## 1.33 0.00 1.34
#使用icount函数生成1000个随机数
set.seed(123)
system.time(foreach(icount(1000), .combine='+') %do% rnorm(4))
## user system elapsed
## 1.08 0.00 1.08
#4 并行计算
foreach包创作是为了解决一些并行计算问题,将"%do%“更改为“%dopar%”前面例子就可以实现并行计算。并行计算一些小任务会比按顺序运算它们花费更多的时间,所以当普通运算足够快的时候,并没有必要使用并行计算模式改进其运算效率。
##4.1并行随机森林
下面使用随机森林来演示并行计算
#生成矩阵x作为输入值,y作为目标因子
x <- matrix(runif(500), 100)
y <- gl(2, 50)
#导入randomForest包
require(randomForest)
## Loading required package: randomForest
## randomForest 4.6-7
## Type rfNews() to see new features/changes/bug fixes.
如果我们要创建一个包含1200棵树的随机森林模型,在6核CPU电脑上,我们可以将其分割为六块执行randomForest函数六次,同时将ntree参赛设为200,最后再将结果合并。
a.按顺序执行代码
rf <- foreach(ntree=rep(200, 6), .combine=combine) %do%
randomForest(x, y, ntree=ntree)
rf
##
## Call:
## randomForest(x = x, y = y, ntree = ntree)
## Type of random forest: classification
## Number of trees: 1200
## No. of variables tried at each split: 2
b.并行计算
将%do%改为“%dopar%”,同时使用.packages调用randomForest:
rf <- foreach(ntree=rep(200,6), .combine=combine, .packages="randomForest") %dopar%
randomForest(x, y, ntree=ntree)
rf
##
## Call:
## randomForest(x = x, y = y, ntree = ntree)
## Type of random forest: classification
## Number of trees: 1200
## No. of variables tried at each split: 2
##4.2并行应用apply函数
apply函数式R软件中的基本函数,下面我们看看如何实现apply的并行计算。
applyKernel <- function(newX, FUN, d2, d.call, dn.call=NULL, ...) {
ans <- vector("list", d2)
for(i in 1:d2) {
tmp <- FUN(array(newX[,i], d.call, dn.call), ...)
if(!is.null(tmp)) ans[[i]] <- tmp
}
ans
}
applyKernel(matrix(1:16, 4), mean, 4, 4)
## [[1]]
## [1] 2.5
##
## [[2]]
## [1] 6.5
##
## [[3]]
## [1] 10.5
##
## [[4]]
## [1] 14.5
使用foreach包运行上述函数
applyKernel <- function(newX, FUN, d2, d.call, dn.call=NULL, ...){
foreach(i=1:d2) %dopar%
FUN(array(newX[, i], d.call, dn.call), ...)
}
applyKernel(matrix(1:16, 4), mean, 4, 4)
## [[1]]
## [1] 2.5
##
## [[2]]
## [1] 6.5
##
## [[3]]
## [1] 10.5
##
## [[4]]
## [1] 14.5
该方法将导致newX数组被发送至每一个并行运算器中,有时每一个并行运算只需要newX的一列数据,此时我们就需要避免这种情况。利用iterators包迭代器遍历矩阵的列,这种方法可以解决该问题。
applyKernel <- function(newX, FUN, d2, d.call, dn.call=NULL, ...) {
foreach(x=iter(newX, by='col')) %dopar%
FUN(array(x, d.call, dn.call), ...)
}
applyKernel(matrix(1:16, 4), mean, 4, 4)
## [[1]]
## [1] 2.5
##
## [[2]]
## [1] 6.5
##
## [[3]]
## [1] 10.5
##
## [[4]]
## [1] 14.5
上述代码只是将矩阵中的指导列赋予并行计算器,将newX矩阵放在更大的矩阵块中会更有效率。iblkcol函数可以返回一个迭代器,该迭代器返回多个列的原始矩阵。该种方法意味着对原始矩阵中的子矩阵的列运算自定义的函数:
iblkcol <- function(a, chunks) {
n <- ncol(a)
i <- 1
nextEl <- function() {
if (chunks <= 0 || n <= 0) stop('StopIteration')
m <- ceiling(n / chunks)
r <- seq(i, length=m)
i <<- i + m
n <<- n - m
chunks <<- chunks - 1
a[,r, drop=FALSE]
}
obj <- list(nextElem=nextEl)
class(obj) <- c('abstractiter', 'iter')
obj
}
applyKernel <- function(newX, FUN, d2, d.call, dn.call=NULL, ...) {
foreach(x=iblkcol(newX, 3), .combine='c', .packages='foreach') %dopar% {
foreach(i=1:ncol(x)) %do%
FUN(array(x[,i], d.call, dn.call), ...)
}
}
applyKernel(matrix(1:16, 4), mean, 4, 4)
## [[1]]
## [1] 2.5
##
## [[2]]
## [1] 6.5
##
## [[3]]
## [1] 10.5
##
## [[4]]
## [1] 14.5
#5 列表解析
foreach提供了when函数实现条件运算
foreach(a=irnorm(1, count=10), .combine='c') %:% when(a >= 0) %do% sqrt(a)
## [1] 1.079 1.185 1.465 1.396
下面是foreach结合when函数完成的简单快速排序函数示例:
qsort <- function(x) {
n <- length(x)
if (n == 0) {
x
} else {
p <- sample(n, 1)
smaller <- foreach(y=x[-p], .combine=c) %:% when(y <= x[p]) %do% y
larger <- foreach(y=x[-p], .combine=c) %:% when(y > x[p]) %do% y
c(qsort(smaller), x[p], qsort(larger))
}
}
qsort(runif(12))
## [1] 0.1481 0.2000 0.2769 0.4729 0.4747 0.5730 0.6394 0.6524 0.8315 0.8325
## [11] 0.8413 0.8724
#6 总结
大多数并行计算都主要完成三件事情:将问题分割小块、对小块问题进行并行计算、合并计算结果。foreach包中,迭代器完成分割工作,”%dopar%“函数实现对小块的并行计算,”.combine"函数完成合并工作。