机器学习实战读书笔记(2)--决策树

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37407587/article/details/81087420

决策树

决策树的一个重要任务是为了数据中所蕴含的知识信息,因此决策树可以使用一系列不熟悉的数据集合,并从中提取系列规则,在这些机器根据数据集创建规则时,就是机器学习的过程.专家系统中经常使用决策树

决策树的构造

优点:计算复杂度不高,输出结果易于理解,对中间值缺失不敏感,可以处理不相关特征数据

缺点:可能会产生过度匹配的问题(overfit)

适用数据类型:数值型和标称型

决策树构建的关键是如何找出当前作为根节点的特征,使得一次划分的划分效果最好,所谓划分效果最好从数学概念上来说就是信息熵下降最多.
构造决策树时,需要解决的第一个问题是当前数据集上哪个特征对分类起到决定性作用.按照这个特征对数据集划分后,会产生一些子数据集,如果一个数据集中所有的元素都属于同一类,则分类结束,如果不是并且还有待划分的特征的话则继续划分.

信息增益

在划分数据集前后信息价值发生的变化叫做信息增益.而划分效果最好的特征即是使信息增益最大的特征.那么首先要解决如何计算信息的价值,信息 x i 的价值可以用 l ( x i ) = l o g 2 P ( x i ) 来定义,如果 P ( x i ) = 1 也即这件事必然发生,则信息的价值为0.为了计算熵,我们需要计算所有类别的所有可能包含的信息的期望值,有

H = i = 1 n P ( x i ) l o g 2 P ( x i )

接下来使用Python计算信息熵

from math import log
import operator

def calshannonEnt(dataSet):
    ent = 0.0
    numData = len(dataSet)
    LabelList = [example[-1] for example in dataSet ]
    UniqueLabel = set(LabelList)
    for label in UniqueLabel:
            ent -= LabelList.count(label)/float(numData) * log(LabelList.count(label)/float(numData),2)
    return ent

def createDataSet():
    dataSet = [[1,1,'yes'],
               [1,1,'yes'],
               [1,0,'no'],
               [0,1,'no'],
               [0,1,'no']]
    labels = ['no surfacing','flippers']
    return dataSet,labels

myDat,labels = createDataSet()
calshannonEnt(myDat)
0.9709505944546686

熵越高,则混合的数据越多.得到熵后,就可以按照获取最大信息增益的方法划分数据集

则选择当前的用于分类的属性的思路就是对当前可以用于分类的属性遍历,计算以各个属性作为分类属性时获得的信息增益,最后选择增益最高的属性,继续进行递归处理.

递归终止的三种情况:
- 一个子集中的所有元素都属于同一类
- 已经没有可供分类的属性使用,子集中根据少数服从多数的形式给出标签
- 一个分类下的某个指标达到设定的阈值

def splitDataSet(dataSet, axis, value):
    '''从dataSet中选出axis维度上的值为value的行,将value属性从
    数据行删除,剩余的形成新的行,最后作为列表返回'''
    returnVec = []
    for data in dataSet:
        if data[axis] == value:
            reduceFeatVec = data[:axis]
            reduceFeatVec.extend(data[axis+1:]) # 把axis左右的属性组成一个新的行
            returnVec.append(reduceFeatVec)
    return returnVec

def chooseBestFeatureToSplit(dataSet):
    '''遍历所有可用的属性,计算每个属性对应的信息增益,
    选择信息增益最大的属性,作为一次分类使用的属性'''
    numFeat = len(dataSet[0])
    numData = len(dataSet)
    bestGain = 0.0
    bestFeat = -1
    baseEnt = calshannonEnt(dataSet)
    for i in range(numFeat):
        FeatList = [example[i] for example in dataSet]
        uniqueFeat = set(FeatList) # 用集合把重复的属性消掉
        newEnt = 0.0
        for value in uniqueFeat:
            subDataSet = splitDataSet(dataSet,i,value)
            prob = len(subDataSet) / float(numData)
            newEnt += prob * calshannonEnt(subDataSet) # 计算一个属性的不同值的信息熵,并进行累加
        infoGain = baseEnt - newEnt # 计算当前属性得到的信息增益
        if infoGain > bestGain:
            bestGain = infoGain
            bestFeat = i
    return bestFeat


def majorityCnt(classList):
    '''对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


def createTree(dataSet, labels):
    '''创建递归函数,从根节点建树,树以字典存储'''
    # 判断递归终止条件
    # 判断是否数据集中的数据属于同一类
    classList = [example[-1] for example in dataSet]
    uniqueClassList = set(classList)
    if len(uniqueClassList) == 1:
        return classList[0]
    if len(dataSet[0]) == 1: # 判断是否还有属性可以分类
        return majorityCnt(classList) # ?这里为什么返回的是一个列表而不是抓哟类别
    # 需要继续递归的情况
    bestFeat = chooseBestFeatureToSplit(dataSet)
    bestFeatLabel = labels[bestFeat] # 找到最佳的分类属性,并找到它的标签,作为根节点的key
    myTree = {bestFeatLabel:{}}
    del(labels[bestFeat]) # 删除已被分类的属性的标签,可以继续用到递归中
    values = [example[bestFeat] for example in dataSet]
    uniqueValue = set(values) # 找到属性的所有值,形成一个新的字典,作为bestFeat这个key的value
    for value in uniqueValue:
        subDataSet = splitDataSet(dataSet,bestFeat,value)
        myTree[bestFeatLabel][value] = createTree(subDataSet,labels)
        # 在新的字典内部,每个值作为key,其value是子集构成的tree
    return myTree

mydata,labels = createDataSet()
myTree = createTree(mydata,labels)
myTree
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}

使用决策树进行分类

建好决策树后,拿到测试数据集,按照输入的决策树可以判断数据的类别

扫描二维码关注公众号,回复: 3908728 查看本文章
def classify(inputTree, featLabels, testVec):
    '''
    将一个建好的决策树和特征标签,以及一个待测向量输入
    返回待测向量的类别
    '''
    firstStr = list(inputTree.keys())[0]
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstStr)  # 得到决策树的第一个label的在featlabels中的索引
    for key in secondDict.keys():
        if testVec[featIndex] == key:  # 找到满足的第二层label
            if type(secondDict[key]).__name__ == 'dict':
                classLabel = classify(secondDict[key], featLabels, testVec)
                # 如果第二层label对应的不是叶节点,再递归的去查找
            else:
                classLabel = secondDict[key]
    return classLabel

决策树调优

决策树容易产生过拟合,这就需要对其进行合理的剪枝.
解决欠拟合过拟合的方法
- 增减模型的参数维度
- 增减多项式维度
- 正则化

剪枝是决策树停止分支的方法之一,剪枝分为预先剪枝后剪枝两种.

预先剪枝是在生长过程中设定指标(如树的高度),当达到该指标时就停止生长.

  1. 定义一个高度,当决策树达到该高度时就停止决策树的生长
  2. 达到某个节点的实例具有相同的特征向量,及时这些实例不属于同一类,也可以停止决策树的生长。这个方法对于处理数据的数据冲突问题比较有效。
  3. 定义一个阈值,当达到某个节点的实例个数小于阈值时就可以停止决策树的生长
  4. 定义一个阈值,通过计算每次扩张对系统性能的增益,并比较增益值与该阈值大小来决定是否停止决策树的生长。

后剪枝是在树充分生长之后,对所有相邻的成对节点检查,消去之后是否能够带来让人满意的不纯度增长,如果可以就消去,并将他们的公共父节点作为新的叶节点.

后剪枝的优势是克服”视界局限”,缺点是计算量大得多.对于小样本的情况,后剪枝的方法优于预先剪枝.

猜你喜欢

转载自blog.csdn.net/m0_37407587/article/details/81087420
今日推荐