Machine Learning In Action 学习笔记之 决策树

决策树是一种常见的机器学习算法,也是很容易理解的。顾名思义,它是基于树结构进行决策的。

如下图所示:
在这里插入图片描述
正方形表示 判断模块 , 椭圆形表示 终止模块,一棵决策树包含一个根结点、若干个内部结点和若干个叶结点,叶节点表示决策结果。


1. 决策树的构造

构造决策树,需要解决的第一个问题就是,当前数据集上 哪个特征 在划分数据分类时其决定性作用(就是选择哪个特征进行划分)

创建分支的伪代码createBranch()如下所示:
在这里插入图片描述
这是一个 递归 函数。

决策数算法的流程如下:

  • 收集数据:可使用任何方法。
  • 准备数据:树构造算法只适用标称型数据,因此数据必须 离散化
  • 分析数据:可使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。
  • 训练算法:构造树的数据结构。
  • 测试算法:使用经验树计算错误率。
  • 使用算法:此步骤可以适用任何监督学习算法。

1.1 信息增益

为了进行数据的划分,即选择通过什么特征进行划分,采取的一个原则为:将无序的数据变得有序,即希望决策树的分支节点所包含的样本尽可能属于同一类别,即结点的 “纯度” 越来越高。

  • 信息:符号 xix_i的信息定义为 l(xi)=log2p(xi)l(x_i) = -log_2p(x_i),其中 p(xi)p(x_i) 是选择该分类的概率。
  • 信息熵:熵定义为信息的期望值,它是度量样本集合纯度最常用的一种指标,公式为 Ent(D)=i=1np(xi)log2p(xi)Ent(D) = -\sum_{i=1}^n p(x_i)log_2p(x_i)其中n为分类的数目。
  • 信息增益:当我们选择一个特征 aa 进行划分后,会产生 VV 个分支节点,其中第 vv 个分支结点包含 DD 中所有在属性 aa 上取值为 ava^v 的样本,记为 DvD^v。信息增益定义为:Gain(D,a)=Ent(D)v=1VDvDEnt(Dv) Gain(D, a) = Ent(D) - \sum_{v=1}^V\frac{|D^v|}{|D|}Ent(D^v),信息增益是越大越好。

举例如下:
在这里插入图片描述
首先我们创建一个数据集:

#创建数据集
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 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
    shannoEnt = 0.0
    for key in labelCounts:
        #计算每个类别所占样本集合的比例
        prob = float(labelCounts[key])/numEntries
        #求信息熵
        shannoEnt -= prob * log(prob, 2)
    return shannoEnt

最后求的结果就是 (25log225+35log235)=0.97095...-(\frac{2}{5}log_2\frac{2}{5} + \frac{3}{5}log_2\frac{3}{5}) = 0.97095...


1.2 划分数据集

#按照给定特征划分数据集
#参数:待划分的数据集、划分数据集的特征、特征的返回值
def splitDataSet(dataSet, axis, value):
    # 创建新的list对象
    retDataSet = []
    # 遍历数据集的每一行
    for featVec in dataSet:
        if featVec[axis] == value:
       	 	# 去掉axis特征
            reducedFeatVec = featVec[:axis]
            # 将符合条件的添加到返回的数据集
            # extend() 函数用于在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)。
            reducedFeatVec.extend(featVec[axis+1:])
            # 列表中嵌套列表
            retDataSet.append(reducedFeatVec)
    return retDataSet

在这里插入图片描述
选择最好的数据集划分方法, 代码如下:

#选择最好的数据集划分方式
def chooseBestFeatureToSplit(dataSet):
    #算出特征的数目
    numFeatures = len(dataSet[0]) - 1
    #print(numFeatures)
    #求得所有样本的信息熵
    baseEntropy = calcShannonEnt(dataSet)
    #定义最优信息增益和最优划分属性
    bestInfoGain = 0.0
    bestFeature = -1
    # 遍历所有特征
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        #print(uniqueVals)
        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 = infoGain
            bestFeature = i
    return bestFeature

在这里插入图片描述
表示第0个特征为最佳划分属性。


1.3 递归构建决策树

当我们选择好划分的属性之后,数据将被分支开来,被向下传递到树分支的下一个结点,在这个结点上,再次划分数据,这样就是 递归 的一个过程。

递归结束的条件为:

  • 程序处理完所有划分数据集的属性,这样类标签却不是唯一的,那么就采用“少数服从多数”原则进行类别的确定。

  • 每个分支下的所有实例都有相同的分类(这样就没必要在划分了),这样就得到了一个叶子结点,类别就是这个叶子结点上所有数据相同的类别。

“少数服从多数” 代码如下:

#统计classList中出现次数最多的元素(类标签)
def majorityCnt(classList):
    classCount = {}
    # 统计classList中每个元素出现的次数
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote] = 0
        classCount[vote] += 1
    # 根据字典的值降序排序
    # operator.itemgetter(1)获取对象的第1列的值
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    # 返回出现次数最多的元素
    return sortedClassCount[0][0]

创建树的函数代码如下:

#创建树
def createTree(dataSet, labels, featLabels):
    # 取分类标签
    classList = [example[-1] for example in dataSet]
    # 如果类别完全相同则停止继续划分
    if classList.count(classList[0]) == len(classList):
        #print(classList)
        return classList[0]
    # 遍历完所有特征时得不到唯一类别的分组,返回出现次数最多的类标签
    if len(dataSet[0]) == 1:
        return majorityCnt(classList)
    # 选择最优特征
    bestFeat = chooseBestFeatureToSplit(dataSet)
    bestFeatLabel = labels[bestFeat]
    featLabels.append(bestFeatLabel)
    # 根据标签生成树
    myTree = {bestFeatLabel:{}}
    # 删除已经使用的特征标签
    del(labels[bestFeat])
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        #递归构建决策树
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
    return myTree

运行后,生成一棵如下的树:
在这里插入图片描述


1.4 使用决策树进行分类

#使用决策树的分类函数
def classify(inputTree, featLabels, testVec):
    #获取根节点
    firstStr = next(iter(inputTree))
    #获取子树
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstStr)
    for key in secondDict.keys():
        if testVec[featIndex] == key:
            if type(secondDict[key]).__name__ == 'dict':
                #递归执行
                classLabel = classify(secondDict[key], featLabels, testVec)
            else:
                classLabel = secondDict[key]
    return classLabel

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章