《机器学习实战》总结:ID3决策树

来源:互联网 发布:科比布莱恩特生涯数据 编辑:程序博客网 时间:2024/05/29 15:36

熵(Entropy)

1、定义

熵是对随机变量不确定程度的度量。设 X 是一个离散型随机变量,其可取的值有 x1,x2,...xn,且其概率分布函数(cdf)为 pi=P(X=xi)
在《信息论基础 第2版》一书中,对熵的定义如下:

一个离散型随机变量X的熵Ent(X)定义为

Ent(X)=i=1npilog2(pi)
其中熵的单位为比特。

根据熵的定义,熵表示在一个数据集合中,需要用多少的比特位来表示数据所属分类的总数。比如,抛一个硬币有两种可能:正面朝上和反面朝上。则在一个记录了多次抛硬币的结果的数据集合中的所有数据属于两个类:正面朝上类和反面朝上类。这两个类只需要一个比特位就可以表示,则 H()=1, 而根据公式进行计算也可以知道

H()=[12log2(12)+12log2(12)]=1
由此我们可以理解熵为什么可以表示一个数据集合的不确定程度(即混乱程度):熵越大,表示数据集合中所有数据所属的分类越多,这样,当我们从一个数据集合中随机选择一个数据出来,它属于哪个类的不确定性就越大;反之,当一个数据集合的熵很小甚至等于 0 时,我们可以认为这个集合中的所有数据都属于同一个类,这样随机选择一个数,这个数肯定就属于那个类了。因此熵越小的数据集合,其混乱程度就越低。

2、用python计算给定数据集合的熵

在分类算法中,数据的形式一般以矩阵表示,如下表格为海洋生物数据:

ID 不浮出水面是否可以生存 是否有脚蹼 属于鱼类 1 是 是 是 2 是 是 是 3 是 否 否 4 否 是 否 5 否 是 否

其中 属于鱼类 是该数据集中的类标签。显然在这个数据集合中有:

p1=P(X=yes)=25
p2=P(X=no)=35
则该数据集合的熵为:
Ent(X)=[25log2(25)+35log2(35)]0.97

用python计算给定数据集合的熵代码如下:

import numpy as npimport pandas as pdimport matplotlib as mplimport math% matplotlib inline
def CalEnt(labels):    # calculate the frequencies of each class    frequencies = {}    for label in labels:        frequencies[label] = frequencies.get(label, 0) + 1    frequencies = np.array(list(frequencies.values())) / len(labels)    # calculate the entropy of the dataset corresponding to the class labels    Ent = sum([-(fre * math.log(fre, 2)) for fre in frequencies])    return Entlabels = ['yes', 'yes', 'no', 'no', 'no']print('Entropy:')print(CalEnt(labels))
Entropy:0.970950594455

让我们尝试增加一个类别”maybe”的数据,看看熵是如何变化的:

labels2 = ['maybe', 'yes', 'yes', 'no', 'no', 'no']print('Entropy2:')print(CalEnt(labels2))
Entropy2:1.45914791703

信息增益(information gain)

1、定义

要了解信息增益,我们先用上面的海洋生物数据作为例子来说明为什么将数据集进行划分后可以减少熵:
假设将海洋生物的数据按照不浮出水面是否可以生存划分成两个数据子集:一个是“是”的子集,一个是“否”的子集,则划分后得到如下表格:

ID 不浮出水面是否可以生存 是否有脚蹼 属于鱼类 1 是 是 是 2 是 是 是 3 是 否 否 ID 不浮出水面是否可以生存 是否有脚蹼 属于鱼类 4 否 是 否 5 否 是 否

假设第一个子集为S1,第二个子集为S2,则可以分别计算这两个子集的熵:

labels_S1 = ['yes', 'yes', 'no']labels_S2 = ['no', 'no']print("S1's entropy: ", CalEnt(labels_S1))print("S1's entropy: ", CalEnt(labels_S2))
S1's entropy:  0.918295834054S1's entropy:  0.0

为了比较划分数据集后的熵与原来的熵,我们计算划分后的熵的期望值

E(T;S)=|S1||S|Ent(S1)+|S2||S|Ent(S2)
上式中,T 表示划分的特征向量,在这里即不浮出水面是否可以生存|S| 表示S集合中元素的总个数。
根据上面的公式,我们可以计算出划分数据集后熵的期望值大约为 0.55
显然,划分数据集后熵明显减少了,而熵减少的量就是信息增益:
ImformationGain(T)=Ent(S)E(T;S)=0.970.55=0.42
在这个层面上来说,信息增益就是衡量划分数据集后数据混乱度的减少程度的量。

上述划分的特征向量 (T)不浮出水面是否可以生存,现在我们用另一个特征向量 T1 是否有脚蹼来划分数据集,则可得到如下表格:

ID 不浮出水面是否可以生存 是否有脚蹼 属于鱼类 1 是 是 是 2 是 是 是 4 否 是 否 5 否 是 否 ID 不浮出水面是否可以生存 是否有脚蹼 属于鱼类 3 是 否 否

计算该类划分的熵期望值可以得到:

labels_S1 = ['yes', 'yes', 'no', 'no']labels_S2 = ['no']print('Expected entropy: ', len(labels_S1) / len(labels) * CalEnt(labels_S1) + len(labels_S2) / len(labels) * CalEnt(labels_S2))
Expected entropy:  0.8

此时信息增益为 0.970.8=0.17,显然 0.17<0.42,说明按照特征向量不浮出水面是否可以生存来划分数据集能够获得更大的信息增益,也就是使数据集的混乱程度减少更多。这个方法对于构造决策树至关重要,事实上,决策树的构造过程就是不断根据特征向量划分数据集、把数据分成不同的类的过程,而每次划分数据集,都要选取划分后信息增益最大的特征向量进行划分。

2、用python选取给定数据集中信息增益最大的特征向量

对于上面的海洋生物数据集,假设用 0 表示“否”,1 表示“是”,则上述数据集可以表示为:

第0列 第1列 1 1 1 1 1 0 0 1 0 1

其中,第0列表示不浮出水面是否可以生存列,第1列表示是否有脚蹼列。

数据集对应的python列表形式为:dataSet = [[1, 1], [1, 1], [1, 0], [0, 1], [0, 1]],

而数据集中各个特征向量(即各行)对应的标签数组为:labels = [‘yes’, ‘yes’, ‘no’, ‘no’, ‘no’],

对于给定的数据集dataSet(一般是二维数组),选取信息增益最大的特征向量的python代码如下:

# 选择划分后得到信息增益最大的特征向量,返回这个特征向量和对应的最大信息增益def chooseBestFeatureToSplit(dataSet, labels):    numRows = len(dataSet)    numFeatures = len(dataSet[0])    originalEnt = CalEnt(labels)    maxInfoGain = 0.0    bestFeature = 0    for i in range(numFeatures):        subDataSets, newLabels, values = splitDataSet(i, dataSet, labels)        newEnt = 0.0        for subLabels in newLabels:            newEnt += float(len(subLabels)) / float(len(labels)) * CalEnt(subLabels)        infoGain = originalEnt - newEnt        if infoGain > maxInfoGain:            maxInfoGain = infoGain            bestFeature = i    return bestFeature, maxInfoGain# 根据选择到的特征向量坐标(axis的值)对数据集进行划分,返回划分后的所有数据子集,每个子集对应的标签列表,# 以及每个数据子集对应的特征向量的值def splitDataSet(axis, dataSet, labels):    axis = int(axis)    newLabels = []    numRows = len(dataSet)    featureValues = []    subDataSets = []    subLabels = []    values = []    for i in range(numRows):        featureValues.append(dataSet[i][axis])    featureValues = set(featureValues)    for value in featureValues:        subDataSet, subLabel = splitByValue(axis, value, dataSet, labels)        subDataSets.append(subDataSet)        subLabels.append(subLabel)        values.append(value)    return subDataSets, subLabels, values# 根据特征向量的某个值对数据集进行划分,把所有含有这个特征向量的值为value的记录挑选出来形成数据子集# 返回数据子集和对应的标签列表def splitByValue(axis, value, dataSet, labels):    subDataSet = []    subLabel = []    for i in range(len(dataSet)):        if dataSet[i][axis] == value:            newFeature = dataSet[i]            subDataSet.append(newFeature)            subLabel.append(labels[i])    return subDataSet, subLabeldataSet = [[1, 1], [1, 1], [1, 0], [0, 1], [0, 1]]labels = ['yes', 'yes', 'no', 'no', 'no']bestFeature, maxInfoGain = chooseBestFeatureToSplit(dataSet, labels)subDataSets, subLabels, values = splitDataSet(bestFeature, dataSet, labels)print('subsets: ', subDataSets)print('subLabels: ', subLabels)print('best feature: ', bestFeature)print('max information gain: ', maxInfoGain)
subsets:  [[[0, 1], [0, 1]], [[1, 1], [1, 1], [1, 0]]]subLabels:  [['no', 'no'], ['yes', 'yes', 'no']]best feature:  0max information gain:  0.419973094022

如上图程序运行结果所示,信息增益最大值为 0.42,对应的特征向量坐标为 0,即不浮出水面是否可以生存这个特征向量,与上面的分析结果一样。清楚了怎样划分数据集,使子集的混乱程度更小,我们就可以开始构建决策树了。

ID3 决策树

要了解决策树,首先让我们来看看python中可以怎样表示一棵树。在C语言中,得到一棵树只需要知道这棵树的根节点指针,而在python中,可以利用字典这个数据结构来表示一棵树,其中字典中的keys表示父结点,而对应的values表示子结点。
首先我们来创建最简单的ID3决策树,前面已经说过,创建决策树的过程就是不断把数据集划分成混合度更小直至熵接近0的子集的过程,当子集中的熵接近于0时,我们就可以认为该子集中的所有数据属于同一个类,此时分类就结束了,当前的数据集就成为决策树的叶子结点。我们可以认为,在决策树中,各个结点就是一个数据集,父结点是待划分的数据集,子结点是把父结点划分后形成的数据子集,决策树的构建过程就是把根结点数据集一直划分到叶子结点数据集的过程,这个过程是递归定义的。

# 创建决策树def buildDecisionTree(dataSet, labels, feature_dict, labels_dict):    # 当数据集中所有的标签都相同时,说明所有数据属于同一类,直接返回对应的标签结果    if labels.count(labels[0]) == len(labels):        return labels_dict[labels[0]]    # 否则,每次选择信息增益最大的特征向量继续划分数据集,递归建立决策树    bestFeature, maxInfoGame = chooseBestFeatureToSplit(dataSet, labels)    subDataSets, subLabels, values = splitDataSet(bestFeature, dataSet, labels)    subTree = {}    subTree[feature_dict[bestFeature]] = {}    for i in range(len(values)):        subTree[feature_dict[bestFeature]][values[i]] = buildDecisionTree(subDataSets[i], subLabels[i], feature_dict, labels_dict)    return subTreedataSet = [[1, 1], [1, 1], [1, 0], [0, 1], [0, 1]]labels = ['yes', 'yes', 'no', 'no', 'no']feature_dict = { 0 : "No surfacing", 1 : "Flippers" }labels_dict = { 'yes' : "Fish", 'no' : "Not fish" }decisionTree = buildDecisionTree(dataSet, labels, feature_dict, labels_dict)decisionTree
{'No surfacing': {0: 'Not fish', 1: {'Flippers': {0: 'Not fish', 1: 'Fish'}}}}

由结果可知,我们成功地根据样本数据集训练出了一棵决策树,而我们的目的是利用这个决策树对未知的数据进行分类,因此最后还需要一个分类函数:

def classify(decisionTree, features, featuresName):    firstClass = list(decisionTree.keys())[0]    nextDict = decisionTree[firstClass]    firstIndex = featuresName.index(firstClass)    nextClass = nextDict[features[firstIndex]]    if type(nextClass).__name__ == "dict":        return classify(nextClass, features, featuresName)    else:        return nextClasslabel1 = classify(decisionTree, [1, 1], ["No surfacing", "Flippers"])label2 = classify(decisionTree, [1, 0], ["No surfacing", "Flippers"])print("The first animal is ", label1)print("The second animal is ", label2)
The first animal is  FishThe second animal is  Not fish

至此,我们完成了一棵ID3决策树的创建和使用。ID3决策树的缺点是可能造成过度匹配,因此我们在实际应用中需要适当裁剪决策树,去掉一些不必要的叶子节点。
以上是我个人学习了《机器学习实战》第3章关于ID3决策树的总结,并根据自己的理解独立写了一遍代码,感觉收获很大,对于ID3决策树的原理基本是理解了,不足之处请指正。

参考文献

1、《机器学习实战》 Peter Harrington 著,李悦 李鹏 曲亚东 王斌 译,人民邮电出版社
2、《信息论基础 第2版》Thomas M.Cover Joy A.Thomas 著,阮吉寿 张华 译,机械工业出版社

原创粉丝点击