优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据。
缺点:可能会产生过度匹配问题。
适用数据类型:数值型和标称型。
缺点:可能会产生过度匹配问题。
适用数据类型:数值型和标称型。
步骤:
- 收集原始数据集(数据向量及特征标签),数据向量最后一项为分类标签
- 以分类标签为基准,计算该原始数据集的熵E0
- 根据特征标签,分别计算按特征划分数据集后的子数据集的熵Ei,E0-Ei为该划分的信息增益,选择信息增益最大的划分(这表示该划分熵最小,最有序)
- 对每个划分的子数据集使用步骤3的方式继续划分
- 如果划分的子数据集为分类标签则停止;如果划分的子数据集只有一个分类则停止
- 以字典形式记录数据集按特征划分的结构(可存储到文件,以便后期直接载入使用)
- 输入待预测数据向量,根据决策树特征划分进行遍历,最终获取预测分类标签。
创建分支的伪代码函数createBranch() 如下所示:
检测数据集中的每个子项是否属于同一分类:
If so return 类标签;
Else
寻找划分数据集的最好特征
划分数据集
创建分支节点
for 每个划分的子集
调用函数createBranch并增加返回结果到分支节点中
return 分支节点
香农熵计算公式:
构建决策树工作原理如下:
得到原始数据集,然后基于最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分之后,数据将被向下传递到树分支的下一个节点,在这个节点上,我们可以再次划分数据。因此我们可以采用递归的原则处理数据集。
递归结束的条件是:程序遍历完所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类。如果所有实例具有相同的分类,则得到一个叶子节点或者终止块。
import math
#创建数据集
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
#计算给定数据集的香农熵
def calcShannonEnt(dataSet):
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*math.log(prob, 2)
return shannonEnt
#按给定特征划分数据集
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
#选择最好的数据集划分方式,返回划分特征
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0
bestFeature = -1
for i in range(numFeatures):
featList = [x[i] for x in dataSet]
uniqueVals = set(featList)
newEntropy = 0.0
#计算第i个特征划分后的信息增益
for value in uniqueVals:
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):
classCount = {}
for vote in classList:
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.items(), key=lambda x: x[1], reverse=True)
return sortedClassCount[0][0]
#创建树(训练决策树)
def createTree(dataSet, labels):
classList = [x[-1] for x in dataSet]
#类别完全相同则停止划分
if classList.count(classList[0]) == len(classList):
return classList[0]
#遍历完所有特征,返回出现概率最高的分类
if len(dataSet[0]) == 1:
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}}
del(labels[bestFeat])
featValues = [x[bestFeat] for x in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree
myData, labels = createDataSet()
myTree = createTree(myData, labels)
print(myTree)
#使用决策树的分类函数
def classify(inputTree, featLabels, testVec):
firstStr = list(inputTree.keys())[0]
secondDict = inputTree[firstStr]
featIndex = featLabels.index(firstStr)
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
myData, labels = createDataSet()
classify(myTree, labels, [1, 1])