决策树
优点:计算复杂度不高,输出结果易于理解,对中间值缺失不敏感,可以处理不相关特征数据 缺点:可能会产生过度匹配问题 适用数据类型:数值型和标称型
解释: ??标称型:标称型目标变量的结果只在有限目标集中取值,比如真与假(标称型目标变量主要用于分类) ??数值型(连续型):数值型目标变量则可以从无限的数值集合中取值
??决策树经常用于解决处理分类问题,它的一个重要任务是为了理解数据中所蕴含的知识信息,因此决策树可以使用不熟悉的数据集并从中提取出一系列规则,这些机器根据数据集创建规则的过程,就是机器学习的过程。专家系统中经常使用决策树,而且决策树给出结果往往可以匹敌人类专家。
决策树的构造
决策树的一般流程
- 收集数据:可以使用任何方法
- 准备数据:树构造的算法只适用于标称型数据,因此数值型数据必须离散化
- 分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期
- 训练算法:构造树的数据结构
- 测试算法:使用经验树计算错误率
- 使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好的理解数据的内在含义
一些决策树算法采用二分法划分数据,而这里使用ID3算法划分数据集,每一次划分数据集时只选取一个特征属性。
1、信息增益
??划分数据集的最大原则:将无序的数据变得有序。组织杂乱无章数据的一种方式就是使用信息论度量信息,量信息是量化处理信息的分支科学。我们可以在划分数据之前或之后使用信息量化度量信息的内容。 ??划分数据之前之后信息发生的变化称之为信息增益,知道如何计算信息增益,就可以计算每一个特征值划分数据集获得信息增益,获得信息增益最高的特征就是最好的选择。 ??集合信息的度量方式是香农熵或简称熵。熵定义信息的期望值。若待分类的事务可能划分在多个分类之中,则符号x_i的信息定义为:
l
(
x
i
)
=
?
l
o
g
2
p
(
x
i
)
l(x_i)=-log_2 p(x_i)
l(xi?)=?log2?p(xi?)
??为了计算熵,需要计算所有类别所有可能包含的信息期望,通过下面公式得到:
?
∑
i
=
1
n
p
(
x
i
)
l
o
g
2
p
(
x
i
)
-\sum_{i=1}^n p(x_i) log_2 p(x_i)
?i=1∑n?p(xi?)log2?p(xi?)
??熵越高,混合的数据也就越多。得到熵之后,可以按照获取最大信息增益的方法划分数据集。
2、划分数据集
??分类算法除了需要测量信息熵,还需要划分数据集,度量划分数据的熵,以便判断当前是否正确的划分了数据集。我们对每一个特征划分数据集的结果计算一次信息熵,然后判断按照哪个特征划分数据集是最好的方式。
3、递归构建决策树
??目前我们已经学习了冲数据集构造决策树所需的子功能模块。其工作原理是:
- 得到原始数据集
- 基于最好的属性值划分数据集,由于特征值可能有多于两个,因此可能存在大于两个分支的数据划分
- 第一次划分后,数据将向下传递到树分支的下一个节点,在这个节点上,我们可以再次划分数据集
- 最后采用递归的原则处理数据集
??递归结束的条件是:遍历完所有的数据集的属性,或者每个分支下的所有实例都具有相同的分类。若所有实例都具有相同的分类,则得到一个叶子节点或者终止块。任何到达叶子节点的数据必然是属于叶子节点的分类。
from math import log
import operator
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*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 = [example[i] for example in dataSet]
uniqueVals = set(featList)
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
if infoGain > bestInfoGain:
bestInfoGain = infoGain
bestFeature = i
return bestFeature
def majorityCnt(classList):
classCount = {}
for vote in classList:
if vote in classCount.keys():
classCount[vote] = 0
classCount += 1
sortedClassCount = sorted(
classCount.items(), 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)
bestFeatLabel = 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
dataSet, labels = createDataSet()
re = createTree(dataSet, labels)
print(re)
在Python中使用Matplotlib注解绘制树形图
??决策树的主要优点是直观易于理解,若不能将其直观地表示出来,就无法发挥其优势
1、Matplotlib注解
??Matplotlib提供了非常有用的注解工具annotations,它可以在数据图形上添加文本注解,注解通常用于解释数据内容。
2、构造注解树
??我们必须知道有多少个叶子结点,以便可以正确的确定x轴的长度;还学要知道树的层数以便可以正确的确定y的高度
3、完整代码示例
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
decisionNode = dict(boxstyle='sawtooth', fc='0.8')
leafNode = dict(boxstyle='round4', fc='0.8')
arrow_args = dict(arrowstyle='<-')
def plotNode(nodeTxt, centerPt, parentPt, nodeType):
createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',
xytext=centerPt, va='center', ha='center', bbox=nodeType, arrowprops=arrow_args)
def plotMidText(cntrPt, parentPt, txtString):
xMid = (parentPt[0]-cntrPt[0])/2.0+cntrPt[0]
yMid = (parentPt[1]-cntrPt[1])/2.0+cntrPt[1]
createPlot.ax1.text(xMid, yMid, txtString)
def plotTree(myTree, parentPt, nodeTxt):
numLeafs = getNumLeafs(myTree)
depth = getTreeDepth(myTree)
firstStr = list(myTree.keys())[0]
cntrPt = (plotTree.xOff+(1.0+float(numLeafs)) /
2.0/plotTree.totalW, plotTree.yOff)
plotMidText(cntrPt, parentPt, nodeTxt)
plotNode(firstStr, cntrPt, parentPt, decisionNode)
sceondDict = myTree[firstStr]
plotTree.yOff = plotTree.yOff-1.0/plotTree.totalD
for key in sceondDict.keys():
if type(sceondDict[key]).__name__ == 'dict':
plotTree(sceondDict[key], cntrPt, str(key))
else:
plotTree.xOff = plotTree.xOff+1.0/plotTree.totalW
plotNode(sceondDict[key], (plotTree.xOff,
plotTree.yOff), cntrPt, leafNode)
plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
plotTree.yOff = plotTree.yOff+1.0/plotTree.totalD
def createPlot(inTree):
fig = plt.figure(1, facecolor='white')
fig.clf()
axprops = dict(xticks=[], yticks=[])
createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)
plotTree.totalW = float(getNumLeafs(inTree))
plotTree.totalD = float(getTreeDepth(inTree))
plotTree.xOff = -0.5/plotTree.totalW
plotTree.yOff = 1.0
plotTree(inTree, (0.5, 1.0), '')
plt.show()
def getNumLeafs(myTree):
numLeafs = 0
firstStr = list(myTree.keys())[0]
secondDict = myTree[firstStr]
for key in secondDict.keys():
if type(secondDict[key]).__name__ == 'dict':
numLeafs += getNumLeafs(secondDict[key])
else:
numLeafs += 1
return numLeafs
def getTreeDepth(myTree):
maxDepth = 0
firstStr = list(myTree.keys())[0]
secondDict = myTree[firstStr]
for key in secondDict.keys():
if type(secondDict[key]).__name__ == 'dict':
thisDepth = 1+getTreeDepth(secondDict[key])
else:
thisDepth = 1
if thisDepth > maxDepth:
maxDepth = thisDepth
return maxDepth
def retrieveTree(i):
listOfTree = [{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}, {
'no surfacing': {0: 'no', 1: {'flippers': {0: {'head': {0: 'no', 1: 'yes'}}, 1: 'no'}}}},
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}, 3: 'maybe'}}]
return listOfTree[i]
createPlot(retrieveTree(0))
测试和存储分类器
??主要内容是使用决策树构建分类器,并介绍实际应用中如何存储分类器
1、测试算法:使用决策树执行分类
??依靠训练数据构造决策树之后,可用于实际数据的分类。在实行数据分类时,需要依靠决策树以及用于构造决策树的标签向量;然后程序比较测试数据与决策树的上的数值,递归执行该过程直到进入叶子节点;最后将测试数据定义为叶子结点所属的类型。
2、适用算法:决策树的存储
??为了节省更多的计算时间,最好能够在每次执行分类时调用已经构建好的决策树,需要使用python模块pickle序列化对象。序列化对象可以在磁盘上保存对象,并在需要的时候读取出来。
3、完整代码示例
from math import log
import operator
import pickle
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*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 = [example[i] for example in dataSet]
uniqueVals = set(featList)
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
if infoGain > bestInfoGain:
bestInfoGain = infoGain
bestFeature = i
return bestFeature
def majorityCnt(classList):
classCount = {}
for vote in classList:
if vote in classCount.keys():
classCount[vote] = 0
classCount += 1
sortedClassCount = sorted(
classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
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
def storeTree(inputTree, filename):
fw = open(filename, 'wb')
pickle.dump(inputTree, fw)
fw.close()
def grabTree(filename):
fr = open(filename, 'rb+')
return pickle.load(fr)
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)
bestFeatLabel = 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
dataSet, labels = createDataSet()
listOfTree = [{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}, {
'no surfacing': {0: 'no', 1: {'flippers': {0: {'head': {0: 'no', 1: 'yes'}}, 1: 'no'}}}},
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}, 3: 'maybe'}}]
print(classify(listOfTree[0], labels, [1, 0]))
print(classify(listOfTree[0], labels, [1, 1]))
storeTree(dataSet, 'classifierStorage.txt')
print(grabTree('classifierStorage.txt'))
示例:使用决策树预测隐形眼镜的类型
使用决策树预测隐形眼镜类型的一般流程
- 收集数据:提供文本文件
- 准备数据:解析tab键分隔的数据行
- 分析数据:快速检查数据,确保正确的解析数据内容,使用createPlot()函数绘制最终的树形图
- 训练算法:适用上文采用的createTree
- 测试算法:编写测试函数验证决策树可以正确分类给定的数据实例
- 使用算法:存储树的数据结构,以便下次使用时无需重新构造树
完整代码示例
import P36
import P43
fr = open('lenses.txt')
lenses = [inst.strip().split('\t') for inst in fr.readlines()]
lensesLables = ['age', 'prescript', 'astigmatic', 'tearRate']
lensesTree = P36.createTree(lenses, lensesLables)
print(lensesTree)
P43.createPlot(lensesTree)
注意 :匹配选项过多会产生过度匹配,为了减少过度匹配可以采用裁剪决策树,去掉一些非必要的叶子结点。若叶子结点只能增加少许信息,则可以删除该节点将它并入到其他叶子结点中。
小结
??决策树分类器就像带有终止块的流程图,终止块表示分类结果。开始处理数据集时,需要测量数据中的不一致性,也就是熵,然后寻找最优方案划分数据集,直到数据集中所有数据属于同一分类。ID3算法可以用于划分标称型数据集。构建决策树时,通常采用递归的方法将数据集转化为决策树。
其中lenses.txt文件数据为:
young myope no reduced no lenses
young myope no normal soft
young myope yes reduced no lenses
young myope yes normal hard
young hyper no reduced no lenses
young hyper no normal soft
young hyper yes reduced no lenses
young hyper yes normal hard
pre myope no reduced no lenses
pre myope no normal soft
pre myope yes reduced no lenses
pre myope yes normal hard
pre hyper no reduced no lenses
pre hyper no normal soft
pre hyper yes reduced no lenses
pre hyper yes normal no lenses
presbyopic myope no reduced no lenses
presbyopic myope no normal no lenses
presbyopic myope yes reduced no lenses
presbyopic myope yes normal hard
presbyopic hyper no reduced no lenses
presbyopic hyper no normal soft
presbyopic hyper yes reduced no lenses
presbyopic hyper yes normal no lenses
|