我的机器学习之旅(六):决策树

决策树概念:

分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点和有向边组成。结点有两种类型:内部节点和叶节点,内部节点表示一个特征或属性,叶节点表示一个类。 
分类的时候,从根节点开始,对实例的某一个特征进行测试,根据测试结果,将实例分配到其子结点;此时,每一个子结点对应着该特征的一个取值。如此递归向下移动,直至达到叶结点,最后将实例分配到叶结点的类中。

例如判断某款物品的潜在买家:

决策树可以看成一个if-then规则的集合:由决策树的根结点到叶结点的每一条路径构建一条规则;路径上的内部结点的特征对应着规则的条件,而叶结点对应着分类的结论。决策树的路径和其对应的if-then规则集合是等效的,它们具有一个重要的性质:互斥并且完备。这里的意思是说:每一个实例都被一条路径或一条规则所覆盖,而且只被一条规则所覆盖。

几个基本概念

特征选择问题希望选取对训练数据具有良好分类能力的特征,这样可以提高决策树学习的效率。如果利用一个特征进行分类的结果与随机分类的结果没有很大差别,则称这个特征是没有分类能力的(对象是否喜欢打游戏应该不会成为关键特征吧,也许也会……)。为了解决特征选择问题,找出最优特征,先要介绍一些信息论里面的概念。

1.信息熵(香农熵)

熵是表示随机变量不确定性的度量。设X是一个取有限个值的离散随机变量,其概率分布为:

随机变量的熵为:

熵越大,随机变量的不确定性就越大。

数据集熵值计算函数:

from math import logdef calcShannonEnt(dataSet):
    """dataSet 为前n-1列为特征,最后一列为类别的数据集 """
    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) # 以2为底的对数
    return shannonEnt

2.条件熵

设有随机变量(X,Y),其联合概率分布为

条件熵H(Y|X)表示在已知随机变量XX的条件下随机变量Y的不确定性。随机变量X给定的条件下随机变量Y的条件熵H(Y|X),定义为X给定条件下Y的条件概率分布的熵对X的数学期望:

这里,pi=P(X=xi),i=1,2,?,n.pi=P(X=xi),i=1,2,?,n.

3.信息熵

信息增益表示得知特征XX的信息而使得类Y的信息的不确定性减少的程度。特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即

这个差又称为互信息。信息增益大的特征具有更强的分类能力。 
根据信息增益准则的特征选择方法是:对训练数据集(或子集)计算其每个特征的信息增益,选择信息增益最大的特征。 
计算信息增益的算法如下:

通过划分数据集,并计算信息增益

def splitDataSet(dataSet, axis, value):
    ‘‘‘
    按照给定特征划分数据集
    :param dataSet:待划分的数据集
    :param axis:划分数据集的特征
    :param value: 需要返回的特征的值
    :return: 划分结果列表
    ‘‘‘
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]     #chop out axis used for splitting
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

def calcConditionalEntropy(dataSet, i, featList, uniqueVals):
    ‘‘‘
    计算X_i给定的条件下,Y的条件熵
    :param dataSet:数据集
    :param i:维度i
    :param featList: 数据集特征列表
    :param uniqueVals: 数据集特征集合
    :return: 条件熵
    ‘‘‘
    conditionEnt = 0.0
    for value in uniqueVals:
        subDataSet = splitDataSet(dataSet, i, value)
        prob = len(subDataSet) / float(len(dataSet))  # 极大似然估计概率
        conditionEnt += prob * calcShannonEnt(subDataSet)  # 条件熵的计算
    return conditionEnt

def calcInformationGain(dataSet, baseEntropy, i):
    ‘‘‘
    计算信息增益
    :param dataSet:数据集
    :param baseEntropy:数据集的信息熵
    :param i: 特征维度i
    :return: 特征i对数据集的信息增益g(D|X_i)
    ‘‘‘
    featList = [example[i] for example in dataSet]  # 第i维特征列表
    uniqueVals = set(featList)  # 转换成集合
    newEntropy = calcConditionalEntropy(dataSet, i, featList, uniqueVals)
    infoGain = baseEntropy - newEntropy  # 信息增益,就yes熵的减少,也就yes不确定性的减少
    return infoGain

两种算法

1,ID3算法

ID3算法由Ross Quinlan发明,建立在“奥卡姆剃刀”的基础上:越是小型的决策树越优于大的决策树(be simple简单理论)。ID3算法中根据信息增益评估和选择特征,每次选择信息增益最大的特征作为判断模块建立子结点。ID3算法可用于划分标称型数据集,没有剪枝的过程,为了去除过度数据匹配的问题,可通过裁剪合并相邻的无法产生大量信息增益的叶子节点(例如设置信息增益阀值)。使用信息增益的话其实是有一个缺点,那就是它偏向于具有大量值的属性。就是说在训练集中,某个属性所取的不同值的个数越多,那么越有可能拿它来作为分裂属性,而这样做有时候是没有意义的,另外ID3不能处理连续分布的数据特征,于是就有了C4.5算法。CART算法也支持连续分布的数据特征。

步骤:

ID3算法思想描述:

a.对当前例子集合,计算属性的信息增益;

b.选择信息增益最大的属性Ai(关于信息增益后面会有详细叙述)

c.把在Ai处取值相同的例子归于同于子集,Ai取几个值就得几个子集

d.对依次对每种取值情况下的子集,递归调用建树算法,即返回a,

e.若子集只含有单个属性,则分支为叶子节点,判断其属性值并标上相应的符号,然后返回调用处。

寻找最优划分方式代码

def chooseBestFeatureToSplitByID3(dataSet):
    ‘‘‘
            选择最好的数据集划分方式
    :param dataSet:数据集
    :return: 划分结果
    ‘‘‘
    numFeatures = len(dataSet[0]) - 1  # 最后一列yes分类标签,不属于特征向量
    baseEntropy = calcShannonEnt(dataSet)
    bestInfoGain = 0.0
    bestFeature = -1
    for i in range(numFeatures):  # 遍历所有特征
        infoGain = calcInformationGain(dataSet, baseEntropy, i)     # 计算信息增益
        if (infoGain > bestInfoGain):  # 选择最大的信息增益
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature  # 返回最优特征对应的维度

2.C4.5算法

C4.5算法是用于生成决策树的一种经典算法,是ID3算法的一种延伸和优化。C4.5算法对ID3算法主要做了一下几点改进: 
??(1)通过信息增益率选择分裂属性,克服了ID3算法中通过信息增益倾向于选择拥有多个属性值的属性作为分裂属性的不足; 
??(2)能够处理离散型和连续型的属性类型,即将连续型的属性进行离散化处理; 
??(3)构造决策树之后进行剪枝操作; 
??(4)能够处理具有缺失属性值的训练数据。

    • 优点: 
      (1)通过信息增益率选择分裂属性,克服了ID3算法中通过信息增益倾向于选择拥有多个属性值的属性作为分裂属性的不足; 
      (2)能够处理离散型和连续型的属性类型,即将连续型的属性进行离散化处理; 
      (3)构造决策树之后进行剪枝操作; 
      (4)能够处理具有缺失属性值的训练数据。
    • 缺点: 
      (1)算法的计算效率较低,特别是针对含有连续属性值的训练样本时表现的尤为突出。 
      (2)算法在选择分裂属性时没有考虑到条件属性间的相关性,只计算数据集中每一个条件属性与决策属性之间的期望信息,有可能影响到属性选择的正确性。

步骤:

代码:

创建树

def majorityCnt(classList):
    ‘‘‘
    采用多数表决的方法决定叶结点的分类
    :param: 所有的类标签列表
    :return: 出现次数最多的类
    ‘‘‘
    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):
    ‘‘‘
    创建决策树
    :param: dataSet:训练数据集
    :return: labels:所有的类标签
    ‘‘‘
    classList = [example[-1] for example in dataSet]
    if classList.count(classList[0]) == len(classList):
        return classList[0]             # 第一个递归结束条件:所有的类标签完全相同
    if len(dataSet[0]) == 1:
        return majorityCnt(classList)   # 第二个递归结束条件:用完了所有特征
    #bestFeat = chooseBestFeatureToSplitByID3(dataSet)   # 最优划分特征
    bestFeat = chooseBestFeatureToSplitByC45(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[:]       # 复制所有类标签,保证每次递归调用时不改变原始列表的内容
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
    return myTree

测试分类

构造了决策树之后,我们就可以将它用于实际数据的分类,在执行分类时,需要输入决策树和用于构造树的所有类标签向量。然后,程序比较测试数据与决策树上的数值,递归执行该过程直到进入叶结点;最后将测试数据定义为叶结点所属的类型。

def classify(inputTree,featLabels,testVec):
    ‘‘‘
           利用决策树进行分类
    :param: inputTree:构造好的决策树模型
    :param: featLabels:所有的类标签
    :param: testVec:测试数据
    :return: 分类决策结果
    ‘‘‘
    firstStr = inputTree.keys()[0]
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstStr)
    key = testVec[featIndex]
    valueOfFeat = secondDict[key]
    if isinstance(valueOfFeat, dict):
        classLabel = classify(valueOfFeat, featLabels, testVec)
    else: classLabel = valueOfFeat
    return classLabel

可视化树

import matplotlib.pyplot as plt
import tree
# 定义文本框和箭头格式
decisionNode = dict(boxstyle="round4", color=‘#3366FF‘)  # 定义判断结点形态
leafNode = dict(boxstyle="circle", color=‘#FF6633‘)  # 定义叶结点形态
arrow_args = dict(arrowstyle="<-", color=‘g‘)  # 定义箭头

#计算叶结点数
def getNumLeafs(myTree):
    numLeafs = 0
    firstStr = list(myTree.keys())[0]
    secondDict = myTree[firstStr]
    for key in secondDict.keys():
        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 plotNode(nodeTxt, centerPt, parentPt, nodeType):
    createPlot.ax1.annotate(nodeTxt, xy=parentPt,  xycoords=‘axes fraction‘,
             xytext=centerPt, textcoords=‘axes fraction‘,
             va="center", ha="center", bbox=nodeType, arrowprops=arrow_args )

# 在父子结点间填充文本信息
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 - 1.0/plotTree.totalD # 减少y偏移
    for key in secondDict.keys():
        if type(secondDict[key]).__name__==‘dict‘:
            plotTree(secondDict[key],cntrPt,str(key))        #recursion
        else:   #it‘s a leaf node print the leaf node
            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 + 1.0/plotTree.totalD
#if you do get a dictonary you know it‘s a tree, and the first element will be another dict

def createPlot(inTree):
    fig = plt.figure(1, facecolor=‘white‘)
    fig.clf()
    axprops = dict(xticks=[], yticks=[])
    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()

两个测试用例

1.

myDat = [[‘youth‘, ‘no‘, ‘no‘, ‘1‘, ‘refuse‘],
               [‘youth‘, ‘no‘, ‘no‘, ‘2‘, ‘refuse‘],
               [‘youth‘, ‘yes‘, ‘no‘, ‘2‘, ‘agree‘],
               [‘youth‘, ‘yes‘, ‘yes‘, ‘1‘, ‘agree‘],
               [‘youth‘, ‘no‘, ‘no‘, ‘1‘, ‘refuse‘],
               [‘mid‘, ‘no‘, ‘no‘, ‘1‘, ‘refuse‘],
               [‘mid‘, ‘no‘, ‘no‘, ‘2‘, ‘refuse‘],
               [‘mid‘, ‘yes‘, ‘yes‘, ‘2‘, ‘agree‘],
               [‘mid‘, ‘no‘, ‘yes‘, ‘3‘, ‘agree‘],
               [‘mid‘, ‘no‘, ‘yes‘, ‘3‘, ‘agree‘],
               [‘elder‘, ‘no‘, ‘yes‘, ‘3‘, ‘agree‘],
               [‘elder‘, ‘no‘, ‘yes‘, ‘2‘, ‘agree‘],
               [‘elder‘, ‘yes‘, ‘no‘, ‘2‘, ‘agree‘],
               [‘elder‘, ‘yes‘, ‘no‘, ‘3‘, ‘agree‘],
               [‘elder‘, ‘no‘, ‘no‘, ‘1‘, ‘refuse‘],
               ]
labels = [‘age‘, ‘working?‘, ‘house?‘, ‘credit_situation‘]

myTree = createTree(myDat, labels)
createPlot(myTree)
labels = [‘age‘, ‘working?‘, ‘house?‘, ‘credit_situation‘]
print(classify(myTree,labels ,[‘youth‘,‘no‘,‘no‘,1]))###结果为refuse

2.网上数据

import pandas as pd
import numpy as np

name_c=[‘sample code number‘,‘clump thickness‘, ‘uniformity of cell size‘, ‘uniformity of cell shape‘,‘marginal adhesion‘,‘single epithelial cell‘,
       ‘bare nuclei‘,‘bland chromatin‘,‘normal nucleoli‘,‘mitoses‘,‘class‘
       ]
data=pd.read_csv(‘http://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data‘,names=name_c)
data=data.replace(to_replace=‘?‘,value=np.nan)
data=data.dropna(how=‘any‘)
myDat=data[name_c[1:]].values[:].tolist()
myTree = createTree(myDat, name_c[1:])
createPlot(myTree)
print( classify(myTree,name_c[1:],[5,3,2,1,3,‘1‘,1,1,1]))

###结果为2

原文地址:https://www.cnblogs.com/allenren/p/8660415.html

时间: 2024-11-10 16:09:18

我的机器学习之旅(六):决策树的相关文章

机器学习搭便车指南–决策树(1)

机器学习搭便车指南–决策树(1) 1. 决策树的基本概念 通常使用的分类回归树(class and regress tree)是一个二叉树.它的形式一般为:  决策树有两种节点: 中间节点和叶子节点. 每个中间节点有4个参数: a) 决策函数. 是一个特征的取值. 当特征小于等于这个值得时候决策路径走左边, 当特征大于这个值得时候决策树走右边. b) 不纯度值(impurity value). 是当前节点的不纯度值. 关于不纯度值得意义后面会讲到. c) 覆盖样本个数(n_samples). 是

机器学习基石第六讲:theory of generalization

博客已经迁移至Marcovaldo's blog (http://marcovaldong.github.io/) 机器学习基石第六讲继续讨论"学习是否可行的问题". Restriction of Break Point 继续前面的讨论,我们看mH(N)是否会有一个很小的增长速度.回顾前面的四种成长函数及其break point.我们知道k是一个成长函数的break point,那比k大的值全是break point. mH(N)是一个hypothesis在N个数据点上可以产生的dic

机器学习中的算法——决策树模型组合之随机森林与GBDT

前言: 决策树这种算法有着很多良好的特性,比如说训练时间复杂度较低,预测的过程比较快速,模型容易展示(容易将得到的决策树做成图片展示出来)等.但是同时,单决策树又有一些不好的地方,比如说容易over-fitting,虽然有一些方法,如剪枝可以减少这种情况,但是还是不够的. 美国金融银行业的大数据算法:随机森林模型+综合模型 模型组合(比如说有Boosting,Bagging等)与决策树相关的算法比较多,这些算法最终的结果是生成N(可能会有几百棵以上)棵树,这样可以大大的减少单决策树带来的毛病,有

从软件工程的角度写机器学习4——-C4.5决策树的工程实现

C4.5决策树的工程实现 这篇文章开始,将讲述一系列机器学习算法的工程实现方案.出于常用且简单的考虑,选择了C4.5决策树作为第一个算法. 工程框架 鉴于本篇是第一个算法实现,应此需要把整个工程框架介绍一下. 出于最优性能考虑,本框架是为C/C++语言设计的.不过即使用其他语言,也可以按这个框架实现,模块还可以再精简. 本工程定位: 1.无脑版机器学习算法库,使用者基本不需要了解任何算法细节,也不需要了解配置的算法参数含义. 2.可分离的算法库,算法库输出的模型文件可以方便地被其他工程解析使用.

机器学习笔记-监督学习之决策树

0机器学习中分类和预测算法的评估: 准确率 速度 健壮性 可规模性 可解释性 1决策树(判定树)的概念 决策树是一个类似于流程图的树结构(可以是二叉树或多叉树):其中,每个内部结点表示在一个属性上的测试,每个分支代表一个属性输出,而每个树叶结点代表类或类分布.树的最顶层是根结点.机器学习中分类方法中的一个重要算法. 2.熵(entropy)概念 信息和抽象如何度量?1948年,香农提出"信息熵"的概念. 一条信息的信息量大小和它的不确定性有直接的关系,要搞清楚一件非常非常不确定的事情,

机器学习实战笔记3(决策树)

决策树的优势就在于数据形式非常容易理解,而kNN的最大缺点就是无法给出数据的内在含义. 1:简单概念描述 决策树的类型有很多,有CART.ID3和C4.5等,其中CART是基于基尼不纯度(Gini)的,这里不做详解,而ID3和C4.5都是基于信息熵的,它们两个得到的结果都是一样的,本次定义主要针对ID3算法.下面我们介绍信息熵的定义. 事件ai发生的概率用p(ai)来表示,而-log2(p(ai))表示为事件ai的不确定程度,称为ai的自信息量,sum(p(ai)*I(ai))称为信源S的平均信

机器学习(三)——决策树(decision tree)算法介绍

0.机器学习中分类和预测算法的评估标准 准确率 速度 强壮性 可规模性 可解释性 1.什么是决策树/判定树(decision tree)? 判定树是一个类似于流程图的树结构:其中,每个内部节点表示一个属性上的测试,每个分支代表一个属性的输出,而每个树叶节点代表类或者类分布.树的最顶层是根节点. 2.机器学习中分类方法的重要算法是决策树 3.构造决策树的基本算法 3.1熵(entropy)概念    信息和抽象,如何度量?1948年,香农提出了 "信息熵(entropy)"的概念,一条信

用Python开始机器学习(2:决策树分类算法)

http://blog.csdn.net/lsldd/article/details/41223147 从这一章开始进入正式的算法学习. 首先我们学习经典而有效的分类算法:决策树分类算法. 1.决策树算法 决策树用树形结构对样本的属性进行分类,是最直观的分类算法,而且也可以用于回归.不过对于一些特殊的逻辑分类会有困难.典型的如异或(XOR)逻辑,决策树并不擅长解决此类问题. 决策树的构建不是唯一的,遗憾的是最优决策树的构建属于NP问题.因此如何构建一棵好的决策树是研究的重点. J. Ross Q

机器学习(四)—决策树

摘要:本文首先浅谈了自己对决策树的理解,进而通过Python一步步构造决策树,并通过Matplotlib更直观的绘制树形图,最后,选取相应的数据集对算法进行测试. 最近在看<机器学习实战>这本书,因为一直想好好了解机器学习方面的算法,加之想学Python,就在朋友的推荐之下选择了这本同等定位的书.今天就来学习一下决策树,所有的代码均python3.4实现,确实与2.7有很多不同. 决策树和KNN一样,都是处理分类问题的算法.对于决策树的定义不计其数,就我个人而言,首先单看名字,就想到了最小生成