说在这个系列前面的话:
这个机器学习小窥系列是我学习Peter Harrington的《机器学习实战》和周志华的《机器学习》两本书的一些心得,读书笔记。第一次学习机器学习的一些算法,学习的过程当中对于一些内容我适当地先省略掉了,比如说决策树算法中一些防止过拟合的措施,等以后有必要再补上这些内容。然后我认为这个系列最重要的一个贡献就是:我为《机器学习实战》上的代码加上了我自己理解的注释,众所周知,这本书上的代码没有注释,这对于没有从来没有接触过机器学习然后直接使用这本书作为入门同学还是有一定的阻碍。
第三章决策树
1.1决策树背景概述
决策树(Decision Tree)算法是一种基本的分类与回归方法,是最经常使用的数据挖掘算法之一。我们这章节只讨论用于分类的决策树。
决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是 if-then 规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。
决策树学习通常包括 3 个步骤:特征选择、决策树的生成和决策树的修剪(本章没有讨论这部分,西瓜书上决策树部分介绍了)。
1.2决策树的定义
分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点(node)和有向边(directed edge)组成。结点有两种类型:内部结点(internal node)和叶结点(leaf node)。内部结点表示一个特征或属性(features),叶结点表示一个类(labels)。
用决策树对需要测试的实例进行分类:从根节点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到其子结点;这时,每一个子结点对应着该特征的一个取值。如此递归地对实例进行测试并分配,直至达到叶结点。最后将实例分配到叶结点的类中。
2决策树相关原理
2.1决策树的构造
在构造决策树之前,我们要解决的第一个问题就是,当前数据集上哪个特征在划分数据分类时具有决定性的作用,只有找到最具决定性的特征,才划分出最好的结果,所以我们要找到好的办法评估每个特征。使用一个特征划分之后,当前 的数据集就会被划分为几个子集分布在第一个决策点的分支上,如果某个分支行的子集已经属于同一类别,则无需继续划分,如果该子集上的数据不属于同一类别,则需要继续重复划分的过程,划分子集的办法和划分原始数据集合的办法相同(这里蕴含着迭代的思想),直到具有相同类型的数据在同一个子集里。
创建分支的伪代码函数createBranch():
def createBranch():
'''
此处运用了迭代的思想。 感兴趣可以搜索 迭代 recursion, 甚至是 dynamic programing。
'''
检测数据集中的所有数据的分类标签是否相同:
If so return 类标签
Else:
寻找划分数据集的最好特征(划分之后信息熵最小,也就是信息增益最大的特征)
划分数据集
创建分支节点
for 每个划分的子集
调用函数 createBranch (创建分支的函数)并增加返回结果到分支节点中
return 分支节点
那么如何评估一个特征具有决定性的作用呢?
著名的ID3决策树学习算法是使用“信息增益”为准则来评估一个划分特征的。谈信息增益之前我们不得不提到“熵”这个名词,熵来源于信息论,由信息论之父克劳德·香农定义,因此熵又叫香农熵,熵这个名词具体的定义我们不需要追究。它的通俗意义是用来形容信息的有序程度的,信息越有序,熵就越低,信息越无序,熵就越高。
接下来这一段很重要!不然后面代码中的熵计算你会一脸懵逼!
信息熵是度量样本集合纯度的一种指标,假定当前样本集合D中第k类样本所占的比例为
(k=1,2,3,….,|y|y是取值的种类,比如说一枚抛出去的硬币有两种可能性,正反)。则D的熵定义为:
Ent(D)的值越小,纯度越高。
假设离散属性a有V个可能的取值(
,
,….
),若使用属性a来对样本集D进行划分,则会产生V个分支结点,其中第v个分支结点包含D中所有在属性a上取值为
的样本,记为
,根据上面的公式计算出
的熵,再考虑到不同分支结点所包含的样本数不同,给分支结点赋予权重
,即样本数越多的分支结点影响越大,于是可以计算出属性a对样本集D进行划分所获得的“信息增益(information gain)”
2.2决策树开发流程
收集数据:可以使用任何方法。
准备数据:树构造算法 (这里使用的是ID3算法,只适用于标称型数据,这就是为什么数值型数据必须离散化。 还有其他的树构造算法,比如CART)
分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。
训练算法:构造树的数据结构。
测试算法:使用训练好的树计算错误率。
使用算法:此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。
3 决策树 项目案例
3.1判定鱼类和非鱼类
使用一下两个特征来划分:鱼类和非鱼类
1.不浮出水面是否能够生存
2.是否有脚蹼
-收集数据:可以使用任何方法
准备数据:树构造算法(这里使用的是ID3算法,因此数值型数据必须离散化。)
分析数据:可以使用任何方法,构造树完成之后,我们可以将树画出来。
训练算法:构造树结构
测试算法:使用习得的决策树执行分类
使用算法:此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义
收集数据:使用任何方法
使用createDataSet()函数输入数据
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.1计算给定数据集的熵
def calcShannonEnt(dataSet):
"""
description: 计算该数据集的香农熵
:param dataSet:给定数据集
:return: 该数据集的香农熵
"""
numEntried = 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])/numEntried
# 计算熵
shannonEnt -=prob*log(prob,2)
return shannonEnt
按给定的特征划分数据集
def splitDataSet(dataSet,axis,value):
"""
description:对dataSet进行分类,如果dataSet的axis的值等于value,
将这一行除了axis列复制到新的数据集中
:param dataSet: 待分类的数据集
:param axis: 选取的某一个特征值
:param value: 返回的特征值
:return: axis列中为value特征值的数据集,当然除去了axis列
"""
# 不能修改原来的数据集,所以使用一个新的列表
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
# 除去axis列,复制剩下的所有特征
reducedFeat = featVec[:axis]
reducedFeat.extend(featVec[(axis+1):])
retDataSet.append(reducedFeat)
return retDataSet
根据公式2.2,来选择具有最大信息收益的特征,划分数据集
def chooseBestFeatureToSplit(dataSet):
"""
description:
:param dataSet: 待划分的数据集
:return: 能使划分的数据集拥有最佳信息增益的特征序号
"""
# 特征数量
numFeature = len(dataSet[0]) - 1
# 数据集的原始熵
baseEntropy = calcShannonEnt(dataSet)
# 最佳收益熵,最佳划分特征编号
bestIfoGain = 0.0; bestFeature = -1
for i in range(numFeature):
# 获得相应的特征下所有的数据
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 > bestIfoGain):
bestIfoGain = infogain
bestFeature = i
return bestFeature
训练算法:创建树结构
def createTree(dataSet,labels):
"""
desc:
:param dataSet: 输入的数据集
:param labels: 标签
:return: 创建好的一个决策树
"""
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
myTree = {bestFeatLabel:{}}
# 删除选中的这个特征标签
del(labels[bestFeat]) # python中一个可变对象作为参数传递时,是引用传递,可以进行全局修改
# 取出最优列的属性
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
# 每次调用createTree()不改变原来的labels内容,使用一个副本进行值传递
subLabels = labels[:]
# 在每个划分数据集上递归调用函数createTree()
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value),subLabels)
return myTree
这里13行代码处,如果使用完了使用了所有的特征属性,当前划分的数据集子集的类标签依然不是唯一的类别,使用多数表决来定义该叶子结点的类别。
def majorityCnt(classList):
"""
desc: 当数据集的所有属性处理完毕后,类标签依然不唯一,就获取叶子结点中占据多数的标签
:param classList: 数据集
:return: 返回占据多数的标签
"""
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]
测试算法:使用决策树进行分类
这个部分的函数我当时看了挺久的,不太能理解。比较难理解的地方我都加上了注释
def classify(inputTree,featLabels,testVec):
"""
desc:略
:param inputTree:决策树模型
:param featLabels:特征对应的标签名称
:param testVec:输入的测试向量,如[1,1],[1,0]
:return:
classlabel: 测试向量对应的结果值
"""
# 获得决策树根结点对应的key值
firstStr = inputTree.keys()[0]
# 通过根结点的key找到对应的value值
secondDict = inputTree[firstStr]
# 判断根结点在labels中的顺序,这样好从testVec中选择相应的顺序开始对照决策树分类
featIndex = featLabels.index(firstStr)
# 找到根结点对应labels的顺序,也就知道从testVec中第几位开始分类
key= testVec[featIndex]
value = secondDict[key]
# 判断分支是否结束,判断条件是:value是不是字典类型
if isinstance(value,dict):
classlabel = classify(value,featLabels,testVec)
else:
classlabel = value
return classlabel
决策树的存储
由于构建决策树是非常耗时的任务,即使是非常小的数据集,如前面这个样本数据也要花费好几秒时间,如果数据集很大则会耗费更多的时间,如果每次都用创建好的决策树解决分类问题,会节省时间,因此我们使用python模块中的pickle序列化对象,序列化对象可以保存在硬盘上,需要的时候可以读取出来。
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)
3.2使用决策树预测隐形眼镜类型
这个项目的数据集在《机器学习实战》书上的官网有,source code部分有一个三十多兆的包,学习这本书必须要把这个包下载下来。
将数据集加入到工作路径下,使用以下的代码加载数据
import decTree,treePlotter #decTree 是我整个决策树的模块,treePLotter是绘制决策树模型的模块
fr = open('C:\Python27\lenses.txt') #这是我python项目的工作路径
lenses = [inst.strip().split('\t') for inst in fr.readlines()]
lensesLabels = ['age','prescript','astigmatic','tearRate']
lensesTree = decTree.createTree(lenses,lensesLabels)
treePlotter.createPlot(lensesTree)
这是我最后建立的决策树模型可视化图
本篇读书笔记到此结束,第一次写博客,我觉得很新鲜。我会坚持把这本书的所有的章节读完。本篇博客献给成峰。