您的位置:首页 > 其它

机器学习实战-决策树

2016-08-16 23:28 393 查看

机器学习实战-决策树

一、决策树简介

k-近邻算法可以完成很多分类任务,但是它最大的缺点就是无法给出数据的内在含义,决策树的主要优势就在于数据形式非常容易理解。

下面是决策树的一般形式:



决策树可以使用不熟悉的数据集合, 并从中提取出一系列规则,机器学习算法最终将使用这些机器从数据集中创造的规则。

决策树:

优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据。

缺点:可能会产生过度匹配问题。

适用数据类型:数值型和标称型。

二、构造决策树

在构造决策树时,我们需要解决的第一个问题就是,当前数据集上哪个特征在划分数据分类时起决定性作用。为了找到决定性的特征,划分出最好的结果,我们必须评估每个特征。

创建分支的伪代码函数createBranch如下所示:

检测数据集中的每个子项是否属于同一分类:
If so return 类标签;
Else
寻找划分数据集的最好特征
划分数据集
创建分支节点
for每个划分的子集
调用函数createBranch并增加返回结果到分支节点中
return 分支节点


上面的伪代码createBranch是一个递归函数,在倒数第二行直接调用了它自己。后面我们

将把上面的伪代码转换为python代码,这里我们需要进一步了解算法是如何划分数据集的.

决策树的一般流程

收集数据:可以使用任何方法。

准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化。

分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。

训练算法:构造树的数据结构。

测试算法:使用经验树计算错误率。

使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据

的内在含义。

信息増益

划分数据集的大原则是:将无序的数据变得更加有序。我们可以使用多种方法划分数据集,

但是每种方法都有各自的优缺点。组织杂乱无章数据的一种方法就是使用信息论度量信息, 信息论是量化处理信息的分支科学。我们可以在划分数据之前使用信息论量化度量信息的内容。

在划分数据集之前之后信息发生的变化称为信息增益,知道如何计算信息增益,我们就可以计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。

熵定义为信息的期望值,在明晰这个概念之前,我们必须知道信息的定义。如果待分类的事

务可能划分在多个分类之中,则符号Xi的信息定义为

,其中p(Xi)是选择该分类的概率。

为了计算熵,我们需要计算所有类别所有可能值包含的信息期望值,通过下面的公式得到:



给出计算熵的python代码:

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


首先,计算数据集中实例的总数。我们也可以在需要时再计算这个值,但是由于代码中多次用到这个值,为了提高代码效率,我们显式地声明一个变量保存实例总数。然后,创建一个数据字典,它的键值是最后一列的数值 。如果当前键值不存在,则扩展字典并将当前键值加入字典。每个键值都记录了当前类别出现的次数。最后,使用所有类标签的发生频率计算类别出现的概率。我们将用这个概率计算香农熵,统计所有类标签发生的次数。下面我们看看如何使用熵划分数据集。

我们可以用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


熵越高,则混合的数据也越多,我们可以在数据集中添加更多的分类,观察熵是如何变化的。

还有其他方式度量集合的无序程度,比如基尼不纯度方法,本文不再阐述。

分类算法除了需要测量信息熵,还需要划分数据集,度量划分数据集的熵,以便判断当前是否正确地划分了数据集。我们将对每个特征划分数据集的结果计算一次信息熵,然后判断按照哪个特征划分数据集是最好的划分方式。

划分数据集的代码如下:

def splitDataSet(dataSet, axis, value):
'''
按照给定特征划分数据集
:param dataSet:待划分的数据集
:param axis:划分数据集的特征
:param value:需要返回的特征的值
:return:
'''
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis + 1:])
retDataSet.append(reducedFeatVec)
return retDataSet


可以这样理解这段代码:当我们按照某个特征划分数据集时,就需要将所有符合要求的元素抽取出来。

extend方法和append方法的区别:



熵计算将会告诉我们如何划分数据是最好的划分方式!

选择最好的划分方式的程序代码:

def chooseBestFeatureToSplit(dataSet):

'''
选择最好的数据集划分方式,实现特征选取,划分数据集
:param 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 = bestInfoGain
bestFeature = i
return bestFeature


该函数实现了特征选取、划分数据集、计算得出最好的划分数据集的特征。

在函数中调用的数据需要满足一定的要求:第一个要求是,数据必须是一种由列表元素组成的列表,而且所有的列表元素都要具有相同的数据长度;

第二个要求是,数据的最后一列或者每个实例的最后一个元素是当前实例的类别标签。数据集一旦满足上述要求,我们就可以在函数的第一行判定当前数据集包含多少特征属性。

三、学习构造决策树

递归结束的条件是:程序遍历完所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类。

def majorityCnt(classList):
'''投票表决,返回出现次数最多的分类名称'''
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]


上面的代码投票表决代码非常类似,该函数使用分类名称的列表,然后创建键值为classList中唯一值的数据字典,字典对象存储了classList中每个类标签出现的频率,最后利用operator操作键值排序字典,并返回出现次数最多的分类名称。

def createTree(dataSet, labels):
'''
创建树的函数代码
: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 = {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


使用两个输入参数:数据集和标签列表。

标签列表包含了数据集中所有特征的标签,算法本身并不需要这个变量,但是为了给出数据明确的含义,我们将它作为一个输入参数提供。此外,前面提到的对数据集的要求这里依然需要满足。上述代码首先创建了名为classList的列表变量,其中包含了数据集的所有类标签。 递归函数的第一个停止条件是所有的类标签完全相同,则直接返回该类标签。递归函数的第二个停止条件是使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。由于第二个条件无法简单地返回唯一的类标签,这里使用程序majorityCnt函数挑选出现次数最多的类别作为返回值。

字典变量myTree存储了树的所有信息,这对于其后绘制树形图非常重要。当前数据集选取的最好特征存储在变量bestFeat中,得到列表包含的所有属性值。

最后代码遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数createTree(),得到的返回值将被插入到字典变量myTree中,因此函数终止执行时,宇典中将会嵌套很多代表叶子节点信息的字典数据。在解释这个嵌套数据之前,我们先看一下循环的第一行subLabels = labels[:],这行代码复制了类标签,并将其存储在新列表变量subLabels中。之所以这样做,是因为在python语言中函数参数是列表类型时,参数是按照引用方式传递的。为了保证每次调用函数createTree()时不改变原始列表的内容,使用新变量subLabels代替原始列表。

四、小结

决策树分类器就像带有终止块的流程图,终止块表示分类结果。开始处理数据集时,我们首先需要测量集合中数据的不一致性,也就是熵,然后寻找最优方案划分数据集,直到数据集中的所有数据属于同一分类。ID3算法可以用于划分标称型数据集。构建决策树时,我们通常采用递归的方法将数据集转化为决策树。一般我们并不构造新的数据结构,而是使用python语言内嵌的数据结构字典存储树节点信息。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: