對於數據混亂程度的判定準則:基尼不純度、信息熵、方差

兩者都是對數據混雜程度的測度。

總結一句:對於標稱型數據我們通常用信息熵或者基尼不純度來判定混亂程度,對於數值型問題則用方差作爲判斷標                  準。

出處:http://blog.csdn.net/lingtianyulong/article/details/34522757

決策樹是一種簡單的機器學習方法。決策樹經過訓練之後,看起來像是以樹狀形式排列的一系列if-then語句。一旦我們有了決策樹,只要沿着樹的路徑一直向下,正確回答每一個問題,最終就會得到答案。沿着最終的葉節點向上回溯,就會得到一個有關最終分類結果的推理過程。

決策樹:

1
2
3
4
5
6
7
class decisionnode:
  def __init__(self,col=-1,value=None,results=None,tb=None,fb=None):
    self.col=col#待檢驗的判斷條件
    self.value=value#對應於爲了使結果爲true,當前列必須匹配的值
    self.results=results#針對當前分支的結果
    self.tb=tb#結果爲true時,樹上相對於當前節點的子樹上的節點
    self.fb=fb#結果爲false時,樹上相對於當前節點的子樹上的節點

 

下面利用分類迴歸樹的算法。爲了構造決策樹,算法首先創建一個根節點,然後評估表中的所有觀測變量,從中選出最合適的變量對數據進行拆分。爲了選擇合適的變量,我們需要一種方法來衡量數據集合中各種因素的混合情況。對於混雜程度的測度,有幾種度量方式可供選擇:

基尼不純度:將來自集合中的某種結果隨機應用於集合中某一數據項的預期誤差率。

維基上的公式是這樣:

下面是《集體智慧編程》中的python實現:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def uniquecounts(rows):
   results={}
   for rowin rows:
      # The result is the last column
      r=row[len(row)-1]
      if rnot in results: results[r]=0
      results[r]+=1
   return results
 
def giniimpurity(rows):
  total=len(rows)
  counts=uniquecounts(rows)
  imp=0
  for k1in counts:
    p1=float(counts[k1])/total
    #imp+=p1*p1
    for k2in counts:
      if k1==k2:continue
      p2=float(counts[k2])/total
      imp+=p1*p2
  return imp#1-imp

每一結果出現次數除以集合總行數來計算相應概率,然後把所有這些概率值的乘積累加起來。這樣得到某一行數據被隨機分配到錯誤結果的總概率。(顯然直接按照公式的算法(註釋中)效率更高。)這一概率越高,說明對數據的拆分越不理想。

 

熵:代表集合的無序程度。信息論熵的概念在吳軍的《數學之美》中有很好的解釋:

我們來看一個例子,馬上要舉行世界盃賽了。大家都很關心誰會是冠軍。假如我錯過了看世界盃,賽後我問一個知道比賽結果的觀 衆“哪支球隊是冠軍”? 他不願意直接告訴我, 而要讓我猜,並且我每猜一次,他要收一元錢才肯告訴我是否猜對了,那麼我需要付給他多少錢才能知道誰是冠軍呢? 我可以把球隊編上號,從 1 到 32, 然後提問: “冠軍的球隊在 1-16 號中嗎?” 假如他告訴我猜對了, 我會接着問: “冠軍在 1-8 號中嗎?” 假如他告訴我猜錯了, 我自然知道冠軍隊在 9-16 中。 這樣只需要五次, 我就能知道哪支球隊是冠軍。所以,誰是世界盃冠軍這條消息的信息量只值五塊錢。 當然,香農不是用錢,而是用 “比特”(bit)這個概念來度量信息量。 一個比特是一位二進制數,計算機中的一個字節是八個比特。在上面的例子中,這條消息的信息量是五比特。(如果有朝一日有六十四個隊進入決賽階段的比賽,那 麼“誰世界盃冠軍”的信息量就是六比特,因爲我們要多猜一次。) 讀者可能已經發現, 信息量的比特數和所有可能情況的對數函數 log 有關。 (log32=5, log64=6。) 有些讀者此時可能會發現我們實際上可能不需要猜五次就能猜出誰是冠軍,因爲象巴西、德國、意 大利這樣的球隊得冠軍的可能性比日本、美國、韓國等隊大的多。因此,我們第一次猜測時不需要把 32 個球隊等分成兩個組,而可以把少數幾個最可能的球隊分成一組,把其它隊分成另一組。然後我們猜冠軍球隊是否在那幾只熱門隊中。我們重複這樣的過程,根據奪 冠概率對剩下的候選球隊分組,直到找到冠軍隊。這樣,我們也許三次或四次就猜出結果。因此,當每個球隊奪冠的可能性(概率)不等時,“誰世界盃冠軍”的信 息量的信息量比五比特少。香農指出,它的準確信息量應該是 
= -(p1*log p1 + p2 * log p2 + ... +p32 *log p32), 其 中,p1,p2 , ...,p32 分別是這 32 個球隊奪冠的概率。香農把它稱爲“信息熵” (Entropy),一般用符號 H 表示,單位是比特。有興趣的讀者可以推算一下當 32 個球隊奪冠概率相同時,對應的信息熵等於五比特。有數學基礎的讀者還可以證明上面公式的值不可能大於五。對於任意一個隨機變量 X(比如得冠軍的球隊),它的熵定義如下:

《集》中的實現:

1
2
3
4
5
6
7
8
9
10
def entropy(rows):
   from mathimport log
   log2=lambda x:log(x)/log(2
   results=uniquecounts(rows)
   # Now calculate the entropy
   ent=0.0
   for rin results.keys():
      p=float(results[r])/len(rows)
      ent=ent-p*log2(p)
   return ent

 

熵和基尼不純度之間的主要區別在於,熵達到峯值的過程要相對慢一些。因此,熵對於混亂集合的判罰要更重一些。

我們的算法首先求出整個羣組的熵,然後嘗試利用每個屬性的可能取值對羣組進行拆分,並求出兩個新羣組的熵。算法會計算相應的信息增益。信息增益是指當前熵與兩個新羣組經加權平均後的熵之間的差值。算法會對每個屬性計算相應的信息增益,然後從中選出信息增益最大的屬性。通過計算每個新生節點的最佳拆分屬性,對分支的拆分過程和樹的構造過程會不斷持續下去。當拆分某個節點所得的信息增益不大於0的時候,對分支的拆分纔會停止:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def buildtree(rows,scoref=entropy):
  if len(rows)==0:return decisionnode()
  current_score=scoref(rows)
 
  # Set up some variables to track the best criteria
  best_gain=0.0
  best_criteria=None
  best_sets=None
   
  column_count=len(rows[0])-1
  for colin range(0,column_count):
    # Generate the list of different values in
    # this column
    column_values={}
    for rowin rows:
       column_values[row[col]]=1
    # Now try dividing the rows up for each value
    # in this column
    for valuein column_values.keys():
      (set1,set2)=divideset(rows,col,value)
       
      # Information gain
      p=float(len(set1))/len(rows)
      gain=current_score-p*scoref(set1)-(1-p)*scoref(set2)
      if gain>best_gainand len(set1)>0 and len(set2)>0:
        best_gain=gain
        best_criteria=(col,value)
        best_sets=(set1,set2)
  # Create the sub branches  
  if best_gain>0:
    trueBranch=buildtree(best_sets[0])
    falseBranch=buildtree(best_sets[1])
    return decisionnode(col=best_criteria[0],value=best_criteria[1],
                        tb=trueBranch,fb=falseBranch)
  else:
    return decisionnode(results=uniquecounts(rows))

函數首先接受一個由數據行構成的列表作爲參數。它遍歷了數據集中的每一列,針對各列查找每一種可能的取值,並將數據集拆分成兩個新的子集。通過將每個子集的熵乘以子集中所含數據項在元數據集中所佔的比重,函數求出了每一對新生子集的甲醛平均熵,並記錄下熵最低的那一對子集。如果由熵值最低的一對子集求得的加權平均熵比當前集合的當前集合的熵要大,則拆分結束了,針對各種可能結果的計數所得將會被保存起來。否則,算法會在新生成的子集繼續調用buildtree函數,並把調用所得的結果添加到樹上。我們把針對每個子集的調用結果,分別附加到節點的True分支和False分支上,最終整棵樹就這樣構造出來了。

我們可以把它打印出來:

1
2
3
4
5
6
7
8
9
10
11
12
13
def printtree(tree,indent=''):
   # Is this a leaf node?
   if tree.results!=None:
      print str(tree.results)
   else:
      # Print the criteria
      print str(tree.col)+':'+str(tree.value)+'? '
 
      # Print the branches
      print indent+'T->',
      printtree(tree.tb,indent+'  ')
      print indent+'F->',
      printtree(tree.fb,indent+'  ')

現在到我們使用決策樹的時候了。接受新的觀測數據作爲參數,然後根據決策樹對其分類:

1
2
3
4
5
6
7
8
9
10
11
12
13
def classify(observation,tree):
  if tree.results!=None:
    return tree.results
  else:
    v=observation[tree.col]
    branch=None
    if isinstance(v,int)or isinstance(v,float):
      if v>=tree.value: branch=tree.tb
      else: branch=tree.fb
    else:
      if v==tree.value: branch=tree.tb
      else: branch=tree.fb
    return classify(observation,branch)

該函數採用與printtree相同的方式對樹進行遍歷。每次調用後,函數會根據調用結果來判斷是否到達分支的末端。如果尚未到達末端,它會對觀測數據評估,以確認列數據是否與參考值匹配。如果匹配,則會在True分支調用classify,不匹配則在False分支調用classify。

上面方法訓練決策樹會有一個問題:

過度擬合:它可能會變得過於針對訓練數據,其熵值與真實情況相比可能會有所降低。剪枝的過程就是對具有相同父節點的一組節點進行檢查,判斷如果將其合併,熵的增加量是否會小於某個指定的閾值。如果確實如此,則這些葉節點會被合併成一個單一的節點,合併後的新節點包含了所有可能的結果值。這種做法有助於過度避免過度擬合的情況,使得決策樹做出的預測結果,不至於比從數據集中得到的實際結論還要特殊:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def prune(tree,mingain):
  # 如果分支不是葉節點,則對其進行剪枝操作
  if tree.tb.results==None:
    prune(tree.tb,mingain)
  if tree.fb.results==None:
    prune(tree.fb,mingain)
     
  # 如果兩個分支都是葉節點,則判斷它們是否需要合併
  if tree.tb.results!=None and tree.fb.results!=None:
    # 構造合併後的數據集
    tb,fb=[],[]
    for v,cin tree.tb.results.items():
      tb+=[[v]]*c
    for v,cin tree.fb.results.items():
      fb+=[[v]]*c
     
    # 檢查熵的減少情況
    delta=entropy(tb+fb)-(entropy(tb)+entropy(fb)/2)
 
    if delta<mingain:
      # 合併分支
      tree.tb,tree.fb=None,None
      tree.results=uniquecounts(tb+fb)

當我們在根節點調用上述函數時,算法將沿着樹的所有路徑向下遍歷到只包含葉節點的節點處。函數會將兩個葉節點中的結果值合起來形成一個新的列表,同時還會對熵進行測試。如果熵的變化小於mingain參數指定的值,則葉節點也可能成爲刪除對象,以及與其它節點的合併對象。

如果我們缺失了某些數據,而這些數據是確定分支走向所必需的,那麼我們可以選擇兩個分支都走。在一棵基本的決策樹中,所有節點都隱含有一個值爲1的權重,即觀測數據項是否屬於某個特定分類的概率具有百分之百的影響。而如果要走多個分支的話,那麼我們可以給每個分支賦以一個權重,其值等於所有位於該分支的其它數據行所佔的比重:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def mdclassify(observation,tree):
  if tree.results!=None:
    return tree.results
  else:
    v=observation[tree.col]
    if v==None:
      tr,fr=mdclassify(observation,tree.tb),mdclassify(observation,tree.fb)
      tcount=sum(tr.values())
      fcount=sum(fr.values())
      tw=float(tcount)/(tcount+fcount)
      fw=float(fcount)/(tcount+fcount)
      result={}
      for k,vin tr.items(): result[k]=v*tw
      for k,vin fr.items(): result[k]=v*fw     
      return result
    else:
      if isinstance(v,int)or isinstance(v,float):
        if v>=tree.value: branch=tree.tb
        else: branch=tree.fb
      else:
        if v==tree.value: branch=tree.tb
        else: branch=tree.fb
      return mdclassify(observation,branch)

mdclassify與classify相比,唯一的區別在於末尾處:如果發現有重要數據缺失,則每個分支的對應結果值都會被計算一遍,並且最終的結果值會乘以它們各自的權重。

對於數值型問題,我們可以使用方差作爲評價函數來取代熵或基尼不純度。偏低的方差代表數字彼此都非常接近,而偏高的方差則意味着數字分散得很開。這樣,選擇節點判斷條件的依據就變成了拆分後令數字較大者位於樹的一側,數字較小者位於樹的另一側。

所以對於標稱型數據我們通常用信息熵或者基尼不純度來判定混亂程度,對於數值型問題則用方差作爲判斷標準。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章