[白話解析] 通俗解析集成學習之bagging,boosting & 隨機森林
[白話解析] 通俗解析集成學習之bagging,boosting & 隨機森林
0x00 摘要
本文將盡量使用通俗易懂的方式,盡可能不涉及數學公式,而是從整體的思路上來看,運用感性直覺的思考來解釋 集成學習。并且從名著中延伸了具體應用場景來幫助大家深入這個概念。
在機器學習過程中,會遇到很多晦澀的概念,相關數學公式很多,大家理解起來很有困難。遇到類似情況,我們應該多從直覺角度入手思考,用類比或者舉例來附會,這樣往往會有更好的效果。
我在講解論述過程中給自己的要求是:在生活中或者名著中找一個例子,然后用自己的話語闡述出來。
0x01 相關概念
1. 偏置( bias ) vs 方差( variance )
- 平方偏置( bias ),表示所有數據集的平均預測與預期的回歸函數之間的差異。
- 方差( variance ),度量了對于單獨的數據集,模型所給出的解在平均值附近波動的情況,
方差 度量了同樣大小的訓練集的變動所導致的學習性能的變化,度量了在面對同樣規模的不同訓練集時,學習算法的估計結果發生變動的程度(相關于觀測樣本的誤差,刻畫了一個學習算法的精確性和特定性:一個高的方差意味著一個弱的匹配),即刻畫了數據擾動所造成的影響。
偏置 度量了學習算法期望預測與真實結果的偏離程度,度量了某種學習算法的平均估計結果所能逼近學習目標的程度(獨立于訓練樣本的誤差,刻畫了匹配的準確性和質量:一個高的偏置意味著一個壞的匹配),即刻畫了學習算法本身的擬合能力。
網上的比較好的例子是那個標靶的例子,大家可以去看看
- High Variance, low bias 對應就是點都打在靶心附近,但是散亂,所以瞄的是準的,但手不一定穩。
- Low variance, high bias 對應就是點都打的很集中,但不一定是靶心附近,手很穩,但是瞄的不準。
這里試圖用水滸傳來解釋下這兩個概念。
2. 用梁山為例通俗說方差
比如說五虎將,武力平均值就是呼延灼,其下是秦明,董平;其上是林沖,關勝。
假設武力值是 秦明 95,董平 96,呼延灼 97,關勝 98,林沖 99
針對這個樣本,其方差就是秦明,董平,林沖,關勝這四人的武力值和呼延灼差距多少。
如果都和呼延灼需要大戰八百回合才能分出勝負,那么說明方差小。如果大戰三百合就能分出勝負,那說明方差大。
3. 用梁山為例通俗說偏置
假設馬軍八驃騎的武力值如下:徐寧 92、索超 90、張清 86、朱仝 90、史進 88、穆弘 85,花榮 90、楊志 95。
如果有一個模型,用八驃騎來擬合五虎將。這個模型是:任意選取一個八驃騎來擬合。( 實際工作中偏置應該是取八驃騎平均值,這里偏置是任選一個,是為了說明方便 )
如果用楊志來擬合五虎將,則最能逼近五虎將,這時候偏置小。如果選出的是穆弘,則偏置最大。
對于沒遮攔穆弘,大家估計都是只知其名不知其事,可見其武力值最低。
當然穆弘的故事可能是在古本《水滸》有,但是后來世代相傳中從今本《水滸》中被刪除,所以被低視。
水滸故事有系統的文字記錄首見《宣和遺事》。該書講述先后落草太行山梁山、后來《水滸》說成是入伙魯西梁山泊的人物共三十八名。其中的沒遮攔穆橫無疑就是《水滸》中的穆弘。
《宣和遺事》中的穆橫是押運花石綱的十二指使之一。這十二人因公務而結義,后又因營救楊志而同往太行山落草,遂成為開創山寨的基本成員。《水滸傳》處理這十二指使,有八人是和穆橫完全一樣待遇的(林沖、花榮、張清、徐寧、李應、關勝、孫立、楊志)。
這八人全都在《水滸》里成了獨當一面的人物。如果一切順應傳統去發展,穆弘總不致變成一個有名無實的空白人物。
下面是《宣和遺事》
先是朱勔運花石綱時分,差著楊志、李進義、林沖、王雄,花榮、柴進、張青、徐寧、李應、穆橫、關勝、孫立十二人為指使,前往太湖等處,押人夫搬運花石。
那十二人領了文字,結義為兄弟,誓有災厄,各相救援。李進義等十名,運花石已到京城;只有楊志在潁州等候孫立不來,在彼處雪阻。那雪景如何卻是:亂飄僧舍茶煙濕,密灑歌樓酒力微。
那楊志為等孫立不來,又值雪天,旅涂貧困,缺少果足,未免將一口寶刀出市貨賣。終日價無人商量。行至日哺,遇一個惡少后生要賣寶刀,兩個交口廝爭,那后生被楊志揮刀一斫,只見頸隨刀落。楊志上了枷,取了招狀,送獄推勘。結案申奏文字回來,太守判道:“楊志事體雖大,情實可憫。將楊志誥札出身盡行燒毀,配衛州軍城。”斷罷,差兩人防送往衛州交管。正行次,撞著一漢,高叫:“楊指使!”楊志抬頭一覷,卻認得孫立指使。孫立驚怪:“哥怎恁地犯罪”。楊志把那賣刀殺人的事,一一說與孫立。道罷,各人自去。那孫立心中思忖:“楊志因等候我了,犯著這罪。當初結義之時,誓在厄難相救。”只得星夜奔歸京師,報與李進義等知道楊志犯罪因由。這李進義同孫立商議,兄弟十一人往黃河岸上,等待楊志過來,將防送軍人殺了,同往太行山落草為寇去也。
0x02 集成學習(ensemble learning)
1. 為什么要集成
在集成學習理論中,我們將弱學習器(或基礎模型)稱為「模型」,這些模型可用作設計更復雜模型的構件。在大多數情況下,這些基本模型本身的性能并不是非常好,這要么是因為它們具有較高的偏置(例如,低自由度模型),要么是因為他們的方差太大導致魯棒性不強(例如,高自由度模型)。
比如各弱分類器間具有一定差異性(如不同的算法,或相同算法不同參數配置),這會導致生成的分類決策邊界不同,也就是說它們在決策時會犯不同的錯誤。
集成方法的思想是通過將這些弱學習器的偏置和/或方差結合起來,從而創建一個「強學習器」(或「集成模型」),從而獲得更好的性能。
集成學習是一種機器學習范式。其基本思想是:「三個臭皮匠頂個諸葛亮」。「團結就是力量」。「博采眾長」。
在集成學習中,我們會訓練多個模型(通常稱為「弱學習器」)解決相同的問題,并將它們結合起來以獲得更好的結果。最重要的假設是:當弱模型被正確組合時,我們可以得到更精確和/或更魯棒的模型。
我們可以對集成學習的思想做一個概括。對于訓練集數據,我們通過訓練若干個個體學習器,通過一定的結合策略,就可以最終形成一個強學習器,以達到博采眾長的目的。
-
集成學習法由訓練數據構建一組基分類器,然后通過對每個基分類器的預測進行投票來進行分類
-
嚴格來說,集成學習并不算是一種分類器,而是一種分類器結合的方法。
-
如果把單個分類器比作一個決策者的話,集成學習的方法就相當于多個決策者共同進行一項決策。
2. 分類
對于集成學習的分類,常見兩種分類方法:
分類1
個體學習器按照個體學習器之間是否存在依賴關系可以分為兩類:
- 個體學習器之間存在強依賴關系,一系列個體學習器基本都需要串行生成,代表算法是boosting系列算法;
- 個體學習器之間不存在強依賴關系,一系列個體學習器可以并行生成,代表算法是bagging和隨機森林(Random Forest)系列算法。
分類2
集成學習按照基本分類器之間的關系可以分為異態集成學習和同態集成學習。
-
異態集成學習是指弱分類器之間本身不同;
-
而同態集成學習是指弱分類器之間本身相同只是參數不同。
3. 主要問題
集成學習有兩個主要的問題需要解決:
1)怎么訓練每個算法?即如何得到若干個個體學習器。
2)怎么融合每個算法?即如何選擇一種結合策略,將這些個體學習器集合成一個強學習器。
如何得到個體學習器?
主要從以下5個方面得到:
- 基本分類器本身的種類,即其構成算法不同;
- 對數據進行處理不同,比如說boosting,bagging,stacking,cross-validation,hold-out test;
- 對輸入特征進行處理和選擇;
- 對輸出結果進行處理,比如說有的學者提出的糾錯碼;
- 引入隨機擾動;
很重要的一點是:我們對弱學習器的選擇應該和我們聚合這些模型的方式相一致。如果我們選擇具有低偏置高方差的基礎模型,我們應該使用一種傾向于減小方差的聚合方法;而如果我們選擇具有低方差高偏置的基礎模型,我們應該使用一種傾向于減小偏置的聚合方法。
如何選擇一種結合策略
一旦選定了弱學習器,我們仍需要定義它們的擬合方式(在擬合當前模型時,要考慮之前模型的哪些信息?)和聚合方式(如何將當前的模型聚合到之前的模型中?)
這就引出了如何組合這些模型的問題。我們可以用三種主要的旨在組合弱學習器的「元算法」:
-
bagging,該方法通常考慮的是同質弱學習器,相互獨立地并行學習這些弱學習器,并按照某種確定性的平均過程將它們組合起來。
-
boosting,該方法通常考慮的也是同質弱學習器。它以一種高度自適應的方法順序地學習這些弱學習器(每個基礎模型都依賴于前面的模型),并按照某種確定性的策略將它們組合起來。
-
stacking,該方法通常考慮的是異質弱學習器,并行地學習它們,并通過訓練一個「元模型」將它們組合起來,根據不同弱模型的預測結果輸出一個最終的預測結果。
非常粗略地說,我們可以說 bagging 的重點在于獲得一個方差比其組成部分更小的集成模型,而 boosting 和 stacking 則將主要生成偏置比其組成部分更低的強模型(即使方差也可以被減小)。
我們可以看看在水滸傳如何應用結合策略
梁山要打某座州府,究竟用什么辦法呢?
聽吳學究的嘛?
吳學究在生辰綱的表現實在不行,漏洞太多。打大名府等也基本就是一招:事先混進城里,然后里應外合。
所以還是應該集思廣益,采用集成學習。
可以采用兩個辦法
辦法1
神機軍師朱武,混江龍李俊,智多星吳用,入云龍公孫勝,混世魔王樊瑞五個人都分別出一個主意,然后投票選出一個綜合方案。
方法2
吳用先出一個主意,然后朱武針對這個主意做些調整補漏,給出了一個新主意。李俊再針對朱武的主意進行修補,給出了第三個主意...... 最后給出一個最終主意。
辦法1 就是bagging方式的近似模擬
辦法2 就是boosting方式的近似模擬
0x03 Bootstrap
首先需要介紹下Bootstrap,這個其實不屬于集成學習,而是統計學的一種方法,屬于集成學習的先驅。
Boostrap是靴子的帶子的意思,名字來源于“pull up your own boostraps”,意思是通過拉靴子提高自己,本來的意思是不可能發生的事情,但后來發展成通過自己的努力讓事情變得更好。放在組合分類器這里,意思就是通過分類器自己提高分類的性能。
Boostrap是一種抽樣方法。
人們希望獲取整體的全部信息,因為這樣就可以做到“運籌帷幄”——整體都知道了,還有什么樣本是我們不能掌握的嗎?而實際上獲取整體的信息是很難的,甚至是不可能的,所以才有了統計學中的“抽樣”。也就是說,我們只能獲取整體中的某些樣本的信息,人們期望可以通過這些有限的樣本信息來盡可能準確地估計總體,從而為決策提供依據。而Bootstrap方法認為,既然得到的樣本是從總體中“抽取”的,那么為什么不可以把這些樣本當做一個整體,從中進行有放回地再抽取呢?這種方法看似簡單,而實際上卻是十分有效的。
具體的方法是:
(1)采用重復抽樣的方法每次從n個原始樣本中抽取m個樣本(m自己設定)
(2)對于m個樣本計算統計量
(3)重復步驟(1)(2)N次(N一般大于1000),這樣就可以算出N個統計量
(4)計算這N個統計量的方差
比如說,我現在要對一些未知樣本做分類,分類算法選取一種,比如SVM。我要估計的總體參數是準確率(accuracy)。對于n個原始樣本,從步驟(1)開始,每次對抽取出的樣本用SVM訓練出一個模型,然后用這個模型對未知樣本做分類,得到一個準確率。重復N次,可以得到N個準確率,然后對計算出的N個準確率做方差。
為什么要計算這N個統計量的方差而不是期望或者均值。方差表示的是一組數據與其平均水平的偏離程度,如果計算的方差值在一定范圍之內,則表示這些數據的波動不是很大,那么他們的均值就可以用來估計總體的參數,而如果方差很大,這些樣本統計量波動很大,那么總體的參數估計就不太準確?
看起來太簡單了,所以提出者Efron給統計學頂級期刊投稿的時候被拒絕的理由--"太簡單"。
Bootstrap只是提供了一種組合方法的思想,就是將基分類器的訓練結果進行綜合分析,而其它的名稱如Bagging。Boosting是對組合方法的具體演繹。所以由Bootstrap方法開始,我們將導入到集成學習。
0x04 bagging方法
Bagging是boostrap aggregation的縮寫,又叫自助聚集,是一種根據均勻概率分布從數據中重復抽樣(有放回)的技術。至于為什么叫bootstrap aggregation,因為它抽取訓練樣本的時候采用的就是bootstrap的方法!
子訓練樣本集的大小和原始數據集相同。在構造每一個子分類器的訓練樣本時,由于是對原始數據集的有放回抽樣,因此同一個訓練樣本集中可能出現多次同一個樣本數據。
1. Bagging策略過程
- 從樣本集中用Bootstrap采樣選出n個訓練樣本(放回,因為別的分類器抽訓練樣本的時候也要用)
- 在所有屬性上,用這n個樣本訓練分類器(CART or SVM or ...)
- 重復以上兩步m次,就可以得到m個分類器(CART or SVM or ...)
- 將數據放在這m個分類器上跑,最后投票機制(多數服從少數)看到底分到哪一類(分類問題)
- 對于分類問題:由投票表決產生分類結果;對于回歸問題:由n個模型預測結果的均值作為最后預測結果。(所有模型的重要性相同)
2. 總結一下bagging方法
- Bagging通過降低基分類器的方差,改善了泛化誤差
- 其性能依賴于基分類器的穩定性;如果基分類器不穩定,bagging有助于降低訓練數據的隨機波動導致的誤差;如果穩定,則集成分類器的誤差主要由基分類器的偏倚引起
- 由于每個樣本被選中的概率相同,因此bagging并不側重于訓練數據集中的任何特定實例
0x05 隨機森林
隨機森林是結合 Breimans 的 "Bootstrap aggregating" 想法和 Ho 的"random subspace method"以建造決策樹的集合。即 Bagging + 決策樹 = 隨機森林。這是在共擁有m個特征的決策樹中隨機選擇k個特征組成n棵決策樹,再選擇預測結果模式(如果是回歸問題,選擇平均值)。
1. Bagging特點如何應用
假設共有N個樣本,M個特征,讓我們結合Bagging的各個特點看看。
用Bootstrap采樣 (Bagging特點應用) : 每棵樹都有放回的隨機抽取訓練樣本,這里抽取隨機抽取 2/3 的樣本作為訓練集,再有放回的隨機選取 m 個特征作為這棵樹的分枝的依據。
隨機:一個是隨機選取樣本 (Bagging特點應用) ,一個是隨機選取特征。這樣就構建出了一棵樹。之后再在隨機選取的特征中選取最優的特征。這樣能夠使得隨機森林中的決策樹都能夠彼此不同,提升系統的多樣性,從而提升分類性能。
選出優秀特征:隨機森林真正厲害的地方不在于它通過多棵樹進行綜合得出最終結果,而是在于通過迭代使得森林中的樹不斷變得優秀 (森林中的樹選用更好的特征進行分枝)。
迭代得到若干分類器 (Bagging特點應用):按照上面的步驟迭代多次,逐步去除相對較差的特征,每次都會生成新的森林,直到剩余的特征數為 m 為止。假設迭代 x 次,得到 x 個森林。
投票表決 (Bagging特點應用):用另外 1/3 樣本 (也叫做袋外樣本) 做為測試集,對迭代出的 x 個森林進行預測。預測出所有樣本的結果之后與真實值進行比較,選擇套外誤差率最小的森林作為最終的隨機森林模型。
2. 選出優秀特征
對于選出優秀特征這個,需要再做一下解釋。
隨機森林的思想是構建出優秀的樹,優秀的樹需要優秀的特征。那我們需要知道各個特征的重要程度。對于每一棵樹都有個特征,要知道某個特征在這個樹中是否起到了作用,可以隨機改變這個特征的值,使得“這棵樹中有沒有這個特征都無所謂”,之后比較改變前后的測試集誤差率,誤差率的差距作為該特征在該樹中的重要程度,測試集即為該樹抽取樣本之后剩余的樣本(袋外樣本)(由袋外樣本做測試集造成的誤差稱為袋外誤差)。
在一棵樹中對于個特征都計算一次,就可以算法個特征在該樹中的重要程度。我們可以計算出所有樹中的特征在各自樹中的重要程度。但這只能代表這些特征在樹中的重要程度不能代表特征在整個森林中的重要程度。那我們怎么計算各特征在森林中的重要程度呢?每個特征在多棵數中出現,取這個特征值在多棵樹中的重要程度的均值即為該特征在森林中的重要程度。這樣就得到了所有特征在森林中的重要程度。將所有的特征按照重要程度排序,去除森林中重要程度低的部分特征,得到新的特征集。這時相當于我們回到了原點,這算是真正意義上完成了一次迭代。
0x06 Boosting 方法
"Boosting"的基本思想是通過某種方式使得每一輪基學習器在訓練過程中更加關注上一輪學習錯誤的樣本。
Boosting 方法和bagging 方法的工作思路是一樣的:我們構建一系列模型,將它們聚合起來得到一個性能更好的強學習器。然而,與重點在于減小方差的 bagging 不同,boosting 著眼于以一種適應性很強的方式順序擬合多個弱學習器:序列中每個模型在擬合的過程中,會更加重視那些序列中之前的模型處理地很糟糕的觀測數據。
Boosting是一種迭代算法,針對同一個訓練集訓練不同的分類器(弱分類器),然后進行分類,對于分類正確的樣本權值低,分類錯誤的樣本權值高(通常是邊界附近的樣本),最后的分類器是很多弱分類器的線性疊加(加權組合),分類器相當簡單。實際上就是一個簡單的弱分類算法提升(boost)的過程。
每次樣本加入的過程中,通常根據它們的上一輪的分類準確率給予不同的權重。數據通常會被重新加權,來強化對之前分類錯誤數據點的分類。
當boost運行每個模型時,它會跟蹤哪些數據樣本是最成功的,哪些不是。輸出分類錯誤最多的數據集被賦予更重的權重。這些數據被認為更復雜,需要更多的迭代來正確地訓練模型。
在實際分類階段,boosting處理模型的方式也有所不同。在boosting中,模型的錯誤率被跟蹤,因為更好的模型被賦予更好的權重。即 誤差越小的弱分類器,權值越大。這樣,當“投票”發生時,就像bagging一樣,結果更好的模型對最終的輸出有更的強拉動力。
0x07 AdaBoost
由于采用的損失函數不同,Boosting算法也因此有了不同的類型,AdaBoost就是損失函數為指數損失的Boosting算法。采用指數損失的原因是:每一輪最小化指數損失其實是在訓練一個logistic regression模型,這樣就逼近對數幾率 (log odds)。
1. 兩個主要問題
Boosting算法是將“弱學習算法“提升為“強學習算法”的過程。采用Boosting思想實現的算法,需要解決兩個主要問題。
- 如何選擇一組有不同優缺點的弱學習器,使得它們可以相互彌補不足。
- 如何組合弱學習器的輸出以獲得整體的更好的決策表現。
和這兩個問題對應的是加法模型和前向分步算法。
- 加法模型就是說強分類器由一系列弱分類器線性相加而成。
- 前向分步就是說在訓練過程中,下一輪迭代產生的分類器是在上一輪的基礎上訓練得來的。
前向分布算法說:“我可以提供一套框架,不管基函數和損失函數是什么形式,只要你的模型是加法模型,就可以按照我的框架的指導,去求解。” 也就是說,前向分步算法提供了一種學習加法模型的普遍性方法,不同形式的基函數、不同形式的損失函數都可以用這種普遍性方法去求出加法模型的最優化參數,它是一種元算法。
前向分步算法的思路是:加法模型中一共有M個基函數以及與之相應的M個系數,可以從前往后,每次學習一個基函數及其系數。
2. AdaBoost的解決方案
選擇弱學習器
為了解決第一個問題,AdaBoost的辦法是:每輪結束時改變樣本的權值。這樣AdaBoost讓每一個新加入的弱學習器都體現出一些新的數據中的模式。
為了實現這一點,AdaBoost為每一個訓練樣本維護一個權重分布。即對任意一個樣本 xi 都有一個分布 D(i) 與之對應,以表示這個樣本的重要性。
當衡量弱學習器的表現時,AdaBoost會考慮每個樣本的權重。權重較大的誤分類樣本會比權重較小的誤分類樣本貢獻更大的訓練錯誤率。為了獲得更小的加權錯誤率,弱分類器必須更多的聚焦于高權重的樣本,保證對它們準確的預測。
也可以這么理解提高錯誤點的權值的意義:當"下一次分類器"再次分錯了這些點之后,會提高"下一次分類器"整體的錯誤率,這樣就導致 "下一次分類器" 自己的 權重 變的很小,最終導致這個分類器在整個混合分類器的權值變低。
通過修改樣本的權重 D(i) ,也就改變了樣本的概率分布,將關注點放在被錯誤分類的樣本上,減小上一輪被正確分類的樣本權值,提高那些被錯誤分類的樣本權值。這樣就可以引導弱學習器學習訓練樣本的不同部分。
訓練時候,我們是利用前一輪迭代弱學習器的誤差率來更新訓練集的權重,這樣一輪輪的迭代下去。
組合弱學習器
現在,我們獲得了一組已訓練的擁有不同優缺點的弱學習器,如何有效的組合它們,使得相互優勢互補來產生更準確的整體預測效果?這就是第二個問題。
對于第二個問題,AdaBoost采用加權多數表決的方法,加大分類誤差率小的弱分類器的權重,減小分類誤差率大的弱分類器的權重。這個很好理解,正確率高分得好的弱分類器在強分類器中當然應該有較大的發言權。
每一個弱學習器是用不同的權重分布訓練出來的,我們可以看做給不同的弱學習器分配了不同的任務,每個弱學習器都盡力完成給定的任務。直覺上看,當我們要把每個弱學習器的判斷組合到最終的預測結果中時,如果弱學習器在之前的任務中表現優異,我們會更多的相信它,相反,如果弱學習器在之前的任務中表現較差,我們就更少的相信它。
換句話說,我們會加權地組合弱學習器,給每個弱學習器賦予一個表示可信程度的值 wi ,這個值取決于它在被分配的任務中的表現,表現越好 wi 越大,反之越小。
0x08 從水滸傳中派生一個例子看如何使用集成學習
我們找一個例子來詳細看看如何使用集成學習這個概念。現在梁山需要投票來決定是否接受招安。宋江說了,我們要科學民主的進行決策,所以我們采用最新科技:集成學習。
1. Bagging
首先考慮的是bagging方法
如果采用了bagging方法。樣本有放回,而且投票可以并行。
每次抽取 5 個人投票是否接受。
第一次抽出 徐寧 、索超 、朱仝 、花榮、楊志。則 5 票都是 接受招安
第二次抽出 魯智深,武松,朱貴,劉唐,李逵。則 5 票都是 反對招安
第三次抽出 徐寧 、索超,魯智深,武松,阮小二。則 2 票接受,3 票反對,則這次樣本是 反對招安。
最后綜合三次的結果是:反對招安。
現在情況已經對招安不利了,如果再并行進行投票,那么對結果就更無法估計。
這樣的話,對于是否招安就真的是梁山群體民主評議,公明哥哥和吳學究沒辦法后臺黑箱操控了。
如果宋江想黑箱操作,他就不能采用bagging方法,而是需要選擇boosting,因為這個算法的訓練集的選擇不是獨立的,每一次選擇的訓練集都依賴于上一次學習的結果,所以利于宋江暗箱操縱。
2. Boosting
于是宋江決定采取Boosting方法,大家可以看看宋江如何使用boosting一步一步調整bias以達到最好擬合 “接受招安” 這個最終期望的。
"Boosting"的基本思想是通過某種方式使得每一輪基學習器在訓練過程中更加關注上一輪學習錯誤的樣本
所以我們有兩套方案。
方案一:每次每次剔除異常數值
方案二:每次調整異常數值權重
-----------------------------------------------------------------
方案一:每次剔除異常數值,這種適合宋江的樣本全部無法控制的情況
迭代1
樣本:魯智深,武松,朱貴,劉唐,李逵。則 5 票都是 反對招安
宋江說:出家人與世無爭,就不要參與投票了,改換為徐寧,索超兩位兄弟。
宋江跟蹤錯誤率,因為本次弱分類器誤差太大,所以本次權重降低。
迭代2
樣本:徐寧,索超,朱貴,劉唐,李逵。則 2 票接受,3 票反對,則這次樣本是 反對招安
宋江說:鐵牛兄弟不懂事,亂投票,來人亂棒打出去,換成楊志兄弟。
宋江跟蹤錯誤率,因為本次弱分類器誤差太大,所以本次權重降低。
迭代3
樣本:徐寧,索超,朱貴,劉唐,楊志。則 3 票接受,2 票反對,則這次樣本是 接受招安
宋江說:朱貴兄弟平時總是打點酒店,對于時政缺少認知,換成關勝兄弟。
宋江跟蹤錯誤率,因為本次弱分類器誤差較小,所以本次權重增加。
迭代4
樣本:徐寧,索超,關勝,劉唐,楊志。則 4 票接受,1 票反對,則這次樣本是 接受招安
宋江說:劉唐兄弟頭發染色了,不利用梁山形象,換成花榮兄弟。
宋江跟蹤錯誤率,因為本次弱分類器誤差較小,所以本次權重增加。
迭代5
樣本:徐寧,索超,關勝,花榮,楊志。則 5 票接受,則這次樣本是 接受招安。
宋江跟蹤錯誤率,因為本次弱分類器沒有誤差,所以本次權重增加。
即誤差率小的分類器,在最終分類器中的重要程度大。
最后綜合 5 次選舉結果,梁山最后決定接受招安。
-----------------------------------------------------------------
方案二:每次降低異常數值權重,這種適合樣本中有宋江可以控制的頭領
迭代1
樣本:武松,花榮,朱貴,楊志,李逵。則 2 票接受,3 票反對,則本次結論是 反對招安
宋江說:出家人與世無爭,降低武松權重為 1/2 。
宋江跟蹤錯誤率,因為本次弱分類器誤差太大,所以本次權重降低。
迭代2
樣本:武松(權重1/2),花榮,朱貴,楊志,李逵。則 2 票接受,2又1/2 票反對,則這次結論是 反對招安
宋江說:鐵牛兄弟不懂事,亂投票,降低李逵權重為 1/2。
宋江跟蹤錯誤率,因為本次弱分類器誤差太大,所以本次權重降低。
迭代3
樣本:武松(權重1/2),花榮,朱貴,楊志,李逵(權重1/2)。則 2 票接受,2 票反對,則這次結論是 無結論
宋江說:朱貴兄弟平時打點酒店,對于時政缺少認知,降低朱貴權重為 1/2。
宋江跟蹤錯誤率,因為本次弱分類器無結論,所以本次權重為零。
迭代4
樣本:武松(權重1/2),花榮,朱貴(權重1/2),楊志,李逵(權重1/2)。則 2 票接受,1又1/2 票反對,則這次結論是 接受招安
宋江說:這次好,花榮做過知寨,有見地,增加花榮權重。繼續投票。
宋江跟蹤錯誤率,因為本次弱分類器誤差較小,所以本次權重增加。
迭代5
樣本:武松(權重1/2),花榮(權重2),朱貴(權重1/2),楊志,李逵(權重1/2)。則 3 票接受,1又1/2 票反對,則這次結論是 接受招安
宋江說:這次好,楊志做過制使,有見地,增加楊志權重。繼續投票。
宋江跟蹤錯誤率,因為本次弱分類器誤差較小,所以本次權重增加。
迭代6
樣本:武松(權重1/2),花榮(權重2),朱貴(權重1/2),楊志(權重2),李逵(權重1/2)。則 4 票接受,1又1/2 票反對,則這次結論是 接受招安
宋江跟蹤錯誤率,因為本次弱分類器誤差較小,所以本次權重增加。
最后綜合 6 次選舉結果,梁山最后決定接受招安。
-----------------------------------------------------------------
這里能看出來,Boosting算法對于樣本的異常值十分敏感,因為Boosting算法中每個分類器的輸入都依賴于前一個分類器的分類結果,會導致誤差呈指數級累積。
宋江每一次選擇的訓練集都依賴于上一次學習的結果。每次剔除異常數值 或者 調整異常數值權重。(在實際boosting算法中是增加異常數值的權重)。
宋江也根據每一次訓練的訓練誤差得到該次預測函數的權重。
3. 為什么說bagging是減少variance,而boosting是減少bias?
bias描述的是根據樣本擬合出的模型的輸出預測結果的期望與樣本真實結果的差距,簡單講,就是在樣本上擬合的好不好。要想在bias上表現好,low bias,就得復雜化模型,增加模型的參數,但這樣容易過擬合 (overfitting)。
varience描述的是樣本上訓練出來的模型在測試集上的表現,要想在variance上表現好,low varience,就要簡化模型,減少模型的參數,但這樣容易欠擬合(unfitting)。
從我們例子能看出來。
1. Bagging
bagging沒有針對性的對分類器進行調整,只是單純的增加樣本數量和采樣次數,以此來讓平均值逼近結果。
所以bagging的基模型應該本身就是強模型(偏差低方差高)。
所以,bagging應該是對許多強(甚至過強)的分類器求平均。在這里,每個單獨的分類器的bias都是低的,平均之后bias依然低;而每個單獨的分類器都強到可能產生overfitting的程度,也就是variance高,求平均的操作起到的作用就是降低這個variance。
2. Boosting
boosting就是為了讓每一次分類器的結果逐漸接近期望目標。即宋江期望一步一步的boosting到最終接受招安這個結果。這樣才能在樣本上最好擬合“招安”這個期望結果,從最初的“拒絕招安”這個high bias過渡到“接受招安”這個期望結果。
boosting是把許多弱的分類器組合成一個強的分類器。弱的分類器bias高,而強的分類器bias低,所以說boosting起到了降低bias的作用。variance不是boosting的主要考慮因素。
Boosting 是迭代算法,每一次迭代都根據上一次迭代的預測結果對樣本進行加權,所以隨著迭代不斷進行,誤差會越來越小,所以模型的 bias 會不斷降低。這種算法無法并行,例子比如Adaptive Boosting。
4. Bagging vs Boosting
由此我們可以對比Bagging和Boosting:
- 樣本選擇上:Bagging采用的是Bootstrap隨機有放回抽樣,各訓練集是獨立的;而boosting訓練集的選擇不是獨立的,每一次選擇的訓練集都依賴于上一次學習的結果。如果訓練集不變,那么改變的只是每一個樣本的權重。
- 樣本權重:Bagging使用的是均勻取樣,每個樣本權重相等;Boosting根據錯誤率調整樣本權重,錯誤率越大的樣本權重越大。
- 預測函數:Bagging所有的預測函數的權重相等;Boosting根據每一次訓練的訓練誤差得到該次預測函數的權重,誤差越小的預測函數其權重越大。
- 并行計算:Bagging各個預測函數可以并行生成;Boosting各個預測函數必須按順序迭代生成。
0x09 隨機森林代碼
有興趣的同學可以用代碼來論證下Bagging。這里給出兩份代碼。
其一出自 https://blog.csdn.net/colourful_sky/article/details/82082854
# -*- coding: utf-8 -*-
"""
Created on Thu Jul 26 16:38:18 2018
@author: aoanng
"""
import csv
from random import seed
from random import randrange
from math import sqrt
def loadCSV(filename):#加載數據,一行行的存入列表
dataSet = []
with open(filename, 'r') as file:
csvReader = csv.reader(file)
for line in csvReader:
dataSet.append(line)
return dataSet
# 除了標簽列,其他列都轉換為float類型
def column_to_float(dataSet):
featLen = len(dataSet[0]) - 1
for data in dataSet:
for column in range(featLen):
data[column] = float(data[column].strip())
# 將數據集隨機分成N塊,方便交叉驗證,其中一塊是測試集,其他四塊是訓練集
def spiltDataSet(dataSet, n_folds):
fold_size = int(len(dataSet) / n_folds)
dataSet_copy = list(dataSet)
dataSet_spilt = []
for i in range(n_folds):
fold = []
while len(fold) < fold_size: # 這里不能用if,if只是在第一次判斷時起作用,while執行循環,直到條件不成立
index = randrange(len(dataSet_copy))
fold.append(dataSet_copy.pop(index)) # pop() 函數用于移除列表中的一個元素(默認最后一個元素),并且返回該元素的值。
dataSet_spilt.append(fold)
return dataSet_spilt
# 構造數據子集
def get_subsample(dataSet, ratio):
subdataSet = []
lenSubdata = round(len(dataSet) * ratio)#返回浮點數
while len(subdataSet) < lenSubdata:
index = randrange(len(dataSet) - 1)
subdataSet.append(dataSet[index])
# print len(subdataSet)
return subdataSet
# 分割數據集
def data_spilt(dataSet, index, value):
left = []
right = []
for row in dataSet:
if row[index] < value:
left.append(row)
else:
right.append(row)
return left, right
# 計算分割代價
def spilt_loss(left, right, class_values):
loss = 0.0
for class_value in class_values:
left_size = len(left)
if left_size != 0: # 防止除數為零
prop = [row[-1] for row in left].count(class_value) / float(left_size)
loss += (prop * (1.0 - prop))
right_size = len(right)
if right_size != 0:
prop = [row[-1] for row in right].count(class_value) / float(right_size)
loss += (prop * (1.0 - prop))
return loss
# 選取任意的n個特征,在這n個特征中,選取分割時的最優特征
def get_best_spilt(dataSet, n_features):
features = []
class_values = list(set(row[-1] for row in dataSet))
b_index, b_value, b_loss, b_left, b_right = 999, 999, 999, None, None
while len(features) < n_features:
index = randrange(len(dataSet[0]) - 1)
if index not in features:
features.append(index)
# print 'features:',features
for index in features:#找到列的最適合做節點的索引,(損失最小)
for row in dataSet:
left, right = data_spilt(dataSet, index, row[index])#以它為節點的,左右分支
loss = spilt_loss(left, right, class_values)
if loss < b_loss:#尋找最小分割代價
b_index, b_value, b_loss, b_left, b_right = index, row[index], loss, left, right
# print b_loss
# print type(b_index)
return {'index': b_index, 'value': b_value, 'left': b_left, 'right': b_right}
# 決定輸出標簽
def decide_label(data):
output = [row[-1] for row in data]
return max(set(output), key=output.count)
# 子分割,不斷地構建葉節點的過程
def sub_spilt(root, n_features, max_depth, min_size, depth):
left = root['left']
# print left
right = root['right']
del (root['left'])
del (root['right'])
# print depth
if not left or not right:
root['left'] = root['right'] = decide_label(left + right)
# print 'testing'
return
if depth > max_depth:
root['left'] = decide_label(left)
root['right'] = decide_label(right)
return
if len(left) < min_size:
root['left'] = decide_label(left)
else:
root['left'] = get_best_spilt(left, n_features)
# print 'testing_left'
sub_spilt(root['left'], n_features, max_depth, min_size, depth + 1)
if len(right) < min_size:
root['right'] = decide_label(right)
else:
root['right'] = get_best_spilt(right, n_features)
# print 'testing_right'
sub_spilt(root['right'], n_features, max_depth, min_size, depth + 1)
# 構造決策樹
def build_tree(dataSet, n_features, max_depth, min_size):
root = get_best_spilt(dataSet, n_features)
sub_spilt(root, n_features, max_depth, min_size, 1)
return root
# 預測測試集結果
def predict(tree, row):
predictions = []
if row[tree['index']] < tree['value']:
if isinstance(tree['left'], dict):
return predict(tree['left'], row)
else:
return tree['left']
else:
if isinstance(tree['right'], dict):
return predict(tree['right'], row)
else:
return tree['right']
# predictions=set(predictions)
def bagging_predict(trees, row):
predictions = [predict(tree, row) for tree in trees]
return max(set(predictions), key=predictions.count)
# 創建隨機森林
def random_forest(train, test, ratio, n_feature, max_depth, min_size, n_trees):
trees = []
for i in range(n_trees):
train = get_subsample(train, ratio)#從切割的數據集中選取子集
tree = build_tree(train, n_features, max_depth, min_size)
# print 'tree %d: '%i,tree
trees.append(tree)
# predict_values = [predict(trees,row) for row in test]
predict_values = [bagging_predict(trees, row) for row in test]
return predict_values
# 計算準確率
def accuracy(predict_values, actual):
correct = 0
for i in range(len(actual)):
if actual[i] == predict_values[i]:
correct += 1
return correct / float(len(actual))
if __name__ == '__main__':
seed(1)
dataSet = loadCSV('sonar-all-data.csv')
column_to_float(dataSet)#dataSet
n_folds = 5
max_depth = 15
min_size = 1
ratio = 1.0
# n_features=sqrt(len(dataSet)-1)
n_features = 15
n_trees = 10
folds = spiltDataSet(dataSet, n_folds)#先是切割數據集
scores = []
for fold in folds:
train_set = folds[
:] # 此處不能簡單地用train_set=folds,這樣用屬于引用,那么當train_set的值改變的時候,folds的值也會改變,所以要用復制的形式。(L[:])能夠復制序列,D.copy() 能夠復制字典,list能夠生成拷貝 list(L)
train_set.remove(fold)#選好訓練集
# print len(folds)
train_set = sum(train_set, []) # 將多個fold列表組合成一個train_set列表
# print len(train_set)
test_set = []
for row in fold:
row_copy = list(row)
row_copy[-1] = None
test_set.append(row_copy)
# for row in test_set:
# print row[-1]
actual = [row[-1] for row in fold]
predict_values = random_forest(train_set, test_set, ratio, n_features, max_depth, min_size, n_trees)
accur = accuracy(predict_values, actual)
scores.append(accur)
print ('Trees is %d' % n_trees)
print ('scores:%s' % scores)
print ('mean score:%s' % (sum(scores) / float(len(scores))))
其二出自https://github.com/zhaoxingfeng/RandomForest
# -*- coding: utf-8 -*-
"""
@Env: Python2.7
@Time: 2019/10/24 13:31
@Author: zhaoxingfeng
@Function:Random Forest(RF),隨機森林二分類
@Version: V1.2
參考文獻:
[1] UCI. wine[DB/OL].https://archive.ics.uci.edu/ml/machine-learning-databases/wine.
"""
import pandas as pd
import numpy as np
import random
import math
import collections
from sklearn.externals.joblib import Parallel, delayed
class Tree(object):
"""定義一棵決策樹"""
def __init__(self):
self.split_feature = None
self.split_value = None
self.leaf_value = None
self.tree_left = None
self.tree_right = None
def calc_predict_value(self, dataset):
"""通過遞歸決策樹找到樣本所屬葉子節點"""
if self.leaf_value is not None:
return self.leaf_value
elif dataset[self.split_feature] <= self.split_value:
return self.tree_left.calc_predict_value(dataset)
else:
return self.tree_right.calc_predict_value(dataset)
def describe_tree(self):
"""以json形式打印決策樹,方便查看樹結構"""
if not self.tree_left and not self.tree_right:
leaf_info = "{leaf_value:" + str(self.leaf_value) + "}"
return leaf_info
left_info = self.tree_left.describe_tree()
right_info = self.tree_right.describe_tree()
tree_structure = "{split_feature:" + str(self.split_feature) + \
",split_value:" + str(self.split_value) + \
",left_tree:" + left_info + \
",right_tree:" + right_info + "}"
return tree_structure
class RandomForestClassifier(object):
def __init__(self, n_estimators=10, max_depth=-1, min_samples_split=2, min_samples_leaf=1,
min_split_gain=0.0, colsample_bytree=None, subsample=0.8, random_state=None):
"""
隨機森林參數
----------
n_estimators: 樹數量
max_depth: 樹深度,-1表示不限制深度
min_samples_split: 節點分裂所需的最小樣本數量,小于該值節點終止分裂
min_samples_leaf: 葉子節點最少樣本數量,小于該值葉子被合并
min_split_gain: 分裂所需的最小增益,小于該值節點終止分裂
colsample_bytree: 列采樣設置,可取[sqrt、log2]。sqrt表示隨機選擇sqrt(n_features)個特征,
log2表示隨機選擇log(n_features)個特征,設置為其他則不進行列采樣
subsample: 行采樣比例
random_state: 隨機種子,設置之后每次生成的n_estimators個樣本集不會變,確保實驗可重復
"""
self.n_estimators = n_estimators
self.max_depth = max_depth if max_depth != -1 else float('inf')
self.min_samples_split = min_samples_split
self.min_samples_leaf = min_samples_leaf
self.min_split_gain = min_split_gain
self.colsample_bytree = colsample_bytree
self.subsample = subsample
self.random_state = random_state
self.trees = None
self.feature_importances_ = dict()
def fit(self, dataset, targets):
"""模型訓練入口"""
assert targets.unique().__len__() == 2, "There must be two class for targets!"
targets = targets.to_frame(name='label')
if self.random_state:
random.seed(self.random_state)
random_state_stages = random.sample(range(self.n_estimators), self.n_estimators)
# 兩種列采樣方式
if self.colsample_bytree == "sqrt":
self.colsample_bytree = int(len(dataset.columns) ** 0.5)
elif self.colsample_bytree == "log2":
self.colsample_bytree = int(math.log(len(dataset.columns)))
else:
self.colsample_bytree = len(dataset.columns)
# 并行建立多棵決策樹
self.trees = Parallel(n_jobs=-1, verbose=0, backend="threading")(
delayed(self._parallel_build_trees)(dataset, targets, random_state)
for random_state in random_state_stages)
def _parallel_build_trees(self, dataset, targets, random_state):
"""bootstrap有放回抽樣生成訓練樣本集,建立決策樹"""
subcol_index = random.sample(dataset.columns.tolist(), self.colsample_bytree)
dataset_stage = dataset.sample(n=int(self.subsample * len(dataset)), replace=True,
random_state=random_state).reset_index(drop=True)
dataset_stage = dataset_stage.loc[:, subcol_index]
targets_stage = targets.sample(n=int(self.subsample * len(dataset)), replace=True,
random_state=random_state).reset_index(drop=True)
tree = self._build_single_tree(dataset_stage, targets_stage, depth=0)
print(tree.describe_tree())
return tree
def _build_single_tree(self, dataset, targets, depth):
"""遞歸建立決策樹"""
# 如果該節點的類別全都一樣/樣本小于分裂所需最小樣本數量,則選取出現次數最多的類別。終止分裂
if len(targets['label'].unique()) <= 1 or dataset.__len__() <= self.min_samples_split:
tree = Tree()
tree.leaf_value = self.calc_leaf_value(targets['label'])
return tree
if depth < self.max_depth:
best_split_feature, best_split_value, best_split_gain = self.choose_best_feature(dataset, targets)
left_dataset, right_dataset, left_targets, right_targets = \
self.split_dataset(dataset, targets, best_split_feature, best_split_value)
tree = Tree()
# 如果父節點分裂后,左葉子節點/右葉子節點樣本小于設置的葉子節點最小樣本數量,則該父節點終止分裂
if left_dataset.__len__() <= self.min_samples_leaf or \
right_dataset.__len__() <= self.min_samples_leaf or \
best_split_gain <= self.min_split_gain:
tree.leaf_value = self.calc_leaf_value(targets['label'])
return tree
else:
# 如果分裂的時候用到該特征,則該特征的importance加1
self.feature_importances_[best_split_feature] = \
self.feature_importances_.get(best_split_feature, 0) + 1
tree.split_feature = best_split_feature
tree.split_value = best_split_value
tree.tree_left = self._build_single_tree(left_dataset, left_targets, depth+1)
tree.tree_right = self._build_single_tree(right_dataset, right_targets, depth+1)
return tree
# 如果樹的深度超過預設值,則終止分裂
else:
tree = Tree()
tree.leaf_value = self.calc_leaf_value(targets['label'])
return tree
def choose_best_feature(self, dataset, targets):
"""尋找最好的數據集劃分方式,找到最優分裂特征、分裂閾值、分裂增益"""
best_split_gain = 1
best_split_feature = None
best_split_value = None
for feature in dataset.columns:
if dataset[feature].unique().__len__() <= 100:
unique_values = sorted(dataset[feature].unique().tolist())
# 如果該維度特征取值太多,則選擇100個百分位值作為待選分裂閾值
else:
unique_values = np.unique([np.percentile(dataset[feature], x)
for x in np.linspace(0, 100, 100)])
# 對可能的分裂閾值求分裂增益,選取增益最大的閾值
for split_value in unique_values:
left_targets = targets[dataset[feature] <= split_value]
right_targets = targets[dataset[feature] > split_value]
split_gain = self.calc_gini(left_targets['label'], right_targets['label'])
if split_gain < best_split_gain:
best_split_feature = feature
best_split_value = split_value
best_split_gain = split_gain
return best_split_feature, best_split_value, best_split_gain
@staticmethod
def calc_leaf_value(targets):
"""選擇樣本中出現次數最多的類別作為葉子節點取值"""
label_counts = collections.Counter(targets)
major_label = max(zip(label_counts.values(), label_counts.keys()))
return major_label[1]
@staticmethod
def calc_gini(left_targets, right_targets):
"""分類樹采用基尼指數作為指標來選擇最優分裂點"""
split_gain = 0
for targets in [left_targets, right_targets]:
gini = 1
# 統計每個類別有多少樣本,然后計算gini
label_counts = collections.Counter(targets)
for key in label_counts:
prob = label_counts[key] * 1.0 / len(targets)
gini -= prob ** 2
split_gain += len(targets) * 1.0 / (len(left_targets) + len(right_targets)) * gini
return split_gain
@staticmethod
def split_dataset(dataset, targets, split_feature, split_value):
"""根據特征和閾值將樣本劃分成左右兩份,左邊小于等于閾值,右邊大于閾值"""
left_dataset = dataset[dataset[split_feature] <= split_value]
left_targets = targets[dataset[split_feature] <= split_value]
right_dataset = dataset[dataset[split_feature] > split_value]
right_targets = targets[dataset[split_feature] > split_value]
return left_dataset, right_dataset, left_targets, right_targets
def predict(self, dataset):
"""輸入樣本,預測所屬類別"""
res = []
for _, row in dataset.iterrows():
pred_list = []
# 統計每棵樹的預測結果,選取出現次數最多的結果作為最終類別
for tree in self.trees:
pred_list.append(tree.calc_predict_value(row))
pred_label_counts = collections.Counter(pred_list)
pred_label = max(zip(pred_label_counts.values(), pred_label_counts.keys()))
res.append(pred_label[1])
return np.array(res)
if __name__ == '__main__':
df = pd.read_csv("source/wine.txt")
df = df[df['label'].isin([1, 2])].sample(frac=1, random_state=66).reset_index(drop=True)
clf = RandomForestClassifier(n_estimators=5,
max_depth=5,
min_samples_split=6,
min_samples_leaf=2,
min_split_gain=0.0,
colsample_bytree="sqrt",
subsample=0.8,
random_state=66)
train_count = int(0.7 * len(df))
feature_list = ["Alcohol", "Malic acid", "Ash", "Alcalinity of ash", "Magnesium", "Total phenols",
"Flavanoids", "Nonflavanoid phenols", "Proanthocyanins", "Color intensity", "Hue",
"OD280/OD315 of diluted wines", "Proline"]
clf.fit(df.loc[:train_count, feature_list], df.loc[:train_count, 'label'])
from sklearn import metrics
print(metrics.accuracy_score(df.loc[:train_count, 'label'], clf.predict(df.loc[:train_count, feature_list])))
print(metrics.accuracy_score(df.loc[train_count:, 'label'], clf.predict(df.loc[train_count:, feature_list])))
0x10 參考
統計學中“最簡單”的Bootstrap方法介紹及其應用 https://blog.csdn.net/SunJW_2017/article/details/79160369
分類器組合方法Bootstrap, Boosting, Bagging, 隨機森林(一)https://blog.csdn.net/zjsghww/article/details/51591009
集成學習算法與Boosting算法原理 https://baijiahao.baidu.com/s?id=1619984901377076475
xgboost的原理沒你想像的那么難 https://www.jianshu.com/p/7467e616f227
為什么沒有人把 boosting 的思路應用在深度學習上? https://www.zhihu.com/question/53257850
集成學習算法與Boosting算法原理 https://baijiahao.baidu.com/s?id=1619984901377076475
集成學習法之bagging方法和boosting方法 https://blog.csdn.net/qq_30189255/article/details/51532442
總結:Bootstrap(自助法),Bagging,Boosting(提升) https://www.jianshu.com/p/708dff71df3a
https://blog.csdn.net/zjsghww/article/details/51591009
集成學習(Ensemble Learning) https://blog.csdn.net/wydbyxr/article/details/82259728
Adaboost入門教程——最通俗易懂的原理介紹 http://www.uml.org.cn/sjjmwj/2019030721.asp
Boosting和Bagging: 如何開發一個魯棒的機器學習算法 https://ai.51cto.com/art/201906/598160.htm
為什么bagging降低方差,boosting降低偏差?https://blog.csdn.net/sinat_25394043/article/details/104119469
bagging與boosting兩種集成模型的偏差bias以及方差variance 的理解 https://blog.csdn.net/shenxiaoming77/article/details/53894973
用通俗易懂的方式剖析隨機森林 https://blog.csdn.net/cg896406166/article/details/83796557
python實現隨機森林 https://blog.csdn.net/colourful_sky/article/details/82082854
https://github.com/zhaoxingfeng/RandomForest
數據分析(工具篇)——Boosting https://zhuanlan.zhihu.com/p/26215100
知識篇——基于AdaBoost的分類問題 https://www.jianshu.com/p/a6426f4c4e64
數據挖掘面試題之梯度提升樹 https://www.jianshu.com/p/0e5ccc88d2cb
浙公網安備 33010602011771號