二、隨機森林
2.1 概述
2.1.1 集成算法
- 概述:它本身不是一個單獨的機器學(xué)習(xí)算法,而是通過在數(shù)據(jù)上構(gòu)建多個模型,集成所有模型的建模結(jié)果;
- 目標(biāo):集成算法會考慮多個評估器的建模結(jié)果,匯總之后得到一個綜合的結(jié)果,以此來獲取比單個模型更好的回歸或分類表現(xiàn);
-
模型:
(1)多個模型集成成為的模型叫做集成評估器(ensemble estimator);
(2)組成集成評估器的每個模型都叫做基評估器(base estimator); -
集成算法:
(1)裝袋法(Bagging):構(gòu)建多個相互獨立的評估器,然后對其預(yù)測進行平均或多數(shù)表決原則來決定集成評估器的結(jié)果。裝袋法的代表模型就是隨機森林;
(2)提升法(Boosting):基評估器是相關(guān)的,是按順序一一構(gòu)建的。其核心思想是結(jié)合弱評估器的力量一次次對難以評估的樣本進行預(yù)測,從而構(gòu)成一個強評估器。提升法的代表模型有Adaboost和梯度提升樹
(3)stacking:我不到啊,一般都不用;

2.1.2 sklearn中的集成算法模塊
- sklearn中的集成算法模塊ensemble:

集成算法中,有一半以上都是樹的集成模型,可以想見決策樹在集成中必定是有很好的效果。在這堂課中,我們會以隨機森林為例,慢慢為大家揭開集成算法的神秘面紗。
2.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集成算法,它的所有基評估器都是決策樹,分類樹 組成的森林就叫做隨機森林分類器,回歸樹 所集成的森林就叫做隨機森林回歸器。
2.2.1 重要參數(shù)
(1) 控制基評估器(決策樹)的參數(shù)(之前學(xué)過了的)

單個決策樹的準(zhǔn)確率越高,隨機森林的準(zhǔn)確率也會越高,因為裝袋法是依賴于平均值或者少數(shù)服從多數(shù)原則來決定集成的結(jié)果的
(2)n_estimators
- 這是森林中樹木的數(shù)量,即基評估器的數(shù)量。這個參數(shù)對隨機森林模型的精確性影響是單調(diào)的,n_estimators越大,模型的效果往往越好。
1.導(dǎo)入需要的包
#設(shè)定運行環(huán)境
%matplotlib inline
from sklearn.tree import DecisionTreeClassifier
#導(dǎo)入隨機森林分類器
from sklearn.ensemble import RandomForestClassifier
#datasets是一個生成各種數(shù)據(jù)的模塊
from sklearn.datasets import load_wine
2.導(dǎo)入需要的數(shù)據(jù)集
wine = load_wine()
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("Single Tree:{}".format(score_c)
,"Random Forest:{}".format(score_r)
)
#Single Tree:0.8888888888888888 Random Forest:0.9814814814814815
4. 畫出隨機森林和決策樹在一組交叉驗證下的效果對比
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 = "Decision Tree")
plt.legend()
plt.show()

5. 畫出隨機森林和決策樹在十組交叉驗證下的效果對比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 = "Random Forest")
plt.plot(range(1,11),clf_l,label = "Decision Tree")
plt.legend()
plt.show()

6. n_estimators的學(xué)習(xí)曲線
#用來記錄每次的結(jié)果
superpa = []
#讓隨機森林在不同的n_estimators上跑了200次
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)
#輸出最高的準(zhǔn)確率以及對應(yīng)的索引
print(max(superpa),superpa.index(max(superpa)))
plt.figure(figsize=[20,5])
plt.plot(range(1,201),superpa)
plt.show()

(3)random_state
- 隨機森林的本質(zhì)是一種裝袋集成算法(bagging),裝袋集成算法是對基評估器的預(yù)測結(jié)果進行平均或用多數(shù)表決原則來決定集成評估器的結(jié)果。假設(shè)一棵樹判斷錯誤的可能性為0.2(ε),那13棵樹以上都判斷錯誤的可能性是:

i是判斷錯誤的次數(shù),也是判錯的樹的數(shù)量,ε是一棵樹判斷錯誤的概率,(1-ε)是判斷正確的概率,共判對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()
#0.00036904803455582827
- 在隨機森林中的random_state控制的是生成森林的模式,而非讓一個森林中只有一棵樹;
fc = RandomForestClassifier(n_estimators=20,random_state=2)
rfc = rfc.fit(Xtrain, Ytrain)
#隨機森林的重要屬性之一:estimators,查看森林中樹的狀況
rfc.estimators_

每棵樹的random_state都不一樣
rfc.estimators_[0].random_state
for i in range(len(rfc.estimators_)):
print(rfc.estimators_[i].random_state)

當(dāng)random_state固定時,隨機森林中生成是一組固定的樹,但每棵樹依然是不一致的,這是用”隨機挑選特征進行分枝“的方法得到的隨機性。用袋裝法集成時,基分類器應(yīng)當(dāng)是相互獨立的,是不相同的。
(4)bootstrap & oob_score
- 要讓基分類器盡量都不一樣,一種很容易理解的方法是使用不同的訓(xùn)練集來進行訓(xùn)練,而袋裝法正是通過有放回的隨機抽樣技術(shù)來形成不同的訓(xùn)練數(shù)據(jù),bootstrap就是用來控制抽樣技術(shù)的參數(shù);
- bootstrap:其實就是有放回的抽樣。bootstrap參數(shù)默認(rèn)True,代表采用這種有放回的隨機抽樣技術(shù);
- 問題:由于是有放回,一些樣本可能在同一個自助集中出現(xiàn)多次,而其他一些卻可能被忽略,如下圖所示:

一般來說,自助集大約平均會包含63%的原始數(shù)據(jù)。因為每一個樣本被抽到某個自助集中的概率為:

- 當(dāng)n足夠大時,這個概率收斂于1-(1/e),約等于0.632。因此,會有約37%的訓(xùn)練數(shù)據(jù)被浪費掉,沒有參與建模,這些數(shù)據(jù)被稱為袋外數(shù)據(jù)(out of bag data,簡寫為oob)
——>也就是說,在使用隨機森林時,我們可以不劃分測試集和訓(xùn)練集,只需要用袋外數(shù)據(jù)來測試我們的模型即可。當(dāng)n和n_estimators都不夠大的時候,很可能就沒有數(shù)據(jù)掉落在袋外,自然也就無法使用oob數(shù)據(jù)來測試模型了。
如果希望用袋外數(shù)據(jù)來測試,則需要在實例化時就將oob_score這個參數(shù)調(diào)整為True,訓(xùn)練完畢之后,我們可以用隨機森林的另一個重要屬性:oob_score_來查看我們的在袋外數(shù)據(jù)上測試的結(jié)果:
#無需劃分訓(xùn)練集和測試集
rfc = RandomForestClassifier(n_estimators=25,oob_score=True)
rfc = rfc.fit(wine.data,wine.target)
#重要屬性oob_score_,用這個來獲得測試結(jié)果
rfc.oob_score_
2.2.2 重要屬性和接口
- 常用接口:apply, fit, predict和score;
- predict_proba:這個接口返回每個測試樣本對應(yīng)的被分到每一類標(biāo)簽的概率,標(biāo)簽有幾個分類就返回幾個概率;sklearn中的隨機森林是平均每個樣本對應(yīng)的predict_proba返回的概率,得到一個平均概率,從而決定測試樣本的分類。

2.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)
所有的參數(shù),屬性與接口,全部和隨機森林分類器一致。僅有的不同就是回歸樹與分類樹的不同,不純度的指標(biāo),參數(shù)Criterion不一致;
2.3.1 重要參數(shù),屬性與接口
(1)criterion:
-
回歸樹衡量分枝質(zhì)量的指標(biāo),支持的標(biāo)準(zhǔn)有三種:
(1)輸入"mse"使用均方誤差mean squared error(MSE),父節(jié)點和葉子節(jié)點之間的均方誤差的差額將被用來作為特征選擇的標(biāo)準(zhǔn),這種方法通過使用葉子節(jié)點的均值來最小化L2損失;
(2)輸入“friedman_mse”使用費爾德曼均方誤差,這種指標(biāo)使用弗里德曼針對潛在分枝中的問題改進后的均方誤差;
(3)輸入"mae"使用絕對平均誤差MAE(mean absolute error),這種指標(biāo)使用葉節(jié)點的中值來最小化L1損失;
這些和決策樹里頭的都差不多
注意:在回歸樹中,MSE不只是我們的分枝質(zhì)量衡量指標(biāo),也是我們最常用的衡
量回歸樹回歸質(zhì)量的指標(biāo)。在回歸中,我們追求的是,MSE越小越好;
- 重要屬性和接口:都與隨機森林的分類器相一致,還是apply, fit, predict和score最為核心。值得一提的是,隨機森林回歸并沒有predict_proba這個接口,因為對于回歸來說,并不存在一個樣本要被分到某個類別的概率問題,因此沒有predict_proba這個接口;
- 隨機森林回歸用法:和決策樹完全一致,除了多了參數(shù)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")
import sklearn
#sklearn當(dāng)中所有評估指標(biāo)(打分)列表
sorted(sklearn.metrics.SCORERS.keys())
注意:在這里,如果不填寫scoring = "neg_mean_squared_error",交叉驗證默認(rèn)的模型衡量指標(biāo)是R平方,因此交叉驗證的結(jié)果可能有正也可能有負(fù)。而如果寫上scoring,則衡量標(biāo)準(zhǔn)是負(fù)MSE,交叉驗證的結(jié)果只可能為負(fù)。
各種評估指標(biāo):

2.3.2 實例:用隨機森林回歸填補缺失值
面對缺失值,很多人選擇的方式是直接將含有缺失值的樣本刪除,這是一種有效的方法,但是有時候填補缺失值會比直接丟棄樣本效果更好,即便我們其實并不知道缺失值的真實樣貌。在sklearn中,我們可以使用sklearn.impute.SimpleImputer來輕松地將均值,中值,或者其他最常用的數(shù)值填補到數(shù)據(jù)中,在這個案例中,我們將使用均值,0,和隨機森林回歸來填補缺失值,并驗證四種狀況下的擬合狀況,找出對使用的數(shù)據(jù)集來說最佳的缺失值填補方法。
1.導(dǎo)入需要的庫
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. 以波士頓數(shù)據(jù)集為例,導(dǎo)入完整的數(shù)據(jù)集并探索
dataset = load_boston()
dataset.data.shape
#總共506*13=6578個數(shù)據(jù),對于標(biāo)簽是連續(xù)型變量的數(shù)據(jù)用回歸
X_full, y_full = dataset.data, dataset.target
n_samples = X_full.shape[0]
n_features = X_full.shape[1]
3.為完整數(shù)據(jù)集放入缺失值
#首先確定我們希望放入的缺失數(shù)據(jù)的比例,在這里我們假設(shè)是50%,那總共就要有3289個數(shù)據(jù)缺失
rng = np.random.RandomState(0)
missing_rate = 0.5
#計算缺失的個數(shù),np.floor向下取整,返回.0格式的浮點數(shù)
n_missing_samples = int(np.floor(n_samples * n_features * missing_rate))
#所有數(shù)據(jù)要隨機遍布在數(shù)據(jù)集的各行各列當(dāng)中,而一個缺失的數(shù)據(jù)會需要一個行索引和一個列索引
#如果能夠創(chuàng)造一個數(shù)組,包含3289個分布在0~506中間的行索引,和3289個分布在0~13之間的列索引
#那我們就可以利用索引來為數(shù)據(jù)中的任意3289個位置賦空值
#然后我們用0,均值和隨機森林來填寫這些缺失值,然后查看回歸的結(jié)果如何
#randint(下限,上限,n)請在上/下限之間取出n個整數(shù)(會有重復(fù))
missing_features = rng.randint(0,n_features,n_missing_samples)
missing_samples = rng.randint(0,n_samples,n_missing_samples)
#missing_samples = rng.choice(dataset.data.shape[0],n_missing_samples,replace=False)
#我們現(xiàn)在采樣了3289個數(shù)據(jù),遠遠超過我們的樣本量506,所以我們使用隨機抽取的函數(shù)randint。但如果我們需要
#的數(shù)據(jù)量小于我們的樣本量506,那我們可以采用np.random.choice來抽樣,choice會隨機抽取不重復(fù)的隨機數(shù),
#因此可以幫助我們讓數(shù)據(jù)更加分散,確保數(shù)據(jù)不會集中在一些行中
#創(chuàng)建副本,用于創(chuàng)建缺失的數(shù)據(jù)集
X_missing = X_full.copy()
#y不能為空,不然就變成了無監(jiān)督學(xué)習(xí)
y_missing = y_full.copy()
#讓其中指定的位置轉(zhuǎn)為空值
X_missing[missing_samples,missing_features] = np.nan
#轉(zhuǎn)換成DataFrame是為了后續(xù)方便各種操作,numpy對矩陣的運算速度快到拯救人生,但是在索引等功能上卻不如pandas來得好用
X_missing = pd.DataFrame(X_missing)
此時的X_missing和y_missing就變成了:


4.使用0和均值填補缺失值
#使用均值進行填補
from sklearn.impute import SimpleImputer
#第一個參數(shù)指明缺失值長啥樣,第二個參數(shù)指明用什么來填補缺失值
imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean')
#訓(xùn)練+導(dǎo)出——>fit_transform:先訓(xùn)練后轉(zhuǎn)出的接口
X_missing_mean = imp_mean.fit_transform(X_missing)
#使用0進行填補
imp_0 = SimpleImputer(missing_values=np.nan, strategy="constant",fill_value=0)
X_missing_0 = imp_0.fit_transform(X_missing)
5.使用隨機森林填補缺失值
對于一個有n個特征的數(shù)據(jù)來說,其中特征T有缺失值,我們就把特征T當(dāng)作標(biāo)簽,其他的n-1個特征和原本的標(biāo)簽組成新的特征矩陣。那對于T來說,它沒有缺失的部分,就是我們的Y_test,這部分?jǐn)?shù)據(jù)既有標(biāo)簽也有特征,而它缺失的部分,只有特征沒有標(biāo)簽,就是我們需要預(yù)測的部分。
特征T不缺失的值對應(yīng)的其他n-1個特征 + 本來的標(biāo)簽:X_train
特征T不缺失的值:Y_train
特征T缺失的值對應(yīng)的其他n-1個特征 + 本來的標(biāo)簽:X_test
特征T缺失的值:未知,我們需要預(yù)測的Y_test
這種做法,對于某一個特征大量缺失,其他特征卻很完整的情況,非常適用。
那如果數(shù)據(jù)中除了特征T之外,其他特征也有缺失值怎么辦?
答案是遍歷所有的特征,從缺失最少的開始進行填補(因為填補缺失最少的特征所需要的準(zhǔn)確信息最少)。
填補一個特征時,先將其他特征的缺失值用0代替,每完成一次回歸預(yù)測,就將預(yù)測值放到原本的特征矩陣中,再繼續(xù)填補下一個特征。每一次填補完畢,有缺失值的特征會減少一個,所以每次循環(huán)后,需要用0來填補的特征就越來越少。當(dāng)
進行到最后一個特征時(這個特征應(yīng)該是所有特征中缺失值最多的),已經(jīng)沒有任何的其他特征需要用0來進行填補了,而我們已經(jīng)使用回歸為其他特征填補了大量有效信息,可以用來填補缺失最多的特征。
遍歷所有的特征后,數(shù)據(jù)就完整,不再有缺失值了
X_missing_reg = X_missing.copy()
#找出數(shù)據(jù)集中缺失值從小到大排列的特征們的順序,agrsort帶有索引
#返回:從小到大排序順序所對應(yīng)的索引
sortindex = np.argsort(X_missing_reg.isnull().sum(axis=0)).values
X_missing_reg 的值:

#構(gòu)建我們的新特征矩陣(沒被選中去填充的特征 + 原始標(biāo)簽)和新標(biāo)簽(被選中去填充的特征)
#不在原來的矩陣上進行填補,所以用新的矩陣來替代
df = X_missing_reg
#構(gòu)建新標(biāo)簽,用切片的方式取出對應(yīng)列
fillc = df.iloc[:,6]
#新特征矩陣,將新標(biāo)簽和新矩陣連接起來,axis=1說明用行進行匹配(下面這個代碼只能運行一次,不然會有一大堆矩陣)
df = pd.concat([df.iloc[:,df.columns != 6],pd.DataFrame(y_full)],axis=1)
此時的df:

#在新特征矩陣中,對含有缺失值的列,進行0的填補
df_0 =SimpleImputer(missing_values=np.nan,
strategy='constant',fill_value=0).fit_transform(df)
此時的df_0:

#找出我們的訓(xùn)練集和測試集
#被選中要填充的特征中(現(xiàn)在是我們的標(biāo)簽),存在的那些值,是非空值
Ytrain = fillc[fillc.notnull()]
#被選中要填充的特征中(現(xiàn)在是我們的標(biāo)簽),不存在的那些值,是空值
#我們所需要的是Ytest的索引,用來指明哪些是空值
Ytest = fillc[fillc.isnull()]
#在新特征矩陣上,被選出來的要填充的特征的非空值所對應(yīng)的記錄
Xtrain = df_0[Ytrain.index,:]
#在新特征矩陣上,被選出來的要填充的特征的空值所對應(yīng)的記錄
Xtest = df_0[Ytest.index,:]
#跑模型
#用隨機森林回歸來填補缺失值
rfc = RandomForestRegressor(n_estimators=100)
rfc = rfc.fit(Xtrain, Ytrain)
#用predict接口將Xtest導(dǎo)入,得到預(yù)測結(jié)果(回歸結(jié)果),就是我們要用來填補空值的值
Ypredict = rfc.predict(Xtest)
所得到的Ypredict就是一串標(biāo)簽(隨即回歸森林預(yù)測所得):

#將填補好的特征返回到我們的原始的特征矩陣中
X_missing_reg.loc[X_missing_reg.iloc[:,6].isnull(),6] = Ypredict
最終得到了填補完畢的X_missing_reg:

用上述原理調(diào)用for循環(huán):
#for循環(huán)來進行填補缺失值,共有五步
for i in sortindex:
#構(gòu)建我們的新特征矩陣(沒被選中去填充的特征 + 原始標(biāo)簽)和新標(biāo)簽(被選中去填充的特征)
#不在原來的矩陣上進行填補,所以用新的矩陣來替代
df = X_missing_reg
#構(gòu)建新標(biāo)簽,用切片的方式取出對應(yīng)列
fillc = df.iloc[:,i]
#新特征矩陣,將新標(biāo)簽和新矩陣連接起來,axis=1說明用行進行匹配(下面這個代碼只能運行一次,不然會有一大堆矩陣)
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)
#找出我們的訓(xùn)練集和測試集
#被選中要填充的特征中(現(xiàn)在是我們的標(biāo)簽),存在的那些值,是非空值
Ytrain = fillc[fillc.notnull()]
#被選中要填充的特征中(現(xiàn)在是我們的標(biāo)簽),不存在的那些值,是空值
#我們所需要的是Ytest的索引,用來指明哪些是空值
Ytest = fillc[fillc.isnull()]
#在新特征矩陣上,被選出來的要填充的特征的非空值所對應(yīng)的記錄
Xtrain = df_0[Ytrain.index,:]
#在新特征矩陣上,被選出來的要填充的特征的空值所對應(yīng)的記錄
Xtest = df_0[Ytest.index,:]
#跑模型
#用隨機森林回歸來填補缺失值
rfc = RandomForestRegressor(n_estimators=100)
rfc = rfc.fit(Xtrain, Ytrain)
#用predict接口將Xtest導(dǎo)入,得到預(yù)測結(jié)果(回歸結(jié)果),就是我們要用來填補空值的值
Ypredict = rfc.predict(Xtest)
#將填補好的特征返回到我們的原始的特征矩陣中
X_missing_reg.loc[X_missing_reg.iloc[:,i].isnull(),i] = Ypredict
填完之后的X_missing_reg:

此時我們有了四種數(shù)據(jù):完整數(shù)據(jù)X_full、用回歸數(shù)據(jù)來填的X_missing_reg、用0填的X_missing_0和用均值填的X_missing_mean
6. 對填補好的數(shù)據(jù)進行建模
對所有數(shù)據(jù)進行建模,取得MSE結(jié)果
X = [X_full,X_missing_mean,X_missing_0,X_missing_reg]
#mse要越小越好
mse = []
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', cv=5).mean()
mse.append(scores * -1)
所得到的得分結(jié)果為:

7. 用所得結(jié)果畫出條形圖
x_labels = ['Full data',
'Zero Imputation',
'Mean Imputation',
'Regressor Imputation']
colors = ['r', 'g', 'b', 'orange']
#畫出畫布
plt.figure(figsize=(12, 6))
#添加子圖plt.subplot(一般在有多個圖標(biāo)要畫的時候用來定義圖標(biāo)位置,這里可以有多個圖)
ax = plt.subplot(111)
for i in np.arange(len(mse)): #相當(dāng)于range(mse)相當(dāng)于y軸
#畫條形圖(從左往右)barh(共有i個數(shù),現(xiàn)在畫第i個數(shù),alpha是顏色深度,align說明放在哪)
ax.barh(i, mse[i],color=colors[i], alpha=0.6, align='center')
ax.set_title('Imputation Techniques with Boston Data')
#讓X軸特征不從0開始
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')
#按x的標(biāo)簽分類
ax.set_yticklabels(x_labels)
plt.show()

2.4 機器學(xué)習(xí)中調(diào)參的基本思想
-
泛化誤差(Genelization error):用來衡量模型在未知數(shù)據(jù)上的準(zhǔn)確率的指標(biāo):
當(dāng)模型在未知數(shù)據(jù)(測試集或者袋外數(shù)據(jù))上表現(xiàn)糟糕時,我們說模型的泛化程度不夠,泛化誤差大,模型的效果不好。泛化誤差受到模型的結(jié)構(gòu)(復(fù)雜度)影響:

當(dāng)模型太復(fù)雜,模型就會過擬合,泛化能力就不夠,所以泛化誤差大。當(dāng)模型太簡單,模型就會欠擬合,擬合能力就不夠,所以誤差也會大。只有當(dāng)模型的復(fù)雜度剛剛好的才能夠達到泛化誤差最小的目標(biāo)。
——>對樹模型來說,樹越茂盛,深度越深,枝葉越多,模型就越復(fù)雜。所以樹模型是天生位于圖的右上角的模型,隨機森林是以樹模型為基礎(chǔ),所以隨機森林也是天生復(fù)雜度高的模型。隨機森林的參數(shù),都是向著一個目標(biāo)去:減少模型的復(fù)雜度,把模型往圖像的左邊移動,防止過擬合;
- 調(diào)參的時候的注意點:
- 模型太復(fù)雜或者太簡單,都會讓泛化誤差高,我們追求的是位于中間的平衡點;
- 模型太復(fù)雜就會過擬合,模型太簡單就會欠擬合;
- 對樹模型和樹的集成模型來說,樹的深度越深,枝葉越多,模型越復(fù)雜;
- 樹模型和樹的集成模型的目標(biāo),都是減少模型復(fù)雜度,把模型往圖像的左邊移動。
-
調(diào)參順序:
一般就調(diào)2~4就行了
接下來就可以開始實操了:
2.4.1 案例:隨機森林在乳腺癌數(shù)據(jù)上的調(diào)參隨機森林在乳腺癌數(shù)據(jù)上的調(diào)參
1. 導(dǎo)入需要的庫
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.導(dǎo)入數(shù)據(jù)集,探索數(shù)據(jù)導(dǎo)入數(shù)據(jù)集,探索數(shù)據(jù)
data = load_breast_cancer()
#是一個二分類數(shù)據(jù),樣本量很少,容易發(fā)生過擬合
3. 進行一次簡單的建模,看看模型本身在數(shù)據(jù)集上的效果
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
#這里可以看到,隨機森林在乳腺癌數(shù)據(jù)上的表現(xiàn)本就還不錯,在現(xiàn)實數(shù)據(jù)集上,基本上不可能什么都不調(diào)就看到95%以上的準(zhǔn)確率
4. 隨機森林調(diào)整的第一步:無論如何先來調(diào)n_estimators在這里我們選擇學(xué)習(xí)曲線,可以使用網(wǎng)格搜索嗎?可以,但是只有學(xué)習(xí)曲線,才能看見趨勢我個人的傾向是,要看見n_estimators在什么取值開始變得平穩(wěn),是否一直推動模型整體準(zhǔn)確率的上升等信息第一次的學(xué)習(xí)曲線,可以先用來幫助我們劃定范圍,我們?nèi)∶渴畟€數(shù)作為一個階段,來觀察n_estimators的變化如何引起模型整體準(zhǔn)確率的變化
scorel = []
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()
scorel.append(score)
#輸出最高分以及其對應(yīng)的n_estimators的值
print(max(scorel),(scorel.index(max(scorel))*10)+1)
plt.figure(figsize=[20,5])
plt.plot(range(1,201,10),scorel)
plt.show()
#list.index([object])
#返回這個object在列表list中的索引
所得結(jié)果為:

5. 在確定好的范圍內(nèi),進一步細化學(xué)習(xí)曲線
- 跟上面其實都一樣,不過范圍重新劃了一下
scorel = []
for i in range(65,75):
rfc = RandomForestClassifier(n_estimators=i,
n_jobs=-1,
random_state=90)
score = cross_val_score(rfc,data.data,data.target,cv=10).mean()
scorel.append(score)
print(max(scorel),([*range(65,75)][scorel.index(max(scorel))]))
plt.figure(figsize=[20,5])
plt.plot(range(65,75),scorel)
plt.show()

接下來就進入網(wǎng)格搜索,我們將使用網(wǎng)格搜索對參數(shù)一個個進行調(diào)整
6. 為網(wǎng)格搜索做準(zhǔn)備,書寫網(wǎng)格搜索的參數(shù)
為什么我們不同時調(diào)整多個參數(shù)呢?原因有兩個:
- 1)同時調(diào)整多個參數(shù)會運行非常緩慢,在課堂上我們沒有這么多的時間;
- 2)同時調(diào)整多個參數(shù),會讓我們無法理解參數(shù)的組合是怎么得來的,所以即便網(wǎng)格搜索調(diào)出來的結(jié)果不好,我們也不知道從哪里去改。在這里,為了使用復(fù)雜度-泛化誤差方法(方差-偏差方法),我們對參數(shù)進行一個個地調(diào)整。
"""
有一些參數(shù)是沒有參照的,很難說清一個范圍,這種情況下我們使用學(xué)習(xí)曲線,看趨勢
從曲線跑出的結(jié)果中選取一個更小的區(qū)間,再跑曲線
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)}
對于大型數(shù)據(jù)集,可以嘗試從1000來構(gòu)建,先輸入1000,每100個葉子一個區(qū)間,再逐漸縮小范圍
有一些參數(shù)是可以找到一個范圍的,或者說我們知道他們的取值和隨著他們的取值,模型的整體準(zhǔn)確率會如何變化,
這樣的參數(shù)我們就可以直接跑網(wǎng)格搜索
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. 開始按照參數(shù)對模型整體準(zhǔn)確率的影響程度進行調(diào)參,首先調(diào)整max_depth
#調(diào)整max_depth
param_grid = {'max_depth':np.arange(1, 20, 1)}
# 一般根據(jù)數(shù)據(jù)的大小來進行一個試探,乳腺癌數(shù)據(jù)很小,所以可以采用1~10,或者1~20這樣的試探
# 但對于像digit recognition那樣的大型數(shù)據(jù)來說,我們應(yīng)該嘗試30~50層深度(或許還不足夠
# 更應(yīng)該畫出學(xué)習(xí)曲線,來觀察深度對模型的影響
rfc = RandomForestClassifier(n_estimators=73
,random_state=90
)
#第一個參數(shù)是模型,第二個是要搜索的參數(shù),第三個就是次數(shù)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(data.data,data.target)
#調(diào)整后的最佳參數(shù)
GS.best_params_
#{'max_depth': 8}
#調(diào)整后的最佳分?jǐn)?shù)
GS.best_score_
#0.9666353383458647
注意:將max_depth設(shè)置為有限之后,模型的準(zhǔn)確率下降了,這是因為:限制max_depth,是讓模型變得簡單,把模型向左推,而模型整體的準(zhǔn)確率下降了,即整體的泛化誤差上升了,這說明模型現(xiàn)在位于圖像左邊,即泛化誤差最低點的左邊(偏差為主導(dǎo)的一邊);
——>除了max_features,我們沒有任何參數(shù)可以調(diào)整了
因為max_depth,min_samples_leaf和min_samples_split是剪枝參數(shù),是減小復(fù)雜度的參數(shù)。
8. 調(diào)整max_features
#調(diào)整max_features
param_grid = {'max_features':np.arange(5,30,1)}
"""
max_features是唯一一個即能夠?qū)⒛P屯螅ǖ头讲罡咂睿┩?,也能夠?qū)⒛P屯遥ǜ叻讲畹推睿┩频膮?shù)。我
們需要根據(jù)調(diào)參前,模型所在的位置(在泛化誤差最低點的左邊還是右邊)來決定我們要將max_features往哪邊調(diào)。
現(xiàn)在模型位于圖像左側(cè),我們需要的是更高的復(fù)雜度,因此我們應(yīng)該把max_features往更大的方向調(diào)整,可用的特征
越多,模型才會越復(fù)雜。max_features的默認(rèn)最小值是sqrt(n_features),因此我們使用這個值作為調(diào)參范圍的
最小值。
"""
rfc = RandomForestClassifier(n_estimators=73
,random_state=90
)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(data.data,data.target)
GS.best_params_
# {'max_features': 24}
GS.best_score_
# 0.9666666666666668
網(wǎng)格搜索返回了max_features的最小值,可見max_features升高之后,模型的準(zhǔn)確率降低了。這說明,我們把模型往右推,模型的泛化誤差增加了。前面用max_depth往左推,現(xiàn)在用max_features往右推,泛化誤差都增加,這說明模型本身已經(jīng)處于泛化誤差最低點,已經(jīng)達到了模型的預(yù)測上限,沒有參數(shù)可以左右的部分了。剩下的那些誤差,是噪聲決定的,已經(jīng)沒有方差和偏差的舞臺了。
9. 調(diào)整min_samples_leaf
#調(diào)整min_samples_leaf
param_grid={'min_samples_leaf':np.arange(1, 1+10, 1)}
#對于min_samples_split和min_samples_leaf,一般是從他們的最小值開始向上增加10或20
#面對高維度高樣本量數(shù)據(jù),如果不放心,也可以直接+50,對于大型數(shù)據(jù),可能需要200~300的范圍
#如果調(diào)整的時候發(fā)現(xiàn)準(zhǔn)確率無論如何都上不來,那可以放心大膽調(diào)一個很大的數(shù)據(jù),大力限制模型的復(fù)雜度
rfc = RandomForestClassifier(n_estimators=73
,random_state=90
)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(data.data,data.target)
GS.best_params_
#{'min_samples_leaf': 1}
GS.best_score_
#0.9666353383458647
10. 不懈努力,繼續(xù)嘗試min_samples_split
#調(diào)整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_
#{'min_samples_split': 2}
GS.best_score_
#0.9666353383458647
和min_samples_leaf一樣的結(jié)果,返回最小值并且模型整體的準(zhǔn)確率降低了
11. 最后嘗試一下criterion
#調(diào)整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_
#{'criterion': 'gini'}
GS.best_score_
#0.9666353383458647
12. 調(diào)整完畢,總結(jié)出模型的最佳參數(shù)
rfc = RandomForestClassifier(n_estimators=73,random_state=90)
score = cross_val_score(rfc,data.data,data.target,cv=10).mean()
score
#0.9666353383458647
score - score_pre
#0.0017543859649122862
總結(jié):在整個調(diào)參過程之中,我們首先調(diào)整了n_estimators(無論如何都請先走這一步),然后調(diào)整max_depth,通過max_depth產(chǎn)生的結(jié)果,來判斷模型位于復(fù)雜度-泛化誤差圖像的哪一邊,從而選擇我們應(yīng)該調(diào)整的參數(shù)和調(diào)參的方向。如果感到困惑,也可以畫很多學(xué)習(xí)曲線來觀察參數(shù)會如何影響我們的準(zhǔn)確率,選取學(xué)習(xí)曲線中單調(diào)的部分來放大研究(如同我們對n_estimators做的)。學(xué)習(xí)曲線的拐點也許就是我們一直在追求的,最佳復(fù)雜度對應(yīng)的泛化誤差最低點(也是方差和偏差的平衡點)。
網(wǎng)格搜索:它也可以一起調(diào)整多個參數(shù),大家只要有時間,可以自己跑一下,看看網(wǎng)格搜索會給我們怎樣的結(jié)果,有時候,它的結(jié)果比我們的好,有時候,我們手動調(diào)整的結(jié)果會比較好。當(dāng)然了,我們的乳腺癌數(shù)據(jù)集非常完美,所以只需要調(diào)n_estimators一個參數(shù)就達到了隨機森林在這個數(shù)據(jù)集上表現(xiàn)得極限。在我們上周使用的泰坦尼克號案例的數(shù)據(jù)中,我們使用同樣的方法調(diào)出了如下的參數(shù)組合:
rfc = RandomForestClassifier(n_estimators=68
,random_state=90
,criterion="gini"
,min_samples_split=8
,min_samples_leaf=1
,max_depth=12
,max_features=2
,max_leaf_nodes=36
)
這個組合的準(zhǔn)確率達到了83.915%,比單棵決策樹提升了大約7%,比調(diào)參前的隨機森林提升了2.02%,這對于調(diào)參來說其實是一個非常巨大的進步。
不過,泰坦尼克號數(shù)據(jù)的運行緩慢,大家量力量時間而行,可以試試看用復(fù)雜度-泛化誤差方法(方差-偏差方法)來解讀一下這個調(diào)參結(jié)果和過程。
