本文根据最近学习机器学习书籍 网络文章的情况,特将一些学习思路做了归纳整理,详情如下.如有不当之处,请各位大拿多多指点,在此谢过.
一、决策树(decision tree)概述
1、决策树概念
决策树(decision tree)是一个树结构(可以是二叉树或非二叉树)。其每个非叶节点表示一个特征属性上的测试,每个分支代表这个特征属性在某个值域上的输出,而每个叶节点存放一个类别。使用决策树进行决策的过程就是从根节点开始,测试待分类项中相应的特征属性,并按照其值选择输出分支,直到到达叶子节点,将叶子节点存放的类别作为决策结果。
2 工作原理
在构造决策树时,需要解决的第一个问题就是,当前数据集上哪个特征在划分数据分类时起到来决定性的作用。为了找到决定性的特征,我们需要对每个特征都要进行评估.完成测试后,原始数据就被划分为几个数据子集.这些数据子集会分布在第一个决策点的所有分支上.若某一分支下的数据属于同一类型,则当前无需阅读的垃圾邮件(以系统自动筛选垃圾邮件为例)已经被正确地划分数据分类,没必要再对数据集进行分类.否则,则需要重复划分数据子集的过程.这里划分子集的算法和划分原始数据集的方法相同,直至所有具有相同类型的数据都进入一个数据子集内.构造决策树伪代码函数createBranch()如下:
检测数据集中的每个子项是否属于同一分类:
IF so return 类标签
Else
寻找划分数据集的最好特征
划分数据集
创建分支节点
for 每个划分的子集
调用函数createBranch()并增加返回结果到分支节点中
return 分支节点
一旦我们构造了一个决策树模型,以它为基础来进行分类将是非常容易的。具体做法是,从根节点开始,对实例的某一特征进行测试,根据测试结构将实例分配到其子节点(也就是选择适当的分支);沿着该分支可能达到叶子节点或者到达另一个内部节点时,那么就使用新的测试条件递归执行下去,直到抵达一个叶子节点。当到达叶子节点时,我们便得到了最终的分类结果。下面介绍一个小例子。
通俗来说,决策树分类的思想类似于找对象。现想象一个女孩的母亲要给这个女孩介绍男朋友,于是有了下面的对话:
女儿:多大年纪了?
母亲:26。
女儿:长的帅不帅?
母亲:挺帅的。
女儿:收入高不?
母亲:不算很高,中等情况。
女儿:是公务员不?
母亲:是,在税务局上班呢。
女儿:那好,我去见见。
这个女孩的决策过程就是典型的分类树决策。相当于通过年龄、长相、收入和是否公务员对将男人分为两个类别:见和不见。假设这个女孩对男人的要求是:30岁以下、长相中等以上并且是高收入者或中等以上收入的公务员,那么这个可以用下图表示女孩的决策逻辑:
上图完整表达了这个女孩决定是否见一个约会对象的策略,其中绿色节点表示判断条件,橙色节点表示决策结果,箭头表示在一个判断条件在不同情况下的决策路径,图中红色箭头表示了上面例子中女孩的决策过程。
这幅图基本可以算是一颗决策树,说它“基本可以算”是因为图中的判定条件没有量化,如收入高中低等等,还不能算是严格意义上的决策树,如果将所有条件量化,则就变成真正的决策树了。
3、决策树的相关特性
优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据。
缺点:可能会产生过度匹配问题。
使用数据类型: 数值型和标称型。
4、 一般流程
(1) 收集数据: 可以使用任何方法.
(2) 准备数据: 构造算法只适用于标称型数据,因此数值型数据必须离散化.
(3) 分析数据: 可以使用任何方法,构造树完成之后,应该检查图形是否符合预期.
(4) 训练算法: 构造树的数据结构.
(5) 测试算法: 使用经验树计算错误率.
(6) 使用算法: 此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义.
二 决策树场景
假设,现在有一个叫做 "十五个问题" 的游戏,游戏的规则很简单:参与游戏的一方在脑海中想某个事物,其他参与者向他提问,只允许提 15个问题,问题的答案也只能用对或错回答。问问题的人通过推断分解,逐步缩小待猜测事物的范围,最后得到游戏的答案。决策树的工作原理与15个问题类似,用户输入一系列数据后给出游戏答案。
下图给出了一个假想的邮件分类系统,它首先检测发送邮件域名.如果地址为myEmployer.com,则将其放在"无聊时需要阅读的邮件"中。否则,则需要检查邮件内容中是否包含单词 曲棍球 ,若包含则将邮件归入"需要及时处理的朋友邮件",否则则归类到"无需阅读的垃圾邮件"。
决策树一个很重要的任务就是为了理解数据中所蕴含的知识信息(这与K-近邻算法无法给出数据的内在含义有着显著不同),因此决策树可以使用不熟悉的数据集合,并从中提取出一系列规则,这些机器根据数据集创建规则的过程,就是机器学习的过程。
三 决策树项目案例一 对海洋生物进行鱼和非鱼判断
1 项目情况
下表中的数据包含5个海洋生物,特征: 不浮出水面是否可以生存和是否有脚蹼.现将动物划分为两类: 鱼和非鱼.如果想依据给出的特征选出一个来划分数据,就涉及到要将划分数据的依据进行量化后才可以判断出来.
我们先构造进行数据输入的createDataSet()函数和计算给定数据集的香农熵函数calcShannonEnt()
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
#信息增益
#计算给定数据的香农熵
def calcShannonEnt(dataSet): #the the number of unique elements and their occurance
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.00000
for key in labelCounts:
prob = float(labelCounts[key]) /numEntries
shannonEnt -= prob * log(prob,2) #log base 2
return shannonEnt
分别执行
myDat,labels=createDataSet()
myDat
得到
calcShannonEnt(myDat)
得到
熵越高,则混合的数据越多,我们可用在数据集中添加更多的分类,观察熵是如何变化的.
按照给定特征划分数据集,将指定特征的特征值等于 value 的行剩下列作为子数据集。
def splitDataSet(dataSet, index, value):
"""splitDataSet(通过遍历dataSet数据集,求出index对应的colnum列的值为value的行)
就是依据index列进行分类,如果index列的数据等于 value的时候,就要将 index 划分到我们创建的新的数据集中
Args:
dataSet 数据集 待划分的数据集
index 表示每一行的index列 划分数据集的特征
value 表示index列对应的value值 需要返回的特征的值。
Returns:
index列为value的数据集【该数据集需要排除index列】
"""
retDataSet = []
for featVec in dataSet:
# index列为value的数据集【该数据集需要排除index列】
# 判断index列的值是否为value
if featVec[index] == value:
# chop out index used for splitting
# [:index]表示前index行,即若 index 为2,就是取 featVec 的前 index 行
reducedFeatVec = featVec[:index]
'''
请百度查询一下: extend和append的区别
list.append(object) 向列表中添加一个对象object
list.extend(sequence) 把一个序列seq的内容添加到列表中
1、使用append的时候,是将new_media看作一个对象,整体打包添加到music_media对象中。
2、使用extend的时候,是将new_media看作一个序列,将这个序列和music_media序列合并,并放在其后面。
result = []
result.extend([1,2,3])
print result
result.append([4,5,6])
print result
result.extend([7,8,9])
print result
结果:
[1, 2, 3]
[1, 2, 3, [4, 5, 6]]
[1, 2, 3, [4, 5, 6], 7, 8, 9]
'''
reducedFeatVec.extend(featVec[index+1:])
# [index+1:]表示从跳过 index 的 index+1行,取接下来的数据
# 收集结果值 index列为value的行【该行需要排除index列】
retDataSet.append(reducedFeatVec)
return retDataSet
选择最好的数据集划分方式:
def chooseBestFeatureToSplit(dataSet):
"""chooseBestFeatureToSplit(选择最好的特征)
Args:
dataSet 数据集
Returns:
bestFeature 最优的特征列
"""
# 求第一行有多少列的 Feature, 最后一列是label列嘛
numFeatures = len(dataSet[0]) - 1
# 数据集的原始信息熵
baseEntropy = calcShannonEnt(dataSet)
# 最优的信息增益值, 和最优的Featurn编号
bestInfoGain, bestFeature = 0.0, -1
# iterate over all the features
for i in range(numFeatures):
# create a list of all the examples of this feature
# 获取对应的feature下的所有数据
featList = [example[i] for example in dataSet]
# get a set of unique values
# 获取剔重后的集合,使用set对list数据进行去重
uniqueVals = set(featList)
# 创建一个临时的信息熵
newEntropy = 0.0
# 遍历某一列的value集合,计算该列的信息熵
# 遍历当前特征中的所有唯一属性值,对每个唯一属性值划分一次数据集,计算数据集的新熵值,并对所有唯一特征值得到的熵求和。
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value)
# 计算概率
prob = len(subDataSet)/float(len(dataSet))
# 计算信息熵
newEntropy += prob * calcShannonEnt(subDataSet)
# gain[信息增益]: 划分数据集前后的信息变化, 获取信息熵最大的值
# 信息增益是熵的减少或者是数据无序度的减少。最后,比较所有特征中的信息增益,返回最好特征划分的索引值。
infoGain = baseEntropy - newEntropy
print 'infoGain=', infoGain, 'bestFeature=', i, baseEntropy, newEntropy
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
训练算法:构造树的数据结构
创建树的函数
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet]
# 如果数据集的最后一列的第一个值出现的次数=整个集合的数量,也就说只有一个类别,就只直接返回结果就行
# 第一个停止条件:所有的类标签完全相同,则直接返回该类标签。
# count() 函数是统计括号中的值在list中出现的次数
if classList.count(classList[0]) == len(classList):
return classList[0]
# 如果数据集只有1列,那么最初出现label次数最多的一类,作为结果
# 第二个停止条件:使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。
if len(dataSet[0]) == 1:
return majorityCnt(classList)
# 选择最优的列,得到最优列对应的label含义
bestFeat = chooseBestFeatureToSplit(dataSet)
# 获取label的名称
bestFeatLabel = labels[bestFeat]
# 初始化myTree
myTree = {bestFeatLabel: {}}
# 注:labels列表是可变对象,在PYTHON函数中作为参数时传址引用,能够被全局修改
# 所以这行代码导致函数外的同名变量被删除了元素,造成例句无法执行,提示'no surfacing' is not in list
del(labels[bestFeat])
# 取出最优列,然后它的branch做分类
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
# 求出剩余的标签label
subLabels = labels[:]
# 遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数createTree()
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
# print 'myTree', value, myTree
return myTree
测试算法:使用决策树执行分类
def classify(inputTree, featLabels, testVec):
"""classify(给输入的节点,进行分类)
Args:
inputTree 决策树模型
featLabels Feature标签对应的名称
testVec 测试输入的数据
Returns:
classLabel 分类的结果值,需要映射label才能知道名称
"""
# 获取tree的根节点对于的key值
firstStr = inputTree.keys()[0]
# 通过key得到根节点对应的value
secondDict = inputTree[firstStr]
# 判断根节点名称获取根节点在label中的先后顺序,这样就知道输入的testVec怎么开始对照树来做分类
featIndex = featLabels.index(firstStr)
# 测试数据,找到根节点对应的label位置,也就知道从输入的数据的第几位来开始分类
key = testVec[featIndex]
valueOfFeat = secondDict[key]
print '+++', firstStr, 'xxx', secondDict, '---', key, '>>>', valueOfFeat
# 判断分枝是否结束: 判断valueOfFeat是否是dict类型
if isinstance(valueOfFeat, dict):
classLabel = classify(valueOfFeat, featLabels, testVec)
else:
classLabel = valueOfFeat
return classLabel
三 项目案例2: 使用决策树预测隐形眼镜类型
项目概述
隐形眼镜类型包括硬材质、软材质以及不适合佩戴隐形眼镜。我们需要使用决策树预测患者需要佩戴的隐形眼镜类型。
开发流程
(1)收集数据: 提供的文本文件。
(2)解析数据: 解析 tab 键分隔的数据行
(3)分析数据: 快速检查数据,确保正确地解析数据内容,使用 createPlot() 函数绘制最终的树形图。
(4)训练算法: 使用 createTree() 函数。
(5)测试算法: 编写测试函数验证决策树可以正确分类给定的数据实例。
(6)使用算法: 存储树的数据结构,以便下次使用时无需重新构造树。
收集数据:提供的文本文件
文本文件数据格式如下:
young myope no reduced no lenses
pre myope no reduced no lenses
presbyopic myope no reduced no lenses
解析数据:解析 tab 键分隔的数据行
lecses = [inst.strip().split('\t') for inst in fr.readlines()]
lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']
分析数据:快速检查数据,确保正确地解析数据内容,使用 createPlot() 函数绘制最终的树形图。
treePlotter.createPlot(lensesTree)
>>> lensesTree = trees.createTree(lenses, lensesLabels)
>>> lensesTree
{'tearRate': {'reduced': 'no lenses', 'normal': {'astigmatic':{'yes':
{'prescript':{'hyper':{'age':{'pre':'no lenses', 'presbyopic':
'no lenses', 'young':'hard'}}, 'myope':'hard'}}, 'no':{'age':{'pre':
'soft', 'presbyopic':{'prescript': {'hyper':'soft', 'myope':
'no lenses'}}, 'young':'soft'}}}}}
其实决策树跟带终止块的流程图类似,所以这里的终止块就是分类结果.当我们进行数据处理时,首先要对集合中数据的不一致性进行测量评估,也就是计算香农熵,下一步才可以寻找最有方案划分数据,最终实现所有具有相同类型的数据都划分到同一个数据子集里面.在构建数据树时,我们一般采用递归方把数据集转化为决策树.多数情况下,我们不构造新的数据结构,而是采用Python语言内嵌的数据结构字典存储树节点信息.每一步选择信息增益最大的特征作为决策块,最终来完成决策树的生成.
Matplotlib的注解功能,可以让将存储的树结构转化为容易理解的图形.隐形眼镜的例子说明决策树可能会产生过多的数据集划分,结果导致过度匹配数据集的问题.当然可以通过裁剪决策树,合并相邻的不能产生信息增益的叶节点,来解决这个问题(过度匹配).
关于决策树的构造算法,这里本文只是用了ID3算法,当然还有C4.5和CART算法.对于决策树的完整工作过程而言,包括三部分:
1 特征选择;
2 生成决策树;
3 剪枝部分
而除去ID3算法,其他两个算法都有剪枝部分过程.所以这也迎合来隐形眼镜过拟合的问题.
关于决策树部分,笔者先整理到这里,后续有机会会针对C4.5和CART算法做些归纳整理.有不足之处,请各位同仁多多指导.