在foreach循环中运行h2o算法?

3

我天真地认为在foreach循环中并行调用多个h2o.gbm是很简单的。但是却遇到了奇怪的错误。

Error in { : 
         task 3 failed - "java.lang.AssertionError: Can't unlock: Not locked!"

以下是代码:

代码如下

library(foreach)
library(doParallel)
library(doSNOW)

Xtr.hf = as.h2o(Xtr)
Xval.hf = as.h2o(Xval)

cl = makeCluster(6, type="SOCK")
registerDoSNOW(cl)
junk <- foreach(i=1:6, 
            .packages=c("h2o"), 
            .errorhandling = "stop",
            .verbose=TRUE) %dopar% 
{
   h2o.init(ip="localhost", nthreads=2, max_mem_size = "5G") 
   for ( j in 1:3 ) { 
     bm2 <- h2o.gbm(
     training_frame = Xtr.hf,  
     validation_frame = Xval.hf, 
     x=2:ncol(Xtr.hf),
     y=1,          
     distribution="gaussian",
     ntrees = 100,
     max_depth = 3,
     learn_rate = 0.1,
     nfolds = 1)
  }
  h2o.shutdown(prompt=FALSE)    
  return(iname)
}
stopCluster(cl)
1个回答

4

注意:这种使用R的并行foreach可能性不大,但我会先回答你的问题,然后解释原因。(顺便说一下,在本答案中,当我使用“群集”一词时,我指的是H2O群集(即使它只是在您的本地计算机上),而不是R“群集”。)

我已经重写了您的代码,假设意图是创建一个单个的H2O群集,其中将制作所有模型:

library(foreach)
library(doParallel)
library(doSNOW)
library(h2o)

h2o.init(ip="localhost", nthreads=-1, max_mem_size = "5G") 

Xtr.hf = as.h2o(Xtr)
Xval.hf = as.h2o(Xval)

cl = makeCluster(6, type="SOCK")
registerDoSNOW(cl)
junk <- foreach(i=1:6, 
            .packages=c("h2o"), 
            .errorhandling = "stop",
            .verbose=TRUE) %dopar% 
{
   for ( j in 1:3 ) { 
     bm2 <- h2o.gbm(
     training_frame = Xtr.hf,  
     validation_frame = Xval.hf, 
     x=2:ncol(Xtr.hf),
     y=1,          
     distribution="gaussian",
     ntrees = 100,
     max_depth = 3,
     learn_rate = 0.1,
     nfolds = 1)

   #TODO: do something with bm2 here?

  }
  return(iname)  #???
}
stopCluster(cl)

即以大纲形式:

  • 启动 H2O,并将 XtrXval 加载到其中
  • 在您的 R 客户端中启动 6 个线程
  • 在每个线程中依次创建 3 个 GBM 模型

我删除了 h2o.shutdown() 命令,猜测您不想这样做(当关闭 H2O 集群时,您刚刚创建的模型会被删除)。我还突出了您可能需要对模型进行处理的位置。我已经为 H2O 提供了您计算机上的所有线程(即 nthreads=-1h2o.init() 中),而不仅仅是 2 个。

您可以并行地创建 H2O 模型,但这通常是一个不好的想法,因为它们最终会争夺资源。最好逐个进行计算,并依靠 H2O 自己的并行代码将计算分布到集群中。(当集群是单个计算机时,这往往非常有效。)

由于您费心在 R 中创建了并行循环,这让我觉得您可能错过了 H2O 的工作方式:它是用 Java 编写的服务器,而 R 只是一个轻量级客户端,用于发送 API 调用。GBM 计算不是在 R 中进行的;它们都是在 Java 代码中完成的。

另一种解释您的代码的方式是运行多个 H2O 实例,即多个 H2O 集群。如果您有一组计算机,并且您知道 H2O 算法在多节点集群上的扩展性不太好,那么这可能是个好主意。但是,在单台计算机上这样做几乎肯定是不好的。但出于论证目的,以下是如何执行此操作(未经测试):

library(foreach)
library(doParallel)
library(doSNOW)

cl = makeCluster(6, type="SOCK")
registerDoSNOW(cl)
junk <- foreach(i=1:6, 
            .packages=c("h2o"), 
            .errorhandling = "stop",
            .verbose=TRUE) %dopar% 
{
   library(h2o)
   h2o.init(ip="localhost", port = 54321 + (i*2), nthreads=2, max_mem_size = "5G") 

    Xtr.hf = as.h2o(Xtr)
    Xval.hf = as.h2o(Xval)

   for ( j in 1:3 ) { 
     bm2 <- h2o.gbm(
     training_frame = Xtr.hf,  
     validation_frame = Xval.hf, 
     x=2:ncol(Xtr.hf),
     y=1,          
     distribution="gaussian",
     ntrees = 100,
     max_depth = 3,
     learn_rate = 0.1,
     nfolds = 1)

    #TODO: save bm2 here
  }
  h2o.shutdown(prompt=FALSE)    
  return(iname)  #???
}
stopCluster(cl)

现在大纲如下:
  • 创建6个R线程
  • 在每个线程中启动一个H2O集群,该集群在本地主机上运行,但端口是唯一的。(因为每个H2O集群实际上使用两个端口,“i * 2”)
  • 将您的数据上传到H2O集群(即将重复6次,每个集群都执行一次)。
  • 制作3个GBM模型,一个接一个地进行。
  • 对这些模型做些什么
  • 终止当前线程的集群。

如果你的机器有12+个线程和30+ GB内存,并且数据相对较小,那么这将与使用一个H2O集群并串行制作12个GBM模型一样有效。如果没有,我认为它会更糟。 (但是,如果您已经在6台远程计算机上预先启动了6个H2O集群,则可能会成为一种有用的方法-我必须承认,一直以来我一直在想如何做到这一点,并且在看到您的问题之前从未想过使用并行库!)

注意:截至当前版本(3.10.0.6),我知道上面的代码不起作用,因为在h2o.init()中存在错误,有效地表示它忽略了端口。(解决方法:在命令行上预先启动所有6个H2O集群,或在环境变量中设置端口。)


感谢您的解释。所以您的代码和我的唯一区别就是 h2o.init(ip="localhost", port = 54321 + (i*2),...)。通过分配不同的端口,h2o为每个线程创建了一个单独的集群。 - horaceT
@horaceT 同时 as.h2o() 数据上传必须放在 for 循环内部。(我也把 library(h2o) 放在 foreach 循环里,不过我不确定是否有必要。)(正如所指出的那样,该代码在修复端口错误之前都无法工作。) - Darren Cook
我还没有测试过,但我只是想理解这个概念。调用 h2o.init(...) 会创建一个集群,每个集群都附加到一个且仅一个线程上。我不能在同一个集群内运行多个线程。这就是它应该工作的方式吗? - horaceT
1
我发现将H2O集群始终视为与您的R客户端在不同的机器(或一组机器)上有助于理解。 H2O集群将使用您分配给它的所有线程。 您的R客户端仅向远程机器上的该集群发送指令。 客户机上没有任何计算,因此,无论您的R客户端是使用单个线程还是多个线程,都不会使任何操作更快。您可以连接多个客户端到H2O集群,每个客户端上传,下载,启动模型等。 - Darren Cook

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接