第四章 决策树和ID3 python3.5.1代码理解

一.部分概念:

决策树:为了对新事例进行分类。

决策树学习的目的:为了获得泛化能力强的决策树。

决策树包括根结点,内部结点,叶结点:

1)根结点:包涵样本全集。

2)内部结点:对应于一个测试属性。

3)叶结点:对应于测试结果。

比如下图中的色泽就是根结点,里面矩形框的是内部结点,椭圆就是叶结点即我们最终的结果。

二.决策树学习基本算法:

输入:训练集D={(x1,y1),(x2,y2),...};属性集{a1,a2…}. 过程:函数TreeGenerate(D,A)

1: 生成结点node;

2: if D中样本全属于同一类别C then                                                                       3:     将node标记为C类叶结点; return

4: end if

5: if A=∅ OR D中样本在A上取值相同 then

6: 将node标记为叶结点,其类别标记为D中样本数最多的类; return

7: end if

8: 从A中选择最优化分属性a*

9: for a*中的每一个值 do

10:       为node生成一个分支; 令Dv表示D中在a*上取值为的样本子集;

11:       if Dv为空 then

12:           将分支结点标记为叶结点,其类别标记为D中样本最多的类; return

13:      else

14:          以为TreeGenerate(Dv,A\{a*})支结点

15:      end if

16: end for

输出:以node为根结点的一棵决策树

 

三.ID3算法Iterative Dichotomiser 3)

其中,Pk是某一类在当前数据集所占比例,|Dv|是符合某属性值的样本数量在当前数据集的比例,公式1.2)下面的括号内的意思是增益值=当前样本的总信息熵 — (各属性值的样本比例乘以该属性值的信息熵的总和。比如周志华《机器学习》P77的例子所示。

 决策树是什么思想?其实很简单,就是找一个最佳划分属性,按照这个属性的值一直划分,直到样本分类成功;然后再继续找下一个最佳划分属性,按照属性值划分;如此循环往复直到所有样本都已经被分到同一类中就停止递归。决策树中重要的是C4.5,而ID3是他的基础。

四.前剪枝和后剪枝: 

1.剪枝目的:原因是避免决策树产生过拟合(Overfitting)样本。

2.预剪枝(前剪枝): 在构造决策树的同时进行剪枝。所有决策树的构建方法,都是在无法进一步降低熵的情况下才会停止创建分支的过程,为了避免过拟合,对每个结点进行估计,若当前结点的划分不能带来决策树泛化性能的提升,就停止划分,并将当前结点作为叶结点。以当前结点样本类别比例大的为判定类。

3.后剪枝: 先从已经训练完整的决策树中,自底向上对非叶结点进行考察(即划分结点)。若该结点对应子树提换为叶结点(该子树换为判定类结果)后泛化性能提升,就剪枝。

4.评估方式 可以采用留出法,一部分作为训练集,一部分作为‘验证集’,当验证集的精度在划分后降低时,则不划分节点,直接作为叶结点,若划分后验证集精度提高了,就保留。

5.优缺点:后剪枝运用较多,且后剪枝比前剪枝保留了更多的分支,故一般,后剪枝的欠拟合风险小,但是自底向上把所有非叶结点都计算一番,所以后剪枝训练开销比前剪枝要大很多。

五.ID3缺点

  ID3是基本的决策树构建算法,作为决策树经典的构建算法,其具有结构简单、清晰易懂的特点。虽然ID3比较灵活方便,但是有以下几个缺点:

 

 (1)采用信息增益进行分裂,分裂的精确度可能没有采用信息增益率进行分裂高

 

   (2)不能处理连续型数据,只能通过离散化将连续性数据转化为离散型数据

 

   (3)不能处理缺省值

 

   (4)没有对决策树进行剪枝处理,很可能会出现过拟合的问题

 

六. ID3代码:这是别人的代码,只显示最终决策树,没有预测。数据集和完整代码请点击代码中的第一个链接下载。强烈建议参考小白解释,他写的比较好,第二个是注重总体函数功能解释。

  1 # -*- coding: utf-8 -*-
  2 '''
  3 参考文档:  小白解释- http://blog.csdn.net/thither_shore/article/details/52358174
  4            总体概述- http://blog.csdn.net/sysu_cis/article/details/51842736
  5 '''
  6 
  7 '''生成树的函数'''###############################
  8 
  9 from numpy import *  
 10 import numpy as np  
 11 import pandas as pd
 12 from math import log  
 13 import operator
 14 import ipdb
 15 
 16 '''
 17 计算数据集的信息熵(Information Gain)增益函数(机器学习实战中信息熵叫香农熵)
 18 '''
 19 def calcInfoEnt(dataSet):#本题中Label即好or坏瓜     #dataSet每一列是一个属性(列末是Label)
 20     numEntries = len(dataSet)                        #每一行是一个样本,17行数据
 21     labelCounts = {}                                #给所有可能的分类创建字典labelCounts初值{key:value... },便于统计A类多少个,B类多少个。
 22 
 23     for featVec in dataSet:                            #按行循环:即featVev取遍了数据集中的每一行
 24         currentLabel = featVec[-1]                    #featVec[-1]取遍最后一列值即Label
 25         if currentLabel not in labelCounts.keys():    #如果当前的Label在字典中还没有,发现未有的类别就创建它,就是发现一个类别就统计它。
 26             labelCounts[currentLabel] = 0            #则先赋值0来创建这个词,字典形式。
 27         labelCounts[currentLabel] += 1                #计数, 统计每类Label数量(这行不受if限制),值是1,键就+1计数,currentLabel没有就加进来,从0开始计数。
 28 
 29 
 30     InfoEnt = 0.0
 31     for key in labelCounts:                            #遍历每类Label
 32         prob = float(labelCounts[key])/numEntries   #各类Label熵累加
 33         InfoEnt -= prob * log(prob,2)                #ID3用的信息增益公式,值越小,纯度越高。P75公式(4.2)
 34     return InfoEnt
 35 
 36 
 37 
 38 '''
 39 对于离散特征: 取出该特征取值为value的所有样本
 40 '''
 41 def splitDiscreteDataSet(dataSet, axis, value):        #dataSet是当前结点(待划分)集合,列表
 42                                                     #axis指示划分所依据的属性, 是划分属性的列号,如0,1,2...
 43                                                     #value该属性用于划分的取值, 属性值对应字符串
 44                                                     #就是说,axis对划分属性,value是此属性的具体值a1,a2,a3...,根据这些属性值来划分训练集样本。
 45                                                     #比如:纹理-清晰 对应样本D1,纹理-稍糊对应样本D2,纹理-模糊样本D3.把当前数据集D给划分成3份。
 46     retDataSet = []                                    #为return Data Set分配一个列表用来储存                                                                            
 47     for featVec in dataSet:
 48         if featVec[axis] == value:
 49             reducedFeatVec = featVec[:axis]            #该特征之前的特征仍保留在样本dataSet中
 50             reducedFeatVec.extend(featVec[axis+1:]) #该特征之后的特征仍保留在样本dataSet中,extend()即添加列数!
 51                                                     #这两句意思是把当前划分axis列去掉,剩下的的样本集,如《机器学习》P77倒数第三段的例子所示。
 52 
 53             retDataSet.append(reducedFeatVec)        #把这个样本加到list中,并去除掉本划分属性
 54     return retDataSet
 55 '''
 56 连续值可以循环作为划分点(个数不变),而离散值中被选取的最优划分点用过就扔掉,不再采用。
 57 对于连续特征: 返回特征取值大于value的所有样本(以value为阈值将集合分成两部分)
 58 '''
 59 def splitContinuousDataSet(dataSet, axis, value): 
 60     retDataSetG=[]                                    #将储存取值大于value的样本
 61     retDataSetL=[]                                    #将储存取值小于value的样本
 62     for featVec in dataSet:
 63         if featVec[axis]>value:                     #大于阈值,则符合的样本数据的列从[0,axis)U[axis+1,end),
 64                                                     # 还是和离散值一样,去掉划分属性,只不过该划分属性是连续值而已。
 65             reducedFeatVecG=featVec[:axis]
 66             reducedFeatVecG.extend(featVec[axis+1:])  
 67             retDataSetG.append(reducedFeatVecG)    #去掉划分属性列,合并成一个列表
 68         else:
 69             reducedFeatVecL=featVec[:axis]
 70             reducedFeatVecL.extend(featVec[axis+1:])  
 71             retDataSetL.append(reducedFeatVecL)
 72     return retDataSetG,retDataSetL                    #返回两个集合, 是含2个元素的tuple形式
 73  
 74 '''
 75 根据InfoGain选择当前最好的划分特征(以及对于连续变量还要选择以什么值划分)
 76 '''
 77 def chooseBestFeatureToSplit(dataSet,labels):  
 78     numFeatures=len(dataSet[0])-1           #第0行属性的个数为8,最后一个类别去掉
 79     baseEntropy=calcInfoEnt(dataSet)        #计算数据集的信息熵
 80     bestInfoGain=0.0; bestFeature=-1        #初始化最佳划分点(最大信息增益值)和最佳特征
 81     bestSplitDict={}                        #初始化最佳划分字典集合
 82     for i in range(numFeatures):
 83         #遍历所有特征:下面这句是取每一行的第i个, 即得当前集合所有样本第i个feature的值
 84         featList=[example[i] for example in dataSet]
 85         #判断是否为离散特征
 86         if not (type(featList[0]).__name__=='float' or type(featList[0]).__name__=='int'):
 87 ### 对于离散特征:求若以该特征划分的熵增
 88             uniqueVals = set(featList)                #从列表中创建集合set(得列表唯一元素值)
 89             newEntropy = 0.0
 90             for value in uniqueVals:                #遍历该离散特征每个取值,求第i列属性每个不同值的熵*他们的概率
 91                 subDataSet = splitDiscreteDataSet(dataSet, i, value)#计算每个取值的信息熵
 92                 prob = len(subDataSet)/float(len(dataSet)) #subDataSet是去掉符合划分属性值剩下的样本。
 93                 newEntropy += prob * calcInfoEnt(subDataSet)#属性各取值的熵累加,对应于P77 0.998-()括号内的东西。
 94                                                                 #如果是C4.5即计算增益率:信息增益值除以划分属性值熵的和,是为了纠正信息增益值对于样本数量较多的 偏好。
 95                                                                 #就是接着如下代码:intrinsic_value -= prob*log(prob,2)
 96                                                                 # infoGain = baseEntropy - newEntropy
 97                                                                 #  if (splitInfo == 0): # fix the overflow bug
 98                                                                 #             continue       # continue是跳过当次循环中剩下的语句,执行下一次循环
 99                                                                 # infoGainRatio = infoGain / intrinsic_value
100             infoGain = baseEntropy - newEntropy        #得到以该特征划分的熵增 ,形如P77开头例子
101 ### 对于连续特征:求若以该特征划分的熵增(区别:n个数据则需添n-1个候选划分点, 并选最佳划分点) 
102         else:
103             #产生n-1个候选划分点: 比如0.3,0.7,0.8 求出2个均值:0.5,0.75. 2个连续点变成1个划分点,3个连续值就是2个划分点,所以n个变成n-1个划分点。对此新序列考察最佳划分点即可。
104             sortfeatList=sorted(featList)
105             splitList=[]           #用来存放n-1个划分点
106             for j in range(len(sortfeatList)-1):      #产生n-1个候选划分点
107                 splitList.append((sortfeatList[j]+sortfeatList[j+1])/2.0)
108             bestSplitEntropy=10000                  #设定一个很大的熵值(之后用)
109             #遍历n-1个候选划分点: 求选第j个候选划分点划分时的熵增, 并选出最佳划分点
110             for j in range(len(splitList)):            #划分点已经是各个中位数
111                 value=splitList[j]
112                 newEntropy=0.0
113                 DataSet = splitContinuousDataSet(dataSet, i, value)
114                 subDataSetG=DataSet[0]
115                 subDataSetL=DataSet[1]
116                 probG = len(subDataSetG) / float(len(dataSet))
117                 newEntropy += probG * calcInfoEnt(subDataSetG)
118                 probL = len(subDataSetL) / float(len(dataSet))
119                 newEntropy += probL * calcInfoEnt(subDataSetL)   #某结点D去掉某划分属性后的信息熵
120                 if newEntropy < bestSplitEntropy:                #刚开始默认第1个候选熵是最小,然后和后面的比较,小的就替换当前熵。
121                     bestSplitEntropy=newEntropy
122                     bestSplit=j                                 #此时j个划分点的信息熵是最小
123             bestSplitDict[labels[i]] = splitList[bestSplit]        #字典记录当前连续属性的最佳划分点
124 
125             infoGain = baseEntropy - bestSplitEntropy       #计算以该节点划分的熵增
126 ### 在所有属性(包括连续和离散)中选择可以获得最大熵增的属性
127         if infoGain>bestInfoGain:
128             bestInfoGain=infoGain
129             bestFeature=i
130     #若当前节点的最佳划分特征为连续特征,则需根据“是否小于等于其最佳划分点”进行二值化处理
131     #即将该特征改为“是否小于等于bestSplitValue”, 例如将“密度”变为“密度<=0.3815”
132     #注意:以下这段直接操作了原dataSet数据, 之前的那些float型的值相应变为0和1
133     #【为何这样做?】在函数createTree()末尾将看到解释,就是离散值的二值化处理。
134     if type(dataSet[0][bestFeature]).__name__=='float' or \
135     type(dataSet[0][bestFeature]).__name__=='int':
136         bestSplitValue = bestSplitDict[labels[bestFeature]]  #字典里键值对是最佳划分属性:对应的值 labels[bestFeature]是Density属性, bestSplitValue就是0.381499999
137         labels[bestFeature]=labels[bestFeature]+'<='+str(bestSplitValue) #Density <= 0.38149999999999995
138         for i in range(shape(dataSet)[0]):
139             if dataSet[i][bestFeature]<=bestSplitValue:
140                 dataSet[i][bestFeature]=1                   #若概该样本的density<=最佳划分点,就把该离散属性值改为1,否则改为0
141             else:
142                 dataSet[i][bestFeature]=0
143     return bestFeature
144   
145 '''
146 若特征已经划分完,节点下的样本还没有统一取值,则需要进行投票:计算每类Label个数, 取max者
147 '''
148 def majorityCnt(classList):
149     print(classList)
150     classCount={}                                      #将创建键值为Label类型的字典
151     for vote in classList:
152         if vote not in classCount.keys():
153             classCount[vote]=0                      #第一次出现的Label加入字典
154         classCount[vote]+=1                          #计数
155     return max(classCount)
156 
157 
158 '''
159     #主程序:递归产生决策树
160     # dataSet:当前用于构建树的数据集, 最开始就是data_full,然后随着划分的进行越来越小。
161     # 这是因为进行到到树分叉点上了. 第一次划分之前17个瓜的数据在根节点,然后选择第一个bestFeat是纹理.
162     #  纹理的取值有清晰、模糊、稍糊三种;
163     # 将瓜分成了清晰(9个),稍糊(5个),模糊(3个),这时应该将划分的类别减少1以便于下次划分。
164     # labels:当前数据集中有的用于划分的类别(这是因为有些Label当前数据集没了, 比如假如到某个点上西瓜都是浅白没有深绿了)
165     # data_full:全部的数据 
166     # label_full:全部的类别   
167 '''
168 numLine = numColumn = 2 #这句是因为之后要用global numLine……至于为什么我一定要用global
169 # 我也不完全理解。如果我只定义local变量总报错,我只好在那里的if里用global变量了。求解。
170 def createTree(dataSet,labels,data_full,labels_full):  
171     classList=[example[-1] for example in dataSet]    #把数据集的最后一列类别提取出来。
172     #递归停止条件1:当前节点所有样本属于同一类;(注:count()方法统计某元素在列表中出现的次数),当然不要划分了,因为ID3决策树就是用来分类的,D里面全部是同一个类别,为什么还要分呢,已近划分好了呀!
173     #print(classList)
174     if classList.count(classList[0])==len(classList):
175         return classList[0]
176     #递归停止条件2:当前节点上样本集合为空集(即特征的某个取值上已经没有样本了):
177     global numLine,numColumn
178     (numLine,numColumn)=shape(dataSet) #开始的时候为(17, 9)后面(5, 8)。。。
179     if float(numLine)==0:
180         return 'empty'
181     #递归停止条件3:所有可用于划分的特征均使用过了,则调用majorityCnt()投票定Label;
182     #没有剩余属性可以用来进一步划分样本.在这种情况下.使用多数表决,将给定的结点转换成树叶,并以样本中元组个数最多的类别作为类别标记,同时也可以存放该结点样本的类别分布
183     if float(numColumn)==1:  #只有一列属性了,当然没得划分属性了。
184         return majorityCnt(classList)
185     #不停止时继续划分:
186     bestFeat=chooseBestFeatureToSplit(dataSet,labels)#调用函数找出当前最佳划分特征是第几个
187     bestFeatLabel=labels[bestFeat]                  #当前最佳划分特征
188     myTree={bestFeatLabel:{}}                      #树是以字典形式存储划分点和对应类别标签的。
189     featValues=[example[bestFeat] for example in dataSet] #样本在bestFeature 下划分的所有属性
190     uniqueVals=set(featValues)
191     if type(dataSet[0][bestFeat]).__name__=='str':       #数据集的第0行样本,第最佳划分属性列,对应属性值为‘str’类型的判断。
192         currentlabel=labels_full.index(labels[bestFeat])   #找到划分属性的列号。
193         featValuesFull=[example[currentlabel] for example in data_full]  #所有该划分点的属性值
194         uniqueValsFull=set(featValuesFull)                   #使得当前最佳划分点所有属性值可以囊括,如第一次是Texture的{'fuzzy', 'distinct', 'blur'}三个属性值.
195     del(labels[bestFeat]) #划分完后, 即当前特征已经使用过了, 故将其从“待划分特征集”中所对应的位置删去
196     #【递归调用】针对当前用于划分的特征(bestFeat)的每个取值,划分出一个子树。
197     for value in uniqueVals:                        #遍历该特征【现存的】取值
198         subLabels=labels[:]                         #删掉划分属性后剩下的lables 列表
199         if type(dataSet[0][bestFeat]).__name__=='str':
200             uniqueValsFull.remove(value)              #划分后删去(从uniqueValsFull中删!)
201         myTree[bestFeatLabel][value]=createTree(splitDiscreteDataSet(dataSet,bestFeat,value),subLabels,data_full,labels_full)
202         #用splitDiscreteDataSet()是由于, 所有的连续特征在划分后都被我们定义的chooseBestFeatureToSplit()处理成离散取值了。
203     if type(dataSet[0][bestFeat]).__name__=='str':  #若该特征离散【更详见后注】
204         for value in uniqueValsFull:#则可能有些取值已经不在【现存的】取值中了
205                                     #这就是上面为何从“uniqueValsFull”中删去
206                                     #因为那些现有数据集中没取到的该特征的值,保留在了其中
207             myTree[bestFeatLabel][value]=majorityCnt(classList)
208     return myTree
209 
210 '''生成树调用的语句'''###########################################################
211 
212 df=pd.read_csv('watermelon3_0_En.csv')  
213 data=df.values[:,1:].tolist() #所有数据拿出来,17行9列,去掉索引列。
214 data_full=data[:] #列表里面有17行列表,每一行是一个样本的数据。
215 
216 labels=df.columns.values[1:-1].tolist()  #把最后一列样本类别作为一列。8个属性
217 
218 labels_full=labels[:]   #属性名称
219 
220 myTree=createTree(data,labels,data_full,labels_full)  
221 
222 print(myTree)
223 '''
224 params: 
225 dataSet:用于构建树的数据集,最开始就是data_full,然后随着划分的进行越来越小,第一次划分之前是17个瓜的数据在根节点,然后选择第一个bestFeat是纹理 
226 纹理的取值有清晰、模糊、稍糊三种,将瓜分成了清晰(9个),稍糊(5个),模糊(3个),这个时候应该将划分的类别减少1以便于下次划分 
227 labels:还剩下的用于划分的类别 
228 data_full:全部的数据 
229 label_full:全部的类别 
230 '''

参考资料:

  小白解释- http://blog.csdn.net/thither_shore/article/details/52358174
  总体概述- http://blog.csdn.net/sysu_cis/article/details/51842736
周志华《机器学习》

转载于:https://www.cnblogs.com/DesertHero2013/p/7799867.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值