目录
1.基本概念
你是否和朋友玩过一种游戏,游戏内容是:你的朋友心里想一个东西,你通过不断提出问题来缩小这个猜测的范围,最终猜出他心中的结果。决策树其实原理和这个游戏有着相似的地方。即通过不断的提问,分割,逐渐缩小范围,分类的过程。
2.如何划分
那么假如我们今天要设计一个决策树来进行分类,首先我们要面对的问题就是:如何高效,准确的划分我们手中的数据集呢? 划分的数据集到哪种地步我就可以停止了呢?如果数据集中的特征属性很多,每次我该如何选择特征属性进行划分是比较好的?
解决了这几个问题,一个决策树的大概雏形就已经出现了。下面我们对这些问题来一一解答。
2.1 信息增益
我们划分数据集的主要目标在于,让无序的数据变得有序。首先我们来介绍信息增益,在划分数据集之前之后信息发生的变化称为信息增益,如果我们可以计算出信息增益,那么就可以通过衡量信息增益来确定是否该选择某个特征属性作为最佳划分属性了。因为获得信息增益最高的那个特征属性就是最佳划分属性。
集合信息的度量方式称为香农熵或者简称为熵,这个名字来源于信息论之父克劳德·香农。
熵定义为信息的期望值,所以我们要先明确,信息的定义:如果待分类的事务可能划分在多个分类之中,则符号xi的信息定义为:
其中p(xi)是选择该分类的概率也就是第xi类样本所占总样本D的比例。而为了计算熵,我们要计算所有类别所有可能的信息期望值,y是分类的数目。
这个Ent(D)的值越小,则说明分类中的纯度越高,即同一类别的可能性越大。如果按照a属性来划分数据集D,划分后的数据子集为,然后我们可以计算出,该种分类后所得到的信息增益。
通过比较信息增益的大小,则可以得到最佳划分属性。
2.2 增益率(C4.5决策树算法)
上述采用信息增益来计算划分的手法是ID3决策树算法的核心,但其中有个不可忽视的问题是,该种算法会对取值数目较多的属性有所偏好(例如数据集中的编号如果也作为特征属性进行划分,则划分后的每一个节点下面只有一个样本,纯度达到最大值,信息熵为0,但这样的决策树几乎没有泛化能力),为了减少这种影响,C4.5决策树算法引入了增益率来选择最佳划分属性。具体增益率定义公式如下:当采用属性a来进行划分时,通常a的取值数目越多,IV(a) 就会越大。
需要注意的是:增益率准则会对可取值数目较少的属性有所偏好,为了避免这种情况。首先使用ID3算法计算出信息增益高于平均水平的候选属性,接着C4.5计算这些候选属性的增益率,选其中最大的。
2.3 基尼指数(CART决策树)
CART决策树使用基尼指数(Gini index)来作为划分属性的选取标准,基尼指数的直观含义是从集合D中随机任取两个样本,两个样本不同类的概率,也就是Gini(D)越小,则说明集合D纯度越高。
对于属性a来说,选择属性a作为划分属性来计算基尼指数公式为:
根据这个公式,我们就可以选择使得划分后Gini_index() 的值最小的那个属性作为最佳划分属性。
3.剪枝处理
剪枝处理是决策树用来解决过拟合问题的常见手段,其中包括预剪枝和后剪枝两种。
- 预剪枝:在划分前先进行评估,根据结果来决定是否进行分支。
- 后剪枝:在决策树已经构造好之后,从底向上的(从分支的末端到开端)评估分支是否需要存在。
其中提到的评估,也就是性能度量,则按照常见的性能度量手段方法,如留出法等进行计算。
下面的图片依次是没有剪枝处理、预剪枝处理以及后剪枝处理的决策树。
图1 无剪枝处理
图2 预剪枝处理
图3 后剪枝处理
4.连续与缺失值
对于离散值属性,我们已经讨论了很多,但是对于连续值属性,我们是否还能像上文一样处理呢?当属性为连续值时,则不能通过简单的值来进行划分,可以通过二分法来进行处理。C4.5决策数算法中采用了这种方法。核心为:给定样本集D与连续属性a,二分法试图找到一个划分点 t 将样本集D在属性a上分为≤t与>t 两部分。
具体方法为:
- 首先,将a在D上的n个不同取值进行从小到大的排序处理,并将相邻两个属性的均值作为候选点,这样就将其离散化了。
- 然后分别计算划分后的信息增益
- 选择增益最大的划分点作为最佳划分点
在实际遇到的数据集中,经常会遇到里面有缺失值的情况,对于这种情况,我们又不能将有缺失值的数据完全做丢弃处理,那么我们该如何在属性值缺失的情况下进行划分属性选择呢? 对于给定的划分属性,如果样本在该属性上的值有缺失,那么我们该如何对样本进行划分呢?
定义:
其中为无缺失值样本子集所占比例,为无缺失值样本子集中每个类别的比例,为每个分支所含样本比例。
通过在样本集D中选取在属性a上没有缺失值的样本子集,计算在该样本子集上的信息增益,最终的信息增益等于该样本子集划分后信息增益乘以样本子集占样本集的比重。即:
5.多变量决策树
若将每个属性看作是空间中的一个坐标轴,由多个属性组成的一个样本则是空间中的一个数据点,对其分类则意味着在空间中找寻样本间的分类边界,决策树的分类边界特点为轴平行,它的分类边界由多个与坐标轴平行的分段组成,如下图(瓜的分类):
而多变量决策树则是可以画出“斜”的分类边界。其分类时考虑的不是某个属性,而是属性的线性组合。
与前面的单一变量的决策树分类图对比多变量决策树分类图,明显更加清晰,也更加高效。
6.决策树的代码实现
trees.py
#该模块用来定义决策树相关算法
from math import log
import operator
#定义计算香农熵函数
def calcshannonEnt(dataSet):
num = len(dataSet)
labelCounts = { }
for feat in dataSet:
currentLabel = feat[-1]
if currentLabel not in labelCounts.keys() :
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key]/num) # 分类概率,某个类别占总的比例
shannonEnt -= prob *log(prob,2) #信息期望值公式
return shannonEnt
def creatDataSet():
#创建数据集
dataSet = [[1,1,'yes'],
[1,1,'yes'],
[1,0,'no'],
[0,1,'no'],
[0,1,'no']]
label = ['no surfacing','flippers']
return dataSet,label
def splitDataSet(dataSet, axis, value):
#按照给定的特征划分数据集 dataSet 是待划分数据集, axis是划分数据集的特征,value 是需要返回的特征的值
#给定的特征就是axis 划分时 会判断axis和value是否相等进而来进行划分数据
copyDataSet=[] #创建一个副本来保证原始数据不被更改 ‘python语言在函数中传递的是列表的引用’
for featVec in dataSet:
#print(featVec)
if featVec[axis]==value:
reduceFeatVec = featVec[:axis] #将featVec[axis]这个元素前面的特征给reduceFeatVec
#print('reduceFeatVec ='+str(reduceFeatVec) )
reduceFeatVec.extend(featVec[axis+1:])
#print('reduceFeatVec.extend =' + str(reduceFeatVec))
copyDataSet.append(reduceFeatVec) #append 和extend 的区别: append将要添加
#的元素作为一个整体,extend则会拆开加入
#print('copyDataSet = '+str(copyDataSet))
return copyDataSet
def chooseBestFeature2Split(dataSet):
#选择最好的特征来进行数据集划分,通过计算信息熵,选择信息增量最大的那个
#调用函数的限制:1.数据必须是一种由列表元素组成的列表,而且所有的列表元素都要具有相同的数据长度
# 2.数据的最后一列或者每个实例的最后一个元素是当前实例的类别标签
numFeatures = len(dataSet[0])-1 #最后一列是类别标签
baseEntropy = calcshannonEnt(dataSet) #计算数据集的原始香农熵,并保存。用于后面与分类
#后的香农熵进行比较
bestInfoGain = 0.0;bestFeature = -1
for i in range(numFeatures):
#将dataSet中的数据先按行依次放入example中,然后取得example中的example[i]元素,放入列表featList中
featList = [example[i] for example in dataSet] # 按行将元素取出来放到featList
#中,等效于转置 可以看下面的例子
uniqueValues = set(featList) #从列表中创建集合,是得到列表唯一值的最快方法
newEntropy = 0.0
for value in uniqueValues:
subDataSet = splitDataSet(dataSet,i,value)
prob = len(subDataSet)/float(len(dataSet))
newEntropy += prob * calcshannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
def majorityCnt(classList):
#该函数使用分类名称的列表,然后创建键值为classList中唯一值的数据字典,
#字典对象存储了classList中每个类标签出现的频率,最后利用operator操作键值排序字典,并返回出现次数最多的分类名称
classCount = { }
for vote in classList:
# 判断classCount字典中是否有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 creatTree(dataSet,labels):
#创建决策树
classList = [example[-1] for example in dataSet] # 取dataSet中每一行的最后一个元素
#(即类标签)
if classList.count(classList[0]) == len(classList): #递归函数的第一个停止条件是所有的
#类标签完全相同,则直接返回该类标签
return classList[0]
if len(dataSet[0]) == 1 : #递归函数的第二个停止条件是使用完了
#所有特征,仍然不能将数据集划分成仅包含唯一类别的分组
return majorityCnt(classList) # 遍历完所有特征,返回出现次数最多的
bestFeat = chooseBestFeature2Split(dataSet)
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}}
del(labels[bestFeat])
featValues = [example[bestFeat] for example in dataSet]
uniqueValues = set(featValues)
for value in uniqueValues:
subLabels = labels[:]
myTree[bestFeatLabel[value]=creatTree(splitDataSet(dataSet,bestFeat,value),subLabels)
return myTree
ps:本文是在参加datawhale组队学习,学习周志华老师的《机器学习》过程的学习笔记。文中出现的图片均引自《机器学习》,《机器学习》是初学者入门机器学习领域的很好的教材。推荐给想要入门学习机器学习领域的同路者。
参考书籍:
《机器学习》周志华
《机器学习实战》 Peter Harrington