1. 程式人生 > >Machine Learning學習筆記(十三)隨機森林(RandomForest)

Machine Learning學習筆記(十三)隨機森林(RandomForest)

隨機森林(RandomForest)

一、知識鋪墊

1.1 決策樹

決策樹是機器學習最基本的模型,在不考慮其他複雜情況下,我們可以用一句話來描述決策樹:如果得分大於等於60分,那麼你及格了。(if-then語句)

這是一個最最簡單的決策樹的模型,我們把及格和沒及格分別附上標籤,及格(1),沒及格(0),那麼得到的決策樹是這樣的

但是我們幾乎不會讓計算機做這麼簡單的工作,我們把情況變得複雜一點

引用別的文章的一個例子

這是一張女孩對於不同條件的男性是否會選擇見面的統計表,圖中是否見面作為我們需要分類的結果,因此最後我們的結果無非就只是是和否兩種情況。這是一個二分類的問題,但是需要判斷的條件多了很多,現在不僅僅只是一個判斷就能得出結果了,但是從上圖我們找到了一個結果為否的記錄,因此如果一個男性在城市無房產、年收入小於 17w 且離過婚,則可以預測女孩不會跟他見面。

那麼問題就來了,在這種複雜的情況下,決策樹怎麼構建?

先通過城市是否擁有房產這條特徵,把這10個人劃分為2類

這個分類結果並不是很好,因為它沒有將見面與不見面完全的分開,在演算法中,當然不能憑我們的“感覺”去評價分類結果的好壞。我們需要用一個數去表示。

1.2  Gini不純度

1、資訊熵

這裡介紹資訊理論中的資訊量資訊熵的知識

資訊量:資訊量是對資訊的度量,就跟溫度的度量是攝氏度一樣,資訊的大小跟隨機事件的概率有關。

例如: 在哈爾濱的冬天,一條訊息說:哈爾濱明天溫度30攝氏度,這個事件肯定會引起轟動,因為它發生的概率很小(資訊量大)。日過是夏天,“明天溫度30攝氏度”可能沒有人覺得是一個新聞,因為夏天溫度30攝氏度太正常了,概率太大了(資訊點太小了)

從這個例子中可以看出 一個隨機事件的資訊量的大小與其發生概率是成反相關的。

夏農定義的一個事件的資訊資訊量為:I(X) = log2(1/p) 其中p為事件X發生的概率

資訊熵:Entropy 一個隨機變數 X 可以代表n個隨機事件,對應的隨機變為X=xi,那麼熵的定義就是 X的加權資訊量。

H(x) = p(x1)I(x1)+...+p(xn)I(x1) 

        = p(x1)log2(1/p(x1)) +.....+p(xn)log2(1/p(xn))

        = -p(x1)log2(p(x1)) - ........-p(xn)log2(p(xn))

其中p(xi)代表xi發生的概率

例如有32個足球隊比賽,每一個隊的實力相當,那麼每一個對勝出的概率都是1/32

那麼 要猜對哪個足球隊勝出 非常困難,這個時候的熵H(x) = 32 * (1/32)log(1/(1/32)) = 5熵也可以作為一個系統的混亂程度的標準。

試想如果32個隊中有一個是ac米蘭,另外31個對是北郵計算機1班隊,2班,...31班那麼幾乎只有一個可能 ac米蘭勝利的概率是100%,其他的都是0%,這個系統的熵

就是 1*log(1/1) = 0. 這個系統其實是有序的,熵很小,而前面熵為5 系統處於無序狀態。

 

2、基尼不純度

      基尼不純度的大概意思是 一個隨機事件變成它的對立事件的概率

      例如 一個隨機事件X ,P(X=0) = 0.5 ,P(X=1)=0.5

      那麼基尼不純度就為   P(X=0)*(1 - P(X=0)) +   P(X=1)*(1 - P(X=1))  = 0.5

       一個隨機事件Y ,P(Y=0) = 0.1 ,P(Y=1)=0.9

      那麼基尼不純度就為P(Y=0)*(1 - P(Y=0)) +   P(Y=1)*(1 - P(Y=1))  = 0.18

     很明顯 X比Y更混亂,因為兩個都為0.5 很難判斷哪個發生。而Y就確定得多,Y=0發生的概率很大。而基尼不純度也就越小。

    所以基尼不純度也可以作為 衡量系統混亂程度的標準

Gini不純度是對分類結果好壞的度量標準

他的值是:1-每個標籤佔總數的比例的平方和。即1–∑mi=1fi2

對於上述的結果來講,總的集合D被分為兩個集合D1,D2,假設見面為1,不見面為0。

那麼D1的不純度為1-f1^2-f0^2,總數為5,見面的佔了全部,則f1=1,f0=0,結果為0

D2的不純度為1-f1^2-f0^2,f1=0.8,f0=0.2,結果為0.32

ok,那麼整個分類結果的Gini不純度就是D1/D與0的乘積 加上 D2/D與0.32的乘積,為0.16

Gini值代表了某一個分類結果的“純度”,我們希望結果的純度很高,這樣就不需要對這一結果進行處理了。

從以上分析可以看出,Gini值越小,純度越高,結果越好。

三、決策樹的生成

在第一個例子中“如果得分大於等於60分,那麼你及格了”中,生成決策樹步驟是首先選擇特徵,“得分”,然後確定臨界值,“>=60”

1.複雜的情況下也是一樣,對於每一個特徵,找到一個使得Gini值最小的分割點(這個分割點可以是>,<,>=這樣的判斷,也可以是=,!=),然後比較每個特徵之間最小的Gini值,作為當前最優的特徵的最優分割點(這實際上涉及到了兩個步驟,選擇最優特徵以及選擇最優分割點)。

2.在第一步完成後,會生成兩個葉節點,我們對這兩個葉節點做判斷,計算它的Gini值是否足夠小(若是,就將其作為葉子不再分類)

3.將上步得到的葉節點作為新的集合,進行步驟1的分類,延伸出兩個新的葉子節點(當然此時該節點變成了父節點)

4.迴圈迭代至不再有Gini值不符合標準的葉節點

四、決策樹的缺陷

我們用決策樹把一個平面上的眾多點分為兩類,每一個點都有(x1,x2)兩個特徵,下面展示分類的過程

 

最後生成的決策樹,取了四個分割點,在圖上的顯示如下,只要是落在中央矩形區域內預設是綠色,否則為紅色

不過這種情況是分類引數選擇比較合理的情況(它不介意某些綠色的點落在外圍),但是當我們在訓練的時候需要將所有的綠點無差錯的分出來(即引數選擇不是很合理的情況),決策樹會產生過擬合的現象,導致泛化能力變弱。

 

五、隨機森林

鑑於決策樹容易過擬合的缺點,隨機森林採用多個決策樹的投票機制來改善決策樹,我們假設隨機森林使用了m棵決策樹,那麼就需要產生m個一定數量的樣本集來訓練每一棵樹,如果用全樣本去訓練m棵決策樹顯然是不可取的,全樣本訓練忽視了局部樣本的規律,對於模型的泛化能力是有害的

產生n個樣本的方法採用Bootstraping法,這是一種有放回的抽樣方法,產生n個樣本

而最終結果採用Bagging的策略來獲得,即多數投票機制

隨機森林的生成方法:

1.從樣本集中通過重取樣的方式產生n個樣本

2.假設樣本特徵數目為a,對n個樣本選擇a中的k個特徵,用建立決策樹的方式獲得最佳分割點

3.重複m次,產生m棵決策樹

4.多數投票機制來進行預測

(需要注意的一點是,這裡m是指迴圈的次數,n是指樣本的數目,n個樣本構成訓練的樣本集,而m次迴圈中又會產生m個這樣的樣本集)

六、隨機森林實戰

資料集: 
我們的資料集是來自一個著名的資料探勘競賽網站,是一個關於泰坦尼克號,遊客生存情況的調查。可以從這裡下載:https://www.kaggle.com/c/titanic/data 

1.讀入資料

import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
train = pd.read_csv("E:/train.csv", dtype={"Age": np.float64},)
train.head(10)

這裡寫圖片描述

稍微分析一下,我們就可以篩選出對一個遊客的生存與否有關的變數:Pclass, Sex, Age, SibSp,Parch,Fare, Embarked. 一般來說,遊客的名字,買的船票號碼對其的生存情況應該影響很小。

len(train_data)
out:891

我們共有891條資料,將近900條,我們使用600條作為訓練資料,剩下的291條作為測試資料,通過對隨機森林的引數不斷調優,找出在測試結果上,預測最為精確的隨機森林模型。 
在具體的實驗之前,我們看一下使用隨機森林模型,需要注意哪幾個變數: 
在 sklearn中,隨機森林的函式模型是:

RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
            max_depth=None, max_features='auto', max_leaf_nodes=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, n_estimators=10, n_jobs=1,
            oob_score=False, random_state=None, verbose=0,
            warm_start=False)

引數分析 
A. max_features: 
隨機森林允許單個決策樹使用特徵的最大數量。 Python為最大特徵數提供了多個可選項。 下面是其中的幾個: 
Auto/None :簡單地選取所有特徵,每顆樹都可以利用他們。這種情況下,每顆樹都沒有任何的限制。 
sqrt :此選項是每顆子樹可以利用總特徵數的平方根個。 例如,如果變數(特徵)的總數是100,所以每顆子樹只能取其中的10個。“log2”是另一種相似型別的選項。 
0.2:此選項允許每個隨機森林的子樹可以利用變數(特徵)數的20%。如果想考察的特徵x%的作用, 我們可以使用“0.X”的格式。 
max_features如何影響效能和速度? 
增加max_features一般能提高模型的效能,因為在每個節點上,我們有更多的選擇可以考慮。 然而,這未必完全是對的,因為它降低了單個樹的多樣性,而這正是隨機森林獨特的優點。 但是,可以肯定,你通過增加max_features會降低演算法的速度。 因此,你需要適當的平衡和選擇最佳max_features。 
B. n_estimators: 
在利用最大投票數或平均值來預測之前,你想要建立子樹的數量。 較多的子樹可以讓模型有更好的效能,但同時讓你的程式碼變慢。 你應該選擇儘可能高的值,只要你的處理器能夠承受的住,因為這使你的預測更好更穩定。 
C. min_sample_leaf: 
如果您以前編寫過一個決策樹,你能體會到最小樣本葉片大小的重要性。 葉是決策樹的末端節點。 較小的葉子使模型更容易捕捉訓練資料中的噪聲。 一般來說,我更偏向於將最小葉子節點數目設定為大於50。在你自己的情況中,你應該儘量嘗試多種葉子大小種類,以找到最優的那個。

下面我們對上面提到的三個引數,進行調優,首先引數A,由於在我們的這個資料中,資料段總共只有七八個,所以我們就簡單的選取所有的特徵,所以我們只需要對剩下的兩個變數進行調優。 
在sklearn自帶的隨機森林演算法中,輸入的值必須是整數或者浮點數,所以我們需要對資料進行預處理,將字串轉化成整數或者浮點數:

def harmonize_data(titanic):
    # 填充空資料 和 把string資料轉成integer表示
    # 對於年齡欄位發生缺失,我們用所有年齡的均值替代
    titanic["Age"] = titanic["Age"].fillna(titanic["Age"].median())
    # 性別男: 用0替代
    titanic.loc[titanic["Sex"] == "male", "Sex"] = 0
    # 性別女: 用1替代
    titanic.loc[titanic["Sex"] == "female", "Sex"] = 1

    titanic["Embarked"] = titanic["Embarked"].fillna("S")

    titanic.loc[titanic["Embarked"] == "S", "Embarked"] = 0
    titanic.loc[titanic["Embarked"] == "C", "Embarked"] = 1
    titanic.loc[titanic["Embarked"] == "Q", "Embarked"] = 2

    titanic["Fare"] = titanic["Fare"].fillna(titanic["Fare"].median())

    return titanic

train_data = harmonize_data(train)

上面的程式碼是對原始資料進行清洗,填補缺失資料, 把string型別資料轉化成int資料 
下面的工作,我們開始劃分訓練資料和測試資料,總的資料有891個,我們用600個訓練資料集,剩下的291個作為測試資料集。

# 列出對生存結果有影響的欄位
predictors = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked"]
# 存放不同引數取值,以及對應的精度,每一個元素都是一個三元組(a, b, c)
results = []
# 最小葉子結點的引數取值
sample_leaf_options = list(range(1, 500, 3))
# 決策樹個數引數取值
n_estimators_options = list(range(1, 1000, 5))
groud_truth = train_data['Survived'][601:]

for leaf_size in sample_leaf_options:
    for n_estimators_size in n_estimators_options:
        alg = RandomForestClassifier(min_samples_leaf=leaf_size, n_estimators=n_estimators_size, random_state=50)
        alg.fit(train_data[predictors][:600], train_data['Survived'][:600])
        predict = alg.predict(train_data[predictors][601:])
        # 用一個三元組,分別記錄當前的 min_samples_leaf,n_estimators, 和在測試資料集上的精度
        results.append((leaf_size, n_estimators_size, (groud_truth == predict).mean()))
        # 真實結果和預測結果進行比較,計算準確率
        print((groud_truth == predict).mean())

# 列印精度最大的那一個三元組
print(max(results, key=lambda x: x[2]))

總的來說,調參對隨機森林來說,不會發生很大的波動,相比神經網路來說,隨機森林即使使用預設的引數,也可以達到良好的結果。在我們的例子中,通過粗略的調參,可以在測試集上達到84%的預測準確率.
附上全部程式碼:(執行時間比較久)

__author__ = 'Administrator'
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier

train = pd.read_csv("E:/train.csv", dtype={"Age": np.float64},)


def harmonize_data(titanic):
    # 填充空資料 和 把string資料轉成integer表示

    titanic["Age"] = titanic["Age"].fillna(titanic["Age"].median())

    titanic.loc[titanic["Sex"] == "male", "Sex"] = 0
    titanic.loc[titanic["Sex"] == "female", "Sex"] = 1

    titanic["Embarked"] = titanic["Embarked"].fillna("S")

    titanic.loc[titanic["Embarked"] == "S", "Embarked"] = 0
    titanic.loc[titanic["Embarked"] == "C", "Embarked"] = 1
    titanic.loc[titanic["Embarked"] == "Q", "Embarked"] = 2

    titanic["Fare"] = titanic["Fare"].fillna(titanic["Fare"].median())

    return titanic

train_data = harmonize_data(train)

predictors = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked"]
results = []
sample_leaf_options = list(range(1, 500, 3))
n_estimators_options = list(range(1, 1000, 5))
groud_truth = train_data['Survived'][601:]

for leaf_size in sample_leaf_options:
    for n_estimators_size in n_estimators_options:
        alg = RandomForestClassifier(min_samples_leaf=leaf_size, n_estimators=n_estimators_size, random_state=50)
        alg.fit(train_data[predictors][:600], train_data['Survived'][:600])
        predict = alg.predict(train_data[predictors][601:])
        # 用一個三元組,分別記錄當前的 min_samples_leaf,n_estimators, 和在測試資料集上的精度
        results.append((leaf_size, n_estimators_size, (groud_truth == predict).mean()))
        # 真實結果和預測結果進行比較,計算準確率
        print((groud_truth == predict).mean())

# 列印精度最大的那一個三元組
print(max(results, key=lambda x: x[2]))

七、隨機森林模型的總結

隨機森林是一個比較優秀的模型,在我的專案的使用效果上來看,它對於多維特徵的資料集分類有很高的效率,還可以做特徵重要性的選擇。執行效率和準確率較高,實現起來也比較簡單。但是在資料噪音比較大的情況下會過擬合,過擬合的缺點對於隨機森林來說還是較為致命的。

 

參考連結

[1]https://blog.csdn.net/mao_xiao_feng/article/details/52728164