概念
決策樹(Decision Tree)分為兩大類,回歸樹(Regression Decision Tree)和分類樹(Classification Decision Tree)。前者用于預測實數值,如明天的溫度、用戶的年齡、網頁的相關程度;后者用于分類標簽值,如晴天/陰天/霧/雨、用戶性別、網頁是否是垃圾頁面。這里要強調的是,前者的結果加減是有意義的,如10歲+5歲-3歲=12歲,后者則無意義,如男+男+女=到底是男是女?下面先介紹分類樹,決策樹一般情況下指的是分類樹。
分類樹是一種非線性有監督分類模型,隨機森林是一種非線性有監督分類模型。線性分類模型比如說邏輯回歸,可能會存在不可分問題,但是非線性分類就不存在。決策樹是機器學習中最接近人類思考問題的過程的一種算法,通過若干個節點,對特征進行提問并分類(可以是二分類也可以使多分類),直至最后生成葉節點(也就是只剩下一種屬性)。
分類樹是一種簡單但是廣泛使用的分類器。通過訓練數據構建決策樹,可以高效的對未知的數據進行分類。決策數有兩大優點:1)決策樹模型可以讀性好,具有描述性,有助于人工分析;2)效率高,決策樹只需要一次構建,反復使用,每一次預測的最大計算次數不超過決策樹的深度。
信息熵:熵代表信息的不確定性,信息的不確定性越大,熵越大;比如“明天太陽從東方升起”這一句話代表的信息我們可以認為為0;因為太陽從東方升起是個特定的規律,我們可以把這個事件的信息熵約等于0;說白了,信息熵和事件發生的概率成反比:數學上把信息熵定義如下:H(X)=H(P1,P2,…,Pn)=-∑P(xi)logP(xi)
互信息:指的是兩個隨機變量之間的關聯程度,即給定一個隨機變量后,另一個隨機變量不確定性的削弱程度,因而互信息取值最小為0,意味著給定一個隨機變量對確定一另一個隨機變量沒有關系,最大取值為隨機變量的熵,意味著給定一個隨機變量,能完全消除另一個隨機變量的不確定性。
一、分類決策樹
一個簡單的決策樹示意圖:
有人找我借錢(當然不太可能。。。),借還是不借?我會結合根據我自己有沒有錢、我自己用不用錢、對方信用好不好這三個特征來決定我的答案,即分成兩類。
轉到更普遍一點的視角,對于一些有特征的數據,如果我們能夠有這么一顆決策樹,我們也就能非常容易地預測樣本的結論。所以問題就轉換成怎么求一顆合適的決策樹,也就是怎么對這些特征進行排序。
在對特征排序前先設想一下,對某一個特征進行決策時,我們肯定希望分類后樣本的純度越高越好,也就是說分支結點的樣本盡可能屬于同一類別。
所以在選擇根節點的時候,我們應該選擇能夠使得“分支結點純度最高”的那個特征。在處理完根節點后,對于其分支節點,繼續套用根節點的思想不斷遞歸,這樣就能形成一顆樹。這其實也是貪心算法的基本思想。那怎么量化“純度最高”呢?熵就當仁不讓了,它是我們最常用的度量純度的指標。其數學表達式如下:其中N表示結論有多少種可能取值,p表示在取第k個值的時候發生的概率,對于樣本而言就是發生的頻率/總個數。(注意log是以2為底。)比如有20個樣本(X)的二分類問題,有15個樣本是狗,5個樣本不是狗,那么此時的熵為:
H(X)=-(0.75xlog0.75+0.25xlog0.25)=0.811;如果20個樣本全部是一類,那么該樣本的熵為0;如果20個樣本每類10個此時熵最大。樣本分布越均勻越混亂,熵越大。熵越小,說明樣本越純。擴展一下,樣本X可能取值為n種(x1。。。。xn)。可以證明,當p(xi)都等于1/n 時,也就是樣本絕對均勻,熵能達到最大。當p(xi)有一個為1,其他都為0時,也就是樣本取值都是xi,熵最小。
1.1 決策樹算法
ID3
假設在樣本集X中,對于一個特征a,它可能有(a1,a2。。。an)這些取值,如果用特征a當根節點對樣本集X進行劃分,肯定會有n個分支結點。剛才提了,我們希望劃分后,分支結點的樣本越純越好,也就是分支結點的“總熵”越小越好。由于每個分支結點的樣本個數不一樣,因此我們計算“總熵”時應該做一個加權,假設第i個結點樣本個數為W(ai),其在所有樣本中的權值為W(ai) / W(X)。所以我們可以得到一個總熵:
這個公式代表含義一句話:加權后各個結點的熵的總和。這個值應該越小,分類后的樣本純度越高。
這時候,我們引入一個名詞叫信息增益G(X,a),意思就是a這個特征給樣本帶來的信息的提升。公式就是:
由于對一個樣本而言,H(X)是一個固定值,因此信息增益G應該越大越好。尋找使得信息增益最大的特征作為目標結點,并逐步遞歸構建樹,這就是ID3算法的思想。
以一個簡單的例子來說明信息增益的計算:
上面的例子,我們計算一下如果以特征1作為目標結點的信息增益:
首先計算樣本的熵H(X):
再計算所有分支結點的總熵,可以看到特征1有3個結點A、B、C,其分別為6個、6個、5個樣本。所以A的權值為6/17, B的權值為6/17, C的為5/17。因為我們希望劃分后結點的純度越高越好,即總熵最小,因此還需要再分別計算結點A、B、C的熵。
特征1=A:樣本有3個是、3個否,其熵為:
特征1=B:2個是、4個否,其熵為0.918;特征1=C:4個是、1個否,其熵為0.722。這樣分支結點的總熵就等于:
特征1的信息增益G就等于0.998-0.889=0.109
類似地,我們也能算出其他的特征的信息增益,最終取信息增益最大的特征作為根節點。
C4.5
在ID3算法中其實有個很明顯的問題。如果有一個樣本集,它有一個叫id或者姓名之類的(唯一的)的特征,那就完蛋了。設想一下,如果有N個樣本,id這個特征肯定會把這個樣本也分成N份,也就是有N個結點。由于每個結點只有一個值,那每個結點的熵就為0。就是說所有分支結點的總熵為0,那么這個特征的信息增益一定會達到最大值。因此如果此時用ID3作為決策樹算法,根節點必然是id這個特征。這顯然這是不合理的。一般情況下,如果一個特征對樣本劃分的過于稀疏,這個也是不合理的(取值特別多的特征不適合做根節點)。
為了解決這個問題,C4.5算法采用了信息增益率來作為特征選取標準。所謂信息增益率,是在信息增益基礎上,除了一項split information,來懲罰值更多的屬性。
而這個split information其實就是特征個數的熵H(A)。還以上面的例子說明,如果以特征1作為結點,可以分為A B C三種結點,那么:
H(A)=H(特征1)=-(6/17xlog(6/17)x2+5/17(log(5/17))).
為什么這樣可以減少呢,以上面id的例子來理解一下。如果id把n個樣本分成了n份,那id這個特征的取值的概率都是1/n,文章引言已經說了,樣本絕對均勻的時候,熵最大。
因此這種情況,以id為特征,雖然信息增益最大,但是懲罰因子split information也最大,以此來拉低其增益率,這就是C4.5的思想。
CART(Classification And Regression Tree,分類與回歸樹)
決策樹的目的最終還是尋找到區分樣本的純度的量化標準。在CART決策樹中,采用的是基尼指數來作為其衡量標準。基尼系數(和基尼指數有區別的)直觀的理解是,從集合中隨機抽取兩個樣本,如果樣本集合越純,取到不同樣本的概率越小。這個概率反應的就是基尼系數。因此如果一個樣本有K個分類,假設樣本的某一個特征a有n個取值的話,那么以a為根節點會有n個子節點,其某一個結點下取到不同樣本的概率為:
而基尼指數,則是對所有結點的基尼系數進行加權處理
計算出來后,我們會選擇基尼指數最小的那個特征作為最優劃分特征。
1.2 過擬合解決辦法
剪枝
剪枝的目的其實就是防止過擬合,它是決策樹防止過擬合的最主要手段。決策樹中,為了盡可能爭取的分類訓練樣本,所以我們的決策樹也會一直生長。但是呢,有時候訓練樣本可能會學的太好,以至于把某些樣本的特有屬性當成一般屬性。這時候就我們就需要主動去除一些分支,來降低過擬合的風險。
剪枝一般有兩種方式:預剪枝和后剪枝。
預剪枝
一般情況下,只要結點樣本已經100%純了,樹才會停止生長。但這個可能會產生過擬合,因此我們沒有必要讓它100%生長,所以在這之前,設定一些終止條件來提前終止它。這就叫預剪枝,這個過程發生在決策樹生成之前。
一般我們預剪枝的手段有:
1、限定樹的深度
2、節點的子節點數目小于閾值
3、設定結點熵的閾值
后剪枝
顧名思義,這個剪枝是在決策樹建立過程后。后剪枝算法的算法很多,有些也挺深奧,這里提一個簡單的算法的思想Reduced-Error Pruning (REP)。該剪枝方法考慮將樹上的每個節點都作為修剪的候選對象,但是有一些條件決定是否修剪,通常有這幾步:
1、刪除其所有的子樹,使其成為葉節點。
2、賦予該節點最關聯的分類
3、用驗證數據驗證其準確度與處理前比較
如果不比原來差,則真正刪除其子樹。然后反復從下往上對結點處理。這個處理方式其實是處理掉那些“有害”的節點。
1.3 隨機森林
構建大量的決策樹組成森林來防止過擬合;雖然單個樹可能存在過擬合,但通過廣度的增加就會消除過擬合現象。隨機森林(Random forest):生成多顆決策樹,投票選舉的原則。
集成學習的概念:
圖中可以看到,每個個體學習器(弱學習器)都可包含一種算法,算法可以相同也可以不同。如果相同,我們把它叫做同質集成,反之則為異質。
隨機森林則是集成學習采用基于bagging策略的一個特例。
從上圖可以看出,bagging的個體學習器的訓練集是通過隨機采樣得到的。通過n次的隨機采樣,我們就可以得到n個樣本集。對于這n個樣本集,我們可以分別獨立的訓練出n個個體學習器,再對這n個個體學習器通過集合策略來得到最終的輸出,這n個個體學習器之間是相互獨立的,可以并行。
Baggging 與 Boosting
Baggging 和Boosting都是模型融合的方法,可以將弱分類器融合之后形成一個強分類器,而且融合之后的效果會比最好的弱分類器更好。
Baggging
隨機森林使用的是baggging,即從原始樣本集中抽取訓練集,每輪使用 bootstraping 的方法抽取 n 個訓練樣本,然后放回(在訓練集中,有些樣本可能被多次抽取到,而有些樣本可能一次都沒有被抽中)。經過 k 輪抽取,得到 k 個訓練集(k個訓練集之間是相互獨立的)。每次使用一個訓練集得到一個模型,k 個訓練集共得到 k 個模型。由于是隨機采樣,這樣每次的采樣集是和原始樣本集不同的,和其他采樣集也是不同的,這樣得到的個體學習器也是不同的。(注:這里并沒有具體的分類算法或回歸方法,我們可以根據具體問題采用不同的分類或回歸方法,如決策樹、感知器等。)
隨機森林最主要的問題是有了 k 個模型,怎么設定結合策略,主要方式也有這么幾種:
- 加權平均法
當學習器的權值都為1/n時,這個平均法叫簡單平均法。
- 投票法
對分類問題:將上步得到的 k 個模型采用投票的方式得到分類結果。投票法類似我們生活中的投票,如果每個學習器的權值都是一樣的。有絕對投票法,也就是票數過半;相對投票法,少數服從多數。如果有加權,依然是少數服從多數,只不過這里面的數是加權后的。
Boosting
AdaBoosting方式每次都使用全部的樣本,每輪訓練改變樣本的權重。下一輪訓練的目標是找到一個函數 f 來擬合上一輪的殘差。當殘差足夠小或者達到設置的最大迭代次數則停止。Boosting會減小在上一輪訓練正確的樣本的權重,增大錯誤樣本的權重。(對的殘差小,錯的殘差大)梯度提升的Boosting方式是使用代價函數對上一輪訓練出的模型函數f的偏導來擬合殘差。
二者之間的區別
樣本選擇
Bagging:訓練集是在原始集中有放回選取的,從原始集中選出的各輪訓練集之間是獨立的。
Boosting:每一輪的訓練集不變,只是訓練集中每個樣例在分類器中的權重發生變化。而權值是根據上一輪的分類結果進行調整。樣例權重
Bagging:使用均勻取樣,每個樣例的權重相等。
Boosting:根據錯誤率不斷調整樣例的權值,錯誤率越大則權重越大。預測函數
Bagging:所有預測函數的權重相等。
Boosting:每個弱分類器都有相應的權重,對于分類誤差小的分類器會有更大的權重。并行計算
Bagging:各個預測函數可以并行生成。
Boosting:各個預測函數只能順序生成,因為后一個模型參數需要前一輪模型的結果。
這兩種方法都是把若干個分類器整合為一個分類器的方法,只是整合的方式不一樣,最終得到不一樣的效果,將不同的分類算法套入到此類算法框架中一定程度上會提高了原單一分類器的分類效果,但是也增大了計算量。下面是將決策樹與這些算法框架進行結合所得到的新的算法:
- Bagging + 決策樹 = 隨機森林
- AdaBoost + 決策樹 = 提升樹
- Gradient Boosting + 決策樹 = GBDT
提升樹和GBDT,下面會分別介紹。
1.4 例子
以一個簡單的二次函數的代碼來看看決策樹怎么用吧。
訓練數據是100個隨機的真實的平方數據,不同的深度將會得到不同的曲線;測試數據也是隨機數據,但是不同深度的樹的模型,產生的預測值也不太一樣,如圖:
這幅圖的代碼如下:
我的是python 3.6環境,需要安裝numpy、matplotlib、sklearn這三個庫,需要的話直接pip install,大家可以跑跑看看,雖然簡單但挺有趣。
# -*- coding:utf-8 -*-
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeRegressor
if __name__ == "__main__":
# 準備訓練數據
N = 100
x = np.random.rand(N) * 6 - 3
x.sort()
y = x*x
x = x.reshape(-1, 1)
mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['axes.unicode_minus'] = False
# 決策樹深度及其曲線顏色
depth = [2, 4, 6, 8, 10]
clr = 'rgbmy' # 實際值
plt.figure(facecolor='w')
plt.plot(x, y, 'ro', ms=5, mec='k', label='實際值')
# 準備測試數據
x_test = np.linspace(-3, 3, 50).reshape(-1, 1)
# 構建決策樹
dtr = DecisionTreeRegressor()
# 循環不同深度情況下決策樹的模型,并用之測試數據的輸出
for d, c in zip(depth, clr):
# 設置最大深度(預剪枝)
dtr.set_params(max_depth=d)
# 訓練決策樹
dtr.fit(x, y)
# 用訓練數據得到的模型來驗證測試數據
y_hat = dtr.predict(x_test)
# 畫出模型得到的曲線
plt.plot(x_test, y_hat, '-', color=c, linewidth=2, markeredgecolor='k', label='Depth=%d' % d)
# 一些畫圖的基本參數
plt.legend(loc='upper center', fontsize=12)
plt.xlabel('X')
plt.ylabel('Y')
plt.grid(b=True, ls=':', color='#606060')
plt.title('二次函數決策樹', fontsize=15)
plt.tight_layout(2)
plt.show()
二、回歸決策樹
2.1 回歸樹
首先看看回歸樹是如何工作的。下面我們以對人的性別判別/年齡預測為例來說明,每個instance都是一個我們已知性別/年齡的人,而feature則包括這個人上網的時長、上網的時段、網購所花的金額等。
作為對比,先說分類樹,我們知道C4.5分類樹在每次分枝時,會選取信息增益率最大的特征,按照該標準分枝得到新節點,用同樣方法繼續分枝直到所有人都被分入性別唯一的葉子節點,或達到預設的終止條件,若最終葉子節點中的性別不唯一,則以多數人的性別作為該葉子節點的性別。
回歸樹總體流程也是類似,不過在每個節點(不一定是葉子節點)都會得一個預測值,以年齡為例,該預測值等于屬于這個節點的所有人年齡的平均值。分枝時窮舉每一個feature的每個閾值找最好的分割點,但衡量最好的標準不再是和熵有關的標準,而是最小化均方差--即(每個人的年齡-預測年齡)平方和除以 N。這很好理解,被預測出錯的人數越多,錯的越離譜,均方差就越大,通過最小化均方差能夠找到最靠譜的分枝依據。分枝直到每個葉子節點上人的年齡都唯一(這太難了)或者達到預設的終止條件(如葉子個數上限),若最終葉子節點上人的年齡不唯一,則以該節點上所有人的平均年齡做為該葉子節點的預測年齡。
2.2 提升樹
提升樹以回歸樹為基分類器。它的idea在于,第一個回歸樹預測的效果可能一般,但是第二個回歸樹把第一個預測錯的殘差作為輸入。也就是說,如果一個點的值被預測錯誤,那么在下一個回歸樹里面的模型的權值會變大。通過這個方式,來提高模型的效果。
舉個例子:訓練提升樹的步驟:
- step1 構建第一個回歸樹T1(x)
如何構建回歸樹T1(x)?
a. 從數據集里面找到一個切分點s,將數據集分成兩個部分。
b. 對于每個部分,找到一個值c,使得內部的y到所有的平方損失函數最小。
(遍歷所有可能的切分點s,找到最好的效果。那么問題又來了,如何判斷一個點的切分的效果好與壞?) - 在上一顆回歸樹回歸的基礎上,把殘差作為下一棵回歸樹的任務,繼續構造回歸樹。
- 不斷循環這個過程
計算c的公式是:
判斷一個點s,切分效果的好與壞的評價標準的時候:
下面,我們帶入這個具體的例子里面進行分析。假設,現在的切分點是s=1.5 , 那么數據集就會被分成兩個部分,一個是R1={1} , R2={2, 3 , ..., 10} 。對于切分的兩個部分里面,求c1和c2。根據上面的公式,c1=5.56 , c2=7.50 。那么,在我們這個例子里面,m(s)的值是:
如果,遍歷所有可能的切分點,對于每一個切分點都會有一個值。
也就說,當在s=6.5的時候,切分的效果是最好的。
也就是說,我們現在得到了第一顆回歸樹,T1(x)。對于小于6.5的數據,我們把他預測成6.24,對于大于等于6.5的數據,我們把它預測成8.91。然后,就到了最重要的一步,將殘差數據放入下一個回歸樹進行訓練。
下面去訓練下一個學習器:
判斷的終止條件是:
對于求出的第一個回歸樹:
對于求出的第二個回歸樹:
依次類推:
最后:
2.3 梯度提升樹GBDT
GBDT(Gradient Boosting Decision Tree) 又叫 MART(Multiple Additive Regression Tree),是一種迭代的決策樹算法,該算法由多棵決策樹組成,所有樹的結論累加起來做最終答案。它在被提出之初就和SVM一起被認為是泛化能力(generalization)較強的算法。近些年更因為被用于搜索排序的機器學習模型而引起大家關注。
GBDT的核心在于累加所有樹的結果作為最終結果,就像前面對年齡的累加(-3是加負3),而分類樹的結果顯然是沒辦法累加的,所以GBDT中的樹都是回歸樹,不是分類樹,這點對理解GBDT相當重要(盡管GBDT調整后也可用于分類但不代表GBDT的樹是分類樹)。
GBDT主要由三個概念組成:Regression Decistion Tree(即DT),Gradient Boosting(即GB),Shrinkage (算法的一個重要演進分枝,目前大部分源碼都按該版本實現)。搞定這三個概念后就能明白GBDT是如何工作的,要繼續理解它如何用于搜索排序則需要額外理解 RankNet 概念,之后便功德圓滿。
算法原理
GBDT的原理和提升樹差不多,但是提升樹在某些時候不方便求殘差,梯度提升樹則是用損失函數的負梯度方向值來近似擬合殘差。GBDT很好的結合了回歸樹與提升樹的思想,并將它們推廣到更一般的情形。例如提升樹中第三步計算殘差是在損失函數為平方損失函數來計算的,如果損失函數為對數函數,則計算殘差 變得不是很方便,以及在第四步訓練回歸樹時,計算節點輸出值 c_m 也變得不容易進行。
算法過程如下:
以上算法將回歸樹和提升樹的算法結合起來,在第5步中求解 ,如果損失函數為平方損失函數,則解法與前面的回歸樹一致,直接取均值即可。如果是其他損失函數,則需要具體進行求解。具體而言,就是取導數為零來解等式。
不過對于分類情況,由于樣本輸出不是連續的值,而是離散的類別,導致我們無法直接從輸出類別去擬合類別輸出的誤差。為了解決這個問題,主要有兩個方法,一個是用指數損失函數,此時GBDT退化為Adaboost算法。另一種方法是用類似于邏輯回歸的對數似然損失函數的方法。也就是說,我們用的是類別的預測概率值和真實概率值的差來擬合損失。本文僅討論用對數似然損失函數的GBDT分類。而對于對數似然損失函數,我們又有二元分類和多元分類的區別。
GBDT損失函數
對于分類算法,其損失函數一般有對數損失函數和指數損失函數兩種:
- 如果是指數損失函數,則損失函數表達式為:
- 如果是對數損失函數,分為二元分類和多元分類兩種:
二元:
多元:
對于回歸算法,常用損失函數有如下4種:
均方差,這個是最常見的回歸損失函數:
絕對損失,這個損失函數也很常見:
Huber損失,它是均方差和絕對損失的折衷產物,對于遠離中心的異常點,采用絕對損失,而中心附近的點采用均方差。這個界限一般用分位數點度量。
分位數損失。它對應的是分位數回歸的損失函數。