一.概念
决策树和随机森林:决策树和随机森林都是非线性有监督的分类模型。
决策树是一种树形结构,树内部每个节点表示一个属性上的测试,每个分支代表一个测试输出,每个叶子节点代表一个分类类别。通过训练数据构建决策树,可以对未知数据进行分类。
- 根节点:最顶层的分类条件
- 叶节点:代表每一个类别号
- 中间节点:中间分类条件
- 分支:代表每一个条件的输出
随机森林是由多个决策树组成,随机森林中每一棵决策树之间没有关联,在得到一个随机森林后,当有新的样本进入的时候,随机森林中的每一棵决策树分别进行判断,分析出该样本属于哪一类,然后最后看哪一类被选择最多,就预测该样本属于这一类。
一个合理的决策树可以描述为:决策树的高度相对低而且树的两边能将数据分类的更彻底。
而想要构建一个最优的决策树,和根节点和每一个中间节点的分类条件的选择密不可分,好的分类条件可以让类别一样的数据在树的一边,从而减少数的高度,因此如何在这些节点上如何选择分类条件成为了一个问题。
决策树的生成原则:
数据不断分裂的递归过程,每一次分裂,尽可能让类别一样的数据在树的一边,当树的叶子节点的数据都是一类的时候,则停止分类。这样分类的数据,每个节点两边的数据不同,将相同的数据分类到树的一侧,能将数据分类的更纯粹。减少树的高度和训练决策树的迭代次数。
注意:训练决策树的数据集要离散化,不然有可能造成训练出来的树有些节点的分支特别多,容易造成过拟合。
二.如何选择分类条件
1.信息熵
信息熵是用来量化信息信息量的指标,信息熵越大,则信息越混乱,信息熵越小,则信息越纯粹。熵的定义如下
其中,n代表当前类别有多少类别代表当前类别中某一类别的概率。
下面举一个例子看一下信息熵的使用
由上图可知,最终"是否购买电脑"类别的信息熵如下:
构建上图中最优的决策树,首先需要知道那个一个分类条件作为根节点是最优的。
如何选择决策树的根节点分类条件,就是找到某列作为分类条件时,使最终类别分类的更彻底,也就是找到在一个分类条件可以使最终的信息熵相对于没有这个分类条件时,信息熵降低的幅度最大(降低最大,就是熵越低,分类越彻底)。
而这里要使用到条件熵和信息增益。
条件熵:在某个分类条件下某个类别的信息熵叫做条件熵,类似于条件概率,在知道Y的情况下,X的不确定性。条件熵一般使用H(X|Y)表示,代表在Y条件下,X的信息熵。
信息增益:代表熵的变化程度。分类前的信息熵减去分类后的信息熵。
因此年龄条件下的信息增益 = 最终"是否购买电脑"的信息熵 - 年龄条件下的信息熵 = 0.94-0.69=0.25
通过对所有分类条件计算信息增益,那么信息增益最大的那个分类条件就是最优的根节点分类条件的选择。
综上所述,在构建决策树时,选择分类分类条件的步骤如下:
- 1.计算叶节点所对应列的信息熵
- 2.计算每个已知列的信息熵
- 3.求得各个条件下的信息增益,选择信息增益大的作为分类条件。选择中间节点时,也以此类推。
在构建决策树时,选择信息增益大的属性作为分类节点的方法也叫 ID3 分类算法。
2.信息增益率
使用信息增益来筛选分类条件,更倾向于选择更混杂的属性。容易出现过拟合问题。可以使用信息增益率来解决这个问题。
信息增益率的公式:
即在某个条件下信息增益除以这个条件的信息熵。
信息增益率消除了当某些属性比较混杂时,使用信息增益来选择分类条件的弊端。使用信息增益率来构建决策树的算法也叫 C4.5 算法。一般相对于信息增益来说,选择信息增益率选择分类条件比较合适。
PS:如果决策树最后一个条件依然没能将数据准确分类,那么在这个节点上就可以使用概率来决定。看看哪些情况出现的多,该情况就是该节点的分类结果。
3.基尼系数
基尼系数也可以表示样本的混乱程度。公式如下:
其中,k代表当前类别有K个类别。P代表当前类别中某一类别的概率,1-P 代表不是当前这个类别的概率。
上图中计算“是否购买电脑”这列的基尼系数:
基尼系数越小代表信息越纯,类别越少,基尼系数越大,代表信息越混乱,类别越多。基尼增益的计算和信息增益相同。假设某列只有一类值,这列的基尼系数为0。
三.决策树预剪枝和后剪枝
决策树对训练集有很好的分类能力,但是对于未知的测试集未必有好的分类能力,导致模型的泛化能力弱,可能发生过拟合问题,为了防止过拟合问题的出现,可以对决策树进行剪枝。剪枝分为预剪枝和后剪枝。
预剪枝:
就是在构建决策树的时候提前停止。比如指定树的深度最大为3,那么训练出来决策树的高度就是3,预剪枝主要是建立某些规则限制决策树的生长,降低了过拟合的风险,降低了建树的时间,但是有可能带来欠拟合问题。
后剪枝:
后剪枝是一种全局的优化方法,在决策树构建好之后,然后才开始进行剪枝。后剪枝的过程就是删除一些子树,这个叶子节点的标识类别通过大多数原则来确定,即属于这个叶子节点下大多数样本所属的类别就是该叶子节点的标识。选择减掉哪些子树时,可以计算没有减掉子树之前的误差和减掉子树之后的误差,如果相差不大,可以将子树减掉。一般使用后剪枝得到的结果比较好。
四.随机森林
随机森林是由多个决策树组成。是用随机的方式建立一个森林,里面由很多决策树组成。随机森林中每一棵决策树之间都是没有关联的。得到随机森林之后,对于一个样本输入时,森林中的每一棵决策树都进行判断,看看这个样本属于哪一类,最终哪一类得到的结果最多,该输入的预测值就是哪一类。
随机森林中的决策树生成过程是对样本数据进行多次行采样和列采样,可以指定随机森林中的树的个数和属性个数,这样当训练集很大的时候,随机选取数据集的一部分,生成一棵树,重复上面过程,可以生成一堆形态各异的树,这些决策树构成随机森林。
随机森林的优点:
随机森林中的每个决策树可以分布式的训练,解决了单棵决策树在数据量大的情况下预算量大的问题。当训练样本中出现异常数据时,决策树的抗干扰能力差,对于随机森林来说也解决了模型的抗干扰能力。
五.SparkMlLib中决策树和随机森林的使用
1.决策树
/**
* 决策树
*/
object ClassificationDecisionTree {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("analysItem")
conf.setMaster("local[3]")
val sc = new SparkContext(conf)
val data: RDD[LabeledPoint] = MLUtils.loadLibSVMFile(sc, "汽车数据样本.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))
//指明分类的类别
val numClasses=2
//指定离散变量,未指明的都当作连续变量处理
//某列下有1,2,3类别 处理时候要自定为4类,虽然没有0,但是程序默认从0开始分类
//这里天气维度有3类,但是要指明4,这里是个坑,后面以此类推
val categoricalFeaturesInfo=Map[Int,Int](0->4,1->4,2->3,3->3)
//设定评判标准 "gini"/"entropy"
val impurity="entropy"
//树的最大深度,太深运算量大也没有必要 剪枝 防止模型的过拟合!!!
val maxDepth=3
//设置离散化程度,连续数据需要离散化,分成32个区间,默认其实就是32,分割的区间保证数量差不多 这个参数也可以进行剪枝
val maxBins=32
//生成模型
val model =DecisionTree.trainClassifier(trainingData,numClasses,categoricalFeaturesInfo,
impurity,maxDepth,maxBins)
val labelAndPreds: RDD[(Double, Double)] = 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("Test Error = " + testErr)
println("Learned classification tree model:\n" + model.toDebugString)
}
}
样本数据示例如下:
1 1:2 2:1 3:1 4:1 5:80
1 1:3 2:2 3:1 4:1 5:77
1 1:3 2:2 3:1 4:1 5:77
1 1:2 2:1 3:1 4:1 5:77
1 1:2 2:1 3:1 4:1 5:72
1 1:3 2:2 3:1 4:1 5:40
1 1:2 2:2 3:1 4:1 5:61
1 1:2 2:1 3:1 4:1 5:69
1 1:2 2:1 3:1 4:1 5:71
2.随机森林
/**
* 随机森林
*
*/
object ClassificationRandomForest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("analysItem")
conf.setMaster("local[3]")
val sc = new SparkContext(conf)
//读取数据
val data = MLUtils.loadLibSVMFile(sc,"汽车数据样本.txt")
//将样本按7:3的比例分成
val splits = data.randomSplit(Array(0.7, 0.3))
val (trainingData, testData) = (splits(0), splits(1))
//分类数
val numClasses = 2
// categoricalFeaturesInfo 为空,意味着所有的特征为连续型变量
val categoricalFeaturesInfo =Map[Int, Int](0->4,1->4,2->3,3->3)
//树的个数
val numTrees = 3
//特征子集采样策略,auto 表示算法自主选取
//"auto"根据特征数量在4个中进行选择
// 1:all 全部特征 。2:sqrt 把特征数量开根号后随机选择的 。 3:log2 取对数个。 4:onethird 三分之一
val featureSubsetStrategy = "auto"
//纯度计算 "gini"/"entropy"
val impurity = "entropy"
//树的最大层次
val maxDepth = 3
//特征最大装箱数,即连续数据离散化的区间
val maxBins = 32
//训练随机森林分类器,trainClassifier 返回的是 RandomForestModel 对象
val model = RandomForest.trainClassifier(trainingData, numClasses, categoricalFeaturesInfo,
numTrees, featureSubsetStrategy, impurity, maxDepth, maxBins)
// //打印模型
// println(model.toDebugString)
//保存模型
//model.save(sc,"汽车保险")
//在测试集上进行测试
val count = testData.map { point =>
val prediction = model.predict(point.features)
// Math.abs(prediction-point.label)
(prediction,point.label)
}.filter(r => r._1 != r._2).count()
println("Test Error = " + count.toDouble/testData.count().toDouble)
println("model "+model.toDebugString)
}
}