本文主要参考Peter Harrington著的《Machine Learning in Action》一书以及华南理工大学的王国强同学的论文《基于深度图像的人体部位识别及动作识别》,若有不妥之处欢迎指出。
ID3算法的基本原理:
ID3算法是采用自上而下构造的决策树进行学习的,其中的关键自然是如何是构建一个决策树。对于决策树构造,我们在每一个分裂节点采用统计测试的方法来确定此分裂分类属性的分类能力。具有最强分类能力的属性将被作为本分类节点的分类属性。然后以此属性将分裂节点的样本分类到此分裂节点所属的分支,在每个分支的节点上形成新的分类样本。然后递归此过程,将分支所得到的分类样本根据以上的方法进行分类直到树生成的截止条件,形成最后具有分类属性的叶子节点。这样就形成了我们所需要的自上而下的构造的决策树,采用贪婪搜索且不用考虑以前的选择。
我们看一个书中的实例:
表中的数据包含5个海洋生物,特征包括:不浮出水面是否可以生存,以及是否有脚蹼,我们可以将这些动物分成两类:鱼类和非鱼类,现在我们想要决定依据第一个特征还是第二个特征划分数据,在回答这个问题之前,我们必须采用量化的方法判断如何划分数据。
def createDataset():
dataset={[1,1,'yes'];
[1,1.'yes'],
[1,0,'no'],
[0,1,'no'],
[0,1,'no']}
labels = ['no surfacing','flippers']
return dataset,labels
怎样评测哪种划分方式是最好的划分?
划分数据集的大原则是:将无需的数据变得更加有序,我们可以使用多种方法去划分数据集,在可以评测哪种数据划分方式是最好的数据划分之前,我们必须学习如何计算信息增益。
在划分数据集之前之后信息发生的变化成为信息增益。知道如何计算信息增益,我们就可以计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。
信息论中有一个广泛使用的度量标准,可以用来精确的描述信息增益,它就是熵(Entrop)。熵是用来描述任意样例集的纯度。假如样本集有c个不同的类别,那么样本集S就具有了c个不同的状态,因此具有c个类别的样本集S的分类熵定义为
其中pi是S中属于类别i的比例。
一个好的分类属性会导致信息增益的值增大,信息增益值越高,分类效果越好,对于属性A,在样本集S中的信息增益Gain(S,A)可以精确定义为:
其中Values(A)是属性A所有可能值的集合,Sv是S中属性A的值为v的子集,也就是:
因此将信息增益作为ID3算法中选择分裂节点最佳分类属性的评价标准。
计算给定数据集的香农熵:
from math import log
def calShannonEnt(dataset)
numEntries = len(dataset)
labelCounts = {}
for featVec in dataset:
currentLabel = featVec[-1]
if currentLabel not in labelCounts.keys():
labelCounts[countLabel] = 0
labelCounts[currentLabel] + - 1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob * log(prob,2)
return shannonEnt
熵越高,则混合的数据也越多,我们可以在数据集中添加更多的分类,观察熵是如何变化的。这里我们增加第三个名为maybe的分类,测试熵的变化:
得到熵之后,我们就可以按照获取最大信息增益的方法划分数据集。
我们对每个特征划分数据集的结果计算一次信息熵,然后判断按照哪个特征划分数据集是最好的划分方式。想想一个分部在二维空间的数据散点图,需要在数据之前画条线,将他们分成两部分,我们应该按照x轴还是y轴划线呢?
按照给定特征划分数据集:
def splitDataSet(dataset,axis,value)
retDataSet=[]
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.extend(reduceDFeatVec)
return retDateSet
递归构建决策树:
上面我们已经学习了从数据集构造决策树所需要的子功能模块,其工作原理如下:得到原始数据集,然后基于最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分之后,数据将被向下传递到书分支的下一个节点,在这个节点,我们可以再次划分上数据。因此我们可以采用递归的原则处理数据集。
递归结束的条件是:程序遍历晚所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类。如果所有实例具有相同的分类,则得到一个叶子节点或者终止块。任何到达叶子节点的数据必然属于叶子节点的分类,如图:
第一个结束条件使得算法可以终止,我们甚至可以设置算法可以划分的最大分组数目,代码如下:
def majorityCnt(classList):
dclassCount={}
for vote in classList:
if vote not in classCount.keys():classCount[vote]=0
classCount[vote] +=1
sortdClassCount = sorted(classCount.iteritems(),key = operator.itemgetter(1),reverse=True)
return sortedClassCount[0][0]
创建树的函数代码:
def createTree(dataSet,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 = chooseBestFeatureToSplit(dataSet)
betFeatLabel = 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