以前の記事に書いたように、R の foreach で並列処理を書くときは、次のように書きます。
library(doParallel)
ExecuteParallelProcess <- function() {
cl <- makeCluster(detectCores())
registerDoParallel(cl)
on.exit(stopCluster(cl))
foreach(i = 1:3, .combine=c) %dopar% {
rnorm(1)
}
}
ExecuteParallelProcess()
[1] 0.6725327 0.8103935 -0.4231717
このプログラムは、正規分布から 3 つの乱数を取得するというものです。
科学技術計算の分野では、シミュレーション結果の再現性を保つために、乱数を固定したいという要求があります。
つまり、何度実行しても同じ結果が得られるように、毎回同じ乱数を生成したいということです。
この目的のために、R では通常 set.seed()
関数を用いて乱数を固定します。
ExecuteParallelProcess <- function() {
set.seed(12345) # この行を追加
cl <- makeCluster(detectCores())
registerDoParallel(cl)
on.exit(stopCluster(cl))
foreach(i = 1:3, .combine=c) %dopar% {
rnorm(1)
}
}
ExecuteParallelProcess()
ExecuteParallelProcess()
[1] -0.3014045 0.7398383 -0.7157335
[1] -0.98871268 -0.03876135 0.31606417
しかし、ご覧のように、並列計算を行う場合、set.seed()
で乱数を固定することはできません。
乱数の固定の仕方は、使用する並列化パッケージによって異なるようですが、ここでは parallel
パッケージの clusterSetRNGStream()
関数を使って乱数を固定してみましょう。
ExecuteParallelProcess <- function() {
cl <- makeCluster(detectCores())
clusterSetRNGStream(cl, 12345) # この行を追加
registerDoParallel(cl)
on.exit(stopCluster(cl))
foreach(i = 1:3, .combine=c) %dopar% {
rnorm(1)
}
}
ExecuteParallelProcess()
ExecuteParallelProcess()
[1] -1.45785035 -0.03518695 0.05799898
[1] -1.45785035 -0.03518695 0.05799898
これにより、並列処理においても乱数を固定することができるようになりました。
ところで、この記事に書いた方法でクラスタを implicit にしたい場合、クラスタオブジェクトを引数に取る clusterSetRNGStream()
は使えません。
そこで便利なのが doRNG
パッケージです。
doRNG
パッケージの registerDoRNG()
関数を使えば、クラスタを implicit にしても乱数の固定ができます。
library(doRNG)
ExecuteParallelProcess <- function() {
registerDoParallel(detectCores())
registerDoRNG(12345) # この行を追加
on.exit(stopImplicitCluster2())
foreach(i = 1:3, .combine=c) %dopar% {
rnorm(1)
}
}
res1 <- ExecuteParallelProcess()
res2 <- ExecuteParallelProcess()
identical(res1, res2)
[1] TRUE
また、doRNG
を使った別の方法として、foreach
に .options.RNG
オプションを指定して、%dorng%
を使う方法もあります。
ExecuteParallelProcess <- function() {
registerDoParallel(detectCores())
on.exit(stopImplicitCluster2())
foreach(i = 1:3, .combine=c, .options.RNG=12345) %dorng% { # この行を変更
rnorm(1)
}
}
res3 <- ExecuteParallelProcess()
res4 <- ExecuteParallelProcess()
identical(res3, res4)
[1] TRUE
ついでに、上記二つの方法の結果が一致することを確認してみます。
identical(res1, res3)
[1] TRUE
以上です。