决策树 ID3 实践

 一、实战文件

色泽 根蒂 敲声 纹理 脐部 触感 好瓜
青绿 蜷缩 浊响 清晰 凹陷 硬滑 是
乌黑 蜷缩 沉闷 清晰 凹陷 硬滑 是
乌黑 蜷缩 浊响 清晰 凹陷 硬滑 是
青绿 蜷缩 沉闷 清晰 凹陷 硬滑 是
浅白 蜷缩 浊响 清晰 凹陷 硬滑 是
青绿 稍蜷 浊响 清晰 稍凹 软粘 是
乌黑 稍蜷 浊响 稍糊 稍凹 软粘 是
乌黑 稍蜷 浊响 清晰 稍凹 硬滑 是
乌黑 稍蜷 沉闷 稍糊 稍凹 硬滑 否
青绿 硬挺 清脆 清晰 平坦 软粘 否
浅白 硬挺 清脆 模糊 稍凹 硬滑 否
浅白 蜷缩 浊响 模糊 稍凹 软粘 否
青绿 稍蜷 浊响 稍糊 稍凹 硬滑 否
浅白 稍蜷 沉闷 稍糊 稍凹 硬滑 否
乌黑 稍蜷 浊响 清晰 稍凹 软粘 否
浅白 蜷缩 浊响 模糊 稍凹 硬滑 否
青绿 蜷缩 沉闷 稍糊 稍凹 硬滑 否

 

二、实战代码

  1 # -*- coding: utf-8 -*-
  2 """
  3 Created on Sat Sep 22 19:48:18 2018
  4 
  5 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  6 !!!!!      
  7 !!!!!          
  8           ID3 algorithm  without data missing or error          
  9           Discontinuous value                                                        
 10                                                                    !!!!!
 11                                                                    !!!!!
 12 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 13 
 14 @author: Administrator
 15 """
 16 
 17 import math;
 18 import pandas as pd;
 19 import pickle;
 20 #获取数据集 返回DataFrame对象  列名为特征名 最后一列为不同类别
 21 def getData():                              
 22     with open('MelonData.txt','r') as f:
 23         y=f.read()
 24     y=y.split('\n')
 25     x=[];
 26     flag=0;
 27     for i in y:
 28         if flag==0:
 29             title=i.split(' ');
 30             flag=1
 31         else:
 32             x.append(i.split(' '))
 33     y=pd.DataFrame(x,columns=title);
 34     return y
 35 
 36 
 37 #留出法 每一类别1:4 开   将样本分为测试样本和训练样本   模型评估
 38 def divSamples(data):
 39     num={};                                       #不同类别数量
 40     cnt={};                                       #分类
 41     kinds=set( list(data[data.columns[-1] ]) );   #获取类别
 42     kinds=list(kinds);                            
 43     
 44     for i in kinds:                               #不同类别初始化        
 45         num[i]=0;
 46         cnt[i]=0;
 47     for i in data[ data.columns[-1] ]:            #统计不同类别数量  
 48         num[ i ]=num[ i ]+1
 49     trainingSets=data.copy();                     #复制一份训练集
 50     testingSets=data.copy();
 51     for i in range(0,len(data)):
 52         if(  cnt[ data[ data.columns[-1] ][i] ] >= num[  data[ data.columns[-1] ][i] ]*4//5  ):
 53             trainingSets=trainingSets.drop(i)
 54         else:
 55             testingSets=testingSets.drop(i)
 56             cnt[ data[ data.columns[-1] ][i] ]+=1
 57     trainingSets.index=list(range(0,len(trainingSets)) );   #重新行索引
 58     testingSets.index=list(range(0,len(testingSets))) ;
 59 
 60     return trainingSets,testingSets
 61     
 62 #计算所有特征下的信息增益
 63 def getFeatureEntropy(data):
 64     num={};                                       #不同类别数量  
 65     kEntropy=0                        #   每一类别的熵
 66     kPoss={}                            #   每一类别的概率( 频率 )
 67     feaEntropy={}                       #   已知每个特征的熵
 68     feaPoss={}                          #   每类特征概率
 69     feaNum={}                           #   统计每个特征不同特征值的不同类别的数量
 70     feaNumAll={}                        #   统计每个特征不同特征值的数量
 71  
 72     #不同类别的熵
 73     kinds=set( list(data[data.columns[-1] ]) );   #获取类别
 74     kinds=list(kinds);           
 75     
 76     for i in kinds:                               #不同类别初始化        
 77         num[i]=0;
 78     for i in data[ data.columns[-1] ]:            #统计不同类别数量  
 79         num[ i ]=num[ i ]+1
 80     
 81     for i in num:  
 82         kPoss[i]=num[i]/len(data);                 #  k类型概率
 83         if(kPoss[i]!=0):
 84             kEntropy+=(-kPoss[i]*math.log2(kPoss[i])); #  k类型的entropy  // log以2为底
 85                                                      
 86     #   计算在每个特征下的熵
 87     
 88     for i in range(0,len(data.columns)-1):         #   初始化feaNum,feaNumAll
 89         feaNumAll[data.columns[i]]={};              
 90         feaNum[data.columns[i]]={}   ;             #   该特征下每个特征值
 91         feaPoss[data.columns[i]]={} ;
 92                
 93         fea=set( data[data.columns[i]] );         
 94         for j in fea:                              #   同一特征值的样本数
 95             feaNumAll[data.columns[i]][j]=0;
 96             feaNum[data.columns[i]][j]={}
 97             for k in kinds:                        #   同一特征值下每个类别的样本数量
 98                 feaNum[data.columns[i]][j][k]=0;    
 99 
100     for i in range(0,len(data.columns)-1):         #   计算在每个特征下的样本数量
101         fea=data[data.columns[i]] ;
102         label=data[data.columns[-1]];
103         for j in range(0,len(fea)):                         #   每个特征值下
104             feaNum[data.columns[i]][fea[j]][label[j]]+=1 ;  #   每个类别的量\
105             feaNumAll[data.columns[i]][fea[j]] +=1;
106      
107     for i in feaNum:                                        #计算不同特征下不同特征值的概率 条件熵       
108         feaPoss[i]={}
109         for j in feaNum[i]:
110             feaPoss[i][j]={}
111             for k in feaNum[i][j]:
112                 feaPoss[i][j][k]=feaNum[i][j][k]/feaNumAll[i][j];
113     
114     for i in feaNumAll:
115         en=0;                          #每个特征下的熵     
116         for j in feaNum[i]:
117             en1=0;
118             if( feaNumAll[i][j]!=0 ):
119                 for k in feaNum[i][j]:
120                     if( feaNum[i][j][k]!=0 ):
121                         en1+=(-feaPoss[i][j][k] *math.log2( feaPoss[i][j][k]) )
122             en+=en1*feaNumAll[i][j]/len(data);
123         feaEntropy[i]=kEntropy-en; 
124     
125     return  feaEntropy;                      #计算信息每个特征的增益字典
126 
127 
128 #划分数据集
129 def splitDataSet(data,column,value):    #划分某一列某一属性值
130     newSet=data.copy();                 #复制新的数据集
131     for i in range(0,len(data)):
132         if( data[column][i]!=value ):
133             newSet=newSet.drop(i);
134     newSet=newSet.drop(columns=column);
135     newSet.index=list(range(0,len(newSet)));
136     return newSet
137 
138 #预剪枝 判断是否要划分  根据划分与不划分的错误率 
139 def prePruning(data,column):      #   data为测试数据
140     label=set(data[ data.columns[-1] ] );
141     lab={};
142     for i in label:             #每一类别数量
143         lab[i]=0;
144    
145     incorNum=0;                 #不划分时错误的数量
146     for i in data[ data.columns[-1] ] :
147         lab[i]+=1;
148     val=0
149     for i in lab:                            
150         if(val<lab[i]):
151             feaNum=lab[i]
152             NotDiv=i;            #不划分时的类别
153             val=lab[i];
154     incorNum=len(data)-feaNum;   
155                                    
156     feaVal=set(data[column]);   #计算划分时的错误数量
157     fea={}                      #特征值集合
158     for i in feaVal:            #初始化
159         fea[i]={};              
160         for j in label:
161             fea[i][j]=0;
162     
163     for i in range( 0,len(data) ):         
164         fea[ data[column][i]  ][ data[data.columns[-1] ][i] ]+=1;
165     
166     divNum=0;                   #  划分时错误的数量
167     for i in fea:     #每特征的类别数量
168         val=0;
169         allNum=0
170         for j in fea[i]:
171             if(fea[i][j]>val):
172                 val=fea[i][j];
173             allNum+=fea[i][j]
174         divNum+=(allNum-val);  
175     if(divNum<=incorNum):
176         return True,NotDiv      ###要划分,NotDiv无意义
177     else:
178         return False,NotDiv;    ##不要划分,NotDiv表示类别
179 
180 #后剪枝
181 def postPruning(testingSet,tree):   # testingSet 测试数据   递归  
182     for i in tree:
183         for j in tree[i]:
184             if( type(tree[i][j])==type({}) ):
185                 newSet=splitDataSet(testingSet,i,j);
186                 tree[i][j]=postPruning(newSet,tree[i][j]);   
187     IsDiv,label=prePruning(testingSet,list(tree)[0]);    #类似预剪枝  所以,用了prePruning 就不必用postPruning 本代码为调用postPruning函数
188     if(IsDiv):
189         return label;
190     else:
191         return tree;        
192                 
193         
194 #建树
195 def createTree(data):
196     label=set(data[ data.columns[-1] ]);
197     if(len(label)==1):
198         return data[ data.columns[-1] ][0]   #如果类别相同则返回该类别
199     tree={};
200     feaEntr=getFeatureEntropy(data)   #获取各特征信息增益
201     val=0
202     for i in feaEntr:
203         if(feaEntr[i]>val):
204             val=feaEntr[i]
205             fea=i
206    
207     IsDiv,NotLabel=prePruning(data,fea);       #预剪枝 判断是否要划分
208     if( feaEntr[fea] > 0.01 and IsDiv ):             #预剪枝
209         tree[fea]={}
210         feaVal=set(data[fea])             #获取不同特征值
211         for i in feaVal:    
212             newSet=splitDataSet(data,fea,i)
213             tree[fea][i]=createTree(newSet);
214     else:
215         tree[fea][i]=NotLabel
216     return tree;
217 
218 #模型评价  返回查准率
219 def modelEvaluation( testingSet,tree ):
220     corrNum=0;
221     testingSet=classify(tree,testingSet);
222     for i in range(0,len(testingSet)):
223         if(testingSet['好瓜'][i]==testingSet['预测'][i]):
224             corrNum+=1
225     return corrNum/len(testingSet)
226 
227 #将新的数据分类
228 def classify(tree,data):      # data 为 DataFrame 类型
229     x=[];
230     for i in range(0,len(data)):
231         tt=tree;
232         while(True):
233             fea=list(tt)[0];
234             if fea in data.columns:
235                 tt=tt[fea][data[fea][i]]
236             else:
237                 x.append(fea);
238                 break;
239     newData=data.copy();
240     newData.insert(len(data.columns),'预测',x)
241     return newData
242 #将决策树序列化        
243 def saveTree(tree):
244     with open('TreeSerializaiton.txt','wb') as f:   #pickle库序列化
245         pickle.dump(tree,f);   
246 
247 def main():
248     xx=getData();
249     trainingSet,testingSet=divSamples(xx);   ##将数据分为训练集,测试集
250     trainingSet=testingSet=xx;               ##此处由于样本数少,不进行划分
251     tree=createTree(trainingSet);            ##创建决策树
252     postPruning(testingSet,tree)             ## 后剪枝 ##剪了和没剪枝一个样,
253     rate=modelEvaluation( testingSet,tree )  ##模型评价 查准率
254     
255 if __name__== '__main__':
256     main()

 

posted @ 2018-09-25 17:47  bear_ge  阅读(239)  评论(0编辑  收藏  举报