本系列教程基本就是摘抄《Python機器學習基礎教程》中的例子內容。html
爲了便於跟蹤和學習,本系列教程在Github上提供了jupyter notebook 版本:node
Github倉庫:https://github.com/Holy-Shine/Introduciton-2-ML-with-Python-notebookpython
系列教程總目錄
Python機器學習基礎教程git
導入必要的包github
import numpy as np import matplotlib.pyplot as plt import pandas as pd import mglearn import os %matplotlib inline
集成(ensemble)是合併多個機器學習模型來構建更強大模型的方法。在機器學習文獻中有許多模型都屬於這一類,但已證實有兩種集成模型對大量分類和迴歸的數據集都是有效的,兩者都以決策樹爲基礎,分別是隨機森林(random forest)和梯度提高決策樹(gradient boosted decision tree)。算法
咱們剛剛說過,決策樹的一個主要缺點在於常常對訓練數據過擬合。隨機森林是解決這個問題的一種方法。隨機森林本質上是許多決策樹的集合,其中每棵樹都和其餘樹略有不一樣。隨機森林背後的思想是,每棵樹的預測可能都相對較好,但可能對部分數據過擬合。若是構造不少樹,而且每棵樹的預測都很好,但都以不一樣的方式過擬合,那麼咱們能夠對這些樹的結果取平均值來下降過擬合。既能減小過擬合又能保持樹的預測能力,這能夠在數學上嚴格證實。bootstrap
爲了實現這一策略,咱們須要構造許多決策樹。每棵樹都應該對目標值作出能夠接受的預測,還應該與其餘樹不一樣。隨機森林的名字來自於將隨機性添加到樹的構造過程當中,以確保每棵樹都各不相同。隨機森林中樹的隨機化方法有兩種:一種是經過選擇用於構造樹的數據點,另外一種是經過選擇每次劃分測試的特徵。咱們來更深刻地研究這一過程。dom
想要構造一個隨機森林模型,你須要肯定用於構造的樹的個數( RandomForestRegressor 或 RandomForestClassifier 的 n_estimators 參數)。好比咱們想要構造 10 棵樹。這些樹在構造時彼此徹底獨立,算法對每棵樹進行不一樣的隨機選擇,以確保樹和樹之間是有區別的。想要構造一棵樹,首先要對數據進行自助採樣(bootstrap sample)。也就是說,從 n_samples 個數據點中有放回地(即同同樣本能夠被屢次抽取)重複隨機抽取一個樣本,共抽取n_samples 次。這樣會建立一個與原數據集大小相同的數據集,但有些數據點會缺失(大約三分之一),有些會重複。機器學習
舉例說明,好比咱們想要建立列表 ['a', 'b', 'c', 'd'] 的自助採樣。一種可能的自主採樣是 ['b', 'd', 'd', 'c'] ,另外一種可能的採樣爲 ['d', 'a', 'd', 'a'] 。性能
接下來,基於這個新建立的數據集來構造決策樹。可是,要對咱們在介紹決策樹時描述的算法稍做修改。在每一個結點處,算法隨機選擇特徵的一個子集,並對其中一個特徵尋找最佳測試,而不是對每一個結點都尋找最佳測試。選擇的特徵個數由 max_features 參數來控制。每一個結點中特徵子集的選擇是相互獨立的,這樣樹的每一個結點可使用特徵的不一樣子集來作出決策。
因爲使用了自助採樣,隨機森林中構造每棵決策樹的數據集都是略有不一樣的。因爲每一個結點的特徵選擇,每棵樹中的每次劃分都是基於特徵的不一樣子集。這兩種方法共同保證隨機森林中全部樹都不相同。
在這個過程當中的一個關鍵參數是 max_features 。若是咱們設置 max_features 等於n_features ,那麼每次劃分都要考慮數據集的全部特徵,在特徵選擇的過程當中沒有添加隨機性(不過自助採樣依然存在隨機性)。若是設置 max_features 等於 1 ,那麼在劃分時將沒法選擇對哪一個特徵進行測試,只能對隨機選擇的某個特徵搜索不一樣的閾值。所以,若是 max_features 較大,那麼隨機森林中的樹將會十分類似,利用最獨特的特徵能夠輕鬆擬合數據。若是 max_features 較小,那麼隨機森林中的樹將會差別很大,爲了很好地擬合數據,每棵樹的深度都要很大。
想要利用隨機森林進行預測,算法首先對森林中的每棵樹進行預測。對於迴歸問題,咱們能夠對這些結果取平均值做爲最終預測。對於分類問題,則用到了「軟投票」(soft voting)策略。也就是說,每一個算法作出「軟」預測,給出每一個可能的輸出標籤的機率。對全部樹的預測機率取平均值,而後將機率最大的類別做爲預測結果。
下面將由 5 棵樹組成的隨機森林應用到前面研究過的 two_moons 數據集上:
from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import make_moons from sklearn.model_selection import train_test_split X,y = make_moons(n_samples=100, noise=0.25, random_state=3) X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42) forest = RandomForestClassifier(n_estimators=5, random_state=2) forest.fit(X_train, y_train)
做爲隨機森林的一部分,樹被保存在 estimator_ 屬性中。咱們將每棵樹學到的決策邊界可視化,也將它們的總預測(即整個森林作出的預測)可視化(圖 2-33):
fig, axes = plt.subplots(2, 3, figsize=(20,10)) for i, (ax, tree) in enumerate(zip(axes.ravel(), forest.estimators_)): ax.set_title("Tree {}".format(i)) mglearn.plots.plot_tree_partition(X_train, y_train, tree, ax=ax) mglearn.plots.plot_2d_separator(forest, X_train, fill=True, ax=axes[-1,-1], alpha=.4) axes[-1,-1].set_title("Random Forest") mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train)
你能夠清楚地看到,這 5 棵樹學到的決策邊界大不相同。每棵樹都犯了一些錯誤,由於這裏畫出的一些訓練點實際上並無包含在這些樹的訓練集中,緣由在於自助採樣。
隨機森林比單獨每一棵樹的過擬合都要小,給出的決策邊界也更符合直覺。在任何實際應用中,咱們會用到更多棵樹(一般是幾百或上千),從而獲得更平滑的邊界。
再舉一個例子,咱們將包含 100 棵樹的隨機森林應用在乳腺癌數據集上:
from sklearn.datasets import load_breast_cancer cancer = load_breast_cancer() X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=0) forest = RandomForestClassifier(n_estimators=100, random_state=0) forest.fit(X_train, y_train) print("Accuracy on training set: {:.3f}".format(forest.score(X_train, y_train))) print("Accuracy on test set: {:.3f}".format(forest.score(X_test, y_test)))
[out]:
Accuracy on training set: 1.000
Accuracy on test set: 0.972
在沒有調節任何參數的狀況下,隨機森林的精度爲 97%,比線性模型或單棵決策樹都要好。咱們能夠調節 max_features 參數,或者像單棵決策樹那樣進行預剪枝。可是,隨機森林的默認參數一般就已經能夠給出很好的結果。
與決策樹相似,隨機森林也能夠給出特徵重要性,計算方法是將森林中全部樹的特徵重要性求和並取平均。通常來講,隨機森林給出的特徵重要性要比單棵樹給出的更爲可靠。參見圖 2-34。
def plot_feature_importance_cancer(model): n_features = cancer.data.shape[1] plt.barh(range(n_features), model.feature_importances_, align='center') plt.yticks(np.arange(n_features), cancer.feature_names) plt.xlabel("Feature importance") plt.ylabel("Feature") plot_feature_importance_cancer(forest)
如你所見,與單棵樹相比,隨機森林中有更多特徵的重要性不爲零。與單棵決策樹相似,隨機森林也給了「worst radius」(最大半徑)特徵很大的重要性,但從整體來看,它實際上卻選擇「worst perimeter」(最大周長)做爲信息量最大的特徵。因爲構造隨機森林過程當中的隨機性,算法須要考慮多種可能的解釋,結果就是隨機森林比單棵樹更能從整體把握數據的特徵。
用於迴歸和分類的隨機森林是目前應用最普遍的機器學習方法之一。這種方法很是強大,一般不須要反覆調節參數就能夠給出很好的結果,也不須要對數據進
行縮放。
從本質上看,隨機森林擁有決策樹的全部優勢,同時彌補了決策樹的一些缺陷。仍然使用決策樹的一個緣由是須要決策過程的緊湊表示。基本上不可能對幾十棵甚至上百棵樹作出詳細解釋,隨機森林中樹的深度每每比決策樹還要大(由於用到了特徵子集)。所以,若是你須要以可視化的方式向非專家總結預測過程,那麼選擇單棵決策樹可能更好。雖然在大型數據集上構建隨機森林可能比較費時間,但在一臺計算機的多個 CPU 內核上並行計算也很容易。若是你用的是多核處理器(幾乎全部的現代化計算機都是),你能夠用 n_jobs 參數來調節使用的內核個數。使用更多的 CPU 內核,可讓速度線性增長(使用 2 個內核,隨機森林的訓練速度會加倍),但設置 n_jobs 大於內核個數是沒有用的。你能夠設置 n_jobs=-1 來使用計算機的全部內核。
你應該記住,隨機森林本質上是隨機的,設置不一樣的隨機狀態(或者不設置 random_state參數)能夠完全改變構建的模型。森林中的樹越多,它對隨機狀態選擇的魯棒性就越好。若是你但願結果能夠重現,固定 random_state 是很重要的。
對於維度很是高的稀疏數據(好比文本數據),隨機森林的表現每每不是很好。對於這種數據,使用線性模型可能更合適。即便是很是大的數據集,隨機森林的表現一般也很好,訓練過程很容易並行在功能強大的計算機的多個 CPU 內核上。不過,隨機森林須要更大的內存,訓練和預測的速度也比線性模型要慢。對一個應用來講,若是時間和內存很重要的話,那麼換用線性模型可能更爲明智。
須要調節的重要參數有 n_estimators 和 max_features ,可能還包括預剪枝選項(如 max_depth )。 n_estimators 老是越大越好。對更多的樹取平都可以下降過擬合,從而獲得魯棒性更好的集成。不過收益是遞減的,並且樹越多須要的內存也越多,訓練時間也越長。經常使用的經驗法則就是「在你的時間 / 內存容許的狀況下儘可能多」。
前面說過, max_features 決定每棵樹的隨機性大小,較小的 max_features 能夠下降過擬合。通常來講,好的經驗就是使用默認值:對於分類,默認值是 max_features=sqrt(n_features) ;對於迴歸,默認值是 max_features=n_features 。增大 max_features 或 max_leaf_nodes 有時也能夠提升性能。它還能夠大大下降用於訓練和預測的時間和空間要求。
梯度提高迴歸樹是另外一種集成方法,經過合併多個決策樹來構建一個更爲強大的模型。雖然名字中含有「迴歸」,但這個模型既能夠用於迴歸也能夠用於分類。與隨機森林方法不一樣,梯度提高採用連續的方式構造樹,每棵樹都試圖糾正前一棵樹的錯誤。默認狀況下,梯度提高迴歸樹中沒有隨機化,而是用到了強預剪枝。梯度提高樹一般使用深度很小(1 到 5 之間)的樹,這樣模型佔用的內存更少,預測速度也更快。
梯度提高背後的主要思想是合併許多簡單的模型(在這個語境中叫做弱學習器),好比深度較小的樹。每棵樹只能對部分數據作出好的預測,所以,添加的樹愈來愈多,能夠不斷迭代提升性能。
梯度提高樹常常是機器學習競賽的優勝者,而且普遍應用於業界。與隨機森林相比,它一般對參數設置更爲敏感,但若是參數設置正確的話,模型精度更高。
除了預剪枝與集成中樹的數量以外,梯度提高的另外一個重要參數是 learning_rate (學習率),用於控制每棵樹糾正前一棵樹的錯誤的強度。較高的學習率意味着每棵樹均可以作出較強的修正,這樣模型更爲複雜。經過增大 n_estimators 來向集成中添加更多樹,也能夠增長模型複雜度,由於模型有更多機會糾正訓練集上的錯誤。
下面是在乳腺癌數據集上應用 GradientBoostingClassifier 的示例。默認使用 100 棵樹,最大深度是 3,學習率爲 0.1:
from sklearn.ensemble import GradientBoostingClassifier X_trian, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=0) gbrt = GradientBoostingClassifier(random_state=0) gbrt.fit(X_train, y_train) print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train))) print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))
[out]:
Accuracy on training set: 1.000
Accuracy on test set: 0.958
因爲訓練集精度達到 100%,因此極可能存在過擬合。爲了下降過擬合,咱們能夠限制最大深度來增強預剪枝,也能夠下降學習率:
gbrt = GradientBoostingClassifier(random_state=0, max_depth=1) gbrt.fit(X_train,y_train) print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train))) print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))
[out]:
Accuracy on training set: 0.991
Accuracy on test set: 0.972
gbrt = GradientBoostingClassifier(random_state=0, learning_rate=0.01) gbrt.fit(X_train, y_train) print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train))) print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))
[out]:
Accuracy on training set: 0.988
Accuracy on test set: 0.965
下降模型複雜度的兩種方法都下降了訓練集精度,這和預期相同。在這個例子中,減少樹的最大深度顯著提高了模型性能,而下降學習率僅稍稍提升了泛化性能。
對於其餘基於決策樹的模型,咱們也能夠將特徵重要性可視化,以便更好地理解模型(圖 2-35)。因爲咱們用到了 100 棵樹,因此即便全部樹的深度都是 1,查看全部樹也是不現實的:
gbrt = GradientBoostingClassifier(random_state=0, max_depth=1) gbrt.fit(X_train, y_train) plot_feature_importance_cancer(gbrt)
能夠看到,梯度提高樹的特徵重要性與隨機森林的特徵重要性有些相似,不過梯度提高徹底忽略了某些特徵。
因爲梯度提高和隨機森林兩種方法在相似的數據上表現得都很好,所以一種經常使用的方法就是先嚐試隨機森林,它的魯棒性很好。若是隨機森林效果很好,但預測時間太長,或者機器學習模型精度小數點後第二位的提升也很重要,那麼切換成梯度提高一般會有用。
若是你想要將梯度提高應用在大規模問題上,能夠研究一下 xgboost 包及其 Python 接口,在寫做本書時,這個庫在許多數據集上的速度都比 scikit-learn 對梯度提高的實現要快(有時調參也更簡單)。
梯度提高決策樹是監督學習中最強大也最經常使用的模型之一。其主要缺點是須要仔細調參,並且訓練時間可能會比較長。與其餘基於樹的模型相似,這一算法不須要對數據進行縮放就能夠表現得很好,並且也適用於二元特徵與連續特徵同時存在的數據集。與其餘基於樹的模型相同,它也一般不適用於高維稀疏數據。(1是訓練時間慢,2是特徵選擇會浪費大量的有效特徵)
梯度提高樹模型的主要參數包括樹的數量 n_estimators 和學習率 learning_rate ,後者用於控制每棵樹對前一棵樹的錯誤的糾正強度。這兩個參數高度相關,由於 learning_rate 越低,就須要更多的樹來構建具備類似複雜度的模型。隨機森林的 n_estimators 值老是越大越好,但梯度提高不一樣,增大 n_estimators 會致使模型更加複雜,進而可能致使過擬合。一般的作法是根據時間和內存的預算選擇合適的 n_estimators ,而後對不一樣的learning_rate 進行遍歷。
另外一個重要參數是 max_depth (或 max_leaf_nodes ),用於下降每棵樹的複雜度。梯度提高模型的 max_depth 一般都設置得很小,通常不超過 5。