天天看點

監督學習 | 內建學習 之Bagging、随機森林及Sklearn實作內建學習1. 投票分類器2. Bagging & Pasting3. 随機森林參考資料

文章目錄

  • 內建學習
  • 1. 投票分類器
    • 1.1 硬投票法
    • 1.2 軟投票法
  • 2. Bagging & Pasting
    • 2.1 包外評估
    • 2.2 Random Patches 和 随機子空間
  • 3. 随機森林
    • 3.1 極端随機樹
    • 3.2 特征重要性
  • 參考資料

相關文章:

機器學習 | 目錄

監督學習 | 決策樹原理及Python實作

監督學習 | 決策樹之Sklearn實作

監督學習 | 內建學習之AdaBoost原理及Slearn實作

內建學習

內建學習

(ensemble learning)通過建構并結合多個學習器來完成學習任務,有時也被稱為多分類器系統(multi-classifier system)、基于委員會的學習(committee-based learning)等。

下圖顯示出內建學習的一般結構:先産生一組“

個體學習器

”(individual learner),再用某種政策将它們結合起來,此時內建中隻包括同種類型的個體學習器,例如“決策樹內建”中全是決策樹,“神經網絡內建”中全是神經網絡,這樣的內建是“

同質的

”(homogeneous)。

同質集合

中的個體學習器亦稱“

基學習器

”(base learner),相應的學習算法稱為“

基學習算法

”(base learning algorithm)。

內建也可以包含不同類型的個體學習器,例如同時包含決策樹和神經網絡,這樣的內建是“

異質

”的(heterogenous)。

異質內建

中的個體學習器由不同的學習算法生成,這時不再有基學習算法;相應的,個體學習器一般不稱為基學習器,常稱為“

元件學習器

”(component learner)或直接稱個體學習器。

監督學習 | 內建學習 之Bagging、随機森林及Sklearn實作內建學習1. 投票分類器2. Bagging & Pasting3. 随機森林參考資料

圖1 內建學習示意圖

內建學習通過将多個學習器進行結合,常可獲得比單一學習器顯著優越的泛化性能。這對“弱學習器”(weak learner)尤為明顯,是以內建學習都是針對弱學習器進行的,而基學習器有時也被直接稱為

弱學習器

。但需要注意的是,雖然從理論上來說使用弱學習器內建足以獲得更好的性能,但在實踐中處于種種考慮,例如希望使用較少的個體學習器,或是重用關于常見學習器的一些經驗等,人們往往還會使用比較強的學習器。

弱學習器

:常指泛化能力略優于随機猜測的學習器;例如在二分類問題上精度率高于 50% 的分類器。

根據個體學習器的生成方式,目前的內建學習方法大緻可分為兩大類,即個體學習器間存在強依賴關系、必須串行生成的序列化方法(Boosting,降低偏差),以及個體學習器間不存在強依賴關系、可同時生成的并行化方法(Bagging、随機森林,降低方差)。[1]

表1 內建算法分類

個體學習器\聚合函數 平均法【回歸】 投票法【分類】 學習法
基學習器(同質) Bagging、随機森林 Bagging、随機森林 AdaBooting
元件學習器(異質) \ 投票分類器 \

1. 投票分類器

1.1 硬投票法

假設你已經訓練好了一些分類器,每個分類器的準确率約為 80% 。大概包括:一個邏輯回歸分類器、一個 SVM 分類器、一個随機森林分類器、一個 K-近鄰分類器,如下圖所示:

監督學習 | 內建學習 之Bagging、随機森林及Sklearn實作內建學習1. 投票分類器2. Bagging & Pasting3. 随機森林參考資料

圖2 訓練多重分類器

這時,要建立出一個更好的分類器,最簡單的辦法就是聚合每個分類器的預測,然後将得到票最多的結果最為預測類别。這種大多數投票分類器被稱為

硬投票分類器

監督學習 | 內建學習 之Bagging、随機森林及Sklearn實作內建學習1. 投票分類器2. Bagging & Pasting3. 随機森林參考資料

圖3 硬投票分類器預測

當預測器盡可能互相獨立時,內建方法的效果最優。獲得多種分類器的方法之一就是使用不同的算法進行訓練。這會增加它們犯不同類型錯誤的機會,進而提升內建的準确率。

Sklearn 實作:

Sklearn中的 VotingClassifier 類可以實作投票分類器。

from sklearn.ensemble import VotingClassifier

VotingClassifier(estimators, voting='hard', weights=None, n_jobs=None, flatten_transform=True)
           

參數設定:

estimator: slist of (string, estimator) tuples

Invoking the fit method on the VotingClassifier will fit clones of those original estimators that will be stored in the class attribute self.estimators_. An estimator can be set to None or 'drop' using set_params.
           

voting: str, {‘hard’, ‘soft’} (default=’hard’)

If ‘hard’, uses predicted class labels for majority rule voting. Else if ‘soft’, predicts the class label based on the argmax of the sums of the predicted probabilities, which is recommended for an ensemble of well-calibrated classifiers.
           

weights: array-like, shape (n_classifiers,), optional (default=

None

)

Sequence of weights (float or int) to weight the occurrences of predicted class labels (hard voting) or class probabilities before averaging (soft voting). Uses uniform weights if None.
           

n_jobs: int or None, optional (default=None)

The number of jobs to run in parallel for fit. None means 1 unless in a joblib.parallel_backend context. -1 means using all processors. See Glossary for more details.

【并行 CPU 數量,-1 為使用所有可用 CPU】
           

flatten_transform: bool, optional (default=True)

Affects shape of transform output only when voting=’soft’ If voting=’soft’ and flatten_transform=True, transform method returns matrix with shape (n_samples, n_classifiers * n_classes). If flatten_transform=False, it returns (n_classifiers, n_samples, n_classes).
           
# 1. 導入資料:
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_moons

X, y = make_moons(n_samples=500, noise=0.30, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

# 2. 訓練個體預測器:
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC

log_clf = LogisticRegression(solver="liblinear", random_state=42)
rnd_clf = RandomForestClassifier(n_estimators=10, random_state=42)
svm_clf = SVC(gamma="auto", random_state=42)


# 3. 導入硬投票分類器并拟合:
from sklearn.ensemble import VotingClassifier

voting_clf = VotingClassifier(
    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
    voting='hard')
voting_clf.fit(X_train, y_train)

# 4. 看看每個分類器在測試集上的準确率,可以看到投票分類器略勝于所有個體分類器。
from sklearn.metrics import accuracy_score

for clf in (log_clf, rnd_clf, svm_clf, voting_clf):
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)
    print(clf.__class__.__name__, accuracy_score(y_test, y_pred))
           
LogisticRegression 0.864
RandomForestClassifier 0.872
SVC 0.888
VotingClassifier 0.896
           

1.2 軟投票法

如果所有分類器都能夠估算出類别的機率(即有 predict_proba() 方法),那麼可以将機率在所有單個分類器傻姑娘平均,然後将平均機率最高的類别作為預測,這被稱為

軟投票法

。通常來說,它比硬投票法的表現更優,因為它給予那些高度自信的投票更高的權重。

Sklearn 實作:

from sklearn.ensemble import VotingClassifier

voting_clf = VotingClassifier(
    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
    voting='soft')
voting_clf.fit(X_train, y_train)
           

注意!需要確定所有分類器都可以估算出機率。預設情況下,SVC 類是不行的,是以需要将其超參數 probability 設為 True(這會導緻 SVC 使用交叉驗證來估算類别機率,減慢訓練速度)。

# 1. 導入資料
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_moons

X, y = make_moons(n_samples=500, noise=0.30, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

# 2. 訓練個體分類器:
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC

log_clf = LogisticRegression(solver="liblinear", random_state=42)
rnd_clf = RandomForestClassifier(n_estimators=10, random_state=42)
svm_clf = SVC(gamma="auto", probability=True, random_state=42)

# 3. 導入軟投票分類器并拟合:
voting_clf = VotingClassifier(
    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
    voting='soft')
voting_clf.fit(X_train, y_train)

from sklearn.metrics import accuracy_score

for clf in (log_clf, rnd_clf, svm_clf, voting_clf):
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)
    print(clf.__class__.__name__, accuracy_score(y_test, y_pred))
           
LogisticRegression 0.864
RandomForestClassifier 0.872
SVC 0.888
VotingClassifier 0.912
           

可以看見,軟投票器的準确率從硬投票器的 89.6% 提升到了 91.2%。

2. Bagging & Pasting

旗面提到,獲得不同種類分類器的方法之一是使用不同的訓練算法。還有另一種方法是每個個體學習器使用的算法相同(基學習器),但是在不同的訓練集随機子集上進行訓練。

  • 采樣時如果将

    樣本放回

    ,這種方法叫作

    bagging

    (bootstrap aggregating,自舉彙聚法)。
  • 采樣時如果

    樣本不放回

    ,這種方法叫作

    pasting

換句話說,bagging 和 pasting 都允許訓練執行個體在多個基學習器中被采樣,但是隻有 bagging 允許訓練執行個體被同一個基學習器多次采樣。

采樣過程和訓練過程如下圖所示:

監督學習 | 內建學習 之Bagging、随機森林及Sklearn實作內建學習1. 投票分類器2. Bagging & Pasting3. 随機森林參考資料

圖4 pasting/bagging 訓練集采集和訓練

一旦預測器訓練完成,內建就可以通過簡單地聚合所有預測器的預測,來對新執行個體做出預測。聚合函數通常是

統計法

(即硬投票法)用于

分類

,或是

平均法

用于

回歸

;在 Sklearn 中,如果基學習器能夠估算類别機率(SVM 需添加超參數 probability=True),則将使用軟投票法。

每個基學習器單獨的偏差都高于原始訓練集上訓練的偏差,但是通過聚合,同時降低了偏差和方法。總的來說,最終結果是,于直接在原始訓練集上訓練的單個基學習器相比,內建的偏差相近,但是方差更低。

如圖 4 所示,可以通過不同的 CPU 核心甚至是不同的伺服器,并行地訓練集基學習器。類似地,預測也可以并行。這正是 bagging 和 pasting 方法流行的原因之一,它們非常易于拓展。

Slearn實作:

Sklearn 提供了一個簡單的 API,可用 BaggingClassifier 類進行 bagging 和 pasting (或 BaggingRegressor 用于回歸)。

from sklearn.ensemble import BaggingClassifier

BaggingClassifier(base_estimator=None, n_estimators=10, max_samples=1.0, max_features=1.0, bootstrap=True, bootstrap_features=False, oob_score=False, warm_start=False, n_jobs=None, random_state=None, verbose=0)[source]¶
           

參數設定:

base_estimator: 基學習器

n_estimators: 基學習器數量

max_samples: 0.0~1.0,每個基學習器的訓練樣本量 【對樣本進行抽樣】

bootstrap: True,預設 bagging,想要使用 pasting 可以設定為 False 【抽樣樣本是否放回】

max_features:int or float, optional (default=1.0) 【對特征進行抽樣】(2.2)

bootstrap_features:False,預設 bagging,想要使用 pasting 可以設定為 False 【抽樣特征是否放回】(2.2)

n_jobs: CPU 核數,-1 為使用所有核心

oob_score: False,包外評估(2.1)

如果基學習器能夠估算類别機率(SVM 需添加超參數 probability=True),則将使用軟投票法。

下面使用決策樹作為基學習器,進行 Bagging 內建學習:

# 導入資料
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_moons

X, y = make_moons(n_samples=500, noise=0.30, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

# 以 500 個決策樹作為基學習器,建立并拟合 Bagging 內建學習器
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier

bag_clf = BaggingClassifier(
    DecisionTreeClassifier(random_state=42), 
    n_estimators=500,
    max_samples=100,
    bootstrap=True, 
    n_jobs=-1, 
    random_state=42)
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)

from sklearn.metrics import accuracy_score
print(accuracy_score(y_test, y_pred))
           
0.904
           

可以看到,使用決策樹作為基學習器的 Bagging 內建算法(随機森林)的準确率與投票分類器相仿,我們來比較一下同單一的決策樹相比,內建算法的準确率和邊界:

tree_clf = DecisionTreeClassifier(random_state=42)
tree_clf.fit(X_train, y_train)
y_pred_tree = tree_clf.predict(X_test)
print(accuracy_score(y_test, y_pred_tree))
           
0.856
           
from matplotlib.colors import ListedColormap

def plot_decision_boundary(clf, X, y, axes=[-1.5, 2.5, -1, 1.5], alpha=0.5, contour=True):
    x1s = np.linspace(axes[0], axes[1], 100)
    x2s = np.linspace(axes[2], axes[3], 100)
    x1, x2 = np.meshgrid(x1s, x2s)
    X_new = np.c_[x1.ravel(), x2.ravel()]
    y_pred = clf.predict(X_new).reshape(x1.shape)
    custom_cmap = ListedColormap(['#fafab0','#9898ff','#a0faa0'])
    plt.contourf(x1, x2, y_pred, alpha=0.3, cmap=custom_cmap)
    if contour:
        custom_cmap2 = ListedColormap(['#7d7d58','#4c4c7f','#507d50'])
        plt.contour(x1, x2, y_pred, cmap=custom_cmap2, alpha=0.8)
    plt.plot(X[:, 0][y==0], X[:, 1][y==0], "yo", alpha=alpha)
    plt.plot(X[:, 0][y==1], X[:, 1][y==1], "bs", alpha=alpha)
    plt.axis(axes)
    plt.xlabel(r"$x_1$", fontsize=18)
    plt.ylabel(r"$x_2$", fontsize=18, rotation=0)
           
import matplotlib.pyplot as plt
plt.figure(figsize=(11,4))
plt.subplot(121)
plot_decision_boundary(tree_clf, X, y)
plt.title("Decision Tree", fontsize=14)
plt.subplot(122)
plot_decision_boundary(bag_clf, X, y)
plt.title("Decision Trees with Bagging", fontsize=14)
plt.show()
           
監督學習 | 內建學習 之Bagging、随機森林及Sklearn實作內建學習1. 投票分類器2. Bagging & Pasting3. 随機森林參考資料

圖5 單個決策樹與 500 個決策樹的 bagging 內建對比

圖 5 比較了兩種決策邊界,一個是單個的決策樹,一個是由 500 個決策樹組成的 bagging 內建,均在衛星資料集上訓練完成。可以看出,內建預測的泛化效果很可能比單獨的決策樹要好一些;二者偏差相近,但是內建的方差更小(兩邊訓練集上的錯誤數量差不多,但是內建的決策邊界更規則)。

2.1 包外評估

對于任意給定的基學習器,使用 bagging,有些執行個體可能會被采樣多次,而有些執行個體則可能根本不被采用。BaggingClassifier 預設采樣 m 個訓練執行個體,然後放回樣本(bootstrap=True),m 是訓練集的大小。這意味着對每個預測器來說,平均隻對 63% 的訓練執行個體進行采樣。剩餘 37% 未被采樣的訓練執行個體稱為

包外

(oob)執行個體。注意,對于所有基學習器來說,這是不一樣的 37%。

随着 m 增長,這個比率接近 1 − e x p ( − 1 ) ≈ 63.212 1-exp(-1) \approx 63.212% 1−exp(−1)≈63.212

既然基學習器在訓練的時候從未見過這些包外執行個體,正好可以用這些執行個體進行評估,進而不需要單獨的驗證集或是交叉驗證。将每個訓練器在其包外執行個體上的評估結果進行平均,就可以得到對內建的評估。

Sklearn實作:

在 Sklearn 中,建立 BaggingClassifier 時,設定

oob_score=True

,就可以在訓練結束後自動進行包外評估。

# 通過變量 oob_score_ 獲得最終評估分數
bag_clf = BaggingClassifier(
    DecisionTreeClassifier(random_state=42), 
    n_estimators=500,
    oob_score=True,
    bootstrap=True, 
    n_jobs=-1, 
    random_state=42)
bag_clf.fit(X_train, y_train)
bag_clf.oob_score_
           
0.8986666666666666
           

根據包外評估結果,這個 BaggingClassifier 分類器很可能在測試集上達到約 89.87%的準确率,通過下面的驗證,我們得到在測試集上的準确率為 91.2%,與上面結果非常接近。

from sklearn.metrics import accuracy_score
y_pred = bag_clf.predict(X_test)
print(accuracy_score(y_test, y_pred))
           
0.912
           

每個訓練執行個體的包外決策函數也可以通過變量

oob_decision_function_

獲得。本例中(基學習器具備 predict_proba() 方法),決策函數傳回的是每個執行個體的類别機率。例如,包外評估估計,第二個訓練執行個體有 60.6% 的機率屬于正類(以及 39.4% 的機率屬于負類):

array([[0.32352941, 0.67647059],
       [0.35625   , 0.64375   ],
       [1.        , 0.        ],
       [0.        , 1.        ],
       [0.        , 1.        ],
       [0.06145251, 0.93854749],
       [0.35465116, 0.64534884],
       [0.01142857, 0.98857143],
       [0.98930481, 0.01069519],
       [0.97409326, 0.02590674]])
           

2.2 Random Patches 和 随機子空間

BaggingClassifier 也支援對特稱進行抽樣(之前是對資料進行抽樣),這通過兩個超參數決定:“

max_samples

”和“

bootstrap_features

”。它們的工作方式跟 max_samples 和 bootstrap 相同,隻是抽樣對象不再是執行個體,而是特征。是以,每個基學習器将用輸入特征的随機子集進行訓練。

這對于處理高維輸入(例如圖像)特别有用。對訓練執行個體和特征都進行抽樣,這被稱為

Random Patches

方法。

而保留所有訓練執行個體(即 bootstrap=Flase 并且 max_sample=1.0)但是對特征進行抽樣(即 bootstrap_features=True 并且/或 max_features<1.0),這被稱為

随機子空間法

對特征抽樣給基學習器帶來更大多多樣性,是以以略高一點的偏差換取來更低的方差。

3. 随機森林

随機森林是決策樹(無剪枝)的內建,随機森林裡單顆樹的生長過程中,每個節點在分裂時僅考慮一個随機子集包含的特征。通常用 bagging (有時也可能是 pasting)方法訓練(如2. Bagging & Pasting 一樣)。訓練集大小通過 max_samples 來設定。除了先建構一個 BaggingClassifier 然後将結果傳輸到 DecisionTreeClassifier,還有一種方法就是使用

RandomForestClassifier

類,這種方法更友善,對決策樹更優化(同樣,對于回歸任務也有一個

RadomForestRegressor

類)。

Sklearn 實作:

from sklearn.ensemble import RandomForestClassifier

RandomForestClassifier(n_estimators=100, criterion='gini', max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features='auto', max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False, class_weight=None, ccp_alpha=0.0)
           

參數設定:

除了少量例外,RandomForestClassifier 具有 DecisionTreeClassifier 以及 BaggingClassifier 的所有超參數,前者用來控制樹的生長,後者用來控制內建本身。

少量例外:沒有splitter(強制為 random),沒有 presort(強制為 False),沒有 max_samples(強制為1.0),沒有 base_estimator(強制為 DecisionTreeClassifier 與給定超參數)
# BaggingClassifier 實作

from sklearn.datasets import make_moons

X, y = make_moons(n_samples=500, noise=0.30, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

bag_clf = BaggingClassifier(
    DecisionTreeClassifier(splitter="random", max_leaf_nodes=16, random_state=42),
    n_estimators=500, max_samples=1.0, bootstrap=True, n_jobs=-1, random_state=42)

bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)
           
# RandomForestClassifier 實作

from sklearn.ensemble import RandomForestClassifier

rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, n_jobs=-1, random_state=42)
rnd_clf.fit(X_train, y_train)

y_pred_rf = rnd_clf.predict(X_test)
           
# 比較兩種方法的差距

np.sum(y_pred == y_pred_rf) / len(y_pred)  # almost identical predictions
           
0.976
           

3.1 極端随機樹

随機森林裡單顆樹的生長過程中,每個節點在分裂時僅考慮一個随機子集包含的特征。如果我們對每個特征使用随機門檻值,而不是搜尋得出的最佳門檻值(如正常決策樹),則可能讓決策樹生長得更加随機。

這種極端随機的決策樹組成的森林,被稱為

極端随機樹內建

(Extra-Trees)。同樣,它也是以更高的偏差換取了更低的方差極端随機樹訓練起來比正常随機森林要快很多,因為在每個節點上找到每個特征的最佳門檻值時決策樹生長中最耗時的任務之一。

使用 Sklearn 的

ExtraTreesClassifier

的 API 與 RandomForestClassifier 相同。同理,

ExtraTreesRegressor

的 API 與 RandomForestRegressor 的API 也相同。

通常來說,很難預先知道 RandomForestClassifier 與 ExtraTreesClassifier 誰好誰差,唯一的方法就是兩種都嘗試一遍,然後使用交叉驗證(還需要使用網絡搜尋調超參數)進行比較。

3.2 特征重要性

最後,如果檢視單個決策樹會發現,重要的特征更可能出現在靠近跟節點的位置,而不重要的特征通常出現在葉節點的位置(甚至根本不出現)。是以,通過計算一個特征在森林所有樹上的平均深度,可以估算出一個特征的重要程度。Sklearn 在訓練結束後自動計算每個特征的重要性。通過變量

feature_importances_

可以通路到這個計算結果。

下面在鸢尾花資料集上訓練了一個 RandomForestClassifier,并輸出了各個特征的重要性。看起來最重要的特征是花瓣長度(44%)和寬度(42%),而花萼的長度和寬度則相對不那麼重要(分别是 11%和 2%)

from sklearn.datasets import load_iris
iris=load_iris()

from sklearn.ensemble import RandomForestClassifier
rnd_clf=RandomForestClassifier(n_estimators=500, n_jobs=-1)
rnd_clf.fit(iris['data'], iris['target'])

for name, score in zip(iris['feature_names'], rnd_clf.feature_importances_):
    print(name,score)
           
sepal length (cm) 0.11051994534871025
sepal width (cm) 0.02132805477543799
petal length (cm) 0.41963589720955286
petal width (cm) 0.44851610266629877
           

是以,如果想要快速了解什麼是真正的特征,随機森領是一個非常便利的方法,特别是當你需要執行特征選擇的時候。[3]

參考資料

[1] 周志華. 機器學習[M]. 北京: 清華大學出版社, 2016: 171-172.

[2] Aurelien Geron, 王靜源, 賈玮, 邊蕤, 邱俊濤. 機器學習實戰:基于 Scikit-Learn 和 TensorFlow[M]. 北京: 機械工業出版社, 2018: 165-168.

[3] Aurelien Geron, 王靜源, 賈玮, 邊蕤, 邱俊濤. 機器學習實戰:基于 Scikit-Learn 和 TensorFlow[M]. 北京: 機械工業出版社, 2018: 169-174.

繼續閱讀