基于Spark的GBDT + LR模型实现
基于Spark的GBDT + LR模型实现
测试数据来源http://archive.ics.uci.edu/ml/machine-learning-databases/adult/
该模型利用Spark mllib的GradientBoostedTrees作为GBDT部分,因为ml模块的GBTClassifier对所生成的模型做了相当严密的封装,导致难以获取某些类或方法。而GradientBoostedTrees所需的训练数据为mllib下的LabeledPoint,所以下面的数据预处理的目标是将cat数据进行编码并生成LabeledPoint。
数据预处理部分
import org.apache.spark.mllib.linalg.{SparseVector => OldSparseVector}
import org.apache.spark.sql.functions._
import spark.implicits._
val path = ""
val manualSchema = StructType(Array(
StructField("age", IntegerType, true),
StructField("workclass", StringType, true),
StructField("fnlwgt", IntegerType, true),
StructField("education", StringType, true),
StructField("education-num", IntegerType, true),
StructField("marital-status", StringType, true),
StructField("occupation", StringType, true),
StructField("relationship", StringType, true),
StructField("race", StringType, true),
StructField("sex", StringType, true),
StructField("capital-gain", IntegerType, true),
StructField("capital-loss", IntegerType, true),
StructField("hours-per-week", IntegerType, true),
StructField("native-country", StringType, true),
StructField("label", StringType, true)))
val df = spark.read
.option("header", false)
.option("delimiter", ",")
.option("nullValue", "?")
.schema(manualSchema)
.format("csv")
.load(path + "adult.data.txt")
// .limit(1000)
// 去掉代表序列号的col
var df1 = df.drop("fnlwgt")
.na.drop()
val allFeature = df1.columns.dropRight(1)
// colName和index的映射
val colIdx = new util.HashMap[String, Int](allFeature.length)
var idx = 0
while (idx < allFeature.length){
colIdx.put(allFeature(idx), idx)
idx += 1
}
val numCols = Array("age", "education-num", "capital-gain", "capital-loss", "hours-per-week")
val catCols = df1.columns.dropRight(1).diff(numCols)
val numLen = numCols.length
val catLen = catCols.length
// 处理label
def labeludf(elem: String):Int = {
if (elem == "<=50K") 0
else 1
}
val labelIndexer = udf(labeludf(_:String):Int)
// 也可以用 when 函数
// val labelIndexer = when($"lable" === "<=50K", 0).otherwise(1)
df1 = df1.withColumn("indexed_label", labelIndexer(col("label"))).drop("label")
// 处理cat列
// 所有cat列统一编码,例如有两列cat,第一列为性别,第二列为早、午、晚,那么第一列的编码为0或1,而第二列的编码为2、3或4。下面实现仿照StringIndexer,可能更高效
val inderMap: util.HashMap[String, util.HashMap[String, Int]] = new util.HashMap(catCols.length)
var i = numCols.length
for (column <- catCols) {
val uniqueElem = df1.select(column)
.groupBy(column)
.agg(count(column))
.select(column)
.map(_.getAs[String](0))
.collect()
val len = uniqueElem.length
var index = 0
val freqMap = new util.HashMap[String, Int](len)
while (index < len) {
freqMap.put(uniqueElem(index), i)
index += 1
i += 1
}
inderMap.put(column, freqMap)
}
val bcMap = spark.sparkContext.broadcast(inderMap)
val d = i
// 合并为LabeledPoint
val df2 = df1.rdd.map { row =>
val indics = new Array[Int](numLen + catLen)
val value = new Array[Double](numLen + catLen)
var i = 0
for (col <- numCols) {
indics(i) = i
value(i) = row.getAs[Int](colIdx.get(col)).toDouble
i += 1
}
for (col <- catCols) {
indics(i) = bcMap.value.get(col).get(row.getAs[String](colIdx.get(col)))
value(i) = 1
i += 1
}
new LabeledPoint(row.getAs[Int](numLen + catLen), new OldSparseVector(d, indics, value))
}
val ds = df2.toDF("label", "feature")
ds.write.save(path + "processed")
GBDT模型部分(省略调参部分)
val path = ""
val df = spark.read
.load(path)
.rdd
.map(row => LabeledPoint(row.getAs[Double](0), row.getAs[OldSparseVector](1)))
// Train a GradientBoostedTrees model.
val boostingStrategy = BoostingStrategy.defaultParams("Classification")
boostingStrategy.numIterations = 10
boostingStrategy.treeStrategy.numClasses = 2
boostingStrategy.treeStrategy.maxDepth = 3
boostingStrategy.learningRate = 0.3
// Empty categoricalFeaturesInfo indicates all features are continuous.
boostingStrategy.treeStrategy.categoricalFeaturesInfo = Map[Int, Int]()
val model = GradientBoostedTrees.train(df, boostingStrategy)
model.save(spark.sparkContext, path + "GBDTmodel")
GBDT与LR混合部分
object GBTLRTraining {
// 遍历一颗决策树,找出其出口的叶子节点id
def predictModify(node: OldNode, features: OldSparseVector): Int = {
val split = node.split
if (node.isLeaf) {
node.id - 1 // 改为0-base
} else {
if (split.get.featureType == FeatureType.Continuous) {
if (features(split.get.feature) <= split.get.threshold) {
predictModify(node.leftNode.get, features)
} else {
predictModify(node.rightNode.get, features)
}
} else {
if (split.get.categories.contains(features(split.get.feature))) {
predictModify(node.leftNode.get, features)
} else {
predictModify(node.rightNode.get, features)
}
}
}
}
// 获取每棵树的出口叶子节点id数组
def getGBTFeatures(gbtModel: GradientBoostedTreesModel, oldFeatures: OldSparseVector): Array[Int] = {
val GBTMaxIter = gbtModel.trees.length
val leafIdArray = new Array[Int](GBTMaxIter)
for (i <- 0 until GBTMaxIter) {
val treePredict = predictModify(gbtModel.trees(i).topNode, oldFeatures)
leafIdArray(i) = treePredict
}
leafIdArray
}
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder()
.master("local[*]")
.appName("TEST")
// 本地配置
.config("spark.sql.shuffle.partitions", 12)
.config("spark.default.parallelism", 12)
.config("spark.memory.fraction", 0.75)
// .config("spark.memory.ofHeap.enabled", true)
// .config("spark.memory.ofHeapa.size", "2G")
.config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
// .config("spark.executor.memory", "2G")
.getOrCreate()
spark.sparkContext.setLogLevel("ERROR")
import org.apache.spark.sql.functions._
import spark.implicits._
val path = ""
val df = spark.read
.load(path)
val model = GradientBoostedTreesModel.load(spark.sparkContext, path + "GBDTmodel")
val bcmodel = spark.sparkContext.broadcast(model)
var treeNodeNum = 0
var treeDepth = 0
// 获取最大的树的数据
for (elem <- model.trees) {
if (treeNodeNum < elem.numNodes){
treeNodeNum = elem.numNodes
treeDepth = elem.depth
}
}
val leafNum = math.pow(2, treeDepth).toInt
val nonLeafNum = treeNodeNum - leafNum
val totalColNum = leafNum * model.trees.length
// print(leafNum + " " + nonLeafNum + " " + tree.numNodes + " " + totalColNum)
// 利用之前训练好的GBT模型进行特征提取,并把原特征OldSparseVector转化为ml的SparseVector,让后续的LR使用
val addFeatureUDF = udf { features: OldSparseVector =>
val gbtFeatures = getGBTFeatures(bcmodel.value, features)
var i = 0
while (i < gbtFeatures.length){
val leafIdx = gbtFeatures(i) - nonLeafNum
// 有些树可能没有生长完全,leafIdx没有达到最大的树的最后一层,这里将这些情况默认为最大的树的最后一层的第一个叶子节点。
gbtFeatures(i) = (if (leafIdx < 0) 0 else leafIdx) + i * leafNum
i += 1
}
val idx = gbtFeatures
val values = Array.fill[Double](idx.length)(1.0)
Vectors.sparse(totalColNum, idx, values)
}
val dsWithCombinedFeatures = df
.withColumn("lr_feature", addFeatureUDF(col("feature")))
// dsWithCombinedFeatures.show(false)
val lr = new LogisticRegression()
.setMaxIter(500)
.setFeaturesCol("lr_feature")
.setLabelCol("label")
val lrmodel = lr.fit(dsWithCombinedFeatures)
val res = lrmodel.transform(dsWithCombinedFeatures)
// res.show(false)
val evaluator1 = new MulticlassClassificationEvaluator().setMetricName("accuracy")
.setLabelCol("label")
.setPredictionCol("prediction")
println("ACC:" + evaluator1.evaluate(res))
val evaluator2 = new BinaryClassificationEvaluator().setMetricName("areaUnderROC")
.setLabelCol("label")
.setRawPredictionCol("prediction")
println("AUC:" + evaluator2.evaluate(res))
}
}
参考资料:
https://github.com/wzhe06/CTRmodel
基于Spark的GBDT + LR模型实现的更多相关文章
- GBDT+LR算法解析及Python实现
1. GBDT + LR 是什么 本质上GBDT+LR是一种具有stacking思想的二分类器模型,所以可以用来解决二分类问题.这个方法出自于Facebook 2014年的论文 Practical L ...
- ctr中的GBDT+LR的优点
1 为什么gbdt+lr优于gbdt? 其实gbdt+lr类似于做了一个stacking.gbdt+lr模型中,把gbdt的叶子节点作为lr的输入,而gbdt的叶子节点相当于它的输出y',用这个y'作 ...
- 客户流失?来看看大厂如何基于spark+机器学习构建千万数据规模上的用户留存模型 ⛵
作者:韩信子@ShowMeAI 大数据技术 ◉ 技能提升系列:https://www.showmeai.tech/tutorials/84 行业名企应用系列:https://www.showmeai. ...
- 在Java Web中使用Spark MLlib训练的模型
PMML是一种通用的配置文件,只要遵循标准的配置文件,就可以在Spark中训练机器学习模型,然后再web接口端去使用.目前应用最广的就是基于Jpmml来加载模型在javaweb中应用,这样就可以实现跨 ...
- 基于spark邮件自动分类
代码放在github上:click me 一.数据说明 数据集为英文语料集,一共包含20种类别的邮件,除了类别soc.religion.christian的邮件数为997以外每个类别的邮件数都是100 ...
- 基于Spark ALS构建商品推荐引擎
基于Spark ALS构建商品推荐引擎 一般来讲,推荐引擎试图对用户与某类物品之间的联系建模,其想法是预测人们可能喜好的物品并通过探索物品之间的联系来辅助这个过程,让用户能更快速.更准确的获得所需 ...
- 大数据实时处理-基于Spark的大数据实时处理及应用技术培训
随着互联网.移动互联网和物联网的发展,我们已经切实地迎来了一个大数据 的时代.大数据是指无法在一定时间内用常规软件工具对其内容进行抓取.管理和处理的数据集合,对大数据的分析已经成为一个非常重要且紧迫的 ...
- spark概念、编程模型和模块概述
http://blog.csdn.net/pipisorry/article/details/50931274 spark基本概念 Spark一种与 Hadoop 相似的通用的集群计算框架,通过将大量 ...
- 基于Spark自动扩展scikit-learn (spark-sklearn)(转载)
转载自:https://blog.csdn.net/sunbow0/article/details/50848719 1.基于Spark自动扩展scikit-learn(spark-sklearn)1 ...
随机推荐
- Matlab学习笔记(一)
一.MATLAB概述 (一)运行环境 命令行窗口(Command Window) 表 1-1 数据显示格式设置(e_one_1.m) 格式 实例 说明 format short 3.1416 小数点后 ...
- admin——django自带数据库管理工具
admin是Django自带的数据库管理工具,是一个app 在admin.py中可以自己定制类来使admin数据库管理页面展现自己想要查看的信息 models.py from django.db im ...
- JavaEE JDBC ResultSet内外移动
ResultSet内外移动 @author ixenos 内外移动指位置光标的移动 内移动就是一个ResultSet得到后的那个光标! 外移动就是多个ResultSet的迭代 内移动 一般的数据库都不 ...
- 添物不花钱学JavaEE(基础篇)- Tomcat
Tomcat是大家常用的Java Web容器. 添物网使用的也是Tomcat. 官方网址: http://tomcat.apache.org/ 官方文档看看. 可以看的图书 <Tomcat权威指 ...
- noip模拟赛 数颜色
分析:高级数据结构学傻了.....一眼看上去觉得是莫队,发现带修改,于是分块,由于写的常数很大,只有70分. 正解很简单,记录下颜色为c的每只兔子的位置,每次二分找这个区间有多少只这种颜色的兔子就可以 ...
- 【bzoj3747】[POI2015]Kinoman - 线段树(经典)
Description 共有m部电影,编号为1~m,第i部电影的好看值为w[i]. 在n天之中(从1~n编号)每天会放映一部电影,第i天放映的是第f[i]部. 你可以选择l,r(1<=l< ...
- ****HTML模板资源汇总
站长素材: http://sc.chinaz.com/tag_moban/HTML.html wordpress模板: http://www.cssmoban.com/wpthemes/ http:/ ...
- Spring Boot 2.1.5 正式发布,1.5.x 即将结束使命!
Spring Boot 官网在 2019/03/15 这天发布了 Spring Boot 2.1.5 正式版,栈长表示真跟不上了.. 官宣如下 : https://spring.io/blog/201 ...
- 作为一名CEO
你 不能怕得罪人 不能奢望完成工作的时候 有太多的愉悦感 你 必须要去做左右为难但左右亦可的 操蛋决策 你 得脸皮够厚 肚囊儿宽超 什么事情都能快速消化 哪怕 是 一坨屎 你 还得 决不放弃 永不言败 ...
- #!/usr/bin/env 脚本解释程序的作用
the Zimbu programming language http://www.zimbu.org/getting-started -------------------------------- ...