一、决策树的构造
决策树 |
---|
优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据 |
缺点:可能会产生过度匹配问题 |
使用数据类型:数值型和标称型 |
在构造决策树时,我们需要解决的第一个问题是,当前数据集上哪个特征在划分数据分类时起决定性作用。为了找到决定性的特征,划分出最好的结果,我们必须评估每个特征。完成测试之后,原始数据集被划分为几个数据子集。这些数据子集会分布在第一个决策点的所有分支。如果某个分支下的数据属于同一类型,则当前数据已经正确地分类,无需进一步对数据集进行分割。如果数据子集内的数据不属于同一类型,则需要重新划分数据子集。
决策树的一般流程 |
---|
(1)收集收据:可以使用任何方法 |
(2)准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化 |
(3)分析数据:可以使用任何方法,构造书完成之后,我们应该检查图形是否符合预期 |
(4)训练算法:构造树的数据结构 |
(5)测试算法:使用经验树计算错误率 |
(6)使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义 |
如果依据某个属性划分数据将会产生4个可能的值,我们将把数据划分为四块,并创建四个不同的分支。这里将使用ID3算法划分数据集,该算法处理如何划分数据集,何时停止划分数据集。每次划分数据集时我们只选取一个特征属性,如果训练集中存在20个特征,第一次应该选择哪个特征作为划分的参考属性?
1.信息增益
划分数据集的原则是:将无无序的数据变得更加有序。我们可以使用多种方法划分数据集,但是每种方法都有各自的优缺点。组织杂乱无章数据的一种方法就是使用信息论度量信息,信息论是量化处理信息的分支科学。我们可以在划分数据之前或之后使用信息论度量信息的内容。
在划分数据集之前之后信息发生的变化称为信息增益,计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。集合信息的度量方式称为香农熵或者简称熵。
熵定义为信息的期望值。如果待分类的事务可能划分在多个分类之中,则符合
xi
的信息定义为
为了计算熵,我们需要计算所有类别所有可能值包含的信息期望值:
计算信息熵函数tree.py
from math import log
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 createDataSet():
dataSet = [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
labels = ['no surfacing', 'flippers']
return dataSet, labels
另外一个度量集合无序程序的方法是基尼不纯度,简单地说就是从一个数据集中随机选出子项,度量其被错误分类到其他分组里的概率。
2.划分数据集
分类算法除了需要测量信息熵,还需要划分数据集,度量划分数据集的熵,以便判断当前是否正确地划分了数据集。我们将对每个特征划分数据集的结果计算一次信息熵,然后判断按照哪个特征划分数据集时最好的划分方式。想象一个分布式在二维空间的数据散点图,需要在数据之间划条线,将它们分成两部分,我们应该按照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.append(reducedFeatVec)
return retDataSet
3.选取最优分类的特征
循环计算香农熵,计算去除某个特征后的香农熵的值,用整个数据集的香农熵减去去除某个特征后的香农熵就等于这个特征的信息增益,增益最大的就是这个数据集最优分类的特征。
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] # 提取数据集中第i个特征的所有取值放到列表里面 例如: featList = [1,0,1,1,0]
uniqueVals = set(featList) # 去重复值 uniqueVals = [0,1]
newEntropy = 0.0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value) # 从数据集中获取第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):
'''
此方法和knn分类方式类似
'''
classCount = {}
for vote in 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]
4.创建决策树
基本思路:
1.首先创建数据集和特征标签。
2.把集合的分类标签存放在列表里面,判断列表元素是否全部一样;判断特征是否全部用完。
3.选取最优特征,返回最优特征索引和贴上特征类别标签,并且把对应的特征和特征标签删除。
4.创建决策树对象,把最优特征的标签以键的形式保存,值为空。
5.获取最优特征的所有类别
6.循环最优特征的所有类别,以类别为节点继续迭代原函数,直到特征用完或者无需再分为止。
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
二、决策树的绘制
绘制树节点
import matplotlib.pyplot as plt
from pylab import mpl # Matplotlib无法正常显示中文,因为matplot缺少中文字体
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
decisionNode = dict(boxstyle="sawtooth", fc="black", alpha=0.8) # boxstyle:边框的样式;fc:底色的颜色;alpha:颜色的深浅程度
leafNode = dict(boxstyle="round4", fc="0.3")
arrow_args = dict(arrowstyle="<-", connectionstyle='bar', color="red") # arrowstyle:线段的样式; connectionstyle:线段的展示形式(曲线、圆弧) color:线段颜色
def plotNode(nodeTxt, centerPt, parentPt, nodeType):
'''
:param nodeTxt: 标签的内容
:param centerPt: 标签的位置
:param parentPt: 注释的位置,即一个指向某个节点的注释
:param nodeType: 节点于注释之间连线的样式
:return:
xycoords 和textcoords 表示xy点和相关注释的坐标; arrowprops连线的样式
annotate方法是对图片进行注释
'''
createPlot.axl.annotate(s=nodeTxt, xy=parentPt, xycoords='axes fraction', xytext=centerPt,
textcoords='axes fraction',
va="center", ha="center", bbox=nodeType, arrowprops=arrow_args)
def createPlot():
fig = plt.figure(1, facecolor='white')
fig.clf()
createPlot.axl = plt.subplot(111, frameon=False) # frameon=False移除边框
plotNode(U"决策点", (0.5, 0.1), (0.1, 0.5), decisionNode)
plotNode(u'叶节点', (0.8, 0.1), (0.3, 0.8), leafNode)
plt.show()