决策树是一种基于树结构,使用层层推理来解决分类(回归)问题的算法
决策树由下面几种元素构成:
决策树模型的三个步骤
根据特征选择不同方法有三种经典的决策树算法:ID3、C4.5、CART
特征选择的目标:决策树分类的目标就是让同类样本最终在同一叶节点中,不同类最终在不同叶节点中,所以在划分过程中,我们希望决策树的分支节点所包含的样本尽可能属于同一类别,即节点的“纯度”越来越高。
采用信息增益作为特征选择的方法。“信息熵”是度量样本纯度的常用的一种指标信息熵越小,纯度越高。
有 G a i n ( D , a ) Gain(D,a) Gain(D,a)可知,信息增益越大,使用属性a来进行划分得到纯度提升越大,所以我们选择信息增益最大的属性来进行划分。
使用信息增益来进行特征选择一个缺点是:该方法对数目较多的属性有所偏好。
C4.5算法采用信息增益率作为特征选择的方法,此方法正是为了解决信息增益准则对可取值较多的属性有所偏好的不利影响。
但遗憾的是,采用欣喜增益率准也有缺点:对可取值较少的属性有所偏好。
CART采用基尼指数作为特征选择的方法。
基尼指数表示在数据集中随机抽取两个样本,他们类别不一致的概率,很明显,基尼指数越小,即随机抽取的两个样本类别不一致的概率越小,类别一致的概率就越高,那么纯度就越高。采用基尼指数也减少了大量的对数运算。
另外,CART可以用于回归任务。
剪枝处理是决策树算法解决过拟合的主要手段,基本策略有:
ID3算法没有剪枝策略,
根据衡量泛化能力的方法有很多不同策略的剪枝,ID3算法采用悲观剪枝策略,CART采用代价复杂度剪枝策略。
**代价复杂度剪枝(后剪枝)**主要包含以下两个步骤:
1. 如何在特征值缺失的情况下进行划分特征的选择?
2. 选定该划分特征,模型对于缺失该特征值的样本该进行怎样处理?
CART采取的机制是代理分裂,即如果某个存在缺失值的特征是当前最佳的分裂特征,那么我们需要遍历剩余所有特征,选择能与最佳增益的缺失特征分裂之后增益最接近的特征进行分裂,剩余特征中如果也存在缺失值,那这些特征也忽略。选定划分特征之后,当前缺失该特征的样本划分到左右子树取决于代理特征。这种处理缺失值的方法,需要遍历其他所有特征代价太大。Sklearn中决策树模型没有缺失值的处理。
C4.5处理缺失值的方法,是通过计特征上未缺失值的样本的信息增益,然后加上个未缺失样本的权重,选择划分特征之后,对于缺失特征值的样本,让该样本以不同概率划分到不同的节点中去。
决策树的优点:
1、计算复杂度不高,输出结果易于理解,
2、对中间值的缺失不敏感(CART、C4.5),比较适合处理有缺失属性的样本
3、可以处理不相关特征数据。
4、可以同时处理标称型和数值型数据
缺点:
1、容易过拟合
2、容易忽略数据集中属性的相互关联
3、使用信息增益和CART的决策树对可取数目较多的属性有所偏好,使用信息增益率对可取值较少的属性有所偏好。
除了特征选择,剪枝处理的差异之外,还可以从以下角度来考虑三者之间的差异。
CART回归树和CART分类树的建立算法大部分都是类似的,所以我们这里只讨论CART回归树和CART分类树之间的建立算法的不同。
回归树和分类树的最主要区别在于样本输出,如果样本输出是离散值,那么这是一颗分类树,如果样本输出是连续值,那么这是一颗回归树。
除了概念上的不同之外,CART回归树和CART分类树的建立和预测的其区别主要有下面两点:
对于连续值的处理,CART分类时是基于基尼系数的大小来度量特征的各个划分点的优劣情况。这比较适合分类树,对于回归模型,我们知道最常用的度量方式是均方误差,这里回归树采用的是和方差的度量方式,对于任意划分特征A,对应的任意划分点s两边划分成的数据集D1和D2,求出使D1和D2各自集合的均方差最小,同时D1和D2的均方差之和最小对应的特征和特征值划分点。表达式为:
对于决策树建立后做预测的方式,上面讲到了CART分类树采用叶子节点里概率最大的类别作为当前节点的预测类别。而回归树输出不是类别,它采用的是用最终叶子的均值或者中位数来预测输出结果。
除了上面提到了以外,CART回归树和CART分类树的建立算法和预测没有什么区别。
优点:
缺点:
"""
伪代码:
creteBranch函数
检测数据集中的每个子项是否属于同一分类:
if so return 类标签;
else
寻找划分数据集的最好特征
划分数据集
创建分支节点
for 每个划分子集
调用函数createBranch并增加返回结果到分支节点中
return 分支节点
"""
from math import *
import operator
class Tree:
def clacShannonEnt(self, dataset):
"""
计算香浓熵的函数
:param dataset:数据集
:return: 香浓熵的值
"""
numEntries = len(dataset)
labelCounts = {}
for featVec in dataset:
currentLabel = featVec[-1]
labelCounts[currentLabel] = labelCounts.get(currentLabel, 0)+1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob*log(prob, 2)
return shannonEnt
def splitDataSet(self,dataset, axis, value):
"""
按照给定特征划分数据集
:param dataset: 待划分数据集
:param axis: 待划分数据集特征索引
:param value: 需要返回的特征的值
:return:
"""
retDataSet = []
for featVec in dataset:
if featVec[axis] == value:
reduceFeatVec = featVec[:axis]
reduceFeatVec.extend(featVec[axis+1:])
retDataSet.append(reduceFeatVec)
return retDataSet
def chooseBestFeatureToSplit(self,dataset):
"""
选择最好的数据集划分方式
:param dataset: 数据集
:return: 最好的划分特征
"""
numFeatures = len(dataset[0]) - 1
bestEntropy = self.clacShannonEnt(dataset)
bestInfoGain = 0.0
bestFeature = -1
for i in range(numFeatures):
featList = [example[i] for example in dataset]
uniqueVals = set(featList)
newEntropy = 0.0
for value in uniqueVals:
subDataSet = self.splitDataSet(dataset, i, value)
prob = len(subDataSet)/float(len(dataset))
newEntropy += prob*self.clacShannonEnt(subDataSet)
infoGain = bestEntropy-newEntropy
if (infoGain>bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
def majorityCnt(self, classList):
"""
多数投票法
:param classList:分类名称的列表
:return: 返回出现次数最多的类
"""
classCount = {}
for vote in classList:
classCount[vote] = classCount.get(vote, 0) + 1
sortedClassCount = sorted(classCount.iteritems(),
key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
def createTree(self, dataset, labels):
"""
创建树的函数代码
:param dataset: 数据集
:param labels: 标签列表
:return: 决策树
"""
classList = [example[-1] for example in dataset]
#print(classList)
#递归终止条件
if classList.count(classList[0]) == len(classList):
return classList[0]
if len(dataset[0])==1:
return self.majorityCnt(classList)
bestFeat = self.chooseBestFeatureToSplit(dataset)
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}}
del(labels[bestFeat])
featValue = [example[bestFeat] for example in dataset]
uniqueVals = set(featValue)
for value in uniqueVals:
subLabels = labels[:]
myTree[bestFeatLabel][value] = self.createTree(self.splitDataSet(dataset, bestFeat, value)
, subLabels)
return myTree
def classify(self, inputTree, featLabels, testVec):
global classLabel
firstStr = list(inputTree.keys())[0]
# print(featLabels)
# print(firstStr)
secondDict = inputTree[firstStr]
featIndex = featLabels.index(firstStr)
#print(featIndex)
for key in secondDict.keys():
print(key)
if testVec[featIndex] == key:
if type(secondDict[key]).__name__=='dict':
classLabel = self.classify(secondDict[key],featLabels,testVec)
else:
classLabel = secondDict[key]
return classLabel
if __name__ == "__main__":
dataset = [[1,1,'yes'],
[1,1,"yes"],
[1,0,'no'],
[0,1,'no'],
[0,1,'no']]
labels = ["no surfacing", "fileters"]
#print(labels.index("no surfacing"))
tree = Tree()
myTree = tree.createTree(dataset, labels)
print(myTree)
a = tree.classify(myTree,["no surfacing", "fileters"], [1,0])
print("leibie",a)