机器学习实战读书笔记系列3——决策树

1.算法定义

  1. 灵魂:根据某种指标进行树的分裂达到分类/回归的目的,希望纯度越高越好,即熵为0
  2. 从数据集合中提取出一系列规则,可以更好的理解数据的内在含义
  3. 与KNN一样,是结果确定的分类算法,,数据实例会被明确分到某个类中
  4. 优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征的数据
    缺点:可能会产生过度匹配问题
  5. 适用于数值型与标称型数据。树构造算法只适用于标称型数据,数值型数据必须离散化
  6. 类型:分类树,回归树
    分类树分析是指预测结果是数据所属的类(比如某个电影去看还是不看)
    回归树分析是指预测结果可以被认为是实数(例如房屋的价格,或患者在医院中的逗留时间)
    分类和回归树(CART,Classification And Regression Tree)分析是用于指代上述两种树的总称

2.决策树构造

需考虑的问题:1.数据集的哪个特征在分类时起决定性作用——评估特征,直到所有具有相同类型的数据均在一个数据子集内(量化分类效果,如:信息增益ID3、信息增益率C4.5、基尼系数CART等)

创建分支的伪代码createBranch函数

检测数据集中的每一个子项是否属于同一分类:
    If so return 类标签;
    Else
           寻找划分数据集的最好的特征
           划分数据集
           创建分支节点
                  for 每个划分的子集
                        调用函数createBranch并增加返回结果到分支节点中
             return 分支节点

2.1信息增益

定义1:划分数据集之前之后信息发生的变化成为信息增益,获得信息增益最高的就是的特征是最好的选择。——熵的减少or无序度的减少
定义2:集合信息的度量方式称为香农熵或熵(信息的期望值)。——度量数据集的无序程度。熵值越高,则混合的数据也越多。数据集中类别数增加,则相应的熵增加。

如何计算熵?
在这里插入图片描述
表示所有类别所有可能值包含的信息期望值,log里表示选择某个分类的概率
若所有样本属于同一类,则H=0
所有样本正负样例相等,则H=1
正负样例数量不等,则H基于0到1之间
如何度量信息增益?
熵相减

2.2划分数据集方法

方法:——获取最大信息增益,熵减少,无序度减少(属性划分方法不一样)
1.二分法
2.ID3算法(划分标称型数据集,无法直接处理数值型数据)——信息增益(度量标准:熵)
核心思想:以信息增益度量属性选择,选择分裂后信息增益最大的属性进行分裂
3.C4.5——信息增益率
4.CART——GINI基尼指数(gini impurity 基尼不纯度)
注意:这些算法在运行时并不是在每次划分时都消耗特征,即特征数目并不是在每次划分数据集时都会减少
原则:将无序的数据变得更加有序——如何度量?——信息论量化度量信息——对每个特征划分数据集的结果计算一次信息熵

2.3递归构建决策树

递归终止条件:
1.程序遍历完所有划分数据集的属性。如果处理完所有属性,类标签依然不唯一,应用多数表决的方法决定改叶子节点的分类
2.每个分支下的实例都具有相同的分类
决策树上有两种节点:叶子节点(含有类标签)和判断节点
使用python内嵌的数据结构字典存储节点信息,并不构造新的数据结构

3.使用matplotlib注解绘制(plot)树形图

3.1 matplotlib 注解

注解工具annotations:可以在数据图形上添加文本注释

3.2 构造注解树

4.测试与存储分类器

测试

需要决策树以及用于构造树的标签向量(特征标签列表用于确定用于划分数据集的特征在哪个位置)
比较测试数据与决策树上的数值,递归执行进入叶子节点
测试数据定义为叶子节点所属的类型

存储——可持久化分类器

python模块序列化对象pickle,在磁盘上保存对象并在需要的时候读取调用
任何对象都可以执行序列化操作,包括字典对象
预先提炼并存储数据集中的知识,在对事物进行分类时再使用这些知识

5.应用实例

使用决策树预测隐形眼镜类型(根据眼部状况推荐眼镜)
存在问题

决策树能很好匹配实验数据,但是匹配选项可能过多——过度匹配
解决:裁剪决策树,去掉一些不必要的叶子结点(如果叶子结点只能增加少许信息,则可以删除该节点,将他加入到其他叶子结点中)

'''
Created on Sep 20, 2018
Decision Tree Source Code for Machine Learning in Action Ch. 3
@author: Peter Harrington
'''
from math import log
import operator

def createDataSet():
    dataSet = [[1, 1, 'yes'],
               [1, 1, 'yes'],
               [1, 0, 'no'],
               [0, 1, 'no'],
               [0, 1, 'no']]
    labels = ['no surfacing','flippers'] #特征值:不浮出水面是否可以生存;是否有脚蹼;
    #change to discrete values
    return dataSet, labels

#计算给定数据集的香农熵
def calcShannonEnt(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 #鱼与非鱼的概率
        shannonEnt -= prob * log(prob,2) #log base 2
    return shannonEnt
    

#按照给定特征划分数据集
def splitDataSet(dataSet, axis, value):  #参数:带划分的数据集;划分数据的特征;需要返回的特征的值
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value: #按照某个特征划分数据集,需要将所有符合元素抽取出来
            reducedFeatVec = featVec[:axis]     #chop out axis used for splitting
            reducedFeatVec.extend(featVec[axis+1:])  #extend将列表中的每个元素加进来; append()将整个列表当做一个元素加进来
            retDataSet.append(reducedFeatVec)
    return retDataSet


#遍历整个数据集,循环计算香农熵和splitDataSet()函数,找到最好的划分方式
#选择最好的数据集划分方式    
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1      #获取特征的个数,最后一列为标签 the last column is used for the labels
    baseEntropy = calcShannonEnt(dataSet)  #整个数据集原始香农熵(无序度量值)
    bestInfoGain = 0.0; bestFeature = -1
    for i in range(numFeatures):       #循环遍历所有特征
        featList = [example[i] for example in dataSet]#获取这一列特征的所有值create a list of all the examples of this feature
        uniqueVals = set(featList)       #获取某一列独立的特征值 get a set of unique values
        newEntropy = 0.0
        for value in uniqueVals: #遍历当前特征中的所有唯一的属性值
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet)/float(len(dataSet))
            newEntropy += prob * calcShannonEnt(subDataSet)     
        infoGain = baseEntropy - newEntropy     #calculate the info gain; ie reduction in entropy
        if (infoGain > bestInfoGain):       #compare this to the best gain so far
            bestInfoGain = infoGain         #if better than current best, set to best
            bestFeature = i
    return bestFeature                      #returns an integer

#叶子节点中类标签不唯一,采取多数表决机制
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): # 参数:数据集,标签列表
    classList = [example[-1] for example in dataSet] #获取标签
    #if是终止条件
    if classList.count(classList[0]) == len(classList): #类别完全相同则停止划分
        return classList[0]#stop splitting when all of the classes are equal
    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:
        subLabels = labels[:]       #copy all of labels, so trees don't mess up existing 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] #获得内嵌的第二个字典
    featLabels=['no surfacing','flippers']
    featIndex = featLabels.index(firstStr) #确定划分标签的索引
    key = testVec[featIndex] #获得测试集上的特征
    valueOfFeat = secondDict[key] #测试集在这个划分特征上的取值
    if isinstance(valueOfFeat, dict): # 判断当前特征值对应的是否是个叶节点。是叶节点则对应一个值,不是叶节点则对应一个字典
        #sinstance() 函数来判断一个对象是否是一个已知的类型
        classLabel = classify(valueOfFeat, featLabels, testVec)
    else: classLabel = valueOfFeat  #是叶节点,返回当前节点的分类标签
    return classLabel

#使用pickle模块存储决策树
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, labels=createDataSet()
    
    
    print("香农熵=",calcShannonEnt(dataSet)) #计算数据的香农熵
    print(splitDataSet(dataSet,0,1)) #根据某个特征划分得到的数据集
    print(chooseBestFeatureToSplit(dataSet)) #选择最好的特征
    myTree=createTree(dataSet,labels)
    print(myTree)
    print(classify(myTree,labels,[1,1]))  #测试决策树的分类效果
    storeTree(myTree,'decisionTreeStorage.txt')
    grabTree('decisionTreeStorage.txt')
    
    
    

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值