下表爲是否適合打壘球的決策表,預測E= {天氣=晴,溫度=適中,溼度=正常,風速=弱} 的場合,是否合適中打壘球。數組
天氣 app |
溫度 ide |
溼度 函數 |
風速 學習 |
活動 spa |
晴code |
炎熱orm |
高blog |
弱排序 |
取消 |
晴 |
炎熱 |
高 |
強 |
取消 |
陰 |
炎熱 |
高 |
弱 |
進行 |
雨 |
適中 |
高 |
弱 |
進行 |
雨 |
寒冷 |
正常 |
弱 |
進行 |
雨 |
寒冷 |
正常 |
強 |
取消 |
陰 |
寒冷 |
正常 |
強 |
進行 |
晴 |
適中 |
高 |
弱 |
取消 |
晴 |
寒冷 |
正常 |
弱 |
進行 |
雨 |
適中 |
正常 |
弱 |
進行 |
晴 |
適中 |
正常 |
強 |
進行 |
陰 |
適中 |
高 |
強 |
進行 |
陰 |
炎熱 |
正常 |
弱 |
進行 |
雨 |
適中 |
高 |
強 |
取消 |
如何發現這些數據之中所掩藏的規律,從而較好的預測在給定條件下,所可能的結果。決策樹是一種以示例爲基礎的概括學習方法,可以較好的解決這類問題。
請給出布爾函數(A * -B)+ C(+:或,*:與,-非)的最小體積(或結點)決策樹。
當C爲1時,AB無論取何值整個表達式都爲真,此時這個表達式就能夠肯定真假,因此選擇C做爲頭結點。若C爲0,表達式沒法肯定真假,還需進一步看AB的取值,A與非B是與的關係,二者具備相同的地位,因此接下來不管取A仍是B均可以,整個決策樹構造結果以下圖所示。
相似於這個簡單例子對於打壘球這些數據,咱們能夠將天氣,溫度,溼度,風速(能夠成爲屬性或特徵)類比成布爾函數的ABC,而它們的取值,如天氣的取值能夠是晴,雨,陰類比成ABC布爾取值真假,那麼活動的取消或進行,就能夠類比成整個布爾表達式的真或假。要構造一顆最小體積決策樹,就要每次在各個屬性中找到區分度最大的屬性來做爲當前決策樹的節點。
熵
一般熵表示事物的混亂程度,熵越大表示混亂程度越大,越小表示混亂程度越小。對於隨機事件S,若是咱們知道它有N種取值狀況,每種狀況發生的概論爲,那麼這件事的熵就定義爲:
例如對於打壘球的例子,要求活動的熵H(活動)。在活動一欄屬性中發現活動的取值有兩種:取消(5個)和進行(9個),它們所佔的比例分別爲5/14,9/14。那麼H(活動)的取值爲:,算出的結果約爲0.94。
對於熵的理解
若是一件事發生的多是1,不發生的肯爲0那麼這件事的熵爲=0,這就代表這件事確定發生,沒有不發生的狀況,那麼它的混亂程度是最小的0。同理當不發生的多是1,混亂程度也是0。當發生與不發生各佔一半時,這件事就越很差肯定,因此此時熵爲最大,其圖像以下圖所示。
計算熵的代碼以下
1 def calcShannonEnt(dataSet):#計算香農熵 2 numEntries = len(dataSet) 3 4 labelCounts = {} 5 for featVec in dataSet: 6 currentLabel = featVec[-1] #取得最後一列數據,計算該屬性取值狀況有多少個 7 if currentLabel not in labelCounts.keys(): 8 labelCounts[currentLabel] = 0 9 labelCounts[currentLabel]+=1 10 11 #計算熵 12 shannonEnt = 0.0 13 for key in labelCounts: 14 prob = float(labelCounts[key])/numEntries 15 shannonEnt -= prob*log(prob,2) 16 17 return shannonEnt
信息增益
隨機事件未按照某個屬劃的不一樣取值劃分時的熵減去按照某個屬性的不一樣取值劃分時的平均熵。即先後兩次熵的差值。
仍是對於打壘球的例子,未按照某個屬劃的不一樣取值劃分時的熵即H(活動)已算出未0.94。如今按照天氣屬性的不一樣取值來劃分,發現天氣屬性有3個不一樣取值分別爲晴,陰,雨。劃分好後以下圖所示。
天氣 |
溫度 |
溼度 |
風速 |
活動 |
晴 |
炎熱 |
高 |
弱 |
取消 |
晴 |
炎熱 |
高 |
強 |
取消 |
晴 |
適中 |
高 |
弱 |
取消 |
晴 |
寒冷 |
正常 |
弱 |
進行 |
晴 |
適中 |
正常 |
強 |
進行 |
陰 |
炎熱 |
高 |
弱 |
進行 |
陰 |
寒冷 |
正常 |
強 |
進行 |
陰 |
適中 |
高 |
強 |
進行 |
陰 |
炎熱 |
正常 |
弱 |
進行 |
雨 |
寒冷 |
正常 |
強 |
取消 |
雨 |
適中 |
高 |
強 |
取消 |
雨 |
適中 |
高 |
弱 |
進行 |
雨 |
寒冷 |
正常 |
弱 |
進行 |
雨 |
適中 |
正常 |
弱 |
進行 |
在天氣爲晴時有5種狀況,發現活動取消有3種,進行有2種,計算如今的條件熵
=0.971
同理天氣爲陰時有4種狀況,活動進行的有4種,則條件熵爲:
=0
同理天氣爲雨時有5種狀況,活動取消的有2種,進行的有3種,則條件熵爲:
=0.971
因爲按照天氣屬性不一樣取值劃分時,天氣爲晴佔整個狀況的5/14,天氣爲陰占整個狀況的4/14,天氣爲雨佔整個狀況的5/14,則按照天氣屬性不一樣取值劃分時的帶權平均值熵爲:算出的結果約爲0.693.
則此時的信息增益Gain(活動,天氣)= H(活動) - H(活動|天氣) = 0.94- 0.693 = 0.246
同理咱們能夠計算出按照溫度屬性不一樣取值劃分後的信息增益:
Gain(活動,溫度)= H(活動) - H(活動|溫度) = 0.94- 0.911 = 0.029
按照溼度屬性不一樣取值劃分後的信息增益:
Gain(活動,溼度)= H(活動) - H(活動|溼度) = 0.94- 0.789 = 0.151
按照風速屬性不一樣取值劃分後的信息增益:
Gain(活動,風速)= H(活動) - H(活動|風速) = 0.94- 0.892 = 0.048
對於信息增益的理解
信息增益就是兩個熵的差,當差值越大說明按照此劃分對於事件的混亂程度減小越有幫助。
計算各個屬性的信息增益,並選擇信息增益最大的屬性的代碼以下
1 #定義按照某個特徵進行劃分的函數splitDataSet 2 #輸入三個變量(待劃分的數據集,特徵,分類值) 3 #axis特徵值中0表明no surfacing,1表明flippers 4 #value分類值中0表明否,1表明是 5 def splitDataSet(dataSet,axis,value): 6 retDataSet = [] 7 for featVec in dataSet:#取大列表中的每一個小列表 8 if featVec[axis]==value: 9 reduceFeatVec=featVec[:axis] 10 reduceFeatVec.extend(featVec[axis+1:]) 11 retDataSet.append(reduceFeatVec) 12 13 return retDataSet #返回不含劃分特徵的子集 14 15 def chooseBestFeatureToSplit(dataSet): 16 numFeature = len(dataSet[0]) - 1 17 baseEntropy = calcShannonEnt(dataSet) 18 bestInforGain = 0 19 bestFeature = -1 20 21 for i in range(numFeature): 22 featList = [number[i] for number in dataSet]#獲得某個特徵下全部值(某列) 23 uniquelVals = set(featList) #set無重複的屬性特徵值,獲得全部無重複的屬性取值 24 25 #計算每一個屬性i的概論熵 26 newEntropy = 0 27 for value in uniquelVals: 28 subDataSet = splitDataSet(dataSet,i,value)#獲得i屬性下取i屬性爲value時的集合 29 prob = len(subDataSet)/float(len(dataSet))#每一個屬性取值爲value時所佔比重 30 newEntropy+= prob*calcShannonEnt(subDataSet) 31 inforGain = baseEntropy - newEntropy #當前屬性i的信息增益 32 33 if inforGain>bestInforGain: 34 bestInforGain = inforGain 35 bestFeature = i 36 37 return bestFeature#返回最大信息增益屬性下標
決策樹的構造就是要選擇當前信息增益最大的屬性來做爲當前決策樹的節點。所以咱們選擇天氣屬性來作爲決策樹根節點,這時天氣屬性有3取值可能:晴,陰,雨,咱們發現當天氣爲陰時,活動全爲進行所以這件事情就能夠肯定了,而天氣爲晴或雨時,活動中有進行的也有取消的,事件還沒法肯定,這時就須要在當前按照天氣屬性劃分下的剩下的屬性中遞歸再次計算活動熵和信息增益,選擇信息增益最大的屬性來做爲下一個節點,直到整個事件可以肯定下來。
例如當天氣爲晴時,獲得以下表所示的事件
天氣 |
溫度 |
溼度 |
風速 |
活動 |
晴 |
炎熱 |
高 |
弱 |
取消 |
晴 |
炎熱 |
高 |
強 |
取消 |
晴 |
適中 |
高 |
弱 |
取消 |
晴 |
寒冷 |
正常 |
弱 |
進行 |
晴 |
適中 |
正常 |
強 |
進行 |
咱們須要遞歸處理,繼續在溫度,溼度,風速這三個屬性中找到信息增益最大的屬性來作爲下一個節點。
首先繼續計算活動熵,此時有5個樣例,活動取消有3個,進行有2個,則活動熵爲:
=0.971
接着計算信息增益,在天氣爲晴的前提下,按照溫度屬性的不一樣取值分類後結果以下所示
天氣 |
溫度 |
溼度 |
風速 |
活動 |
晴 |
炎熱 |
高 |
弱 |
取消 |
晴 |
炎熱 |
高 |
強 |
取消 |
晴 |
適中 |
高 |
弱 |
取消 |
晴 |
寒冷 |
正常 |
弱 |
進行 |
晴 |
適中 |
正常 |
強 |
進行 |
發現溼度爲高時有3種狀況,活動取消有3種,進行有0種,則條件熵爲:
=0
溼度正常有2種狀況,活動取消0種,進行2中,則條件熵爲:
=0
因爲按照溼度屬性不一樣取值劃分時,溼度爲高佔總狀況的3/5,溼度正常佔總狀況的2/5,則按照溼度屬性不一樣取值劃分時的帶權平均值熵爲:,算出的結果約爲0。
因此此時在天氣爲晴的前提下,按照溼度屬性的不一樣取值劃分的信息增益爲:
Gain= H(活動|天氣=晴) - H(活動|天氣,溼度) = 0.971- 0=0.971
同理還需繼續計算在天氣爲晴的前提下,按照溫度,風速屬性的不一樣取值劃分的信息增益,找到信息增益最大的做爲決策樹的下一個節點。
遞歸構造決策樹的代碼以下
1 #遞歸建立樹,用於找出出現次數最多的分類名稱 2 def majorityCnt(classList): 3 classCount={} 4 for vote in classList:#統計當前劃分下每中狀況的個數 5 if vote not in classCount.keys(): 6 classCount[vote]=0 7 classCount[vote]+=1 8 sortedClassCount=sorted(classCount.items,key=operator.itemgetter(1),reversed=True)#reversed=True表示由大到小排序 9 #對字典裏的元素按照value值由大到小排序 10 print("****************") 11 print(sortedClassCount[0][0]) 12 return sortedClassCount[0][0] 13 14 15 def createTree(dataSet,labels): 16 classList=[example[-1] for example in dataSet]#建立數組存放全部標籤值,取dataSet裏最後一列(結果) 17 #類別相同,中止劃分 18 if classList.count(classList[-1])==len(classList):#判斷classList裏是否全是一類,count() 方法用於統計某個元素在列表中出現的次數 19 return classList[-1] #當全是一類時中止分割 20 #長度爲1,返回出現次數最多的類別 21 if len(classList[0])==1: #當沒有更多特徵時中止分割,即分到最後一個特徵也沒有把數據徹底分開,就返回多數的那個結果 22 return majorityCnt(classList) 23 #按照信息增益最高選取分類特徵屬性 24 bestFeat=chooseBestFeatureToSplit(dataSet)#返回分類的特徵序號,按照最大熵原則進行分類 25 bestFeatLable=labels[bestFeat] #該特徵的label, #存儲分類特徵的標籤 26 27 myTree={bestFeatLable:{}} #構建樹的字典 28 del(labels[bestFeat]) #從labels的list中刪除該label 29 30 featValues=[example[bestFeat] for example in dataSet] 31 uniqueVals=set(featValues) 32 for value in uniqueVals: 33 subLables=labels[:] #子集合 ,將labels賦給sublabels,此時的labels已經刪掉了用於分類的特徵的標籤 34 #構建數據的子集合,並進行遞歸 35 myTree[bestFeatLable][value]=createTree(splitDataSet(dataSet,bestFeat,value),subLables) 36 return myTree
最後獲得的決策樹以下圖所示
整個程序以下
1 from math import log 2 from operator import * 3 4 def storeTree(inputTree,filename): 5 import pickle 6 fw=open(filename,'wb') #pickle默認方式是二進制,須要制定'wb' 7 pickle.dump(inputTree,fw) 8 fw.close() 9 10 def grabTree(filename): 11 import pickle 12 fr=open(filename,'rb')#須要制定'rb',以byte形式讀取 13 return pickle.load(fr) 14 15 16 def createDataSet(): 17 ''' 18 dataSet=[[1,1,'yes'],[1,1,'yes'],[1,0,'no'],[0,1,'no'],[0,1,'no']] 19 labels = ['no surfacing','flippers'] 20 ''' 21 dataSet = [['sunny','hot','high','weak','no'], 22 ['sunny','hot','high','strong','no'], 23 ['overcast','hot','high','weak','yes'], 24 ['rain','mild','high','weak','yes'], 25 ['rain','cool','normal','weak','yes'], 26 ['rain','cool','normal','strong','no'], 27 ['overcast','cool','normal','strong','yes'], 28 ['sunny','mild','high','weak','no'], 29 ['sunny','cool','normal','weak','yes'], 30 ['rain','mild','normal','weak','yes'], 31 ['sunny','mild','normal','strong','yes'], 32 ['overcast','mild','high','strong','yes'], 33 ['overcast','hot','normal','weak','yes'], 34 ['rain','mild','high','strong','no']] 35 labels = ['outlook','temperature','humidity','wind'] 36 return dataSet,labels 37 38 def calcShannonEnt(dataSet):#計算香農熵 39 numEntries = len(dataSet) 40 41 labelCounts = {} 42 for featVec in dataSet: 43 currentLabel = featVec[-1] #取得最後一列數據,該屬性取值狀況有多少個 44 if currentLabel not in labelCounts.keys(): 45 labelCounts[currentLabel] = 0 46 labelCounts[currentLabel]+=1 47 48 #計算熵 49 shannonEnt = 0.0 50 for key in labelCounts: 51 prob = float(labelCounts[key])/numEntries 52 shannonEnt -= prob*log(prob,2) 53 54 return shannonEnt 55 56 #定義按照某個特徵進行劃分的函數splitDataSet 57 #輸入三個變量(待劃分的數據集,特徵,分類值) 58 #axis特徵值中0表明no surfacing,1表明flippers 59 #value分類值中0表明否,1表明是 60 def splitDataSet(dataSet,axis,value): 61 retDataSet = [] 62 for featVec in dataSet:#取大列表中的每一個小列表 63 if featVec[axis]==value: 64 reduceFeatVec=featVec[:axis] 65 reduceFeatVec.extend(featVec[axis+1:]) 66 retDataSet.append(reduceFeatVec) 67 68 return retDataSet #返回不含劃分特徵的子集 69 70 def chooseBestFeatureToSplit(dataSet): 71 numFeature = len(dataSet[0]) - 1 72 baseEntropy = calcShannonEnt(dataSet) 73 bestInforGain = 0 74 bestFeature = -1 75 76 for i in range(numFeature): 77 featList = [number[i] for number in dataSet]#獲得某個特徵下全部值(某列) 78 uniquelVals = set(featList) #set無重複的屬性特徵值,獲得全部無重複的屬性取值 79 80 #計算每一個屬性i的概論熵 81 newEntropy = 0 82 for value in uniquelVals: 83 subDataSet = splitDataSet(dataSet,i,value)#獲得i屬性下取i屬性爲value時的集合 84 prob = len(subDataSet)/float(len(dataSet))#每一個屬性取值爲value時所佔比重 85 newEntropy+= prob*calcShannonEnt(subDataSet) 86 inforGain = baseEntropy - newEntropy #當前屬性i的信息增益 87 88 if inforGain>bestInforGain: 89 bestInforGain = inforGain 90 bestFeature = i 91 92 return bestFeature#返回最大信息增益屬性下標 93 94 #遞歸建立樹,用於找出出現次數最多的分類名稱 95 def majorityCnt(classList): 96 classCount={} 97 for vote in classList:#統計當前劃分下每中狀況的個數 98 if vote not in classCount.keys(): 99 classCount[vote]=0 100 classCount[vote]+=1 101 sortedClassCount=sorted(classCount.items,key=operator.itemgetter(1),reversed=True)#reversed=True表示由大到小排序 102 #對字典裏的元素按照value值由大到小排序 103 104 return sortedClassCount[0][0] 105 106 107 def createTree(dataSet,labels): 108 classList=[example[-1] for example in dataSet]#建立數組存放全部標籤值,取dataSet裏最後一列(結果) 109 #類別相同,中止劃分 110 if classList.count(classList[-1])==len(classList):#判斷classList裏是否全是一類,count() 方法用於統計某個元素在列表中出現的次數 111 return classList[-1] #當全是一類時中止分割 112 #長度爲1,返回出現次數最多的類別 113 if len(classList[0])==1: #當沒有更多特徵時中止分割,即分到最後一個特徵也沒有把數據徹底分開,就返回多數的那個結果 114 return majorityCnt(classList) 115 #按照信息增益最高選取分類特徵屬性 116 bestFeat=chooseBestFeatureToSplit(dataSet)#返回分類的特徵序號,按照最大熵原則進行分類 117 bestFeatLable=labels[bestFeat] #該特徵的label, #存儲分類特徵的標籤 118 119 myTree={bestFeatLable:{}} #構建樹的字典 120 del(labels[bestFeat]) #從labels的list中刪除該label 121 122 featValues=[example[bestFeat] for example in dataSet] 123 uniqueVals=set(featValues) 124 for value in uniqueVals: 125 subLables=labels[:] #子集合 ,將labels賦給sublabels,此時的labels已經刪掉了用於分類的特徵的標籤 126 #構建數據的子集合,並進行遞歸 127 myTree[bestFeatLable][value]=createTree(splitDataSet(dataSet,bestFeat,value),subLables) 128 return myTree 129 130 131 if __name__=="__main__": 132 my_Data,labels = createDataSet() 133 134 #print(calcShannonEnt(my_Data)) 135 Mytree = createTree(my_Data,labels) 136 print(Mytree)