zoukankan      html  css  js  c++  java
  • 机器学习实战二 (Decision Tree)

    机器学习实战二 (decision tree)

    我们经常使用决策树处理分类问题,近来也有研究表明,决策树是最经常使用的数据挖掘算法。决策树的概念十分简单,就是基于一系列的判断,有点像是编程语言中的条件判断,只不过条件的定义复杂一些。

    决策树一个很重要的任务就是是提取数据中蕴含的知识信息,因此决策树可以使用不熟悉的数据集,从中提取规则,这个过程就是机器学习的过程,而且不像kNN每次都要重新计算,决策树训练的结果是可以保存的,只需训练一次即可,因此效率很高。

    Decisiong Tree(决策树)

    优点:计算相对容易,输入结果易于理解,对于缺失值不敏感,而且可以处理不相关特征数据

    缺点:有时会产生过度匹配的问题

    原理:决策树对于数据集的划分,是基于信息论中的概念,不同的特征,对于类别的表征强度是不同的,这就需要对于数据集的每个特征进行评估,然后制定规则,递归的采用这个过程。

    决策树的算法不止一种,书里采用的是ID3算法

    先来看看基础的概念,如何度量信息?

    1、信息增益

    划分数据集的最大原则是:将无序的数据变得更加有序。这种无序或者有序的状态,可以用信息熵来衡量。划分数据集前后的熵的变化,称为信息增益,能取得最高信息增益的特征,就是最具有代表性的特征。

    信息熵被定义为信息的期望值,计算公式如下:
    (lleft( x_{i} ight) =-log _{2}Pleft( x_{i} ight))

    其中(x_{i})代表某一类别。
    那么所有类别总的信息期望值为:
    (H=-sum ^{n}_{i=1}pleft( x_{i} ight) log _{2}pleft( x_{i} ight))

    Python代码如下:

    from math import log
    import operator
    import pickle
    from treePlotter import *
    
    #计算香农熵
    def calcShannonEnt(dataSet):
        numEntries = len(dataSet)
        labelCounts = {}
        for featVec in dataSet:
            currentLabel = featVec[-1]
            # dict中get的始用,避免条件判断
            labelCounts[currentLabel] = labelCounts.get(currentLabel, 0) + 1
        shannonEnt = 0.0
        for key in labelCounts:
            prob = labelCounts[key] / numEntries
            # 根据熵计算公式
            shannonEnt -= prob * log(prob, 2)
        return shannonEnt
    

    信息越无序,则计算出来的熵就越大,因此可以用信息熵来定量化信息。

    2、划分数据集

    有了熵的计算,信息增益就比较好算了。先对原始数据集计算一次熵值,然后按照某个特征对数据集进行划分,然后计算划分后数据集的熵值,两者相减,即可计算出信息增益,取其中信息增益最大的特征,然后就是一个递归的。

    根据某一特征划分数据集是简单的。

    # 划分数据集
    def splitDataSet(dataSet, axis, value):
        retDataSet = []
        for featVec in dataSet:
            if featVec[axis] == value:
                reducedFeatureVec = featVec[:axis]
                reducedFeatureVec.extend(featVec[axis + 1:])
                retDataSet.append(reducedFeatureVec)
        return  retDataSet
    

    那么选择最好的特征就要将划分数据集与信息增益结合。

    # 选择最好的数据集划分
    def chooseBestFeatureToSplit(dataSet):
        numFeatures = len(dataSet[0]) - 1
        # 原始熵值
        baseEntropy = calcShannonEnt(dataSet)
        bestInfoGain = 0.0
        bestFeature = -1
        for i in range(numFeatures):
            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) / len(dataSet)
                newEntropy += prob * calcShannonEnt(subDataSet)
            # 信息增益是熵的减少
            infoGain = baseEntropy - newEntropy
            if(infoGain > bestInfoGain):
                bestInfoGain = infoGain
                bestFeature = i
        return bestFeature
    
    3、递归构建决策树

    基本的模块已经写好,剩下的就是将它们组合成决策树的流程了。

    决策树是一棵递归的树,这就需要声明递归终止的条件。条件有两个:

    • 按照某一属性划分类别没有变化,终止
    • 所有属性均已耗尽,但是类别标签依旧不唯一,这种情况通常采用多数表决的方式,以出现次数最多标签作为类别标签。
    # 投票表决
    def majorityCnt(classList):
        classCount = {}
        for vote in classList:
            classCount[vote] = classCount.get(vote, 0) + 1
        sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1),
                                  reverse=True)
        return sortedClassCount[0][0]
    

    剩下的,就是创建决策树了。

    # 创建树
    def createTree(dataSet, labels):
        classList = [example[-1] for example in dataSet]
        # 类别相同就终止
        if classList.count(classList[0]) == len(classList):
            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]
        uniqueVals = set(featValues)
        for value in uniqueVals:
            # list复制
            subLabels = labels[:]
            myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),
                                                      subLabels)
        return myTree
    

    然后就可以执行分类了。

    # 根据决策树执行分类
    def classify(inputTree, featLabels, testVec):
        firstStr = list(inputTree.keys())[0]
        secondDict = inputTree[firstStr]
        featIndex = featLabels.index(firstStr)
        for key in secondDict:
            if testVec[featIndex] == key:
                if type(secondDict[key]).__name__ == 'dict':
                    classLabel = classify(secondDict[key], featLabels, testVec)
                else:
                    classLabel = secondDict[key]
        return classLabel
    

    分类也是一个递归的过程,不困难。

    4、决策树的存储

    构造决策树是个很复杂的过程,但好在我们不需要每次都训练,这样的效率无疑很低,因此我们需要的,就是每次决策树构造好之后,我们就将它存储,需要的时候再读取进来,这里采用的是pickle序列化对象

    # 存储决策树
    def storeTree(inputTree, filename):
        fw = open(filename, 'wb')
        pickle.dump(inputTree, fw)
        fw.close()
    
    
    # 读取决策树
    def grapTree(filename):
        fr = open(filename, 'rb')
        return pickle.load(fr)
    

    无论是存储还是读取,都相当的简单。现在,就可以用手上训练好的规则,去检验整个世界了!

    5、决策树绘制

    原书中有关于决策树绘制的部分,很复杂,与机器学习关系不大,代码一并放在Github中,仅供参考。

    决策树存在的问题:

    • 优势特征太多,会导致过度匹配的问题,这时就需要对决策树进行修剪
    • 本次采用ID3算法,还有CART和C4.5算法,后面会介绍到
    • ID3算法只能用于划分标称型数据集,无法处理数值型

    Next:kNN和决策树是确定的分类算法,数据实例会被明确的划分到某个分类,下一章的分类算法将不能完全确定数据示例所属分类,只是给出概率,即朴素贝叶斯方法。

    Reference:

    却道,此心安处是吾乡
  • 相关阅读:
    驱动下的异常处理
    头文件 .h 与源文件 .ccp 的区别
    驱动程序进阶篇
    驱动中链表的使用
    内存操作相关内核 API 的使用
    链表的概念、建立、删除与插入
    编写简单的 NT 式驱动程序的加载与卸载工具
    驱动程序入门篇
    c++ 指针的简单用法
    CTL_CODE 宏 详解
  • 原文地址:https://www.cnblogs.com/lucifer25/p/8194202.html
Copyright © 2011-2022 走看看