Spark MLlib模型训练—回归系列算法

Spark MLlib模型训练—回归系列算法

今天这一讲,我们会结合房屋预测场景,一起学习回归、分类与聚类中的典型算法在 Spark MLlib 框架下的具体用法。掌握这些用法之后,针对同一类机器学习问题(回归、分类或是聚类),你就可以在其算法集合中,灵活、高效地做算法选型。

在这个场景中,我们有 3 个实例,分别是房价预测、房屋分类和房屋聚类。房价预测我们并不陌生,在前面的学习中,我们一直在尝试把房价预测得更准。

房屋分类,它指的是,给定离散标签(Label),如“OverallQual”(房屋质量),结合房屋属性特征,将所有房屋分类到相应的标签取值,如房屋质量的“好、中、差”三类。

而房屋聚类,它指的是,在不存在标签的情况下,根据房屋特征向量,结合“物以类聚”的思想,将相似的房屋聚集到一起,形成聚类。

今天我们的目标还是房价预测也就是回归系列算法,最开始我们一直采用线性模型进行拟合,但是线性模型的拟合能力相当有限,所以这一讲我们开始使用决策树进行拟合。

GBDT房价预测

在特征工程的两讲中,我们一直尝试使用线性模型来拟合房价,但线性模型的拟合能力相当有限。决策树系列模型属于非线性模型,在拟合能力上,更胜一筹。

模型训练基本上可以分为 3 个环节:

  1. 准备训练样本
  2. 定义模型并拟合训练数据
  3. 验证模型效果

除了模型定义,第一个与第三个环节实际上是通用的。不论我们采用哪种模型,训练样本其实都大同小异,度量指标(不论是用于回归的 RMSE,还是用于分类的 AUC)本身也与模型无关。因此,今天这一讲,我们把重心放在第二个环节,

前面我们讲过决策树系列模型及其衍生算法,也就是随机森林与 GBDT 算法。这两种算法既可以解决分类问题,也可以用来解决回归问题。既然 GBDT 擅长拟合残差,那么我们不妨用它来解决房价预测的(回归)问题。

// numericFields代表数值字段,indexFields为采用StringIndexer处理后的非数值字段
val assembler = new VectorAssembler()
.setInputCols(numericFeatures ++ indexFields)
.setOutputCol("features")
 
// 创建特征向量“features”
engineeringDF.drop("features")
engineeringDF = assembler.transform(engineeringDF)
 
import org.apache.spark.ml.feature.VectorIndexer
 
// 区分离散特征与连续特征
val vectorIndexer = new VectorIndexer()
.setInputCol("features")
.setOutputCol("indexedFeatures")
// 设定区分阈值
.setMaxCategories(30)
 
// 完成数据转换
engineeringDF = vectorIndexer.fit(engineeringDF).transform(engineeringDF)

我们之前已经学过了 VectorAssembler 的用法,它用来把多个字段拼接为特征向量。你可能已经发现,在 VectorAssembler 之后,我们使用了一个新的特征处理函数对 engineeringDF 进一步做了转换,这个函数叫作 VectorIndexer。它是用来干什么的呢?

简单地说,它用来帮助决策树系列算法(如 GBDT、随机森林)区分离散特征与连续特征。连续特征也即数值型特征,数值之间本身是存在大小关系的。而离散特征(如街道类型)在经过 StringIndexer 转换为数字之后,数字与数字之间会引入原本并不存在的大小关系。

这个问题要怎么解决呢?首先,对于经过 StringIndexer 处理过的离散特征,VectorIndexer 会进一步对它们编码,抹去数字之间的比较关系,从而明确告知 GBDT 等算法,该特征为离散特征,数字与数字之间相互独立,不存在任何关系。

VectorIndexer 对象的 setMaxCategories 方法,用于设定阈值,该阈值用于区分离散特征与连续特征,我们这里设定的阈值为 30。这个阈值有什么用呢?凡是多样性(Cardinality)大于 30 的特征,后续的 GBDT 模型会把它们看作是连续特征,而多样性小于 30 的特征,GBDT 会把它们当作是离散特征来进行处理。

说到这里,你可能会问:“对于一个特征,区分它是连续的、还是离散的,有这么重要吗?至于这么麻烦吗?”还记得在决策树基本原理中,特征的“提纯”能力这个概念吗?对于同样一份数据样本,同样一个特征,连续值与离散值的“提纯”能力可能有着天壤之别。还原特征原本的“提纯”能力,将为决策树的合理构建,打下良好的基础。

import org.apache.spark.ml.regression.GBTRegressor
 
// 定义GBDT模型
val gbt = new GBTRegressor()
.setLabelCol("SalePriceInt")
.setFeaturesCol("indexedFeatures")
// 限定每棵树的最大深度
.setMaxDepth(5)
// 限定决策树的最大棵树
.setMaxIter(30)
 
// 区分训练集、验证集
val Array(trainingData, testData) = engineeringDF.randomSplit(Array(0.7, 0.3))
 
// 拟合训练数据
val gbtModel = gbt.fit(trainingData)

可以看到,我们通过定义 GBTRegressor 来定义 GBDT 模型,其中 setLabelCol、setFeaturesCol 都是老生常谈的方法了,不再赘述。值得注意的是 setMaxDepth 和 setMaxIter,这两个方法用于避免 GBDT 模型出现过拟合的情况,前者限定每棵树的深度,而后者直接限制了 GBDT 模型中决策树的总体数目。后面的训练过程,依然是调用模型的 fit 方法。

为了方便你学习,这里我把核心逻辑的完整代码粘贴在这里

    // 从CSV文件创建DataFrame
    val trainDF: DataFrame = spark.read.format("csv").option("header", true).load(filePath)
    var engineeringDF
  • 11
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值