zoukankan      html  css  js  c++  java
  • 《ML in Action》笔记(2) —— ID3决策树

    算法概述

    • 决策树是一种分类器,通过一系列“精心设计”的检验记录属性的问题路径,将记录归入某一个类别。构造决策树需要使用训练数据集,找到最优的问题路径设置。建立决策树后,既可用于对新纪录的分类判决。

    • 决策树的问题路径并不是随意设置的,而是要遵循最优/次最优的规则。理论上对于给定的属性集,可构造的决策树数量为指数级,而在实际构造中,一般采用贪心策略的递归算法。其核心思路是在构建每层分类问题是都遵循信息增益最大原则(即熵或基尼不纯性最小)。

    • 直观地理解,最优划分策略就是能够将不同类别的记录尽可能的区分开来。如果经过一个检验问题后,不同类别的记录还是混杂在每个叶结点,这个问题肯定不是一个好的分类问题。

    • 最优划分策略的度量有几种:熵、基尼不纯性、分类误差。这三种度量是一致的,而熵的区分度较好,所以一般就使用熵。(关于熵的直观理解比较困难,但熵是一个超经典的度量变量,可参见信息论有关内容)

    • 建立决策树的另一个关键问题是决策树的停止条件,也就是决策树的大小,每个叶结点终止下分的条件。

    构建决策树(ID3算法)

    • 主要步骤:
      1. 按照某一个属性进行数据集划分
      2. 计算每一种划分的香农熵(信息增益)
      3. 选出最优的(信息增益最大)的划分属性
      4. 按该属性划分后,对子结点进行递归
      5. 对于达到停止条件的子结点,根据记录数选举分类结果

    【trees.py: part1 —— 决策树核心函数calcShannonEnt()】

    def calcShannonEnt(dataSet):
        # 数据集记录个数
        numEntries = len(dataSet)
        # 初始化,保存各分类的记录个数
        labelCounts = {}
        # 遍历数据集,统计各分类标签的记录个数
        for featVec in dataSet: 
            # 当前记录的分类标签
            currentLabel = featVec[-1]
            # 分类标签计数+1
            if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0
            labelCounts[currentLabel] += 1
        # 初始化,保存香农熵计算结果
        shannonEnt = 0.0
        # 遍历标签dict
        for key in labelCounts:
            # 计算频率
            prob = float(labelCounts[key])/numEntries
            # 累加熵
            shannonEnt -= prob * log(prob,2) #log base 2
        #返回熵
        return shannonEnt
    

    【trees.py: part2 —— 决策树核心函数splitDataSet()】

    # 按照给定特征划分数据集
    def splitDataSet(dataSet, axis, value):
        # 初始化,划分后的数据集
        retDataSet = []
        # 遍历数据集
        for featVec in dataSet:
            # 指定属性=输入参数时:
            if featVec[axis] == value:
                # 提取该记录其他属性值,拼接
                reducedFeatVec = featVec[:axis]     
                reducedFeatVec.extend(featVec[axis+1:])
                # 将该新纪录加入划分后数据集
                retDataSet.append(reducedFeatVec)
        # 返回划分后数据集
        return retDataSet
    

    【trees.py: part3 —— 决策树核心函数chooseBestFeatureToSplit()】

    # 选出划分后信息增益最大的属性
    def chooseBestFeatureToSplit(dataSet):
        # 取数据集的属性数
        numFeatures = len(dataSet[0]) - 1
        # 计算数据集的初始香农熵
        baseEntropy = calcShannonEnt(dataSet)
        # 初始化,最大增益、最佳属性ID
        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)/float(len(dataSet))
                newEntropy += prob * calcShannonEnt(subDataSet)
            # 计算按该属性划分的信息增益
            infoGain = baseEntropy - newEntropy
            # 判断是否信息增益最大
            if (infoGain > bestInfoGain):
                bestInfoGain = infoGain
                bestFeature = i
        # 输出信息增益最大的属性ID
        return bestFeature
    

    【trees.py: part4 —— 决策树核心函数majorityCnt()】

    # 叶结点最终归属类别的表决
    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=lambda item:item[1], reverse=True)
        # 返回类别标签
        return sortedClassCount[0][0]
    

    【trees.py: part5 —— 决策树核心函数createTree()】

    # 创建递归决策树
    def createTree(dataSet,lb):
        # 由于传址参数的问题。故增加一行代码,将lb变量另外复制一份。使用copy.copy()
        labels = copy.copy(lb)
        # 取出训练数据集的所有类别信息
        classList = [example[-1] for example in dataSet]
        # 巧用count(),统计分类列表中与第一个结果相同的个数,若该个数等于数组总长度,则说明所有记录归属同一类别,停止划分
        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:{}}
        # 在labels中删去已用于划分的属性
        # 注:labels列表是可变对象,在PYTHON函数中作为参数时传址引用,能够被全局修改,所以这行代码导致函数外的同名变量被删除了元素,造成例句无法执行,提示'no surfacing' is not in list
        del(labels[bestFeat])
        # 取出该属性的所有值
        featValues = [example[bestFeat] for example in dataSet]
        # 剔重数值
        uniqueVals = set(featValues)
        # 遍历数值,对所有数值划分后的数据子集进行递归
        for value in uniqueVals:
            # 复制当前labels,以便原变量不被改变
            subLabels = labels[:]
            # splitDataSet对最佳属性的当前数值进行划分,然后递归调用createTree,子进程构建的决策树逐层代入myTree树结构
            myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
        return myTree 
    

    【trees.py: part6 —— 决策树核心函数classify()】

    def classify(inputTree,featLabels,testVec):
        # 获得首层属性名称
        # 注:原代码为firstStr = myTree.keys()[0]。在python3中报错'dict_keys' object does not support indexing。因为python3的dict.keys返回dict_keys对象,支持iterable但不支持indexable,所以要将类型转换为list。
        firstStr = list(inputTree.keys())[0]
        # 获得首层树DICT
        secondDict = inputTree[firstStr]
        # 反查首层树所用属性的ID
        featIndex = featLabels.index(firstStr)
        # 获得测试向量的该属性数值
        key = testVec[featIndex]
        # 获得该数值对应的子结点
        valueOfFeat = secondDict[key]
        # 如果子结点是字典,则递归继续分类;否则就得到分类结果
        if isinstance(valueOfFeat, dict): 
            # valueOfFeat是下层树,可直接用于递归
            classLabel = classify(valueOfFeat, featLabels, testVec)
        else: classLabel = valueOfFeat
        # 返回分类结果
        return classLabel
    

    【trees.py: part7 —— 存储和调用决策树】

    # 将决策树存储
    def storeTree(inputTree,filename):
        import pickle
        # pickle(除了最早的版本外)是二进制格式的,打开文件需带 'b' 标志。
        fw = open(filename,'wb')
        pickle.dump(inputTree,fw)
        fw.close()
        
    # 读取决策树
    def grabTree(filename):
        import pickle
        fr = open(filename,'rb')
        return pickle.load(fr)
    

    【IPYTHON —— 运行案例lenses】

    fr = open('lenses.txt')
    #读取行、拆分、去除回车符
    lenses = [inst.strip().split('	') for inst in fr.readlines()]
    lensesLabels = ['age','prescript','astigmatic','tearRate']
    lensesTree = trees.createTree(lenses,lensesLabels)
    

    算法小结

    PYTHON小结

  • 相关阅读:
    配置禅道遇到的那些事儿
    HDU 1181
    HDU1016
    HDU 1518
    2015长春区域赛赛后总结
    Codeforces Round #322 (Div. 2) A B C
    Codeforces Round #325 (Div. 2) A B
    Codeforces Round #324 (Div. 2) A B
    CSU 1530
    CSU 1535
  • 原文地址:https://www.cnblogs.com/herzog/p/6378054.html
Copyright © 2011-2022 走看看