决策树ID3算法原理及源码解析

>>>>>>>>>>>>>>>Sklearn虽然很好用,但是调包侠是没有出路的>>>>>>>>>>>>>>>>>>>>>>

简单的举一个例子根据头发和声音判断学生的性别:

数据如下:

头发声音性别

根据ID3算法的思想,使用信息增益最大的特征对数据进行划分,因为朝着信息增益最大的地方划分,可以使得信息熵减小,信息熵越小,代表样本会越来越纯。

信息熵的公式为:

                           Ent(D)=-\sum_{i=1}^{n}p(x_{i})log_{2}\,p(x_{i})

信息增益公式为:

                         Gain(D,a)=Ent(D)-\sum_{v=1}^{v}\frac{\left | D^{v} \right |}{\left | D \right |}Ent(D^{v})

  \sum_{v=1}^{v}\frac{\left | D^{v} \right |}{\left | D \right |}Ent(D^{v})也称为经验条件熵,事实上做信息增益就是两个信息熵之前做减法。

离散属性a有v个可能值\left \{ a^{1},a^{2},......,a^{v} \right \},使用属性a对D划分会产生v个分支结点,第v个分支结点包含了D中所有在属性a上的取值为a^{v}的样本,记为D^{v}

(1)根据数据表格可得,数据集的信息熵为:

8位同学中,男生有3位,女生有8位

H=-(\frac{3}{8}\ast log_{2}\frac{3}{8}+\frac{5}{8}\ast log_{2}\frac{5}{8})=0.9544

(2)根据头发这一特征属性进行划分的信息增益计算如下:

通过头发属性进行划分的结果为:长头发中有1男3女,短头发中有2男2女

Ent(D^{long})=-(\frac{1}{4}\ast log_{2}\frac{1}{4}+\frac{3}{4}\ast log_{2}\frac{3}{4})=0.8113

Ent(D^{short})=-(\frac{2}{4}\ast log_{2}\frac{2}{4}+\frac{2}{4}\ast log_{2}\frac{2}{4})=1

因此Ent(D^{hair})=H-\frac{4}{8}Ent(D^{long})-\frac{4}{8}Ent(D^{short})=0.0487

(3)同理根据声音这一特征属性进行划分的信息增益计算如下:

根据声音这一属性划分的分类结果为:声音粗的有3男3女,声音细的有0男2女

Ent(D^{rough})=-(\frac{3}{6}\ast log_{2}\frac{3}{6}+\frac{3}{6}\ast log_{2}\frac{3}{6})=1

Ent(D^{fine})=-\frac{2}{2}\ast log_{2}\frac{2}{2}=0

因此Ent(D^{voice})=H-\frac{6}{8}Ent(D^{rough})-\frac{2}{8}Ent(D^{fine})=0.2087

按照ID3算法的核心思想根据信息增益最大的属性来划分,因此选择使用声音这一特征属性来划分,能够使得决策树区分样本的能力更强,更具有代表性。

接下来使用python代码来实现ID3算法:

from math import log
import operator

def calcShannonEnt(dataSet):  # 计算数据的信息熵(entropy)
    numEntries = len(dataSet)  # 数据条数
    labelCounts = {}
    for featVec in dataSet:
        currentLabel = featVec[-1] # 每行数据的最后一个字(类别)
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0 # 这一步其实就是在字典里面初始化每个类别的个数
        labelCounts[currentLabel] += 1  # 统计有多少个类以及每个类的数量
    shannonEnt = 0
    for key in labelCounts:
        prob=float(labelCounts[key])/numEntries # 计算单个类的熵值
        shannonEnt -= prob*log(prob,2) # 累加每个类的熵值
    return shannonEnt

def createDataSet1():    # 创造示例数据
    dataSet = [['长', '粗', '男'],
               ['短', '粗', '男'],
               ['短', '粗', '男'],
               ['长', '细', '女'],
               ['短', '细', '女'],
               ['短', '粗', '女'],
               ['长', '粗', '女'],
               ['长', '粗', '女']]
    features = ['头发','声音']  #两个特征
    return dataSet,features

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  # 获得特征的个数  2个
    baseEntropy = calcShannonEnt(dataSet)  # 原始的信息熵
    bestInfoGain = 0
    bestFeature = -1
    for i in range(numFeatures): # 遍历两个特征
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList) # 引入集合
        newEntropy = 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 # 返回的是最优特征的索引

def majorityCnt(classList):    # 按分类后类别数量排序,比如:最后分类为2男1女,则判定为男;
    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)
    #print(sortedClassCount)
    return sortedClassCount[0][0]

# 构建决策树(ID3决策树)
def createTree(dataSet, labels):
    classList = [example[-1] for example in dataSet]  # 类别:男或女
    if classList.count(classList[0]) == len(classList):  # 最终叶子结点中都是一个类别的话就return那个类别
        return classList[0]
    if len(dataSet[0]) == 1:
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet) #选择最优特征的索引
    bestFeatLabel = labels[bestFeat]
    myTree = {bestFeatLabel:{}} # 分类结果以字典形式保存
    del(labels[bestFeat])  # labels中只有头发这个属性了
    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


if __name__=='__main__':
    dataSet, labels = createDataSet1()  # 创造示列数据
    print(createTree(dataSet, labels))  # 输出决策树模型结果

最终代码的运行结果为:

{'声音': {'粗': {'头发': {'长': '女', '短': '男'}}, '细': '女'}}

将决策树可视化出来的效果图为:

总结:

(1)ID3算法存在的缺点: 
   1)ID3算法在选择根节点和内部节点中的分支属性时,采用信息增益作为评价标准。信息增益的缺点是倾向于选择取值较多是属性,在有些情况下这类属性可能不会提供太多有价值的信息。 
   2)ID3算法只能对描述属性为离散型属性的数据集构造决策树 。

(2)为了改进ID3算法的缺点,提出了ID4.5算法和CART算法

   1)ID4.5算法使用信息增益率来划分属性,增益率准则对可取值数目较少的属性有所偏好

  2)CART算法使用基尼指数(Gini index)来选择划分属性,CART算法既可以做分类也可以做回归,随机森林算法中的弱分类器就是使用了CART算法。

文章部分参考至:https://blog.csdn.net/csqazwsxedc/article/details/65697652

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值