周志華《機器學習》課後習題解答系列(五):Ch4 - 決策樹

Snoopy_Yuan發表於2017-04-06

本章所涉及程式設計練習採用Python-sklearn的方式,環境搭建可參考 資料探勘入門:Python開發環境搭建(eclipse-pydev模式).

檢視相關答案和原始碼,歡迎訪問我的Github:PY131/Machine-Learning_ZhouZhihua.

本章概要

本章講述決策樹(decision tree),相關內容包括:

  • 決策樹生成(construction)

子決策(sub-decision)、遞迴生成演算法(basic algorithm)、最優劃分屬性、純度(purity)、資訊熵(information entropy)、資訊增益(information gain)、ID3 、增益率(gain ratio)、C4.5 、基尼指數(gini index)、CART

  • 剪枝(pruning)

過擬合、泛化能力、預剪枝(prepruning)(自上而下)、決策樹樁(decision stump)、欠擬合、後剪枝(postpruning)(自下而上)、完全樹。

  • 連續屬性、缺失值(continuous variables, missing values)

連續屬性離散化、二分法(bi-partition)、值缺失時屬性劃分、缺值樣本劃分、權重、加權資訊增益;

  • 多變數決策樹(multivariate decision tree)

斜劃分、斜決策樹(oblique decision tree)、非葉節點-線性分類器;

此外還提及了C4.5Rule、OC1、感知機樹等擴充方法,以及增量學習演算法ID4、ID5R、ITI等;

決策樹的優劣總結

根據sklearn官網 - 1.10.Decision Trees總結如下:

  1. 優勢(Advantages):

    • 易理解,解釋性好,易視覺化;
    • 資料預處理少;
    • 複雜度O(logN);
    • 支援標稱變數+連續變數;
    • 支援多輸出;
    • 白盒模型,布林邏輯;
    • 模型好壞易驗證;
    • 容忍先驗知識錯;
  2. 劣勢(Disadvantages):

    • 決策樹生成易太大、過擬合;(需要剪枝、設定樹最大深度等後續操作。)
    • 模型生成不穩定,易受小錯誤樣本影響;
    • 學習最優模型是N-P難題,貪心搜尋易陷入區域性最優;(可採用隨機初始化生成多個模型。)
    • 不支援非線性邏輯,如XOR
    • 資料不平衡時生成的樹形差;

課後練習

4.1 衝突資料影響決策樹

這裡寫圖片描述

考慮決策樹的生成(書p74圖4.2),演算法生成葉節點,並遞迴返回條件有:

  • 當前節點的所有樣本屬於同一類,葉節點類標籤 -> 當前類;
  • 當前節點的所有樣本在屬性上取值相同,葉節點類標籤 -> 樣本中最多類;

由此可見,若兩訓練資料樣本特徵向量相同,那麼它們會到達決策樹的同一葉節點(只代表某一類),若二者資料標籤不同(衝突資料),則會出現訓練誤差,決策樹與訓練集不一致。

如果沒有衝突資料,到達某節點的樣本會出現以下兩種情況:

  • 樣本間特徵向量相同且屬於同一類,滿足遞迴結束條件,該節點為葉節點,類標籤正確(無訓練誤差);
  • 樣本間特徵向量不同時,遞迴結束條件不滿足,資料會根據屬性繼續劃分,直到上一條情況出現。

綜上得證,當資料集不含衝突資料時,必存在與訓練集一致(訓練誤差為0)的決策樹。


4.2 決策樹劃分選擇準則

這裡寫圖片描述

由於訓練集和真實集往往存在差異,若採用訓練誤差作為度量,模型常會出現過擬合,導致泛化能力差。


4.3 程式設計實現ID3演算法

這裡寫圖片描述

即ID3演算法,這裡我們基於Python獨立程式設計實現。詳細過程見:

周志華《機器學習》課後習題解答系列(五):Ch4.3 - 程式設計實現ID3演算法


4.4 程式設計實現CART演算法與剪枝操作

這裡寫圖片描述

即CART演算法,這裡我們基於Python獨立程式設計實現。詳細過程見:

周志華《機器學習》課後習題解答系列(五):Ch4.4 - 程式設計實現CART演算法與剪枝操作


4.5 基於對率迴歸進行劃分選擇

這裡寫圖片描述

這裡提一下我的思路:
參考書p90-91的多變數決策樹模型,這裡我們將每個非葉節點作為一個對率迴歸分類器,輸出為”是”、”否”兩類,形成形如二叉樹的決策樹。


4.6 各種決策樹演算法的比較

這裡寫圖片描述

簡要的分析一下:

  • ID3演算法基於資訊熵增益,CART演算法則採用了基尼係數。兩種劃分屬性選擇均是基於資料純度的角度,方法差距應該不大(CART可能要好一點)。而對率迴歸進行劃分選擇,以斜劃分的方式,實現了多變數參與劃分,其模型決策邊界更光滑。
  • 相比於決策樹的生成演算法,剪枝操作更影響模型效能。

4.7 非遞迴決策樹生成演算法 - DFS

這裡寫圖片描述

下面主要是本題的一種視角:

首先做一些分析:

  • 從資料結構演算法的角度來看,生成一棵樹常用遞迴迭代兩種模式。
  • 採用遞迴時,由於在遞迴時要儲存程式入口出口指標和大量臨時變數等,會涉及到不斷的壓棧與出棧,當遞迴層次加深,壓棧多於出棧,記憶體消耗擴大。
  • 這裡要採用佇列資料結構來生成決策樹,雖然避免了遞迴操作產生的記憶體消耗,但需要更大的額外儲存空間。
  • 用MaxDepth來控制樹的深度,即深度優先(Depth Fisrt)的形式,一般來說,使用遞迴實現相對容易,當然也可以用非遞迴來實現。

下面設計出基於佇列+深度控制的決策樹非遞迴生成演算法:

----
輸入: 訓練集 D = {(x1,y1),(x2,y2),...,(xm,ym)}.
      屬性集 A = {a1, a2,...,ad}.

過程: 函式 TreeGenerate(D,A):
1. 生成根節點 root;
2. 初始化深度 depth = 0;
3. 生成棧 stack (為儲存頂節點root及其對應的資料D和深度depth);
4. 
5. while D != Φ OR stack不為空:
6.     if D != Φ, then
7.         if D中樣本全屬於同一類別C, then
8.             root標記為C類葉節點, D = Φ, continue;
9.         end if
10.        if depth == MaxDepth OR D中樣本在屬性A上取值相同, then
11.             root標記為D取值中最多類的葉節點, D = Φ, continue;
12.        end if
13.        從A中選擇最優劃分屬性a*, 令Dv表示D中在a*上取值為a*v的樣本子集;
14.        生成子節點 child, 為root建立分支指向child;
15.        將[root, D\{Dv}, A, depth]壓入棧stack;
16.        令 root = child, D = Dv, A = A\{a*}, depth = depth+1;
17.    else
18.        從stack中彈出[root, D, A, depth];
19.    end if

輸出: 樹的根節點root.(即以root為根節點的樹) 
----

實際上,這裡的演算法實用的是棧而非完全意義上的佇列。

個人認為,從資料結構的角度來看,棧和佇列的最大區別在於FILO和FIFO,即存取元素時索引的區別,並不存在太大的儲存實現區別。進一步說明,對於很多程式環境,如C++,Java等,均可以基於佇列(Queue)構造棧(Stack)結構,由此構建的棧資料結構和佇列底資料結構層實現相同。

題幹中所說的棧“溢位”,主要應該是指遞迴時程式資訊壓棧所導致,相比於非遞迴的演算法,其壓棧資料量大得多。

故而此處的演算法實現直接採用棧實現。

關於本題的另一種視角是:

對於深度優先搜尋,採用佇列儲存每層當前節點的兄弟節點與父節點,這樣佇列的消耗相較於上面的一種方法要大一些(如當前節點的兄弟節點,父節點及其兄弟節點,祖父節點及其兄弟節點…)。


4.8 非遞迴決策樹生成演算法 - BFS

這裡寫圖片描述

本題實際上是BFS與DFS的比較:

  • 對於深度優先搜尋,每深入一層需要儲存上一層節點的資訊以方便回溯遍歷(其儲存的是一條路徑);
  • 對於廣度優先搜尋,每深入一層需要儲存當前層兄弟節點資訊以實現遍歷(其儲存的是每層資訊,儲存量會大一些);

兩種方法各自有防止佇列過大化的閾值(即MaxDepth和MaxNode),所以兩種方法均可將記憶體消耗控制在一定範圍之內。

當資料屬性相對較多,屬性不同取值相對較少時,樹會比較寬,此時深度優先所需記憶體較小,反之寬度優先較小。


相關文章