机器学习【四】决策树

时间:2024-04-15 10:24:24

1、决策树简介

1.1 决策树概述

决策树算法是一种基于树形结构的分类算法,它能从给定的无序的训练样本中,提炼出树型的分类模型,树形中包含判断模块和终止模块。它是一种典型的分类算法,首先对数据进行处理,利用归纳算法生成可读的规则和决策树,然后使用决策对新数据进行分析。本质上决策树是通过一系列规则对数据进行分类的过程。决策树是一种监督学习,因此需要提前给出类别标签和数据集合。

决策树的原理:每个决策树都表述了一种树型结构,它由它的分支来对该类型的对象依靠属性进行分类。每个决策树可以依靠对源数据库的分割进行数据测试。这个过程可以递归式的对树进行修剪。 当不能再进行分割或一个单独的类可以被应用于某一分支时,递归过程就完成了。

常用的决策树算法有ID3,C4.5这两种算法本质上是一样的。ID3此算法的目的在于减少树的深度,但是忽略了叶子数目的研究。C4.5算法在ID3的基础上进行了改进,对于预测变量的缺值处理、剪枝技术、派生规则等方面作了较大改进,既适合于分类问题,又适合于回归问题。

流程图形式的决策树

 

1.2 ID3算法

基本的ID3 算法通过自顶向下构造决策树来进行学习。

构造过程是从“哪一个属性将在树的根结点被测试?”这个问题开始的。

为了回答这个问题,使用统计测试来确定每一个实例属性单独分类训练样例的能力。

(1)分类能力最好的属性被选作树的根结点的测试。

(2)然后为根结点属性的每个可能值产生一个分支,并把训练样例排列到适当的分支(也就是,样例的该属性值对应的分支)之下。

(3)然后重复整个过程,用每个分支结点关联的训练样例来选取在该点被测试的最佳属性。

这形成了对合格决策树的贪婪搜索(greedy search ),也就是算法从不回溯重新考虑以前的选择。

ID3 算法的核心问题是选取在树的每个结点要测试的属性。

我们希望选择的是最有助于分类实例的属性。那么衡量属性价值的一个好的定量标准是什么呢?这里将定义一个统计属性,称为“信息增益(information gain )”,用来衡量给定的属性区分训练样例的能力。ID3算法通过计算每个属性的信息增益,认为信息增益高的是好属性,每次划分选取信息增益最高的属性为划分标准,重复这个过程,直至生成一个能完美分类训练样例的决策树。

1.3 信息增益

 划分数据集的原则就是将无序的数据变得更加有序,组织数据的一种方式就是用信息论度量信息。

在划分数据集之前和之后信息所发生的变化称为信息增益。通过计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。

熵(entropy):度量随机变量的不确定性。
定义:假设随机变量X的可能取值有x1,x2, ... , xn,对于每一个可能的取值xi,其概率 P(X=xi) = p, ( i = 1,2, ... , n)
因此随机变量X的熵:
 \({H=-\sum_{n}^{i=1}p(x_{i})log_{2}p(x_{i})}\)

 推广到样本集合D,随机变量X是样本的类别,即,假设样本有K个类别,每个类别的概率是,其中|Ck|表示类别k的样本个数,|D|表示样本总数

则对于样本集合D来说熵(经验熵)为:

\(H(D)=-\sum_{k=1}^{K}\frac{\left | C_{_{k}} \right |}{\left | D \right |}log_{2}\frac{\left | C_{_{k}} \right |}{\left | D \right |}\)

 对信息增益的理解:对于待划分的数据集D,其 entroy(前)是一定的,但是划分之后的熵 entroy(后)是不定的,entroy(后)越小说明使用此特征划分得到的子集的不确定性越小(也就是纯度越高),因此 entroy(前) -  entroy(后)差异越大,说明使用当前特征划分数据集D的话,其纯度上升的更快。而我们在构建最优的决策树的时候总希望能更快速到达纯度更高的集合,这一点可以参考优化算法中的梯度下降算法,每一步沿着负梯度方法最小化损失函数的原因就是负梯度方向是函数值减小最快的方向。同理:在决策树构建的过程中我们总是希望集合往最快到达纯度更高的子集合方向发展,因此我们总是选择使得信息增益最大的特征来划分当前数据集D

#计算给定数据集的熵
from math import log

def calcShannonEnt (dataSet):
    numEntroiese = 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]) / numEntroiese
        shannonEnt -=prob * log(prob,2)
    return shannonEnt

2.构造决策树

2.1 划分数据集

构建决策树时第一个问题就是,当前哪个特征在划分数据分类时起决定性作用。

第一轮我们评估每个特征,划分出几个数据子集,这些数据子集会分布在第一个决策点的所有分支上。如果某个分支下的数据属于同一类型则已正确划分数据分类,无需进一步分割。如果数据子集内的数据不属于同一类型,则递归重复划分,直到所有具有相同类型的数据在同一个数据子集内。

#按照给定特征划分数据集
def splitDataSet (dataSet,axis,value):
    retDataSet = []
    for featVec in dataSet:
        #如果数据符合特征,就将其抽取出来
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet
#选择最好的数据集划分方式
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1
    baseEntory = calcShannonEnt(dataSet)
    bestInfoGain = 0.0
    bestFeaturs = -1
    #创建一个唯一的分类列表,遍历数据集中的所有特征
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        newEntroy = 0.0
        #遍历当前特征中的所有唯一属性值,对每个特征划分一次数据集
        # 计算每种划分方式的信息熵,在对所有唯一特征值得到的熵求和
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet,i,value)
            prob = len(subDataSet) / float(len(dataSet))
            newEntroy += prob * calcShannonEnt(subDataSet)
        infoGain = baseEntory - newEntroy
        #计算最好的信息增益
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeaturs = i
    return bestFeaturs  #返回最好特征划分的索引值

2.2 递归构建决策树

构建一棵决策树的基本原理:得到原始数据集,然后基于最好的属性值划分数据集,由于属性值可能多余两个,因此可能会存在大于两个分支的数据集划分。第一次划分之后,数据将向下传递到树分支的下一个节点,在该结点上继续划分。这里可以使用递归的方法处理数据集。

递归的条件:程序遍历完所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类。如果所有实例具有相同的分类,则得到一个叶子节点或者终止块。

#采用投票表决的方法决定该叶子节点的分类
from DecisionTree.entropy import chooseBestFeatureToSplit, splitDataSet


def majorityCnt(classList):
    classCount = {}
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote] = 0
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    return sortedClassCount[0][0]

#创建树
def createTree(dataSet,labels):
    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]) #从labels的list中删除该label
    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

递归函数的第一个停止条件是所有类标签完全相同,则直接返回该类标签。第二个停止条件是用完了所有的类标签,却还不能将数据集分为仅包含一类的分组,这里再挑选出一组出现频次最多的类别作为返回值。

创建树之后,字典变量myTree中存储了树的所有信息。将最好的特征存储在变量bestFeat中,得到列表包含的所有属性值。

3.测试和存储分类器

3.1 测试算法:使用决策树执行分类

在执行数据分类时,需要决策树以及用于构造树的标签向量。然后,程序比较测试数据与决策树上的数值,递归执行该过程直到进入叶子节点;最后将测试数据定义为叶子节点所属的类型。

#使用决策树的分类函数
def classify(inputTree,featLables,testVec):
    firstStr=list(inputTree.keys())[0] #获取树的第一个特征属性
    secondDict=inputTree[firstStr] #树的分支,子集合Dict
    #使用index方法查找当前列表中第一个匹配firstStr变量的元素
    featIndex=featLables.index(firstStr)
    for key in secondDict.keys():
        #递归遍历整棵树,如果到达叶子节点,则返回当前节点的分类标签
        if testVec[featIndex]==key:
            if type(secondDict[key]).__name__==\'dict\':
                classLabel=classify(secondDict[key],featLables,testVec)
            else:classLabel=secondDict[key]
    return classLabel

3.2 使用算法:决策树的存储

为了节省时间和成本,可以将已经创建的决策树(如字典形式)保存在硬盘上,需要使用的时候直接读取就好。 

#使用pickle模块存储决策树
def storeTree(inputTree,filename):
    import pickle
    fw=open(filename,\'wb\') #pickle默认方式是二进制,需要制定\'wb\'
    pickle.dump(inputTree,fw)
    fw.close()

def grabTree(filename):
    import pickle
    fr=open(filename,\'rb\')#需要制定\'rb\',以byte形式读取
    return pickle.load(fr)

4.小结

决策树分类器就像带有终止块的流程图,终止块表示分类结果。开始处理数据集时,我们首先需要测量集合中数据的不一致性,也就是熵,然后寻找最优方案划分数据集,直到数据集中的所有数据属于同一分类。ID3算法可以用于划分标称型数据集。构建决策树时,我们通常采用递归的方法将数据集转化为决策树。一般我们并不构造新的数据结构,而是使Python语言内嵌的 数据结构字典存储树节点信息。对于数据集过度匹配问题,可以采用剪枝。