机器学习实战二 (Decision Tree)

机器学习实战二 (decision tree)

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

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

Decisiong Tree(决策树)

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

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

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

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

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

1、信息增益

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

信息熵被定义为信息的期望值,计算公式如下:
\(l\left( x_{i}\right) =-\log _{2}P\left( x_{i}\right)\)

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

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:

posted @ 2018-01-04 16:47  plantree  阅读(260)  评论(0编辑  收藏  举报