隨機森林在sklearn中的實現


隨機森林在sklearn中的實現

1 概述

1.1 集成算法概述

集成學習(ensemble learning)是時下非常流行的機器學習算法,它本身不是一個單獨的機器學習算法,而是通 過在數據上構建多個模型,集成所有模型的建模結果。基本上所有的機器學習領域都可以看到集成學習的身影,在
現實中集成學習也有相當大的作用,它可以用來做市場營銷模擬的建模,統計客戶來源,保留和流失,也可用來預
測疾病的風險和病患者的易感性。在現在的各種算法競賽中,隨機森林,梯度提升樹(GBDT),Xgboost 等集成 算法的身影也隨處可見,可見其效果之好,應用之廣。

集成算法的目標:
集成算法會考慮多個評估器的建模結果,匯總之后得到一個綜合的結果,以此來獲取比單個模型更好的回歸或分類表現。

多個模型集成成為的模型叫做集成評估器(ensemble estimator),組成集成評估器的每個模型都叫做基評估器
(base estimator)。通常來說,有三類集成算法:裝袋法(Bagging),提升法(Boosting)和 stacking。

ensemble

裝袋法的核心思想是構建多個相互獨立的評估器,然后對其預測進行平均或多數表決原則來決定集成評估器的結
果。裝袋法的代表模型就是隨機森林。
提升法中,基評估器是相關的,是按順序一一構建的。其核心思想是結合弱評估器的力量一次次對難以評估的樣本
進行預測,從而構成一個強評估器。提升法的代表模型有 Adaboost 和梯度提升樹。

1.2 sklearn 中的集成算法

  • sklearn 中的集成算法模塊 ensemble

    類的功能
    ensemble.AdaBoostClassifier AdaBoost 分類
    ensemble.AdaBoostRegressor Adaboost 回歸
    ensemble.BaggingClassifier 裝袋分類器
    ensemble.BaggingRegressor 裝袋回歸器
    ensemble.ExtraTreesClassifier Extra-trees 分類(超樹,極端隨機樹)
    ensemble.ExtraTreesRegressor Extra-trees 回歸
    ensemble.GradientBoostingClassifier 梯度提升分類
    ensemble.GradientBoostingRegressor 梯度提升回歸
    ensemble.IsolationForest 隔離森林
    ensemble.RandomForestClassifier 隨機森林分類
    ensemble.RandomForestRegressor 隨機森林回歸
    ensemble.RandomTreesEmbedding 完全隨機樹的集成
    ensemble.VotingClassifier 用於不合適估算器的軟投票/多數規則分類器

    集成算法中,有一半以上都是樹的集成模型,可以想見決策樹在集成中必定是有很好的效果。在這堂課中,我們會
    以隨機森林為例,慢慢為大家揭開集成算法的神秘面紗。

2 RandomForestClassifier

class sklearn.ensemble.RandomForestClassifier (n_estimators=’10’, criterion=’gini’, max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=’auto’, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False, class_weight=None)

隨機森林是非常具有代表性的 Bagging 集成算法,它的所有基評估器都是決策樹,分類樹組成的森林就叫做隨機森 林分類器,回歸樹所集成的森林就叫做隨機森林回歸器。這一節主要講解 RandomForestClassifier,隨機森林分類 器。

2.1 重要參數

2.1.1 控制基評估器的參數

參數 含義
criterion 不純度的衡量指標,有基尼系數和信息熵兩種選擇
max_depth 樹的最大深度,超過最大深度的樹枝都會被剪掉
min_samples_leaf 一個節點在分枝后的每個子節點都必須包含至少 min_samples_leaf 個訓練樣 本,否則分枝就不會發生
min_samples_split 一個節點必須要包含至少 min_samples_split 個訓練樣本,這個節點才允許被分 枝,否則分枝就不會發生
max_features max_features 限制分枝時考慮的特征個數,超過限制個數的特征都會被舍棄, 默認值為總特征個數開平方取整
min_impurity_decrease 限制信息增益的大小,信息增益小於設定數值的分枝不會發生

單個決策樹的准確率越高,隨機森林的准確率也會越高,因為裝袋法是依賴於平均值或者少數服從多數原則來決定集成的結果的。

2.1.2 n_estimators

這是森林中樹木的數量,即基評估器的數量。這個參數對隨機森林模型的精確性影響是單調的,n_estimators 越 大,模型的效果往往越好。但是相應的,任何模型都有決策邊界,n_estimators 達到一定的程度之后,隨機森林的 精確性往往不在上升或開始波動,並且,n_estimators 越大,需要的計算量和內存也越大,訓練的時間也會越來越 長。對於這個參數,我們是渴望在訓練難度和模型效果之間取得平衡。

n_estimators 的默認值在 0.22 以前版本的 sklearn 中是 10,但是在即將更新的 0.22 版本中,這個默認值會被修正為 100。這個修正顯示出了使用者的調參傾向:要更大的 n_estimators。

樹模型的優點是簡單易懂,可視化之后的樹人人都能夠看懂,可惜隨機森林是無法被可視化的。所以為了更加直觀
地讓大家體會隨機森林的效果,我們來進行一個隨機森林和單個決策樹效益的對比。我們依然使用紅酒數據集。

  1. 導入我們需要的包

    %matplotlib inline
    from sklearn.tree import DecisionTreeClassifier
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.datasets import load_wine
    
  2. 導入需要的數據集

    wine = load_wine()
    wine.data
    wine.target
    
  3. sklearn 建模的基本流程

    from sklearn.model_selection import train_test_split
    Xtrain,Xtest,Ytrain,Ytest = train_test_split(wine.data, wine.target,test_size=0.3)
    
    clf = DecisionTreeClassifier(random_state=0)
    rfc = RandomForestClassifier(random_state=0)
    
    clf = clf.fit(Xtrain,Ytrain)
    rfc = rfc.fit(Xtrain,Ytrain)
    
    score_c = clf.score(Xtest,Ytest)
    score_r = rfc.score(Xtest,Ytest)
    
    print("Sigle Tree:{}".format(score_c)
          ,"Random Forest:{}".format(score_r)
         )
    
    Sigle Tree:0.8703703703703703 Random Forest:0.9814814814814815
    
    從這個結果我們能夠看出,隨機森林的效果要比決策樹好很多。
    
  4. 畫出隨機森林和決策樹在一組交叉驗證下的效果對比

    交叉驗證:是數據集划分為 n 分,依次取每一份做測試集,每 n-1 份做訓練集,多次訓練模型以觀測模型穩定性的方法

    # 交叉驗證
    from sklearn.model_selection import cross_val_score
    import matplotlib.pyplot as plt
    
    rfc = RandomForestClassifier(n_estimators=25)
    rfc_s = cross_val_score(rfc, wine.data, wine.target, cv=10)
    
    clf = DecisionTreeClassifier()
    clf_s = cross_val_score(clf,wine.data,wine.target,cv=10)
    
    plt.plot(range(1,11),rfc_s,label="RandomForest")
    plt.plot(range(1,11),clf_s,label="DecisionTree")
    plt.legend()
    plt.show()
    

    cross_val_clf7rft

    為了給兩條曲線添加圖例,上面代碼分別畫兩條曲線,下面是更簡單的寫法:

    label = "RandomForest" 
    for model in [RandomForestClassifier(n_estimators=25),DecisionTreeClassifier()]: 
        score = cross_val_score(model,wine.data,wine.target,cv=10) 
        print("{}:".format(label)),print(score.mean()) 
        plt.plot(range(1,11),score,label = label) 
        plt.legend() 
        label = "DecisionTree"
    plt.show()
    
  5. 畫出隨機森林和決策樹在十組交叉驗證下的效果對比

    rfc_l = []
    clf_l = []
    
    for i in range(10):
        rfc = RandomForestClassifier(n_estimators=25)
        rfc_s = cross_val_score(rfc,wine.data,wine.target,cv=10).mean()
        rfc_l.append(rfc_s)
        
        clf = DecisionTreeClassifier()
        clf_s = cross_val_score(clf,wine.data,wine.target,cv=10).mean()
        clf_l.append(clf_s)
        
    plt.plot(range(1,11),rfc_l,label="RandomForest")
    plt.plot(range(1,11),clf_l,label="DecisionTree")
    plt.legend()
    plt.show()
    

    是否有注意到,單個決策樹的波動軌跡和隨機森林一致?再次驗證了我們之前提到的,單個決策樹的准確率越高,隨機森林的准確率也會越高

  6. n_estimators 的學習曲線

    superpa = []
    for i in range(200):
        rfc = RandomForestClassifier(n_estimators=i+1,n_jobs=-1)
        rfc_s = cross_val_score(rfc,wine.data,wine.target,cv=10).mean()
        superpa.append(rfc_s)
    
    print(max(superpa),superpa.index(max(superpa)))
    plt.figure(figsize=[20,5])
    plt.plot(range(1,201),superpa)
    plt.show()
    

    n_estimators

2.1.3 random_state

隨機森林的本質是一種裝袋集成算法(bagging),裝袋集成算法是對基評估器的預測結果進行平均或用多數表決 原則來決定集成評估器的結果。在剛才的紅酒例子中,我們建立了 25 棵樹,對任何一個樣本而言,平均或多數表決原則下,當且僅當有 13 棵以上的樹判斷錯誤的時候,隨機森林才會判斷錯誤。單獨一棵決策樹對紅酒數據集的分類准確率在 0.85 上下浮動,假設一棵樹判斷錯誤的可能性為 0.2($\varepsilon $),那 20 棵樹以上都判斷錯誤的可能性是:

\[e_{random_forest}=\sum_{i=13}^{25}C_{25}^{i}{\varepsilon}^i(1-\varepsilon)^{25-i} =0.000369 \]

其中,i 是判斷錯誤的次數,也是判錯的樹的數量,\(\varepsilon\)是一棵樹判斷錯誤的概率,\((1-\varepsilon)\)是判斷正確的概率,共判對 25-i 次。采用組合,是因為 25 棵樹中,有任意 i 棵都判斷錯誤。

import numpy as np
from scipy.special import comb

np.array([comb(25,i)*(0.2**i)*((1-0.2)**(25-i)) for i in range(13,26)]).sum()

可見,判斷錯誤的幾率非常小,這讓隨機森林在紅酒數據集上的表現遠遠好於單棵決策樹。
那現在就有一個問題了:我們說袋裝法服從多數表決原則或對基分類器結果求平均,這即是說,我們默認森林中的每棵樹應該是不同的,並且會返回不同的結果。設想一下,如果隨機森林里所有的樹的判斷結果都一致(全判斷對或全判斷),那隨機森林無論應用何種集成原則來求結果,都應該無法比單棵決策樹取得更好的效果才對。但我們使用了一樣的類 DecisionTreeClassifier,一樣的參數,一樣的訓練集和測試集,為什么隨機森林里的眾多樹會有 不同的判斷結果?

問到這個問題,很多小伙伴可能就會想到了:sklearn 中的分類樹 DecisionTreeClassifier 自帶隨機性,所以隨機森 林中的樹天生就都是不一樣的。我們在講解分類樹時曾提到,決策樹從最重要的特征中隨機選擇出一個特征來進行分枝,因此每次生成的決策樹都不一樣,這個功能由參數 random_state 控制。

隨機森林中其實也有 random_state,用法和分類樹中相似,只不過在分類樹中,一個 random_state 只控制生成一 棵樹,而隨機森林中的 random_state 控制的是生成森林的模式,而非讓一個森林中只有一棵樹。

rfc = RandomForestClassifier(n_estimators=20,random_state=2)
rfc = rfc.fit(Xtrain,Ytrain)

#隨機森林的重要屬性之一:estimators,查看森林中樹的狀況
rfc.estimators_[0].random_state

for i in range(len(rfc.estimators_)):
    print(rfc.estimators_[i].random_state)

我們可以觀察到,當 random_state 固定時,隨機森林中生成是一組固定的樹,但每棵樹依然是不一致的,這是 用”隨機挑選特征進行分枝“的方法得到的隨機性。並且我們可以證明,當這種隨機性越大的時候,袋裝法的效果一般會越來越好。用袋裝法集成時,基分類器應當是相互獨立的,是不相同的。

但這種做法的局限性是很強的,當我們需要成千上萬棵樹的時候,數據不一定能夠提供成千上萬的特征來讓我們構
築盡量多盡量不同的樹。因此,除了 random_state。我們還需要其他的隨機性。

2.1.4 bootstrap & oob_score

要讓基分類器盡量都不一樣,一種很容易理解的方法是使用不同的訓練集來進行訓練,而袋裝法正是通過有放回的隨機抽樣技術來形成不同的訓練數據,bootstrap 就是用來控制抽樣技術的參數。

在一個含有 n 個樣本的原始訓練集中,我們進行隨機采樣,每次采樣一個樣本,並在抽取下一個樣本之前將該樣本 放回原始訓練集,也就是說下次采樣時這個樣本依然可能被采集到,這樣采集 n 次,最終得到一個和原始訓練集一 樣大的,n 個樣本組成的自助集。由於是隨機采樣,這樣每次的自助集和原始數據集不同,和其他的采樣集也是不 同的。這樣我們就可以自由創造取之不盡用之不竭,並且互不相同的自助集,用這些自助集來訓練我們的基分類
器,我們的基分類器自然也就各不相同了。

bootstrap 參數默認 True,代表采用這種有放回的隨機抽樣技術。通常,這個參數不會被我們設置為 False。

bootstrap

然而有放回抽樣也會有自己的問題。由於是有放回,一些樣本可能在同一個自助集中出現多次,而其他一些卻可能被忽略,一般來說,自助集大約平均會包含 63%的原始數據。因為每一個樣本被抽到某個自助集中的概率為:

\[1-(1-\frac{1}{n})^n \]

當 n 足夠大時,這個概率收斂於 1-(1/e),約等於 0.632。因此,會有約 37%的訓練數據被浪費掉,沒有參與建模, 這些數據被稱為袋外數據(out of bag data,簡寫為 oob)。除了我們最開始就划分好的測試集之外,這些數據也可 以被用來作為集成算法的測試集。也就是說,在使用隨機森林時,我們可以不划分測試集和訓練集,只需要用袋外數據來測試我們的模型即可。當然,這也不是絕對的,當 n 和 n_estimators 都不夠大的時候,很可能就沒有數據掉 落在袋外,自然也就無法使用 oob 數據來測試模型了。

如果希望用袋外數據來測試,則需要在實例化時就將 oob_score這個參數調整為True,訓練完畢之后,我們可以用 隨機森林的另一個重要屬性:oob_score_來查看我們的在袋外數據上測試的結果:

# 無需划分訓練集和測試集
rfc = RandomForestClassifier(n_estimators=25,oob_score=True)
rfc = rfc.fit(wine.data,wine.target)

# 重要屬性oob_score_
rfc.oob_score_

2.2 重要屬性和接口

至此,我們已經講完了所有隨機森林中的重要參數,為大家復習了一下決策樹的參數,並通過 n_estimators, random_state,boostrap和oob_score這四個參數幫助大家了解了袋裝法的基本流程和重要概念。同時,我們還 介紹了.estimators_.oob_score_ 這兩個重要屬性。除了這兩個屬性之外,作為樹模型的集成算法,隨機森林自然也有.feature_importances_這個屬性。

隨機森林的接口與決策樹完全一致,因此依然有四個常用接口:apply, fit, predictscore。除此之外,還需要注 意隨機森林的 predict_proba 接口,這個接口返回每個測試樣本對應的被分到每一類標簽的概率,標簽有幾個分類 就返回幾個概率。如果是二分類問題,則 predict_proba 返回的數值大於 0.5 的,被分為 1,小於 0.5 的,被分為 0。 傳統的隨機森林是利用袋裝法中的規則,平均或少數服從多數來決定集成的結果,而 sklearn 中的隨機森林是平均 每個樣本對應的 predict_proba 返回的概率,得到一個平均概率,從而決定測試樣本的分類。

rfc = RandomForestClassifier(n_estimators=20,random_state=2)
rfc = rfc.fit(Xtrain,Ytrain)

rfc.feature_importances_ # 每個特征的重要程度
rfc.apply(Xtest) # 每個樣本在每個樹中的節點的索引
rfc.predict(Xtest) # 樣本點在每個結果的可能性
rfc.predict_proba(Xtest) # 樣本的平均概率

掌握了上面的知識,基本上要實現隨機森林分類已經是沒問題了。從紅酒數據集的表現上來看,隨機森林的效用比
單純的決策樹要強上不少,大家可以自己更換其他數據來試試看(比如泰坦尼克號數據)。

Bonus:Bagging 的另一個必要條件

之前我們說過,在使用袋裝法時要求基評估器要盡量獨立。其實,袋裝法還有另一個必要條件:基分類器的判斷准確率至少要超過隨機分類器,即時說,基分類器的判斷准確率至少要超過 50%。之前我們已經展示過隨機森林的准確率公式,基於這個公式,我們畫出了基分類器的誤差率ε和隨機森林的誤差率之間的圖像。大家可以自己運行一 下這段代碼,看看圖像呈什么樣的分布。

import numpy as np
x = np.linspace(0,1,20)
y = []
for epsilon in np.linspace(0,1,20):
    E = np.array([comb(25,i)*(epsilon**i)*((1-epsilon)**(25-i)) for i in range(13,26)]).sum()
    y.append(E)

plt.plot(x,y,"o--",label="when estimators are different")
plt.plot(x,x,"--",color="red",label="when estimators are different")
plt.xlabel("individual estimator's error")
plt.ylabel("RandomForest's error")
plt.legend()
plt.show()
plt.savefig("bagging_condition",dpi=300)
bagging_condition

可以從圖像上看出,當基分類器的誤差率小於 0.5,即准確率大於 0.5 時,集成的效果是比基分類器要好的。相反,當基分類器的誤差率大於 0.5,袋裝的集成算法就失效了。所以在使用隨機森林之前,一定要檢查,用來組成隨機森林的分類樹們是否都有至少 50%的預測正確率。

3 RandomForestRegressor

class sklearn.ensemble.RandomForestRegressor (n_estimators=’warn’, criterion=’mse’, max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=’auto’, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False)

所有的參數,屬性與接口,全部和隨機森林分類器一致。僅有的不同就是回歸樹與分類樹的不同,不純度的指標,參數 Criterion 不一致。

3.1 重要參數,屬性與接口

criterion

回歸樹衡量分枝質量的指標,支持的標准有三種:

1)輸入"mse"使用均方誤差 mean squared error(MSE),父節點和葉子節點之間的均方誤差的差額將被用來作為 特征選擇的標准,這種方法通過使用葉子節點的均值來最小化 L2 損失

2)輸入“friedman_mse”使用費爾德曼均方誤差,這種指標使用弗里德曼針對潛在分枝中的問題改進后的均方誤差 3)輸入"mae"使用絕對平均誤差MAE(mean absolute error),這種指標使用葉節點的中值來最小化L1損失

\[MSE=\frac{1}{N}\sum_{i=1}^{N}(f_i-y_i)^2 \]

其中 N 是樣本數量,i 是每一個數據樣本,fi是模型回歸出的數值,yi 是樣本點 i 實際的數值標簽。所以 MSE 的本質, 其實是樣本真實數據與回歸結果的差異。在回歸樹中,MSE 不只是我們的分枝質量衡量指標,也是我們最常用的衡量回歸樹回歸質量的指標,當我們在使用交叉驗證,或者其他方式獲取回歸樹的結果時,我們往往選擇均方誤差作為我們的評估(在分類樹中這個指標是 score 代表的預測准確率)。在回歸中,我們追求的是,MSE 越小越好。

然而,回歸樹的接口 score 返回的是 R 平方,並不是 MSE。R 平方被定義如下:

\[R^2=1-\frac{u}{v} \]

\[u=\sum_{i=1}^{N}(f_i-y_i)^2 ,v=\sum_{i=1}^{N}(f_i-\widehat{y_i})^2 \]

其中\(u\)是殘差平方和(MSE * N),\(v\)是總平方和,N 是樣本數量,i 是每一個數據樣本,\(f_i\)是模型回歸出的數值,\(y_i\) 是樣本點實際的數值標簽。\(\widehat{y_i}\)是真實數值標簽的平均數。R 平方可以為正為負(如果模型的殘差平方和遠遠大於 模型的總平方和,模型非常糟糕,R 平方就會為負),而均方誤差永遠為正。

值得一提的是,雖然均方誤差永遠為正,但是 sklearn 當中使用均方誤差作為評判標准時,卻是計算"負均方誤差“(neg_mean_squared_error)。這是因為 sklearn 在計算模型評估指標的時候,會考慮指標本身的性質,均 方誤差本身是一種誤差,所以被 sklearn 划分為模型的一種損失(loss),因此在 sklearn 當中,都以負數表示。真正的 均方誤差 MSE 的數值,其實就是 neg_mean_squared_error 去掉負號的數字。

重要屬性和接口

最重要的屬性和接口,都與隨機森林的分類器相一致,還是 apply, fit, predict 和 score 最為核心。值得一提的是,隨 機森林回歸並沒有 predict_proba 這個接口,因為對於回歸來說,並不存在一個樣本要被分到某個類別的概率問題,因此沒有 predict_proba 這個接口。

隨機森林回歸用法

和決策樹完全一致,除了多了參數 n_estimators。

from sklearn.datasets import load_boston
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestRegressor

boston = load_boston()
regressor = RandomForestRegressor(n_estimators=100,random_state=0)
cross_val_score(regressor
                ,boston.data
                ,boston.target
                ,cv = 10
                ,scoring = "neg_mean_squared_error"
               )

返回十次交叉驗證的結果,注意在這里,如果不填寫 scoring = "neg_mean_squared_error",交叉驗證默認的模型衡量指標是 R 平方,因此交叉驗證的結果可能有正也可能有負。而如果寫上 scoring,則衡量標准是負 MSE,交叉驗 證的結果只可能為負。

# sklearn當中的模型評估指標列表
import sklearn
sorted(sklearn.metrics.SCORERS.keys())
['accuracy',
'adjusted_mutual_info_score',
'adjusted_rand_score',
'average_precision',
'balanced_accuracy',
'completeness_score',
'explained_variance',
'f1',
'f1_macro',
'f1_micro',
'f1_samples',
'f1_weighted',
'fowlkes_mallows_score',
'homogeneity_score',
'jaccard',
'jaccard_macro',
'jaccard_micro',
'jaccard_samples',
'jaccard_weighted',
'max_error',
'mutual_info_score',
'neg_brier_score',
'neg_log_loss',
'neg_mean_absolute_error',
'neg_mean_gamma_deviance',
'neg_mean_poisson_deviance',
'neg_mean_squared_error',
'neg_mean_squared_log_error',
'neg_median_absolute_error',
'neg_root_mean_squared_error',
'normalized_mutual_info_score',
'precision',
'precision_macro',
'precision_micro',
'precision_samples',
'precision_weighted',
'r2',
'recall',
'recall_macro',
'recall_micro',
'recall_samples',
'recall_weighted',
'roc_auc',
'roc_auc_ovo',
'roc_auc_ovo_weighted',
'roc_auc_ovr',
'roc_auc_ovr_weighted',
'v_measure_score']

3.2 實例:用隨機森林回歸填補缺失值

我們從現實中收集的數據,幾乎不可能是完美無缺的,往往都會有一些缺失值。面對缺失值,很多人選擇的方式是直接將含有缺失值的樣本刪除,這是一種有效的方法,但是有時候填補缺失值會比直接丟棄樣本效果更好,即便我們其實並不知道缺失值的真實樣貌。在 sklearn 中,我們可以使用 sklearn.impute.SimpleImputer 來輕松地將均值,中值,或者其他最常用的數值填補到數據中,在這個案例中,我們將使用均值,0,和隨機森林回歸來填補缺失值,並驗證四種狀況下的擬合狀況,找出對使用的數據集來說最佳的缺失值填補方法。

  1. 導入需的庫

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    from sklearn.datasets import load_boston
    from sklearn.impute import SimpleImputer # 填補缺失值的類
    from sklearn.ensemble import RandomForestRegressor
    
  2. 以波士頓數據集為例,導入完整的數據集並探索

    dataset = load_boston()
    dataset.data.shape
    # 總共506*13=6578個數據
    X_full,y_full = dataset.data,dataset.target
    n_samples = X_full.shape[0]
    n_features = X_full.shape[1]
    
  3. 為完整數據集放入缺失值

    首先確定我們希望放入的缺失數據的比例,在這里我們假設是 50%,那總共就要有 3289 個數據缺失。

    rng = np.random.RandomState(0)
    missing_rate = 0.5
    n_missing_samples = int(np.floor(n_samples*n_features*missing_rate))# np.floor向下取整,返回.0格式的浮點數
    

    所有數據要隨機遍布在數據集的各行各列當中,而一個缺失的數據會需要一個行索引和一個列索引。如果能夠創造一個數組,包含 3289 個分布在 0~506 中間的行索引,和 3289 個分布在 0~13 之間的列索引,那我們就可以利用索引來為數據中的任意 3289 個位置賦空值。然后我們用 0,均值和隨機森林來填寫這些缺失值,然后查看回歸的結果如何。

    # randint(下限,上限,n)在上下限之間取出n個整數
    missing_features = rng.randint(0,n_features,n_missing_samples)
    missing_samples = rng.randint(0,n_samples,n_missing_samples)
    

    我們現在采樣了 3289 個數據,遠遠超過我們的樣本量 506,所以我們使用隨機抽取的函數 randint。但如果我們需要
    的數據量小於我們的樣本量 506,那我們可以采用 np.random.choice 來抽樣,choice 會隨機抽取不重復的隨機數,因此可以幫助我們讓數據更加分散,確保數據不會集中在一些行中。

    missing_samples = rng.choice(n_samples,n_missing_samples,replace=False)
    

    接着,更改數據集,填充空值。此處是將數據集進行了備份,同時是以 np.nan 作為填充。

    X_missing = X_full.copy()
    y_missing = y_full.copy()
    
    # 可以直接通過列表形式賦值
    X_missing[missing_samples,missing_features] = np.nan
    
    #轉換成DataFrame是為了后續方便各種操作,numpy對矩陣的運算速度快到拯救人生,但是在索引等功能上卻不如 pandas來得好用
    X_missing = pd.DataFrame(X_missing)
    
  4. 使用 0 和均值填補缺失值

    # 使用均值進行填補
    # SimpleImputer是填補缺失值的類,strategy為填補數據的類型
    imp_mean = SimpleImputer(missing_values=np.nan,strategy='mean') # 實例化
    X_missing_mean = imp_mean.fit_transform(X_missing) # 訓練fit+導出predict>>>特殊的接口fit_transform
    
    # 使用0進行填補
    imp_0 = SimpleImputer(missing_values=np.nan,strategy="constant",fill_value=0)
    X_missing_0 = imp_0.fit_transform(X_missing)
    
    # 查看是否還有空值,無空值,證明完全填補
    pd.DataFrame(X_missing_mean).isnull().sum()
    
  5. 使用隨機森林填補缺失值

    任何回歸都是從特征矩陣中學習,然后求解連續型標簽 y 的過程,之所以能夠實現這個過程,是因為回歸算法認為,特征矩陣和標簽之前存在着某種聯系。實際上,標簽和特征是可以相互轉換的,比如說,在一個“用地區,環境,附近學校數量”預測“房價”的問題中,我們既可以用“地區”,“環境”,“附近學校數量”的數據來預測“房價”,也可以反過來,用“環境”,“附近學校數量”和“房價”來預測“地區”。而回歸填補缺失值,正是利用了這種思想。

    對於一個有 n 個特征的數據來說,其中特征 T 有缺失值,我們就把特征 T 當作標簽,其他的 n-1 個特征和原本的標簽組成新的特征矩陣。那對於 T 來說,它沒有缺失的部分,就是我們的 Y_test,這部分數據既有標簽也有特征,而它缺失的部 分,只有特征沒有標簽,就是我們需要預測的部分。

    特征 T 不缺失的值對應的其他 n-1 個特征 + 本來的標簽:X_train
    特征 T 不缺失的值:Y_train

    特征 T 缺失的值對應的其他 n-1 個特征 + 本來的標簽:X_test
    特征 T 缺失的值:未知,我們需要預測的 Y_test

    這種做法,對於某一個特征大量缺失,其他特征卻很完整的情況,非常適用。

    那如果數據中除了特征 T 之外,其他特征也有缺失值怎么辦?
    答案是遍歷所有的特征,從缺失最少的開始進行填補(因為填補缺失最少的特征所需要的准確信息最少)。填補一個特征時,先將其他特征的缺失值用 0 代替,每完成一次回歸預測,就將預測值放到原本的特征矩陣中,再繼續填補下一個特征。每一次填補完畢,有缺失值的特征會減少一個,所以每次循環后,需要用 0 來填補的特征就越來越少。當進行到最后一個特征時(這個特征應該是所有特征中缺失值最多的),已經沒有任何的其他特征需要用 0 來進行填補了, 而我們已經使用回歸為其他特征填補了大量有效信息,可以用來填補缺失最多的特征。

    遍歷所有的特征后,數據就完整,不再有缺失值了。

    X_missing_reg = X_missing.copy()
    # 找出數據集中,缺失值從小到大排列的特征的順序,且得到這些特征的索引
    # argsort返回從小到大排序的順序所對應的索引
    sortindex = np.argsort(X_missing_reg.isnull().sum(axis=0)).values
    
    for i in sortindex:
        # 構建我們的新特征矩陣(沒有被選中去填充的特征+原始的標簽)和新標簽(被選中去填充的特征)
        df = X_missing_reg
        # 新標簽
        fillc = df.iloc[:,i]
        # 新特征矩陣
        df = pd.concat([df.iloc[:,df.columns != i],pd.DataFrame(y_full)],axis=1)
        
        # 在新特征矩陣中,對含有缺失值的列,進行0的填補
        df_0 = SimpleImputer(missing_values=np.nan,strategy='constant',fill_value=0).fit_transform(df)
        # 找出我們的訓練集和測試集
        # 現在標簽中存在的非空值
        Ytrain = fillc[fillc.notnull()]
        # 現在標簽中的空值
        # 不需要Ytest的值,要的是Ytest的索引
        Ytest = fillc[fillc.isnull()]
        # 在新特征矩陣上,被選出來的要填充的非空值對應的記錄
        Xtrain = df_0[Ytrain.index,:]
        # 新特征矩陣上,被選出來的要填充的那個特征的空值對應的記錄
        Xtest = df_0[Ytest.index]
        
        # 用隨機森林回歸來填補缺失值
        rfc = RandomForestRegressor(n_estimators=100)# 實例化
        rfc = rfc.fit(Xtrain,Ytrain)# 訓練
        # 用predict接口將Xtest導入,得到預測結果作為填補空值的值
        Ypredict = rfc.predict(Xtest)
        
        # 將填補好的特征返回原始特征矩陣中
        X_missing_reg.loc[X_missing_reg.iloc[:,i].isnull(),i] = Ypredict
    
  6. 對填補好的數據進行建模

    from sklearn.model_selection import cross_val_score
    # 對所有數據進行建模,取得MSE結果
    X = [X_full,X_missing_mean,X_missing_0,X_missing_reg]
    
    mse = []
    std = []
    for x in X:
        estimator = RandomForestRegressor(random_state=0, n_estimators=100)
        scores = cross_val_score(estimator,x,y_full,scoring='neg_mean_squared_error').mean()
        mse.append(scores * -1)
        
    [*zip(["X_full","X_missing_mean","X_missing_0","X_missing_reg"],mse)]
    
    [('X_full', 21.62860460743544),
    ('X_missing_mean', 40.84405476955929),
    ('X_missing_0', 49.50657028893417),
    ('X_missing_reg', 19.440723075441657)]
    

    所得值越小越好,所以,在此次結果中,用隨機森林填充空值的效果要比完全值要好。

  7. 用所得結果畫出條形圖

    x_labels = [
        'Full data'
        ,'Zero Imputation'
        ,'Mean Imputation'
        ,'Regressor Imputation'
    ]
    colors = ['r','g','b','orange']
    
    plt.figure(figsize=(12,6))
    ax = plt.subplot(111)
    for i in np.arange(len(mse)):
        ax.barh(i,mse[i],color=colors[i],alpha=0.6,align='center')
    ax.set_title("Imputation Techniques with Boston Data")
    ax.set_xlim(left=np.min(mse) * 0.9
               ,right=np.max(mse) * 1.1)
    ax.set_yticks(np.arange(len(mse)))
    ax.set_xlabel('MSE')
    ax.set_yticklabels(x_labels)
    plt.savefig('compare_imputation',dpi=300)
    plt.show()
    

    compare_imputation

4 機器學習中調參的基本思想

大多數的機器學習相關的書都是遍歷各種算法和案例,為大家講解各種各樣算法的原理和用途,但卻對調參探究甚少。這中間有許多原因,其一是因為,調參的方式總是根據數據的狀況而定,所以沒有辦法一概而論;其二是因為,其實大家也都沒有特別好的辦法。

通過畫學習曲線,或者網格搜索,我們能夠探索到調參邊緣(代價可能是訓練一次模型要跑三天三夜),但是在現實中,高手調參恐怕還是多依賴於經驗,而這些經驗,來源於:1)非常正確的調參思路和方法,2)對模型評估指標的理解,3)對數據的感覺和經驗,4)用洪荒之力去不斷地嘗試。

我們也許無法學到高手們多年累積的經驗,但我們可以學習他們對模型評估指標的理解和調參的思路。

那我們首先來講講正確的調參思路。模型調參,第一步是要找准目標:我們要做什么?一般來說,這個目標是提升某個模型評估指標,比如對於隨機森林來說,我們想要提升的是模型在未知數據上的准確率(由 score 或 oob_score_來衡量)找准了這個目標,我們就需要思考:模型在未知數據上的准確率受什么因素影響?在機器學 習中,我們用來衡量模型在未知數據上的准確率的指標,叫做泛化誤差(Genelization error)

泛化誤差

當模型在未知數據(測試集或者袋外數據)上表現糟糕時,我們說模型的泛化程度不夠,泛化誤差大,模型的效果不好。泛化誤差受到模型的結構(復雜度)影響。看下面這張圖,它准確地描繪了泛化誤差與模型復雜度的關系,當模型太復雜,模型就會過擬合,泛化能力就不夠,所以泛化誤差大。當模型太簡單,模型就會欠擬合,擬合能力就不夠,所以誤差也會大。只有當模型的復雜度剛剛好的才能夠達到泛化誤差最小的目標。

genelization

那模型的復雜度與我們的參數有什么關系呢?對樹模型來說,樹越茂盛,深度越深,枝葉越多,模型就越復雜。所以樹模型是天生位於圖的右上角的模型,隨機森林是以樹模型為基礎,所以隨機森林也是天生復雜度高的模型。隨機森林的參數,都是向着一個目標去:減少模型的復雜度,把模型往圖像的左邊移動,防止過擬合。當然了,調參沒有絕對,也有天生處於圖像左邊的隨機森林,所以調參之前,我們要先判斷,模型現在究竟處於圖像的哪一邊。泛化誤差的背后其實是“偏差-方差困境”,原理十分復雜,無論你翻開哪一本書,你都會看見長篇的數學論證和每個字都能看懂但是連在一起就看不懂的文字解釋。在下一節偏差 vs 方差中,我用最簡單易懂的語言為大家解釋了泛化 誤差背后的原理,大家選讀。那我們只需要記住這四點:

1)模型太復雜或者太簡單,都會讓泛化誤差高,我們追求的是位於中間的平衡點
2)模型太復雜就會過擬合,模型太簡單就會欠擬合
3)對樹模型和樹的集成模型來說,樹的深度越深,枝葉越多,模型越復雜
4)樹模型和樹的集成模型的目標,都是減少模型復雜度,把模型往圖像的左邊移動

那具體每個參數,都如何影響我們的復雜度和模型呢?我們一直以來調參,都是在學習曲線上輪流找最優值,盼望能夠將准確率修正到一個比較高的水平。然而我們現在了解了隨機森林的調參方向:降低復雜度,我們就可以將那些對復雜度影響巨大的參數挑選出來,研究他們的單調性,然后專注調整那些能最大限度讓復雜度降低的參數。對於那些不單調的參數,或者反而會讓復雜度升高的參數,我們就視情況使用,大多時候甚至可以退避。基於經驗,我對各個參數對模型的影響程度做了一個排序。在我們調參的時候,大家可以參考這個順序。

參數 對模型在未知數據上的評估性能的影響 影響程度
n_estimators 提升至平穩,n_estimators↑,不影響單個模型的復雜度 ⭐⭐⭐⭐
max_depth 有增有減,默認最大深度,即最高復雜度,向復雜度降低的方向調參
max_depth↓,模型更簡單,且向圖像的左邊移動
⭐⭐⭐
min_samples _leaf 有增有減,默認最小限制 1,即最高復雜度,向復雜度降低的方向調參
min_samples_leaf↑,模型更簡單,且向圖像的左邊移動
⭐⭐
min_samples _split 有增有減,默認最小限制 2,即最高復雜度,向復雜度降低的方向調參
min_samples_split↑,模型更簡單,且向圖像的左邊移動
⭐⭐
max_features 有增有減,默認 auto,是特征總數的開平方,位於中間復雜度,既可以 向復雜度升高的方向,也可以向復雜度降低的方向調參
max_features↓,模型更簡單,圖像左移 max_features↑,模型更復雜,圖像右移
max_features 是唯一的,既能夠讓模型更簡單,也能夠讓模型更復雜的參 數,所以在調整這個參數的時
criterion 有增有減,一般使用 gini 看具體情況

有了以上的知識儲備,我們現在也能夠通過參數的變化來了解,模型什么時候到達了極限,當復雜度已經不能再降低的時候,我們就不必再調整了,因為調整大型數據的參數是一件非常費時費力的事。除了學習曲線和網格搜索,我們現在有了基於對模型和正確的調參思路的“推測”能力,這能夠讓我們的調參能力更上一層樓。

偏差 vs 方差(選讀)

一個集成模型(f)在未知數據集(D)上的泛化誤差 E(f;D),由方差(var),偏差(bais)和噪聲(\(\varepsilon\))共同決定。

\[E(f;D)=bias^2(x)+var(x)+\varepsilon^2 \]

關鍵概念:偏差與方差
觀察下面的圖像,每個點就是集成算法中的一個基評估器產生的預測值。紅色虛線代表着這些預測值的均值,而藍色的線代表着數據本來的面貌。
偏差:模型的預測值與真實值之間的差異,即每一個紅點到藍線的距離。在集成算法中,每個基評估器都會有自己的偏差,集成評估器的偏差是所有基評估器偏差的均值。模型越精確,偏差越低。
方差:反映的是模型每一次輸出結果與模型預測值的平均水平之間的誤差,即每一個紅點到紅色虛線的距離,衡量模型的穩定性。模型越穩定,方差越低。

bias

其中偏差衡量模型是否預測得准確,偏差越小,模型越“准”;而方差衡量模型每次預測的結果是否接近,即是說方差越小,模型越“穩”;噪聲是機器學習無法干涉的部分,為了讓世界美好一點,我們就不去研究了。一個好的模型,要對大多數未知數據都預測得”准“又”穩“。即是說,當偏差和方差都很低的時候,模型的泛化誤差就小,在未知數據上的准確率就高。

偏差大 偏差小
方差大 模型不適合這個數據
換模型
過擬合
模型很復雜
對某些數據集預測很准確
對某些數據集預測很糟糕
方差小 欠擬合
模型相對簡單
預測很穩定
但對所有的數據預測都不太准確
泛化誤差小,我們的目標

通常來說,方差和偏差有一個很大,泛化誤差都會很大。然而,方差和偏差是此消彼長的,不可能同時達到最小值。這個要怎么理解呢?來看看下面這張圖:

genelization_bias

從圖上可以看出,模型復雜度大的時候,方差高,偏差低。偏差低,就是要求模型要預測得“准”。模型就會更努力去學習更多信息,會具體於訓練數據,這會導致,模型在一部分數據上表現很好,在另一部分數據上表現卻很糟糕。模型泛化性差,在不同數據上表現不穩定,所以方差就大。而要盡量學習訓練集,模型的建立必然更多細節,復雜程度必然上升。所以,復雜度高,方差高,總泛化誤差高。

相對的,復雜度低的時候,方差低,偏差高。方差低,要求模型預測得“穩”,泛化性更強,那對於模型來說,它就不需要對數據進行一個太深的學習,只需要建立一個比較簡單,判定比較寬泛的模型就可以了。結果就是,模型無法在某一類或者某一組數據上達成很高的准確度,所以偏差就會大。所以,復雜度低,偏差高,總泛化誤差高。

我們調參的目標是,達到方差和偏差的完美平衡!雖然方差和偏差不能同時達到最小值,但他們組成的泛化誤差卻可以有一個最低點,而我們就是要尋找這個最低點。對復雜度大的模型,要降低方差,對相對簡單的模型,要降低偏差。隨機森林的基評估器都擁有較低的偏差和較高的方差,因為決策樹本身是預測比較”准“,比較容易過擬合的模型,裝袋法本身也要求基分類器的准確率必須要有 50%以上。所以以隨機森林為代表的裝袋法的訓練過程旨在降低方差,即降低模型復雜度,所以隨機森林參數的默認設定都是假設模型本身在泛化誤差最低點的右邊。

所以,我們在降低復雜度的時候,本質其實是在降低隨機森林的方差,隨機森林所有的參數,也都是朝着降低方差的目標去。有了這一層理解,我們對復雜度和泛化誤差的理解就更上一層樓了,對於我們調參,也有了更大的幫助。

關於方差-偏差的更多內容,大家可以參考周志華的《機器學習》。

5 實例:隨機森林在乳腺癌數據上的調參

在這節課中,我們了解了隨機森林,並且學習了機器學習中調參的基本思想,了解了方差和偏差如何受到隨機森林的參數們的影響。這一節,我們就來使用我們剛才學的,基於方差和偏差的調參方法,在乳腺癌數據上進行一次隨機森林的調參。乳腺癌數據是 sklearn 自帶的分類數據之一。

案例中,往往使用真實數據,為什么我們要使用 sklearn 自帶的數據呢?因為真實數據在隨機森林下的調參過程, 往往非常緩慢。真實數據量大,維度高,在使用隨機森林之前需要一系列的處理,因此不太適合用來做直播中的案例演示。在本章,我為大家准備了 kaggle 上下載的辨別手寫數字的數據,有 4W 多條記錄 700 多個左右的特征,隨機 森林在這個辨別手寫數字的數據上有非常好的表現,其調參案例也是非常經典,但是由於數據的維度太高,太過復雜,運行一次完整的網格搜索需要四五個小時,因此不太可能拿來給大家進行演示。我們上周的案例中用的泰坦尼克號數據,用來調參的話也是需要很長時間,因此我才選擇 sklearn 當中自帶的,結構相對清晰簡單的數據來為大 家做這個案例。大家感興趣的話,可以進群去下載數據,也可以直接到 kaggle 上進行下載,數據集名稱是 DigitRecognizer(https://www.kaggle.com/c/digit-recognizer)。

那我們接下來,就用乳腺癌數據,來看看我們的調參代碼。

  1. 導入需要的庫

    from sklearn.datasets import load_breast_cancer
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.model_selection import GridSearchCV
    from sklearn.model_selection import cross_val_score
    import matplotlib.pyplot as plt
    import pandas as pd
    import numpy as np
    
  2. 導入數據集,探索數據

    data = load_breast_cancer()
    data.data.shape
    data.target
    

    可以看到,乳腺癌數據集是二分類,有 569 條記錄,30 個特征,單看維度雖然不算太高,但是樣本量非常少。過擬合的情況可能存在。

  3. 進行一次簡單的建模,看看模型本身在數據集上的效果

    rfc = RandomForestClassifier(n_estimators=100,random_state=90)
    score_pre = cross_val_score(rfc,data.data,data.target,cv=10).mean()
    
    score_pre
    

    0.9648809523809524

    這里可以看到,隨機森林在乳腺癌數據上的表現本就還不錯,在現實數據集上,基本上不可能什么都不調就看到 95%以上的准確率。

  4. 隨機森林調整的第一步:無論如何先來調 n_estimators

    在這里我們選擇學習曲線,可以使用網格搜索嗎?可以,但是只有學習曲線,才能看見趨勢。
    我個人的傾向是,要看見 n_estimators 在什么取值開始變得平穩,是否一直推動模型整體准確率的上升等信息 第一次的學習曲線,可以先用來幫助我們划定范圍,我們取每十個數作為一個階段,來觀察 n_estimators 的變化如何 引起模型整體准確率的變化

    score1 = []
    for i in range(0,200,10):
        rfc = RandomForestClassifier(n_estimators=i+1
                                     ,n_jobs = -1
                                     ,random_state = 90
                                    )
        score = cross_val_score(rfc,data.data,data.target,cv=10).mean()
        score1.append(score)
    # 獲得交叉驗證平均值中的最大值及其下標
    # 因為是10個為單位取平均,所以下標要乘以10
    print(max(score1),(score1.index(max(score1))*10)+1) 
    plt.figure(figsize=[20,5])
    plt.plot(range(1,201,10),score1)
    plt.savefig('nestimators_roc',dpi=300)
    plt.show()
    
    0.9631265664160402 71
    

    nestimators_roc

    可以看出,從 0 到 25 時曲線陡峭,證明准確率有所提升,之后曲線就開始小范圍波動,改變不是很大。返回的結果是 71 ,這時,我們可以在小范圍內進一步細化學習曲線。

  5. 在確定好的范圍內,進一步細化學習曲線

    score2 = []
    for i in range(71,81):
        rfc = RandomForestClassifier(n_estimators=i+1
                                     ,n_jobs = -1
                                     ,random_state = 90
                                    )
        score = cross_val_score(rfc,data.data,data.target,cv=10).mean()
        score2.append(score)
    # 獲得交叉驗證平均值中的最大值及其下標
    # 因為是10個為單位取平均,所以下標要乘以10
    print(max(score2),(score2.index(max(score2))*10)+1) 
    plt.figure(figsize=[20,5])
    plt.plot(range(71,81),score2)
    plt.savefig('nestimators_roc2',dpi=300)
    plt.show()
    
    0.9666353383458647 72
    

    nestimators_roc2

    從結果可以看出,經過細化,准確率稍微有點提升。

    調整 n_estimators 的效果顯著,模型的准確率立刻上升了 0.005。接下來就進入網格搜索,我們將使用網格搜索對 參數一個個進行調整。為什么我們不同時調整多個參數呢?原因有兩個:1)同時調整多個參數會運行非常緩慢,在課堂上我們沒有這么多的時間。2)同時調整多個參數,會讓我們無法理解參數的組合是怎么得來的,所以即便網格搜索調出來的結果不好,我們也不知道從哪里去改。在這里,為了使用復雜度-泛化誤差方法(方差-偏差方法),我們對參數進行一個個地調整。

  6. 為網格搜索做准備,書寫網格搜索的參數

    有一些參數是沒有參照的,很難說清一個范圍,這種情況下我們使用學習曲線,看趨勢。從曲線跑出的結果中選取一個更小的區間,再跑曲線。

    param_grid = {'n_estimators':np.arange(0, 200, 10)} 
    param_grid = {'max_depth':np.arange(1, 20, 1)} param_grid = {'max_leaf_nodes':np.arange(25,50,1)}
    

    對於大型數據集,可以嘗試從 1000 來構建,先輸入 1000,每 100 個葉子一個區間,再逐漸縮小范圍.有一些參數是可以找到一個范圍的,或者說我們知道他們的取值和隨着他們的取值,模型的整體准確率會如何變化,這樣的參數我們就可以直接跑網格搜索。

    param_grid = {'criterion':['gini', 'entropy']} 
    param_grid = {'min_samples_split':np.arange(2, 2+20, 1)} 
    param_grid = {'min_samples_leaf':np.arange(1, 1+10, 1)} 
    param_grid = {'max_features':np.arange(5,30,1)}
    
  7. 開始按照參數對模型整體准確率的影響程度進行調參,首先調整 max_depth

    # 調整max_depth
    param_grid = {'max_depth':np.arange(1,20,1)}
    #   一般根據數據的大小來進行一個試探,乳腺癌數據很小,所以可以采用1~10,或者1~20這樣的試探
    #   但對於像digit recognition那樣的大型數據來說,我們應該嘗試30~50層深度(或許還不足夠 
    #   更應該畫出學習曲線,來觀察深度對模型的影響
    rfc = RandomForestClassifier(n_estimators=39
                                 ,random_state = 90
                                )
    GS = GridSearchCV(rfc,param_grid,cv=10)
    GS.fit(data.data,data.target)
    GS.best_params_
    GS.best_score_
    
    {'max_depth': 6}
    0.9631265664160402
    

    在這里,我們注意到,將 max_depth 設置為有限之后,模型的准確率下降了。限制 max_depth,是讓模型變得簡 單,把模型向左推,而模型整體的准確率下降了,即整體的泛化誤差上升了,這說明模型現在位於圖像左邊,即泛化誤差最低點的左邊(偏差為主導的一邊)。通常來說,隨機森林應該在泛化誤差最低點的右邊,樹模型應該傾向於過擬合,而不是擬合不足。這和數據集本身有關,但也有可能是我們調整的 n_estimators 對於數據集來說太大, 因此將模型拉到泛化誤差最低點去了。然而,既然我們追求最低泛化誤差,那我們就保留這個 n_estimators,除非 有其他的因素,可以幫助我們達到更高的准確率。

    當模型位於圖像左邊時,我們需要的是增加模型復雜度(增加方差,減少偏差)的選項,因此 max_depth 應該盡量大,min_samples_leaf 和 min_samples_split 都應該盡量小。這幾乎是在說明,除了 max_features,我們沒有任何 參數可以調整了,因為 max_depth,min_samples_leaf 和 min_samples_split 是剪枝參數,是減小復雜度的參數。在這里,我們可以預言,我們已經非常接近模型的上限,模型很可能沒有辦法再進步了。

    那我們這就來調整一下 max_features,看看模型如何變化。

  8. 調整 max_features

    max_features 是唯一一個即能夠將模型往左(低方差高偏差)推,也能夠將模型往右(高方差低偏差)推的參數。我 們需要根據調參前,模型所在的位置(在泛化誤差最低點的左邊還是右邊)來決定我們要將 max_features 往哪邊調。 現在模型位於圖像左側,我們需要的是更高的復雜度,因此我們應該把 max_features 往更大的方向調整,可用的特征 越多,模型才會越復雜。max_features 的默認最小值是 sqrt(n_features),因此我們使用這個值作為調參范圍的 最小值。

    param_grid={"max_features":np.arange(5,30,1)}
    rfc = RandomForestClassifier(n_estimators=39
                                 ,random_state=90
                                )
    GS = GridSearchCV(rfc,param_grid,cv=10)
    GS.fit(data.data,data.target)
    
    GS.best_params_
    GS.best_score_
    
    {'max_features': 6}
    0.968421052631579
    

    網格搜索返回了 max_features 的最小值,可見 max_features 升高之后,模型的准確率降低了。這說明,我們把模 型往右推,模型的泛化誤差增加了。前面用 max_depth 往左推,現在用 max_features 往右推,泛化誤差都增加, 這說明模型本身已經處於泛化誤差最低點,已經達到了模型的預測上限,沒有參數可以左右的部分了。剩下的那些誤差,是噪聲決定的,已經沒有方差和偏差的舞台了。

    如果是現實案例,我們到這一步其實就可以停下了,因為復雜度和泛化誤差的關系已經告訴我們,模型不能再進步了。調參和訓練模型都需要很長的時間,明知道模型不能進步了還繼續調整,不是一個有效率的做法。如果我們希望模型更進一步,我們會選擇更換算法,或者更換做數據預處理的方式。但是在課上,出於練習和探索的目的,我們繼續調整我們的參數,讓大家觀察一下模型的變化,看看我們預測得是否正確。

    依然按照參數對模型整體准確率的影響程度進行調參。

  9. 調整 min_samples_leaf

    對於 min_samples_split 和 min_samples_leaf,一般是從他們的最小值開始向上增加 10 或 20。面對高維度高樣本量數據,如果不放心,也可以直接+50,對於大型數據,可能需要 200~300 的范圍。如果調整的時候發現准確率無論如何都上不來,那可以放心大膽調一個很大的數據,大力限制模型的復雜度。

    param_grid = {'min_samples_leaf':np.arange(1,1+10,1)}
    rfc = RandomForestClassifier(n_estimators=39
                                 ,random_state=90
                                )
    GS = GridSearchCV(rfc,param_grid,cv=10)
    GS.fit(data.data,data.target)
    
    GS.best_score_
    GS.best_params_
    
    0.9613721804511279
    {'min_samples_leaf': 4}
    

    可以看見,網格搜索返回了 min_samples_leaf 的最小值,並且模型整體的准確率還降低了,這和 max_depth 的情 況一致,參數把模型向左推,但是模型的泛化誤差上升了。在這種情況下,我們顯然是不要把這個參數設置起來
    的,就讓它默認就好了。

  10. 不懈努力,繼續嘗試 min_samples_split

    param_grid = {'min_samples_split':np.arange(2,2+20,1)}
    rfc = RandomForestClassifier(n_estimators=39
                                 ,random_state=90
                                )
    GS = GridSearchCV(rfc,param_grid,cv=10)
    GS.fit(data.data,data.target)
    GS.best_params_
    GS.best_score_
    
    {'min_samples_split': 3}
    0.9613721804511279
    

    和 min_samples_leaf 一樣的結果,返回最小值並且模型整體的准確率降低了。

  11. 最后嘗試一下 criterion

    param_grid = {'criterion':['gini','entropy']}
    rfc = RandomForestClassifier(n_estimators=39
                                 ,random_state=90
                                )
    GS = GridSearchCV(rfc,param_grid,cv=10)
    GS.fit(data.data,data.target)
    GS.best_params_
    GS.best_score_
    
    {'criterion': 'entropy'}
    0.9649122807017543
    
  12. 調整完畢,總結出模型的最佳參數

    rfc = RandomForestClassifier(n_estimators=39,random_state=90) 
    score = cross_val_score(rfc,data.data,data.target,cv=10).mean() 
    score
    score - score_pre
    

    在整個調參過程之中,我們首先調整了 n_estimators(無論如何都請先走這一步),然后調整 max_depth,通過 max_depth 產生的結果,來判斷模型位於復雜度-泛化誤差圖像的哪一邊,從而選擇我們應該調整的參數和調參的 方向。如果感到困惑,也可以畫很多學習曲線來觀察參數會如何影響我們的准確率,選取學習曲線中單調的部分來放大研究(如同我們對 n_estimators 做的)。學習曲線的拐點也許就是我們一直在追求的,最佳復雜度對應的泛化 誤差最低點(也是方差和偏差的平衡點)。

    網格搜索也可以一起調整多個參數,大家只要有時間,可以自己跑一下,看看網格搜索會給我們怎樣的結果,有時候,它的結果比我們的好,有時候,我們手動調整的結果會比較好。當然了,我們的乳腺癌數據集非常完美,所以只需要調 n_estimators 一個參數就達到了隨機森林在這個數據集上表現得極限。

6 附錄

6.1 Bagging vs Boosting

裝袋法 Bagging 提升法 Boosting
評估器 相互獨立,同時運行 相互關聯,按順序依次構建,后建的模型會在先建模型預測失敗的樣本上有更多的權重
抽樣數集 有放回抽樣 有放回抽樣,但會確認數據的權重,每次抽樣都會給容易預測失敗的樣本更多的權重
決定集成的結果 平均或少數服從多數原則 加權平均,在訓練集上表現更好的模型會有更大的權重
目標 降低方差,提高模型整體的穩定性 降低偏差,提高模型整體的精確度
單個評估器存在過擬合問題的時候 能夠一定程度上解決過擬合問題 可能會加劇過擬合問題
單個評估器的效力比較弱的時候 不是非常有幫助 很可能會提升模型表現
代表算法 隨機森林 梯度提升樹,Adaboost

6.2 RFC的參數列表

RFC_params1

6.3 RFC的屬性列表

6.4 RFC的接口列表


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM