机器学习实战笔记:决策树

    决策树的每一次判定都是对某一属性的测试,每个测试的结果或是导出最终结论,或是导出进一步的判定问题。决策的最终结论则对应最终的判定结果。

    一般的,一棵决策树包含一个根结点,若干内部结点和若干个叶结点:

  • 每个叶结点对应于一个决策结果,存放一个类别;
  • 每个非叶结点表示一个特征属性测试;
  • 每个分支代表这个特征属性在某个域上的输出;
  • 每个结点包含的样本集合通过属性测试被划分到子结点中;根结点包含样本全集;

决策树的构造:

    构造决策树的关键是——当前数据集上哪个特征在划分数据分类时起决定性作用。为了找到决定性的特征,划分出最好的结果,我们要评估每个特征。完成评估测试后,原始数据集(根结点)就被划分为几个数据子集。这些数据子集会分布在第一个决策点的所有分支上。如果某个分支下的数据属于同一类型,则无需进一步对该数据子集进行分割。如果数据子集内的数据不属于同一类型,则需要重复划分数据子集的过程(即对当前数据子集再划分)。划分数据子集的算法和划分原始数据集的方法相同,直到所有具有相同类型的数据均在一个数据子集内。

    伪代码如下:

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

 划分选择:

       划分选择,即选择最优划分属性。属性划分的目的是让各个划分出来的子节点尽可能“纯度”越高,即该数据子集中的样本尽可能属于同一类别。主要有以下三种算法:ID3,C4.5和CART。本文使用ID3算法。

        计算信息熵(香农熵):熵即为信息的期望,信息定义为-log2p(xi),则数据集D的熵为:Ent(D)=-∑(i=1,n)log2p(xi)

"""计算信息熵"""
def calcShannonEnt(dataSet):
    numEntries=len(dataSet)  #数据集中样本(实例)的总数   len(numpy.array)输出array的第一维中的个数,这里就是样本的个数
    labelCounts={}   #样本标记字典初始化
    #为所有可能的分类创建字典
    for featVec in dataSet:
        currentLabel=featVec[-1]   #字典的键值就是样本矩阵的最后一列数值(即样本标签)
        if currentLabel not in labelCounts.keys(): #如果当前键值不在字典中,则扩展字典并将该键值存入
            labelCounts[currentLabel]=0
        labelCounts[currentLabel]+=1  #然后记录当前类别出现的次数
    shannonEnt=0.0  #初始化信息熵
    #Ent=-∑pk*log2pk
    for key in labelCounts:
        prob=float(labelCounts[key])/numEntries
        shannonEnt-=prob*log(prob,2)  #以2为底求对数
    return shannonEnt

        计算信息增益:假定离散属性a有V个可能的取值{a1,a2,..,av},若使用a来对样本集D进行划分,会产生V个分支节点,其中第v个分支节点包含了D中所有在属性a上取值为av的样本,记为Dv。则Dv的信息熵为Ent(Dv),考虑到不同的分支结点所包含的样本数不同,给该分支结点赋予权重|Dv|/|D|,于是可以计算属性a对样本集D进行划分所获得的信息增益为:

                                                Gain(D,a)=Ent(D)-∑(v=1,V)|Dv|/|D|*Ent(Dv)

           信息增益越大,意味着使用属性a来进行划分所获得的“纯度提升”越大。根据信息增益进行特征划分的代码如下:

"""按照给定特征划分数据集"""
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   #样本的特征数,dataSet[0]表示第一个样本,因为包括一个标签所以要减一
    baseEntropy=calcShannonEnt(dataSet)   #计算数据集信息熵
    bestInfoGain=0.0  #初始化最优信息增益
    bestFeature=-1   #初始化最好的特征(下标)
    for i in range(numFeatures):
        #创建为一个分类标签列表
        featList=[example[i] for example in dataSet]
        uniqueVals=set(featList)   #用featList,set()函数创建一个无序不重复元素集
        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

  递归创建决策树:

        递归结束的条件有三个:

  1. 当前结点包含的样本全属于同一类别,无需划分;
  2. 当前属性集为空,或者所有样本在所有属性上取值相同,无法划分;
  3. 当前结点包含的样本集合为空,不能划分;

        在第二种情形下,我们把当前结点标记为叶结点,并将其类别设定为该结点所含样本最多的类别。第三种情形下,把当前结点标记为叶结点,但其类别设定为其父节点所含样本最多的类别。

        实际上,递归结束的条件简单地说就是遍历完所有划分数据集的属性,或者每个分支下的所有样本实例都具有相同的分类。如果所有样本实例具有相同的分类,则得到一个叶子节点或者终止块。条件2意味着,程序处理了所有的属性后,但类标签依然不是唯一的,这时对叶结点进行投票法,将其类别设定为该结点所含样本最多的类别。

        代码如下:

"""多数表决(投票法)决定叶子节点的分类,用于已经划分了所有属性,但叶子节点中样本仍然完全属于同一类别"""
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]    #初始化样本标签列表
    #递归返回条件1:所有样本属于同一类别
    if classList.count(classList[0])==len(classList):  #判断整个数据集的类别是否属于同一类别,若属于同一类别则停止划分
        return classList[0]
    #递归返回条件2,特征全部遍历完,没有将样本分为同一个类别
    if len(dataSet[0])==1:  #数据集中不再有样本特征后停止遍历
        return majorityCnt(classList)  #返回包含样本数最多的类别
    #若特征没有全部遍历完,选择最优特征划分
    bestFeat=chooseBestFeatureToSplit(dataSet)  #当前数据集选取的最优特征(选择bestFeat来划分数据集)这里是一个索引下标
    bestFeatLabel=labels[bestFeat]  #用索引下标对应着找出相应的特征标签
    #开始创建树,myTree保存了树的所有信息
    myTree={bestFeatLabel:{}}  #用字典形式存储返回值
    del(labels[bestFeat])  #将分类过了的分类标签删除
    #创建树,遍历当前选取的特征包含的所有属性值
    featValues=[example[bestFeat] for example in dataSet]
    uniqueValues=set(featValues)  #该特征包含的所有的属性值放入集合,属性值是不重合的
    #在每一个属性值上递归调用createTree
    for value in uniqueValues:
        subLabels=labels[:]  #保存labels数据,防止因为引用方式传递导致原始数据变化
        #递归生成决策树
        myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeat,value),subLabels)  #得到的返回值插入字典myTree
    return myTree


    

猜你喜欢

转载自blog.csdn.net/qq_29599907/article/details/80641870