概念:
决策树是一种树形结构的分类器,通过顺序询问分类点的属性决定分类点最终的类别。
通常根据特征的信息增益或其他指标,构建一颗决策树。
在分类时,只需要按照决策树中的结点依次进行判断,即可得到样本所属类别。
决策树本质上是寻找一种对特征空间上的划分,旨在构建一个训练数据拟合的好,并且复杂度小的决策树。
特点:
- knn最大的缺点就是无法给出数据的内在含义
优势:
- 输出结果易于理解,数据形式非常容易理解
- 计算复杂度不高
- 对中间值的缺失不敏感
- 可以处理不相关特征数据
缺点:
- 可能会产生过度匹配问题
使用数据类型:
- 数值型和标称型
重要任务:
理解数据中所蕴含的知识信息,决策树可以使用不熟悉的数据集合,并从中提取出一系列规则,这些机器根据数据集创建规则的过程,就是机器学习的过程。
例子:
- 长方形:判断模块(decision block)
- 椭圆形:终止模块(terminating block):表示已经得出结论,可以终止运行
- 分支(branch):从判断模块引出的左右箭头
决策树的构造:
构造决策树时,我们需要解决的第一个问题就是,当前数据集上哪个特征在划分数据分类时起决定性作用?
为了找到决定性的特征,划分出最好的结果,我们必须评估每个特征。
决策树的一般流程:
- 收集数据:可以使用任何方法
- 准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化
- 分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期
- 训练算法:构造树的数据结构
- 测试算法:使用经验树计算错误率
- 使用算法: 此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义
使用算法:
根据分割方法的不同:有基于信息论(Information Theory)的方法和基于最小GINI指数(lowest GINI index)的方法。
对应前者的常见方法有ID3、C4.5,后者的有CART。
此处使用ID3算法:处理如何划分数据集,何时停止划分数据集。
每次划分数据集时,我们只选取一个特征属性,那么第一次我们选择哪个特征作为划分的参考属性呢?
需要采用量化的方法判断先选用哪个特征来划分数据。
信息论:
划分数据集的大原则:将无序的数据变得更加有序。
信息的定义:如果待分类的事务可能划分在多个分类之中,则符合 xi 的信息定义为
其中p(xi)是选择该分类的概率。信息熵(Entropy): 集合信息的度量方式
熵的概念主要是指信息的混乱程度,变量的不确定性越大,熵的值也就越大
为了计算熵,我们需要计算所有类别所有可能值包含的信息期望值:
其中n是分类的数目。信息增益(infomation gain):划分数据集之前之后信息发生的变化
信息增益指的是划分前后熵的变化,可以用下面的公式表示:
1.计算给定数据集的香农熵:
度量数据集的无序程度
from math import log
import operator
def calcShannonEnt(dataSet): # 计算香农熵
numEntries = len(dataSet) # 计算数据集中实例的总数
labelCounts = {} # 为所有可能分类创建字典
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 # 分类的概率
tmp = prob * log(prob,2)
print tmp
shannonEnt -= prob * log(prob,2) #log base 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']
#change to discrete values
return dataSet, labels
if __name__ == '__main__':
myDat, labels = createDataSet()
print myDat
print '--------'
print labels
shannonEnt = calcShannonEnt(myDat)
print '--------'
print shannonEnt
labelCounts:{'yes':2,'no':3}
输出shannonEnt=0.970950594455
熵越高,则混合的数据也越多。
增加一个maybe分类:myDat[0][-1] = 'maybe'
输出shannonEnt=1.37095059445,显然,有3个分类的熵更高
得到熵之后,我们就可以按照获取最大信息增益的方法划分数据集
2.划分数据集:
'''
按照给定特征划分数据集:
当我们按照某个特征划分数据集时,就需要将所有符合要求的元素抽取出出来
'''
def splitDataSet(dataSet, axis, value): # 待划分数据集、划分数据集的特征、需要返回的特征的值
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis] # chop out axis used for splitting
reducedFeatVec.extend(featVec[axis + 1:])
retDataSet.append(reducedFeatVec)
return retDataSet
def createDataSet():
dataSet = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
labels = ['no surfacing','flippers']
#change to discrete values
return dataSet, labels
if __name__ == '__main__':
myDat, labels = createDataSet()
temp1 = splitDataSet(myDat,0,1) # 用第一个特征来划分数据集,需要返回的特征值为1
print temp1
temp2 = splitDataSet(myDat,0,0) # 用第一个特征来划分数据集,需要返回的特征值为0
print temp2
3.选择最好的数据集划分方式:
from machinelearninginaction.Ch03.trees import calcShannonEnt,splitDataSet,createDataSet
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 # the last column is used for the labels 计算特征值的数量为2
baseEntropy = calcShannonEnt(dataSet) # 基础香农熵值为0.970950594455
bestInfoGain = 0.0
bestFeature = -1
for i in range(numFeatures): # iterate over all the features
featList = [example[i] for example in dataSet] # create a list of all the examples of this feature # 获取某个特征的所有特征值
uniqueVals = set(featList) # get a set of unique values 所有特征值去重后的列表
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): # compare this to the best gain so far
bestInfoGain = infoGain # if better than current best, set to best
bestFeature = i
return bestFeature # returns an integer
if __name__ == '__main__':
myDat, labels = createDataSet()
result = chooseBestFeatureToSplit(myDat)
print result
'''
第0个特征,的信息增益为0.419973094022,数据无序度减少最多
按1个特征,的信息增益为0.170950594455
'''
chooseBestFeatureToSplit(dataSet)调用时,需要满足一定的要求:
- 数据必须是由列表元素组成的列表,而且所有的列表元素需要满足一定的要求
- 数据的最后一列或者每个实例的最后一个元素是当前实例的类别标签
4.递归构建决策树
如果数据集已经处理了所有属性,但是类标签依然不是唯一的,此时我们需要决定如何定义该叶子节点,在这种情况下,我们通常采用多数表决的方法决定该叶子节点的分类。
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]
创建决策树基本思路:
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
matplotlib绘制树形图:
使用文本注解绘制树节点:
'''
使用文本注解绘制树节点
'''
import matplotlib.pyplot as plt
decisionNode = dict(boxstyle="sawtooth", fc="0.8")
leafNode = dict(boxstyle="round4", fc="0.8")
arrow_args = dict(arrowstyle="<-")
def plotNode(nodeTxt, centerPt, parentPt, nodeType):
'''
:param nodeTxt: 标签的内容
:param centerPt: 标签的位置
:param parentPt: 注释的位置,即一个指向某个节点的注释
:param nodeType: 节点于注释之间连线的样式
:return:
xycoords 和textcoords 表示xy点和相关注释的坐标; arrowprops连线的样式
annotate方法是对图片进行注释
'''
createPlot.ax1.annotate(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()
axprops = dict(xticks=[], yticks=[])
# createPlot.ax1 = plt.subplot(111, frameon=False, **axprops) #no ticks
createPlot.ax1 = plt.subplot(111, frameon=False) #ticks for demo puropses
plotNode('decisionNode',(0.5, 0.1),(0.1, 0.5),decisionNode)
plotNode('leafNode', (0.8, 0.1), (0.3, 0.8), leafNode)
plt.show()
if __name__ == '__main__':
createPlot()
构造注解树:
确定x轴的长度:有多少个叶节点
确定y轴的长度:树有多少层
测试和存储分类器:
测试算法:使用决策树执行分类:
使用算法:决策树的存储:
- 序列化对象可以在磁盘上保存对象,并在需要的时候读取出来。
- 任何对象都可以执行序列化操作
def storeTree(inputTree, filename):
import pickle
fw = open(filename, 'w')
pickle.dump(inputTree, fw)
fw.close()
def grabTree(filename):
import pickle
fr = open(filename)
return pickle.load(fr)
可以将分类器存储在硬盘上,而不用每次对数据分类时重新学习一遍。
结果:
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
示例:使用决策树预测隐形眼镜类型:
附录:
信息定义基本内容:
通常,一个信源发送出什么符号是不确定的,衡量它可以根据其出现的概率来度量。概率大,出现机会多,不确定性小;反之就大。
- 不确定性函数f是概率P的单调递降函数;
- 两个独立符号所产生的不确定性应等于各自不确定性之和,即f(P1,P2)=f(P1)+f(P2),这称为可加性。
同时满足这两个条件的函数f是对数函数
信息量起码该满足些什么特点呢?
- 一,起码不是个负数吧,不然说句话还偷走信息呢~
- 二,起码信息量和信息量之间可以相加吧!假如你告诉我的第一句话的信息量是3,在第一句话的基础上又告诉我一句话,额外信息量是4,那么两句话信息量加起来应该等于7吧!难道还能是5是9?
- 三,刚刚已经提过,信息量跟概率有关系,但我们应该会觉得,信息量是连续依赖于概率的吧!就是说,某一个概率变化了0.0000001,那么这个信息量不应该变化很大。
- 四,刚刚也提过,信息量大小跟可能结果数量有关。假如每一个可能的结果出现的概率一样,那么对于可能结果数量多的那个事件,新信息有更大的潜力具有更大的信息量,因为初始状态下不确定性更大。
满足以上特点的函数
负的对数函数,也就是-log(x)!底数取大于1的数保证这个函数是非负的就行。前面再随便乘个正常数也行。
- a. 为什么不是正的?因为假如是正的,由于x是小于等于1的数,log(x)就小于等于0了。满足特点一。
- b. 由于-log(xy) = -log(x) -log(y),所以也是对的。满足特点二。学数学的同学注意,这里的y可以是给定x的条件概率,当然也可以独立于x。
- c. 假如x是一个概率,那么log(x)是连续依赖于x的。满足特点三。
- d. 假如有n个可能结果,那么出现任意一个的概率是1/n,而-log(1/n)是n的增函数。满足特点四。
- By the way,这个函数是唯一的(除了还可以多乘上任意一个常数),有时间可以自己证明一下,或者查书。所以我们知道一个事件的信息量就是这个事件发生的概率的负对数。