《机器学习实战》决策树构建学习

概要记录

  • Decision Tree基本学习,学习自《机器学习实战》P32 - P42 (基于信息增益的决策树构建)

  • 决策树核心问题:当前数据集使用哪个属性/特征进行数据分类时起到了决定性作用,能够得到最好的划分结果。

信息增益:

  • 划分数据集的大原则:将无序的数据变得更加有序

  • 划分数据集前后信息发生的变化成为信息增益,计算每个特征值划分数据集得到的信息增益,获得信息增益最高的特征(属性)就是最好的特征(属性)

  • 信息增益计算部分还可参见周志华《机器学习》P76,基本步骤是:

      1. 根据属性的取值划分子集;
      1. 计算每个子集的熵;
      1. 根据每个子集占总数据集的数量比与S2计算的每个子集的熵,计算
        关于该属性的熵。
  • 另外一个度量无序程度的方法是"基尼不纯度(GiNi impurity)",简单地说就是从一个数据集中司机选取子项,度量其被错误地分类到其他分组的概率

划分数据集

  • 对每个特征划分数据集的结果计算一次熵,然后判断哪个特征是划分数据集的最好方式;

  • 数据集划分可以这样想象:假设有2个维度的特征,则存在在二维空间的数据散点图,需要在数据之间画一条线将他们分为两部分,那么应该按照x轴还是y轴划线呢?
    (按照哪个特征划线,就是在哪个维度划线,目的在于每次划线后得到一个数据较纯即更加有序的类别的数据)

  • 选择最好特征chooseBestFeatureToSplit()就是在选择最好的数据集划分方式。

  • 信息增益是熵的减少或数据无序度的减少,信息增益越大,数目数据从无序变得有序的程度就大

  • 注意:这里的特征其实就是属性

递归构建决策树

  • 得到原始数据集,然后基于最好属性值划分数据集,由于特征值可能多于2个,因此可能存在大于2个分支的数据集划分。第一次划分后,数据集将被向下传递到树分支的下一个节点,在这个节点上可以再次划分数据,因此可以采用递归的原则处理数据。

  • 递归结束的条件是:程序遍历完所有划分数据集的属性,或每个分支下的所有实例都居于相同的分类

  • 书中实例说明:

    • 以[[1,1,'yes'],[1,1,'yes'],[1,0,'no'],[0,1,'no'],[0,1,'no']]为例,首先选出了最佳属性是第0个属性(no surfacing),当值为0是都为no;当值为1时不一致[[1,yes],[1,yes],[1,no]],因此再次选择最佳属性是第1个特征(flippers),当值为1时种类都是yes,值为0时种类都是no。

    • 最终得到划分结果: {'no surfacing':{0:'no',1:{'flippers':{0:'no',1:'yes'}}}}

代码:

# -*- coding: utf-8 -*-
"""
整理自:《机器学习实战》P32 - P42 
"""
from math import log
import operator


def createDataSet():
    dataSet = [[1,1,'yes'],
                [1,1,'yes'],
                [1,0,'no'],
                [0,1,'no'],
                [0,1,'no']
               ]
    # QUESTION:labels是第i个特征的含义
    laebls = ['no surfacing', 'flippers']
    return dataSet,laebls


def clacShannonEnt(dataSet):
    """
    功能:计算dataSet的熵(度量数据的无序程度)
    """
    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.0
    for key in labelCounts:
        prob = float(labelCounts[key]) / numEntries
        # 以2为底求对数
        shannonEnt -= prob * log(prob,2)
    return shannonEnt


def chooseBestFeatureToSplit(dataSet):
    """
    功能:选取特征,划分数据集,计算得出最好的划分数据集的特征
    param: dataSet: 数据集
    return: i: 返回i则说明第i个特征是最好的用于划分数据集的特征
    """
    numFeatures = len(dataSet[0]) - 1
    baseEntropy = clacShannonEnt(dataSet)
    baseInfoGain = 0.0
    beatFeature = -1
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        newEntropy = 0.0
        for value in uniquevVals:
            subDataSet = splitDataSet(dataSet,i,value)
            # 计算每种划分方式的信息熵
            prob = len(subDataSet) / float(len(dataSet))
            newEntropy += prob * clacShannonEnt(subDataSet)
        # 信息增益是熵的减少或数据无序度的减少
        infoGain = baseEntropy - newEntropy
        # 信息增益
        if infoGain > baseInfoGain:
            baseInfoGain = infoGain
            baseFeature = i 
    return beseFeature
    

def splitDataSet(dataSet, axis, value):
    '''
    功能:按照给定特征划分数据集 数据集第axis位上值为value的集合
    注意:1.划分数据集的大原则:将无序的数据变得更加有序
         2. 划分数据集前后信息发生的变化成为信息增益,获得信息增益高的特征就是最好的选择

    eg. 若dataSet = [[1,1,'yes'], [1,1,'yes'], [1,0,'no'], [0,1,'no'],[0,1,'no']],splitDataSet(dataset,0,1)
        结果为[[1,'yes'],[1,'yes'],[0,'no']]
        注:当featVec = [1,0,'no']时,featVec[:axis] = featVec[:0] = [], featVec[axis+1:] = featVec[1:] = [0,'no']
    '''
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            # 抽取
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis + 1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

def majorityCnt(classList):
    """
    功能:用多数表决方法确定叶子节点的分类
    """
    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)
        return sortedClassCount[0][0]


def createTree(dataSet,labels):
    """
    param: dataSet: 数据集
    param: labels: 标签列表(每个属性的名字)
    """
    classList = [example[-1] for example in dataSet]
    # 类别完全相同则停止划分
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    # 便利完所有特征时返回出现次数最多的
    # QUESTION: 为什么??len(dataSet[0]) == 1
    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]
    # uniquevVals 是最佳属性值的所有取值
    uniquevVals = set(featValues)
    for value in uniquevVals:
        subLabels = labels[:]
        # 最佳划分属性的每个取值下都对应一颗子树,这颗子树可能一个子树,也可能是一个叶子节点
        '''
            例如,讨论bestFeat=0时的情况:
            当value=0,时候,splitDataSet()的结果是[1,no],[1,no],因此crrateTree([[1,no],[1,no]],subLabels)的结果可以直接返回no
            当bestFeat=0,value=1,时候,splitDataSet()的结果是[1,no],[1,no],因此crrateTree([[1,yes],[1,yes],[1,no]],subLabels)无法直接确定种类,因此要再次选择最佳属性
        '''
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value),subLabels)
    return myTree

if __name__ == '__main__':
    dataSet,laebls = createDataSet()
    print (createTree(dataSet,labels))

2018.05.21

你可能感兴趣的:(《机器学习实战》决策树构建学习)