机器学习(四):C4.5决策树(基础篇)

机器学习(四):C4.5决策树(基础篇)

在这里插入图片描述
相关的决策树文章:

问题一:为什么要使用C4.5决策树?

前面我们介绍了ID3决策树,ID3决策树有一个很大的缺点:信息增益反映了给定一个条件下以后不确定减少的程度,必然是分得越细的数据集确定性越高,也就是条件熵越小,信息增益越大。但是这样下来只能处理离散型属性,并且倾向于选择取值较多的属性。而C4.5采用的是信息增益率来作为分支的准则。很好的解决了这一缺点。在平时运用上,C4.5要多于ID3.

问题二:C4.5如何进行分支?

信息增益率是C4.5决策树进行分支的一个很重要的指标。在之前的学习中,我们了解了香农熵,和信息增益,那么什么是信息增益率?

信息增益的定义:

C a i n r a t i o ( D , a ) = G a i n ( D , a ) I V ( a ) Cain_{ratio}(D,a) = \frac{Gain(D,a)}{IV(a)} Cainratio(D,a)=IV(a)Gain(D,a)

其中:

I V ( a ) = − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ l o g 2 ∣ D v ∣ ∣ D ∣ IV(a)=-\displaystyle\sum_{v=1}^{V}\frac{|D^v|}{|D|}log_2\frac{|D^v|}{|D|} IV(a)=v=1VDDvlog2DDv

分子很简单,分子为信息增益(可参考ID3决策树(基础篇)
分母为属性a的熵,成为属性a的”固有值“。属性a的可能取值数目越多(即V越大),则IV(a)的值通常会越大。

需要注意的是:增益率准则对可取值数目较少的属性有偏好,因此,C4.5算法不是直接选择信息增益率最大的候选划分属性,而是使用了一个启发式:先从候选划分属性中找出信息增益率高于平均水平的属性,再从中选择增益率最高的。

代码实现

C4.5决策树的代码与ID3的代码实现差别不大:只需要更改部分函数即可:

# -*- coding: utf-8 -*-
"""
决策树C4.5的实现
"""

from math import log
import operator
import pandas as pd
import plotTrees

def majorityCnt(classList):
    """
    找到最大频繁向量(多数表决器)
    :param classList:训练集
    :return:最大值
    """
    classCounts = {}
    for value in classList:   #遍历训练集中的每一个变量
        if (value not in classCounts.keys()):  #如果变量不在列表中
            classCounts[value] = 0    #新建一个字典的键
        classCounts[value] += 1    #数量加一
    sortedClassCount = sorted(classCounts.items(), key=operator.itemgetter(1), reverse=True)  #排序
    return sortedClassCount[0][0]   #输出第一个,即最大值

def splitDataSet(dataSet, axis, value):
    """
    以靠指定列的指定值来划分数据集,比如划分西瓜瓜皮形状为椭圆的数据集
    :param axis: 索引列,即形状
    :param value: 索引列的特定值,即椭圆
    :return:
    """
    retDataSet = []
    for festdataVal in dataSet:
        if festdataVal[axis] == value:
            reducedFeatVal = festdataVal[:axis]    #这两行去掉索引列
            reducedFeatVal.extend(festdataVal[axis+1:])
            retDataSet.append(reducedFeatVal)
    return retDataSet

def calcShannonEnt(columnIndex, dataSet):
    """
    计算香农熵
    :param dataSet:
    :return:
    """
    numEntries = len(dataSet)   #获得数据集的长度
    labelCounts = {}     #计算标签的字典
    for featDataVal in dataSet:
        currentLabels = featDataVal[columnIndex]   #取最后一个标签值
        if currentLabels not in labelCounts.keys():  #判断有没有在标签里面
            labelCounts[currentLabels] = 0
        labelCounts[currentLabels] += 1
    shannonEnt = 0.0
    for key in labelCounts.keys():   #key有几个遍历几次
        prob = labelCounts[key]/float(numEntries)  #计算频率
        shannonEnt -= prob*log(prob, 2)
    return shannonEnt

def chooseBestFeatureToSplitOfFurther(dataSet):
    """
            选择信息增益率最大的特征值
            :param dataSet:数据集
            :return:
            """
    numFeatures = len(dataSet[0]) - 1  # 看数据集而定,数据中如果最后一行为标签,则删去
    baseEntropy = calcShannonEnt(-1, dataSet)  # 计算所有数据集的香农熵
    bestFeaturesindex = 0  # 最佳特征的索引
    bestInfoGainRatio = 0.0  # 最佳信息熵

    for i in range(numFeatures):  # 有几个特征值循环几次
        featEntropy = calcShannonEnt(i, dataSet)
        featList = []  # 特征值列表
        for example in dataSet:  # 获得这个列的值
            featList.append(example[i])
        uniqueVals = set(featList)  # 相同的数据并没有意义,去重
        newEntropy = 0.0  # 新信息熵
        for value in uniqueVals:  # 得到该列的特征值
            subDataSet = splitDataSet(dataSet, i, value)  # 划分数据集
            prob = len(subDataSet) / float(len(dataSet))  # 权重或者条件概率
            newEntropy += prob * calcShannonEnt(-1, subDataSet)  # 计算信息增益后面的条件经验熵
        infoGain = baseEntropy - newEntropy  # 计算信息增益
        if featEntropy == 0.0 :
            infoGainRatio = 0.0
        else:
            infoGainRatio = infoGain/float(featEntropy)
        if infoGainRatio > bestInfoGainRatio:  # 更改最大经验熵
            bestInfoGainRatio = infoGainRatio
            bestFeaturesindex = i
    return bestFeaturesindex  # 输出最大经验熵的索引

def diferFeature(dataSet, label):
    numFeatures = len(dataSet[0]) - 1  # 看数据集而定,数据中如果最后一行为标签,则删去
    baseEntropy = calcShannonEnt(-1, dataSet)  # 计算所有数据集的香农熵
    sumEntropy = 0.0
    featureEntropy = []
    dellabel = []
    retDataSet = dataSet
    label1 = label.copy()
    for i in range(numFeatures):  # 有几个特征值循环几次
        featList = []  # 特征值列表
        for example in dataSet:  # 获得这个列的值
            featList.append(example[i])
        uniqueVals = set(featList)  # 相同的数据并没有意义,去重
        newEntropy = 0.0  # 新信息熵
        for value in uniqueVals:  # 得到该列的特征值
            subDataSet = splitDataSet(dataSet, i, value)  # 划分数据集
            prob = len(subDataSet) / float(len(dataSet))  # 权重或者条件概率
            newEntropy += prob * calcShannonEnt(-1, subDataSet)  # 计算信息增益后面的条件经验熵
        infoGain = baseEntropy - newEntropy  # 计算信息增益
        featureEntropy.append(infoGain)
        sumEntropy += infoGain
    averageEntropy = sumEntropy/numFeatures
    for i in range(numFeatures):
        if featureEntropy[i]<averageEntropy:
            dellabel.append(labels[i])
    label.append('jieguo')
    for i in range(len(dellabel)):
        label1.remove(dellabel[i])
    retDataSet = pd.DataFrame(retDataSet, columns=label)
    retDataSet = retDataSet.drop(dellabel, axis=1)
    retDataSet = retDataSet.values.tolist()
    return retDataSet, label1


def createTree(dataSet, label):
    """
    创建树
    :param dataSet:
    :param label:
    :return:
    """
    classList = [] #获得每一个标签
    for classVal in dataSet:
        classList.append(classVal[-1])
    if classList.count(classList[0]) == len(classList): #如果全部标签都相同
        return classList[0]  #返回该标签
    if len(dataSet[0]) == 1:  #如果一列只有一个特征
        return majorityCnt(classList)
    #dataSet, label = self.diferFeature(dataSet, label)
    #获取最优的索引值
    bestFeatureIndex = chooseBestFeatureToSplitOfFurther(dataSet)
    #获取最优索引值的名称
    bestFeatureLabel = label[bestFeatureIndex]
    mytree = {bestFeatureLabel:{}}  #创建根节点
    del(label[bestFeatureIndex])   #删去用过的最优节点
    bestFeature = []   #最优的特征
    for example in dataSet:
        bestFeature.append(example[bestFeatureIndex])
    uniquesVal = set(bestFeature)  #最优特征的种类
    for val in uniquesVal:
        subLabel = label[:]  #创建个子标签
        mytree[bestFeatureLabel][val] = createTree(splitDataSet(dataSet, bestFeatureIndex, val), subLabel)  #递归
    return mytree

def classify( inputTree, featLable, testVec):
    """
    获取分类的结果(算法的使用器)
    :param inputTree:决策树字典
    :param featLable: 标签列表
    :param testVec: 测试向量
    :return:
    """
    #获取根节点的名称,并且把根节点变成列表
    firstSide = list(inputTree.keys())
    #根节点名称string类型
    firstStr = firstSide[0]
    #获得根节点对应得子节点
    secondDict = inputTree[firstStr]
    #获得子节点在标签列表得索引
    featIndex = featLable.index(firstStr)
    #获得测试向量的值
    key = testVec[featIndex]
    #获取树干向量后的变量
    valueOfFeat = secondDict[key]
    #判断是子结点还是叶子节点:子结点就回调分类函数,叶子结点就是分类结果
    if isinstance(valueOfFeat, dict):
        classLabel = classify(valueOfFeat, featLable, testVec)
    else:
        classLabel = valueOfFeat
    return classLabel

def storeTree(inputTree, filename):
    #写入文件
    import pickle
    fw = open(filename, 'wb+')
    pickle.dump(inputTree,fw)
    fw.close()

def grabTree(filename):
    #读取数
    import  pickle
    fr = open(filename, 'rb')
    return pickle.load(fr)

if __name__ == "__main__":
    #因为数据集寻找比较复杂,所以采用自己随机建立数组
    dataSet = [[1, 1, 3, 4, 2, 3, 'yes'],
               [0, 1, 3, 3, 6, 3, 'yes'],
               [1, 0, 4, 3, 6, 4, 'no'],
               [0, 1, 2, 4, 2, 3, 'no'],
               [0, 0, 3, 3, 2, 4, 'no']]
    labels = ['no surfacing', 'flippers', 'table', 'type', '1', '2']
    #复制
    label1 = labels.copy()
    #创建树
    mytree = createTree(dataSet, label1)
    print(mytree)
    #使用树的操作
    a = classify(mytree, labels, [0,1, 3, 4])
    print(a)
    plotTrees.createPlot(mytree)


我们来看看结果:
在这里插入图片描述
可以很好的看出,生成了我么想要的决策树。以及测试结果出现的答案。

  • 1
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值