1 算法概述
1.1 结构分析
决策树是一种依托决策而建立起来的树,其中,每一个内部节点表示一个属性上的测试,每一个分支代表一个测试的结果输出,每一个叶子代表一种类别。
如上图所示就是一个决策树,首先分析所给数据集是否为同一类别,如果是,那就不用划分了,如果不是,就寻找划分数据集最好的特征进行划分(也就是影响数据集划分因素最明显的特征划分,这个后面会专门介绍最大信息增益法划分,知道怎么用就中),比如上图一开始就是选择的是:发送邮件域名地址为某某莫,每个内部节点(数据子集)代表一个二分类问题,每个叶子节点是分类的结果,遇到内部节点就一直划分,直到划分的数据子集都属于同一类型的数据时,就停止划分,此时,也就意味着你的决策树已经建好。
1.2 算法思想
这是一种寻找数据集内部之间规律的算法,以信息熵为度量,构造一颗熵值下降最快的数,到叶子节点处的熵值为零。此时,每一个叶子代表一个类别。
在决策树的每一个非叶子结点划分之前,先计算每一个属性所带来的信息增益,选择最大信息增益的属性来划分,因为信息增益越大,区分样本的能力就越强,越具有代表性,很显然这是一种自顶向下的贪心策略。以上就是ID3算法的核心思想
1.3 算法实现步骤
①计算每种特征划分方式的信息熵,选取信息熵最大的一种划分方式
②递归的构建决策树
2 实现
(1)创造数据集
(2)计算数据集信息熵
def calcShannonEnt(dataSet):
numEntries = len(dataSet) #len()是计算变量长度的函数 ,numEntries=5
labelCounts = {} #为所有可能分类创造字典
#遍历每条数据集样本,如果字典里没有数据集中的类别,将此类别存入字典中,如果有,将此类别数目加1
#以下for循环里计算结果为labelCounts=['yes':2,'no':3]
#其实,以下for循环可以用一句话概括:
#labelCounts[currentLabel]=labelCounts.get(currentLabel,0)+1
for featVec in dataSet: #the the number of unique elements and their occurance
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 #numEntries=5
shannonEnt -= prob * log(prob,2) #log base 2
return shannonEnt
(3)根据上面计算的信息熵,划分数据集
(4)选择最好的划分结果
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #特征数,-1是因为dataSet最后一列是标签类别
baseEntropy = calcShannonEnt(dataSet) #计算总数据集的信息熵
bestInfoGain = 0.0; bestFeature = -1
for i in range(numFeatures): #遍历每一个特征
featList = [example[i] for example in dataSet]#遍历每个特征的数据集
uniqueVals = set(featList) #第i个特征取值集合,如果i=1,则uniqueVals=[1,1,1,0,0]
newEntropy = 0.0
for value in uniqueVals: #依据特征划分数据集,并计算每种划分方式的信息熵
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet)/float(len(dataSet))
newEntropy += prob * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy #calculate the info gain; ie reduction in entropy
if (infoGain > bestInfoGain): #选择最大信息熵的划分结果
bestInfoGain = infoGain #if better than current best, set to best
bestFeature = i
return bestFeature #returns an integer
(5)递归构造决策树
多数表决的方法决定叶子节点的分类 –该叶子属于哪一类的样本最多,我们就说该叶子属于哪类
def createTree(dataSet,labels):
classList = [example[-1] for example in dataSet] #数据集所有标签列表
#终止条件1;类别完全相同,表示就只有一个类别,则停止继续划分 返回标签-叶子节点
if classList.count(classList[0]) == len(classList):
return classList[0]#stop splitting when all of the classes are equal
#终止条件2:无法将数据集划分成唯一类别时,采用多数表决法决定叶子的分类
if len(dataSet[0]) == 1: #stop splitting when there are no more features in dataSet
return majorityCnt(classList) #遍历完所有的特征时返回出现次数最多的
#开始创建树
bestFeat = chooseBestFeatureToSplit(dataSet) #选择最好的方式进行划分数据集,得到最好划分数据集特征
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}} #用上面得到的最好划分数据集特征建立一个空树(空字典)
del(labels[bestFeat]) #删除labels[bestFeat]
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals: #遍历当前选择的特征包含的所有属性
subLabels = labels[:] #copy all of labels, so trees don't mess up existing labels
#递归调用构建树的过程,直到遍历完所有划分数据集属性,所有相同类别的数据均被分到同一个数据子集中
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
(6)用决策树进行预测标签
def classify(inputTree,featLabels,testVec):
firstStr = inputTree.keys()[0] #第一个特征属性:firstStr='no surfacing'
#除去第一个特征属性的字典:secondDict={0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}
secondDict = inputTree[firstStr]
featIndex = featLabels.index(firstStr) #寻找第一个特征属性在特征属性列表中的位置:featIndex=0
for key in secondDict.keys(): #遍历整棵树,比较待测特征与树节点的值,直到找到特征值完全匹配的叶子节点
if testVec[featIndex] == key:
if type(secondDict[key]).__name__ == 'dict':
classLabel = classify(secondDict[key], featLabels, testVec)
else: classLabel = secondDict[key]
return classLabel