1. 程式人生 > >決策樹模型

決策樹模型

1.概述

決策樹是一種簡單的機器學習方法,它是對被觀測資料進行分類的一種相當直觀的方法。

  • 優點:計算複雜度不高,輸出結果易於理解,對中間值的缺失不敏感,可以處理不相關特徵資料。
  • 缺點:可能會產生過度匹配問題。
  • 適用資料型別:數值型和標稱型。

2.決策樹的構造

決策樹學習的關鍵是如何選擇最優劃分屬性。一般而言,隨著劃分過程的不斷進行,我們希望決策樹的分支節點所包含的樣本儘可能屬於同一類別,即結點的“純度”越來越高。

資訊增益:在劃分資料集之前之後資訊發生的變化稱為資訊增益。

知道如何計算資訊增益,我們就可以計算每個特徵值劃分資料集獲得的資訊增益,獲得資訊增益最高的特徵就是最好的選擇。

實際上,資訊增益準則對可取值數目較多的屬性有所偏好,為減少這種偏好可能帶來的不利影響,註明的C4.5決策樹演算法不直接使用資訊增益,而是使用“增益率”來選擇最優劃分屬性。

程式碼:

class decisionnode:
  def __init__(self,col=-1,value=None,results=None,tb=None,fb=None):
    self.col=col
    self.value=value
    self.results=results
    self.tb=tb
    self.fb=fb
# Divides a set on a specific column. Can handle numeric
# or nominal values
def divideset(rows,column,value):
   # Make a function that tells us if a row is in 
   # the first group (true) or the second group (false)
   split_function=None
   if isinstance(value,int) or isinstance(value,float):
      split_function=lambda row:row[column]>=value
   else:
      split_function=lambda row:row[column]==value
   
   # Divide the rows into two sets and return them
   set1=[row for row in rows if split_function(row)]
   set2=[row for row in rows if not split_function(row)]
   return (set1,set2)

# Create counts of possible results (the last column of 
# each row is the result)
def uniquecounts(rows):
   results={}
   for row in rows:
      # The result is the last column
      r=row[len(row)-1]
      if r not in results: results[r]=0
      results[r]+=1
   return results

2.1基尼不純度(Gini Impurity)

定義:指將來自集合中的某種結果隨機應用於集合中某一資料項的預期誤差率。

公式:Gini(D)=\sum_{k = 1}^{\left | \gamma \right |} \sum_{{k}' \neq k} p_{k}{p}'_{k}

程式碼:

# Probability that a randomly placed item will
# be in the wrong category
def giniimpurity(rows):
  total=len(rows)
  counts=uniquecounts(rows)
  imp=0
  for k1 in counts:
    p1=float(counts[k1])/total
    for k2 in counts:
      if k1==k2: continue
      p2=float(counts[k2])/total
      imp+=p1*p2
  return imp

該函式利用集合中每一項結果出現的次數除以集合的總行數來計算相應的概率,然後將所有這些概率值的乘積累加起來。這樣就會得到某一行資料被隨機分配到錯誤結果的概率,這一概率的值越高,就說明對資料的拆分越不理想。

2.2熵

公式:

Ent(D)= -\sum^{\left| \gamma \right|}_{k=1}p_{k}log_{2}p_{k}

p(i) = frequency(outcome) = count(outcome) / count(total rows)

程式碼:

# Entropy is the sum of p(x)log(p(x)) across all 
# the different possible results
def entropy(rows):
   from math import log
   log2=lambda x:log(x)/log(2)  
   results=uniquecounts(rows)
   # Now calculate the entropy
   ent=0.0
   for r in results.keys():
      p=float(results[r])/len(rows)
      ent=ent-p*log2(p)
   return ent

3.遞迴建樹

首先求出整個群組的熵,然後嘗試利用每個屬性的可能取值對群組進行拆分,並求出兩個新群組的熵,然後計算針對每個屬性計算相應的資訊增益,然後從中選出資訊增益最大的屬性。通過計算每個新生節點的最佳拆分屬性,對分支的拆分過程和樹的構造過程會不斷地持續下去。當拆分某個節點所得的資訊增益不大於0時,對分支的拆分才停止。

在決策樹基本演算法中,有三種情形會導致遞迴返回:(1)當前結點包含的樣本全屬於同一類別,無需劃分(2)當前屬性集為空,或是所有樣本在所有屬性上取值相同,無法劃分;(3)當前結點包含的樣本集合為空,不能劃分。

資訊增益公式:Gain(D,\alpha ) = Ent(D) - \sum _{v-1}^{V}\frac{\left | D^{\gamma} \right |}{D}Ent(D^{\gamma})

程式碼:

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 col in range(0,column_count):
    # Generate the list of different values in
    # this column
    column_values={}
    for row in rows:
       column_values[row[col]]=1
    # Now try dividing the rows up for each value
    # in this column
    for value in 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_gain and 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))

4.對新的觀測資料進行分類

每次呼叫之後,函式會根據呼叫結果來判斷是否到達分支的末端。如果尚未到達末端,它會對觀測資料做出評估,以確認列資料是否與參考值相匹配。如果匹配,則會再次在True分支上呼叫classify,如果不匹配,則會在False分支上呼叫classify。

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)

5.決策樹的剪枝

剪枝手段可以解決過度擬合問題,決策樹剪枝的主要策略有“預剪枝”和“後剪枝”。

5.1預剪枝

預剪枝熬對劃分前後的泛化效能進行估計,若劃分後驗證集的精度提高,則進行劃分,否則禁止劃分。

決策樹樁:僅有一層劃分的決策樹。

預剪枝使得決策樹的很多分支都沒有“展開”,這不僅降低了過擬合的風險,還顯著減少了決策樹的訓練時間開銷和測試時間開銷。但另一方面,有些分支的當前劃分雖不能提升泛化效能、甚至可能導致泛化效能暫時下降,但在其基礎上進行的後續劃分卻有可能導致效能顯著提高;預剪枝基於“貪心”本質禁止這些分支展開,給預剪枝決策樹帶來了欠擬合的風險。

5.2後剪枝

後剪枝先從訓練集生成一棵完整決策樹,然後自下往上遍歷樹,對於任一根節點,如果合併分支能提高驗證集的精度,則合併。

後剪枝決策樹通常比預剪枝決策樹保留了更多的分支。一般情形下,後剪枝決策樹的欠擬合風險很小,泛化效能往往優於預剪枝決策樹。但後剪枝過程是在生成完全決策樹之後進行的,並且要自底向上地對樹中的所有非葉節點進行逐一考察,因此其訓練時間開銷比未剪枝決策樹和預剪枝決策樹都要大得多。

程式碼:

def prune(tree,mingain):
  # If the branches aren't leaves, then prune them
  if tree.tb.results==None:
    prune(tree.tb,mingain)
  if tree.fb.results==None:
    prune(tree.fb,mingain)
    
  # If both the subbranches are now leaves, see if they
  # should merged
  if tree.tb.results!=None and tree.fb.results!=None:
    # Build a combined dataset
    tb,fb=[],[]
    for v,c in tree.tb.results.items():
      tb+=[[v]]*c
    for v,c in tree.fb.results.items():
      fb+=[[v]]*c
    
    # Test the reduction in entropy
    delta=entropy(tb+fb)-(entropy(tb)+entropy(fb)/2)

    if delta<mingain:
      # Merge the branches
      tree.tb,tree.fb=None,None
      tree.results=uniquecounts(tb+fb)