2016-10-09 11 views
0

私は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_parallelapply_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 

私はここで何が起こっているのか知りたいです。それは、私の質問はまだです並列化を使用してこの種の最適化問題をスピードアップするにはどうしたらいいですか?

+0

私が探しているのは、https://stackoverflow.com/questions/6689937/r-problem-with-foreach-dopar-inside-function-called-by-optimのコメントと関連していると思います。特に「各ノードでデータを個別にロードしたい」 – Adrian

+0

https://github.com/hadley/multidplyr/blob/master/vignettes/multidplyr.mdは関連性があります。その点を見てみましょう – Adrian

+0

「例:データ移動のコスト "のスライドhttp://www.labs.hpe.com/research/systems-research/R-workshop/Indrajit-talk5.pdfは、私が" very_big_list必要以上にコピーする」 – Adrian

答えて

2

私には、並列関数の評価にあまりにも多くのオーバーヘッドがあるため、それは価値があると思われます。各評価ではなく、一度クラスタを作成および停止することを検討してください。また、私はあなたがグラジエントを提供しないと信じているので、ソルバーは有限の違いを起こす可能性があり、多数の関数評価呼び出しにつながる可能性があります。グラデーションを提供することを検討することをお勧めします。

+0

ありがとうございます(+1)。あなたが提案したようにnlmコールごとに一度クラスタを作成して停止する編集を追加しました。これは高速化されますが、非並行バージョンはまだまだ高速です。グラデーションを提供することはおそらく助けになると私は同意しますが、私は並列化されていないバージョンに比べて並列化されたバージョンの性能に興味があります。並列化されたバージョンを改善する他の明白な方法はありますか? 'apply_my_objfn_in_parallel_single_cluster'であっても、' very_big_list'のデータは必要以上にコピーされると思います。あれは正しいですか? – Adrian

+1

私は 'parLapply'が多くのデータをシャッフルしていると信じています。パラレルステップでデータを安価に渡すだけでは、オーバーヘッドが大きくなる可能性があります。これを克服する一つの方法は、 'bigmemory'パッケージのような、いくつかの共有メモリ構造を使うことです。 –

+0

http://www.labs.hpe.com/research/systems-research/R-workshop/Indrajit-talk5.pdfの4番目のスライドは関連性があります。 (1) 'my_big_list 'をN個(N個のマシンのコアの数)に分割する方法、(2)各ワーカーに作品を一度送って(一度ワーカーが作品を永久に参照する方法) (3) 'nlm'が新しいパラメータ値を考慮するたびに、リストの目的関数値を計算するようにしますか? – Adrian

関連する問題