私はRで最適化を行っています。私の問題は、大きなリストのデータをループする目的関数にnlm
を実行することです。目的関数を並列に実行して最適化を高速化したいと思います。それをどうやってやりますか?Rで最適化を実行しているときに目的関数を並列に呼び出す
以下の例では、並列化されたソリューションがオリジナルよりも遅いというおもちゃの問題を設定しました。オーバーヘッドを減らし、nlm
コールの並列化バージョンを高速化するコードを変更するにはどうすればよいですか?
library(parallel)
## What is the right way to do optimization when the objective function is run in parallel?
## Don't want very_big_list to be copied more than necessary
set.seed(952)
my_objfn <- function(list_element, parameter) {
return(sum((list_element - parameter)^2)) # Simple example
}
apply_my_objfn_in_parallel <- function(parameter, very_big_list, max_cores=3) {
cluster <- makeCluster(min(max_cores, detectCores() - 1))
objfn_values <- parLapply(cluster, very_big_list, my_objfn, parameter=parameter)
stopCluster(cluster)
return(Reduce("+", objfn_values))
}
apply_my_objfn <- function(parameter, very_big_list) {
objfn_values <- lapply(very_big_list, my_objfn, parameter=parameter)
return(Reduce("+", objfn_values))
}
my_big_list <- replicate(2 * 10^6, sample(seq_len(100), size=5), simplify=FALSE)
parameter_guess <- 20
mean(c(my_big_list, recursive=TRUE)) # Should be close to 50
system.time(test_parallel <- nlm(apply_my_objfn_in_parallel, parameter_guess,
very_big_list=my_big_list, print.level=0)) # 84.2 elapsed
system.time(test_regular <- nlm(apply_my_objfn, parameter_guess,
very_big_list=my_big_list, print.level=0)) # 63.6 elapsed
私は私のラップトップ上で(4つのCPUを、そうmakeCluster(min(max_cores, detectCores() - 1))
によって返されたクラスタは3つのコアを持っている)、これを実行しました。上の最後の行では、apply_my_objfn_in_parallel
はapply_my_objfn
よりも長くかかります。私はこれが(1)私は3つのコアしか持っておらず、(2)nlm
が並列化された目的関数を呼び出すたびに、それは新しいクラスターをセットアップし、すべてを破棄してコピーします。それは無駄に思えます - 何とかクラスタを設定し、nlm
コールごとに1回だけリストをコピーすると、よりよい結果が得られますか?もしそうなら、どうしたらいいですか?アーウィンの答えの後
編集(「作成し、各評価に一度の代わりに、クラスタを停止考える」):
私のラップトップに加えて## Modify function to use single cluster per nlm call
apply_my_objfn_in_parallel_single_cluster <- function(parameter, very_big_list, my_cluster) {
objfn_values <- parLapply(my_cluster, very_big_list, my_objfn, parameter=parameter)
return(Reduce("+", objfn_values))
}
run_nlm_single_cluster <- function(very_big_list, parameter_guess, max_cores=3) {
cluster <- makeCluster(min(max_cores, detectCores() - 1))
nlm_result <- nlm(apply_my_objfn_in_parallel_single_cluster, parameter_guess,
very_big_list=very_big_list, my_cluster=cluster, print.level=0)
stopCluster(cluster)
return(nlm_result)
}
system.time(test_parallel <- nlm(apply_my_objfn_in_parallel, parameter_guess,
very_big_list=my_big_list, print.level=0)) # 49.0 elapsed
system.time(test_regular <- nlm(apply_my_objfn, parameter_guess,
very_big_list=my_big_list, print.level=0)) # 36.8 elapsed
system.time(test_single_cluster <- run_nlm_single_cluster(my_big_list,
parameter_guess)) # 38.4 elapsed
(上記のコメントで経過時間)、私は走りました30コアのサーバー上のコード。私の経過時間は、apply_my_objfn
で107、run_nlm_single_cluster
で74でした。小さなノートパソコンよりも時間がかかっていたのには驚きましたが、より多くのコアがある場合は、単一クラスタの並列最適化が通常の非並列バージョンよりも勝っていることは意味があります。
完全性についての別の編集、(アーウィンの答えの下のコメントを参照してください):ここでは、分析勾配を使用して非平行なソリューションです。意外なことに、数値勾配よりも遅いです。
## Add gradients
my_objfn_value_and_gradient <- function(list_element, parameter) {
return(c(sum((list_element - parameter)^2), -2*sum(list_element - parameter)))
}
apply_my_objfn_with_gradient <- function(parameter, very_big_list) {
## Returns objfn value with gradient attribute, see ?nlm
objfn_values_and_grads <- lapply(very_big_list, my_objfn_value_and_gradient, parameter=parameter)
objfn_value_and_grad <- Reduce("+", objfn_values_and_grads)
stopifnot(length(objfn_value_and_grad) == 2) # First is objfn value, second is gradient
objfn_value <- objfn_value_and_grad[1]
attr(objfn_value, "gradient") <- objfn_value_and_grad[2]
return(objfn_value)
}
system.time(test_regular <- nlm(apply_my_objfn, parameter_guess,
very_big_list=my_big_list, print.level=0)) # 37.4 elapsed
system.time(test_regular_grad <- nlm(apply_my_objfn_with_gradient, parameter_guess,
very_big_list=my_big_list, print.level=0,
check.analyticals=FALSE)) # 45.0 elapsed
私はここで何が起こっているのか知りたいです。それは、私の質問はまだです並列化を使用してこの種の最適化問題をスピードアップするにはどうしたらいいですか?
私が探しているのは、https://stackoverflow.com/questions/6689937/r-problem-with-foreach-dopar-inside-function-called-by-optimのコメントと関連していると思います。特に「各ノードでデータを個別にロードしたい」 – Adrian
https://github.com/hadley/multidplyr/blob/master/vignettes/multidplyr.mdは関連性があります。その点を見てみましょう – Adrian
「例:データ移動のコスト "のスライドhttp://www.labs.hpe.com/research/systems-research/R-workshop/Indrajit-talk5.pdfは、私が" very_big_list必要以上にコピーする」 – Adrian