1. 什么是集成學習?
如果你隨機向幾千個人詢問一個復雜問題,然后匯總它們的答案。在許多情況下你會發現,這個匯總的回答比專家的答案還要好,這被稱為集體智慧,同樣,如果你聚合一組預測器的預測,得到的預測結果也比最好的單個預測器要好,這樣的一組預測器,我們稱為集成,也被稱為集成學習。集成學習可以用於分類問題集成,回歸問題集成,特征選取集成,異常點檢測集成等等,可以說所有的機器學習領域都可以看到集成學習的身影。
集成學習有兩個主要的問題需要解決:
- 如何得到若干個個體學習器
- 如何選擇一種結合策略,將這些個體學習器集合成一個強學習器
2. 集成學習之結合策略
2.1 個體學習器已知的情況下
blending就是將所有已知的個體學習器g(t)結合起來,發揮集體的智慧得到G強學習器 。值得注意的一點是這里的g(t)都是已知的。blending通常有三種形式:
- uniform:簡單地計算所有g(t)的平均值
- non-uniform:所有g(t)的線性組合
- conditional:所有g(t)的非線性組合
其中,uniform采用投票、求平均的形式更注重穩定性,而non-uniform和conditional追求的更復雜准確的模型,但存在過擬合的危險
2.2 個體學習器未知的情況下
blending是建立在所有g(t)已知的情況。那如果所有個體學習器g(t)未知的情況,對應的就是學習法,做法就是一邊學g(t),一邊將它們結合起來。學習法通常也有三種形式(與blending的三種形式一一對應):
- Bagging:通過bootstrap方法,得到不同g(t),計算所有g(t)的平均值
- AdaBoost:通過bootstrap方法,得到不同g(t),所有g(t)的線性組合
- Decision Tree:通過數據分割的形式得到不同的g(t),所有g(t)的非線性組合
除了這些基本的集成模型之外,我們還可以把某些模型結合起來得到新的集成模型,例如:
- Bagging與Decision Tree結合起來組成了Random Forest。Random Forest中的Decision Tree是比較“茂盛”的樹,即每個樹的g(t)都比較強一些。
- AdaBoost與Decision Tree結合組成了AdaBoost-DTree。AdaBoost-DTree的Decision Tree是比較“矮弱”的樹,即每個樹的g(t)都比較弱一些,由AdaBoost將所有弱弱的樹結合起來,讓綜合能力更強。
- GradientBoost與Decision Tree結合就構成了經典的算法GBDT。
2.3 集成學習優點
集成的核心是將所有的g(t)結合起來,融合到一起,即集體智慧的思想。這種做法之所以能得到很好的模型g(t),是因為集成具有兩個方面的優點:cure underfitting和cure overfitting。
-
集成 models 有助於防止欠擬合(underfitting)。它把所有比較弱的 結合起來,利用集體智慧來獲得比較好的模型 。集成就相當於是特征轉換,來獲得復雜的學習模型。
-
第二,集成 models 有助於防止過擬合(overfitting)。它把所有 進行組合,容易得到一個比較中庸的模型,類似於SVM的最大間隔一樣的效果,從而避免一些極端情況包括過擬合的發生。從這個角度來說,集成起到了正則化的效果。
由於集成具有這兩個方面的優點,所以在實際應用中集成 models 都有很好的表現
注:以上內容摘抄自:https://zhuanlan.zhihu.com/p/32412775,感謝博主分享
3. Bagging和Pasting
Bagging和Posting方法在訓練過程中,各基分類器之間無強依賴,可以並行進行訓練,其中很著名的算法之一是基於決策樹基分類器的隨機森林(random forest)。
為了讓基分類器之間相互獨立,將訓練集分為若干子集(當訓練樣本較少時,子集之間可能有重疊)。該方法更像是一個集體決策的過程,每個個體都進行單獨學習,學習的內容可以相同,可以不同,也可以部分重疊,但由於個體之間存在差異,最終做出的判斷不會完全一致,在最終做決策時,每個個體單獨做出判斷,再通過投票的方式做出最后的集體決策
Bagging和Posting的唯一區別在於對於同一個基分類器抽樣的樣本數據是否可以放回,bagging抽樣后的數據可以放回繼續抽樣,但posting抽樣后的數據不可以放回,換句話說,bagging和pasting都允許訓練實例在多個預測器中被多次采樣名單是只有bagging允許訓練實例被同一個預測器多次采樣
由於bagging和Posting是並行訓練機制,所以可以通過不同的cpu內核甚至是不同的服務器並行地訓練預測器和分類,這也正是bagging和pasting方法如此流行的原因之一,它們非常易於擴展
4. Boosting
Boosting方法訓練基分類器時采用串行的方式,各個基分類器之間有依賴
它的基本思想是將基分類器層層疊加,每一層在訓練的時候,對前一層基分類器分錯的樣本,給與更高的權重,測試時,根據各層分類器的結果的加權得到最終的結果,這涉及到兩組加權系數,將在Adaboost算法中詳細說明
Boosting的過程很類似於人類學習的過程,我們學習新知識的過程往往是迭代的,第一遍學習的時候,我們會記住一部分知識,但往往也會犯一些錯誤,對於這些錯誤,我們的印象會很深,在第二遍學習的時候,就會針對犯過的錯誤的只是加強學習,以減少類似的錯誤發生,不斷循環往復,直到犯錯誤的次數減少到很低的程度
4.1 AdaBoost
AdaBoost算法:
- (1) 初始化每個實例權重\(w^{(i)} = \frac{1}{m}\),m為樣本個數
- (2) 使用第一個預測器訓練,計算其誤差率\(r_1\) $$r_j = \frac{\sum_{i=1,j_{j}^{(i)}!=y^{(i)}}^mw^{i}}{\sum_{i=1}^mw^{(i)}}$$
- (3) 計算預測器的權重,預測器的准確率越高,其權重就越高,如果只是隨機猜測,則其權重接近於零。但是,如果大部分情況下它都是錯的(也就是准確率比隨機猜測還低),那么它的權重為負$$\alpha_j = \eta \log \frac{1-r_j}{r_j}$$,\(\eta為學習率超參數,默認為1\)
- (4) 對每個實例的權重進行更新$$w^{{i}} \leftarrow \begin{cases}
w^{(i)}&(y_j^{(i)} == y^{(i)})\
w^{(i)}\exp(a_j)& (y_j^{(i)} != y^{(i)})
\end{cases}$$ - (5) 將所有實例的權重歸一化(即除以\(\sum_{i=1}^mw^{(i)}\))
- (6) 使用更新后的權重訓練一個新的預測器,然后重復整個過程(計算新預測器的權重,更新實例權重,然后對另一個預測器進行訓練)
- (7) 當到達所需數量的預測器或得到完美的預測器時,算法停止
預測的時候,AdaBoost就是簡單地計算所有預測器的預測結果,並使用預測其權重對它們進行加權,最后,得到大多數加權投票的類別就是預測器給出的預測類別
4.2 梯度提升
另一個非常受歡迎的提升法是梯度提升(Gradient Boosting),跟AdaBoost一樣,梯度提升也是逐步在集成中添加預測器,每一個都對前序做出改正,不同之處在於,它不像Adaboost那樣在每個迭代中調整實例權重,而是讓新的預測器針對前一個預測器的殘差進行擬合
舉個簡單的回歸示例,使用決策樹作為基礎預測器(梯度提升當然也適用於回歸任務),這被稱為梯度樹提升或者梯度提升回歸樹(DBRT)
from sklearn.tree import DecisionTreeRegressor
tree_reg1 = DecisionTreeRegressor(max_depth=2)
tree_reg1.fit(x,y)
現在針對第一個預測器的殘差,訓練第二個DecisionTreeRegressor
y2 = y - tree_reg1.predict(x)
tree_reg2 = DecisionTreeRegressor(max_depth=2)
tree_reg2.fit(x,y2)
然后,針對第二個預測器的殘差,訓練第三個回歸器
y3 = y2 - tree_reg1.predict(x)
tree_reg2 = DecisionTreeRegressor(max_depth=2)
tree_reg2.fit(x,y3)
現在,有了一個包含三棵樹的集成,將所有樹的預測相加,從而對新實例進行預測相加,從而對新實例進行預測
y_pred = sum(tree.predict(x_new) for tree in (tree_reg1,tree_reg2,tree_reg3))
5. sklearn集成學習實踐
5.1 訓練隨機森林分類器
在<機器學習之決策樹原理和sklearn實踐>博文中為衛星數據訓練的決策樹基礎上訓練一個隨機森林分類器
需求:
- a.生產1000個訓練集子集,每個子集包含隨機挑選的100個實例,提示:使用sklearn的ShuffleSplit來實現
- b.使用之前得到的超參數,在每個子集上訓練一個決策樹,在測試集上評估這1000個決策樹。因為訓練集更小,所以這些決策樹的表現可能比第一個決策樹要差一點
- c.用每個測試集實例,生成1000個決策樹的預測,然后僅保留次數最頻繁的預測(可以使用Scipy的mode()函數),這樣你在測試集上獲得大多數投票的結果
- d.評估測試集上的這些預測,你得到的准確率應該比第一個模型更高
代碼實現:
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split,ShuffleSplit
dataset = make_moons(n_samples=10000,noise=0.4)
features,labels = dataset[0],dataset[1]
x_train,x_test = train_test_split(features,test_size=0.2,random_state=42)
y_train,y_test = train_test_split(labels,test_size=0.2,random_state=42)
train_subset = []
# 創建1000個訓練子集
ss = ShuffleSplit(n_splits=1000,test_size = 0.01,train_size=0.01,random_state=42)
for train_index,_ in ss.split(features):
train_subset.append((features[train_index],labels[train_index]))
# 使用x_train和y_train訓練一個決策時
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import accuracy_score
decisionTree = DecisionTreeClassifier(criterion='gini')
param_grid = {'max_leaf_nodes': [i for i in range(2,10)]}
gridSearchCV = GridSearchCV(decisionTree,param_grid=param_grid,cv=3,verbose=1)
gridSearchCV.fit(x_train,y_train)
y_prab = gridSearchCV.predict(x_test)
print('accuracy score:',accuracy_score(y_test,y_prab))
print(gridSearchCV.best_params_)
print(gridSearchCV.best_estimator_)
Fitting 3 folds for each of 8 candidates, totalling 24 fits
accuracy score: 0.8525
{'max_leaf_nodes': 9}
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
max_features=None, max_leaf_nodes=9, 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')
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done 24 out of 24 | elapsed: 0.0s finished
# 克隆1000個決策樹
from sklearn.base import clone
import numpy as np
estimators = [clone(gridSearchCV.best_estimator_) for _ in range(1000)]
#在測試集上評估這1000個決策樹
accuracy_score_array = []
for tree, (x_mini_train, y_mini_train) in zip(estimators, train_subset):
tree.fit(x_mini_train, y_mini_train)
y_predict = tree.predict(x_test)
accuracy_score_array.append(accuracy_score(y_test,y_predict))
print('avg accuracy_score:', np.mean(accuracy_score_array))
avg accuracy_score: 0.815643
# 用每個測試集實例,生成1000個決策樹的預測,然后僅保留次數最頻繁的預測
y_pred = np.zeros((len(estimators),len(x_test)),dtype=np.uint8)
for tree_index, tree in enumerate(estimators):
y_pred[tree_index] = tree.predict(x_test)
from scipy.stats import mode
y_pred_majority_votes, n_votes = mode(y_pred, axis=0)
print(accuracy_score(y_test, y_pred_majority_votes.reshape([-1])))
0.8605
5.1 RandomForestClassifier隨機森林分類器
通過4.1已經發現,隨機森林是決策樹的集成,通常用bagging(有時也可能是pasting)方法訓練,構成隨機森林分類器有兩種方法:
-
(1)先構建一個BaggingClassifier然后將結果傳輸到DecisionTreeClassifier
bag_clf = BaggingClassifier(
DecisionTreeClassifier(splitter='random',max_leaf_nodes=16),
n_estimators=500,max_samples=1.0,bootstrap=True,n_jobs=-1) -
(2) 使用RandomForestClassifier類,這種方法更方便,對決策樹更優化(同樣,對於回歸任務也有一個RandomForestRegresson類)
from sklearn.ensemble import RandomForestClassifier
rnd_clf = RandomForestClassifier(n_estimators=500,max_leaf_nodes=16,n_jobs=-1)
rnd_clf.fit(x_train,y_train)
y_pred_rf = rnd_clf.predict(x_test)
RandomForestClassifier具有DecisionTreeClassifier的所有超參數,已經BaggingClassifier的所有超參數,前者用來控制樹的生長,后者用來控制集成本身
隨機樹的生長引入了更多的隨機性:分裂節點時不再是搜索最好的特征,而是在一個隨機生成的子集里搜索最好的特征,這導致決策樹具有更大的隨機性,用更高的偏差換取更低的方差,總之,還是產生了一個整體性能更優的模型
重要特征:
單看一顆決策樹,重要的特征更可能出現在靠近根節點的位置,而不重要的特征通常出現在靠近葉節點的位置(甚至根本不出現),因此,通過計算一個特征在森林中所有樹上的平均深度,可以算出一個特征的重要程度
sklearn在訓練結束后會自動計算每個特征的重要性,通過變量feature_importances_可以訪問到這個結果
5.3 ExtraTreesClassifier極端隨機樹
隨機森林里單棵樹的生長過程中,每個節點在分裂時僅考慮到了一個隨機子集所包含的特征,如果我們對每個特征使用隨機閾值,而 不是搜索得出的最佳閾值(如常規決策樹),則可能讓決策樹生長得更加隨機,這種極端隨機得決策樹組成的森林,被稱為極端隨機樹集成。
同樣,它也是以更高的偏差換取最低的方差,極端隨機樹訓練起來比常規隨機森林要快得多,因為在每個節點上找到每個特征的最佳閾值是決策樹生長中最耗時的任務之一
- 通常來說,很難預先知道一個RandomForestClassifier是否比一個ExtraTreesClassifier更好或是更差,唯一的方法是兩種都嘗試一遍,然后使用交叉驗證(還需要使用網格搜索調整超參數)進行比較
參考資料: