決策樹:
使用決策樹算法,我們從樹根開始,基於可獲得最大信息增益(information gain,IG)的特征來對數據進行划分,我們將在下一節詳細介紹信息增益的概念。
通過迭代處理,在每個子節點上重復此划分過程,直到葉子節點。這意味着在每一個節點處,所有的樣本都屬於同一類別。
在實際應用中,這可能會導致生成一棵深度很大且擁有眾多節點的樹,這樣容易產生過擬合問題,由此,我們一般通過對樹進行“剪枝”來限定樹的最大深度。
最大化信息增益——獲知盡可能准確的結果
為了在可獲得最大信息增益的特征處進行節點划分,需要定義一個可通過樹學習算法進行優化的目標函數。
在此,目標函數能夠在每次划分時實現對信息增益的最大化。
信息增益只不過是父節點的不純度與所有子節點不純度總和之差——子節點的不純度越低,信息增益越大。
然而,出於簡化和縮小組合搜索空間的考慮,大多數(包括scikit-learn)庫中實現的都是二叉決策樹。
這意味着每個父節點被划分為兩個子節點:Dleft和Dright。
就目前來說,二叉決策樹中常用的三個不純度衡量標准或划分標准分別是:
基尼系數(Gini index,IG)、熵(entropy,IH),以及誤分類率(classification error,IE)。
熵:
如果某一節點中所有的樣本都屬於同一類別,則其熵為0,當樣本以相同的比例分屬於不同的類時,熵的值最大。
對二類別分類來說,當p(i=1|t)=1或p(i=0|t)=0時,熵為0,如果類別均勻分布,即p(i=1|t)=0.5且p(i=1|t)=0.5時,熵為1。
在決策樹中熵的准則就是使得互信息最大化。
基尼系數
可以理解為降低誤分類可能性的標准
與熵類似,當所有類別是等比例分布時,基尼系數的值最大
# 在實踐中,基尼系數與熵通常會生成非常類似的結果,並不值得花費大量時間使用不純度標准評估樹的好壞,而通常嘗試使用不同的剪枝算法。
誤分類率:
這是一個對於剪枝方法很有用的准則,但是不建議用於決策樹的構建過程,因為它對節點中各類別樣本數量的變動不敏感。
使用誤分類率得到的信息增益都是相同,在使用基尼系數時,與划分A(IGG=0.125)相比,更傾向於使用B(IGG=0.16)的划分,
因為這樣子節點處的類別純度相對更高
熵的純度系統最高
那么你到底應該使用基尼不純度還是信息熵呢?其實,大多數情況下,它們並沒有什么大的不同,產生的樹都很相似。
基尼不純度的計算速度略微快一些,所以它是個不錯的默認選擇。
它們的不同在於,基尼不純度傾向於從樹枝中分裂出最常見的類別,而信息熵則傾向於生產更平衡的樹。
決策樹可以通過將特征空間進行矩形划分的方式來構建復雜的決策邊界。然而,必須注意:深度越大的決策樹,決策邊界也就越復雜,因而很容易產生過擬合現象。
scikit-learn一個吸引人的功能就是,它允許將訓練后得到的決策樹導出為.dot格式的文件,這使得我們可以使用GraphViz程序進行可視化處理。
通過觀察GraphViz創建的圖像,可以很好地回溯決策樹在訓練數據集上對各節點進行划分的過程。
from sklearn.tree import export_graphviz
export_graphviz(
tree_clf,
out_file=image_path("iris_tree.dot"),
feature_names=iris.feature_names[2:],
class_names=iris.target_names,
rounded=True,
filled=True
)
dot -Tpng iris_tree.dot -o iris_tree.png
決策樹的眾多特性之一就是, 它不需要太多的數據預處理, 尤其是不需要進行特征的縮放或者歸一化。
估計分類概率:
決策樹還可以估計某個實例屬於特定類 k 的概率:首先遍歷樹來查找此實例的葉節點,然后它返回此節點中類 k 的訓練實例的比例。
CART 訓練算法:
Scikit-Learn 用分裂回歸樹(Classification And Regression Tree,簡稱 CART)算法訓練決策樹(也叫“增長樹”)。
這種算法思想真的非常簡單:首先使用單個特征 k 和閾值 (例如,“花瓣長度 ≤2.45cm ”)將訓練集分成兩個子集。
它如何選擇 k 和 呢?它尋找到能夠產生最純粹的子集一對 ,然后通過子集大小加權計算。
當它成功的將訓練集分成兩部分之后, 它將會繼續使用相同的遞歸式邏輯繼續的分割子集,然后是子集的子集。
當達到預定的最大深度之后將會停止分裂(由 max_depth 超參數決定),或者是它找不到可以繼續降低不純度的分裂方法的時候。
幾個其他超參數(之后介紹)控制了其他的停止生長條件。
CART 算法是一種貪婪算法:它貪婪地搜索最高級別的最佳分割方式,然后在每個深度重復該過程。
它不檢查分割是否能夠在幾個級別中的全部分割可能中找到最佳方法。貪婪算法通常會產生一個相當好的解決方法,但它不保證這是全局中的最佳解決方案。
而不幸的是,尋找最優樹是一個已知的NP完全問題:[1]需要的時間是O(exp(m)),所以即使是很小的訓練集,也相當棘手。
這就是為什么我們必須接受一個“相當不錯”的解。
計算復雜度:
進行預測需要從根到葉遍歷決策樹。通常來說,決策樹大致平衡,因此遍歷決策樹需要經歷大約O(log2(m))個節點。(注:log2是以2為底的對數。
等於log2(m)=log(m)/log(2)。)而每個節點只需要檢查一個特征值,所以總體預測復雜度也只是O(log2(m)),與特征數量無關。
如此,即便是處理大型數據集,預測也很快。
但是,訓練時在每一個節點,算法都需要在所有樣本上比較所有特征(如果設置了max_features會少一些)。這導致訓練的復雜度為O(n×m log(m))。
對於小型訓練集(幾千個實例以內),Scikit-Learn可以通過對數據預處理(設置presort=True)來加快訓練,
但是對於較大訓練集而言,可能會減慢訓練的速度。
正則化超參數:
決策樹幾乎不對訓練數據做任何假設(於此相反的是線性回歸等模型,這類模型通常會假設數據是符合線性關系的)。
如果不添加約束,樹結構模型通常將根據訓練數據調整自己,使自身能夠很好的擬合數據,而這種情況下大多數會導致模型過擬合。
這一類的模型通常會被稱為非參數模型,這不是因為它沒有任何參數(通常也有很多),而是因為在訓練之前沒有確定參數的具體數量,
所以模型結構可以根據數據的特性自由生長。
與此相反的是,像線性回歸這樣的參數模型有事先設定好的參數數量,所以自由度是受限的,這就減少了過擬合的風險(但是增加了欠擬合的風險)。
DecisionTreeClassifier 類還有一些其他的參數用於限制樹模型的形狀:
min_samples_split (節點在被分裂之前必須具有的最小樣本數)
min_samples_leaf (葉節點必須具有的最小樣本數)
min_weight_fraction_leaf (和 min_samples_leaf 相同,但表示為加權總數的一小部分實例)
max_leaf_nodes (葉節點的最大數量)
max_features (在每個節點被評估是否分裂的時候,具有的最大特征數量)。
增加 min_* hyperparameters 或者減少 max_* hyperparameters 會使模型正則化。
一些其他算法的工作原理是在沒有任何約束條件下訓練決策樹模型,讓模型自由生長,然后再對不需要的節點進行剪枝。
當一個節點的全部子節點都是葉節點時,如果它對純度的提升不具有統計學意義,我們就認為這個分支是不必要的。
標准的假設檢驗,例如卡方檢測,通常會被用於評估一個概率值 -- 即改進是否純粹是偶然性的結果(也叫原假設)。
如果 p 值比給定的閾值更高(通常設定為 5%,也就是 95% 置信度,通過超參數設置),那么節點就被認為是非必要的,它的子節點會被刪除。
這種剪枝方式將會一直進行,直到所有的非必要節點都被刪光。
回歸:
決策樹也能夠執行回歸任務,讓我們使用 Scikit-Learn 的 DecisionTreeRegressor 類構建一個回歸樹,
讓我們用 max_depth = 2 在具有噪聲的二次項數據集上進行訓練。
from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor(max_depth=2)
tree_reg.fit(X, y)
這棵樹看起來非常類似於你之前建立的分類樹,它的主要區別在於,它不是預測每個節點中的樣本所屬的分類,而是預測一個具體的數值。
CART 算法的工作方式與之前處理分類模型基本一樣,不同之處在於,現在不再以最小化不純度的方式分割訓練集,而是試圖以最小化 MSE 的方式分割訓練集。
和處理分類任務時一樣,決策樹在處理回歸問題的時候也容易過擬合。
不穩定性:
決策樹很喜歡設定正交化的決策邊界,(所有邊界都是和某一個軸相垂直的),這使得它對訓練數據集的旋轉很敏感。
解決這個難題的一種方式是使用 PCA 主成分分析(第八章),這樣通常能使訓練結果變得更好一些。
更加通俗的講,決策時的主要問題是它對訓練數據的微小變化非常敏感。
通過隨機森林將弱分類器集成為強分類器:
由於具備好的分類能力、可擴展性、易用性等特點,隨機森林(random forest)過去十年間在機器學習應用領域獲得了廣泛的關注。
直觀上,隨機森林可以視為多棵決策樹的集成。
集成學習的基本理念就是將弱分類器集成為魯棒性更強的模型,即一個能力更強的分類器,集成后具備更好的泛化誤差,不易產生過擬合現象。
隨機森林算法可以概括為四個簡單的步驟:
1)使用bootstrap抽樣方法隨機選擇n個樣本用於訓練(從訓練集中隨機可重復地選擇n個樣本)。
2)使用第1)步選定的樣本構造一棵決策樹。節點划分規則如下:
(1)不重復地隨機選擇d個特征;
(2)根據目標函數的要求,如最大化信息增益,使用選定的特征對節點進行划分。
3)重復上述過程1~2000次。
4)匯總每棵決策樹的類標進行多數投票(majority vote)。
雖然隨機森林沒有決策樹那樣良好的可解釋性,但其顯著的優勢在於不必擔心超參值的選擇。
我們通常不需要對隨機森林進行剪枝,因為相對於單棵決策樹來說,集成模型對噪聲的魯棒性更好。
在實踐中,我們真正需要關心的參數是為構建隨機森林所需的決策樹數量(步驟3))。
通常情況下,決策樹的數量越多,隨機森林整體的分類表現就越好,但這同時也相應地增加了計算成本。
盡管在實踐中不常見,但是隨機森林中可優化的其他超參分別是:bootstrap抽樣的數量(步驟1))以及在節點划分中使用的特征數量(步驟(2))
通過選擇bootstrap抽樣中樣本數量n,我們可以控制隨機森林的偏差與方差權衡的問題。
如果n的值較大,就降低了隨機性,由此更可能導致隨機森林的過擬合。反之,我們可以基於模型的性能,通過選擇較小的n值來降低過擬合。
包括scikit-learn中RandomForestClassifier在內的大多數對隨機森林的實現中,bootstrap抽樣的數量一般與原始訓練集中樣本的數量相同,
因為這樣在折中偏差與方差方面一般會有一個好的均衡結果。
而對於在每次節點划分中用到的特征數量m,我們選擇一個比訓練集中特征總量小的值。
在scikit-learn及其他程序實現中常用的默認值一般是開方,其中m是訓練集中特征的總量。
forest = RandomForestClassifier(criterion='entropy',n_estimators=10,random_state=1,n_jobs=2)
或:
bag_clf = BaggingClassifier(DecisionTreeClassifier(splitter="random", max_leaf_nodes=16),n_estimators=500, max_samples=1.0, bootstrap=True,
n_jobs=-1)
集成學習——組合不同的模型:
構建一組分類器的集合,使得整體分類效果優於其中任意一個單獨的分類器。
集成學習:
集成方法(ensemble method)的目標是:將不同的分類器組合成為一個元分類器,與包含於其中的單個分類器相比,元分類器具有更好的泛化性能。
例如:假設我們收集到了10位專家的預測結果,集成方法允許我們以一定策略將這10位專家的預測進行組合,與每位專家單獨的預測相比,它具有更好的准確性和魯棒性。
多數投票(majority voting)原則:
將大多數分類器預測的結果作為最終類標,也就是說,將得票率超過50%的結果作為類標。
嚴格來說,多數投票僅用於二類別分類情形。不過,很容易將多數投票原則推廣到多類別分類,也稱作簡單多數票法(plurality voting)。
基於訓練集,我們首先訓練m個不同的成員分類器(C1,…,Cm)。在多數投票原則下,可集成不同的分類算法,如決策樹、支持向量機、邏輯斯諦回歸等。
此外,我們也可以使用相同的成員分類算法擬合不同的訓練子集。這種方法典型的例子就是隨機森林算法,它組合了不同的決策樹分類器。
想要通過簡單的多數投票原則對類標進行預測,我們要匯總所有分類器Cj的預測類標,並選出得票率最高的類標:
為了說明集成方法的效果為何好於單個成員分類器,我們借用下組合學中的概念。
假定每個分類器都是獨立的,且出錯率之間是不相關的。基於這些假設,我們可以將成員分類器集成后的出錯概率簡單地表示為二項分布的概率密度函數
在滿足所有假設的條件下,集成后的出錯率(0.034)遠遠小於單個分類器的出錯率(0.25)。
請注意,在此演示示例中,當集成分類中分類器個數n為偶數時,若預測結果為五五分,我們則將其以錯誤對待,不過僅有一半的可能會出現這種情況。
為了比較成員分類器在不同出錯率的情況下與集成分類器出錯率的差異,我們用Python實現其概率密度函數:
def ensemble_error(n_classifier,error):
k_start = math.ceil(n_classifier/2.0)
probs = [comb(n_classifier,k)*error**k *(1-error)**(n_classifier-k)
for k in range(k_start,n_classifier+1)]
return sum(probs)
畫圖可知,當成員分類器出錯率低於隨機猜測時(ε<0.5),集成分類器的出錯率要低於單個分類器。
硬投票分類器只是統計每個分類器的投票,然后挑選出得票最多的類別。
如果所有的分類器都能夠預測類別的概率(例如他們有一個 predict_proba() 方法),那么你就可以讓 sklearn 以最高的類概率來預測這個類,
平均在所有的分類器上。這種方式叫做軟投票。他經常比硬投票表現的更好,因為它給予高自信的投票更大的權重。
你可以通過把 voting="hard" 設置為 voting="soft" 來保證分類器可以預測類別概率。然而這不是 SVC類的分類器默認的選項,
所以你需要把它的 probability hyperparameter 設置為 True (這會使 SVC 使用交叉驗證去預測類別概率,其降低了訓練速度,
但會添加 predict_proba() 方法)。
from sklearn.ensemble import VotingClassifier
voting_clf = VotingClassifier(estimators=[('lr', log_clf), ('rf', rnd_clf),('svc', svm_clf)],voting='hard')
實現一個簡單的多數投票分類器:
基於多數投票原則,使用Python實現一個簡單的集成分類器
集成算法允許我們使用單獨的權重對不同分類算法進行組合。我們的目標是構建一個更加強大的元分類器,以在特定的數據集上平衡單個分類器的弱點。
為了使用Python代碼實現加權多數投票,可以使用NumPy中的argmax和bincount函數:
np.argmax(np.bincount([0,0,1],weights=[0.2,0.2,0.6]))
通過predict_proba方法,scikit-learn中的分類器可以返回樣本屬於預測類標的概率。
如果集成分類器事先得到良好的修正,那么在多數投票中使用預測類別的概率來替代類標會非常有用。
為實現基於類別預測概率的加權多數投票,我們可以再次使用NumPy中的numPy.average和np.argmax方法:
ex = np.array([[0.9,0.1],[0.8,0.2],[0.4,0.6]])
p = np.average(ex,axis=0,weights=[0.2,0.2,0.6])
print(np.argmax(p))
實現一個MajorityVotingClassifier,以10折交叉驗證作為評估標准,MajorityVotingClassifier的性能與單個成員分類器相比有着質的提高。
評估與調優集成分類器:
在本節,我們將在測試數據上計算MajorityVoteClassifier類的ROC曲線,以驗證其在未知數據上的泛化性能。
請記住,訓練數據並未應用於模型選擇,使用它們的唯一目的就是對分類系統的泛化性能做出無偏差的估計。
for clf,label,clr,ls in zip(all_clf,clf_labels,colors,linestyles):
y_pred = clf.fit(X_train,y_train).predict_proba(X_test)[:,1] # 測出結果
fpr, tpr, thresholds = roc_curve(y_true=y_test,y_score=y_pred)
roc_auc = auc(x=fpr,y=tpr)
plt.plot(fpr, tpr,color=clr,linestyle=ls,label="%s (auc=%0.2f)" %(label,roc_auc))
因為只使用了分類樣本中的兩個特征,集成分類器的決策區域到底是什么樣子可能會引起我們的興趣。
由於邏輯斯諦回歸和k-近鄰流水線會自動對數據進行預處理,因此不必事先對訓練特征進行標准化。
不過出於可視化考慮,也就是在相同的度量標准上實現決策區域,我們在此對訓練集做了標准化處理。
也如我們所預期的那樣,集成分類器的決策區域看起來像是各成員分類器決策區域的混合。
在學習集成分類的成員分類器調優之前,我們調用一下get_param方法,以便對如何訪問GridSearch對象內的單個參數有個基本的認識:
得到get_params方法的返回值后,我們現在知道怎樣去訪問成員分類器的屬性了。如"decisiontreeclassifier__max_depth"
出於演示的目的,先通過網格搜索來調整邏輯斯諦回歸分類器的正則化系數C以及決策樹的深度。
正如我們所見,當選擇的正則化強度較小時(C=100.0),我們能夠得到最佳的交叉驗證結果,而決策樹的深度似乎對性能沒有任何影響,
這意味着使用單層決策樹足以對數據進行划分。
請注意,在模型評估時,不止一次使用測試集並非一個好的做法,本節不打算討論超參調優后的集成分類器泛化能力的評估。
將繼續學習另外一種集成方法:bagging。
我們在本節實現的多數投票方法有時也稱為堆疊(stacking)。
不過,堆疊算法更典型地應用於組合邏輯斯諦回歸模型,以各獨立分類器的輸出作為輸入,通過對這些輸入結果的繼承來預測最終的類標
bagging——通過bootstrap樣本構建集成分類器:
# bagging是有放回的隨機抽樣,pasting是不放回的隨機抽樣,而boosting是循環訓練預測器,每一次都對其前序做出一些改正。
bagging是一種與上一節實現的MajorityVoteClassifier關系緊密的集成學習技術
不過,此算法沒有使用相同的訓練集擬合集成分類器中的單個成員分類器。對每一個分類器都使用相同的訓練算法,但是在不同的訓練集上去訓練它們。
由於原始訓練集使用了boostrap抽樣(有放回的隨機抽樣),這也就是bagging被稱為boostrap aggregating的原因。
在每一輪的bagging循環中,它們都被可放回隨機抽樣。每個bootstrap抽樣都被用於分類器Cj的訓練,這就是一棵典型的未剪枝的決策樹:
bagging還與我們在第3章中曾經介紹過的隨機森林緊密相關。實際上,隨機森林是bagging的一個特例,它使用了隨機的特征子集去擬合單棵決策。
在實戰中,分類任務會更加復雜,數據集維度會更高,使用單棵決策樹很容易產生過擬合,這時bagging算法就可顯示出其優勢了。
最后,我們需注意bagging算法是降低模型方差的一種有效方法。
然而,bagging在降低模型偏差方面的作用不大,這也是我們選擇未剪枝決策樹等低偏差分類器作為集成算法成員分類器的原因。
聚合函數通常對分類是統計模式(例如硬投票分類器)或者對回歸是平均。
通常情況下,集成的結果是有一個相似的偏差,但是對比與在原始訓練集上的單一分類器來講有更小的方差。
在sklearn中的 Bagging 和 Pasting:
sklearn 為 Bagging 和 Pasting 提供了一個簡單的API: BaggingClassifier 類(或者對於回歸可以是 BaggingRegressor 。
接下來的代碼訓練了一個 500 個決策樹分類器的集成,每一個都是在數據集上有放回采樣 100 個訓練實例下進行訓練
(這是 Bagging 的例子,如果你想嘗試Pasting,就設置 bootstrap=False )。
n_jobs 參數告訴 sklearn 用於訓練和預測所需要 CPU核的數量。(-1 代表着 sklearn 會使用所有空閑核):
from sklearn.ensemble import BaggingClassifier
rom sklearn.tree import DecisionTreeClassifier
bag_clf = BaggingClassifier(DecisionTreeClassifier(), n_estimators=500,
max_samples=100, bootstrap=True, n_jobs=-1)
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)
Bootstrap 在每個預測器被訓練的子集中引入了更多的分集,所以 Bagging 結束時的偏差比Pasting 更高,但這也意味着預測因子最終變得不相關,
從而減少了集合的方差。總體而言,Bagging 通常會導致更好的模型,這就解釋了為什么它通常是首選的。
Out-of-Bag 評價:
對於 Bagging 來說,一些實例可能被一些分類器重復采樣,但其他的有可能不會被采樣。
BaggingClassifier為默認采樣。BaggingClassifier 默認是有放回的采樣 m 個實例( bootstrap=True ),其中 m 是訓練集的大小,
這意味着平均下來只有63%的訓練實例被每個分類器采樣,剩下的37%個沒有被采樣的訓練實例就叫做 Out-of-Bag 實例。
注意對於每一個的分類器它們的 37% 不是相同的。
因為在訓練中分類器從來沒有看到過 oob 實例,所以它可以在這些實例上進行評估,而不需要單獨的驗證集或交叉驗證。
你可以拿出每一個分類器的 oob 來評估集成本身。
在 sklearn 中,你可以在訓練后需要創建一個 BaggingClassifier 來自動評估時設置 oob_score=True 來自動評估。接下來的代碼展示了這個操作。
評估結果通過變量 oob_score_ 來顯示:
bag_clf = BaggingClassifier(DecisionTreeClassifier(), n_estimators=500,bootstrap=True, n_jobs=-1, oob_score=True)
bag_clf.fit(X_train, y_train)
bag_clf.oob_score_ # 0.93066666666666664,再與下面測試集上的結果比較一下
根據這個 obb 評估, BaggingClassifier 可以再測試集上達到93.1%的准確率,讓我們修改一下:
from sklearn.metrics import accuracy_score
y_pred = bag_clf.predict(X_test)
accuracy_score(y_test, y_pred) # 0.93600000000000005
我們在測試集上得到了 93.6% 的准確率,足夠接近了!
對於每個訓練實例 oob 決策函數也可通過 oob_decision_function_ 變量來展示。在這種情況下(當基決策器有 predict_proba() 時)
決策函數會對每個訓練實例返回類別概率。例如,oob 評估預測第二個訓練實例有 60.6% 的概率屬於正類(39.4% 屬於負類):
bag_clf.oob_decision_function_
array([[ 0., 1.], [ 0.60588235, 0.39411765],[ 1., 0. ],... [ 1. , 0. ],[ 0., 1.],[ 0.48958333, 0.51041667]])
隨機貼片與隨機子空間
BaggingClassifier 也支持采樣特征。它被兩個超參數 max_features 和 bootstrap_features 控制。
他們的工作方式和 max_samples 和 bootstrap 一樣,但這是對於特征采樣而不是實例采樣。因此,每一個分類器都會被在隨機的輸入特征內進行訓練。
當你在處理高維度輸入下(例如圖片)此方法尤其有效。對訓練實例和特征的采樣被叫做隨機貼片。
保留了所有的訓練實例(例如 bootstrap=False 和 max_samples=1.0 ),但是對特征采樣( bootstrap_features=True 並且/或者
max_features 小於 1.0)叫做隨機子空間。
采樣特征導致更多的預測多樣性,用高偏差換低方差。
隨機森林:
通常用bagging(有時也可能是pasting)方法訓練
極端隨機樹
如前所述,隨機森林里單棵樹的生長過程中,每個節點在分裂時僅考慮到了一個隨機子集所包含的特征。
如果我們對每個特征使用隨機閾值,而不是搜索得出的最佳閾值(如常規決策樹),則可能讓決策樹生長得更加隨機。
這種極端隨機的決策樹組成的森林,被稱為極端隨機樹集成[4](或簡稱Extra-Trees)。
同樣,它也是以更高的偏差換取了更低的方差。
極端隨機樹訓練起來比常規隨機森林要快很多,因為在每個節點上找到每個特征的最佳閾值是決策樹生長中最耗時的任務之一。
使用Scikit-Learn的ExtraTreesClassifier可以創建一個極端隨機樹分類器。它的API與RandomForestClassifier相同。
同理,ExtraTreesRegressor與RandomForestRegressor的API也相同。
通常來說,很難預先知道一個RandomForestClassifier是否會比一個ExtraTreesClassifier更好或是更差。
唯一的方法是兩種都嘗試一遍,然后使用交叉驗證(還需要使用網格搜索調整超參數)進行比較。
提升法:
提升法(Boosting,最初被稱為假設提升)是指可以將幾個弱學習器結合成一個強學習器的任意集成方法。
大多數提升法的總體思路是循環訓練預測器,每一次都對其前序做出一些改正。
在boosting中,集成分類器包含多個非常簡單的成員分類器,這些成員分類器性能僅稍好於隨機猜測,常被稱作弱學習機。
典型的弱學習機例子就是單層決策樹。boosting主要針對難以區分的訓練樣本,也就是說,弱學習機通過在錯誤分類樣本上的學習來提高集成分類的性能。
與bagging不同,在boosting的初始化階段,算法使用無放回抽樣從訓練樣本中隨機抽取一個子集。
原始的boosting過程可總結為如下四個步驟:
1)從訓練集D中以無放回抽樣方式隨機抽取一個訓練子集d1,用於弱學習機C1的訓練。
2)從訓練集中以無放回抽樣方式隨機抽取第2個訓練子集d2,並將C1中誤分類樣本的50%加入到訓練集中,訓練得到弱學習機C2。
3)從訓練集D中抽取C1和C2分類結果不一致的樣本生成訓練樣本集d3,以此訓練第3個弱學習機C3。
4)通過多數投票組合三個弱學習機C1、C2和C3。
與bagging模型相比,boosting可同時降低偏差和方差。在實踐中,boosting算法(如AdaBoost)也存在明顯的高方差問題,也就是說,對訓練數據有過擬合傾向。
AdaBoost:
在本節對集成方法的介紹中,我們將重點討論boosting算法中一個常用例子:AdaBoost(Adaptive Boosting的簡稱)。
AdaBoost與這里討論的原始boosting過程不同,它使用整個訓練集來訓練弱學習機,其中訓練樣本在每次迭代中都會重新被賦予一個權重,
在上一弱學習機錯誤的基礎上進行學習進而構建一個更加強大的分類器。
新預測器對其前序進行糾正的辦法之一,就是更多地關注前序擬合不足的訓練實例。
從而使新的預測器不斷地越來越專注於難纏的問題,這就是AdaBoost使用的技術。
過程:
1.首先需要訓練一個基礎分類器(比如決策樹),用它對訓練集進行預測。
2.然后計算該預測器的加權誤差率rj,計算該預測器權重αj,更新實例權重,對錯誤分類的訓練實例增加其相對權重w
# 實例權重w用來衡量決策樹更新權重w時,該實例對權重w的更新系數大小,影響每個實例的貢獻(所以只能單個訓練?兩個for循環)
# 乘以一個 實例權重 w 矩陣? 類似於批量訓練的 預測結果最后平均出一個值來更新權重 w,平均的時候先乘以一個實例權重矩陣即可。
預測器的准確率越高,其權重就越高。如果它只是隨機猜測,則其權重接近於零。
但是,如果大部分情況下它都是錯的(也就是准確率比隨機猜測還低),那么它的權重為負。
3.使用這個最新的權重對第二個分類器進行訓練,然后再次對訓練集進行預測,繼續更新權重,並不斷循環向前。
4.當到達所需數量的預測器,或得到完美的預測器時,算法停止。
5.預測時簡單地計算所有預測器的預測結果,並使用預測器權重αj對它們進行加權。
算法步驟:
1.以等值方式為權重向量w賦值,總和為1
2.在m輪的boosting操作中,對第j輪做如下操作
訓練一個加權的弱學習機
預測樣本類標
計算權重錯誤率,正確為1,錯誤為0
計算相關系數αj
更新權重,計算正確會被降低權重,錯誤會增加
歸一化權重,總為1
3.完成最終的預測。
學習率越低,波動會越小。
AdaBoost這種依序循環的學習技術跟梯度下降有一些異曲同工之處,差別只在於——不再是調整單個預測器的參數使成本函數最小化,
而是不斷在集成中加入預測器,使模型越來越好。
一旦全部預測器訓練完成,集成整體做出預測時就跟bagging或pasting方法一樣了,除非預測器有不同的權重,因為它們總的准確率是基於加權后的訓練集。
序列學習技術的一個重要的缺點就是:它不能被並行化(只能按步驟),因為每個分類器只能在之前的分類器已經被訓練和評價后再進行訓練。
因此,它不像Bagging和Pasting一樣。
sklearn 通常使用 Adaboost 的多分類版本 SAMME(這就代表了 分段加建模使用多類指數損失函數)。
如果只有兩類別,那么 SAMME 是與 Adaboost 相同的。如果分類器可以預測類別概率(例如如果它們有 predict_proba() ),
如果 sklearn 可以使用 SAMME 叫做 SAMME.R 的變量(R 代表“REAL”),這種依賴於類別概率的通常比依賴於分類器的更好。
from sklearn.ensemble import AdaBoostClassifier
tree = DecisionTreeClassifier(criterion="entropy",max_depth=1)
ada = AdaBoostClassifier(base_estimator=tree,n_estimators=500,learning_rate=0.1,random_state=0)
可以發現,與單層決策樹相比,AdaBoost分類器能夠些許提高分類性能,並且與上一節中訓練的bagging分類器的准確率接近。
不過請注意:重復使用測試集進行模型選擇不是一個好的方法。集成分類器的泛化性能可能會被過度優化。
如果你的AdaBoost集成過度擬合訓練集,你可以試試減少估算器數量,或是提高基礎估算器的正則化程度。
對集成技術做一個總結:毋庸置疑,與單獨分類器相比,集成學習提高了計算復雜度。
但在實踐中,我們需仔細權衡是否願意為適度提高預測性能而付出更多的計算成本。
梯度提升:
另一個非常著名的提升算法是梯度提升。與 Adaboost 一樣,梯度提升也是通過向集成中逐步增加分類器運行的,每一個分類器都修正之前的分類結果。
然而,它並不像 Adaboost 那樣每一次迭代都更改實例的權重,而是讓新的預測器針對前一個預測器的殘差進行擬合。
我們來看一個簡單的回歸示例,使用決策樹作為基礎預測器(梯度提升當然也適用於回歸任務),這被稱為梯度樹提升或者是梯度提升回歸樹(GBRT)。
tree_reg1 = DecisionTreeRegressor(max_depth=2)
tree_reg1.fit(X, y)
現在在第一個分類器的殘差上訓練第二個分類器:
y2 = y - tree_reg1.predict(X) # 預測結果偏差
tree_reg2 = DecisionTreeRegressor(max_depth=2)
tree_reg2.fit(X, y2)
隨后在第二個分類器的殘差上訓練第三個分類器:
y3 = y2 - tree_reg1.predict(X)
tree_reg3 = DecisionTreeRegressor(max_depth=2)
tree_reg3.fit(X, y3)
現在我們有了一個包含三個回歸器的集成。它可以通過集成所有樹的預測來在一個新的實例上進行預測。
y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3)) # 求和
可以使用 sklean 中的 GradientBoostingRegressor 來訓練 GBRT 集成。
與 RandomForestClassifier 相似,它也有超參數去控制決策樹的生長(例如 max_depth , min_samples_leaf 等等),
也有超參數去控制集成訓練,例如基分類器的數量( n_estimators )。
接下來的代碼創建了與之前相同的集成:
from sklearn.ensemble import GradientBoostingRegressor
gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0)
gbrt.fit(X, y)
超參數learning_rate對每棵樹的貢獻進行縮放。如果你將其設置為低值,比如0.1,則需要更多的樹來擬合訓練集,但是預測的泛化效果通常更好。
這是一種被稱為收縮的正則化技術。
要找到樹的最佳數量,可以使用早期停止法(參見第4章)。簡單的實現方法就是使用staged_predict()方法:
它在訓練的每個階段(一棵樹時,兩棵樹時,等等)都對集成的預測返回一個迭代器(用於測量每個訓練階段的驗證誤差,從而找到樹的最優數量)。
以下代碼訓練了一個擁有120棵樹的GBRT集成,然后測量每個訓練階段的驗證誤差,從而找到樹的最優數量,最后使用最優樹數重新訓練了一個GBRT集成:
gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=120)
gbrt.fit(X_train, y_train)
errors = [mean_squared_error(y_val, y_pred) for y_pred in gbrt.staged_predict(X_test)] # 預測器的數量
bst_n_estimators = np.argmin(errors)
gbrt_best = GradientBoostingRegressor(max_depth=2,n_estimators=bst_n_estimators)
實際上,要實現早期停止法,不一定需要先訓練大量的樹,然后再回頭找最優的數字,還可以真的提前停止訓練。
設置warm_start=True,當fit()方法被調用時,Scikit-Learn會保留現有的樹,從而允許增量訓練。
以下代碼會在驗證誤差連續5次迭代未改善時,直接停止訓練:
gbrt = GradientBoostingRegressor(max_depth=2, warm_start=True)
for n_estimators in range(1, 120):
gbrt.n_estimators = n_estimators # 調整數量,繼續fit
gbrt.fit(X_train, y_train) #
y_pred = gbrt.predict(X_val)
val_error = mean_squared_error(y_val, y_pred)
if val_error < min_val_error:
min_val_error = val_error
error_going_up = 0
else:
error_going_up += 1
if error_going_up == 5:
break # early stopping
GradientBoostingRegressor類還可以支持超參數subsample,指定用於訓練每棵樹的實例的比例。
例如,如果subsample=0.25,則每棵樹用25%的隨機選擇的實例進行訓練。現在你可以猜到,這也是用更高的偏差換取了更低的方差
# 模型降低了過擬合,可能會欠擬合,方差減小,偏差增大(過擬合至少訓練集表現好,模式復雜,多個樣本集來說偏差較小)
同時在相當大的程度上加速了訓練過程。這種技術被稱為隨機梯度提升。
梯度提升也可以使用其他成本函數,通過超參數loss來控制(有關更多詳細信息,請參閱Scikit-Learn的文檔)。
XGBoost
XGBoost是2014年2月誕生的專注於梯度提升算法的機器學習函數庫,此函數庫因其優良的學習效果以及高效的訓練速度而獲得廣泛的關注。
XGBoost實現的是一種通用的Tree Boosting算法,此算法的一個代表為梯度提升決策樹(Gradient Boosting Decision Tree, GBDT),
又名MART(Multiple Additive Regression Tree)。
GBDT的核心在於后面的樹擬合的是前面預測值的殘差,這樣可以一步步逼近真值。然而,之所以擬合殘差可以逼近到真值,是因為使用了平方損失作為損失函數。
如果換成是其他損失函數,使用殘差將不再能夠保證逼近真值。
XGBoost的方法是,將損失函數做泰勒展開到第二階,使用前兩階作為改進的殘差。
可以證明,傳統GBDT使用的殘差是泰勒展開到一階的結果,因此,GBDT是XGBoost的一個特例。
支持列抽樣。
在決策樹中,模型復雜度體現在樹的深度上。XGBoost使用了一種替代指標,即葉子節點的個數。
此外,與許多其他機器學習模型一樣,XGBoost也加入了L2正則項,來平滑各葉子節點的預測值。
速度快的原因:
1.連續型特征的處理,只枚舉若干個分位點,例如將所有樣本根據此特征進行排序,然后均分10份,兩份之間斷開的數值即為分位點,
枚舉所有9個分位點后,使用降低損失最多的那個作為分叉點。
2. 利用數據稀疏性
數據稀疏有三個原因:缺失數據;某些特征本身就含有大量的0;對離散特征做了one-hot處理。
每次分叉時,都指定一條默認分支,如果樣本的這個特征為0,就走這個默認分支。
3.數據的預排序和分塊存儲
分叉的時候為了判斷分叉點,需要對每個特征進行排序。這個步驟是要重復多次的,
因此XGBoost在訓練之前預先對數據進行每一列做了排序,並按列存儲到內存中。
4.減少讀寫相關,提高Cache命中率
5.數據量大時,提高硬盤吞吐率
示例:
import xgboost as xgb
xlf = xgb.XGBClassifier() # 是xgboost的sklearn包,可以使用sklearn的utils如Grid Search等,利用函數參數設置模型參數
xlf.fit(X_train, y_train) # 內部調用原始xgboost的xgb.train(),原始利用param列表設置模型參數
preds = xlf.predict(X_test)
LightGBM:
目前已有的 GBDT 工具基本都是基於預排序的方法(pre-sorted)的決策樹算法(如 xgboost),
而LightGBM是基於 Histogram 的決策樹算法,帶深度限制的Leaf-wise的葉子生長策略,直方圖做差加速,直接支持類別特征(Categorical Feature),
Cache命中率優化,基於直方圖的稀疏特征優化
1.Histogram 算法
直方圖算法的基本思想是先把連續的浮點特征值離散化成k個整數,同時構造一個寬度為k的直方圖。
在遍歷數據的時候,根據離散化后的值作為索引在直方圖中累積統計量,當遍歷一次數據后,直方圖累積了需要的統計量,
然后根據直方圖的離散值,遍歷尋找最優的分割點。
減少了內存,直方圖算法不僅不需要額外存儲預排序的結果,而且可以只保存特征離散化后的值。
在計算上的代價也大幅降低,預排序算法每遍歷一個特征值就需要計算一次分裂的增益,而直方圖算法只需要計算k次
當然,Histogram 算法並不是完美的。由於特征被離散化后,找到的並不是很精確的分割點,所以會對結果產生影響。
但在不同的數據集上的結果表明,離散化的分割點對最終的精度影響並不是很大,甚至有時候會更好一點。
2.帶深度限制的 Leaf-wise 的葉子生長策略
它拋棄了大多數 GBDT 工具使用的按層生長 (level-wise) 的決策樹生長策略,而使用了帶有深度限制的按葉子生長 (leaf-wise) 算法。
Level-wise 過一次數據可以同時分裂同一層的葉子,容易進行多線程優化,也好控制模型復雜度,不容易過擬合。
但實際上 Level-wise 是一種低效的算法,因為它不加區分的對待同一層的葉子,帶來了很多沒必要的開銷,因為實際上很多葉子的分裂增益較低,
沒必要進行搜索和分裂。
Leaf-wise 則是一種更為高效的策略,每次從當前所有葉子中,找到分裂增益最大的一個葉子,然后分裂,如此循環。
因此同 Level-wise 相比,在分裂次數相同的情況下,Leaf-wise 可以降低更多的誤差,得到更好的精度。
Leaf-wise 的缺點是可能會長出比較深的決策樹,產生過擬合。
因此 LightGBM 在 Leaf-wise 之上增加了一個最大深度的限制,在保證高效率的同時防止過擬合。
3.直方圖加速
一個葉子的直方圖可以由它的父親節點的直方圖與它兄弟的直方圖做差得到。
通常構造直方圖,需要遍歷該葉子上的所有數據,但直方圖做差僅需遍歷直方圖的k個桶。
利用這個方法,LightGBM 可以在構造一個葉子的直方圖后,可以用非常微小的代價得到它兄弟葉子的直方圖,在速度上可以提升一倍。
4.直接支持類別特征
實際上大多數機器學習工具都無法直接支持類別特征,一般需要把類別特征,轉化到多維的0/1 特征,降低了空間和時間的效率。
而類別特征的使用是在實踐中很常用的。基於這個考慮,LightGBM 優化了對類別特征的支持,可以直接輸入類別特征,不需要額外的0/1 展開。
並在決策樹算法上增加了類別特征的決策規則。在 Expo 數據集上的實驗,相比0/1 展開的方法,訓練速度可以加速 8 倍,並且精度一致。
據我們所知,LightGBM 是第一個直接支持類別特征的 GBDT 工具。
超參:
1.核心參數
- boosting_type,默認gbdt,可選rf,dart,goss
gbdt, 傳統的梯度提升決策樹
rf, Random Forest (隨機森林)
dart, Dropouts meet Multiple Additive Regression Trees
goss, Gradient-based One-Side Sampling (基於梯度的單側采樣)
- learning_rate 默認設置成0.1
- n_estimators
- num_leaves 調整 num_leaves 的取值時, 應該讓其小於 2^(max_depth)
- n_jobs
- device cpu, gpu
2.控制參數:
- max_depth
- min_child_samples 默認20,一個葉子上數據的最小數量. 可以用來處理過擬合.對於大數據來說幾百或幾千
- min_child_weight,0.001,一個葉子上的最小 hessian 和. 類似於 min_data_in_leaf, 可以用來處理過擬合.
- colsample_bytree 1.0,加速訓練,處理過擬合
- subsample 1.0,將在不進行重采樣的情況下隨機選擇部分數據
- subsample_freq 0 bagging 的頻率, 0 意味着禁用 bagging. k 意味着每 k 次迭代執行bagging
- early_stopping_round 如果一個驗證集的度量在 early_stopping_round 循環中沒有提升, 將停止訓練
- reg_alpha L1 正則
- reg_lambda L2 正則
- min_split_gain 0,執行切分的最小增益
- subsample_for_bin:用來構建直方圖的數據的數量,在設置更大的數據時, 會提供更好的培訓效果, 但會增加數據加載時間。如果數據非常稀疏, 則將其設置為更大的值
3.IO參數
- max_bin 默認255,值越大,准確率越高,可能會過擬合
- save_binary 如果設置為 true LightGBM 則將數據集(包括驗證數據)保存到二進制文件中。 可以加快數據加載速度。
- ignore_column ignore_column=0,1,2
- categorical_feature categorical_feature=0,1,2
示例:
import lightgbm as lgb
lf = lgb.LGBMClassifier() # 類似,封裝了sklearn
lf.fit()