在spark上运行分布式xgboost

版权声明:本文为小墨鱼博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zwqjoy/article/details/86608214

XGBoost分布式概述

在XGBoost设计之初,就考虑了分布式的实现。树模型最重要的一个问题即是分割点的确定,XGBoost在单机的环境中,数据全部load进内存,feature已经按照值的大小排好序,采用一个叫做“exact greedy algorithm”算法,经过线性扫描,就可以快速的找到最佳的分割点;但是在分布式环境中,数据分布在各个节点,这种情况下,要找到最佳的分割点是很不容易的,XGBoost提供了一个近似算法来解决这个问题,近似算法的核心在于根据特征的分布来提供一组候选的分割点,至于如何保证候选分割集的有效性和理论上的完备性,不在本文的讨论范围,有兴趣的读者可以参考文献[2]。

分布式环境中,多个节点共同工作,结果采用Allreduce的机制来同步,xgboost依赖dmlc/rabit来完成这个工作

rabit:可容错的Allreduce库

Allreduce是MPI提供的一个主要功能,但是MPI一般不是特别受到广泛欢迎,原因之一就是它本身不容错,但如果砍掉MPI的多余接口,只保留Allreduce和Broadcast,支持容错则简单很多。原因是Allreduce有一个很好的性质,每一个节点最后拿到的是一样的结果,这意味着我们可以让一些节点记住结果。当有节点挂掉重启的时候,可以直接向还活着的节点索要结果就可以了。

容错过程:

1、Node1在第二个checkpoint之后的第一次和第二次allreduce中间挂了

2、当Node1重启,它会调用LoadCheckPoint,这样可以从其他节点得到最近的一次CheckPoint

3、Node1从当前的CheckPoint开始跑,并进行第一次Allreduce,这时其他节点已经知道了结果并把结果告诉Node1

4、当Node1执行到第二个Allreduce,这时大家就已经完全同步上了

有了Allreduce容错机制和近似算法确定分割点,XGBoost算法可以运行在很多已知的集群上,如MPI,Yarn...

XGBoost on Spark

由于spark等基于JVM平台的大数据处理系统应用越来越广泛,2016.4月 XGBoost推出了基于spark/Flink的XGBoost4J(XGBoost for JVM Platform) XGBoost4J Code Examples  

XGBoost4J的核心与XGBoost一样,分布式实现仍然采用rabit Allreduce,但是抽象了一套Java/Scala接口,供spark平台使用。
 

XGBoost-spark 特征处理和参数选择与单机版本基本一致,在分布式环境中需要注意的几个问题:

1、numWorker参数应该与executor数量设置一致,executor-cores设置为1(笔者认为的最优化的配置)

2、在train的过程中,每个partition占用的内存最好限制在executor-memory的1/3以内,因为除了本来训练样本需要驻留的内存外,xgboost为了速度的提升,为每个线程申请了额外的内存,并且这些内存是JVM所管理不到的

3、对于需要在集群机器上共享的资源,比如字典/库文件等,spark提供了一套类似于hadoop distribute cached的机制来满足需求

XGBoost-spark 实际使用

1 添加依赖

scala:2.11.8 ,  jdk:1.8, xgboost:0.81, spark:2.3

    <dependencies>

        <dependency>
            <groupId>ml.dmlc</groupId>
            <artifactId>xgboost4j</artifactId>
            <version>0.81</version>
        </dependency>
        <dependency>
            <groupId>ml.dmlc</groupId>
            <artifactId>xgboost4j-example</artifactId>
            <version>0.81</version>
        </dependency>
        <dependency>
            <groupId>ml.dmlc</groupId>
            <artifactId>xgboost4j-spark</artifactId>
            <version>0.81</version>
        </dependency>

    </dependencies>

2. xgboost scala 代码

package xgboost


import ml.dmlc.xgboost4j.scala.spark.XGBoostClassifier

import org.apache.log4j.{Level, Logger}
import org.apache.spark.ml.feature.{StringIndexer, VectorAssembler}
import scala.collection.mutable.ArrayBuffer

import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.DataFrame

object SparkTraining {

  def processData(df_data: DataFrame): DataFrame = {
    var columns = df_data.columns.clone()
    var feature_columns = new ArrayBuffer[String]()
    for (i <- 1 until columns.length) {
      feature_columns += columns(i)
    }

    val stringIndexer = new StringIndexer()
      .setInputCol("_c0")
      .setOutputCol("class")
      .fit(df_data)

    val labelTransformed = stringIndexer.transform(df_data).drop("_c0")

    val train_vectorAssembler = new VectorAssembler()
      .setInputCols(feature_columns.toArray)
      .setOutputCol("features")

    val xgbData = train_vectorAssembler.transform(labelTransformed).select("features", "class")

    return xgbData
  }

  def main(args: Array[String]): Unit = {

    Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)
    val spark = SparkSession.builder()
      //.master("local")
      .appName("xgboost_spark_demo")
      //      .config("spark.memory.fraction", 0.3)
      //      .config("spark.shuffle.memoryFraction", 0.5)
      .getOrCreate()

    //step 1: 读取 CSV 数据
    val df_train = spark.read.format("com.databricks.spark.csv")
      .option("header", "false")
      .option("inferSchema", true.toString)
      .load("/Users/pboc_train.csv")


    //step 2: 处理CSV 数据
    var xgbTrain = processData(df_train)


    val df_test = spark.read.format("com.databricks.spark.csv")
      .option("header", "false")
      .option("inferSchema", true.toString)
      .load("/Users/pboc_test.csv")

    var xgbTest = processData(df_test)

    val xgbParam = Map("eta" -> 0.1f,
      "objective" -> "binary:logistic",
      "num_round" -> 100,
      "num_workers" -> 4
    )
    val xgbClassifier = new XGBoostClassifier(xgbParam)
      .setEvalMetric("auc")
      .setMaxDepth(5)
      .setFeaturesCol("features")
      .setLabelCol("class")

    println("Start Trainning ......")
    val xgbClassificationModel = xgbClassifier.fit(xgbTrain)
    println("End Trainning ......")

    println("Predicting ...")
    val results = xgbClassificationModel.transform(xgbTest)
    results.show()
  }
}

3. 打包并提交spark任务

使用maven 打包成 xgboostDemo.jar

spark-submit --master yarn --deploy-mode client  --num-executors 3 --executor-cores 10  --executor-memory 20G --driver-memory 5G  --queue demo_dm  --class xgboost.SparkTraining  xgboostDemo.jar

XGBoost与LR的结合

XGBoost+LR结合的思想源于facebook的研究论文[5],使用树模型来做特征选择,最后用LR来输出CTR分数,充分利用了两种模型的优点,实践证明,XGBoost+LR离线评估和线上AB都优于单独XGBoost。除了论文中提供的方案带来的收益外,我们还将这种stacking的想法做了发挥,工程上单独抽取出LR层,这样做有如下优点:

1、对于一些类似于ID类的非连续特征,可以单独使用LR层来承载

2、事实上很多feature extraction 强大的模型稍作处理都可以作为LR层的输入,处理得当的话,LR还是很强大的

3、通过在LR层组合不同的特征来源,方便的做到“刻画”和“泛化”的结合,类似于deep and wide这样的思想

4、LR本身的优势,适合大规模并行,online learning算法成熟等等。。。

参考文献

[1]: XGBoost: A Scalable Tree Boosting System

[2]: XGBoost: A Scalable Tree Boosting System Supplementary Material

[3]: 分享一个spark xgboost可运行的实例

[4]:  XGBoost与spark在广告排序中的应用

[5]: 关于spark的mllib学习总结(Java版)

[6]: 利用xgboost4j下的xgboost分类模型案例

[7]: xgboost之spark上运行-scala接口

[8]: Using Spark, Scala and XGBoost On The Titanic Dataset from Kaggle

[9]: Evaluation Metrics - RDD-based API

猜你喜欢

转载自blog.csdn.net/zwqjoy/article/details/86608214
今日推荐