# [白話解析] 通俗解析整合學習之bagging,boosting & 隨機森林 ## 0x00 摘要 本文將盡量使用通俗易懂的方式,儘可能不涉及數學公式,而是從整體的思路上來看,運用感性直覺的思考來解釋 **整合學習**。並且從名著中延伸了具體應用場景來幫助大家深入這個概念。 在機器學習過程中,會遇到很多晦澀的概念,相關數學公式很多,大家理解起來很有困難。遇到類似情況,我們應該多從直覺角度入手思考,用類比或者舉例來**附會**,這樣往往會有更好的效果。 我在講解論述過程中給自己的要求是:在生活中或者名著中找一個例子,然後用自己的話語闡述出來。 ## 0x01 相關概念 ### 1. 偏置( bias ) vs 方差( variance ) - 平方偏置( bias ),表示所有資料集的平均預測與預期的迴歸函式之間的差異。 - 方差( variance ),度量了對於單獨的資料集,模型所給出的解在平均值附近波動的情況, **方差** 度量了同樣大小的訓練集的變動所導致的學習效能的變化,度量了在面對同樣規模的不同訓練集時,學習演算法的估計結果發生變動的程度(相關於觀測樣本的誤差,刻畫了一個學習演算法的精確性和特定性:一個高的方差意味著一個弱的匹配),即刻畫了資料擾動所造成的影響。 **偏置** 度量了學習演算法期望預測與真實結果的偏離程度,度量了某種學習演算法的平均估計結果所能逼近學習目標的程度(獨立於訓練樣本的誤差,刻畫了匹配的準確性和質量:一個高的偏置意味著一個壞的匹配),即刻畫了學習演算法本身的擬合能力。 網上的比較好的例子是那個標靶的例子,大家可以去看看 - High Variance, low bias 對應就是點都打在靶心附近,但是散亂,所以瞄的是準的,但手不一定穩。 - Low variance, high bias 對應就是點都打的很集中,但不一定是靶心附近,手很穩,但是瞄的不準。 這裡試圖用水滸傳來解釋下這兩個概念。 ### 2. 用梁山為例通俗說方差 ```java 比如說五虎將,武力平均值就是呼延灼,其下是秦明,董平;其上是林沖,關勝。 假設武力值是 秦明 95,董平 96,呼延灼 97,關勝 98,林沖 99 針對這個樣本,其方差就是秦明,董平,林沖,關勝這四人的武力值和呼延灼差距多少。 如果都和呼延灼需要大戰八百回合才能分出勝負,那麼說明方差小。如果大戰三百合就能分出勝負,那說明方差大。 ``` ### 3. 用梁山為例通俗說偏置 ```java 假設馬軍八驃騎的武力值如下:徐寧 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 則將主要生成偏置比其組成部分更低的強模型(即使方差也可以被減小)。 #### 我們可以看看在水滸傳如何應用結合策略 ```java 梁山要打某座州府,究竟用什麼辦法呢? 聽吳學究的嘛? 吳學究在生辰綱的表現實在不行,漏洞太多。打大名府等也基本就是一招:事先混進城裡,然後裡應外合。 所以還是應該集思廣益,採用整合學習。 可以採用兩個辦法 辦法1 神機軍師朱武,混江龍李俊,智多星吳用,入雲龍公孫勝,混世魔王樊瑞五個人都分別出一個主意,然後投票選出一個綜合方案。 方法2 吳用先出一個主意,然後朱武針對這個主意做些調整補漏,給出了一個新主意。李俊再針對朱武的主意進行修補,給出了第三個主意...... 最後給出一個最終主意。 辦法1 就是bagging方式的近似模擬 辦法2 就是boosting方式的近似模擬 ``` ## 0x03 Bootstrap 首先需要介紹下Bootstrap,這個其實不屬於整合學習,而是統計學的一種方法,屬於整合學習的先驅。 Boostrap是靴子的帶子的意思,名字來源於“pull up your own boostraps”,意思是通過拉靴子提高自己,本來的意思是不可能發生的事情,但後來發展成通過自己的努力讓事情變得更好。放在組合分類器這裡,意思就是通過分類器自己提高分類的效能。 Boostrap是一種抽樣方法。 人們希望獲取整體的全部資訊,因為這樣就可以做到“運籌帷幄”——整體都知道了,還有什麼樣本是我們不能掌握的嗎?而實際上獲取整體的資訊是很難的,甚至是不可能的,所以才有了統計學中的“抽樣”。也就是說,我們只能獲取整體中的某些樣本的資訊,人們期望可以通過這些有限的樣本資訊來儘可能準確地估計總體,從而為決策提供依據。而Bootstrap方法認為,既然得到的樣本是從總體中“抽取”的,那麼為什麼不可以把這些樣本當做一個整體,從中進行***有放回地再抽取***呢?這種方法看似簡單,而實際上卻是十分有效的。 具體的方法是: ```java (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思想實現的演算法,需要解決兩個主要問題。 1. 如何選擇一組有不同優缺點的弱學習器,使得它們可以相互彌補不足。 2. 如何組合弱學習器的輸出以獲得整體的更好的決策表現。 和這兩個問題對應的是加法模型和前向分步演算法。 - 加法模型就是說強分類器由一系列弱分類器線性相加而成。 - 前向分步就是說在訓練過程中,下一輪迭代產生的分類器是在上一輪的基礎上訓練得來的。 **前向分佈演算法**說:“我可以提供一套框架,不管基函式和損失函式是什麼形式,只要你的模型是加法模型,就可以按照我的框架的指導,去求解。” 也就是說,前向分步演算法提供了一種學習加法模型的普遍性方法,不同形式的基函式、不同形式的損失函式都可以用這種普遍性方法去求出加法模型的最優化引數,它是一種元演算法。 **前向分步演算法的思路是:加法模型中一共有M個基函式以及與之相應的M個係數,可以從前往後,每次學習一個基函式及其係數。** ### 2. AdaBoost的解決方案 #### 選擇弱學習器 為了解決**第一個問題**,AdaBoost的辦法是:每輪結束時改變樣本的權值。這樣AdaBoost讓每一個新加入的弱學習器都體現出一些新的資料中的模式。 為了實現這一點,AdaBoost為每一個訓練樣本維護一個權重分佈。即對任意一個樣本 xi 都有一個分佈 D(i) 與之對應,以表示這個樣本的重要性。 當衡量弱學習器的表現時,AdaBoost會考慮每個樣本的權重。權重較大的誤分類樣本會比權重較小的誤分類樣本貢獻更大的訓練錯誤率。為了獲得更小的加權錯誤率,弱分類器必須更多的聚焦於高權重的樣本,保證對它們準確的預測。 通過修改樣本的權重 D(i) ,也就改變了樣本的概率分佈,將關注點放在被錯誤分類的樣本上,減小上一輪被正確分類的樣本權值,提高那些被錯誤分類的樣本權值。這樣就可以引導弱學習器學習訓練樣本的不同部分。 訓練時候,我們是利用前一輪迭代弱學習器的誤差率來更新訓練集的權重,這樣一輪輪的迭代下去。 #### 組合弱學習器 現在,我們獲得了一組已訓練的擁有不同優缺點的弱學習器,如何有效的組合它們,使得相互優勢互補來產生更準確的整體預測效果?這就是**第二個問題**。 對於第二個問題,AdaBoost採用加權多數表決的方法,加大分類誤差率小的弱分類器的權重,減小分類誤差率大的弱分類器的權重。這個很好理解,正確率高分得好的弱分類器在強分類器中當然應該有較大的發言權。 每一個弱學習器是用不同的權重分佈訓練出來的,我們可以看做給不同的弱學習器分配了不同的任務,每個弱學習器都盡力完成給定的任務。直覺上看,當我們要把每個弱學習器的判斷組合到最終的預測結果中時,如果弱學習器在之前的任務中表現優異,我們會更多的相信它,相反,如果弱學習器在之前的任務中表現較差,我們就更少的相信它。 換句話說,我們會加權地組合弱學習器,給每個弱學習器賦予一個表示可信程度的值 wi ,這個值取決於它在被分配的任務中的表現,表現越好 wi 越大,反之越小。 ## 0x08 從水滸傳中派生一個例子看如何使用整合學習 我們找一個例子來詳細看看如何使用整合學習這個概念。現在梁山需要投票來決定是否接受招安。宋江說了,我們要科學民主的進行決策,所以我們採用最新科技:整合學習。 ### 1. Bagging 首先考慮的是bagging方法 ```java 如果採用了bagging方法。樣本有放回,而且投票可以並行。 每次抽取 5 個人投票是否接受。 第一次抽出 徐寧 、索超 、朱仝 、花榮、楊志。則 5 票都是 接受招安 第二次抽出 魯智深,武松,朱貴,劉唐,李逵。則 5 票都是 反對招安 第三次抽出 徐寧 、索超,魯智深,武松,阮小二。則 2 票接受,3 票反對,則這次樣本是 反對招安。 最後綜合三次的結果是:反對招安。 現在情況已經對招安不利了,如果再並行進行投票,那麼對結果就更無法估計。 這樣的話,對於是否招安就真的是梁山群體民主評議,公明哥哥和吳學究沒辦法後臺黑箱操控了。 ``` 如果宋江想黑箱操作,他就不能採用bagging方法,而是需要選擇boosting,因為這個演算法的訓練集的選擇不是獨立的,每一次選擇的訓練集都依賴於上一次學習的結果,所以利於宋江暗箱操縱。 ### 2. Boosting 於是宋江決定採取Boosting方法,大家可以看看宋江如何使用boosting一步一步調整bias以達到最好擬合 “接受招安” 這個最終期望的。 ```java "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)。 ```java 從我們例子能看出來。 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 ```python # -*- 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 ```python # -*- 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 [機器學習演算法GBDT](https://www.cnblogs.com/bnuvincent/p/9693190.html) [終於有人說清楚了--XGBoost演算法](https://www.cnblogs.com/mantch/p/11164221.html) 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.cnblogs.com/ScorpioLu/p/8295990.html) 知識篇——基於AdaBoost的分類問題 https://www.jianshu.com/p/a6426f4c4e64 資料探勘面試題之梯度提升樹 https://www.jianshu.com/p/0e5c