Spark里的决策树

    本文是对spark文档的一个翻译,原文见https://spark.apache.org/docs/latest/mllib-decision-tree.html

前言

    决策树以及它们的集成都是分类和回归机器学习任务中非常流行的算法。决策树因其易解释性,能够处理类别特征,可扩展到多分类问题,不需要特征缩放,而且可以得到非线性以及特征交互(feature interaction)的优点而被广泛使用。树的集成算法,例如随机森林和提升算法,都在分类和回归问题上取得了顶尖的效果。

    Spark.mlib支持用于二分类、多分类以及回归问题的决策树,也同时支持连续和类别特征。实现方式将数据以行分隔,可以分布式地训练百万样本。

基本算法

    决策树是一种贪心算法,它将特征空间进行递归地二分。对于每个叶节点,决策树会给出一个预测。每个节点都是通过贪心地选择所有可能的切分中最好的切分进行的,从而最大化每个树节点的信息增益。也就是说,每个树节点上切分点的选择是根据得到的,其中IG(D,s)是对一个数据集D采取一个切分s后的信息增益。

节点不纯度(Node impurity)以及信息增益

    节点不纯度是节点内标签均匀度(homogeneity)的一种测量方式。目前提供两种应用于分类问题的不纯度测量方式(基尼不纯度(Gini impurity)和熵(entropy)),以及一种应用于回归问题的不纯度测量方式(方差(variance))。


    信息增益是指父节点的不纯度与两个子节点不纯度加权后的和之间的差异。假设一个切分s将大小为N的数据集D切分为两个子集D_left和D_right,大小分别是N_left和N_right。那么信息增益为:


可能的切分

连续特征

    对于单机实现中的小数据集而言,每一个连续特征的可能切分就是这个特征的不同的取值。有一些实现方式将这些特征值进行排序,然后用排序过的不同值作为可能的切分,从而加速树的计算。
    对大型分布式数据集的特征值进行排序是非常昂贵的。这种实现方式通过对一部分采样的数据进行一种分位数计算(quantile calculation),得到一个近似的切分点集。这种排序过的切分会创造一些“桶”(bins),而桶数的最大值可以通过maxBins参数来设置。
    注意到桶数不能大于样本总数N。如果这个条件不满足,这种树的算法会自动减少桶数。

类别特征

    对于一个有M种可能取值的类别特征而言,一共有2^(M-1)-1种切分的可能。对于二分类(0/1)以及回归问题,我们可以将该数字通过根据平均标签对类别特征值进行排序减小到M-1。例如,一个二分类问题的某一个类别特征有A,B,C三种可能,对应的是标签1的比例为0.2,0.6和0.4,那么类别特征就会被排序为A,C,B。两种可能的切分为A | C,B和A,C | B,其中 | 表示切分。
    对于多分类问题,所有2^(M-1)-1种可能的切分都会尽可能使用。当2^(M-1)-1大于maxBins参数时,我们采用一种(启发式的)方法(类似于在二分类和回归问题上采用的方法)。这M种类别特征将通过不纯度进行排序,得到M-1种潜在的切分。


停止规则

    树的递归构造会在某个节点发生以下情形时停止:
  • 节点深度等于maxDepth参数。
  • 没有切分能使得信息增益大于minInfoGain。
  • 没有切分能使得子节点至少拥有minInstancesPerNode数量的训练样本。


使用窍门

    这里我们通过讨论不同参数给出一些决策树的使用指南。这些参数大致按照重要程度递减的罗列在下方。新用户可以主要考虑“问题规格参数”(problem specification parameters)部分以及maxDepth参数。


问题规格参数

    这些参数描述了你想要解决的问题以及你的数据集。它们应该被给出,而不需要调整。
  • algo:决策树的类型,可以是Classification或Regression。
  • numClasses:分类的数量(仅用于Classification)。
  • categoricalFeaturesInfo:给出哪些特征是类别特征,以及这些特征能够得到多少类别值。这通过一个从特征位置指向特征数量的映射(map)给出的。所有不在这个映射中的类别都将被看作是连续的。
  1. 例如,Map(0 -> 2, 4 -> 10)说明特征0是一个二元类别(取值0或1),而特征4有10种可能(取值{0,1,…,9})。注意到特征位置是始于0的。特征0和特征4分别是特征向量中的第1个和第5个特征。
  2. 注意:你不是必须要给出categoricalFeaturesInfo。这个算法会自动运行并得到合理的结果。不过如果类别特征被适当地指出,性能会更好。


停止条件

    这些参数决定了树什么时候停止建造。当你调整这些参数时,请小心地在held-out测试集上做验证,以免过拟合。
  • maxDepth:树的最大深度。越深的树会越有代表性(潜在的更高的准确率),不过同时训练的花费更高,也更容易过拟合。
  • minInstancesPerNode:如果一个节点想要继续被切分,它的每一个子节点必须拥有至少这个数量的训练样本。这通常会用在随机森林里,因为随机森林通常会比单个树训练的更深。
  • minInfoGain:如果一个节点想要继续被切分,这个切分带来的好处必须大于该数值(比如信息增益)。


可调参数

    这些参数是可调的。调整时请小心地在held-out测试集上验证以防止过拟合。
  • maxBins:对连续特征离散化时采用的桶数。
  1. 增加maxBins可以让算法考虑更多的切分可能,做出细粒度的决策。然而,这同时增加了计算量以及通信量(computation and communication)。
  2. 注意到maxBins参数至少需要是任意类别特征的最大类别数量M。
  • maxMemoryInMB:用于收集足够的统计数据的内存量。
  1. 默认值保守地设定为256MB以保证决策算法在大多数场景下都可以工作。增加该数值能通过减少数据传递量而加快训练。然而,返回数量可能会随着maxMemoryInMB的增加而下降,因为每次循环的通信量与该数值成比例。
  2. 实现细节:为了更快的处理,决策树算法收集可切分的成组的节点们(而不是一次一个节点)的统计数据。一个组里可处理的节点数量是由需要的内存(每个特征都不一样)决定的。maxMemoryInMB参数给出了每个worker可用于计算这些统计信息的内存限制,单位为兆。
  • subsamplingRate:训练集的多大部分用于学习决策树。这个参数对于训练树的集成算法(随机森林和梯度提升树)更为贴近,它可以用于对原始数据进行采样。对于训练单个决策树而言,这个参数没那么重要,因为训练样本数目不是最大的限制。
  • impurity:用于选择切分点的不纯度测量方法(上文介绍的)。这里的测量方法必须与algo参数相符。

缓存(caching)和检查(checkpointing)

    MLlib 1.2加入了几种用于规模化到更大(更深)的决策树以及其集成算法的特性。当maxDepth设置的比较大时,打开节点ID缓存以及检查就很有用。这些参数在随机森林的numTrees参数被设置的较大时也很有用。
  • useNodeIdCache:如果设为true,算法会避免在每次循环时都传递当前的模型(单个树或多个树)到执行器(executor)。
  1. 当树比较深时(加快worker上的计算),或者对于大型的随机森林(减少每次循环上的通信)都比较有用。
  2. 实现细节:默认地,这个算法会将当前模型传递到执行器,从而使得执行器能够用树节点匹配训练样本。当这个设置被打开时,算法会转而缓存这个信息。
    节点ID缓存生成一序列的RDD(每次循环都生成一序列)。这个长序列会导致性能问题,不过检查(checkpointing)中间的RDD可以减轻这个问题。注意,检查只能当useNodeIdCache设置为true时才能使用。
  • checkpointDir:checkpoint节点ID缓存RDD的目录。
  • checkpointInterval:checkpoint节点ID缓存RDD的频率。把这个参数设置得太低会导致另外的更多的对HDFS的写操作;太高又会在执行器执行失败而RDD需要被重新计算时产生问题。

规模

    计算规模大致和训练集数目、特征数量以及maxBins参数成正比。通信规模大致和特征数量以及maxBins参数成正比。
实现的算法既可以读取稀疏(sparse)数据,也可以读取紧凑(dense)数据。然而,算法并没有为稀疏数据做过优化。


例子

分类问题

    下面的例子给出了如何加载一个LIBSVM数据文件,将它变成LabelPoint类型的RDD,然后用决策树进行分类。决策树将基尼不纯度作为不纯度测量方式,树的最大深度为5。计算测试集的偏差来作为算法准确性的度量。

Scala代码:

import org.apache.spark.mllib.tree.DecisionTree
import org.apache.spark.mllib.tree.model.DecisionTreeModel
import org.apache.spark.mllib.util.MLUtils

// Load and parse the data file.
val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")
// Split the data into training and test sets (30% held out for testing)
val splits = data.randomSplit(Array(0.7, 0.3))
val (trainingData, testData) = (splits(0), splits(1))

// Train a DecisionTree model.
//  Empty categoricalFeaturesInfo indicates all features are continuous.
val numClasses = 2
val categoricalFeaturesInfo = Map[Int, Int]()
val impurity = "gini"
val maxDepth = 5
val maxBins = 32

val model = DecisionTree.trainClassifier(trainingData, numClasses, categoricalFeaturesInfo,
  impurity, maxDepth, maxBins)

// Evaluate model on test instances and compute test error
val labelAndPreds = testData.map { point =>
  val prediction = model.predict(point.features)
  (point.label, prediction)
}
val testErr = labelAndPreds.filter(r => r._1 != r._2).count().toDouble / testData.count()
println(s"Test Error = $testErr")
println(s"Learned classification tree model:\n ${model.toDebugString}")

// Save and load model
model.save(sc, "target/tmp/myDecisionTreeClassificationModel")
val sameModel = DecisionTreeModel.load(sc, "target/tmp/myDecisionTreeClassificationModel")

完整的代码见spark repo下的"examples/src/main/scala/org/apache/spark/examples/mllib/DecisionTreeClassificationExample.scala"

回归问题

    下面的例子给出了如何加载一个LIBSVM数据文件,将它变成LabelPoint类型的RDD,然后用决策树解决回归问题。决策树将方差作为不纯度测量方式,树的最大深度为5。计算测试集的MSE(平均平方误差)来作为算法好坏的度量。

Scala代码:

import org.apache.spark.mllib.tree.DecisionTree
import org.apache.spark.mllib.tree.model.DecisionTreeModel
import org.apache.spark.mllib.util.MLUtils

// Load and parse the data file.
val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")
// Split the data into training and test sets (30% held out for testing)
val splits = data.randomSplit(Array(0.7, 0.3))
val (trainingData, testData) = (splits(0), splits(1))

// Train a DecisionTree model.
//  Empty categoricalFeaturesInfo indicates all features are continuous.
val categoricalFeaturesInfo = Map[Int, Int]()
val impurity = "variance"
val maxDepth = 5
val maxBins = 32

val model = DecisionTree.trainRegressor(trainingData, categoricalFeaturesInfo, impurity,
  maxDepth, maxBins)

// Evaluate model on test instances and compute test error
val labelsAndPredictions = testData.map { point =>
  val prediction = model.predict(point.features)
  (point.label, prediction)
}
val testMSE = labelsAndPredictions.map{ case (v, p) => math.pow(v - p, 2) }.mean()
println(s"Test Mean Squared Error = $testMSE")
println(s"Learned regression tree model:\n ${model.toDebugString}")

// Save and load model
model.save(sc, "target/tmp/myDecisionTreeRegressionModel")
val sameModel = DecisionTreeModel.load(sc, "target/tmp/myDecisionTreeRegressionModel")
完整的代码见spark repo下的 "examples/src/main/scala/org/apache/spark/examples/mllib/DecisionTreeRegressionExample.scala"
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值