决策树学习(一)

本文来自我个人博客:www.chenbiaolong.com

概述

决策树学习是一种逼近离散值目标函数的方法,简单来说它可以被表示为多个的if-then的规则表达式。在本文中先主要介绍决策树的基本概念,主要包括熵的概念以及如何选择最优的数据集划分方式

数据准备

这里我们使用《机器学习》(Tom M.Mitchell著)中的例子作为分析的数据源。这颗决策树根据天气情况分类“星期六上午是否适合打网球”。
此处输入图片的描述
从图中可以看出决定是否打球的因素主要有:outlook(天气)、humidity(湿度)和wind(风力)。那么这三个属性哪一个属性是最优的数据集划分方式呢?即我们得到哪一个属性值可以有最大的概率判断出最终的结果(yes或no)呢?获得最优的数据划分方式有一个优势是可以将该属性放在前几个判断节点上,这样在处理大量的数据时可以减少整体数据集的平均搜索深度。这里就需要引入信息熵的概念。

信息增益的度量标准:熵

熵的具体定义参考维基百科。简单的说熵是衡量一个系统的无序程度的物理单位,一个系统越混乱,该系统的熵越大。在信息领域中我们对数据进行分析得到结果的过程是一个熵减过程。
对于一个bool型的系统,给定包含关于某个目标概念的正反样例的样例集S,那么S相对这个布尔型分类的熵为:

Entropy(S)=p+log2p+plog2p

上述公式中,p+代表正样例,比如在本文例子中p+则意味着去打球,而p-则代表反样例,不去打球(在有关熵的所有计算中我们定义0log0为0)。
举例来说,假设S是一个关于布尔概念的有14个样例的集合,它包括9个正例和5个反例(我们采用记号[9+,5-]来概括这样的数据样例),那么S相对于这个布尔样例的熵为:

Entropy[9+5]=9/14log29/145/14log25/14=0.940

根据上述这个公式,我们可以得到:S的所有成员属于同一类,Entropy(S)=0; S的正反样例数量相等,Entropy(S)=1;S的正反样例数量不等,熵介于0,1之间,如下图所示:
此处输入图片的描述
这个图形也符合熵的定义:系统越无序,熵越大。
已经有了熵作为衡量训练样例集合纯度的标准,现在可以定义属性分类训练数据的效力的度量标准。这个标准被称为“信息增益(information gain)”。简单的说,一个属性的信息增益就是由于使用这个属性分割样例而导致的期望熵降低(或者说,样本按照某属性划分时造成熵减少的期望)。更精确地讲,一个属性A相对样例集合S的信息增益Gain(S,A)被定义为:
此处输入图片的描述
直接看公式可能不好理解,这里举个例子:
假定S是一套有关天气的训练样例,描述它的属性包括可能是具有Weak和Strong两个值的Wind。像前面一样,假定S包含14个样例[9+,5-]。在这14个样例中,假定正例中的6个和反例中的2个有Wind =Weak,其他的有Wind=Strong。由于按照属性Wind分类14个样例得到的信息增益可以计算如下:
此处输入图片的描述
这里我们再继续计算一下Sweak。weak集合总共有8个,有6个属于正例2个属于反例即[6+,2-],根据公式计算得出

Sweak=68log2(6/8)28log2(2/8)=0.31125+0.50.811

同理计算出Sstrong=1
通过以上的计算方法可以分别计算出各个属性的信息熵收益(熵减少的量),通过比较各个属性的收益大小可以最终得到最优的信息划分方法。

代码实现

这里我们用python实现以上论述的各个模块功能。代码来自《机器学习实战》。为了保持一致,我们仍然用打球的例子作为数据源,表格如下:
此处输入图片的描述
假设各个属性的映射关系:
outlook –0 sunny;1 overcast; 2 rain
temperature –0 hot ; 1 mild; 2 cool
humidity –0 high; 1 normal;
wind –0 weak; 1 strong;
根据表格得到对应的数据列表:
[0,0,0,0,’no’],
[0,0,0,1,’no’],
[1,0,0,0,’yes’],
[2,1,0,0,’yes’],
[2,2,1,0,’yes’],
[2,2,1,1,’no’],
[1,2,1,1,’yes’],
[0,1,0,0,’no’],
[0,2,1,0,’yes’],
[2,1,1,0,’yes’],
[0,1,1,1,’yes’],
[1,1,0,1,’yes’],
[1,0,1,0,’yes’],
[2,1,0,1,’no’]
将上述表格转换为数据:

def createDataSet():
    dataSet = [[0,0,0,0,'no'],
               [0,0,0,1,'no'],
                [1,0,0,0,'yes'],
                [2,1,0,0,'yes'],
                [2,2,1,0,'yes'],
                [2,2,1,1,'no'],
                [1,2,1,1,'yes'],
                [0,1,0,0,'no'],
                [0,2,1,0,'yes'],
                [2,1,1,0,'yes'],
                [0,1,1,1,'yes'],
                [1,1,0,1,'yes'],
                [1,0,1,0,'yes'],
                [2,1,0,1,'no']]
    return dataSet

给根据特定属性值抽取数据代码:

def splitDataSet(dataSet, axis, value):
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value: 
            reducedFeatVec = featVec[:axis]     #chop out axis used for splitting
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

这个函数将选择属性dataSet[axis]==value的数据集合,需要注意的是输出的retDataSet去掉了dataSet[axis]的属性。举个例子:

def main():
    dataset,label = createDataSet()
    ret_dataset = splitDataSet(dataset, 0, 1)
    print ret_dataset

if __name__ == '__main__':
    main()

splitDataSet(dataset, 0, 1)抽取属性0值为1的数据,得到结果:
[[1,0,0,0,’yes’],[1,2,1,1,’yes’],[1,1,0,1,’yes’],[1,0,1,0,’yes’]]
前面的1均是被函数剔除掉的,实际的结果是:

[[0,0,0,'yes'],[2,1,1,'yes'],[1,0,1,'yes'],[0,1,0,'yes']]

接下来编写计算信息熵的代码

def calcShannonEnt(dataSet):
    numEntries = len(dataSet)
    labelCounts = {}
    for featVec in dataSet: #the the number of unique elements and their occurance
        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])/numEntries
        shannonEnt -= prob * log(prob,2) #log base 2
    return shannonEnt

这个函数比较简单,它计算了输入的dataSet熵值。
接下来就要通过计算各个属性的熵,并找出其中熵减最大的属性。

def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1      #一共有numFeatures属性
    baseEntropy = calcShannonEnt(dataSet)
    bestInfoGain = 0.0; bestFeature = -1
    for i in range(numFeatures):        #iterate over all the features
        featList = [example[i] for example in dataSet] #抽取属性i的值,形成一个列表
        uniqueVals = set(featList)       #get a set of unique values
        newEntropy = 0.0
        for value in uniqueVals: #计算用属性i划分数据集时得到的熵减
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet)/float(len(dataSet))
            newEntropy += prob * calcShannonEnt(subDataSet)     
        infoGain = baseEntropy - newEntropy     #calculate the info gain; ie reduction in entropy
        if (infoGain > bestInfoGain):       #compare this to the best gain so far
            bestInfoGain = infoGain         #if better than current best, set to best
            bestFeature = i
    return bestFeature                      #returns an integer

通过该函数,我们计算得出最佳属性为属性0,即outlook属性。
以下给出整体的测试代码。

from math import log
import operator

def createDataSet():
    dataSet = [[0,0,0,0,'no'],
               [0,0,0,1,'no'],
                [1,0,0,0,'yes'],
                [2,1,0,0,'yes'],
                [2,2,1,0,'yes'],
                [2,2,1,1,'no'],
                [1,2,1,1,'yes'],
                [0,1,0,0,'no'],
                [0,2,1,0,'yes'],
                [2,1,1,0,'yes'],
                [0,1,1,1,'yes'],
                [1,1,0,1,'yes'],
                [1,0,1,0,'yes'],
                [2,1,0,1,'no']]
    return dataSet

def calcShannonEnt(dataSet):
    numEntries = len(dataSet)
    labelCounts = {}
    for featVec in dataSet: #the the number of unique elements and their occurance
        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])/numEntries
        shannonEnt -= prob * log(prob,2) #log base 2
    return shannonEnt

def splitDataSet(dataSet, axis, value):
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]     #chop out axis used for splitting
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1      #the last column is used for the labels
    baseEntropy = calcShannonEnt(dataSet)
    bestInfoGain = 0.0; bestFeature = -1
    for i in range(numFeatures):        #iterate over all the features
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)       #get a set of unique values
        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     #calculate the info gain; ie reduction in entropy
        if (infoGain > bestInfoGain):       #compare this to the best gain so far
            bestInfoGain = infoGain         #if better than current best, set to best
            bestFeature = i
    return bestFeature                      #returns an integer
def main():
    dataset = createDataSet()
    best = chooseBestFeatureToSplit(dataset)
    print best
if __name__ == '__main__':
    main()

小结

本文主要介绍了信息熵的概念,以及如何用熵减的方法选择出最佳划分属性。关于决策树的进一步使用将在下一篇博文写出。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值