ID3決策樹以及Python實現(xiàn)詳細過程
一、劃分特征的評價指標(biāo):
1、信息熵 Ent(D):
信息熵,是度量樣本集合純度的一種指標(biāo),Ent(D)的值越小,則樣本集D的純度越高;
2、信息增益 Gain(D,a):
信息增益越大,則意味著使用屬性a來劃分所獲得的“純度提升”越大;ID3決策樹算法就是基于信息增益來劃分屬性,下面介紹ID3決策樹的構(gòu)建過程;
公式中各變量說明:
D:樣本集;
y:標(biāo)簽(比如好瓜、壞瓜);
pk:某一類樣本占總樣本數(shù)的比例;
V:屬性的取值(比如紋理屬性有3種取值:清晰、稍糊、模糊);
Dv:屬性值==V從樣本集D劃分出的一個樣本子集;
二、決策樹學(xué)習(xí)算法偽代碼:
決策樹的生成是一個遞歸的過程,在決策樹基本算法中,有三種情形會導(dǎo)致遞歸返回:
當(dāng)前結(jié)點包含的樣本全屬于同一類別,無需劃分;當(dāng)前屬性集為空,或是所有樣本在所有屬性上取值相同,無法劃分;當(dāng)前結(jié)點包含的樣本集合為空,不能劃分;
三、決策樹生成實例:
3.1 首先獲取一個訓(xùn)練樣本集D,作為決策樹的訓(xùn)練依據(jù):
3.2 計算信息增益:
1、計算信息熵 Ent(D):
2、計算當(dāng)前特征集合 {色澤,根蒂,敲聲,紋理,臍部,觸感} 中各個特征a的信息增益Gain(D,a):
以“色澤”為例計算Gain(D,色澤):
色澤的取值:{青綠,烏黑,淺白},使用“色澤”特征對D劃分可以得到3個子集:D1(色澤=青綠)={1,4,6,10,13,17},D2(色澤=烏黑)={2,3,7,8,9,15},D1(色澤=淺白)={5,11,12,14,16},計算劃分子集后分支結(jié)點的熵 :
所以,得到Gain(D,色澤):
同理,計算其他特征的信息增益Gain(D,a):
3.3 選取最優(yōu)信息增益,選取最優(yōu)劃分特征:
因為Gain(D,紋理)最大,所以選取“紋理”作為本輪劃分的最優(yōu)劃分特征,繼而可以得到基于“紋理”的根節(jié)點劃分:
3.4 決策樹算法再對每個分支進一步劃分(遞歸):
將每個分支可以看成一個新的樣本集,進行進一步的劃分,在計算各特征信息增益時,需要將上一輪選出的最優(yōu)特征在樣本中去掉,不需要再對該特征進行比較。
就比如D1={1,2,3,4,5,6,8,10,15},特征集合={色澤,根蒂,敲聲,臍部,觸感}。基于D1計算出各特征的信息增益Gain(D1,a):
繼續(xù)選取最大的特征信息增益,選出最優(yōu)劃分特征,即重復(fù)3.3步驟,遞歸實現(xiàn)決策樹的建立;
3.5 生成最終的決策樹:
四、Python實現(xiàn)ID3決策樹:
總樣本集:
['青綠','蜷縮','濁響','清晰','凹陷','硬滑','好瓜'],
['烏黑','蜷縮','沉悶','清晰','凹陷','硬滑','好瓜'],
['烏黑','蜷縮','濁響','清晰','凹陷','硬滑','好瓜'],
['青綠','蜷縮','沉悶','清晰','凹陷','硬滑','好瓜'],
['青綠','稍蜷','濁響','清晰','稍凹','軟粘','好瓜'],
['烏黑','稍蜷','濁響','稍糊','稍凹','軟粘','好瓜'],
['烏黑','稍蜷','濁響','清晰','稍凹','硬滑','好瓜'],
['淺白','蜷縮','濁響','清晰','凹陷','硬滑','好瓜'],
['淺白','蜷縮','濁響','模糊','平坦','硬滑','壞瓜'],
['烏黑','稍蜷','沉悶','稍糊','稍凹','硬滑','壞瓜'],
['青綠','硬挺','清脆','清晰','平坦','軟粘','壞瓜'],
['淺白','蜷縮','濁響','模糊','平坦','軟粘','壞瓜'],
['青綠','稍蜷','濁響','稍糊','凹陷','硬滑','壞瓜'],
['淺白','稍蜷','沉悶','稍糊','凹陷','硬滑','壞瓜'],
['淺白','硬挺','清脆','模糊','平坦','硬滑','壞瓜'],
['烏黑','稍蜷','濁響','清晰','稍凹','軟粘','壞瓜'],
['青綠','蜷縮','沉悶','稍糊','稍凹','硬滑','壞瓜']
下面從總樣本種提取序號5、12、17為驗證集,剩下為訓(xùn)練集進行訓(xùn)練決策樹;
(1)訓(xùn)練集:
['青綠','蜷縮','濁響','清晰','凹陷','硬滑','好瓜'],
['烏黑','蜷縮','沉悶','清晰','凹陷','硬滑','好瓜'],
['烏黑','蜷縮','濁響','清晰','凹陷','硬滑','好瓜'],
['青綠','蜷縮','沉悶','清晰','凹陷','硬滑','好瓜'],
['烏黑','稍蜷','濁響','稍糊','稍凹','軟粘','好瓜'],
['烏黑','稍蜷','濁響','清晰','稍凹','硬滑','好瓜'],
['淺白','蜷縮','濁響','清晰','凹陷','硬滑','好瓜'],
['淺白','蜷縮','濁響','模糊','平坦','硬滑','壞瓜'],
['烏黑','稍蜷','沉悶','稍糊','稍凹','硬滑','壞瓜'],
['淺白','蜷縮','濁響','模糊','平坦','軟粘','壞瓜'],
['青綠','稍蜷','濁響','稍糊','凹陷','硬滑','壞瓜'],
['淺白','稍蜷','沉悶','稍糊','凹陷','硬滑','壞瓜'],
['淺白','硬挺','清脆','模糊','平坦','硬滑','壞瓜'],
['青綠','蜷縮','沉悶','稍糊','稍凹','硬滑','壞瓜']
(2)驗證集:
['青綠','稍蜷','濁響','清晰','稍凹','軟粘'], ['好瓜']
['青綠','硬挺','清脆','清晰','平坦','軟粘'], ['壞瓜']
['烏黑','稍蜷','濁響','清晰','稍凹','軟粘'], ['壞瓜']
下面編寫各個函數(shù),每個函數(shù)有特定的功能,代碼的分析過程已在code后注釋。
4.1 構(gòu)建樣本集:
#? 構(gòu)建數(shù)據(jù)集 # 返回一個元組 (dataSet,labels) def createDataSet(): # 創(chuàng)造示例數(shù)據(jù) dataSet=[['青綠','蜷縮','濁響','清晰','凹陷','硬滑','好瓜'], ['烏黑','蜷縮','沉悶','清晰','凹陷','硬滑','好瓜'], ['烏黑','蜷縮','濁響','清晰','凹陷','硬滑','好瓜'], ['青綠','蜷縮','沉悶','清晰','凹陷','硬滑','好瓜'], ['青綠','稍蜷','濁響','清晰','稍凹','軟粘','好瓜'], ['烏黑','稍蜷','濁響','稍糊','稍凹','軟粘','好瓜'], ['烏黑','稍蜷','濁響','清晰','稍凹','硬滑','好瓜'], ['烏黑','稍蜷','沉悶','稍糊','稍凹','硬滑','壞瓜'], ['青綠','硬挺','清脆','清晰','平坦','軟粘','壞瓜'], ['淺白','蜷縮','濁響','模糊','平坦','軟粘','壞瓜'], ['青綠','稍蜷','濁響','稍糊','凹陷','硬滑','壞瓜'], ['淺白','稍蜷','沉悶','稍糊','凹陷','硬滑','壞瓜'], ['烏黑','稍蜷','濁響','清晰','稍凹','軟粘','壞瓜'], ['青綠','蜷縮','沉悶','稍糊','稍凹','硬滑','壞瓜']] labels = ['色澤','根蒂','敲聲','紋理','臍部','觸感'] #六個特征 return dataSet,labels
函數(shù)作用:用于構(gòu)建訓(xùn)練集
變量說明:
- dataSet:樣本集
- labels:所有特征
4.2 計算信息熵:
#? 計算信息熵 # 返回輸入樣本集dataSet的信息熵 Ent from math import log def calEnt(dataSet): sampleCounts=len(dataSet) # 樣本集的樣本數(shù) labelCounts={} # key為標(biāo)簽值label(好瓜、壞瓜),value為對應(yīng)標(biāo)簽key在樣本集中出現(xiàn)的次數(shù) for sample in dataSet: # 遍歷樣本集dataSet中每個樣本sample label=sample[-1] # 標(biāo)簽label為樣本sample的最后一個元素值 if label not in labelCounts.keys(): # 如果該標(biāo)簽label不在字典labelCounts的key值中 labelCounts[label]=0 # 則新增該key,并賦初值0 labelCounts[label]+=1 # 對遍歷到的每個sample統(tǒng)計其所屬標(biāo)簽的個數(shù) Ent=0.0 # 信息熵初始化 for key in labelCounts: pro=float(labelCounts[key])/sampleCounts # 具體標(biāo)簽占總樣本數(shù)的比例pro Ent-=pro*log(pro,2) # 計算樣本集dataSet的信息熵Ent return Ent
函數(shù)作用:計算樣本集dataSet的信息熵E(dataSet)
變量說明:
dataSet:傳入的樣本集
sampleCounts:樣本集中的樣本數(shù)
labelCounts:key為標(biāo)簽值(好瓜/壞瓜),value為對應(yīng)標(biāo)簽key在樣本集中出現(xiàn)的次數(shù)
sample:具體樣本
label:標(biāo)簽(好瓜、壞瓜)
pro:具體標(biāo)簽占總樣本數(shù)的比例
Ent:樣本集dataSet的熵 Ent(D)
4.3 按給定的特征值劃分出樣本子集:
#? 按給定特征值劃分出樣本子集 # 指定特征列的索引index,對特征值==value的樣本劃分出來為一個樣本子集retDataSet,并對這些樣本的value去掉,返回樣本子集 retDataSet def splitDataSet(dataSet,index,value): # index是指定特征列的索引,value是該特征下的某一特征值 retDataSet=[] for sample in dataSet: # 遍歷樣本集dataSet中的具體樣本sample if sample[index]==value: # 找到目標(biāo)特征值value的索引 # 去除特征值==value這些樣本的vlaue值 reducedSample=sample[:index] # 剪下目標(biāo)索引前的列表 reducedSample.extend(sample[index+1:]) # 將目標(biāo)索引后的列表添加到索引前列表的后面 retDataSet.append(reducedSample) # 將sample[index]==value并去除該vlaue的樣本添加到retDataSet樣本集中 return retDataSet
函數(shù)作用:指定特征列的索引index,對樣本集中特征值==value的具體樣本sample劃分出來,組成一個dataSet的樣本子集retDataSet(并將這些樣本中的這些value去掉,去掉sample[index]的目的是因為下輪比較各特征信息增益Gain從而獲得最大信息增益bestGain(決定最優(yōu)劃分特征bestFeature)時,不能將已選出的最優(yōu)特征放在比較隊列中)
變量說明:
dataSet:傳入的樣本集
index:指定特征列的索引
value:指定特征的某一特征值
sample:dataSet的具體樣本
reducedSample:去除value后的具體樣本(該樣本sample[index]==value)
retDataSet:按指定某一特征值劃分出的樣本子集
4.4 選取當(dāng)前樣本集下的最優(yōu)劃分特征索引:
#? 選取當(dāng)前樣集下的最優(yōu)劃分特征索引 # 返回最優(yōu)劃分特征的索引 bestFeatureIndex def chooseBestFeatureToSplit(dataSet): featureCounts=len(dataSet[0])-1 # 獲取當(dāng)前樣本集的特征個數(shù),-1是因為最后一列是標(biāo)簽 baseEnt=calEnt(dataSet) # 計算當(dāng)前樣本集的信息熵Ent(D) bestGain=0.0;bestFeatureIndex=-1 # 初始化最優(yōu)信息增益bestGain、最優(yōu)特征bestFeature for i in range(featureCounts): # 遍歷每個特征,求各自的信息增益Gain featValList=[sample[i] for sample in dataSet] # 第i個特征下所有樣本出現(xiàn)的特征值(有重復(fù)) uniqueVals=set(featValList) # 第i個特征的可能特征值(無重復(fù)) newEnt=0.0 # 初始化信息熵 for value in uniqueVals: subDataSet=splitDataSet(dataSet,i,value) # 根據(jù)特定的特征值value劃分出的樣本子集 pro=len(subDataSet)/float(len(dataSet)) # 劃分出的樣本子集占總樣本數(shù)的比例 newEnt+=pro*calEnt(subDataSet) # 計算各特征值的熵并加和 Gain=baseEnt-newEnt # 計算信息增益Gain(D,a) if(Gain>bestGain): # 求最大的信息增益Gain bestGain=Gain bestFeatureIndex=i # 獲取最優(yōu)劃分特征的索引 return bestFeatureIndex
函數(shù)作用:計算各特征的信息增益Gain(dataset,feature),從而選出最優(yōu)劃分特征bestFeature,最后返回最優(yōu)劃分特征的索引bestFeatureIndex;
變量說明:
dataSet:傳入的樣本集
featureCounts:當(dāng)前樣本集中特征的個數(shù)
baseEnt:當(dāng)前樣本集的熵 Ent(D)
bestGain:各特征中最大的信息增益 Gain(dataSet,bestFeature)
bestFeatureIndex:最優(yōu)劃分特征的索引列號
sample[i]:具體樣本第i個特征值
featureValList:第i個特征下所有樣本中出現(xiàn)的特征值(有重復(fù)值)
uniqueVals:第i個特征的可能特征值(無重復(fù)值)
newEnt:不同特征值下的熵 Ent(Di)
subDataSet:根據(jù)特定的特征值value劃分出的樣本子集
pro:樣本子集占總樣本數(shù)的比例
Gain:各個特征的信息增益Gain(D,a)
4.5 求樣本集中出現(xiàn)次數(shù)最多的標(biāo)簽:
#? 求樣本集中出現(xiàn)次數(shù)最多的標(biāo)簽 # 用于葉子節(jié)點的取值,返回樣本集中出現(xiàn)次數(shù)最多的標(biāo)簽 sortedLabelCounts[0][0] import operator def majorLabel(labelList): labelCounts={} # key為標(biāo)簽(好瓜/壞瓜),value為標(biāo)簽在labelList中出現(xiàn)的次數(shù) for label in labelList: # 遍歷所有樣本的標(biāo)簽 if label not in labelCounts.keys(): # 如果該標(biāo)簽不在labelCounts的key值中 labelCounts[label]=0 # 則增加該key值,并賦初值=0 labelCounts[label]+=1 # 對labelCounts中已有的標(biāo)簽計數(shù)+1 sortedLabelCounts=sorted(labelCounts.items(),key=operator.itemgetter(1),reverse=True) # 根據(jù)value值逆序排序labelCounts return sortedLabelCounts[0][0] # 返回第一個元素的第一個元素(標(biāo)簽)
函數(shù)作用:選取葉子結(jié)點的取值,返回樣本集中出現(xiàn)次數(shù)最多的標(biāo)簽(好瓜/壞瓜)sortedLabelCounts[0][0];
變量說明:
labelList:返回樣本集中所有樣本的標(biāo)簽(有重復(fù)值)
labelCounts:字典,key為標(biāo)簽,value為該標(biāo)簽key在labelList中出現(xiàn)的次數(shù)
label:具體標(biāo)簽(好瓜/壞瓜)
labelCounts.keys():labelCounts的key值
labelCounts[label]:labelCounts中key值==label對應(yīng)的value值
sortedLabelCounts:根據(jù)value值,逆序排列l(wèi)abelCounts字典
sotredLabelCounts[0][0]:樣本集中出現(xiàn)次數(shù)最多的標(biāo)簽
4.6 遞歸生成決策樹:
#? 生成決策樹 主方法 # 遞歸生成決策樹 decisionTree # 遞歸是逐級由深向淺的返回 def createTree(dataSet,labels): labelList=[sample[-1] for sample in dataSet] # 返回當(dāng)前樣本集dataSet中所有樣本的標(biāo)簽(有重復(fù)值列表) # 跳出遞歸,生成葉子節(jié)點(好瓜/壞瓜) if labelList.count(labelList[0])==len(labelList): # 如果labelList中的標(biāo)簽完全相同 return labelList[0] # 則直接返回該標(biāo)簽 if len(dataSet[0])==1: # 如果當(dāng)前樣本集dataSet的樣本長度==1(只剩最后一列標(biāo)簽,無特征可供繼續(xù)劃分又不滿足所有標(biāo)簽相同) return majorLabel(labelList) # 就返回出現(xiàn)次數(shù)最多的標(biāo)簽作為葉子節(jié)點 bestFeatureIndex=chooseBestFeatureToSplit(dataSet) # 獲取當(dāng)前樣本集dataSet最優(yōu)劃分特征的索引 bestFeature=labels[bestFeatureIndex] # 獲取當(dāng)前樣本集dataSet的最優(yōu)劃分特征 decisionTree={bestFeature:{}} # 字典存儲決策樹的信息 del(labels[bestFeatureIndex]) # 刪除已經(jīng)選出的特征 featureVals=[sample[bestFeatureIndex] for sample in dataSet] # 樣本集中所有樣本中的最優(yōu)特征對應(yīng)的特征值組成的列表(有重復(fù)值) uniqueVals=set(featureVals) # 最優(yōu)特征對應(yīng)的所有可能取值(無重復(fù)值) for value in uniqueVals: # 遍歷最優(yōu)特征所有可能的取值value subLabels=labels[:] # 將最優(yōu)特征去除后的特征列表傳遞給subLabels decisionTree[bestFeature][value]=createTree(splitDataSet(dataSet,bestFeatureIndex,value),subLabels) # 遞歸生成decisionTree return decisionTree
函數(shù)作用:遞歸生成決策樹 decisionTree
變量說明:
dataSet:傳入的樣本集
labels:傳入的特征列表
labelList:存放樣本集dataSet中所有樣本的標(biāo)簽(有重復(fù)值)
sample:樣本集的具體樣本
labelList[0]:第一個樣本的標(biāo)簽
dataSet[0]:樣本集中的第一個樣本
majorLabel(labelList):樣本集中出現(xiàn)次數(shù)最多的標(biāo)簽
bestFeatureIndex:當(dāng)前樣本集中最優(yōu)劃分特征的索引列
bestFeature:當(dāng)前樣本集中最優(yōu)的劃分特征
labels[bestFeatureIndex]:最優(yōu)劃分特征索引對應(yīng)的具體特征
decisionTree:生成的決策樹
featureVals:樣本集dataSet中最優(yōu)特征對應(yīng)的所有特征值(有重復(fù)值)
uniqueVals:最優(yōu)特征對應(yīng)的可能取值(無重復(fù)值)
value:最優(yōu)特征對應(yīng)的具體取值
subLabels:去除最優(yōu)特征后的特征列表
4.7 對決策樣本進行分類:
#? 對驗證樣本進行分類 # 返回一個對樣本分類后的標(biāo)簽classLabel def classify(decisionTree,features,testSample): rootFeature=list(decisionTree.keys())[0] # rootFeature:根節(jié)點是何種特征 rootDict=decisionTree[rootFeature] # rootDict為根節(jié)點的value值,是一個字典 rootFeatureIndex=features.index(rootFeature) # 獲取根節(jié)點在特征列表中的索引 for value in rootDict.keys(): # value為特征rootFeature的不同取值,并遍歷value if testSample[rootFeatureIndex]==value: # 如果待測樣本的該特征的特征值==value if type(rootDict[value])==dict: # 如果該特征值value對應(yīng)的value'是一個字典 classLabel=classify(rootDict[value],features,testSample) # 則需要遞歸繼續(xù)向決策樹的下面結(jié)點查詢 else: # 如果該特征值value對應(yīng)的value'是一個單獨的值(標(biāo)簽) classLabel=rootDict[value] # 則該值就是要找的標(biāo)簽 return classLabel # 返回該樣本testSample的標(biāo)簽
函數(shù)作用:對傳入的待測樣本testSample根據(jù)已生成的決策樹decisionTree計算出該樣本的標(biāo)簽(好瓜/壞瓜),返回該標(biāo)簽 classLabel
變量說明:
decisionTree:某一結(jié)點出發(fā)的決策樹
features:所有特征列表
testSample:待測試樣本
decisionTree.keys():(某一特征值下)對應(yīng)根結(jié)點
decisionTree[rootFeature]:根節(jié)點對應(yīng)的各個分支,字典
rootFeature:根節(jié)點(如紋理)
rootDict:根節(jié)點下的分支,字典(紋理結(jié)點對應(yīng)的三個分支:模糊、清晰、稍糊)
rootFeatureIndex:節(jié)點在特征列表features中的索引;
value:以根節(jié)點為特征的不同特征取值(如模糊/清晰/稍糊)
testSample[rootFeatureIndex]:待測試樣本中以根節(jié)點為特征對應(yīng)的具體特征值
rootDict[value]:具體特征值對應(yīng)的value(可能是一個字典/標(biāo)簽)
classLabel:該待測試樣本計算出的標(biāo)簽
4.8 執(zhí)行:
if __name__=='__main__': # 如果在當(dāng)前模塊/文件下執(zhí)行,將會指定下述代碼 dataSet, labels=createDataSet() decisionTree=createTree(dataSet, labels) print(f"\ndecisionTree={decisionTree}\n") # 輸出決策樹模型結(jié)果 # 驗證集 features= ['色澤','根蒂','敲聲','紋理','臍部','觸感'] # 特征列表 testSample=['淺白','蜷縮','濁響','清晰','凹陷','硬滑'] # 待測樣本 print(f"測試結(jié)果1sampleLabel= {classify(decisionTree,features,testSample)}\n") # 輸出測試結(jié)果 features= ['色澤','根蒂','敲聲','紋理','臍部','觸感'] # 特征列表 testSample=['淺白','硬挺','清脆','模糊','平坦','硬滑'] # 待測樣本 print(f"測試結(jié)果2sampleLabel= {classify(decisionTree,features,testSample)}\n") # 輸出測試結(jié)果 features= ['色澤','根蒂','敲聲','紋理','臍部','觸感'] # 特征列表 testSample=['淺白','蜷縮','濁響','模糊','平坦','硬滑'] # 待測樣本 print(f"測試結(jié)果3sampleLabel= {classify(decisionTree,features,testSample)}\n") # 輸出測試結(jié)果
函數(shù)說明:執(zhí)行主函數(shù)代碼,利用上述各函數(shù)打印出最終的決策樹decisionTree并且對驗證集待測樣本進行測試檢驗
變量說明:
features:特征列表
testSample:待測試樣本
4.9 運行結(jié)果:
decisionTree={'紋理': {'稍糊': {'觸感': {'硬滑': '壞瓜', '軟粘': '好瓜'}}, '清晰': {'根蒂': {'硬挺': '壞瓜', '蜷縮': '好瓜', '稍蜷': {'色澤': {'青綠': '好瓜', '烏黑': {'觸感': {'硬滑': '好瓜', '軟粘': '壞瓜'}}}}}}, '模糊': '壞瓜'}} 測試結(jié)果1sampleLabel= 好瓜 測試結(jié)果2sampleLabel= 壞瓜 測試結(jié)果3sampleLabel= 壞瓜
決策樹decisionTree:
{'紋理': {'模糊': '壞瓜', '清晰': {'根蒂': {'稍蜷': {'色澤': {'烏黑': {'觸感': {'軟粘': '壞瓜', '硬滑': '好瓜'}}, '青綠': '好瓜'}}, '硬挺': '壞瓜', '蜷縮': '好瓜'}}, '稍糊': {'觸感': {'軟粘': '好瓜', '硬滑': '壞瓜'}}}}
可視化為樹狀結(jié)構(gòu)為:
4.10 實現(xiàn)決策樹的總代碼:
#! Decision Tree(ID3算法 信息增益Gain) #? 構(gòu)建數(shù)據(jù)集 # 返回一個元組 (dataSet,labels) def createDataSet(): # 創(chuàng)造示例數(shù)據(jù) dataSet=[['青綠','蜷縮','濁響','清晰','凹陷','硬滑','好瓜'], ['烏黑','蜷縮','沉悶','清晰','凹陷','硬滑','好瓜'], ['烏黑','蜷縮','濁響','清晰','凹陷','硬滑','好瓜'], ['青綠','蜷縮','沉悶','清晰','凹陷','硬滑','好瓜'], ['青綠','稍蜷','濁響','清晰','稍凹','軟粘','好瓜'], ['烏黑','稍蜷','濁響','稍糊','稍凹','軟粘','好瓜'], ['烏黑','稍蜷','濁響','清晰','稍凹','硬滑','好瓜'], ['烏黑','稍蜷','沉悶','稍糊','稍凹','硬滑','壞瓜'], ['青綠','硬挺','清脆','清晰','平坦','軟粘','壞瓜'], ['淺白','蜷縮','濁響','模糊','平坦','軟粘','壞瓜'], ['青綠','稍蜷','濁響','稍糊','凹陷','硬滑','壞瓜'], ['淺白','稍蜷','沉悶','稍糊','凹陷','硬滑','壞瓜'], ['烏黑','稍蜷','濁響','清晰','稍凹','軟粘','壞瓜'], ['青綠','蜷縮','沉悶','稍糊','稍凹','硬滑','壞瓜']] labels = ['色澤','根蒂','敲聲','紋理','臍部','觸感'] #六個特征 return dataSet,labels #? 計算信息熵 # 返回輸入樣本集dataSet的信息熵 Ent from math import log def calEnt(dataSet): sampleCounts=len(dataSet) # 樣本集的樣本數(shù) labelCounts={} # key為標(biāo)簽值label(好瓜、壞瓜),value為對應(yīng)標(biāo)簽key在樣本集中出現(xiàn)的次數(shù) for sample in dataSet: # 遍歷樣本集dataSet中每個樣本sample label=sample[-1] # 標(biāo)簽label為樣本sample的最后一個元素值 if label not in labelCounts.keys(): # 如果該標(biāo)簽label不在字典labelCounts的key值中 labelCounts[label]=0 # 則新增該key,并賦初值0 labelCounts[label]+=1 # 對遍歷到的每個sample統(tǒng)計其所屬標(biāo)簽的個數(shù) Ent=0.0 # 信息熵初始化 for key in labelCounts: pro=float(labelCounts[key])/sampleCounts # 具體標(biāo)簽占總樣本數(shù)的比例pro Ent-=pro*log(pro,2) # 計算樣本集dataSet的信息熵Ent return Ent #? 按給定特征值劃分出樣本子集 # 指定特征列的索引index,對特征值==value的樣本劃分出來為一個樣本子集retDataSet,并對這些樣本的value去掉,返回樣本子集 retDataSet def splitDataSet(dataSet,index,value): # index是指定特征列的索引,value是該特征下的某一特征值 retDataSet=[] for sample in dataSet: # 遍歷樣本集dataSet中的具體樣本sample if sample[index]==value: # 找到目標(biāo)特征值value的索引 # 去除特征值==value這些樣本的vlaue值 reducedSample=sample[:index] # 剪下目標(biāo)索引前的列表 reducedSample.extend(sample[index+1:]) # 將目標(biāo)索引后的列表添加到索引前列表的后面 retDataSet.append(reducedSample) # 將sample[index]==value并去除該vlaue的樣本添加到retDataSet樣本集中 return retDataSet #? 選取當(dāng)前樣集下的最優(yōu)劃分特征索引 # 返回最優(yōu)劃分特征的索引 bestFeatureIndex def chooseBestFeatureToSplit(dataSet): featureCounts=len(dataSet[0])-1 # 獲取當(dāng)前樣本集的特征個數(shù),-1是因為最后一列是標(biāo)簽 baseEnt=calEnt(dataSet) # 計算當(dāng)前樣本集的信息熵Ent(D) bestGain=0.0;bestFeatureIndex=-1 # 初始化最優(yōu)信息增益bestGain、最優(yōu)特征bestFeature for i in range(featureCounts): # 遍歷每個特征,求各自的信息增益Gain featValList=[sample[i] for sample in dataSet] # 第i個特征下所有樣本出現(xiàn)的特征值(有重復(fù)) uniqueVals=set(featValList) # 第i個特征的可能特征值(無重復(fù)) newEnt=0.0 # 初始化信息熵 for value in uniqueVals: subDataSet=splitDataSet(dataSet,i,value) # 根據(jù)特定的特征值value劃分出的樣本子集 pro=len(subDataSet)/float(len(dataSet)) # 劃分出的樣本子集占總樣本數(shù)的比例 newEnt+=pro*calEnt(subDataSet) # 計算各特征值的熵并加和 Gain=baseEnt-newEnt # 計算信息增益Gain(D,a) if(Gain>bestGain): # 求最大的信息增益Gain bestGain=Gain bestFeatureIndex=i # 獲取最優(yōu)劃分特征的索引 return bestFeatureIndex #? 求樣本集中出現(xiàn)次數(shù)最多的標(biāo)簽 # 用于葉子節(jié)點的取值,返回樣本集中出現(xiàn)次數(shù)最多的標(biāo)簽 sortedLabelCounts[0][0] import operator def majorLabel(labelList): labelCounts={} # key為標(biāo)簽(好瓜/壞瓜),value為標(biāo)簽在labelList中出現(xiàn)的次數(shù) for label in labelList: # 遍歷所有樣本的標(biāo)簽 if label not in labelCounts.keys(): # 如果該標(biāo)簽不在labelCounts的key值中 labelCounts[label]=0 # 則增加該key值,并賦初值=0 labelCounts[label]+=1 # 對labelCounts中已有的標(biāo)簽計數(shù)+1 sortedLabelCounts=sorted(labelCounts.items(),key=operator.itemgetter(1),reverse=True) # 根據(jù)value值逆序排序labelCounts return sortedLabelCounts[0][0] # 返回第一個元素的第一個元素(標(biāo)簽) #? 生成決策樹 主方法 # 遞歸生成決策樹 decisionTree # 遞歸是逐級由深向淺的返回 def createTree(dataSet,labels): labelList=[sample[-1] for sample in dataSet] # 返回當(dāng)前樣本集dataSet中所有樣本的標(biāo)簽(有重復(fù)值列表) # 跳出遞歸,生成葉子節(jié)點(好瓜/壞瓜) if labelList.count(labelList[0])==len(labelList): # 如果labelList中的標(biāo)簽完全相同 return labelList[0] # 則直接返回該標(biāo)簽 if len(dataSet[0])==1: # 如果當(dāng)前樣本集dataSet的樣本長度==1(只剩最后一列標(biāo)簽,無特征可供繼續(xù)劃分又不滿足所有標(biāo)簽相同) return majorLabel(labelList) # 就返回出現(xiàn)次數(shù)最多的標(biāo)簽作為葉子節(jié)點 bestFeatureIndex=chooseBestFeatureToSplit(dataSet) # 獲取當(dāng)前樣本集dataSet最優(yōu)劃分特征的索引 bestFeature=labels[bestFeatureIndex] # 獲取當(dāng)前樣本集dataSet的最優(yōu)劃分特征 decisionTree={bestFeature:{}} # 字典存儲決策樹的信息 del(labels[bestFeatureIndex]) # 刪除已經(jīng)選出的特征 featureVals=[sample[bestFeatureIndex] for sample in dataSet] # 樣本集中所有樣本中的最優(yōu)特征對應(yīng)的特征值組成的列表(有重復(fù)值) uniqueVals=set(featureVals) # 最優(yōu)特征對應(yīng)的所有可能取值(無重復(fù)值) for value in uniqueVals: # 遍歷最優(yōu)特征所有可能的取值value subLabels=labels[:] # 將最優(yōu)特征去除后的特征列表傳遞給subLabels decisionTree[bestFeature][value]=createTree(splitDataSet(dataSet,bestFeatureIndex,value),subLabels) # 遞歸生成decisionTree return decisionTree #? 對驗證樣本進行分類 # 返回一個對樣本分類后的標(biāo)簽classLabel def classify(decisionTree,features,testSample): rootFeature=list(decisionTree.keys())[0] # rootFeature:根節(jié)點是何種特征 rootDict=decisionTree[rootFeature] # rootDict為根節(jié)點的value值,是一個字典 rootFeatureIndex=features.index(rootFeature) # 獲取根節(jié)點在特征列表中的索引 for value in rootDict.keys(): # value為特征rootFeature的不同取值,并遍歷value if testSample[rootFeatureIndex]==value: # 如果待測樣本的該特征的特征值==value if type(rootDict[value])==dict: # 如果該特征值value對應(yīng)的value'是一個字典 classLabel=classify(rootDict[value],features,testSample) # 則需要遞歸繼續(xù)向決策樹的下面結(jié)點查詢 else: # 如果該特征值value對應(yīng)的value'是一個單獨的值(標(biāo)簽) classLabel=rootDict[value] # 則該值就是要找的標(biāo)簽 return classLabel # 返回該樣本testSample的標(biāo)簽 if __name__=='__main__': # 如果在當(dāng)前模塊/文件下執(zhí)行,將會指定下述代碼 dataSet, labels=createDataSet() decisionTree=createTree(dataSet, labels) print(f"\ndecisionTree={decisionTree}\n") # 輸出決策樹模型結(jié)果 # 驗證集 features= ['色澤','根蒂','敲聲','紋理','臍部','觸感'] # 特征列表 testSample=['淺白','蜷縮','濁響','清晰','凹陷','硬滑'] # 待測樣本 print(f"測試結(jié)果1sampleLabel= {classify(decisionTree,features,testSample)}\n") # 輸出測試結(jié)果 features= ['色澤','根蒂','敲聲','紋理','臍部','觸感'] # 特征列表 testSample=['淺白','硬挺','清脆','模糊','平坦','硬滑'] # 待測樣本 print(f"測試結(jié)果2sampleLabel= {classify(decisionTree,features,testSample)}\n") # 輸出測試結(jié)果 features= ['色澤','根蒂','敲聲','紋理','臍部','觸感'] # 特征列表 testSample=['淺白','蜷縮','濁響','模糊','平坦','硬滑'] # 待測樣本 print(f"測試結(jié)果3sampleLabel= {classify(decisionTree,features,testSample)}\n") # 輸出測試結(jié)果
總結(jié)
到此這篇關(guān)于ID3決策樹以及Python實現(xiàn)的文章就介紹到這了,更多相關(guān)Python實現(xiàn)ID3決策樹內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python win32com 操作Exce的l簡單方法(必看)
下面小編就為大家?guī)硪黄狿ython win32com 操作Exce的l簡單方法(必看)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05Python實現(xiàn)Opencv cv2.Canny()邊緣檢測
這篇博客將介紹Canny邊緣檢測的概念,并利用cv2.Canny()實現(xiàn)邊緣檢測,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-07-07python中不同數(shù)據(jù)對象的空值校驗總結(jié)
在Python中,我們可以使用不同的方式來校驗數(shù)值的空值、字符串的空值以及對象的空值,本文為大家整理了一些常見的方法,希望對大家有所幫助2024-01-01