前言:决策树(Decision Tree)是一种基本的分类与回归方法,本文主要讨论分类决策树。决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。相比朴素贝叶斯分类,决策树的优势在于构造过程不需要任何领域知识或参数设置,因此在实际应用中,对于探测式的知识发现,决策树更加适用。
一、决策树模型
1.定义
分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点和有向边组成。结点有两种类型:内部节点和叶节点,内部节点表示一个特征或属性,叶节点表示一个类。
分类的时候,从根节点开始,对实例的某一个特征进行测试,根据测试结果,将实例分配到其子结点;此时,每一个子结点对应着该特征的一个取值。如此递归向下移动,直至达到叶结点,最后将实例分配到叶结点的类中。
举一个通俗的栗子,各位立志于脱单的单身男女在找对象的时候就已经完完全全使用了决策树的思想。假设一位母亲在给女儿介绍对象时,有这么一段对话:
母亲:给你介绍个对象。
女儿:年纪多大了?
母亲:26。
女儿:长的帅不帅?
母亲:挺帅的。
女儿:收入高不?
母亲:不算很高,中等情况。
女儿:是公务员不?
母亲:是,在税务局上班呢。
女儿:那好,我去见见。
这个女生的决策过程就是典型的分类决策树。相当于对年龄、外貌、收入和是否公务员等特征将男人分为两个类别:见或者不见。假设这个女生的决策逻辑如下:
上图完整表达了这个女孩决定是否见一个约会对象的策略,其中绿色结点(内部结点)表示判断条件,橙色结点(叶结点)表示决策结果,箭头表示在一个判断条件在不同情况下的决策路径,图中红色箭头表示了上面例子中女孩的决策过程。
这幅图基本可以算是一棵决策树,说它“基本可以算”是因为图中的判定条件没有量化,如收入高中低等等,还不能算是严格意义上的决策树,如果将所有条件量化,则就变成真正的决策树了。
2.决策树与if-then规则
现在我们可以更抽象一些。决策树可以看成一个if-then规则的集合:由决策树的根结点到叶结点的每一条路径构建一条规则;路径上的内部结点的特征对应着规则的条件,而叶结点对应着分类的结论。决策树的路径和其对应的if-then规则集合是等效的,它们具有一个重要的性质:互斥并且完备。这里的意思是说:每一个实例都被一条路径或一条规则所覆盖,而且只被一条规则所覆盖。
3.决策树与条件概率分布
决策树还是给定特征条件下类的条件概率分布的一种表示。该条件分布定义在特征空间的划分(partition)上,特征空间被划分为互不相交的单元(cell),每个单元定义一个类的概率分布就构成了一个条件概率分布。决策树的一条路径对应于划分中的一个单元。决策树所表示的条件概率分布由各个单元给定条件下类的条件概率分布组成。给定实例的特征X,一定落入某个划分,决策树选取该划分里最大概率的类作为结果输出。如图:
图(a)表示该实例的特征向量是二维的(即具有两个特征),图(b)表示给定特征XX分类属于+1+1类的条件概率分布。图中的方块有些地方完全没有,比如x(2)x(2)轴上[a2,1][a2,1]这个区间,说明只要X落在这里,Y就一定是-1的,同理对于[0,a1[0,a1]和[0,a2][0,a2]围起来的一定是+1+1的。有些地方只有一半,比如x(1)x(1)轴上[a1,1][a1,1]这个区间,说明决策树认为XX落在这里,Y只有一半概率是+1+1的,根据选择条件概率大的类别的原则,就认为Y是−1−1的(因为不满足P(Y=+1|X)>0.5P(Y=+1|X)>0.5)。
4.决策树的学习
决策树学习算法包含特征选择、决策树的生成与剪枝过程。决策树的学习算法通常是递归地选择最优特征,并用最优特征对数据集进行分割。开始时,构建根结点,选择最优特征,该特征有几种值就分割为几个子集,每个子集分别递归调用此方法,返回结点,返回的结点就是上一层的子结点。直到所有特征都已经用完,或者数据集只有一维特征为止。
二、特征选择
特征选择问题希望选取对训练数据具有良好分类能力的特征,这样可以提高决策树学习的效率。如果利用一个特征进行分类的结果与随机分类的结果没有很大差别,则称这个特征是没有分类能力的。为了解决特征选择问题,找出最优特征,先要介绍一些信息论里面的概念。
1.熵(entropy)
熵是表示随机变量不确定性的度量。设X是一个取有限个值的离散随机变量,其概率分布为
则随机变量的熵定义为
另外,,当对数的底为2时,熵的单位为bit;为e时,单位为nat。
熵越大,随机变量的不确定性就越大。从定义可验证
Python实现代码如下:
def calcShannonEnt(dataSet):
'''
计算香农熵
:param dataSet:数据集
:return: 计算结果
'''
numEntries = len(dataSet)
labelCounts = {}
for featVec in dataSet: # 遍历每个实例,统计标签的频数
currentLabel = featVec[-1]
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key]) / numEntries
shannonEnt -= prob * log(prob,2) # 以2为底的对数
return shannonEnt
2.条件熵(conditional entropy)
设有随机变量(X,Y),其联合概率分布为
条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性。随机变量X给定的条件下随机变量Y的条件熵H(Y|X),定义为X给定条件下Y的条件概率分布的熵对X的数学期望
这里,
Python实现代码如下:
def splitDataSet(dataSet, axis, value):
'''
按照给定特征划分数据集
:param dataSet:待划分的数据集
:param axis:划分数据集的特征
:param value: 需要返回的特征的值
:return: 划分结果列表
'''
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis] #chop out axis used for splitting
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
def calcConditionalEntropy(dataSet, i, featList, uniqueVals):
'''
计算X_i给定的条件下,Y的条件熵
:param dataSet:数据集
:param i:维度i
:param featList: 数据集特征列表
:param uniqueVals: 数据集特征集合
:return: 条件熵
'''
conditionEnt = 0.0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet) / float(len(dataSet)) # 极大似然估计概率
conditionEnt += prob * calcShannonEnt(subDataSet) # 条件熵的计算
return conditionEnt
3.信息增益(information gain)
信息增益表示得知特征X的信息而使得类Y的信息的不确定性减少的程度。特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即
这个差又称为互信息。信息增益大的特征具有更强的分类能力。
根据信息增益准则的特征选择方法是:对训练数据集(或子集)计算其每个特征的信息增益,选择信息增益最大的特征。
计算信息增益的算法如下:
输入:训练数据集D和特征A;
输出:特征AA对训练数据集D的信息增益g(D,A).
(1)计算数据集D的经验熵H(D)
(2)计算特征A对数据集D的经验条件熵H(D|A)
(3)计算信息增益
Python实现如下:
def calcInformationGain(dataSet, baseEntropy, i):
'''
计算信息增益
:param dataSet:数据集
:param baseEntropy:数据集的信息熵
:param i: 特征维度i
:return: 特征i对数据集的信息增益g(D|X_i)
'''
featList = [example[i] for example in dataSet] # 第i维特征列表
uniqueVals = set(featList) # 转换成集合
newEntropy = calcConditionalEntropy(dataSet, i, featList, uniqueVals)
infoGain = baseEntropy - newEntropy # 信息增益,就yes熵的减少,也就yes不确定性的减少
return infoGain
相对应地,以信息增益作为划分训练数据集的特征的算法称为ID3算法,后面会讲述。
4.信息增益比(information gain ratio)
特征A对训练数据集DD的信息增益比定义为其信息增益g(D,A)与训练数据集D关于特征A的值的熵之比,即
其中,n是特征A取值的个数。
Python实现如下:
def calcInformationGainRatio(dataSet, baseEntropy, i):
'''
计算信息增益比
:param dataSet:数据集
:param baseEntropy:数据集的信息熵
:param i: 特征维度i
:return: 特征i对数据集的信息增益比gR(D|X_i)
'''
return calcInformationGain(dataSet, baseEntropy, i) / baseEntropy
相应地,用信息增益比来选择特征的算法称为C4.5算法。
三、决策树的生成
1.ID3算法
ID3算法由Ross Quinlan发明,建立在“奥卡姆剃刀”的基础上:越是小型的决策树越优于大的决策树(be simple简单理论)。ID3算法中根据信息增益评估和选择特征,每次选择信息增益最大的特征作为判断模块建立子结点。ID3算法可用于划分标称型数据集,没有剪枝的过程,为了去除过度数据匹配的问题,可通过裁剪合并相邻的无法产生大量信息增益的叶子节点(例如设置信息增益阀值)。使用信息增益的话其实是有一个缺点,那就是它偏向于具有大量值的属性。就是说在训练集中,某个属性所取的不同值的个数越多,那么越有可能拿它来作为分裂属性,而这样做有时候是没有意义的,另外ID3不能处理连续分布的数据特征,于是就有了C4.5算法。CART算法也支持连续分布的数据特征。
算法步骤如下:
Python实现:
def chooseBestFeatureToSplitByID3(dataSet):
'''
选择最好的数据集划分方式
:param dataSet:数据集
:return: 划分结果
'''
numFeatures = len(dataSet[0]) - 1 # 最后一列yes分类标签,不属于特征向量
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0
bestFeature = -1
for i in range(numFeatures): # 遍历所有特征
infoGain = calcInformationGain(dataSet, baseEntropy, i) # 计算信息增益
if (infoGain > bestInfoGain): # 选择最大的信息增益
bestInfoGain = infoGain
bestFeature = i
return bestFeature # 返回最优特征对应的维度
def majorityCnt(classList):
'''
采用多数表决的方法决定叶结点的分类
:param: 所有的类标签列表
:return: 出现次数最多的类
'''
classCount={}
for vote in classList: # 统计所有类标签的频数
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True) # 排序
return sortedClassCount[0][0]
def createTree(dataSet,labels):
'''
创建决策树
:param: dataSet:训练数据集
:return: labels:所有的类标签
'''
classList = [example[-1] for example in dataSet]
if classList.count(classList[0]) == len(classList):
return classList[0] # 第一个递归结束条件:所有的类标签完全相同
if len(dataSet[0]) == 1:
return majorityCnt(classList) # 第二个递归结束条件:用完了所有特征
bestFeat = chooseBestFeatureToSplitByID3(dataSet) # 最优划分特征
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}} # 使用字典类型储存树的信息
del(labels[bestFeat])
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:] # 复制所有类标签,保证每次递归调用时不改变原始列表的内容
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
这里使用Python语言的字典类型存储树的信息,简单方便。当然也可以定义一个新的数据结构存储树。
2.C4.5算法
C4.5算法用信息增益率来选择属性,继承了ID3算法的优点。并在以下几方面对ID3算法进行了改进:
- 克服了用信息增益选择属性时偏向选择取值多的属性的不足;
- 在树构造过程中进行剪枝;
- 能够完成对连续属性的离散化处理;
- 能够对不完整数据进行处理。
C4.5算法产生的分类规则易于理解、准确率较高;但效率低,因树构造过程中,需要对数据集进行多次的顺序扫描和排序。也是因为必须多次数据集扫描,C4.5只适合于能够驻留于内存的数据集。在实现过程中,C4.5算法在结构与递归上与ID3完全相同,区别只在于选取决决策特征时的决策依据不同,二者都有贪心性质:即通过局部最优构造全局最优。以下是算法步骤:
3、CART算法
CART,即分类与回归树(classification and regression tree),也是一种应用很广泛的决策树学习方法。但是CART算法比较强大,既可用作分类树,也可以用作回归树。作为分类树时,其本质与ID3、C4.5并有多大区别,只是选择特征的依据不同而已。另外,CART算法建立的决策树一般是二叉树,即特征值只有yes or no的情况(个人认为并不是绝对的,只是看实际需要)。当CART用作回归树时,以最小平方误差作为划分样本的依据。
1.分类树
(1)基尼指数
分类树采用基尼指数选择最优特征。假设有K个类,样本点属于第kk类的概率为,则概率分布的基尼指数定义为
对于给定的样本集合D,其基尼指数为
这里,是D中属于第k类的样本子集,K是类的个数。
Python计算如下:
def calcGini(dataSet):
'''
计算基尼指数
:param dataSet:数据集
:return: 计算结果
'''
numEntries = len(dataSet)
labelCounts = {}
for featVec in dataSet: # 遍历每个实例,统计标签的频数
currentLabel = featVec[-1]
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
Gini = 1.0
for key in labelCounts:
prob = float(labelCounts[key]) / numEntries
Gini -= prob * prob # 以2为底的对数
return Gini
那么在给定特征A的条件下,集合D的基尼指数定义为
基尼指数Gini(D)表示集合D的不确定性,基尼指数Gini(D,A)表示经A=a分割后集合D的不确定性。基尼指数值越大,样本集合的不确定性也就越大,这一点与熵相似。
Python计算如下:
def calcGiniWithFeat(dataSet, feature, value):
'''
计算给定特征下的基尼指数
:param dataSet:数据集
:param feature:特征维度
:param value:该特征变量所取的值
:return: 计算结果
'''
D0 = []; D1 = []
# 根据特征划分数据
for featVec in dataSet:
if featVec[feature] == value:
D0.append(featVec)
else:
D1.append(featVec)
Gini = len(D0) / len(dataSet) * calcGini(D0) + len(D1) / len(dataSet) * calcGini(D1)
return Gini
(2)CART分类树的算法步骤如下:
Python实现如下:
def chooseBestSplit(dataSet):
numFeatures = len(dataSet[0])-1
bestGini = inf; bestFeat = 0; bestValue = 0; newGini = 0
for i in range(numFeatures):
featList = [example[i] for example in dataSet]
uniqueVals = set(featList)
for splitVal in uniqueVals:
newGini = calcGiniWithFeat(dataSet, i, splitVal)
if newGini < bestGini:
bestFeat = i
bestGini = newGini
return bestFeat
# for featVec in dataSet:
# for splitVal in set(dataSet[:,featIndex].tolist()):
# newGini = calcGiniWithFeat(dataSet, featIndex, splitVal)
# if newGini < bestGini:
# bestFeat = featIndex
# bestValue = splitVal
# bestGini = newGini
def majorityCnt(classList):
'''
采用多数表决的方法决定叶结点的分类
:param: 所有的类标签列表
:return: 出现次数最多的类
'''
classCount={}
for vote in classList: # 统计所有类标签的频数
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) # 排序
return sortedClassCount[0][0]
def createTree(dataSet,labels):
'''
创建决策树
:param: dataSet:训练数据集
:return: labels:所有的类标签
'''
classList = [example[-1] for example in dataSet]
if classList.count(classList[0]) == len(classList):
return classList[0] # 第一个递归结束条件:所有的类标签完全相同
if len(dataSet[0]) == 1:
return majorityCnt(classList) # 第二个递归结束条件:用完了所有特征
bestFeat = chooseBestSplit(dataSet) # 最优划分特征
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}} # 使用字典类型储存树的信息
del(labels[bestFeat])
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:] # 复制所有类标签,保证每次递归调用时不改变原始列表的内容
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
2.回归树
回归树的生成实际上也是贪心算法。与分类树不同的是回归树处理的数据连续分布的。废话不多说了,直接贴算法:
CART回归树算法划分样本的依据是最小平方误差。
四、决策树的剪枝
决策树为什么要剪枝?原因是避免决策树过拟合(Overfitting)样本。前面的算法生成的决策树非常详细并且庞大,每个属性都被详细地加以考虑,决策树的树叶节点所覆盖的训练样本都是“纯”的。因此用这个决策树来对训练样本进行分类的话,你会发现对于训练样本而言,这个树表现完好,误差率极低且能够正确得对训练样本集中的样本进行分类。训练样本中的错误数据也会被决策树学习,成为决策树的部分,但是对于测试数据的表现就没有想象的那么好,或者极差,这就是所谓的过拟合(Overfitting)问题。Quinlan教授试验,在数据集中,过拟合的决策树的错误率比经过简化的决策树的错误率要高。
1.预剪枝
通过提前停止树的构建而对树剪枝,一旦停止,节点就是树叶,该树叶持有子集元祖最频繁的类。
停止决策树生长最简单的方法有:
(1)定义一个高度,当决策树达到该高度时就停止决策树的生长
(2).达到某个节点的实例具有相同的特征向量,及时这些实例不属于同一类,也可以停止决策树的生长。这个方法对于处理
数据的数据冲突问题比较有效。
(3)定义一个阈值,当达到某个节点的实例个数小于阈值时就可以停止决策树的生长
(4)定义一个阈值,通过计算每次扩张对系统性能的增益,并比较增益值与该阈值大小来决定是否停止决策树的生长。
2.后剪枝方法
后剪枝(postpruning):它首先构造完整的决策树,允许树过度拟合训练数据,然后对那些置信度不够的结点子树用叶子结点来代替,该叶子的类标号用该结点子树中最频繁的类标记。相比于先剪枝,这种方法更常用,正是因为在先剪枝方法中精确地估计何时停止树增长很困难。
以上可以理解为后剪枝的基本思想,其中后剪枝方法主要有以下几个方法:
Reduced-Error Pruning(REP,错误率降低剪枝)
Pesimistic-Error Pruning(PEP,悲观错误剪枝)
Cost-Complexity Pruning(CCP,代价复杂度剪枝)
EBP(Error-Based Pruning)(基于错误的剪枝)
以下分别进行说明:
1、Reduced-Error Pruning(REP,错误率降低剪枝)
REP方法是一种比较简单的后剪枝的方法,在该方法中,可用的数据被分成两个样例集合:一个训练集用来形成学习到的决策树,一个分离的验证集用来评估这个决策树在后续数据上的精度,确切地说是用来评估修剪这个决策树的影响。
这个方法的动机是:即使学习器可能会被训练集中的随机错误和巧合规律所误导,但验证集合不大可能表现出同样的随机波动。所以验证集可以用来对过度拟合训练集中的虚假特征提供防护检验。
该剪枝方法考虑将书上的每个节点作为修剪的候选对象,决定是否修剪这个结点有如下步骤组成:
(1):删除以此结点为根的子树
(2):使其成为叶子结点
(3):赋予该结点关联的训练数据的最常见分类
(4):当修剪后的树对于验证集合的性能不会比原来的树差时,才真正删除该结点
因为训练集合的过拟合,使得验证集合数据能够对其进行修正,反复进行上面的操作,从底向上的处理结点,删除那些能够最大限度的提高验证集合的精度的结点,直到进一步修剪有害为止(有害是指修剪会减低验证集合的精度)。
REP是最简单的后剪枝方法之一,不过由于使用独立的测试集,原始决策树相比,修改后的决策树可能偏向于过度修剪。这是因为一些不会再测试集中出现的很稀少的训练集实例所对应的分枝在剪枝过如果训练集较小,通常不考虑采用REP算法。
尽管REP有这个缺点,不过REP仍然作为一种基准来评价其它剪枝算法的性能。它对于两阶段决策树学习方法的优点和缺点提供了了一个很好的学习思路。由于验证集合没有参与决策树的创建,所以用REP剪枝后的决策树对于测试样例的偏差要好很多,能够解决一定程度的过拟合问题。
2、Pesimistic-Error Pruning(PEP,悲观错误剪枝)
悲观错误剪枝法是根据剪枝前后的错误率来判定子树的修剪。该方法引入了统计学上连续修正的概念弥补REP中的缺陷,在评价子树的训练错误公式中添加了一个常数,假定每个叶子结点都自动对实例的某个部分进行错误的分类。
把一颗子树(具有多个叶子节点)的分类用一个叶子节点来替代的话,在训练集上的误判率肯定是上升的,但是在新数据上不一定。于是我们需要把子树的误判计算加上一个经验性的惩罚因子。对于一颗叶子节点,它覆盖了N个样本,其中有E个错误,那么该叶子节点的错误率为(E+0.5)/N。这个0.5就是惩罚因子,那么一颗子树,它有L个叶子节点,那么该子树的误判率估计为:
这样的话,我们可以看到一颗子树虽然具有多个子节点,但由于加上了惩罚因子,所以子树的误判率计算未必占到便宜。剪枝后内部节点变成了叶子节点,其误判个数J也需要加上一个惩罚因子,变成J+0.5。那么子树是否可以被剪枝就取决于剪枝后的错误J+0.5在的标准误差内。对于样本的误差率e,我们可以根据经验把它估计成各种各样的分布模型,比如是二项式分布,比如是正态分布。
那么一棵树错误分类一个样本值为1,正确分类一个样本值为0,该树错误分类的概率(误判率)为e(e为分布的固有属性,可以通过统计出来),那么树的误判次数就是伯努利分布,我们可以估计出该树的误判次数均值和标准差:
把子树替换成叶子节点后,该叶子的误判次数也是一个伯努利分布,其概率误判率e为(E+0.5)/N,因此叶子节点的误判次数均值为
使用训练数据,子树总是比替换为一个叶节点后产生的误差小,但是使用校正后有误差计算方法却并非如此,当子树的误判个数大过对应叶节点的误判个数一个标准差之后,就决定剪枝:
这个条件就是剪枝的标准。当然并不一定非要大一个标准差,可以给定任意的置信区间,我们设定一定的显著性因子,就可以估算出误判次数的上下界。
话不多说,上例子:
在上述例子中T8种的类1可以认为是识别错误的T4这课子树的估计错误为5,T4子树的最后叶子节点为3个
上述是悲观错误剪枝。
悲观剪枝的准确度比较高,但是依旧会存在以下的问题:
(1)PeP算法实用的从从上而下的剪枝策略,这种剪枝会导致和预剪枝同样的问题,造成剪枝过度。
(2)Pep剪枝会出现剪枝失败的情况。
3、Cost-Complexity Pruning(CCP,代价复杂度剪枝)
该算法为子树Tt定义了代价(cost)和复杂度(complexity),以及一个可由用户设置的衡量代价与复杂度之间关系的参数α,其中,代价指在剪枝过程中因子树Tt被叶节点替代而增加的错分样本,复杂度表示剪枝后子树Tt减少的叶结点数,α则表示剪枝后树的复杂度降低程度与代价间的关系,定义为:
其中,
|N1|:子树Tt中的叶节点数;
R(t):结点t的错误代价,计算公式为R(t)=r(t)*p(t),
r(t)为结点t的错分样本率,p(t)为落入结点t的样本占所有样本的比例;
R(Tt):子树Tt错误代价,计算公式为R(Tt)=∑R(i),i为子树Tt的叶节点。
CCP剪枝算法分为两个步骤:
(1)对于完全决策树T的每个非叶结点计算α值,循环剪掉具有最小α值的子树,直到剩下根节点。在该步可得到一系列的剪枝树{T0,T1,T2......Tm},其中T0为原有的完全决策树,Tm为根结点,Ti+1为对Ti进行剪枝的结果;
(2)从子树序列中,根据真实的误差估计选择最佳决策树。
上例子:
4、EBP(Error-Based Pruning)(基于错误的剪枝)
•第一步:计算叶节点的错分样本率估计的置信区间上限U
•第二步:计算叶节点的预测错分样本数
–叶节点的预测错分样本数=到达该叶节点的样本数*该叶节点的预测错分样本率U
•第三步:判断是否剪枝及如何剪枝
–分别计算三种预测错分样本数:
•计算子树t的所有叶节点预测错分样本数之和,记为E1
•计算子树t被剪枝以叶节点代替时的预测错分样本数,记为E2
•计算子树t的最大分枝的预测错分样本数,记 为E3
–比较E1,E2,E3,如下:
•E1最小时,不剪枝
•E2最小时,进行剪枝,以一个叶节点代替t
•E3最小时,采用“嫁接”(grafting)策略,即用这个最大分枝代替t
以下是几种剪枝的方法的比较:
REP | PEP | CCP | |
剪枝方式 | 自底向上 | 自顶向下 | 自底向上 |
计算复杂度 | 0(n) | o(n) | o(n2) |
误差估计 | 剪枝集上误差估计 | 使用连续纠正 | 标准误差 |
五、决策树的优点:
• 易于理解和解释。数可以可视化。也就是说决策树属于白盒模型,如果一个情况被观察到,使用逻辑判断容易表示这种规则。相反,如果是黑盒模型(例如人工神经网络),结果会非常难解释。
• 几乎不需要数据预处理。其他方法经常需要数据标准化,创建虚拟变量和删除缺失值。在sklearn中的决策树还不支持缺失值处理。
• 利用训练好的决策树进行分类或回归的时间复杂度仅是训练生成决策树的时间的对数,时间复杂度大大缩小。
• 既可以处理数值变量,也可以处理分类数据,也就是说,如果数据类型不同,也可以在一颗决策树中进行训练。其他方法通常只适用于分析一种类型的变量。
• 可以处理多值输出变量问题。
• 可以使用统计检验验证模型的可靠性。
• 如果真实的数据不符合决策树构建时的假设条件,也可以较好的适用。
六、决策树的缺点:
• 决策树学习可能创建一个过于复杂的树,降低泛化性能,这就是过拟合。修剪机制(现在不支持),构建决策树时设置一个叶子节点需要的最小样本数量,或者树的最大深度,都可以避免过拟合。
• 决策树容易受到噪声影响,同样的数据如果略微改变可能生成一个完全不同的树。这个问题通过集成学习来缓解。
• 学习一颗最优的决策树是一个**NP-完全问题**under several aspects of optimality and even for simple concepts。所以,考虑计算量,在实际工程当中,决策树算法采用启发式算法,例如贪婪算法,可以保证局部最优解,但无法确保是全局最优解。这个问题可以采用集成学习来解决。
• 决策树对于一些问题有局限性,主要是这些问题难以用条件判断来解决。例如,异或问题, parity,multiplexer问题等等.
• 如果某些分类占优势,决策树将会创建一棵有偏差的树。因此,建议在训练之前,先抽样使样本均衡。
七、决策树的复杂度:
构建一个决策树的时间复杂度是 o(n^2*m*log(m)),其中,m是训练样本的数量,m是训练样本的特征数量。而利用决策树分类或回归一个测试样本的时间复杂度是 o(log(n)),也就是说,训练样本数量越多,越容易造成决策树深度爆炸。 深度爆炸的主要原因就是重复计算。
Reference
[1]https://blog.csdn.net/HerosOfEarth/article/details/52347820
[2]https://blog.csdn.net/HerosOfEarth/article/details/52425952