R︱foreach+doParallel並行+聯用迭代器優化內存+並行機器學習算法


      接着之前寫的並行算法parallel包,parallel相比foreach來說,相當於是foreach的進階版,好多東西封裝了。而foreach包更為基礎,而且可自定義的內容很多,而且實用性比較強,可以簡單的用,也可以用得很復雜。筆者將自己的學習筆記記錄一下。

      R︱並行計算以及提高運算效率的方式(parallel包、clusterExport函數、SupR包簡介)

 

 

——————————————————————————————————————

 

 

 

一、foreach包簡介與主要函數解讀

 

      foreach包是revolutionanalytics公司貢獻給R開源社區的一個包,它能使R中的並行計算更為方便。大多數並行計算都主要完成三件事情:將問題分割小塊、對小塊問題進行並行計算、合並計算結果。foreach包中,迭代器完成分割工作,”%dopar%“函數實現對小塊的並行計算,”.combine”函數完成合並工作。

 

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. foreach(..., .combine, .init, .final=NULL, .inorder=TRUE,  
  2.        .multicombine=FALSE,  
  3.        .maxcombine=if (.multicombine) 100 else 2,  
  4.        .errorhandling=c('stop', 'remove', 'pass'),  
  5.        .packages=NULL, .export=NULL, .noexport=NULL,  
  6.        .verbose=FALSE)  
  7. when(cond)  
  8. e1 %:% e2  
  9. obj %do% ex  
  10. obj %dopar% ex  
  11. times(n)  

 

參數解讀:

      (1)%do%嚴格按照順序執行任務(所以,也就非並行計算),%dopar%並行執行任務,%do%時候就像sapply或lapply,%dopar%就是並行啟動器

      (2).combine:運算之后結果的顯示方式,default是list,“c”返回vector, cbind和rbind返回矩陣,"+"和"*"可以返回rbind之后的“+”或者“*”,幫你把數據整合起來,太良心了!!
      (3).init:.combine函數的第一個變量
      (4).final:返回最后結果
      (5).inorder:TRUE則返回和原始輸入相同順序的結果(對結果的順序要求嚴格的時候),FALSE返回沒有順序的結果(可以提高運算效率)。這個參數適合於設定對結果順序沒有需求的情況。
      (6).muticombine:設定.combine函數的傳遞參數,default是FALSE表示其參數是2,TRUE可以設定多個參數
      (7).maxcombine:設定.combine的最大參數
      (8).errorhandling:如果循環中出現錯誤,對錯誤的處理方法
      (9).packages:指定在%dopar%運算過程中依賴的package(%do%會忽略這個選項),用於並行一些機器學習算法。
      (10).export:在編譯函數的時候需要預先加載一些內容進去,類似parallel的clusterExport

 

      如果你不知道自己的機器有沒有啟動並行,你可以通過以下的函數來進行查看,幫助你理解自己電腦的核心數:

 

[plain]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. getDoParWorkers( )    #查看注冊了多少個核,配合doMC package中的registerDoMC( )使用  
  2. getDoParRegistered( ) # 查看doPar是否注冊;如果沒有注冊返回FALSE  
  3. getDoParName( )       #查看已經注冊的doPar的名字  
  4. getDoParVersion( )    #查看已經注冊的doPar的version  


本節內容主要參考:R語言處理大數據

 

 

 

——————————————————————————————————————

 

 

 

二、新手教程:foreach應用

 

1、最簡單模式——堪比lapply

 

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. foreach(a=1:3, b=rep(10, 3)) %do% {  
  2.   a + b  
  3. }  
  4. ## [[1]]  
  5. ## [1] 11  
  6. ##   
  7. ## [[2]]  
  8. ## [1] 12  
  9. ##   
  10. ## [[3]]  
  11. ## [1] 13  

      這個並不是並行,只是有着類似lapply的功能。
      foreach返回的是list格式值,list格式是默認的數據格式。來看看上面的內容怎么用lapply實現:

 

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. lapply(cbind(1:3,rep(10,3),function(x,y) x+y ))  



 

但是有個小細節就是,%do%之后的{}可以隨意寫中間賦值過程,譬如c<-a+b,這個用lapply不是特別好寫。所以這個我超級喜歡!

這里需要注意的一點是:a, b叫循環變量,循環次數取兩者長度中最小的。譬如a=1,2,3 b=1,2,也就只能循環兩次。

 

2、參數:.combine——定義輸出結果的整合

 

      默認是foreach之后返回的是list,你可以指定自己想要的格式。.combine選項連接了“c”函數,該函數的功能是連接所有返回值組成向量。此外,我們可以使用“cbind”將生成的多個向量組合成矩陣,例如生成四組隨機數向量,進而按列合並成矩陣:

 

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. foreach(i=1:4, .combine="cbind") %do% rnorm(4)  
  2. ##      result.1 result.2 result.3 result.4  
  3. ## [1,]  0.26634 -0.73193 -0.25927   0.8632  
  4. ## [2,]  0.54132  0.08586  1.46398  -0.6995  
  5. ## [3,] -0.15619  0.85427 -0.47997   0.2160  
  6. ## [4,]  0.02697 -1.40507 -0.06972   0.2252  


      運算之后結果的顯示方式,default是list,“c”返回vector, cbind和rbind返回矩陣,"+"和"*"可以返回rbind之后的“+”或者“*”,幫你把數據整合起來。

 

      .combine還可以接上自己編譯的函數,這點很人性化,譬如:

 

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. cfun <- function(a, b)  a+b  
  2. foreach(i=1:4, .combine="cfun") %do% rnorm(4)  

 

.combine幫你把輸出結果,再要調整的問題一次性解決了,並且將數據整合也進行並行加速,棒!

 

 

一些關於.combine的c,rbind,cbind,*,+其他案例:

 

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1.  x <- foreach(a=1:3, b=rep(10, 3), .combine="c") %do%  
  2.  {  
  3.     x1<-(a + b);  
  4.      x2<-a*b;  
  5.      c(x1,x2);    
  6.  }  
  7. > x  
  8. [1] 11 10 12 20 13 30  
  9. > x <- foreach(a=1:3, b=rep(10, 3), .combine="rbind") %do%  
  10.  {  
  11.     x1<-(a + b);  
  12.      x2<-a*b;  
  13.      c(x1,x2);    
  14.  }  
  15. > x  
  16.          [,1] [,2]  
  17. result.1   11   10  
  18. result.2   12   20  
  19. result.3   13   30  
  20. > x <- foreach(a=1:3, b=rep(10, 3), .combine="cbind") %do%  
  21.  {  
  22.      x1<-(a + b);  
  23.      x2<-a*b;  
  24.      c(x1,x2);    
  25.  }  
  26. > x  
  27.      result.1 result.2 result.3  
  28. [1,]       11       12       13  
  29. [2,]       10       20       30  
  30. > x <- foreach(a=1:3, b=rep(10, 3), .combine="+") %do%  
  31.  {  
  32.      x1<-(a + b);  
  33.      x2<-a*b;  
  34.      c(x1,x2);    
  35.  }  
  36. > x  
  37. [1] 36 60  
  38. > x <- foreach(a=1:3, b=rep(10, 3), .combine="*") %do%  
  39.  {  
  40.     x1<-(a + b);  
  41.      x2<-a*b;  
  42.      c(x1,x2);    
  43.  }  
  44. > x  
  45. [1] 1716 6000  

 

 

3、參數.inorder——定義輸出結果的順序

 

      .inorder:TRUE則返回和原始輸入相同順序的結果(對結果的順序要求嚴格的時候),FALSE返回沒有順序的結果(可以提高運算效率)。這個參數適合於設定對結果順序沒有需求的情況。

 

      順序這東西,寫過稍微復雜的函數都知道,特別在數據匹配時尤為重要,因為你需要定義一些rownames的名稱,這時候輸出的順序萬一不匹配,可能后面還要花時間匹配過來。

 

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. foreach(i=4:1, .combine='c', .inorder=FALSE) %dopar% {  
  2.   Sys.sleep(3 * i)  
  3.   i  
  4. }  
  5. ## [1] 4 3 2 1  



 

 

——————————————————————————————————————

 

 

 

三、中級教程:利用doParallel並行+聯用迭代器優化內存

1、利用doParallel並行——%dopar%

      foreach包創作是為了解決一些並行計算問題,將”%do%“更改為“%dopar%”前面例子就可以實現並行計算。在並行之前,需要register注冊集群:

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. library(foreach)  
  2. library(doParallel)  
  3.   
  4. cl<-makeCluster(no_cores)  
  5. registerDoParallel(cl)  

      要記得最后要結束集群(不是用stopCluster()):stopImplicitCluster()

 

 

2、參數when——按條件運算

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. foreach(a=irnorm(1, count=10), .combine='c') %:% when(a >= 0) %do% sqrt(a)  


      其中when是通過%:%來導出,而且%:%之后,還可以接%do%

 

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. qsort <- function(x) {  
  2.   n <- length(x)  
  3.   if (n == 0) {  
  4.     x  
  5.     } else {  
  6.       p <- sample(n, 1)  
  7.       smaller <- foreach(y=x[-p], .combine=c) %:% when(y <= x[p]) %do% y  
  8.       larger <- foreach(y=x[-p], .combine=c) %:% when(y > x[p]) %do% y  
  9.       c(qsort(smaller), x[p], qsort(larger))  
  10.       }  
  11. }  
  12.   
  13. qsort(runif(12))  
  14. ##  [1] 0.1481 0.2000 0.2769 0.4729 0.4747 0.5730 0.6394 0.6524 0.8315 0.8325  
  15. ## [11] 0.8413 0.8724  



 

3、聯用iterators——優化、控制內存

 

 

      iterators是為了給foreach提供循環變量,每次定義一個iterator,它都內定了“循環次數”和“每次循環返回的值”,因此非常適合結合foreach的使用。
      iter(obj, ...):可以接受iter, vector, matrix, data.frame, function。
      nextElem(obj, ...):接受iter對象,顯示對象數值。
 
      以matrix為例,
 
[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. iter(obj, by=c('column', 'cell', 'row'), chunksize=1L, checkFunc=function(...) TRUE, recycle=FALSE, ...)  

      參數解讀:
      by:按照什么順序循環;
      matrix和data.frame都默認是“row”,“cell”是按列依次輸出(所以對於“cell”,chunksize只能指定為默認值,即1)
      chunksize:每次執行函數nextElem后,按照by的設定返回結果的長度。如果返回結構不夠,將取剩余的全部。
      checkFunc=function(...) TRUE:執行函數checkFun,如果返回TRUE,則返回;否則,跳過。
      recycle:設定在nextElem循環到底(“錯誤: StopIteration”)是否要循環處理,即從頭再來一遍。
 

(1)iter+function迭代輸出

 

      來看一個iter案例,幫你把函數分塊給你,不用一次性導入計算,耗費內存:

 

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. > a  
  2.      [,1] [,2] [,3] [,4] [,5]  
  3. [1,]    1    5    9   13   17  
  4. [2,]    2    6   10   14   18  
  5. [3,]    3    7   11   15   19  
  6. [4,]    4    8   12   16   20  
  7. > i2 <- iter(a, by = "column", checkFunc=function(x) sum(x) > 50)  
  8. > nextElem(i2)  
  9.      [,1]  
  10. [1,]   13  
  11. [2,]   14  
  12. [3,]   15  
  13. [4,]   16  
  14. > nextElem(i2)  
  15.      [,1]  
  16. [1,]   17  
  17. [2,]   18  
  18. [3,]   19  
  19. [4,]   20  
  20. > nextElem(i2)  
  21. 錯誤: StopIteration  

      不過,如果沒有next了,就會出現報錯,這時候就需要稍微注意一下。

 

 

      iter(function()rnorm(1)),使用nextElem可以無限重復;但是iter(rnorm(1)),只能來一下。
更有意思的是對象如果是iter,即test1 <- iter(obj); test2 <- iter(test1),那么這兩個對象是連在一起的,同時變化。

 

 

      (2)生成隨機數

 

 

      irnorm(..., count);irunif(..., count);irbinom(..., count);irnbinom(..., count);irpois(..., count)是內部生成iterator的工具,分別表示從normal,uniform,binomial,negativity binomial和Poisson分布中隨機選取N個元素,進行count次。
 
      其中,negative binomial分布:其概率積累函數(probability mass function)為擲骰子,每次骰子為3點的概率為p,在第r+k次恰好出現r次的概率。
 
      icount(count)可以生成1:conunt的iterator;如果count不指定,將從無休止生成1:Inf
      icountn(vn)比較好玩,vn是指一個數值向量(如果是小數,則向后一個數取整,比如2.3 --> 3)。循環次數為prod(vn),每次返回的向量中每個元素都從1開始,不超過設定 vn,變化速率從左向右依次遞增。
 
      idiv(n, ..., chunks, chunkSize)返回截取從1:n的片段長度,“chunks”和“chunkSize”不能同時指定,“chunks”為分多少片段(長度從大到小),“chunkSize”為分段的最大長度(長度由大到小)
      iapply(X, MARGIN):與apply很像,MARGIN中1是row,2是column
      isplit(x, f, drop=FALSE, ...):按照指定的f划分矩陣
 
[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. > i2 <- icountn(c(3.4, 1.2))  
  2. > nextElem(i2)  
  3. [1] 1 1  
  4. > nextElem(i2)  
  5. [1] 2 1  
  6. > nextElem(i2)  
  7. [1] 3 1  
  8. > nextElem(i2)  
  9. [1] 4 1  
  10. > nextElem(i2)  
  11. [1] 1 2  
  12. > nextElem(i2)  
  13. [1] 2 2  
  14. > nextElem(i2)  
  15. [1] 3 2  
  16. > nextElem(i2)  
  17. [1] 4 2  
  18. > nextElem(i2)  
  19. 錯誤: StopIteration  



 

——————————————————————————————————————

 

四、高級教程:並行機器學習算法

 

      並行計算一些小任務會比按順序運算它們花費更多的時間,所以當普通運算足夠快的時候,並沒有必要使用並行計算模式改進其運算效率。

      同時,最適合並行莫過於隨機森林算法了。

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. #生成矩陣x作為輸入值,y作為目標因子  
  2. <- matrix(runif(500), 100)  
  3. <- gl(2, 50)  
  4. #導入randomForest包  
  5. require(randomForest)  

 

 

1、獨立循環運行隨機森林算法

 

      如果我們要創建一個包含1200棵樹的隨機森林模型,在6核CPU電腦上,我們可以將其分割為六塊執行randomForest函數六次,同時將ntree參賽設為200,最后再將結果合並。

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. rf <- foreach(ntree=rep(200, 6), .combine=combine) %do%  
  2.   randomForest(x, y, ntree=ntree)  
  3. rf  
  4. ##   
  5. ## Call:  
  6. ##  randomForest(x = x, y = y, ntree = ntree)   
  7. ##                Type of random forest: classification  
  8. ##                      Number of trees: 1200  
  9. ## No. of variables tried at each split: 2  

 

      分開來運行6個200樹的隨機森林算法。

 

2、參數.packages——並行運行隨機森林算法

 

      將%do%改為“%dopar%”,同時使用.packages調用randomForest:

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. rf <- foreach(ntree=rep(200,6), .combine=combine, .packages="randomForest") %dopar%   
  2.   randomForest(x, y, ntree=ntree)  
  3. rf  

      通過.packages來將函數包導入其中,類似parallel中的clusterEvalQ,但是foreach在一個函數里面包含了函數、包的導入過程。

 

      當然還可以使用一些其他包,使用.packages參數來加載包,比如說:.packages = c("rms", "mice")

 

3、參數.export——將doParallel並行寫入函數

 

      寫入函數有個問題就是,運行函數的時候,運用不了R外面內存環境的變量。而且會報錯:
[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. test <- function (exponent) {  
  2.   foreach(exponent = 2:4,   
  3.           .combine = c)  %dopar%    
  4.     base^exponent  
  5. }  
  6. test()  
  7.   
  8.  Error in base^exponent : task 1 failed - "object 'base' not found"   


      所以需要在寫函數的時候,將一些外面的內存函數,寫到函數之中,通過.export,而不需要使用clusterExport。注意的是,他可以加載最終版本的變量,在函數運行前,變量都是可以改變的:

 

[html]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. base <- 2  
  2. cl<-makeCluster(2)  
  3. registerDoParallel(cl)  
  4.    
  5. base <- 4  
  6. test <- function (exponent) {  
  7.   foreach(exponent = 2:4,   
  8.           .combine = c,  
  9.           .export = "base")  %dopar%    
  10.     base^exponent  
  11. }  
  12. test()  
  13.    
  14. stopCluster(cl)  
  15.   
  16.  [1]  4  8 16  

 

 

——————————————————————————————————————

 

參考文獻:

 

1、R語言中的並行計算:foreach,iterators, doParallel包

2、foreach包簡介     /    FROM:《Using The foreach Package》

3、 R語言︱大數據集下運行內存管理

4、R︱並行計算以及提高運算效率的方式(parallel包、clusterExport函數、SupR包簡介)

 

轉自:http://blog.csdn.net/sinat_26917383/article/details/53349557

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM