1. 程式人生 > >理解隨機森林:基於Python的實現和解釋

理解隨機森林:基於Python的實現和解釋

隨機森林是一種強大的機器學習模型,得益於各種強大的庫,現在人們可以很輕鬆地呼叫它,但並不是每一個會使用該模型的人都理解它的工作方式。資料科學家 William Koehrsen 用 Python 實現並解釋了決策樹和隨機森林的工作過程。

引言

感謝 Scikit-Learn 這樣的庫,讓我們現在可以非常輕鬆地使用 Python 實現任何機器學習演算法。事實上操作起來很簡單,我們往往無需瞭解任何有關模型內部工作方式的任何知識就能使用它。儘管我們並不需要理解所有細節,但瞭解一些有關模型訓練和預測方式的思路仍然會有很大的幫助。這使得我們可以在模型表現不如預期時對模型進行診斷,或解釋我們的模型做決策的方式——這能幫助我們說服他人使用我們的模型。

本文將介紹如何使用 Python 構建和使用隨機森林。我們不只是簡單地展示程式碼,而會盡力解釋模型的工作方式。我們將從一個解決簡單問題的單個決策樹開始,然後逐漸深入,最終完成一個針對某個真實世界資料科學問題的隨機森林。本文所涉及的完整程式碼可參閱這個 GitHub 上的 Jupyter Notebook:https://github.com/TryEnlight/Machine-Learning-Projects/blob/master/Random%20Forest%20Tutorial.ipynb

理解決策樹

決策樹是隨機森林的構建模組,本身是一種相當直觀的模型。我們可以將決策樹看作是詢問有關資料的問題的流程圖,並最終導向一個預測類別(在迴歸任務上則是連續值)。這是一種可解釋的模型,因為其決策方式類似於我們在現實生活中做法:我們詢問一系列有關資料的問題,直到我們最終達成決策。

決策樹的主要技術細節在於如何構建有關資料的問題。決策樹的構建方式是構建能最大限度降低基尼不純度(Gini Impurity)的問題。

我們稍後一點會談到基尼不純度,現在你只需要知道,這意味著決策樹會盡力構建儘可能純的節點,其中有很高比例的樣本(資料點)都來自同一個類別。

基尼不純度和決策樹構建可能有些難以理解,所以首先我們構建一個決策樹,然後我們通過一些簡單的數學來進行解釋。

一個簡單問題的決策樹

我們先從一個如下所示的非常簡單的二元分類任務開始:

理解隨機森林:基於Python的實現和解釋

我們的資料僅有兩個特徵(預測變數)。這裡共有 6 個數據點,2 種不同的標籤。

儘管這個問題很簡單,但卻無法實現線性分割,也就是說我們不能在這些資料之間用一條直線將各個點劃分到對應的類別。但是,我們可以畫出一系列直線來分開這兩個類別,這實際上就是決策樹在構建系列問題時的做法。

為了建立決策樹,並在資料上進行訓練(擬合),我們可以使用 Scikit-Learn:

from sklearn.tree import DecisionTreeClassifier
# Make a decision tree and train

tree = DecisionTreeClassifier(random_state=RSEED)
tree.fit(X, y)

這就是全部了!

在訓練過程中,我們會向模型提供特徵和標籤,使其能夠學習基於這些特徵對資料點進行分類。我們沒有針對這個簡單問題的測試集,但在進行測試時,我們只向模型提供特徵,然後讓其給出對標籤的預測。

我們可以在訓練資料上測試模型的準確度:

print(f'Model Accuracy: {tree.score(X, y)}')

Model Accuracy: 1.0

可以看到正確率為 100%,這符合預期,因為我們在訓練過程中已經提供過答案(y)。

視覺化決策樹

所以我們在構建決策樹時究竟發生了什麼?我發現最有幫助的理解決策樹的方法是視覺化,我們可以使用 Scikit-Learn 實用程式來做這件事(詳情可參考 https://goo.gl/BYVyQJ 或 https://goo.gl/P98VjQ)。

理解隨機森林:基於Python的實現和解釋

這展現了上述決策樹的整體結構。除葉節點(終端節點)之外的所有節點都有 5 部分:

  1. 基於一個特徵的值的有關資料的問題。每個問題的答案要麼是 True,要麼就是 False。資料點會根據該問題的答案在該決策樹中移動。

  2. gini:節點的基尼不純度。當沿著樹向下移動時,平均加權的基尼不純度必須降低。

  3. samples:節點中觀察的數量。

  4. value:每一類別中樣本的數量。比如,頂部節點中有 2 個樣本屬於類別 0,有 4 個樣本屬於類別 1。

  5. class:節點中大多數點的類別(持平時預設為 0)。在葉節點中,這是該節點中所有樣本的預測結果。

葉節點沒有問題,因為這些節點是得出最終預測結果的地方。要分類一個新的資料點,只需沿樹向下,使用該資料點的特徵來回答問題,直到到達一個葉節點即可,此處的類別即為該樹的預測結果。你可以使用上述的點進行嘗試或測試 Notebook 中不同的預測。

基尼不純度(Gini Impurity)

現在我們應該去理解什麼是基尼不純度了。簡單來說,基尼不純度就是節點中隨機選出的樣本如果根據該節點的樣本分佈標註而因此標註不正確的概率。比如,在頂部(根)節點中,有 44.4% 的可能性將一個隨機選擇的資料點基於該節點的樣本標籤分佈不正確地分類。

基尼不純度是決策樹決定用於分割節點(有關資料的問題)的特徵值的方式。樹會通過所有用於分割的特徵來進行搜尋,以最大化地降低不純度。

基尼不純度為 0 時最完美,因為這意味著隨機選出的樣本不可能被錯誤標註,只有當一個節點中的所有樣本都屬於同一類別時才會出現這種情況!在樹的每一層級,加權的平均基尼不純度都會降低,表明節點變得更純(另一種分割節點的方法是使用資訊增益,這是一個相關的概念)。

一個節點的基尼不純度的公式為:

理解隨機森林:基於Python的實現和解釋

其中 p_i 是該節點中類別 i 中資料點的比例。我們來計算一下根節點的基尼不純度:

理解隨機森林:基於Python的實現和解釋

在這非常簡單的數學運算中,一個非常強大的機器學習模型誕生了!

這是該決策樹頂層的總基尼不純度,因為這裡僅有根節點。在這個決策樹的第二層,最左邊的節點的基尼不純度為 0.5,這似乎表明不純度增大了。但是,每一層應該降低的是基尼不純度的加權平均。每個節點都會根據其樣本佔父節點樣本的比例進行加權。所以整體而言,第二層的基尼不純度為:

理解隨機森林:基於Python的實現和解釋

隨著我們繼續沿決策樹向下,節點最終會越來越純;在最後一層,每個節點的基尼不純度都會達到 0.0,這說明每個節點都只包含單一類別的樣本。這符合我們的預期,因為我們並沒有限制決策樹的深度,讓其可以按需要建立足夠多的層以能分類所有資料點。儘管我們的模型能正確分類所有的訓練資料點,但這並不意味著它就是完美的,因為它與訓練資料可能過擬合了。

過擬合:森林比樹更優的原因

你可能會問為什麼不直接使用一個決策樹?這種分類器堪稱完美,因為根本不會犯任何錯誤!但要記住一個重點:決策樹只是不會在訓練資料上犯錯。

我們知道出現這種情況的原因是我們已經為其提供過答案。而機器學習模型的關鍵在於能很好地泛化用於測試資料。不幸的是,當我們不限制決策樹的深度時,它往往都會與訓練資料過擬合。

過擬合是指我們的模型有很高的方差並且本質上記憶了訓練資料的情況。這意味著其在訓練資料上表現非常好,甚至能達到完美的程度,但這樣它將無法在測試資料上做出準確的預測,因為測試資料是不同的!我們想要的是既能在訓練資料上表現優良,也能很好地分析測試資料的模型。為什麼當我們不限制決策樹的最大深度時會使其容易過擬合呢?因為此時決策樹有不受限制的複雜度,這意味著它會不斷生長,直到針對每個觀察都有一個葉節點,從而完美地分類所有資料點。

要理解決策樹高方差的原因,我們可以將其看作是一個人。想象一下,你必須分析明日蘋果股票是否上漲,然後你決定去詢問幾位分析師。任何一位分析師都可能有很大的差異,並且非常依賴他們各自能獲取的資料——一位分析師可能僅閱讀支援蘋果公司的新聞,因此她可能認為價格會上漲,而另一位分析師最近聽朋友說蘋果產品質量開始下降了,所以她認為應當下跌。這些分析師個體之間有很高的方差,因為他們的答案嚴重依賴於他們見過的資料。

我們也可以不詢問單個分析師,而是綜合大量專家的意見,並基於最常見的答案給出最終決策。因為每位分析師都會看到不同的資料,所以可以預期個體差異會很大,但整個集體的總體方差應該會減小。使用許多個體正是隨機森林方法背後的本質思路:不是使用單個決策樹,而是使用數百或數千個決策樹來組成一個強大的模型。則該模型的最終預測結果即為集體中所有樹的預測的平均。(過擬合問題也被稱為「偏差-方差權衡」問題,是機器學習領域內一大基本研究主題。)

隨機森林

隨機森林是由許多決策樹構成的模型。這不僅僅是森林,而且是隨機的,這涉及到兩個概念:

1.隨機取樣資料點

2.基於特徵的子集分割節點

隨機取樣

隨機森林的一大關鍵是每個樹都在隨機的資料點樣本上進行訓練。這些樣本是可重複地抽取出來的(稱為 bootstrapping),也就是說某些樣本會多次用於單個樹的訓練(如果有需要,也可以禁止這種做法)。其思路是,通過在不同樣本上訓練每個樹,儘管每個樹依據訓練資料的某個特定子集而可能有較高方差,但整體而言整個森林的方差會很低。這種在資料的不同子集上訓練每個單個學習器然後再求預測結果的平均的流程被稱為 bagging,這是 bootstrap aggregating 的縮寫。

特徵的隨機子集

隨機森林背後的另一個概念是:在每個決策樹中,分割每個節點時都只會考慮所有特徵中的一個子集。通常設定為 sqrt(n_features),意思是在每個節點,決策樹會基於一部分特徵來考慮分割,這部分特徵的數量為總特徵數量的平方根。隨機森林也可以在每個節點考慮所有特徵來進行訓練。(在 Scikit-Learn 隨機森林實現中,這些選項是可調控的。)

如果你理解了單個決策樹、bagging 決策樹、特徵的隨機子集,那你就可以很好地理解隨機森林的工作方式了。隨機森林組合了數百或數千個決策樹,並會在稍有不同的觀察集上訓練每個決策樹(資料點是可重複地抽取出來的),並且會根據限定數量的特徵分割每個樹中的節點。隨機森林的最終預測結果是每個單個樹的預測結果的平均。

隨機森林實踐

非常類似於其它 Scikit-Learn 模型,通過 Python 使用隨機森林僅需要幾行程式碼。我們將會構建一個隨機森林,但不是針對上述的簡單問題。為了比較隨機森林與單個決策樹的能力,我們將使用一個真實資料集,並將其分成了訓練集和測試集。

資料集

我們要解決的問題是一個二元分類任務。特徵是個體的社會經濟和生活方式屬性,標籤 0 表示健康狀況差,標籤 1 表示身體健康。該資料集是由美國疾病預防控制中心收集的,可在這裡獲取:https://www.kaggle.com/cdc/behavioral-risk-factor-surveillance-system。這是一個不平衡分類問題,因此準確度並不是合適的度量標準。我們將衡量曲線下受試者工作特性曲線(ROC AUC),該度量的取值範圍為 0(最差)到 1(最好),隨機猜測的分數為 0.5。我們還可以繪製 ROC 曲線來評估模型的表現。

引言提到的 Jupyter Notebook 包含了針對該任務的決策樹和隨機森林實現,但這裡我們只關注隨機森林。在讀取了資料之後,我們可以用以下程式碼例項化並訓練一個隨機森林:

from sklearn.ensemble import RandomForestClassifier

# Create the model with 100 trees

model = RandomForestClassifier(n_estimators=100,
random_state=RSEED,
max_features = 'sqrt',
n_jobs=-1, verbose = 1)

# Fit on training data

model.fit(train, train_labels)

經過幾分鐘的訓練之後,可以通過以下程式碼讓該模型基於測試資料進行預測:

rf_predictions = model.predict(test) rf_probs = model.predict_proba(test)[:, 1]

我們既有類別預測結果(predict),也有預測概率(predict_proba),都是計算 ROC AUC 所需的。有了測試預測結果之後,我們可以將它們與測試標籤進行比較,以計算出 ROC AUC。

from sklearn.metrics import roc_auc_score

# Calculate roc auc

roc_value = roc_auc_score(test_labels, rf_probs)

結果

隨機森林的最終 ROC AUC 結果是 0.87,而單個決策樹的結果是 0.67。如果我們看看訓練分數,可以看到這兩個模型都得到了 1.0 的 ROC AUC,同樣這符合預期,因為我們已經為這些模型提供過訓練資料的答案並且沒有限制最大深度。但是,儘管隨機森林過擬合了,但比起單個決策樹,它仍能遠遠更好地泛化到測試資料上。

檢視模型內部,可以看到單個決策樹的最大深度為 55,共有 12327 個節點。隨機森林中決策樹的平均深度為 46,平均節點數為 13396。即使隨機森林的平均節點數更大,它也能更好地泛化!

我們還可以繪製單個決策樹(上)和隨機森林(下)的 ROC 曲線。曲線越靠近左上角,則模型越好:

理解隨機森林:基於Python的實現和解釋

可以看到隨機森林明顯優於單個決策樹。

我們還可以使用另一種模型診斷方法,即繪製測試預測結果的混淆矩陣(詳見 Jupyter Notebook):

理解隨機森林:基於Python的實現和解釋

特徵重要度

隨機森林中的特徵重要度是指在依據該特徵分割的所有節點上基尼不純度降低的總和。我們可以使用這一指標確定隨機森林認為最重要的預測變數是什麼。特徵重要度可從訓練後的隨機森林中提取出來,並表示成 Pandasdataframe 的形式:

import pandas as pd

fi = pd.DataFrame({'feature': list(train.columns),
'importance': model.feature*importances*}).
sort_values('importance', ascending = False)

fi.head

feature importance
tDIFFWALKt0.036200
tQLACTLM2t0.030694
tEMPLOY1t 0.024156
tDIFFALONt0.022699
tUSEEQUIPt0.016922
tDECIDEt 0.016271
t_LMTSCL1t0.013424
tINCOME2t 0.011929
tCHCCOPD1t0.011506
t_BMI5t 0.011497

我們還可以使用特徵重要度來進行特徵選擇,即移除重要度為 0 或較低的特徵。

視覺化森林中的樹

最後,我們可以視覺化森林中的單個決策樹。這時候我們就必須限定樹的深度了,因為整個樹非常大,難以轉換成單張影象。我將最大深度設定為 6,得到了下面的影象。這仍然是一副很大的圖!

理解隨機森林:基於Python的實現和解釋

接下來的步驟

下一步可以對隨機森林進行優化,可以通過隨機搜尋和 Scikit-Learn 中的 RandomizedSearchCV 來做。

優化是指為給定資料集上的模型尋找最佳的超引數。資料集不同,最佳的超引數也會各有不同,所以我們必須分別在每個資料集上執行優化(也被稱為模型調節)。我喜歡將模型調節看作是尋找機器學習演算法的最佳設定。

引言中提到的 Jupyter Notebook 提供了一個用於隨機森林的模型優化的隨機搜尋的實現。

總結

儘管我們無需理解機器學習模型內部的任何情況也能構建出強大的機器學習模型,但瞭解一些模型工作方式的相關知識將大有裨益。在本文中,我們不僅用 Python 構建和使用了一個隨機森林,而且還對該模型進行了理解。

我們首先了解了單個決策樹,這是隨機森林的基本構建模組;然後我們看到了可以如何將數百或數千個決策樹組合成一個集合模型。當這種集合模型與 bagging和隨機的特徵取樣一起使用時,就被稱為隨機森林。本文中涉及的關鍵概念有:

  1. 決策樹:基於有關特徵值的問題的流程圖進行決策的直觀模型。因為過擬合訓練資料而有很高的方差。

  2. 基尼不純度:決策樹在分割每個節點時所要最小化的指標。表示從一個節點隨機選出的一個樣本依據該節點的樣本分佈而錯誤分類的概率。

  3. bootstrapping:可重複地取樣隨機觀察集。隨機森林用於訓練每個決策樹的方法。

  4. 隨機特徵子集:在考慮如何分割決策樹中的每個節點時,選擇一個隨機的特徵集。

  5. 隨機森林:由數百或數千個使用 bootstrapping、隨機特徵子集和平均投票來做預測的決策樹構成的集合模型。這是 bagging 整合的一個示例。

  6. 偏差-方差權衡:機器學習領域內的一個基本問題,描述了高複雜度的模型和簡單模型之間的權衡。高複雜度模型可以很好地學習訓練資料,但代價是不能很好地泛化到測試資料(高方差);而簡單模型(高偏差)甚至無法學習訓練資料。隨機森林能在降低單個決策樹的方差的同時準確地學習訓練資料,從而在測試資料上得到更好的預測結果。

希望這篇文章能為你提供信心,幫助你理解隨機森林並開始在你自己的專案中使用它。隨機森林是一種強大的機器學習模型,但這不應該妨礙我們理解它的工作方式!我們對一個模型的瞭解越多,我們就能越好地使用它以及解釋它做預測的方式,這樣其他人才會信任它!現在行動起來,用隨機森林解決一些問題吧。