决策树是最早的机器学习算法之一。在1966年提出的CLS学习系统中有了决策树算法的概念,直到1979年才有了ID3算法的原型,1983-1986,ID3算法被进行了总结和简化,正式确立了决策树学习的理论,从机器学习的角度来看,这是决策树算法的起点,1986年,科学家在此基础上进行了改造,引入了节点缓冲区,提出ID4算法,1993年,ID3算法又得到了进一步发展,改进成C4.5算法,成为机器学习的十大算法之一。ID3的另外一根分支是分类回归决策树算法,与C4.5不同的是,分类回归决策树算法主要用于预测,这样决策树理论就完整地覆盖了机器学习中的分类和回归两个领域。本篇主要包括:

  • 决策树的算法思想
  • 信息熵和ID3
  • C4.5算法
  • Scikit-Learn与回归树

4.1 决策树的基本思想

  决策树的思想来源非常朴素,每个人的大脑都有类似if-then这样的逻辑判断,其中If表示条件,then就是选择或者是决策。最早的决策树就是利用这类结构分隔数据。下面从一个实例来讲解最简单的决策树的生成过程。

4.1.1 从一个实例开始 

  假设某家IT公司销售笔记本电脑产品, 为了提高销售收入,公司对各类客户建立了统一的调查表,统计了几个月的销售数据后得到如下的表格:

  老板为了提高销售的效率,希望你通过对表中的潜在客户进行分类,以利于销售人员的工作。这就出现了两个问题:

  1.如何对客户进行分类

  2.如何根据分类的依据,给出对销售人员的指导意见?

问题分析:

  从第一列来看这张表格,表格不大,一共15行,每行表示列去特征值不同值的统计人数。最后一列可以理解为分类标签,去两个值:麦,不买。

  那么对于任意给定特征值的一个客户,算法需要帮助公司将这位客户归类,也就是预测这位客户属于买计算机的那一类,还是输入不买计算机的那一类,并给出判断的依据。

  下面引入CLS(Concept Learning System)算法的思想。为了便于理解,我们先用手工实现上例的决策树。我们将决策树设计为三类节点:根节点,叶子节点和内部节点。如果从一棵空决策树开始,任意选择第一个特征就是根节点;我们按照某种条件进行划分,如果划分到某个子集为空,或子集中的所有样本都已经归为同一个类别标签,那么该子集就是叶节点,否则这些子集就对应于决策树的内部节点;如果是内部节点,就需要选择一个新的类别标签继续对该子集进行划分,直到所有的子集都为叶子节点,即为空或者属于同一类。

  接下来我们按照上述规则进行划分。我们选年龄作为根节点,这个特征值取三个值:老,中,青。我们将所有的样本分为老,中青三个集合,构成决策树的第一层。

  现在我们暂时忽略其它特征,仅关注年龄,将表变为如下形式

  (1)年龄=青,是否购买:不买,买

  (2)年龄=中,是否购买:买

  (3)年龄=老,是否购买:不买,买

  当年龄为中年时,是否购买标签都一致地变为买,此时的中年就称为决策树的叶子节点。当年龄为青年和老年时,是否购买有两个选择,可以继续分解。

  现在,将年龄特征等于青年的选项剪切处理,构成一张新的表格,选择第二个特征---收入,并根据收入排序:

  其中,高收入和低收入的特征值只有一个类别标签,将其作为叶子节点。然后继续划分中等收入的下一个特征---学生,就有了下表:

  学生特征只有两个取值,当取否是,对对应的标签为不买,当取是时,对应的标签为买。次数,学生特征就生成了决策树左侧分支的所有节点。

  如下图所示:(其中圆角矩阵为根节点或者内部节点,也就是可以继续划分的节点;椭圆节点是叶子节点,不能再划分,一般叶子节点都指向一个分类标签,即产生一种决策)。

  接下来,继续右侧分支的划分,这里划分我们做一个简单的变化,划分的顺序为信誉->收入->学生->计数,这样整个划分过程就变得简单了。当信誉为良时类别标签仅有一个选项,就是买,那么信誉为良的叶子节点:当信誉取值为优的时候,类别标签仅有一个选项,就是不买,如下图所示::

  最终的划分结果如下图:

  我们把所有买的节点都放在右侧,这样,对于任何用户,当出现从内部向左到叶子节点的路径时,就是不购买的用户。

  从定性的角度对潜在客户做出判断,下面给出定量的判断:

  我们知道,计数特征总数为1024,将途中的路径变除以1024,就得到了每个节点的购买概率。

4.1.2 决策树的算法框架

  1.决策树主函数

  各种决策树的主函数都大同小异,本质上是一个递归函数。这个函数的主要功能是按照某种规则生长出决策树的各个分支节点,并根据终止条件结束算法。一般来说,主函数需要完成以下几个功能:

  2.计算最优特征子函数

  计算最优特征子函数是除了主函数外最重要的函数。每种决策树之所以不同,一般都是因为最优特征选择的标准上有所差异,不同的标准导致不同的决策树,例如ID3的最优特征值选择标准是信息增益,C4.5是信息增益率,CART是节点方差的大小等。后面所讲的理论部分,都是针对特征选择标准而言的。

  在逻辑算法上,一般选择最优特征需要遍历整个数据集,评估每个特征,找到最优的那一个特征返回。

  3.划分数据集函数

  划分数据集函数的主要功能是分割数据集。有的需要删除某个特征轴所在的数据列,返回剩余的数据集;有的干脆将数据集一分为二。虽然实现有所不同,但基本含义都是一致的。

  4.分类器

  所有的机器学习算法都要用于分类或回归预测。决策树的分类器就是通过遍历整个决策树,使测试集数据找到决策树中叶子节点对应的类别标签。这个标签就是返回的结果。

  以上四大部分构成了决策树的基本框架。

4.1.3 信息熵测度

  虽然之前手工实现了上述例子的决策过程,但是将这种实现方法使用编程形式自动计算还存在一些问题。首先,特征集中的数据常常表现为定性字符串数据,称为标称数据,使用这些数据的算法缺乏泛化能力,在实际计算中需要将这些数据量化为数字,也就是离散化。

  例如我们可以将年龄,收入,学生,信誉这些特征值转换为0,1,2,...,n的形式。这样:

  完成了特征离散化,回顾一下前面的手工计算过程,我们可以总结出这样一条规律:数据特征的划分过程是一个将数据集从无序变为有序的过程。这样我们就可以处理特征的划分依据问题,即对于一个由多维特征构成的数据集,如何优选出某个特征作为根节点?进一步扩展这个问题:如何每次都选出特征集中无序度最大的那列特征作为划分节点?

  为了衡量一个事物特征值取值的有(无)序程度,下面我们引入一个重要的概念:信息熵。为了便于理解,我们将这个词拆分为两部分:“信息”和“熵”。

  所谓“熵”就是表示 任何一种能量在空间中分布的均匀程度。分布越均匀,熵就越大。在定义熵之前,香农定义了信息的概念:信息就是对不确定的概念或者认识的消除。

  上式中的对数一般取2位底,就是平常所说的信息单位bit。

  信息熵是事物不确定性的度量标准,也称为信息的单位或“测度”。在决策树中个,它不仅能用来衡量类别的不确定性,也可以用来度量包含不同特征的数据样本和类别的不确定性。也就是说,如果某个特征列向量的信息熵越大,就说明该向量的不确定性程度越大,即混乱程度越大,这时候就应该优先考虑从该特征向量着手来进行划分。信息熵为决策树的划分提供了最重要的依据和标准。

4.2 ID3决策树

4.2.1 ID3算法

  有了上面的概念,我们就可以手工实现以下ID3算法的决策树生成过程。

  (1)计算对给定样本所需要的信息熵。

如下图所示:

  类别标签S被分成两类:买或不买。其中S1(买)=640;S2(不买)=384。那么总S=S1+S2=1024。S1的概率P1=640/1024=0.625;S2的概率P2=384/1024=0.375。

根据公式:

(注:以上log以2为底 )

(2) 计算每个特征的信息熵。

  

根据公式:

根据公式:

(总的-部分的)

  按照这样的裸机价产生的决策树如下:

  从图中可以看出使用信息熵生成的决策树要比之前我们手动设计的决策树层数要少。如果数据集的特征很多,那么使用信息熵创建决策树在结构上要明显优于其他方法。

4.2.2 ID3的实现

  前面我们使用手工计算实现了一棵决策树,接下来,我们将其转换为编码进行实现。先定义一个ID3DTree类来封装算法。

训练集:dataset.dat 关注公众号d528848 回复dataset.data获取

python代码:

from numpy import *
import math
import copy
import pickle
import treePlotter as tpclass ID3DTree(object):def __init__(self):self.tree={ }#生成的树self.dataSet = []#数据集self.labels={}#标签集#导入数据def loadDataSet(self,path,labels):recordlist = []fp = open(path,"rb")#读取文件内容content = fp.read().decode()fp.close()rowlist = content.splitlines()#按行转换成一维表recordlist = [row.split("\t") for row in rowlist if row.strip()]print(recordlist)self.dataSet = recordlistself.labels = labelsdef train(self):labels = copy.deepcopy(self.labels)print(labels)self.tree = self.buildTree(self.dataSet,labels)#构建决策树,创建决策树主程序def buildTree(self,dataSet,labels):#抽取源数据集的决策标签列cateList = [data[-1] for data in dataSet]print(cateList)#程序终止条件1:如果cateList只有一种决策标签,停止划分,返回这个决决策标签if cateList.count(cateList[0]) == len(cateList):return cateList[0]#程序终止条件2:如果数据集的第一个决策标签只有一个,则#返回这个决策标签if len(dataSet[0]) == 1:return self.maxCate(cateList)#算法核心:bestFeat = self.getBestFeat(dataSet) #返回数据集的最优特征值print("bestFeat:",bestFeat)bestFeatLabel = labels[bestFeat]print("bestFeatLabel",bestFeatLabel)tree = {bestFeatLabel:{}}del(labels[bestFeat])#再次抽取最优特征轴的列向量uniqueVals = set([data[bestFeat] for data in dataSet])#去重print("uniqueVals:",uniqueVals)for value in uniqueVals:#决策树的递归增长subLabels = labels[:] #将删除后的特征类别集建立子类别集#按最优特征列和值分隔数据集,即筛选出第bestFeat列值为value的所有元素,返回的是去掉这一列后剩下的数据#本例中根据bestFeat划分为买或者是不买这两个类别,作为左右子树,再继续划分splitDataset = self.splitDataSet(dataSet,bestFeat,value)subTree = self.buildTree(splitDataset,subLabels)tree[bestFeatLabel][value] = subTreereturn tree#计算出现次数最多的类别标签def maxCate(self,catelist):items = dict([(catelist.count(i),i) for  i in catelist])return items([max(items.keys())])#计算最优特征def getBestFeat(self,dataSet):#计算特征向量维,其中最后一列用于类别标签,因此要减去print(dataSet[0])numFeatures = len(dataSet[0]) - 1#特征向量维数-1baseEntropy = self.computeEntropy(dataSet) #基础熵:源数据的香农熵,即买或者不买计算出来的熵值print("baseEntropy:",baseEntropy)bestInfoGain = 0.0 #初始化最优的信息增益bestFeature = -1 #初始化最优的特征轴#外循环:遍历数据集各列,计算最优特征轴#i为数据集列索引:取值范围:0-(numFeatures-1)#依次遍历每个特征for i in range(numFeatures):uniqueVals = set([data[i] for data in dataSet])#去重:该列的唯一值集,也就是求类别,比如青年,中年,老年print("uniqueVals:",uniqueVals)newEntropy = 0.0#初始化该列的香农熵#算不同类型中买与不买的比例以及熵for value in uniqueVals:#内循环按列和唯一值计算香农熵#按选定列i和唯一值分隔数据集,即筛选出第i列值为value的所有元素,返回的是去掉这一列后剩下的数据subDataSet = self.splitDataSet(dataSet,i,value)#各自在总体样本中所占的比例prob = len(subDataSet) / float(len(dataSet))print("prob:",prob)#概率*对应小类别中买与不买的熵值newEntropy += prob * self.computeEntropy(subDataSet)infoGain = baseEntropy - newEntropy #计算最大增益print("infoGain:",infoGain)if(infoGain > bestInfoGain):#如果信息增益>0bestInfoGain = infoGain #用当前信息增益代替之前的最优增益bestFeature = i #重置最优特征为当前列return bestFeature#计算信息熵def computeEntropy(self,dataSet):#计算香农熵datalen = float(len(dataSet))print("datalen",datalen)cateList = [data[-1] for data in dataSet]#从数据集中得到类别标签print("cateList",cateList)#得到类别为key,出现次数value的字典items = dict([(i,cateList.count(i)) for i in cateList])print("items:",items)infoEntropy = 0.0 #初始化香农熵for key in items:#香农熵 = -plog2(p)prob = float(items[key])/dataleninfoEntropy -= prob*math.log(prob,2)return infoEntropy#划分数据集,分隔数据集,删除特征轴所在的数据列,返回剩余的数据集def splitDataSet(self,dataSet,axis,value):rtnList = []for featVec in dataSet:if featVec[axis] == value:rFeatVec = featVec[:axis] #取0-(axis-1)的元素rFeatVec.extend(featVec[axis+1:])#将特征轴(列)之后的元素加回
                rtnList.append(rFeatVec)return rtnListdtree = ID3DTree()
#["age","revenue","student","credit"]
dtree.loadDataSet(r"C:\Users\Administrator\Desktop\dataset.dat",["age","revenue","student","credit"])
dtree.train()
print(dtree.tree)
tp.createPlot(dtree.tree)

所用到的画树的类:treePlotter.py:

'''
Created on Oct 14, 2010@author: Peter Harrington
'''
import matplotlib.pyplot as pltdecisionNode = dict(boxstyle="sawtooth", fc="0.8")
leafNode = dict(boxstyle="round4", fc="0.8")
arrow_args = dict(arrowstyle="<-")def getNumLeafs(myTree):numLeafs = 0firstStr = list(myTree.keys())[0]secondDict = myTree[firstStr]for key in secondDict.keys():if type(secondDict[key]).__name__ == 'dict':  # test to see if the nodes are dictonaires, if not they are leaf nodesnumLeafs += getNumLeafs(secondDict[key])else:numLeafs += 1return numLeafsdef getTreeDepth(myTree):maxDepth = 0firstStr = list(myTree.keys())[0]secondDict = myTree[firstStr]for key in secondDict.keys():if type(secondDict[key]).__name__ == 'dict':  # test to see if the nodes are dictonaires, if not they are leaf nodesthisDepth = 1 + getTreeDepth(secondDict[key])else:thisDepth = 1if thisDepth > maxDepth: maxDepth = thisDepthreturn maxDepthdef 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):  # if the first key tells you what feat was split onnumLeafs = getNumLeafs(myTree)  # this determines the x width of this treedepth = getTreeDepth(myTree)firstStr = list(myTree.keys())[0]  # the text label for this node should be thiscntrPt = (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.totalDfor key in secondDict.keys():if type(secondDict[key]).__name__ == 'dict':  # test to see if the nodes are dictonaires, if not they are leaf nodesplotTree(secondDict[key], cntrPt, str(key))  # recursionelse:  # it's a leaf node print the leaf nodeplotTree.xOff = plotTree.xOff + 1.0 / plotTree.totalWplotNode(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 dictdef createPlot(inTree):fig = plt.figure(1, facecolor='white')fig.clf()axprops = dict(xticks=[], yticks=[])createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)  # no ticks# createPlot.ax1 = plt.subplot(111, frameon=False) #ticks for demo puropsesplotTree.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()# def createPlot():
#    fig = plt.figure(1, facecolor='white')
#    fig.clf()
#    createPlot.ax1 = plt.subplot(111, frameon=False) #ticks for demo puropses
#    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 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'}}, 1: 'no'}}}}]return listOfTrees[i]# createPlot(thisTree)

运行截图:

4.2.5 持久化决策树

from numpy import *
import math
import copy
import pickle
import treePlotter as tpclass ID3DTree(object):def __init__(self):self.tree={ }#生成的树self.dataSet = []#数据集self.labels={}#标签集#导入数据def loadDataSet(self,path,labels):recordlist = []fp = open(path,"rb")#读取文件内容content = fp.read().decode()fp.close()rowlist = content.splitlines()#按行转换成一维表recordlist = [row.split("\t") for row in rowlist if row.strip()]print(recordlist)self.dataSet = recordlistself.labels = labelsdef train(self):labels = copy.deepcopy(self.labels)print(labels)self.tree = self.buildTree(self.dataSet,labels)#构建决策树,创建决策树主程序def buildTree(self,dataSet,labels):#抽取源数据集的决策标签列cateList = [data[-1] for data in dataSet]print(cateList)#程序终止条件1:如果cateList只有一种决策标签,停止划分,返回这个决决策标签if cateList.count(cateList[0]) == len(cateList):return cateList[0]#程序终止条件2:如果数据集的第一个决策标签只有一个,则#返回这个决策标签if len(dataSet[0]) == 1:return self.maxCate(cateList)#算法核心:bestFeat = self.getBestFeat(dataSet) #返回数据集的最优特征值print("bestFeat:",bestFeat)bestFeatLabel = labels[bestFeat]print("bestFeatLabel",bestFeatLabel)tree = {bestFeatLabel:{}}del(labels[bestFeat])#再次抽取最优特征轴的列向量uniqueVals = set([data[bestFeat] for data in dataSet])#去重print("uniqueVals:",uniqueVals)for value in uniqueVals:#决策树的递归增长subLabels = labels[:] #将删除后的特征类别集建立子类别集#按最优特征列和值分隔数据集,即筛选出第bestFeat列值为value的所有元素,返回的是去掉这一列后剩下的数据#本例中根据bestFeat划分为买或者是不买这两个类别,作为左右子树,再继续划分splitDataset = self.splitDataSet(dataSet,bestFeat,value)subTree = self.buildTree(splitDataset,subLabels)tree[bestFeatLabel][value] = subTreereturn tree#计算出现次数最多的类别标签def maxCate(self,catelist):items = dict([(catelist.count(i),i) for  i in catelist])return items([max(items.keys())])#计算最优特征def getBestFeat(self,dataSet):#计算特征向量维,其中最后一列用于类别标签,因此要减去print(dataSet[0])numFeatures = len(dataSet[0]) - 1#特征向量维数-1baseEntropy = self.computeEntropy(dataSet) #基础熵:源数据的香农熵,即买或者不买计算出来的熵值print("baseEntropy:",baseEntropy)bestInfoGain = 0.0 #初始化最优的信息增益bestFeature = -1 #初始化最优的特征轴#外循环:遍历数据集各列,计算最优特征轴#i为数据集列索引:取值范围:0-(numFeatures-1)#依次遍历每个特征for i in range(numFeatures):uniqueVals = set([data[i] for data in dataSet])#去重:该列的唯一值集,也就是求类别,比如青年,中年,老年print("uniqueVals:",uniqueVals)newEntropy = 0.0#初始化该列的香农熵#算不同类型中买与不买的比例以及熵for value in uniqueVals:#内循环按列和唯一值计算香农熵#按选定列i和唯一值分隔数据集,即筛选出第i列值为value的所有元素,返回的是去掉这一列后剩下的数据subDataSet = self.splitDataSet(dataSet,i,value)#各自在总体样本中所占的比例prob = len(subDataSet) / float(len(dataSet))print("prob:",prob)#概率*对应小类别中买与不买的熵值newEntropy += prob * self.computeEntropy(subDataSet)infoGain = baseEntropy - newEntropy #计算最大增益print("infoGain:",infoGain)if(infoGain > bestInfoGain):#如果信息增益>0bestInfoGain = infoGain #用当前信息增益代替之前的最优增益bestFeature = i #重置最优特征为当前列return bestFeature#计算信息熵def computeEntropy(self,dataSet):#计算香农熵datalen = float(len(dataSet))print("datalen",datalen)cateList = [data[-1] for data in dataSet]#从数据集中得到类别标签print("cateList",cateList)#得到类别为key,出现次数value的字典items = dict([(i,cateList.count(i)) for i in cateList])print("items:",items)infoEntropy = 0.0 #初始化香农熵for key in items:#香农熵 = -plog2(p)prob = float(items[key])/dataleninfoEntropy -= prob*math.log(prob,2)return infoEntropy#划分数据集,分隔数据集,删除特征轴所在的数据列,返回剩余的数据集def splitDataSet(self,dataSet,axis,value):rtnList = []for featVec in dataSet:if featVec[axis] == value:rFeatVec = featVec[:axis] #取0-(axis-1)的元素rFeatVec.extend(featVec[axis+1:])#将特征轴(列)之后的元素加回
                rtnList.append(rFeatVec)return rtnList#持久化,存储树到文件def storeTree(self,inputTree,filename):fw = open(filename,'wb')pickle.dump(inputTree,fw)fw.close()#从文件中获取树def grabTree(self,filename):fr = open(filename,"rb")return pickle.load(fr)dtree = ID3DTree()
#["age","revenue","student","credit"]
dtree.loadDataSet(r"C:\Users\Administrator\Desktop\dataset.dat",["age","revenue","student","credit"])
dtree.train()
print(dtree.tree)
tp.createPlot(dtree.tree)
#序列化
dtree.storeTree(dtree.tree,r"C:\Users\Administrator\Desktop\data.tree")
mytree = dtree.grabTree(r"C:\Users\Administrator\Desktop\data.tree")
print("****"*30)
print(mytree)

3.2.6 决策树分类

from numpy import *
import math
import copy
import pickle
import treePlotter as tpclass ID3DTree(object):def __init__(self):self.tree={ }#生成的树self.dataSet = []#数据集self.labels={}#标签集#导入数据def loadDataSet(self,path,labels):recordlist = []fp = open(path,"rb")#读取文件内容content = fp.read().decode()fp.close()rowlist = content.splitlines()#按行转换成一维表recordlist = [row.split("\t") for row in rowlist if row.strip()]print(recordlist)self.dataSet = recordlistself.labels = labelsdef train(self):labels = copy.deepcopy(self.labels)print(labels)self.tree = self.buildTree(self.dataSet,labels)#构建决策树,创建决策树主程序def buildTree(self,dataSet,labels):#抽取源数据集的决策标签列cateList = [data[-1] for data in dataSet]print(cateList)#程序终止条件1:如果cateList只有一种决策标签,停止划分,返回这个决决策标签if cateList.count(cateList[0]) == len(cateList):return cateList[0]#程序终止条件2:如果数据集的第一个决策标签只有一个,则#返回这个决策标签if len(dataSet[0]) == 1:return self.maxCate(cateList)#算法核心:bestFeat = self.getBestFeat(dataSet) #返回数据集的最优特征值print("bestFeat:",bestFeat)bestFeatLabel = labels[bestFeat]print("bestFeatLabel",bestFeatLabel)tree = {bestFeatLabel:{}}del(labels[bestFeat])#再次抽取最优特征轴的列向量uniqueVals = set([data[bestFeat] for data in dataSet])#去重print("uniqueVals:",uniqueVals)for value in uniqueVals:#决策树的递归增长subLabels = labels[:] #将删除后的特征类别集建立子类别集#按最优特征列和值分隔数据集,即筛选出第bestFeat列值为value的所有元素,返回的是去掉这一列后剩下的数据#本例中根据bestFeat划分为买或者是不买这两个类别,作为左右子树,再继续划分splitDataset = self.splitDataSet(dataSet,bestFeat,value)subTree = self.buildTree(splitDataset,subLabels)tree[bestFeatLabel][value] = subTreereturn tree#计算出现次数最多的类别标签def maxCate(self,catelist):items = dict([(catelist.count(i),i) for  i in catelist])return items([max(items.keys())])#计算最优特征def getBestFeat(self,dataSet):#计算特征向量维,其中最后一列用于类别标签,因此要减去print(dataSet[0])numFeatures = len(dataSet[0]) - 1#特征向量维数-1baseEntropy = self.computeEntropy(dataSet) #基础熵:源数据的香农熵,即买或者不买计算出来的熵值print("baseEntropy:",baseEntropy)bestInfoGain = 0.0 #初始化最优的信息增益bestFeature = -1 #初始化最优的特征轴#外循环:遍历数据集各列,计算最优特征轴#i为数据集列索引:取值范围:0-(numFeatures-1)#依次遍历每个特征for i in range(numFeatures):uniqueVals = set([data[i] for data in dataSet])#去重:该列的唯一值集,也就是求类别,比如青年,中年,老年print("uniqueVals:",uniqueVals)newEntropy = 0.0#初始化该列的香农熵#算不同类型中买与不买的比例以及熵for value in uniqueVals:#内循环按列和唯一值计算香农熵#按选定列i和唯一值分隔数据集,即筛选出第i列值为value的所有元素,返回的是去掉这一列后剩下的数据subDataSet = self.splitDataSet(dataSet,i,value)#各自在总体样本中所占的比例prob = len(subDataSet) / float(len(dataSet))print("prob:",prob)#概率*对应小类别中买与不买的熵值newEntropy += prob * self.computeEntropy(subDataSet)infoGain = baseEntropy - newEntropy #计算最大增益print("infoGain:",infoGain)if(infoGain > bestInfoGain):#如果信息增益>0bestInfoGain = infoGain #用当前信息增益代替之前的最优增益bestFeature = i #重置最优特征为当前列return bestFeature#计算信息熵def computeEntropy(self,dataSet):#计算香农熵datalen = float(len(dataSet))print("datalen",datalen)cateList = [data[-1] for data in dataSet]#从数据集中得到类别标签print("cateList",cateList)#得到类别为key,出现次数value的字典items = dict([(i,cateList.count(i)) for i in cateList])print("items:",items)infoEntropy = 0.0 #初始化香农熵for key in items:#香农熵 = -plog2(p)prob = float(items[key])/dataleninfoEntropy -= prob*math.log(prob,2)return infoEntropy#划分数据集,分隔数据集,删除特征轴所在的数据列,返回剩余的数据集def splitDataSet(self,dataSet,axis,value):rtnList = []for featVec in dataSet:if featVec[axis] == value:rFeatVec = featVec[:axis] #取0-(axis-1)的元素rFeatVec.extend(featVec[axis+1:])#将特征轴(列)之后的元素加回
                rtnList.append(rFeatVec)return rtnList#持久化,存储树到文件def storeTree(self,inputTree,filename):fw = open(filename,'wb')pickle.dump(inputTree,fw)fw.close()#从文件中获取树def grabTree(self,filename):fr = open(filename,"rb")return pickle.load(fr)#决策树分类def predict(self,inputTree,featLabels,testVec):#分类器print("inputTree:",inputTree)root = list(inputTree.keys())[0] #树根节点print("root:",root)secondDict = inputTree[root] #value-子树结构或分类标签print("secondDict:",secondDict)featIndex = featLabels.index(root)#根节点在分类标签集中的位置print("featIndex:",featIndex)#取得对应要分类的特征对应的值key = testVec[featIndex]#得到对应的特征值的子树valueOfFeat = secondDict[key]print("valueOfFeat:",valueOfFeat)#如果还是树则继续分类if isinstance(valueOfFeat,dict):#递归分类classLabel = self.predict(valueOfFeat,featLabels,testVec)else:classLabel = valueOfFeatreturn classLabeldtree = ID3DTree()
labels = ["age","revenue","student","credit"]
vector = ['0','1','0','0']
mytree = dtree.grabTree(r"C:\Users\Administrator\Desktop\data.tree")
print("真实输出:","no -> 决策树输出:",dtree.predict(mytree,labels,vector))

ID3的缺点:

3.3 C4.5算法

  C4.5算法成功解决了ID3算法遇到的诸多问题,在业界得到了广泛的应用,并发展成为机器学习的十大算法之一。

3.3.1 信息增益率

  C4.5并没有改变ID3的算法逻辑,基本的程序结构仍与ID3相同,但在节点的划分标准上做了改进。C4.5使用的是信息增益率来代替信息增益进行特征的选择,克服了信息增益在选择特征时偏向于特征值个数较多的不足。信息增益率的定义如下:

C4.5算法python实现:

from numpy import *
import math
import copy
import pickle
import treePlotter as tpclass C45DTree(object):def __init__(self):self.tree={ }#生成的树self.dataSet = []#数据集self.labels={}#标签集#导入数据def loadDataSet(self,path,labels):recordlist = []fp = open(path,"rb")#读取文件内容content = fp.read().decode()fp.close()rowlist = content.splitlines()#按行转换成一维表recordlist = [row.split("\t") for row in rowlist if row.strip()]print("recordList:",recordlist)self.dataSet = recordlistself.labels = labelsdef train(self):labels = copy.deepcopy(self.labels)print("labels:",labels)self.tree = self.buildTree(self.dataSet,labels)#构建决策树,创建决策树主程序def buildTree(self,dataSet,labels):#抽取源数据集的决策标签列cateList = [data[-1] for data in dataSet]print("cateList:",cateList)if(cateList.count(cateList[0]) == len(cateList)):return cateList[0]if(len(dataSet[0]) == 1):return self.maxCate(cateList)bestFeat,featValueList = self.getBestFeat(dataSet)bestFeatLabel = labels[bestFeat]print("bestFeatLabel:",bestFeatLabel)tree = {bestFeatLabel:{}}del(labels[bestFeat])for value in featValueList:subLabels = labels[:]splitDataset = self.splitDataSet(dataSet,bestFeat,value)subTree = self.buildTree(splitDataset,subLabels)tree[bestFeatLabel][value] = subTreereturn tree#计算出现次数最多的类别标签def maxCate(self,catelist):items = dict([(catelist.count(i),i) for  i in catelist])return items([max(items.keys())])#计算信息熵def computeEntropy(self,dataSet):#计算香农熵datalen = float(len(dataSet))print("datalen",datalen)cateList = [data[-1] for data in dataSet]#从数据集中得到类别标签print("cateList",cateList)#得到类别为key,出现次数value的字典items = dict([(i,cateList.count(i)) for i in cateList])print("items:",items)infoEntropy = 0.0 #初始化香农熵for key in items:#香农熵 = -plog2(p)prob = float(items[key])/dataleninfoEntropy -= prob*math.log(prob,2)return infoEntropy#划分数据集,分隔数据集,删除特征轴所在的数据列,返回剩余的数据集def splitDataSet(self,dataSet,axis,value):rtnList = []for featVec in dataSet:if featVec[axis] == value:rFeatVec = featVec[:axis] #取0-(axis-1)的元素rFeatVec.extend(featVec[axis+1:])#将特征轴(列)之后的元素加回rtnList.append(rFeatVec)return rtnList#持久化,存储树到文件def storeTree(self,inputTree,filename):fw = open(filename,'wb')pickle.dump(inputTree,fw)fw.close()#从文件中获取树def grabTree(self,filename):fr = open(filename,"rb")return pickle.load(fr)#决策树分类def predict(self,inputTree,featLabels,testVec):#分类器print("inputTree:",inputTree)root = list(inputTree.keys())[0] #树根节点print("root:",root)secondDict = inputTree[root] #value-子树结构或分类标签print("secondDict:",secondDict)featIndex = featLabels.index(root)#根节点在分类标签集中的位置print("featIndex:",featIndex)#取得对应要分类的特征对应的值key = testVec[featIndex]#得到对应的特征值的子树valueOfFeat = secondDict[key]print("valueOfFeat:",valueOfFeat)#如果还是树则继续分类if isinstance(valueOfFeat,dict):#递归分类classLabel = self.predict(valueOfFeat,featLabels,testVec)else:classLabel = valueOfFeatreturn classLabel#使用信息增益率划分最优节点的方法def getBestFeat(self, dataSet):print("dataSet[0]:",dataSet[0])Num_Feats = len(dataSet[0][:-1])totality = len(dataSet)BaseEntropy = self.computeEntropy(dataSet)ConditionEntropy = []  # 初始化条件熵slpitInfo = []  # for C4.5, calculate gain ratioallFeatVList = []for f in range(Num_Feats):featList = [example[f] for example in dataSet]print("featList:",featList)[splitI, featureValueList] = self.computeSplitInfo(featList)allFeatVList.append(featureValueList)slpitInfo.append(splitI)resultGain = 0.0for value in featureValueList:#把第f列等于value的值取出subSet = self.splitDataSet(dataSet, f, value)appearNum = float(len(subSet))subEntropy = self.computeEntropy(subSet)#计算香农熵resultGain += (appearNum / totality) * subEntropyConditionEntropy.append(resultGain)  # 总条件熵infoGainArray = BaseEntropy * ones(Num_Feats) - array(ConditionEntropy)print("array(slpitInfo):",array(slpitInfo))infoGainRatio = infoGainArray / array(slpitInfo)  # c4.5, info gain ratiobestFeatureIndex = argsort(-infoGainRatio)[0]return bestFeatureIndex, allFeatVList[bestFeatureIndex]#计算划分信息def computeSplitInfo(self, featureVList):numEntries = len(featureVList)featureVauleSetList = list(set(featureVList))print("featureVauleSetList:",featureVauleSetList)valueCounts = [featureVList.count(featVec) for featVec in featureVauleSetList]# caclulate shannonEntpList = [float(item) / numEntries for item in valueCounts]lList = [item * math.log(item, 2) for item in pList]splitInfo = -sum(lList)return splitInfo, featureVauleSetListlabels = ["age","revenue","student","credit"]
dtree = C45DTree()
dtree.loadDataSet(r"D:\机器学习视频\机器学习算法原理与编程实战_cn\ml_cn_book_code-master\chapter03\dataset.dat",labels)
dtree.train()
print("dtree.tree:",dtree.tree)
tp.createPlot(dtree.tree)
vector = ['0','1','0','0']
print("真实输出:","no -> 决策树输出:",dtree.predict(dtree.tree,labels,vector))

运行截图:

  

转载于:https://www.cnblogs.com/xiaochi/p/10975158.html

4.决策树的探赜索隐相关推荐

  1. lightgbm 决策树 可视化 graphviz

    决策树模型,XGBoost,LightGBM和CatBoost模型可视化 安装 graphviz 参考文档 http://graphviz.readthedocs.io/en/stable/manua ...

  2. 梯度提升决策树(GBDT)与XGBoost、LightGBM

    20211224 [机器学习算法总结]XGBoost_yyy430的博客-CSDN博客_xgboost xgboost参数 默认:auto.XGBoost中使用的树构造算法.可选项:auto,exac ...

  3. 决策树模型与学习《一》

    决策树总结<一>.md 决策树模型与学习 1. 定义 一般的,一棵决策树包含一个根结点,若干个内部结点和若干个叶结点:叶结点对应于决策结果,其他每个结点则对应于一个属性测试:每个结点包含的 ...

  4. 机器学习(9)决策树(决策树分类鸢尾花)

    目录 一.基础理论 二.决策树分类鸢尾花 API 1.读取数据 2.划分数据集 3.创建决策树预估器,训练 4.模型评估 方法一:比对法 方法二:计算错误率 代码 一.基础理论 决策树思想: 程序设计 ...

  5. 【机器学习入门】(5) 决策树算法实战:sklearn实现决策树,实例应用(沉船幸存者预测)附python完整代码及数据集

    各位同学好,今天和大家分享一下python机器学习中的决策树算法,在上一节中我介绍了决策树算法的基本原理,这一节,我将通过实例应用带大家进一步认识这个算法.文末有完整代码和数据集,需要的自取.那我们开 ...

  6. 机器学习—决策树构造算法的python实现

    机器学习-决策树算法的python实现 想要实现的效果 先来看下结果 程序原理 数据 完整代码(附有具体解析) 想要实现的效果 对于这个不好玩的决策树,我想要得到的就是通过决策树训练我的数据然后生成这 ...

  7. Udacity机器人软件工程师课程笔记(四)-样本搜索和找回-基于漫游者号模拟器-决策树

    7.做出决策 我们已经建立了感知步骤Perception.py,接下来要构建决策函数decision.py . 我们利用一个基础的人工智能模型--决策树模型.其结构如下: 由于我们采用一个非常简单的模 ...

  8. 决策树的C++实现(CART)

    关于决策树的介绍可以参考: https://blog.csdn.net/fengbingchun/article/details/78880934 CART算法的决策树的Python实现可以参考: h ...

  9. OpenCV3.3中决策树(Decision Tree)接口简介及使用

    OpenCV 3.3中给出了决策树Decision Tres算法的实现,即cv::ml::DTrees类,此类的声明在include/opencv2/ml.hpp文件中,实现在modules/ml/s ...

最新文章

  1. python显示无效语法怎么处理-Python不支持 i ++ 语法的原因解析
  2. EasyUI DataGrid 合并单元格
  3. spring系列-注解驱动原理及源码-属性赋值
  4. 台式计算机单核与双核,什么是单核cpu、双核cpu 单核cpu和双核cpu的区别是什么...
  5. 沃尔沃挖机计算机故障,沃尔沃挖掘机常见故障及原因总结,用户们可以看看
  6. Mybatis源码日志模块分析
  7. docker container
  8. 第二阶段冲刺——个人总结07
  9. 删除分卷php逻辑,Linux LVM(逻辑卷管理)删除详解
  10. PostgreSQL12.2_cn.chm 中文手册 最新版
  11. 从阿里云购买、域名购买、SSL免费购买到SSL集成开发(网络编程安全三)
  12. python中-是什么意思
  13. springboot 实现redis高并发抢票服务
  14. 软考-高项计算1--投资回收周期计算(涉及概念:投资回收期 折现因子 净现值 累计净现值 现金流 累计净现金流)
  15. 鼠标dpi设置多少合适呢?查看鼠标dpi的方法
  16. pySerial使用初步
  17. Matter协议特性解析(二) 分身(Multiple Fabiric)和权限控制
  18. 有一分热,发一分光,爬取鲁迅先生《经典语录》
  19. MFC-最简单的MFC程序
  20. Google Scholar GB格式引用

热门文章

  1. 【Unity】打包报错 com.android.buil.gradle.internal.tasks.workers$ActionFacade
  2. 高通骁龙处理器最新的排名
  3. xp系统共享打印机服务器不可用,那种方式能够解决WinXP访问Win10共享打印机指定网络不可用的问题?...
  4. Adaptive Personalized Federated Learning 论文解读+代码解析
  5. 一文教你快速搞懂速度曲线规划之S形曲线(超详细+图文+推导+附件代码)
  6. Tampermonkey脚本编写
  7. 计算机存储1pb等于多少kb,存储容量:1TB等于多少GB?1PB等于多少TB?1EB等于多少PB?
  8. Java转化音频格式 m4a-wav
  9. Trucksim(一):Trucksim动力学模型搭建
  10. 简述python文件操作_Python 文件操作的详解及实例