决策树(Decision Tree)

决策树的一个重要任务是为了理解数据中所蕴含的知识信息,因此决策树可以使用不熟悉的数据集合,并从中提取出一系列规则,这些机器根据数据集创建规则的过程,就是机器学习的过程。专家系统中经常使用决策树,而且决策树给出结果往往可以匹敌在当前领域具有几十年工作经验的人类专家。
 
优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据。
缺点:可能会产生过度匹配为问题。
适用数据类:数值型和标称型。
决策树的一般流程:
(1)收集数据:可以使用任何方法。
(2)准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化。
(3)分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。
(4)训练算法:构造树的数据结构。
(5)测试算法:使用经验树计算错误率。
(6)使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义。
 
集合信息的度量方式成为香农熵或者简称为熵
计算熵的公式:-∑p(x₁)log₂p(x₁)
熵越高,则混合的数据也越多。
tree.py

# -*- coding:utf-8 -*-
import operator
from math import log
# 计算熵值
def calcShannonEnt(dataSet):
    numberEntries = 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:
        # 公式:-∑p(x₁)log₂p(x₁)
        prob = float(labelCounts[key]) / numberEntries
        shannonEnt -= prob * log(prob, 2)
    return shannonEnt
# 计算熵值测试数据
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
# 数据,划分特征,返回特征
# 就是返回 第(划分特性)特征为(返回特征)的数据集
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
# 选择信息增益值最大的特征点,返回特征点索引
#
# 遍历每个特征点,对每个特征点求熵值加权平均数,返回信息增益最大的索引
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1
    baseEntropy = calcShannonEnt(dataSet)  # 计算熵值
    bestInfoGain = 0.0  # 最大信息增益值
    bestFeature = -1  # 最优特征点索引
    for i in range(numFeatures):  # 遍历每个特征
        # example 可以提取出列表第几个元素,返回一个新集合
        featList = [example[i] for example in dataSet]
        # print(featList)
        uniqueVals = set(featList)  # 设置set集合,里面的每一个元素 都是集合中的一项
        # 这里也就是把当前特征点所有特征值提取出来
        # print(uniqueVals)
        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
    return bestFeature
def majorityCnt(classList):
    classCount = {}
    for vote in classList:
        if vote not in classCount.keys(): classCount[vote] = 0
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.iteritems(), 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):  # 两个判断结束的标志 数量相等||数据个数为1
        # print('in flag one      ', classList[0])
        return classList[0]
    if len(dataSet[0]) == 1:
        # print('in flag two      ', majorityCnt(classList))
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)  # 选择信息增益值最大的特征点,返回特征点索引
    bestFeatLabel = labels[bestFeat]
    myTree = {bestFeatLabel: {}}
    # print('out of for   ', myTree)
    del (labels[bestFeat])
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        subLabels = labels[:]
        # myTree中 bestFeatLabel中值为value的值是 返回值
        # 这样就做到了 {'no surfacing': {0: 'no'}} 类似
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
        # print('inner for    ', myTree)
    return myTree
if __name__ == '__main__':
    dataSet, labels = createDataSet()
    print(dataSet)
    print()
    # print(chooseBestFeatureToSplit(dataSet))
    print('result', createTree(dataSet, labels))

 
 
treePlotter.py

# -*- coding:utf-8 -*-
import operator
import matplotlib.pyplot as plt
# 绘制属性图
decisionNode = dict(boxstyle="sawtooth", fc="0.8")
leafNode = dict(boxstyle="round4", fc="0.8")  # 规定属性待大小
arrow_args = dict(arrowstyle="<-")  # 规定箭头方向
def plotNode(nodeTxt, centerPt, parentPt, nodeType):
    # 在全局变量createPlot.ax1中绘图
    createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',
                            xytext=centerPt, textcoords='axes fraction',
                            va="center", ha="center", bbox=nodeType, arrowprops=arrow_args)
def createPlot1():
    # 创建一个新图形
    fig = plt.figure(1, facecolor='white')
    # 清空绘图区
    fig.clf()
    # 给全局变量createPlot.ax1赋值
    createPlot.ax1 = plt.subplot(111, frameon=False)
    # 设置起点值
    plotNode('a decision node', (0.5, 0.1), (0.1, 0.5), decisionNode)
    plotNode('a leaf node', (0.8, 0.1), (0.3, 0.8), leafNode)
    plt.show()
def getNumLeafs(myTree):
    numLeafs = 0
    # 这里因为python2 和python3 对keys 返回类型的更改,python2 返回的是一个列表
    # python3 返回的是一个对象 更像一个集合
    # 所以这里需要传给list然后再索引
    firstStr = list(myTree.keys())[0]
    # 获得关键字下的数据 即当前结点下的子树
    secondDict = myTree[firstStr]
    # 遍历子树
    for key in secondDict.keys():
        # 运用自带的方法判断是不是字典 如果是则递归 如果不是则叶子数+1
        if type(secondDict[key]).__name__ == 'dict':
            numLeafs += getNumLeafs(secondDict[key])
        else:
            numLeafs += 1
    return numLeafs
def getTreeDepth(myTree):
    maxDepth = 0
    firstStr = list(myTree.keys())[0]
    secondDict = myTree[firstStr]
    for key in secondDict.keys():
        # 如果是字典 则继续向下递归
        if type(secondDict[key]).__name__ == 'dict':
            thisDepth = 1 + getTreeDepth(secondDict[key])
        else:
            thisDepth = 1
            # 找出最大深度作为这棵树的深度
        if thisDepth > maxDepth:
            maxDepth = thisDepth
    return maxDepth
def retrieveTree(i):
    listOfTrees = [{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}},
                   {'no surfacing': {0: 'no', 1: {'flippers': {0: {'head': {0: 'no', 1: 'yes'}}, 3: 'maybe'}}}},
                   {'no surfacing': {0: 'no', 1: {'flippers': {0: {'head': {0: 'no', 1: 'yes'}}, 1: 'no'}}}}
                   ]
    return listOfTrees[i]
# 计算父节点和子节点的中间位置 并在此处添加简单的文本信息
def plotMidText(cntrPt, parentPt, txtString):
    xMid = (parentPt[0] - cntrPt[0]) / 2.0 + cntrPt[0]
    yMid = (parentPt[1] - cntrPt[1]) / 2.0 + cntrPt[1]
    createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)
def plotTree(myTree, parentPt, nodeTxt):
    # 计算树的宽
    # 因为这个函数需要被用来递归 所以全局的两个深度和叶子节点个数不能被重复使用
    # 需要重新被定义
    numLeafs = getNumLeafs(myTree)
    # 计算树的高
    depth = getTreeDepth(myTree)
    firstStr = list(myTree.keys())[0]
    # 下一个节点的位置
    cntrPt = (plotTree.xOff + (1.0 + float(numLeafs)) / 2.0 / plotTree.totalW, plotTree.yOff)
    # 计算父节点和子节点的中间位置,并在此处添加简单的文本信息
    plotMidText(cntrPt, parentPt, nodeTxt)
    # 绘制此节点带箭头的注解
    plotNode(firstStr, cntrPt, parentPt, decisionNode)
    # 新的树,即当前结点的子树
    secondDict = myTree[firstStr]
    # 按比例减少全局变量plotTree.yOff
    plotTree.yOff = plotTree.yOff - 1.0 / plotTree.totalD
    for key in secondDict.keys():
        # 判断子节点是否为字典类型
        if type(secondDict[key]).__name__ == 'dict':
            # 是的话表明该节点也是一个判断节点,递归调用plotTree()函数
            plotTree(secondDict[key], cntrPt, str(key))
        else:
            # 不是的话更新x坐标值
            plotTree.xOff = plotTree.xOff + 1.0 / plotTree.totalW
            # 绘制此节点带箭头的注解
            plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
            # 绘制此节点带箭头的注解
            plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
    # 按比例增加全局变量plotTree.yOff
    plotTree.yOff = plotTree.yOff + 1.0 / plotTree.totalD
def createPlot(inTree):
    # 创建一个新图形
    fig = plt.figure(1, facecolor='white')
    # 清空绘图区
    fig.clf()
    # 创建一个字典
    axprops = dict(xticks=[], yticks=[])
    # 给全局变量createPlot.ax1赋值
    createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)
    plotTree.totalW = float(getNumLeafs(inTree))
    plotTree.totalD = float(getTreeDepth(inTree))
    # 设置起点值
    plotTree.xOff = -0.5 / plotTree.totalW
    plotTree.yOff = 1.0
    # 绘制数
    plotTree(inTree, (0.5, 1.0), '')
    plt.show()
if __name__ == '__main__':
    tree = retrieveTree(0)
    tree1 = retrieveTree(1)
    tree2 = retrieveTree(2)
    createPlot(tree2)

 


0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注