决策树-ID3算法

啥是决策树:

决策树是一种分类器,通过训练数据构建决策树可以对未知的类别的拥有相同属性的数据进行分类。

决策树的优点:

  • 容易理解,方便可读
  • 效率高
  • 训练数据可以包含错误,因为决策树学习是通过统计进行分类的,对错误有很好的健壮性

适用情况:

  1. 实例是由“属性-值”对表示的:实例是用一系列固定的属性(例如,温度)和它们的值(例如,热)来描述的。在最简单的决策树学习中,每一个属性取少数的离散的值(例如,热、中、冷)。这里要求属性值是离散的。如果是连续的属性值,可通过将其划分到离散的箱中对其进行离散化处理,每个箱都指定有对应的上限和下限。
  2. 目标函数具有离散的输出值:决策树给每个实例赋予一个布尔型的分类,例如在销售某件无片当中有售完或没有售完两种情况。如果训练实例可表示为属性值对,同时目标分类具有离散的输出值(如,售完和没有售完),那么这样的问题就特别适合用决策树来进行学习。
  3. 可能需要析取的描述,决策树可以表示析取表达式。

例子:新建酒吧的评估问题
(手动模拟决策树建树过程)
这个例子是关于某个啤酒厂的,它需要对在某些位置是否适合建新酒吧进行评估,也就是说,对于某个指定位置,是应该建新酒吧,还是应该将其划为不适合?
为了对新位置进行评估,需要对有关该位置坐落区域的诸多属性进行调查,这些属性描述包括:该位置是一个城市呢还是一个较大的城镇,该区域内是否有大学,居住区属于什么类型。如果需要,还包括附近是否有工业区(公园),以及该区域内公共交通的质量和学校的数量。每个属性都有多个取值,例如,城市属性取值为{Y-是,N-否},居住区类型属性取值为{M-中等,S-小型,N-无,L-大型}, 交通条件取值为{A-一般,P-差,G-好}。
这个啤酒厂拥有一个数据库,其中包括现有酒吧及其相应属性值。下表列出了该数据库的一个子集。每个现有酒吧/饭馆都有一个类别, 其中,+(正)表示该位置被认为是成功的,-(负)表示该位置尚可,但他们已不希望将来再进行类似的投资了。该啤酒厂希望利用这个数据库找到一组规则,以便能够帮助他们决定某个新位置的潜在可用性。

这里写图片描述

(1)IF交通条件=差 AND有无工业区=无THEN不购买该位置(因为例子{2,4,10,13,18都是负的) 。
(2)IF交通条件=好 THEN购买该位置(因为例子{7,12,16,19,20}都是正的) 。
沿着从根结点到叶结点的每条路径就会得到相应的规则。路径上的每个结点都表示了一个属性,而该属性对应于路径上的那个分支就给出了相应的属性值。

得到的决策树如下
这里写图片描述

ID3算法:

基本的ID3算法通过自顶向下构造决策树来进行学习。那么构造一颗决策树要先选择根节点,怎么选呢?
这里要用到信息增益的熵计算公式,比较啰嗦,后面记录。
假如现在通过熵增益计算公式得到了一个根节点,然后为根结点属性的每个可能值产生一个分支,并把训练例排列到适当的分支(也就是,训练例的该属性值对应的分支)之下。然后重复整个过程,用每个分支结点关联的训练例来选取在该点被测试的最佳属性。

例如,在上面的图中,例子{1,3,6,8,11,15,17}的交通条件属性都取值为A(一般)。
交通条件属性有三个取值{一般-A,差-P,好-G},所以在根结点下就有三个分支,相应地也创建了三个新结点。然后,按照该属性的相应取值将这些例子分别赋给这三个新结点,如例子{2,4,5,9,10,13,14,18}的交通条件属性都取值为P。

构造出决策树后,可以直观的看出。交通条件好的地方适合当酒吧(交通条件为G)

上面的说的熵增益和根节点的选择方法如下:

熵(entropy),用来描述一个系统的混乱程度

1.只有两种信息的熵

假设一个系统S含有两种信息, 其中p1表示第一种信息在S中的比例, p2表示第二种信息在S中的比例, 那么系统S相对于这两种信息分类的熵为:

Entropy(S)=p1log2p1p2log2p2 (1)
在有关熵的所有计算中我们定义0log0为0。

假设S是一个有14个训练例的集合,它包括9个正例和5个反例 ,我们采用记号[ 9+,5-]来概括这样的训练例集。那么S相对于这个正反例两种信息分类的熵为:

Entropy ([9+,5-] )= - (9/14)log2(9/14) - (5/14)log2(5/14) =0.940 (2)

如果S的所有成员属于同一类,那么S的熵为0。例如,如果所有的成员都是第一种, p1 =1,那么p2 就是0,Entropy (S)= -1•log2(1) - 0•log2 (0)= -1•0-0•log2(0)=0。当集合中两种信息的数量相等时,熵为l。当集合中两种信息的数量不等时,熵介于0和1之间。

2.具有多种信息的系统的熵

如果目标属性具有n个不同的信息,那么S相对于n种信息的分类的熵定义为:

Entropy(S)=ni=1(pilog2pi) (3)

其中,pi是S中属于类别i的比例

3.系统分类后的熵

设原系统S的熵为Entropy(S),又系统S具有属性A,我们用Values(A)表示属性A所有可能值的集合,Sv是S中属性A的值为v的集合

vValues(A)|Sv||S|Entropy(Sv)

计算熵的例子:

根据天气判断“是否适合打网球”。

这里写图片描述

设S是上表中训练例的集合,包括14个训练例,其中9个为正例,5个为反例,表示为[9+,5-], 相对于正反例, S的熵Entropy(S) =0.940(见(2)) 。现考虑S的属性Wind,它有两个值Weak和Strong, 14个训练例中具有Weak 值的有6个正例, 2个反例, ,表示为[6+,2-];同时, 14个训练例中具有Strong 值的有3个正例, 3个反例, ,表示为[3+,3-],按照属性Wind分类14个训练例得到的分类后的熵计算如下。
这里写图片描述
由此可见, 使用属性Wind属性分类后, S的熵大大降低。我们再考虑S的属性Humidity,按其分类14个训练例得到的分类后的熵计算如下。
这里写图片描述

信息增益最佳分类属性:

一个属性A相对训练例集合S的信息增益Gain(S,A)被定义为:

这里写图片描述

等式(4)的第一项就是原集合S的熵,第二项是用A分类S后熵的期望值。这个第二项描述的期望熵就是每个子集的熵的加权和,权值为属于Sv 的样例占原始样例S的比例,所以Gain(S,A)是由于知道属性A的值而导致的期望熵减少

这里写图片描述

相对于目标分类(即是否适合打网球),Humidity比Wind有更大的信息增益。这里,E代表熵,S代表原始样例集合。已知初始集合S有9个正例和5个反例,即[9+,5-]。用Humidity分类这些样例产生了子集[ 3+,4- ] (Humidity=High)和[6+,1-] (Humidity=Normal)。这种分类的信息增益为0.151,而对于属性Wind增益仅为0.048

使用ID3算法完整实现”是否适合打网球”

第一步,创建决策树的最顶端结点

ID3算法计算每一个候选属性(也就是Outlook、Temperature、Humidity和Wind)的信息增益,然后选择信息增益最高的一个。

所有四个属性的信息增益为:
Gain(S,Outlook)=0.246
Gain(S,Humidity)=0.151
Gain(S,Wind)=0.048
Gain(S,Temperature)=0.029

第二步,创建分枝

Outlook被选作根结点的决策属性,并为它的每一个可能值(也就是Sunny,Overcast和Rain)在根结点下创建分支。

注意到每一个Outlook =Overcast的样例都是PlayTennis的正例,所以,树的这个结点成为一个叶子结点,它对目标属性的分类是PlayTennis=Yes。相反,对应Outlook =Sunny和Outlook=Rain的后继结点还有非0的熵,所以决策树会在这些结点下进一步展开。
如图
这里写图片描述

Ssunny={D1,D2,D8,D9,D11}
Gain(Ssunny,Humidity)=0.970-(3/5)0.0-(2/5)0.0=0.970
Gain(Ssunny,Temperature=0.970-(2/5)0.0-(2/5)1.0-(1/5)0.0=0.570
Gain(Ssunny,Wind)=0.970-(2/5)1.0-(3/5)0.918=0.019

这里写图片描述

对于非终端的后继结点,再重复前面的过程选择一个新的属性来分割训练样例,这一次仅使用与这个结点关联的训练样例。已经被树的较高结点测试的属性被排除在外,以便任何给定的属性在树的任意路径上最多仅出现一次。对于每一个新的叶子结点继续这个过程,直到满足以下两个条件中的任一个:(1)所有的属性已经被这条路径包括;(2)与这个结点关联的所有训练样例都具有相同的目标属性值(也就是它们的熵为0)。

最后的决策树如下图
这里写图片描述

决策树算法的问题:

上面的例子描述的算法增长树的每一个分支的深度,直到恰好能对训练样例完美地分类,但是当数据中有噪声或训练样例的数量太少以至于不能产生目标函数的有代表性的采样时,这个策略便会遇到困难。在以上任一种情况发生时,这个简单的算法产生的树会过度拟合训练样例。

过度拟合的通俗意思就是该算法非常完美的满足了训练例,最后使得该算法只对训练例中的数据有非常精确的结果,由于训练例的局限性,对未知数据预测造成了很大的偏差。

例如,在上面打网球的例子当中增加一条训练正例,但却被误标示为反例
< Outlook=Sunny,Temperature=Hot,Humidity=Normal,Wind=Strong,PlayTennis=No>

增加这个不正确的样例导致ID3建立一个更复杂的树,当然,新的决策树会完美地拟合训练样例集,然而,由于新的决策结点只是拟合训练样例中噪声的结果。

有几种途径可被用来避免决策树学习中的过度拟合

  1. 预剪枝,在ID3算法完美分类训练数据之前就停止树增长(比如树达到了一定高度)
  2. 后剪枝,即允许树过度拟合数据,然后对这个树进行后修剪。

通常后剪枝比预剪枝更加常用
后剪枝的方法有

  1. Reduced-Error Pruning(REP,错误率降低剪枝)
  2. Pesimistic-Error Pruning(PEP,悲观错误剪枝)
  3. Cost-Complexity Pruning(CCP,代价复杂度剪枝)
  4. EBP(Error-Based Pruning)(基于错误的剪枝)

python代码:
(来自机器学习实战那本书)

from math import log
import operator

def createDataSet():#创建数据集
    dataSet= [[1,1,'yes'],#1表示是,0表示否
    [1,1,'yes'],
    [1,0,'no'],
    [0,1,'no'],
    [0,1,'no']
    ]
    labels = ['no surfaceing','flippers']#数据的标签
    return dataSet , labels

def calcShannonEnt(dataSet):#计算熵
    numEntries = len(dataSet)
    labelCounts = {}
    for featVec in dataSet:
        currentLabel = featVec[-1]#按照最后一列,即yes和no来计算数目
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel]=0
        labelCounts[currentLabel]+=1
        shannonEnt = 0.0
        for key in labelCounts:
            prob = float(labelCounts[key])/numEntries
            shannonEnt -= prob * log(prob,2)
    return shannonEnt


def splitDataSet(dataSet,axis,value):#数据,数据特征,特征值
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            reduceFeatVec = featVec[:axis]#剔除第axis列属性
            reduceFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reduceFeatVec)
    return retDataSet

def chooseBestFeatureToSplit(dataSet):#计算数据当中熵增益最大的特征
    numFeatures = len(dataSet[0])-1
    baseEntropy = calcShannonEnt(dataSet)
    bestInfoGain = 0.0
    bestFeaturn = -1
    for i in range(numFeatures):#select column
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        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
                bestFeaturn = i
    return bestFeaturn

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)
    return sortedClassCount[0][0]

def createTree(dataSet,labels):#建树
    classList = [example[-1] for example in dataSet]#获取所有数据行当中的yes和no
    if classList.count(classList[0]) == len(classList):#如果只有yes或者只有no,说明分类完成
        return classList[0]
    if len(dataSet[0])==1:#数据表当中只剩下一列
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)#选择熵增益最大的做根节点
    bestFeatLabel = labels[bestFeat]#获取熵增益最大的标签
    myTree = {bestFeatLabel:{}}#建树
    del(labels[bestFeat])#删除找到的熵增益最大的标签的属性列
    featValues = [example[bestFeat] for example in dataSet]#获取最大熵增益标签的所有属性(0和1)
    uniqueVals = set(featValues)
    for value in uniqueVals:#按照根节点的标签的属性分类建树(也就是按照0和1属性分枝出左右子树)
        subLabels = labels[:]
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value),subLabels)
    return myTree

myDat,myLab=createDataSet()
#print (myDat)
print(createTree(myDat,myLab))

使用决策树来执行分类

def classify(inputTree,featLabels,testVec):#传入的数据为dict类型
    firstSides = list(inputTree.keys())
    firstStr = firstSides[0]#找到第一个标签
    ##这里表明了python3和python2版本的差别,上述两行代码在2.7中为:firstStr = inputTree.key()[0] 
    secondDict = inputTree[firstStr]#建一个以当前找到标签为根的树,用来遍历节点使用
    featIndex = featLabels.index(firstStr)#找到在label中firstStr的下标
    for key in secondDict.keys():
        if testVec[featIndex] == key:
            if type(secondDict[key]) == dict:
                classLabel = classify(secondDict[key],featLabels,testVec)
            else:
                classLabel = secondDict[key]
    return classLabel   #比较测试数据中的值和树上的值,最后得到节点


myDat,myLab=createDataSet()
myLab1=myLab[:]
myTree=createTree(myDat,myLab)
print(classify(myTree,myLab1,[1,0]))

to be continue~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值