隨機森林 RF 算法的原理(一)

隨機森林算法是一種重要的基於 Bagging 集成學習方法,可用來做分類、迴歸等問題。隨機森林算法是由一系列的決策樹組成,他通過自助法(Bootstrap)重採樣技術,從原始訓練樣本集中有放回的重複隨機抽取 m 個樣本,生成新的訓練樣本集合,然後根據自主樣本集生成 k 個分類樹組成隨機森林,新數據的分類結果按分類樹投票多少形成的分數定。其實質是對決策樹算法的一種改進,將多個決策樹合併在一起,每棵樹的建立依賴於一個獨立抽取得樣本,森林中的每棵樹具有相同的分佈,分類誤差取決於每一棵樹的分類能力和它們之間的相關性。

要了解隨機森林,先要了解集成學習和決策樹。

一、集成學習

處理前面複雜的分類問題,我們試圖尋找一個高效的算法處理,但通常會花費很多資源。集成學習是訓練多個分類器,利用這些分類器來解決同一個問題。相當與“三個臭皮匠賽過一個諸葛亮”(哈哈哈,我看到這的時候憋不住了,哈哈哈哈)。

集成學習:通過構建並結合多個學習器來完成學習任務,有時稱爲多分類器系統,其結構如下圖所示。

先產生一組個體學習器,在利用某種策略將其結合起來,個體學習器通常由一個現有的學習算法從訓練數據產生,例如C4.5決策樹算法、BP神經網絡算法等。這些集成中只包含同種類型的個體學習器,即同質,同質集成中的個體學習器亦稱基學習器,相應的學習算法稱爲基學習算法。集成中也可以包含不同類型的個體學習器,這時相應的學習算法不再是基學習算法。要獲得好的集成,個體學習器至少不差於弱學習器(指泛化性能略優於隨機猜測的學習器),即學習器不能太壞、要有多樣性、學習器之間具有差距。在集成學習方法中,其泛化能力比單個學習算法的強,所以集成學習的很多理論研究都是針對弱學習器進行的。根據多個分類器學習方式的不同,可以分成並行生成的 Bagging 算法和串行生成的 Boosting 算法。

集成學習有如下的特點: 
(1)將多個分類方法聚集在一起,以提高分類的準確率,聚集的算法可以是不同,也可以相同;
(2)集成學習法由訓練數據構建一組基分類器,然後通過對每個基分類器的預測進行投票來進行分類; 
(3)集成學習不算一種分類器,而是一種分類器結合的方法; 
(4)通常一個集成分類器的分類性能會好於單個分類器; 
(5)如果把單個分類器比作一個決策者的話,集成學習的方法就相當於多個決策者共同進行一項決策。

Bagging 算法:它的特點是各個弱學習器之間沒有依賴關係,可以並行擬合,隨機森林算法就基於 Bagging 算法。該算法是通過對訓練樣本隨機有放回的抽取,產生多個訓練數據的子集,並在每一個訓練記得子集上訓練一個分類器,最終的分類結果是由多個分類器的分類結果投票產生。訓練一個Bagging 集成與直接使用基學習算法訓練一個學習器的複雜度同階,這說明Bagging 是一個很高效的集成學習算法。Bagging算法主要關注降低方差,因此它在不剪枝決策樹、神經網絡等易受樣本擾動的學習器上效果更爲明顯。

爲什麼要有放回的抽取?

爲獲得好的集成,我們希望個體學習器不能太差,如果採樣出的每個子集都完全不同,則每個基學習器只能用到一小部分訓練數據,甚至不足以進行有效學習,無法確保產生出比較好的基學習器,爲解決這個問題使用相互有交疊的採樣子集。

所以通過m次有放回的隨機採樣操作,如我們得到含m個樣本的採樣集,初始訓練集中有的樣本在採樣集多次出現,下有的則從未出現,初始訓練集中約有63.2%的樣本出現在採樣集中。然後基於每個採樣集訓練出一個基學習器,所再將這些基學習器結合,這就是Bagging 的基本流程,圖下圖所示。

Boosting 算法:是一族可將弱學習器提升爲強學習器的算法。它是通過順序的給訓練集中的數據項重新加權創造不同的基礎學習器,先從初始訓練集訓練出一個基學習器,再根據基學習器的表現對訓練樣本分佈進行調整,使得先前基學習器做錯的訓練樣本在後續受到更多的關注,然後基於調整後的樣本分佈來訓練下一個基學習器。根據每次訓練集之中每個樣本的分類是否正確,以及上次的總體分類的準確率,來確定每個樣本的權值。將修改過權值的新數據集送給下層分類器進行訓練,最後將每次訓練得到的分類器最後融合起來,作爲最後的決策分類器。Boosting 算法有很多版本,目前使用最廣泛的是 AdaBoost  算法和 GBDT 算法。Boosting 算法主要關注降低偏差,因此Boosting 算法能基於泛化性能相當弱的學習器構建出很強的集成。如下圖Boosting 算法的整個過程:

二、決策樹

決策樹算法是一種常用的機器學習算法,在分類問題中,決策樹算法通過樣本中某一維屬性的值,將樣本劃分到不同的類別中。決策樹是一種樹形結構,其中每個內部節點表示一個屬性上的測試,每個分支代表一個測試輸出,每個葉節點代表一種類別。決策樹是一個預測模型,他代表的是對象屬性與對象值之間的一種映射關係。樹中每個節點表示某個對象,而每個分叉路徑則代表的某個可能的屬性值,而每個葉結點則對應從根節點到該葉節點所經歷的路徑所表示的對象的值。

決策樹學習也是資料探勘中一個普通的方法,每個決策樹可以依靠對源數據庫的分割進行數據測試。這個過程可以遞歸式的對樹進行修剪。 當不能再進行分割或一個單獨的類可以被應用於某一分支時,遞歸過程就完成了。另外隨機森林分類器將許多決策樹結合起來以提升分類的正確率。

1.在決策樹算法中,有以下標準:信息增益、增益率和基尼指數。

信息增益:對於給定的數據集,劃分前後信息熵的減少量稱爲信息增益,即

igain(D,A)=Entropy(D)-\sum_{p=1}^{P}\frac{\left | D_{p} \right |}{\left | D \right |}Entropy(D_{p})

其中Entropy(D)表示數據集D的信息熵,\left | D_{p} \right | 表示的是屬於第p類的樣本的個數。

Entropy(D)=-\sum_{k=1}^{K}p_{k}log_{2}p_{k}

當把樣本劃分爲兩個獨立的子數據集D1和D2時,信息熵爲:

Entropy(D)=\frac{\left |D_{1} \right |}{\left |D \right |}Entropy(D_{1})+\frac{\left |D_{2} \right |}{\left |D \right |}Entropy(D_{2})

信息熵表示的數據集中的不純度,信息熵較小表明數據集純度提升。

增益率:作爲選擇最優化分屬性的方法,計算方法:

gain_ratio(D,A)=\frac{igain(D,A)}{IV(A)}

其中IV(A)稱爲特徵A的固有值,

IV(A)=-\sum_{p=1}^{P}\frac{\left | D_{p} \right |}{\left | D \right |}log_{2}\frac{\left | D_{p} \right |}{\left | D \right |}

基尼指數:也可以選擇最優的劃分屬性,在數據集D中,假設有K個分類,則樣本屬於第k個類的概率爲pk。則計算方法:

Gini(p)=1-\sum_{k=1}^{K}p_{k}^{2}

2.數據劃分停止的標準

  • 節點中的樣本數小於給定閾值;
  • 樣本集中的基尼指數小於給定閾值(樣本基本屬於同一類);
  • 沒有更多特徵。

3.CART分類樹算法

決策樹模型主要有ID3算法,C4.5算法和CART算法。其中CART算法與ID3算法,C4.5算法不同的是它能處理分類問題也能處理迴歸問題。

在CART分類樹算法中,利用Gini指數作爲劃分標準,通過樣本中的特徵,對樣本進行劃分,直到所有的葉節點中的所有樣本都爲同一類爲止。

CART分類樹構建過程:

  1. 計算現有特徵對該數據集的基尼指數,對於每一個特徵A,可以對樣本點A是否爲a可以將數據集D分成數據集D1,D2;
  2. 對於所有的特徵A和所有可能的切分點a,選擇基尼指數最小的特徵以及相對應的切分點作爲最優特徵和最佳切分點。 
  3. 對最優子樹遞歸調用1.2到滿足停止條件。 
  4. 生成CART分類樹。 

下面就CART算法在python中試試:
 

# -*- coding: utf-8 -*-
"""
Created on Thu Mar 14 10:37:24 2019

@author: 2018061801
"""
#cart_train.py
import numpy as np
import pickle as pk
class node:
    '''樹的節點的類
    '''
    def __init__(self, fea=-1, value=None, results=None, right=None, left=None):
        self.fea = fea  # 用於切分數據集的屬性的列索引值
        self.value = value  # 設置劃分的值
        self.results = results  # 存儲葉節點的值
        self.right = right  # 右子樹
        self.left = left  # 左子樹
        
def load_data(data_file):
    '''導入訓練數據
    input:  data_file(string):保存訓練數據的文件
    output: data(list):訓練數據
    '''
    data = []
    f = open(data_file)
    for line in f.readlines():
        sample = []
        lines = line.strip().split("\t")
        for x in lines:
            sample.append(float(x))  # 轉換成float格式
        data.append(sample)
    f.close()  
    return data  
     
def split_tree(data, fea, value):
    '''根據特徵fea中的值value將數據集data劃分成左右子樹
    input:  data(list):訓練樣本
            fea(float):需要劃分的特徵index
            value(float):指定的劃分的值
    output: (set_1, set_2)(tuple):左右子樹的聚合
    '''
    set_1 = []  # 右子樹的集合
    set_2 = []  # 左子樹的集合
    for x in data:
        if x[fea] >= value:
            set_1.append(x)
        else:
            set_2.append(x)
    return (set_1, set_2)

def leaf(dataSet):
    '''計算葉節點的值
    input:  dataSet(list):訓練樣本
    output: np.mean(data[:, -1])(float):均值
    '''
    data = np.mat(dataSet)
    return np.mean(data[:, -1])

def err_cnt(dataSet):
    '''迴歸樹的劃分指標
    input:  dataSet(list):訓練數據
    output: m*s^2(float):總方差
    '''
    data = np.mat(dataSet)
    return np.var(data[:, -1]) * np.shape(data)[0]

def build_tree(data, min_sample, min_err):
    '''構建樹
    input:  data(list):訓練樣本
            min_sample(int):葉子節點中最少的樣本數
            min_err(float):最小的error
    output: node:樹的根結點
    '''
    # 構建決策樹,函數返回該決策樹的根節點
    if len(data) <= min_sample:
        return node(results=leaf(data)) 

    # 1、初始化
    best_err = err_cnt(data)
    bestCriteria = None  # 存儲最佳切分屬性以及最佳切分點
    bestSets = None  # 存儲切分後的兩個數據集
    # 2、開始構建CART迴歸樹
    feature_num = len(data[0]) - 1
    for fea in range(0, feature_num):
      feature_values = {}
      for sample in data:
            feature_values[sample[fea]] = 1
      for value in feature_values.keys():
           # 2.1、嘗試劃分
            (set_1, set_2) = split_tree(data, fea, value)
            if len(set_1) < 2 or len(set_2) < 2:
                continue
            # 2.2、計算劃分後的error值
            now_err = err_cnt(set_1) + err_cnt(set_2)
            # 2.3、更新最優劃分
            if now_err < best_err and len(set_1) > 0 and len(set_2) > 0:
                best_err = now_err
                bestCriteria = (fea, value)
                bestSets = (set_1, set_2)

    # 3、判斷劃分是否結束
    if best_err > min_err:
        right = build_tree(bestSets[0], min_sample, min_err)
        left = build_tree(bestSets[1], min_sample, min_err)
        return node(fea=bestCriteria[0], value=bestCriteria[1], \
                    right=right, left=left)
    else:
        return node(results=leaf(data))  # 返回當前的類別標籤作爲最終的類別標       

def predict(sample, tree):
    '''對每一個樣本sample進行預測
    input:  sample(list):樣本
            tree:訓練好的CART迴歸樹模型
    output: results(float):預測值
    '''
    # 1、只是樹根
    if tree.results != None:
        return tree.results
    else:
    # 2、有左右子樹
        val_sample = sample[tree.fea]  # fea處的值
        branch = None
        # 2.1、選擇右子樹
        if val_sample >= tree.value:
            branch = tree.right
        # 2.2、選擇左子樹
        else:
            branch = tree.left
        return predict(sample, branch) 

def cal_error(data, tree):
    ''' 評估CART迴歸樹模型
    input:  data(list):
            tree:訓練好的CART迴歸樹模型
    output: err/m(float):均方誤差
    '''
    m = len(data)  # 樣本的個數   
    n = len(data[0]) - 1  # 樣本中特徵的個數
    err = 0.0
    for i in range(m):
        tmp = []
        for j in range(n):
            tmp.append(data[i][j])
        pre = predict(tmp, tree)  # 對樣本計算其預測值
        # 計算殘差
        err += (data[i][-1] - pre) * (data[i][-1] - pre)
    return err / m

def save_model(regression_tree, result_file):
    '''將訓練好的CART迴歸樹模型保存到本地
    input:  regression_tree:迴歸樹模型
            result_file(string):文件名
    '''
    with open(result_file, 'wb+') as f:
        pk.dump(regression_tree, f)

if __name__ == "__main__":
    # 1、導入訓練數據
    print ("----------- 1、load data -------------")
    data = load_data("D:/anaconda4.3/spyder_work/sine.txt")
    # 2、構建CART樹
    print ("----------- 2、build CART ------------")
    regression_tree = build_tree(data, 30, 0.3)
    # 3、評估CART樹
    print ("----------- 3、cal err -------------")
    err = cal_error(data, regression_tree)
    print ("\t--------- err : ", err)
    # 4、保存最終的CART模型
    print ("----------- 4、save result -----------")  
    save_model(regression_tree, "regression_tree")

 

#rain.txt
0.190350	0.878049
0.306657	-0.109413
0.017568	0.030917
0.122328	0.951109
0.076274	0.774632
0.614127	-0.250042
0.220722	0.807741
0.089430	0.840491
0.278817	0.342210
0.520287	-0.950301
0.726976	0.852224
0.180485	1.141859
0.801524	1.012061
0.474273	-1.311226
0.345116	-0.319911
0.981951	-0.374203
0.127349	1.039361
0.757120	1.040152
0.345419	-0.429760
0.314532	-0.075762
0.250828	0.657169
0.431255	-0.905443
0.386669	-0.508875
0.143794	0.844105
0.470839	-0.951757
0.093065	0.785034
0.205377	0.715400
0.083329	0.853025
0.243475	0.699252
0.062389	0.567589
0.764116	0.834931
0.018287	0.199875
0.973603	-0.359748
0.458826	-1.113178
0.511200	-1.082561
0.712587	0.615108
0.464745	-0.835752
0.984328	-0.332495
0.414291	-0.808822
0.799551	1.072052
0.499037	-0.924499
0.966757	-0.191643
0.756594	0.991844
0.444938	-0.969528
0.410167	-0.773426
0.532335	-0.631770
0.343909	-0.313313
0.854302	0.719307
0.846882	0.916509
0.740758	1.009525
0.150668	0.832433
0.177606	0.893017
0.445289	-0.898242
0.734653	0.787282
0.559488	-0.663482
0.232311	0.499122
0.934435	-0.121533
0.219089	0.823206
0.636525	0.053113
0.307605	0.027500
0.713198	0.693978
0.116343	1.242458
0.680737	0.368910
0.484730	-0.891940
0.929408	0.234913
0.008507	0.103505
0.872161	0.816191
0.755530	0.985723
0.620671	0.026417
0.472260	-0.967451
0.257488	0.630100
0.130654	1.025693
0.512333	-0.884296
0.747710	0.849468
0.669948	0.413745
0.644856	0.253455
0.894206	0.482933
0.820471	0.899981
0.790796	0.922645
0.010729	0.032106
0.846777	0.768675
0.349175	-0.322929
0.453662	-0.957712
0.624017	-0.169913
0.211074	0.869840
0.062555	0.607180
0.739709	0.859793
0.985896	-0.433632
0.782088	0.976380
0.642561	0.147023
0.779007	0.913765
0.185631	1.021408
0.525250	-0.706217
0.236802	0.564723
0.440958	-0.993781
0.397580	-0.708189
0.823146	0.860086
0.370173	-0.649231
0.791675	1.162927
0.456647	-0.956843
0.113350	0.850107
0.351074	-0.306095
0.182684	0.825728
0.914034	0.305636
0.751486	0.898875
0.216572	0.974637
0.013273	0.062439
0.469726	-1.226188
0.060676	0.599451
0.776310	0.902315
0.061648	0.464446
0.714077	0.947507
0.559264	-0.715111
0.121876	0.791703
0.330586	-0.165819
0.662909	0.379236
0.785142	0.967030
0.161352	0.979553
0.985215	-0.317699
0.457734	-0.890725
0.171574	0.963749
0.334277	-0.266228
0.501065	-0.910313
0.988736	-0.476222
0.659242	0.218365
0.359861	-0.338734
0.790434	0.843387
0.462458	-0.911647
0.823012	0.813427
0.594668	-0.603016
0.498207	-0.878847
0.574882	-0.419598
0.570048	-0.442087
0.331570	-0.347567
0.195407	0.822284
0.814327	0.974355
0.641925	0.073217
0.238778	0.657767
0.400138	-0.715598
0.670479	0.469662
0.069076	0.680958
0.294373	0.145767
0.025628	0.179822
0.697772	0.506253
0.729626	0.786514
0.293071	0.259997
0.531802	-1.095833
0.487338	-1.034481
0.215780	0.933506
0.625818	0.103845
0.179389	0.892237
0.192552	0.915516
0.671661	0.330361
0.952391	-0.060263
0.795133	0.945157
0.950494	-0.071855
0.194894	1.000860
0.351460	-0.227946
0.863456	0.648456
0.945221	-0.045667
0.779840	0.979954
0.996606	-0.450501
0.632184	-0.036506
0.790898	0.994890
0.022503	0.386394
0.318983	-0.152749
0.369633	-0.423960
0.157300	0.962858
0.153223	0.882873
0.360068	-0.653742
0.433917	-0.872498
0.133461	0.879002
0.757252	1.123667
0.309391	-0.102064
0.195586	0.925339
0.240259	0.689117
0.340591	-0.455040
0.243436	0.415760
0.612755	-0.180844
0.089407	0.723702
0.469695	-0.987859
0.943560	-0.097303
0.177241	0.918082
0.317756	-0.222902
0.515337	-0.733668
0.344773	-0.256893
0.537029	-0.797272
0.626878	0.048719
0.208940	0.836531
0.470697	-1.080283
0.054448	0.624676
0.109230	0.816921
0.158325	1.044485
0.976650	-0.309060
0.643441	0.267336
0.215841	1.018817
0.905337	0.409871
0.154354	0.920009
0.947922	-0.112378
0.201391	0.768894

結果:

----------- 1、load data -------------
----------- 2、build CART ------------
----------- 3、cal err -------------
        --------- err :  0.017472201263739866
----------- 4、save result -----------

 下節將講隨機森林及其實踐

 

 

參考文獻:趙志勇《python 機器學習算法》

周志華《機器學習》

 

 

 

 

 

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