首页 文章

在R中运行优化时并行调用目标函数

提问于
浏览
0

我在R中进行优化 . 我的问题涉及在一个目标函数上运行 nlm ,该函数循环遍历大量数据 . 我想通过并行运行目标函数来加速优化 . 我应该怎么做呢?

在下面的示例中,我设置了一个玩具问题,其中并行化解决方案比原始解决方案慢 . 如何修改代码以减少开销并加快我的 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 调用并行目标函数时,它会 Build 一个新的集群并分解并复制所有的 my_big_list . 这似乎很浪费 - 如果我以某种方式设置群集并且每次 nlm 调用只复制一次列表,我会得到更好的结果吗?如果是这样,我该怎么做?


在Erwin的回答之后编辑(“考虑创建和停止集群一次而不是在每次评估中”):

## 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 . 令我感到惊讶的是,时间比我小小的笔记本电脑要长,但是当你有更多内核时,单集群并行优化比常规非并行版本更有意义 .


另一个编辑,为了完整性(参见Erwin答案下的评论):这是一个使用分析梯度的非并行解决方案 . 令人惊讶的是,它比数值梯度慢 .

## 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

我在这里继续 . 那说,我的问题仍然是 How can I speed up this sort of optimization problem using parallelization?

1 回答

  • 2

    在我看来,在并行功能评估中有太多的开销使它值得 . 考虑创建和停止集群一次,而不是在每次评估中 . 另外我相信你不提供渐变,因此求解器可能会有限差异,这可能会导致大量的函数评估调用 . 您可能需要考虑提供渐变 .

相关问题