Hatena::ブログ(Diary)

ほくそ笑む このページをアンテナに追加 RSSフィード

2014-12-22

R で超簡単に並列処理を書けるパッケージ pforeach を作った

※この記事は R Advent Calendar 2014 : ATND の 22 日目の記事です。

0. この記事の要約

R の foreach パッケージを改良して、デフォルトで並列計算するようにしたパッケージ pforeach を作りました。

これにより、R での並列計算を下記のようにシンプルに書くことができます。

library(pforeach)
pforeach(i = 1:100)({
  i ** 2
})

これは、従来の foreach で次のように書いたものと同じ動作をします。

library(foreach)
library(doParallel)
cl <- makeCluster(detectCores())
registerDoParallel(cl)
foreach(i = 1:100, .combine = c) %dopar% {
  i ** 2
}
stopCluster(cl)

1. はじめに

R で並列処理を書くのって、結構めんどうだと思いませんか?

私は普段から foreach パッケージを使っていて、ループを書く際は必ず foreach で書きます。

例えば、こんな感じのコード。

library(foreach)
library(dplyr)
foreach(i = 1:150, .combine = c) %do% {
  iris[i, ] %>% select(-Species) %>% sum
}

それで、速度が遅いなーと思ったら、並列計算するようにコードを書き換えるわけですが、これが面倒くさい。

コードを並列計算用に書き換えるには、だいたい次のようなことを考えなければなりません。

  • 並列計算用ライブラリの読み込み
  • クラスタの作成
  • クラスタの登録
  • ループ内で使用するパッケージの指定
  • ループ内にエクスポートする必要のある変数の指定
  • %do% を %dopar% に変更
  • クラスタの終了処理

こういうことを考えた上で出来上がるコードは、下記のようになります。

library(foreach)
library(dplyr)
library(doParallel)               # 並列計算用ライブラリの読み込み
cl <- makeCluster(detectCores())  # クラスタの作成
registerDoParallel(cl)            # クラスタの登録
foreach(i = 1:150, .combine = c,
        .packages = "dplyr"       # ループ内で使用するパッケージの指定
        ) %dopar% {               # %do% を %dopar% に変更
  iris[i, ] %>% select(-Species) %>% sum
}
stopCluster(cl)                   # クラスタの終了処理

めっちゃ複雑 orz

これって、なんとかなりませんか? もっとシンプルに書くことはできないのでしょうか?


というわけで、作りました。

超簡単に R コードを並列化できるパッケージ pforeach です。

先ほどのコードを pforeach で書き直すと、次のようになります。

library(pforeach)
library(dplyr)
pforeach(i = 1:150)({
  iris[i, ] %>% select(-Species) %>% sum
})

並列化する前のコードとほとんど変わっていません。

このコードでクラスタの作成や終了処理、パッケージの指定や変数のエクスポートなど、面倒くさいことはすべて自動的にやってくれます。

2. インストール方法

インストール方法は、次の通りです。

install.packages("devtools") # devtools をインストールしていない場合のみ
devtools::install_github("hoxo-m/pforeach")

3. 使い方

使い方は基本的には上に書いた通りです。

例えば下記のコードを並列化したいときは、

library(foreach)
foreach(i = 1:100, .combine=c) %do% {
  i ** 2
}

次のように書けば OK です。

library(pforeach)
pforeach(i = 1:100)({
  i ** 2
})

pforeach のデフォルト .combine は c です。また、%do% が無くなっていることに注意してください。

4. オプション

pforeach では、foreach のオプションが全て使えます。

foreach のオプションについては下記サイトが詳しいです。

ここでは、pforeach にのみ存在するオプションについて説明します。

4-1. コア数の指定

並列計算で使用するコア数は、.cores 引数で指定できます(デフォルトでは最大コア数を使用します)。

library(pforeach)
pforeach(i = 1:100, .cores = 2)({  # 並列計算で 2 コアのみ使用
  i ** 2
})

また、.cores = -1 などマイナスの値を指定すると、最大コア数 - 1 のようになります。

library(pforeach)
pforeach(i = 1:100, .cores = -1)({  # detectCores() - 1 と同じ
  i ** 2
})
4-2. 乱数の固定

並列計算でランダムな処理が入る場合、再現性を確保するために乱数を固定したい場合があります。

その場合は .seed 引数を指定することで乱数を固定できます。

pforeach(i = 1:3, .seed = 12345)({
  rnorm(1)
})
4-3. 非並列化計算への切り替え

並列化処理をデバッグなどのために逐次処理に切り替えたい場合があります。

これは .parallel 引数を変更することによってできます。

pforeach(i = 1:3, .parallel = FALSE)({  # 非並列化
  rnorm(1)
})

ただし、この切り替えを頻繁に行うことがあるので、簡単のために npforeach() という関数を用意しています。

npforeach(i = 1:3)({  # 非並列化
  rnorm(1)
})

1文字の追加/削除だけで並列/非並列を切り替えられるので、これを使った方が便利です。

4-4. その他

その他の foreach のオプションについては、下記リンクと書籍をご参照ください。

5. データフレームに対する便利関数

データフレームの一行/一列ごとにループを回すということもよくやります。

pforeach では、そんなときに便利な関数を用意しています。

5-1. rows() と irows()

rows() は一行ごとにループを回したいときに便利な関数です。

pforeach(row = rows(iris))({
  row %>% select(-Species) %>% sum
})

さらに、irows() はデータフレームのループカウンタを作る便利関数です。

私は非並列実行時の進行状況チェックなどによく使います。

npforeach(row = rows(iris), i = irows(iris))({
  cat(i, "/", nrow(iris), "\n")
  row %>% select(-Species) %>% sum
})
5-2. cols() と icols()

cols(), icols() は列に対する同様の関数です。

6. 適用例 - ランダムフォレストの並列化

最後に、並列計算の適用例として、ランダムフォレストの並列化をやってみます。

foreach を使って普通に書くと下記のようになります。

library(foreach)
library(doParallel)
library(randomForest)
library(kernlab)

data(spam)
cores <- detectCores()
cl <- makePSOCKcluster(cores)
registerDoParallel(cl)
fit.rf <- foreach(ntree = rep(250, cores), .combine = combine, 
                  .export = "spam", .packages = "randomForest") %dopar% {
  randomForest(type ~ ., data = spam, ntree = ntree)
}
stopCluster(cl)

pforeach を使って書き直すと、下記のように本質的でない部分は取り除かれて、非常にすっきりしたコードとなります。

library(pforeach)
library(randomForest)
library(kernlab)

data(spam)
fit.rf <- pforeach(ntree = rep(250, .cores), .c = combine)({
  randomForest(type ~ ., data = spam, ntree = ntree)
})

7. おわりに

R で超簡単に並列処理を書くことのできる pforeach パッケージを作成しました。

ぜひ、使ってみて感想などお寄せくださるとありがたいです。

8. 参考文献

本パッケージを作成するにあたって、下記書籍を参考にしました。

また、本記事で取り上げたランダムフォレストの並列化コードは、この書籍の例を参考に書いています。

この書籍がなかったら、本パッケージは存在しなかったと言っても過言ではありません。

並列化だけでなく、R における計算高速化に関する様々なことが書かれている、類を見ない書籍だと思います。

私の最近の一番のおススメ書籍です。

michiyomichiyo 2015/05/14 18:20 大変に勉強になりました。
一点、ご教示をよろしくお願いします。
tuneRF関数も同様にpforeach関数を用いて並列化処理できるのでしょうか?

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証