基于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 ...
随机推荐
- python 深浅拷贝&集合
一.深浅拷贝 1.浅拷贝,只会拷贝第一层 s = [1, 'ss', '小可爱'] s1 = s.copy() print(s1) >>> [1, 'ss', '小可爱'] s = ...
- 杭电 2035 (快速幂) 求A^B的最后三位数表示的整数
Description 求A^B的最后三位数表示的整数. 说明:A^B的含义是“A的B次方” Input 输入数据包含多个测试实例,每个实例占一行,由两个正整数A和B组成(1<=A,B&l ...
- Vue如何实现swiper左右滑动内容区控制导航tab同时切换高亮
Vue如何实现左右滑动内容区控制导航tab同时切换高亮,实现的效果是:点击导航按钮时内容区发生改变,左右滑动内容区时导航按钮跟随切换高亮,停留在某个内容区时刷新页面后仍然停留在当前内容区. ...
- Qt笔记——连接第三方库&用libZPlay库获取音频文件的艺术家、专辑等信息
连接第三方库libZPlay 概述 需要.a/.lib ,.h , .dll 三个文件 官网下载 http://libzplay.sourceforge.net/ import .h 链接 .a 放入 ...
- 九度oj 题目1072:有多少不同的面值组合?(set集合)
题目1072:有多少不同的面值组合? 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:3627 解决:1852 题目描述: 某人有8角的邮票5张,1元的邮票4张,1元8角的邮票6张,用这些邮 ...
- codeforces 363A
#include<stdio.h>//这题挺有意思小学学的算盘 int main() { int n,i,m; while(scanf("%d",&n)!=EO ...
- msp430入门编程0
msp430单片机最小系统 msp430入门学习 msp430入门编程
- Organize Your Train part II 字典树(此题专卡STL)
Organize Your Train part II Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 8787 Acce ...
- HDU 5074 Hatsune Miku 2014 Asia AnShan Regional Contest dp(水
简单dp #include <stdio.h> #include <cstring> #include <iostream> #include <map> ...
- [教程] NETGEAR R7800 路由器TFTP刷机方法(适用于.img格式固件各种刷)
本教程是我参照R7800的OP/LEDE固件交流群内文件做的教程,可以说是完善.补充吧. 本帖适用于:① 原厂固件刷原厂固件:② 原厂固件刷第三方固件(.img格式):③ 第三方固件刷回原厂固件(.i ...