当前位置:   article > 正文

机器学习实验二---决策树python_python决策树实验报告

python决策树实验报告


一、了解一下决策树吧

决策树(decision tree)是一类常见的机器学习方法.以二分类任务为例,我们希望从给定训练数据集学得一个模型用以对新示例进行分类,这个把样本分类的任务,可看作对“当前样本属于正类吗?”这个问题的“决策”或“判定”过程.顾名思义,决策树是基于树结构来进行决策的,这恰是人类在面临决策问题时一种很自然的处理机制.

决策树基本流程

一般的,一棵决策树包含一个根结点、若干个内部结点和若干个叶结点;叶结点对应于决策结果,其他每个结点则对应于一个属性测试;每个结点包含的样本集合根据属性测试的结果被划分到子结点中;根结点包含样本全集.从根结点到每个叶结点的路径对应了一个判定测试序列.决策树学习的目的是为了产生一棵泛化能力强,即处理未见示例能力强的决策树,其基本流程遵循简单且直观的“分而治之”(divide-and-conquer)策略,如图:
在这里插入图片描述

信息增益

划分数据集的大原则是:将无需的数据变得更加有序;
“信息嫡”(information entropy)是度量样本集合纯度最常用的一种指标.假定当前样本集合D中第k类样本所占的比例为pk (k = 1,2,…,|y|),则D的信息嫡定义为
在这里插入图片描述

Ent(D)的值越小,则D的纯度越高.值越高信息越混乱。

假定离散属性a有V个可能的取值{a1 , a2,… . ,aV},若使用a来对样本集D进行划分,则会产生V个分支结点,其中第v个分支结点包含了D中所有在属性a上取值为av的样本,记为D”.我们可根据上式计算出Dv的信息嫡,再考虑到不同的分支结点所包含的样本数不同,给分支结点赋予权重|Dv|/|D|,即样本数越多的分支结点的影响越大,于是可计算出用属性α对样本集D进行划分所获得的“信息增益”(information gain)
在这里插入图片描述

一般而言,信息增益越大,则意味着使用属性α来进行划分所获得的“纯度提升”越大.因此,我们可用信息增益来进行决策树的划分属性选择,即在上式算法选择属性a*= argmaxGain(D,a)、著名的ID3决策树学习算就是以信息增益为准则来选择划分属性.

决策树的优缺点

优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感;
缺点:可能产生过度匹配问题

二.数据处理

使用上次实验的数据,每个列分别表示:[‘卷面成绩’,‘班级排名’,‘综测分’,‘奖学金等级’]
其中 奖学金等级 为我们将要预测的标签。
在这里插入图片描述
可以看到属性的数据类型都为连续型,由于连续属性的可取值数目不再有限,因此不能直接对连续型属性的可取值来对节点进行划分,接下来要将数据离散化 。
最简单暴力的方法,排序后按比例划分n类(暂时不考虑信息增益):

#离散化数据
#data数据 axis待划分属性 num划分几类
def discretedata(data,axis,num):
    len_data=len(data)
    #求该属性的最大值和最小值
    max=0;min=100
    for i in range(len_data):
        if data[i][axis]>max:
            max=data[i][axis]
        if data[i][axis]<min:
            min=data[i][axis] 
    #划分区间
    degree=(max-min)*1.0/num
    limit=[min]
    for i in range(num):
        limit.append(min+degree*(i+1))
    print(limit)
   #修改对应的值,1代表最低级     
    for one_data in data:
        for i in range(len(limit)-1):
            if one_data[axis]>=limit[i] and one_data[axis]<=limit[i+1] :
                one_data[axis]=i+1 
    print(data)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

可以看到第一个属性被分为5个等级(根据数据集实际情况调整),接下来对剩余属性进行相同操作
在这里插入图片描述

    mydata=createData('./ch1/grade.txt')
    for i in range(3):
        discretedata(mydata,i,6)#这里分为6个等级
    print(mydata)
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述
这里所有属性都分为6个等级,可以根据实践情况再调整。

三.决策树的构建

计算给定数据集的香农熵

#计算给定数据的熵
def calcEnt(data):
    num=len(data)#获取数据的行数
    labels={}
    #遍历数据的所有行
    for featVec in data:
        #为所有可能分类创建字典
        currentLabel=featVec[-1]
        if currentLabel not in labels.keys():#如果字典中不存在该键则创建并初始化为0
            labels[currentLabel]=0
        labels[currentLabel]+=1#统计每个类别
    Ent=0.0
    for key in labels:
        prob=float(labels[key])/num
        Ent-=prob*log2(prob)
    return Ent
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
print("计算当前数据的熵",calcEnt(mydata))
  • 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
if __name__=='__main__':
    labels=['卷面成绩','班级排名','综测分','奖学金等级']
    mydata=createData('./ch1/grade.txt')
    for i in range(3):
        discretedata(mydata,i,6)
    print("计算当前数据的熵",calcEnt(mydata))
    print("按奖学金为1划分测试:",splitDataSet(mydata,3,1))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述

选择最好的数据划分方式:

def chooseBest(dataset):
    num=len(dataset[0])-1 #除去标签剩余数量
    baseEnt=calcEnt(dataset)#计算整个数据的香农熵
    bestGain=0.0 
    bestFeature=-1
    for i in range(num):#0 1 2
        #创建唯一的分类标签列表
        featlist=[example[i] for example in dataset]#对每行数据取i列放入featlist
        uniqueVals=set(featlist)#集合去重复
        newEnt=0.0
        for value in uniqueVals:
            #计算每种划分方式的信息熵
            subDataSet=splitDataSet(dataset,i,value)
            prob=len(subDataSet)/float(len(dataset))
            newEnt+=prob*calcEnt(subDataSet)
        inforGain=baseEnt-newEnt
        #计算最好的信息熵
        if (inforGain>bestGain):
            bestGain=inforGain
            bestFeature=i
    return bestFeature
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

递归构建决策树

采用递归的原则处理数据集,但程序遍历完所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类时递归结束。

如果数据集已经处理了所有的属性,但是类标签依然不是唯一的,此时我们需要决定如何定义该叶子节点,在这种情况下,通常会采用多数表决的方法决定叶子节点的分类

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)
    print(sortedClassCount)
    return sortedClassCount[0][0]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

创建树的代码:
使用python语句中的字典类型存储树的信息

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=chooseBest(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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
if __name__=='__main__':
    labels=['卷面成绩','班级排名','综测分','奖学金等级']
    mydata=createData('./ch1/grade.txt')
    for i in range(3):
        discretedata(mydata,i,6)
    mytree=createTree(mydata,labels)
    print(mytree)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

测试输出结果:
在这里插入图片描述

四.使用Matplotlib注解绘制树形图

此处参考《机器学习实战》代码

import matplotlib.pyplot as plt

#定义文本框和箭头格式
decisionNode=dict(boxstyle="sawtooth",fc='0.8')
leafNode=dict(boxstyle="round4",fc='0.8')
arrow_args=dict(arrowstyle="<-")

#绘制带箭头的注释
def plotNode(nodeTxt,centerPt,parentPt,nodeType):
    createPlot.axl.annotate(nodeTxt,xy=parentPt,xycoords='axes fraction',
    xytext=centerPt,
    textcoords='axes fraction',
    va="center",ha="center",bbox=nodeType,arrowprops=arrow_args)

#获取叶节点的数目和树的层数
def getNumLeafs(myTree):
    numLeafs=0
#  firstStr=myTree.keys()[0]
    firstStr=list(myTree.keys())[0]
    secondDict=myTree[firstStr]
    for key in secondDict.keys():
        if type(secondDict[key]).__name__=='dict':
            numLeafs+=getNumLeafs(secondDict[key])
        else:
            numLeafs+=1
    return numLeafs

def getTreeDepth(myTree):
    maxDepth=0
    firstStr=list(myTree.keys())[0]
    secondDict=myTree[firstStr]
    for key in secondDict.keys():
        if type(secondDict[key]).__name__=='dict':
            thisDepth=1+getTreeDepth(secondDict[key])
        else:
            thisDepth=1
        if thisDepth>maxDepth:maxDepth=thisDepth
    return maxDepth



def plotMidText(cntrPt,parentPt,txtString):
    xMid=(parentPt[0]-cntrPt[0])/2.0+cntrPt[0]
    yMid=(parentPt[1]-cntrPt[1])/2.0+cntrPt[1]
    createPlot.axl.text(xMid,yMid,txtString)

def plotTree(mytree,parentPt,nodeTxt):
    numLeafs=getNumLeafs(mytree)
    depth=getTreeDepth(mytree)
    firstStr=list(mytree.keys())[0]
    cntrPt=(plotTree.xOff+(1.0+float(numLeafs))/2.0/plotTree.totalW,plotTree.yOff)
    plotMidText(cntrPt,parentPt,nodeTxt)
    plotNode(firstStr,cntrPt,parentPt,decisionNode)
    secondDict=mytree[firstStr]
    plotTree.yOff=plotTree.yOff-1.0/plotTree.totalD
    for key in secondDict.keys():
        if type(secondDict[key]).__name__=='dict':
            plotTree(secondDict[key],cntrPt,str(key))
        else:
            plotTree.xOff=plotTree.xOff+1.0/plotTree.totalW
            plotNode(secondDict[key],(plotTree.xOff,plotTree.yOff),cntrPt,leafNode)
            plotMidText((plotTree.xOff,plotTree.yOff),cntrPt,str(key))
    plotTree.yOff=plotTree.yOff+1.0/plotTree.totalD


def createPlot(inTree):
    fig=plt.figure(1,facecolor='white')
    fig.clf()
    axprops=dict(xticks=[],yticks=[])
    createPlot.axl=plt.subplot(111,frameon=False,**axprops)
    plotTree.totalW=float(getNumLeafs(inTree))
    plotTree.totalD=float(getTreeDepth(inTree))
    plotTree.xOff=-0.5/plotTree.totalW;plotTree.yOff=1.0;plotTree(inTree,(0.5,1.0),'')
    plt.show()


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

当直接采用3等级暴力划分的决策树
在这里插入图片描述

五.总结

出现的问题及解决办法:

  1. ‘dict’ object has no attribute ‘iteritems’
    在这里插入图片描述
    python3.x以上iteritems()已改为items()
    解决办法:
    在这里插入图片描述
  2. ‘numpy.ndarray’ object has no attribute ‘extend’
    在这里插入图片描述
    在递归过程中,有时数据类型变为numpy,将其改为python列表
    解决办法:增加两行代码
    在这里插入图片描述
    3.‘dict_keys’ object does not support indexing
    字面意思,字典不支持索引,可能python2.x可以,3.x以上不行了
    在这里插入图片描述
    解决办法:转化为列表
    在这里插入图片描述

实验结果分析

从数据划分上看:班级排名越低越好;综测分越高越好;卷面成绩越高越好
1.在绘制的决策树中可以看出,班级排名为首选分类属性(越低表示班级排名越高,越容易拿到奖学金),这也符合实际。
2.第二个属性为综测分,最右边的节点可以看出,当综测分等级为3时(最高),综测分低于2等级的都不可能拿到奖学金(分支下所有实例都具有相同类别),等于3时有可能拿到3等奖学金

3.后期可以根据熵来处理连续型属性,当前采用3等级划分已经符合实际了,下次实验有待改进。


2022/11/12

附上本次完整代码和数据集:link

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/271641
推荐阅读
相关标签
  

闽ICP备14008679号