ID3决策树

本博客是用jupyter notebook写的,在CSDN上是用其转换的markdown,想要获取本篇博客的笔记与源码,请访问:我的github

最近在看《机器学习实战》这本书,主要是为了给自己的机器学习中没有学过的知识补充一下,比如这个决策树我就没学过,但是这本书写的吧……还行,有些地方说得不是很清楚,正好学完了决策树也想巩固一下,于是写下此文。

其实我觉得决策树很像一种专家系统,有各种条件判断的分支,来判断符不符合标准、属于哪个类别……我在某人的博客上找到了一个非常典型的图,银行可以根据这张图来判断是否可以放贷给这个人(当然,实际肯定比这幅图复杂得多):
放贷决策树
好了,这就是决策树了,我们根据各个分支,层层筛选,最后判断这个人是否有偿还贷款的能力,没有的话,就不贷款给他了。现在我给你一组数据(书上的数据),我要求,根据不浮出水面是否能生存、有没有脚蹼这两个特征来判断是否属于鱼类,用决策树来表现出来。

id不浮出水面是否能生存有没有脚蹼鱼类
0
1
2
3
4

这个时候可能大家就会有疑惑了,到底第一个特征是用能否浮出水面生存来划分好呢,还是用有没有脚蹼来划分好?有什么评价标准吗?答案是用信息增益。

信息熵与信息增益

先来信息熵的公式亮瞎眼:
H=ni=1p(xi)log2(xi)
伟大的香农提出来的(本人学通信的),n代表的是分类的数目,比如上面的表格中,只有两种类别,是鱼类,不是鱼类,我们用1表示是,0表示否,则不是鱼类的概率为: p(x0)=35 ,是鱼类的概率为: p(x1)=25 我们手算一下目前的信息熵:
H=35log2(35)25log2(25)0.971
那么信息熵和我们设计决策树有什么关系呢?我们每一次选择某一特征作为节点进行划分的时候,都有一个目标——使得这一节点(注意,不是整体)的熵下降最多,也就是信息增益最大。
假设我们以能浮出水面生存作为第一个节点来划分,那么1,2,3作为左子树,4,5作为右子树。在左子树中,是鱼类占 23 ,不是鱼类占 13 ,所以左子树的信息熵 H=23log2(23)13log2(13)0.918 ;在右子树中,是鱼类占0%,不是鱼类占100%,所以右子树的信息熵为: H=1log2(1)=0 ;左子树有1,2,3三个条目,占当前节点的 35 ,右子树有4,5两个条目,占当前节点的 25 ,所以按照能否浮出水面生存来划分的信息熵为 H=350.918+2500.551 相对于未划分前,信息增益为0.420。
那如果以有没有脚蹼作为划分标准,则1,2,4,5作为左子树,3作为右子树。在左子树中,是鱼类和不是鱼类各占 12 ,左子树的信息熵 H=212log2(12)=1 ;右子树不是鱼类为100%,则右子树的信息熵为 H=1log2(1)=0 ;左子树有4个条目,占当前节点的 45 ,右子树有1个条目,占当前节点的 15 ,所以按照能否浮出水面生存来划分的信息熵为 H=451+1500.8 相对于未划分前,信息增益为0.171。
因此,我们选择信息增益最大的划分方案,划分结果如下图:
是否是鱼分类1

递归和停止条件

如果学过数据结构与算法的人应该知道,通常我们画树都是用递归来画的,那么递归截止的条件是啥?可以分两种情况:1、所有的分类都一样了,比如我们上图的右子树,都是鱼类,所有这个右子树可以停止继续递归了;2所有的特征都用完了,假如我们继续对左子树分类,把脚蹼作为分类特征,分完后已经没有其他特征可以继续分类了,这时候也不得不结束递归,但是又有一个问题了,如果我特征用完了,在当前子树下还是有不同类别咋办,那只能选择数量最多的了作为分类。
好了,手工推导已经结束,接下来让代码来实现。
首先我们想一想,需要哪些功能模块:计算信息熵、根据特征划分、选择最佳特征划分、创建树、使用树来分类。不过在此之前,先把数据格式定下来,这样才方便,在这里我使用的是pandas,作为一款数据分析包,其使用起来比较方便。

import numpy as np
import pandas as pd
data=pd.DataFrame({
    'No Surfacing':[1,1,1,0,0],
    'Flippers':[1,1,0,1,1],
    'Fish':['Y','Y','N','N','N']
})
data
FishFlippersNo Surfacing
0Y11
1Y11
2N01
3N10
4N10

信息熵计算模块

思路:把数据放入后要统计分类的比例,根据比例计算信息熵并返回。注意,这里的category的意思是指最终想要分成什么类,不是特征,我们这里是想分辨是否是鱼,所以category就填Fish

def calcShannonEnt(data,category):
    labels=data[category].value_counts()#统计有几类,每个类有多少个
    numEntries=np.sum(labels)#计算总数
    shannonEnt = 0
    for label in labels:
        prob = label/numEntries#计算当前类的概率
        shannonEnt -= prob * np.log2(prob)
    return shannonEnt
calcShannonEnt(data,'Fish')#测试正确
0.97095059445466858

根据特征划分模块

def splitData(data,feature):
    labels=set(data[feature])#该特征一共有几种分类
    retdata=[]
    for index,f in enumerate(labels):
        retdata.append(pd.DataFrame(data[data[feature]==f]))
        del retdata[index][feature]
    return retdata,labels
sp,labels=splitData(data,'No Surfacing')#测试正确
print(sp[0])
print(labels)
  Fish  Flippers
3    N         1
4    N         1
{0, 1}

选择最佳特征划分

这里的category不是特征,是最终要划分的类,在这里我们用的是Fish

def chooseBestFeatureToSplit(data,category):
    baseEntropy = calcShannonEnt(data,category)#计算分类前的信息熵
    features = data.columns.drop(category)#有哪些特征
    numEntries = data[category].count()#总数
    bestInfoGain = 0
    bestFeature = features[0]
    for feature in features:
        splitdata,_ = splitData(data,feature)
        newEntropy = 0
        for item in splitdata:
            prob = item[category].count()/numEntries#计算当前类的概率
            newEntropy += prob * calcShannonEnt(item,category)
        infoGain = baseEntropy - newEntropy
        #print(infoGain)
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeature = feature
    return bestFeature
chooseBestFeatureToSplit(data,'Fish')#测试正确
'No Surfacing'

递归构建决策树

def createTree(data,category):
    #所有分类都一样
    if len(set(data[category])) == 1:
        return data[category].iloc[0]
    #遍历完所有特征时返回出现次数最多的
    if len(set(data.columns)) == 1:
        temp=pd.DataFrame(data[category].value_counts())
        return temp.sort(columns=category).index[-1]
    #以上两点都不符合,继续递归
    bestFeat = chooseBestFeatureToSplit(data,category)
    splitdata,labels = splitData(data,bestFeat)
    #print(bestFeat)
    myTree = {bestFeat:{}}
    for subdata,sublabel in zip(splitdata,labels):
        #print(sublabel)
        myTree[bestFeat][sublabel]=createTree(subdata,category)
        #print(myTree)
    return myTree
myTree=createTree(data,'Fish')
myTree
{'No Surfacing': {0: 'N', 1: {'Flippers': {0: 'N', 1: 'Y'}}}}

用决策树分类

还是会用到递归

def classify(tree,data):
    key=list(tree.keys())
    subtree=tree[key[0]][data[key[0]]]
    if type(subtree) == dict:#说明不在叶子结点
        subdata = data.copy()
        return classify(subtree,subdata)
    return subtree#说明在叶子结点
testdata=data.loc[4,['Flippers','No Surfacing']]
classify(myTree,testdata)
'N'
testdata = data.loc[:,['Flippers','No Surfacing']]
print(testdata)
for index,row in testdata.iterrows():
    print(classify(myTree,row))
   Flippers  No Surfacing
0         1             1
1         1             1
2         0             1
3         1             0
4         1             0
Y
Y
N
N
N

结尾

这样ID3决策树就完成了,至于书上用matplotlib画图,这种锦上添花的内容,高兴做就做吧。不过用matplotlib画树状图,简直是大炮打蚊子,我倒是发现有专门画流程图的软件,叫graphviz,这个软件也提供了Python接口,可以用比较简单清爽的语句来描述流程图,这个软件我没研究,需要画图了再研究吧,谁叫我是懒人呢……

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值