决策树 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()