决策树是非线性有监督的分类模型。
决策树是一种树形结构,树内部每个节点表示一个属性上的测试,每个分支代表一个测试输出,每个叶子节点代表一个分类类别。通过训练数据构建决策树,可以对未知数据进行分类。
1.认识决策树
术语:
根节点:最顶层的分类条件
叶节点:代表每一个类别号
中间节点:中间分类条件
分支:代表每一个条件的输出
二叉树:每一个节点上有两个分支
多叉树:每一个节点上至少有两个分支
2.决策树分类原则
如下图数据集:
要按照前4列的信息,使用决策树预测车祸的发生,如何选择根节点呢?
按照“天气”列作为根节点,使用决策树预测,如图:
按照“温度”列作为根节点,使用决策树预测,如图:
按照“湿度”列作为根节点,使用决策树预测,如图:
使用“风”列作为根节点,使用决策树预测,如图:
通过以上发现,只有使用天气作为根节点时,决策树的高度相对低而且树的两边能将数据分类的更彻底(其他列作为根节点时,树两边分类不纯粹,都有天气)。
决策树的生成原则:数据不断分裂的递归过程,每一次分裂,尽可能让类别一样的数据在树的一边,当树的叶子节点的数据都是一类的时候,则停止分类。这样分类的数据,每个节点两边的数据不同,将相同的数据分类到树的一侧,能将数据分类的更纯粹。减少树的高度和训练决策树的迭代次数。注意:训练决策树的数据集要离散化,不然有可能造成训练出来的树有些节点的分支特别多,容易造成过拟合。
3.选择分类条件
下图:
上图中箱子①中有100个红球。箱子②中有50个红球和50个黑球。箱子③中有10个红球和30个篮球,60个绿球。箱子④中各个颜色均有10中球。发现箱子①中球类单一,信息量少,比较纯粹,箱子④中,球的类别最多,相对①来说比较混乱,信息量大。
如何量化以上每个箱子中信息的纯粹和混乱(信息量的大小)指标,可以使用信息熵或者基尼系数。
1). 信息熵:信息熵是香农在1948年提出来量化信息信息量的指标,熵的定义如下:
其中,n代表当前类别有多少类别,代表当前类别中某一类别的概率。例如下图,
计算“是否购买电脑”这列的信息熵,当前类别“是否购买电脑”有2个类别,分别是“是”和“否”,那么“是否购买电脑”类别的信息熵如下:
通过以上计算可以得到,某个类别下信息量越多,熵越大,信息量越少,熵越小。假设“是否购买电脑”这列下只有“否”这个信息类别,那么“是否购买电脑”这列的信息熵为:
上图中,如果按照“年龄”,“收入层次”,“学生”,“信用等级”列使用决策树来预测“是否购买电脑”。如何选择决策树的根节点分类条件,就是找到某列作为分类条件时,使“是否购买电脑”这列分类的更彻底,也就是找到在某个列作为分类条件下时,“是否购买电脑”信息熵相对于没有这个分类条件时信息熵降低最大(降低最大,就是熵越低,分类越彻底),这个条件就是分类节点的分类条件。这里要使用到条件熵和信息增益。
条件熵:在某个分类条件下某个类别的信息熵叫做条件熵,类似于条件概率,在知道Y的情况下,X的不确定性。条件熵一般使用 表示,代表在Y条件下,X的信息熵。上图中假设在“年龄”条件下,“是否购买电脑”的信息熵为:“年龄”列每个类别下对应的“是否购买电脑”信息熵的和。
H(是否购买电脑|年龄)=H(是否购买电脑|青少年)+H(是否购买电脑|中年)+H(是否购买电脑|老年)
所以:
信息增益:代表熵的变化程度。分类前的信息熵减去分类后的信息熵。如特征Y对训练集D的信息增益为。
在“年龄”条件下,“是否购买电脑”的信息增益为:
g(是否购买电脑,年龄)=H(是否购买电脑)-H(是否购买电脑,年龄)
=0.94-0.69=0.25
由以上可知,按照“记录ID”,“年龄”,“收入层次”,“学生”,“信用等级”列使用决策树来预测“是否购买电脑”,选择分类根分类条件时步骤:
a.计算“是否购买电脑”的信息熵
b.计算在已知各个列的条件熵
H(是够购买电脑|年龄),H(是够购买电脑|收入层次),H(是够购买电脑|是否学生),H(是够购买电脑|信用等级)
c.求各个条件下的信息增益,选择信息增益大的作为分类条件。选择中间节点时,以此类推。
在构建决策树时,选择信息增益大的属性作为分类节点的方法也叫ID3分类算法。
2).基尼系数:基尼系数也可以表示样本的混乱程度。公式如下:
其中,k代表当前类别有K个类别。代表当前类别中某一类别的概率,1-代表不是当前这个类别的概率。
上图中计算“是否购买电脑”这列的基尼系数:
基尼系数越小代表信息越纯,类别越少,基尼系数越大,代表信息越混乱,类别越多。基尼增益的计算和信息增益相同。假设某列只有一类值,这列的基尼系数为0。
4.信息增益率
在上图中,如果将“记录ID”也作为分类条件的话,由于“记录ID”对于“是否购买电脑”列的条件熵为0,可以得到“是否购买电脑”在“记录ID”这个分类条件下信息增益最大。如果选择“记录ID”作为分类条件,容易造成分支特别多,对已有记录ID的数据可以分类出结果,对于新的记录ID有可能不能成功的分类出结果。
使用信息增益来筛选分类条件,更倾向于选择更混杂的属性。容易出现过拟合问题。可以使用信息增益率来解决这个问题。
信息增益率的公式:gr(D,A) = g(D,A)/H(A),在某个条件下信息增益除以这个条件的信息熵。
例如:在“记录ID”条件下,“是否购买电脑”的信息增益最大,信息熵H(记录ID)也比较大,两者相除就是在“记录ID”条件下的信息增益率,结果比较小,消除了当某些属性比较混杂时,使用信息增益来选择分类条件的弊端。使用信息增益率来构建决策树的算法也叫C4.5算法。一般相对于信息增益来说,选择信息增益率选择分类条件比较合适。
如果决策树最后一个条件依然没能将数据准确分类,那么在这个节点上就可以使用概率来决定。看看哪些情况出现的多,该情况就是该节点的分类结果。
5、使用决策树来做回归或者预测值
如上图,使用学历、收入、身高、行业使用决策树来预测收到的邮件数。可以将邮件数分为几类(也可以按照其他列,将邮件数分类),比如邮件数<=23封属于A类,邮件数大于23<邮件数<=30为B类,A类中取邮件的平均数,B类中也取邮件的平均数。就是可以将某些列作为分类条件划分邮件数的类别,再取邮件数的平均数,这样可以使用决策树来预测大概值的范围。
6.决策树预剪枝和后剪枝
决策树对训练集有很好的分类能力,但是对于未知的测试集未必有好的分类能力,导致模型的泛化能力弱,可能发生过拟合问题,为了防止过拟合问题的出现,可以对决策树进行剪枝。剪枝分为预剪枝和后剪枝。
预剪枝:就是在构建决策树的时候提前停止。比如指定树的深度最大为3,那么训练出来决策树的高度就是3,预剪枝主要是建立某些规则限制决策树的生长,降低了过拟合的风险,降低了建树的时间,但是有可能带来欠拟合问题。
后剪枝:后剪枝是一种全局的优化方法,在决策树构建好之后,然后才开始进行剪枝。后剪枝的过程就是删除一些子树,这个叶子节点的标识类别通过大多数原则来确定,即属于这个叶子节点下大多数样本所属的类别就是该叶子节点的标识。选择减掉哪些子树时,可以计算没有减掉子树之前的误差和减掉子树之后的误差,如果相差不大,可以将子树减掉。一般使用后剪枝得到的结果比较好。
剪枝可以降低过拟合问题,如下图:
当来一条数据年龄为中年,信用高,孩子个数是4个时,没有办法分类。可以通过剪枝,降低过拟合问题。
下面用一段代码来实现决策树模型
import org.apache.spark.mllib.tree.DecisionTree
import org.apache.spark.mllib.util.MLUtils
import org.apache.spark.{SparkContext, SparkConf}
/**
* 决策树
训练决策树的模型就是训练一堆if else
*/
object ClassificationDecisionTreeExample {
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")
// 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,这里是个坑,后面以此类推
//将Map集合丢进决策树训练模型中 第一个特征的类别有4个 第二个特征的类别有4个 第三个特征类别有3个 以此类推
//第五个没有写 是因为它的数据没有离散化
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 = 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)
}
}