1. 決策樹的基本概念
我們這里介紹一下一個比較簡單的機器學習系統----決策樹. 它的概念最容易理解, 因為人類的許多決策實際上就是一個決策樹.
通常使用的分類回歸樹(class and regress tree)是一個二叉樹。它的形式一般為:
每個方框代表一個節點. 每個非葉子節點有2個分支, 一個是判定True, 一個判定False. 分別走兩個不同的分支. 葉子節點具有決策權. 任何一個輸入從root出發, 總是會達到且唯一到達一個葉子節點. 這就是決策樹的工作原理.
決策樹有兩種節點: 中間節點和葉子節點。
-
每個中間節點有4個參數:
a) 判定函數。 是一個特征的取值。 當特征小於等於這個值得時候決策路徑走左邊, 當特征大於這個值得時候決策樹走右邊。
b) 不純度值(impurity value). 是當前節點的不純度值. 關於不純度值得意義后面會講到. 他反應了當前節點的預測能力.
c) 覆蓋樣本個數(n_samples). 是指參與此節點決策的樣本個數. 父親節點(p)和兩個孩子節點(l,r)的樣本個數的關系為: n_samples(p) = n_samples(l) + n_samples(r) 覆蓋樣本個數越多, 說明判定函數越穩定. 實際上很容易看出來, 所有的葉子節點所對應的樣本是總樣本的一個划分.
d) 節點取值(node value). 節點取值是一個數組. 數組的長度為類目個數. value = [997, 1154] 表示在2151個樣本數中, 有997個屬於class1, 1154屬於class2. (這是分類樹的意義, 會歸數的取值規則后面會講.) -
每個葉子節點有3個參數. 除了沒有決策函數之外, 其他參數意義一樣.
2. 不純度函數(impurity function)
決策樹最重要的概念就是不純函數(I)的概念. 當一個節點需要分割的時候, 實際上就是找到一個合適的特征的一個合適的取值作為閾值(thresh)進行分割. 那么問題來了, 怎么找到那個合適的特征的合適的取值呢? 主要的依據就是不純度的變化(delta I). 首先我們給出不純度函數的定義. 不純度函數不是一個具體的函數, 它是滿足一系列約束的函數的總稱.
根據輸出實例的取值范圍不同. 決策樹有不同的種類. 如果輸出實例是離散的, 那么決策樹是一個分類樹; 如果輸出實例是連續的, 那么決策樹是一個回歸樹.如果決策樹是分類樹. 那么輸出空間定義為輸出實例所有取值的集合. 這個集合是有限集合. 不失一般性, 使用{1,...,k}這可個取值. 不純度函數(I)的定義為:
每一項其實就是屬於類目c_i的概率, 記為p_i.
如上公式可以看出不純度函數的定義域是長度為k的向量, 向量每個數的取值為0~1, 且加和為1. 第i個數是特征矩陣中屬於類別i的特征向量個數在整個樣本個數(n_sample)的占比.且必須滿足如下約束:
-
當所有樣本都屬於同一類時候I取最小值. 即I在點(1,0,...,0),(0,1,...,0),...,(0,..,0,1)(1,0,...,0),(0,1,...,0), ..., (0,..,0,1)(1,0,...,0),(0,1,...,0),...,(0,..,0,1)取最小值.
-
當樣本中每個類目下樣本個數相同時I取最大值. 即I在點(1/k,..,1/k)取最大值.
-
I對於定義域中每個取值p_1,...,p_k是對稱的. 即I(p_1, p_2,...,p_k) = I(p_2,p_1,..,p_k)等. 從函數圖像的角度理解, 就是函數一定是關於值坐標軸對稱.
-
I必須是絕度凸函數(strickly concave)即設p和p'(注意這里是一個長度為k的向量)為定義域下兩個可能的取值. 那么
另外一個概念是不純度變化(impurity reduction).
我們回到決策樹的結構, 每個父節點的兩個子節點都是對父節點的划分. 那么如何評價這次划分呢? 於是我們引入了不純度變化(impurity reduction)的概念. 我們看着公式來理解:
考慮一般性我們設X_1,X_2...X_s是特征空間X的一個划分. 不純度變化定義為:
這個公式很好理解,令s=2, 那么 X_1,X_2是對於原空間的一個划分. 按理說, 一個好的划分應該讓不純度變低, 以便讓類目歸屬更加清晰. 公式后面一個累加和實際上就是這兩個划分的不純度的期望. 原不純度減去划分后的不純度的期望就是減少的不純度的差值. 顯然這個差值越大說明划分讓子節點的純度更"純".
另外, 需要強調一點, 根據不純度函數的第三個約束, 即不純度函數是定義域下的凸函數, 可以保證不純度變化(impurity reduction) 大於等於0.當且僅當所有划分的樣本數相當取0.
可這么說, 分類決策樹節點划分的依據是找到一個特征的一個取值, 根據這個划分是的不純度縮減量最大.
下面我們介紹兩個常用不純度函數, 信息熵(info entropy)和基尼指數(gini index).
2.1 信息熵(info entropy)
首先介紹一下信息熵的概念. 我們把樣本抽取過程當做一次隨機試驗A, 那么A有k個可能的輸出A_1,A_2,...,A_k . 對應於k個分類. 那么A的信息熵定義為:
信息熵滿足不純度函數的定義. 所以我們定義:
把這個不純度函數的定義帶入到不純度函數的公式中就可以得到相應的不純度變化函數.
這個函數很出名, 我們把它寫出來(后面的基尼指數就不寫了.)
按照教課書的習慣, 當不純度函數為信息熵時不純度變化又叫做信息增益(info gain).
2.2 基尼指數(Gini Index)
這里強調一下不管是信息熵還是基尼指數, 他們都是不純函數的一種表達, 不純度變化的計算沒有任何變化. 我們也可以自己撰寫不純度函數. 當k=2時候, 我們可以吧信息熵和基尼指數在二維坐標上圖形畫出來.
可以看出來這兩個方法的區別很小. 實際過程中使用哪個方法作為不純度函數效果不會有太大變化.
我們現在了解了分類決策樹在每次分割過程中如何評價分割的好壞, 一遍每次找到最佳分割點. 下面我們繼續介紹回歸數是如何定義不純度函數的.
3. 回歸樹
我們考慮一下會歸樹的構建過程. 當決策樹只有一個節點的時候(root節點), 這個節點包含所有待測試樣本. 如果我們需要選擇一個數來預測函數的取值, 我們會選哪個? 由於我們沒有任何多余的信息, 根據最大似然原則, 我們取所有樣本取值的均值, 作為預測值.
同樣的道理隨着決策樹的建立, 所有的葉子節點是root節點的一個划分. 每個葉子節點都充當一個預測器的角色. 每個葉子節點的預測值是每個節點包含的樣本取值的均值.
那么對於回歸決策樹來說, 我們的任務是如何有效的划分讓每個葉子節點更具有代表性. 如果讓一個葉子節點更具有代表性, 我們直觀的感受是這個節點的樣本都趨同於均值(期望), 實際上就是方差較小. 這正是回歸決策樹不純度函數的定義的理論依據.
對於給定的訓練集:
Y是連續變量. 考慮如何生成回歸數.考慮一顆分類樹, 每個葉子節點都對應一組概率值, 表示達到這個葉子節點的樣本分到每個類目下的概率. 回歸數基本結構和分類數一樣, 不同的是每個葉子節點都表示對於到達這個葉子節點的樣本的預測值(f(x)). f(x)等於這個葉子節點所有輸入實例x_i對應的輸出y_i的均值.有的葉子節點是對樣本的一個划分R_1, R_2, ..., R_m
舉個例子:
這顆回歸樹是對拋物線y=1-x^2 ( -1 <x< 1)模擬, 我們把原函數和預測函數花在同一個坐標系統.
我看觀察圖形可以看出, 回歸樹的特點是使用連續的折線來擬合曲線. 因為在每個葉子節點的取值是固定的, 而這顆回歸樹只有8個葉子節點.
回歸樹的構建和分類數構建方法類似. 同樣是需要找個一個划分, 使得不純函數的縮減最大. 但是上面所介紹的不純度函數是根據分類樹定義的. 我們需要定義適應回歸樹的不純度函數.
在回歸數中每個非葉子節點都是樣本空間的一個子集的處理單元. 某個子集為X_m每個樣本x有2個屬性, 一個是實際輸出y, 另一個是預測輸出hat(y). 上面介紹過hat(y)是通過這個節點下所有y的均值算出來的, hat(y)是這個節點下所有y的期望. 反應的是總體上的擬合程度. 那么另外一個衡量集合穩定性的指標是方差, 它衡量hat(y)的代表性. 方差越小, 說明hat(y)越具有代表性. 因此我們可以定義一種不純度函數, 就是這個節點下輸出值的方差.
同樣的帶入不純度變化的公式, 經過必要的化簡我們得到:
值得注意的是, 公式中有2處對於當前節點來說是常數. 只有最小二乘有實際意義. 我們最大化不純度變化, 就是最小化最小二乘函數.
一般的教科書直接寫成這樣的公式:
這種方法又叫做"最小二乘法".
在本文中我們給出一個符合不純度函數定義的解釋. 這樣保證回歸樹和決策樹在實現上是統一的.
4. 小結
上面介紹了決策樹一個重要概念, 不純函數. 不純函數不僅是分類樹的概念, 也是回歸樹的概念. 它們在形式上是統一的. 這點很有意義, 下面我們分析sklearn源碼時候可以發現, 在設計決策樹時候沒有區分分類和回歸樹, 他們的不同都被封裝在不純函數的定義和計算節點的輸出中了.
5. 決策樹的構建
上面一章介紹了分類和回歸數在某個節點如何選擇合適的特征和特征取值進行分裂. 這一章介紹決策樹的構建方法, 即如何決定每步應該分裂的節點. 決策樹的構建一般有2種方法
-
深度優先
-
廣度優先
名稱 圖像 說明 廣度優先 廣度優先按照層次來構建樹的. 首先根據合適的不純度函數來分裂root成2個節點; 然后依次分裂第二層的2個節點; 依次遞推. 深度優先 深度優先采用遞歸的思想, 在每個節點都是優先有左邊的子樹建好, 再建右邊子樹
不管是深度優先還是廣度優先, 決策樹算法都需要知道什么時候一個節點應該作為葉子節點. 如果沒有約束決策樹會一直建設下去知道節點只有一個類目(或者只有一個取值)才停止. 這樣會導致決策樹變得非常龐大, 引發過擬合問題.
一般來說, 當如下條件其中之一滿足的時候, 當前節點停止構建, 作為決策樹的葉子節點.
參數 | 意義 | 終止條件 |
---|---|---|
min_samples_split | 當前節點允許分裂的最小樣本數 | 當前節點樣本數小於這個值時候 |
min_samples_leaf | 葉子節點最少樣本數 | 任何分裂不能導致子節點的樣本數小於此值, 否則禁止分裂 |
min_impurity_split | 分裂的不純度閾值 | 當前節點不純度小於此值時不分裂 |
max_path | 數的最大深度 | 當前節點的深度大於等於此值是不分裂 |
這些控制條件可以控制數的規模, 因為根據不純度變化的定義, 將所有的節點都放在唯一的葉子節點(這樣每個葉子節點不純度都是0)中不純度變化最大. 這顯然不是我們想要的.
6. 決策樹的預測
通過上述的說明, 決策樹的預測就非常的簡單了. 決策樹中只有葉子節點有預測功能. 一個測試樣本從root出發選擇正確的路徑, 一定會走到一個葉子節點. 葉子節點的值即為決策樹對這個>樣本的預測.
-
如果是分類樹. 葉子節點給出每個類別的占比. 概率最大的類別作為這個葉子節點的預測值. 其概率值為置信度.
-
如果是回歸樹. 葉子節點的所有樣本的輸出的平均值作為這個測試樣本的預測值.
7. sckit-learn 決策樹源碼分析
本文的結構實際上就是按照sckit-learn中的決策樹進行設計的. 源代碼在: https://github.com/scikit-learn/scikit-learn/tree/master/sklearn/tree .
首先介紹一下主要文件:
criterion.py*
criterion.py* 主要定義了各種不純度函數及其計算和轉化方法.
主要類 | 功能描述 |
---|---|
abstract Criterion | 定義一系列計算某個不純度或者不純度縮減的方法 1. node_impurity: 計算當前節點的不純度 2. children_impurity: 計算2個子節點的不純度 3. impurity_improvement: 計算當前節點的不純度縮減量 4. proxy_impurity_improvement: 由於當前節點的不純度是一個常數, 因此在比較不純度縮減量時候有更加簡單的方法, 這個方法在分裂過程中最常用, impurity_improvement僅當需要計算不純度縮減絕對值時才用 5. node_value: 當前節點的值, 用於預測. 當決策樹是分類樹時它計算每個類目的概率, 當決策樹是回歸數是, 計算所有樣本輸出的均值, 作為輸出值 |
Class ClassificationCriterion | 繼承 Criterion; 重寫node_value方法, 適應分類樹的計算 |
Class Entropy | 繼承ClassificationCriterion; 重寫impurity各個方法, 符合entropy的定義 |
Class Gini | 繼承ClassificationCriterion; |
Class RegressionCriterion | 繼承 Criterion; 重寫node_value方法, 適應回歸樹的計算 |
class MSE | 繼承 RegressionCriterion 主要實現上文提到回歸數的不純度計算方法 |
Splitter.py*
splitter.py* 主要解決一些工程性的問題. 根據不純度算法的要求, 我們要在每個節點遍歷所有特征的所有取值. 在性能角度需要考慮:
-
如果某個特征取值是連續的, 是否一定需要遍歷所有取值? 如果相鄰的取值差距很小是否可以直接跳過?
-
有沒有一些折中的方案是速度和質量的折中? 比如一些隨機算法?
主要類 | 功能描述 |
---|---|
Abstract Splitter | 抽象類, 定義分割需要的基本方法 |
class BestSplitter | 繼承Splitter 最優質的分割方法, 每次分割幾乎遍歷所有所有特征的所有取值, 但是為了加速, 有一個叫step的參數, 如果相鄰的兩個取值間隔小於step就直接忽略 |
class RandomSplitter | 繼承Splitter 采用隨機算法沒有遍歷特征的所有值, 一般都是使用BestSplitter |
_tree.py*
這個文件是講所有的功能拼裝在一起形成一個可以獨立使用決策樹類. 首先它聲明了一個TreeBuilder的系列類, 用於實現前文所說的深度優先建樹和廣度優先建樹.
tree.py
這是源代碼中為唯一一個python文件(其他都是cython文件). 他主要封裝了_tree.py* 定義和主流機器學習庫兼容的方法體系.
8. 實例
本章, 我們給出一個完整的實例.這個例子很簡單, 但是我們還是可以發現參數調整對於決策樹質量的重要性.
這個實例很簡單. 首先需要使用DecisionTreeRegressor來聲明一個回歸樹. 然后使用fit來訓練, 最后使用predict來預測結果.
圖像中我們看到有點欠擬合, 我們加大max_depth=3和max_depth=5
我們看出來max_depth=5就已經有點過擬合. 但是我們對max_depth=3還有有點不滿意.
我們進一步改進, 維持max_depth=5不變, 增加min_samples_leaf=5的參數. 效果進一步提升.
可以看到參數對於決策樹的質量來說非常重要.
同時我們注意到在0附近不管設置什么參數預測效果都很差. 這是由回歸樹的性質決定的. 回歸決策樹對於稀疏的數據的預測效果是非常差的. 我們從不純度函數的定義就可以理解. 不純度函數是一個葉子節點的方差, 要求方差盡可能小, 這樣越是比較趨同的樣本越容易被分到同一個節點內, 導致本來就稀疏的節點更加稀疏, 稀疏點附近的均值根本就沒有代表性.
9. 決策樹的深度思考
為什么決策樹不需要數據歸一化?
一般來說, 機器學習都需要特征歸一化, 目的是讓特征之間的比較可以在同一個量綱上進行. 但是從數據構建過程來看, 不純函數的計算和比較都是單特征的. 所有決策樹不需要數據的歸一>化. 但是有點需要注意, 從上文中對splitter的源碼分析中可以看出, 決策樹為了加速遍歷沒有真正遍歷所有取值, 當特征的絕對值太小的時候會導致相鄰值的間隔小於step, 因此盡量讓特>征值不要太小(大於0.01比較保險).
如何正確的選擇不純度函數?
在gini函數小節中我們比較了gini和entroy在兩元下的圖形. 可以看到基本上沒有區別. 有研究表明不同的不純度函數對決策樹產生的影響在2%以內[1].
事實上很少有實際案例說明選擇不純度函數有顯著的作用. 所以對於分類決策樹選擇gini, 對於回歸選擇MSE, 這樣的默認配置可以滿足絕大多數的需求.
決策樹哪些參數最重要?
決策樹的重要參數都是防止過擬合的. 我認為2個參數一定要設置:
-
min_samples_leaf 這個sklearn的默認值是1. 經驗上必須大於100, 如果一個節點都沒有100個樣本支持他的決策, 一般都被認為是過擬合.
-
max_path 這個參數控制樹的規模. 決策樹是一個非常直觀的機器學習方法. 一般我們都會把它的決策樹結構打印出來觀察, 如果深度太深對於我們的理解是有難度的. max_path也是防止>過擬合的有效手段.
決策樹為什么選擇二叉樹?
我們回到不純度函數變化公式的本身:
我們假設決策樹每個節點可以有多個子樹. 舉個極端的例子, 某一個特征的所有取值都不相同, 那么可以找到一個划分方法(只要足夠多的子樹)可以讓每個子樹都有唯一的類別. 那么此時公>式的后半段為0. 這樣不純度變化取到最大值. 很明顯這是一個算法上的偏見, 已經形成了過擬合
.
但是如果是二叉樹就可以有效的避免這個問題. 他迫使特征有多個取值這樣的優勢無效.
決策樹的局限性有哪些?
決策樹的主要問題是容易形成過擬合. 如果我們通過各種剪枝和條件限制, 雖然可以避免過擬合, 但是會犧牲特征的有效性. 舉個例子:
-
樣本有1w個測試記錄
-
feature的數量是1k個
-
為了保證模型的有效性, 規定每個葉子節點包含的最少樣本數為100
在構造決策樹的過程中我們可以斷言節點個數不會超過100個, 這樣很多feature不僅沒有多次分裂, 甚至有些特征根本無法參與決策.
為了解決決策樹一系列的問題(這里不一一列舉), 統計學家不再深入決策樹的結構而是提出另外一個思路: 能夠通過構造不同的簡單的決策樹共同決策? 這有點"三個臭皮匠頂一個諸葛亮"的感覺. 我會在下一個文章具體介紹一下這個方法, 即隨機森林. 它是一個更加有效和常用的機器學習算法.
-
http://www.quora.com/Machine-Learning/Are-gini-index-entropy-or-classification-error-measures-causing-any-difference-on-Decision-Tree-classification ↩