Python遇見機器學習 ---- 集成學習 Ensemble Learning

綜述

“餘既滋蘭之九畹兮,又樹蕙之百畝”

本文采用編譯器:jupyter    

集成學習 (ensemble learning)通過構建並結合多個學習器來完成學習任務。下圖顯示出集成學習的一般結構,先產生一組"個體學習器",然後再通過某種方式將他們結合起來解決一個問題。

在日常生活中也充滿着集成學習的例子:比如“買東西找人推薦”“專家會診確定病情”等 。

個體學習器由常見的機器學習模型構成,採用投票方式決定最終結果,爲了達到“集成”的目的,個體學習器應具有差異性。

01 集成學習

import numpy as np
import matplotlib.pyplot as plt

from sklearn import datasets
​
X, y = datasets.make_moons(n_samples=500, noise=0.3, random_state=42)

plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()

 

from sklearn.model_selection import train_test_split
​
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

# 使用邏輯迴歸分類
from sklearn.linear_model import LogisticRegression
​
log_clf = LogisticRegression()
log_clf.fit(X_train, y_train)
log_clf.score(X_test, y_test)
# Out[5]:
# 0.86399999999999999

# 使用SVM方式分類
from sklearn.svm import SVC
​
svm_clf = SVC()
svm_clf.fit(X_train, y_train)
svm_clf.score(X_test, y_test)
# Out[6]:
# 0.88800000000000001

# 使用決策樹進行預測
from sklearn.tree import DecisionTreeClassifier
​
dt_clf = DecisionTreeClassifier()
dt_clf.fit(X_train, y_train)
dt_clf.score(X_test, y_test)
# Out[7]:
# 0.872

# 集成學習,少數服從多數過程
y_predict1 = log_clf.predict(X_test)
y_predict2 = svm_clf.predict(X_test)
y_predict3 = dt_clf.predict(X_test)

y_predict = np.array((y_predict1 + y_predict2 + y_predict3) >= 2, dtype='int')

y_predict[:10]
# Out[10]:
# array([1, 0, 0, 1, 1, 1, 0, 0, 0, 0])

from sklearn.metrics import accuracy_score
​
accuracy_score(y_test, y_predict)
# 可以看出分類準確率比三種單獨的方法要高
# Out[11]:
# 0.90400000000000003

使用Voting Classifier

from sklearn.ensemble import VotingClassifier
​
# 創建對象時,傳入估計器
voting_clf = VotingClassifier(estimators=[
    ('log_clf', LogisticRegression()),
    ('svm_clf', SVC()),
    ('dt_clf', DecisionTreeClassifier())
], voting='hard')

voting_clf.fit(X_train, y_train)
"""
Out[13]:
VotingClassifier(estimators=[('log_clf', LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
          intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
          penalty='l2', random_state=None, solver='liblinear', tol=0.0001,
          verbose=0, warm_start=False)), ('svm_clf...      min_weight_fraction_leaf=0.0, presort=False, random_state=None,
            splitter='best'))],
         flatten_transform=None, n_jobs=1, voting='hard', weights=None)
"""

voting_clf.score(X_test, y_test)
# Out[14]:
# 0.90400000000000003

 

02 Soft Voting

 

由於不同的學習機器確定結果所對應的概率不同,更合理的投票應當考慮權值,稱爲Soft Voting

對於下面的例子,如果不考慮權值,分類結果爲B,但更直觀的來看我們更願意將其歸入A類。

 

如果要設置集合的每一個模型都可以估計概率,滿足scikit-learn的寫法是加入predict_proba函數。

回顧之前介紹過的方法,看看都有哪些可以計算出預測概率:

 

1. 邏輯迴歸

邏輯迴歸模型本身就是基於概率模型的

 

2. k近鄰

kNN也是可以估計概率的,下圖的估計概率 = 2/3 = 66.7%

 

3. 決策樹

決策樹也是可以估計概率的(葉子節點中標記數量較多的點 / 葉子節點中總數據點個數)

 

4. 支持向量機

SVM可以通過一些特殊的方式計算出概率

 

import numpy as np
import matplotlib.pyplot as plt

from sklearn import datasets
​
X, y = datasets.make_moons(n_samples=500, noise=0.3, random_state=42)

plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()

 

from sklearn.model_selection import train_test_split
​
X_train, X_test, y_train, y_test = train_test_split(X, y,random_state=42)

Hard Voting Classifier

 

from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import VotingClassifier
​
voting_clf = VotingClassifier(estimators=[
    ('log_clf', LogisticRegression()),
    ('svm_clf', SVC()),
    ('dt_clf', DecisionTreeClassifier(random_state=666))],
                             voting='hard')

voting_clf.fit(X_train, y_train)
voting_clf.score(X_test, y_test)
# Out[9]:
# 0.89600000000000002

Soft Voting Classifier

 

# 需要設置SVM可以計算概率
voting_clf2 = VotingClassifier(estimators=[
    ('log_clf', LogisticRegression()),
    ('svm_clf', SVC(probability=True)),
    ('dt_clf', DecisionTreeClassifier(random_state=666))],
                             voting='soft')

voting_clf2.fit(X_train, y_train)
voting_clf2.score(X_test, y_test)
# Out[11]:
# 0.91200000000000003

雖然我們有很多機器學習方法,但是從集成的角度來看,仍然不夠多!

所以我們需要對每一種機器學習方法創建更多的子模型,爲了滿足子模型之間必須存在的差異性,我們讓每個子模型只看樣本數據的一部分。

 

比如:一共有500個樣本,每個子模型只看100個樣本數據,每個模型不需要太高的準確率。

假設每個子模型只有51%的準確率:

如果只有1個子模型,整體準確率:51%

如果有3個子模型,整體準確率:0.51^3+C_{3}^{2}\cdot 0.51^2\cdot 0.49 = 51.5\%

如果有500個子模型,整體準確率:\sum_{i=251}^{500}C_{500}^{i}\cdot 0.51^i\cdot 0.49^{500-i} = 65.6\%

如果每個模型的準確率是60%:\sum_{i=251}^{500}C_{500}^{i}\cdot 0.6^i\cdot 0.4^{500-i} = 99.99\%

 

03 Bagging和Pasting

接上一節,有兩種方式只看樣本數據的一部分:

取樣:放回取樣 Bagging       不放回取樣 Pasting

其中Bagging更常用(在統計學中放回取樣叫做 bootstrap)

import numpy as np
import matplotlib.pyplot as plt

from sklearn import datasets
​
X, y = datasets.make_moons(n_samples=500, noise=0.3, random_state=42)

plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()

from sklearn.model_selection import train_test_split
​
X_train, X_test, y_train, y_test = train_test_split(X, y)

使用Bagging

 

# 如果想要集成成百上千個子模型,首選方法是決策樹。因爲這種非參數學習模型更能產生差異較大的子模型
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier
​
# 設置500個子模型,每個子模型看100個數據,放回取樣
bagging_clf = BaggingClassifier(DecisionTreeClassifier(),
                               n_estimators=50, max_samples=100,
                               bootstrap=True)

bagging_clf.fit(X_train, y_train)
bagging_clf.score(X_test, y_test)
# Out[6]:
# 0.92000000000000004

bagging_clf2 = BaggingClassifier(DecisionTreeClassifier(),
                               n_estimators=5000, max_samples=100,
                               bootstrap=True)
bagging_clf2.fit(X_train, y_train)
bagging_clf2.score(X_test, y_test)
# 理論上,子模型數量越多,分類效果越好
# Out[7]:
# 0.93600000000000005

關於Bagging的更多討論

 

1. 放回取樣導致一部分樣本很有可能沒有取到,經過實驗,平均大約有37%的樣本沒有取到,我們就可以使用這些樣本來做測試 / 驗證。在scikit-learn中需要設置oob_score_參數。

2. Bagging的設計思路使得它極易進行並行化處理。參數 n_jobs

3. 當數據樣本特徵過多時(圖像識別領域),可以對特徵進行隨機採樣稱爲Random Subspaces

4. 即針對樣本,有針對特徵進行隨機採樣稱爲Random Patches。

04 oob和更多Bagging相關

 

import numpy as np
import matplotlib.pyplot as plt

from sklearn import datasets
​
X, y = datasets.make_moons(n_samples=500, noise=0.3, random_state=42)

plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()

 使用oob

 

from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier
​
# 指定記錄oob_score,使用沒有取到的數據做測試
bagging_clf = BaggingClassifier(DecisionTreeClassifier(),
                               n_estimators=500, max_samples=100,
                               bootstrap=True, oob_score=True)
bagging_clf.fit(X, y)
"""
Out[6]:
BaggingClassifier(base_estimator=DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=None,
            splitter='best'),
         bootstrap=True, bootstrap_features=False, max_features=1.0,
         max_samples=100, n_estimators=500, n_jobs=1, oob_score=True,
         random_state=None, verbose=0, warm_start=False)
"""

bagging_clf.oob_score_
# Out[7]:
# 0.91600000000000004

n_jobs

 

%%time
bagging_clf = BaggingClassifier(DecisionTreeClassifier(),
                               n_estimators=500, max_samples=100,
                               bootstrap=True, oob_score=True)
bagging_clf.fit(X, y)

"""
CPU times: user 608 ms, sys: 4.58 ms, total: 612 ms
Wall time: 611 ms
"""
%%time
# 傳入n_jobs使用計算機所有的核
bagging_clf = BaggingClassifier(DecisionTreeClassifier(),
                               n_estimators=500, max_samples=100,
                               bootstrap=True, oob_score=True, n_jobs=-1)
bagging_clf.fit(X, y)

"""
CPU times: user 203 ms, sys: 35.5 ms, total: 239 ms
Wall time: 699 ms
"""

bootstrap_features

 

# 對特徵進行隨機取樣,指定最大取樣特徵數(由於只有兩個特徵,每次只取一個是不太合理的)
# 把max_samples與n_estimators設置成相等的即實現了不對樣本隨機採樣
random_subspaces_clf = BaggingClassifier(DecisionTreeClassifier(),
                               n_estimators=500, max_samples=500,
                               bootstrap=True, oob_score=True, 
                               n_jobs=-1,
                               max_features=1, bootstrap_features=True)
random_subspaces_clf.fit(X, y)
random_subspaces_clf.oob_score_
# Out[11]:
# 0.84999999999999998

# 既對樣本進行放回採樣,又對特徵進行放回採樣
# 由多棵樹組成的決策模型就叫做隨機森林
random_patches_clf = BaggingClassifier(DecisionTreeClassifier(),
                               n_estimators=500, max_samples=100,
                               bootstrap=True, oob_score=True, 
                               n_jobs=-1,
                               max_features=1, bootstrap_features=True)
random_patches_clf.fit(X, y)
random_patches_clf.oob_score_
# Out[12]:
# 0.85199999999999998

05 隨機森林和Extra-Trees

 

隨機森林定義:

使用Bagging取樣方法,子模型爲決策樹,並且決策樹在節點劃分上使用隨機的特徵與閾值。

由於提供了額外的隨機性,所以抑制了過擬合即抑制了方差,但增加了偏差。實際使用中可能需要一定的實驗與判斷來選擇模型。因爲節點的劃分只用隨機的特徵與閾值,所以訓練速度較快。

import numpy as np
import matplotlib.pyplot as plt

from sklearn import datasets
​
X, y = datasets.make_moons(n_samples=500, noise=0.3, random_state=42)

plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()

 

隨機森林

 

from sklearn.ensemble import RandomForestClassifier
# 500棵樹的隨機森林
rf_clf = RandomForestClassifier(n_estimators=500, random_state=666, oob_score=True, n_jobs=-1)
rf_clf.fit(X, y)
"""
Out[5]:
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
            max_depth=None, max_features='auto', max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, n_estimators=500, n_jobs=-1,
            oob_score=True, random_state=666, verbose=0, warm_start=False)
"""
rf_clf.oob_score_
# Out[6]:
# 0.89600000000000002

# 設置每棵決策樹最多有幾個葉節點
rf_clf2 = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, random_state=666, oob_score=True, n_jobs=-1)
rf_clf2.fit(X, y)
rf_clf2.oob_score_
# Out[8]:
# 0.92000000000000004

使用Extra-Trees

 

from sklearn.ensemble import ExtraTreesClassifier
​
et_clf = ExtraTreesClassifier(n_estimators=500, bootstrap=True, oob_score=True, random_state=666)
et_clf.fit(X, y)
"""
Out[10]:
ExtraTreesClassifier(bootstrap=True, class_weight=None, criterion='gini',
           max_depth=None, max_features='auto', max_leaf_nodes=None,
           min_impurity_decrease=0.0, min_impurity_split=None,
           min_samples_leaf=1, min_samples_split=2,
           min_weight_fraction_leaf=0.0, n_estimators=500, n_jobs=1,
           oob_score=True, random_state=666, verbose=0, warm_start=False)
"""
et_clf.oob_score_
# Out[11]:
# 0.89200000000000002

 使用集成學習解決迴歸問題

# 使用方法和classifier相同,返回的是迴歸結果
from sklearn.ensemble import BaggingRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import ExtraTreesRegressor

 

06 另一種集成學習思路:Boosting

基本思路:集成多個子模型,每個子模型都在嘗試增強(Boosting)整體的效果。

每一個子模型都在盡力彌補上一個子模型所沒劃分出來的點,每個子模型對於每個點的權重也不相同。

Ada Boosting:

 

Gradient Boosting:

訓練一個模型m1,產生錯誤e1;

針對e1訓練第二個模型m2,產生錯誤e2;

針對e2訓練第二個模型m3,產生錯誤e3;

最終預測結果是:m1+m2+m3+...

Stacking

 

將多個模型的輸出作爲輸入再由另一個模型做出決定。

scikit-learn中沒有對應的接口

 

 

 

 

 

最後,如果有什麼疑問,歡迎和我微信交流。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章