一 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
[6]: 利用xgboost4j下的xgboost分类模型案例
[8]: Using Spark, Scala and XGBoost On The Titanic Dataset from Kaggle
[9]: Evaluation Metrics - RDD-based API