1. 程式人生 > >python實現隨機森林

python實現隨機森林

定義:

隨機森林指的是利用多棵決策樹對樣本進行訓練並預測的一種分類器。可迴歸可分類。
所以隨機森林是基於多顆決策樹的一種整合學習演算法,常見的決策樹演算法主要有以下幾種:
1. ID3:使用資訊增益g(D,A)進行特徵選擇
2. C4.5:資訊增益率 =g(D,A)/H(A)
3. CART:基尼係數
一個特徵的資訊增益(或資訊增益率,或基尼係數)越大,表明特徵對樣本的熵的減少能力更強,這個特徵使得資料由不確定性到確定性的能力越強。

Bagging和Boosting的概念與區別

隨機森林屬於整合學習(Ensemble Learning)中的bagging演算法。在整合學習中,主要分為bagging演算法和boosting演算法。我們先看看這兩種方法的特點和區別。

Bagging(套袋法)

bagging的演算法過程如下:

  • 從原始樣本集中使用Bootstraping方法(自助法,是一種有放回的抽樣方法)隨機抽取n個訓練樣本,共進行k輪抽取,得到k個訓練集。(k個訓練集之間相互獨立,元素可以有重複)

  • 對於k個訓練集,我們訓練k個模型(這k個模型可以根據具體問題而定,比如決策樹,knn等)

  • 對於分類問題:由投票表決產生分類結果;對於迴歸問題:由k個模型預測結果的均值作為最後預測結果。(所有模型的重要性相同)

Boosting(提升法)

boosting的演算法過程如下:

  • 對於訓練集中的每個樣本建立權值wi,表示對每個樣本的關注度。當某個樣本被誤分類的概率很高時,需要加大對該樣本的權值。
  • 進行迭代的過程中,每一步迭代都是一個弱分類器。我們需要用某種策略將其組合,作為最終模型。(例如AdaBoost給每個弱分類器一個權值,將其線性組合最為最終分類器。誤差越小的弱分類器,權值越大)
Bagging,Boosting的主要區別
  • 樣本選擇上:Bagging採用的是Bootstrap隨機有放回抽樣;而Boosting每一輪的訓練集是不變的,改變的只是每一個樣本的權重。
  • 樣本權重:Bagging使用的是均勻取樣,每個樣本權重相等;Boosting根據錯誤率調整樣本權重,錯誤率越大的樣本權重越大。
  • 預測函式:Bagging所有的預測函式的權重相等;Boosting中誤差越小的預測函式其權重越大。
  • 平行計算:Bagging各個預測函式可以並行生成;Boosting各個預測函式必須按順序迭代生成。

下面是將決策樹與這些演算法框架進行結合所得到的新的演算法:

1)Bagging + 決策樹 = 隨機森林

2)AdaBoost + 決策樹 = 提升樹

3)Gradient Boosting + 決策樹 = GBDT

建立流程及舉例

舉例:
這裡寫圖片描述
考慮一個簡單例子:在二分類任務中,假定三個分類器在三個測試樣本上的表現如下圖,其中√表示分類正確,×表示分類錯誤,整合學習的結果通過投票法產生,即“少數服從多數”。如上圖,在(a)中,每個分類器都只有66.6%的精度,但整合學習卻達到了100%;在(b)中,三個分類器沒有差別,整合之後效能沒有提高;在(c)中,每個分類器的精度都只有33.3%,整合學習的結果變得更糟。這個簡單地例子顯示出:要獲得好的整合,個體學習器應“好而不同”,即個體學習器要有一定的“準確性”,即學習器不能太差,並且要有“多樣性”,即學習器間具有差異。

構建:
所以綜上:
隨機森林用於分類時,即採用n個決策樹分類,將分類結果用簡單投票法得到最終分類,提高分類準確率。

簡單來說,隨機森林就是對決策樹的整合,但有兩點不同:

(1)取樣的差異性:從含m個樣本的資料集中有放回的取樣,得到含m個樣本的取樣集,用於訓練。這樣能保證每個決策樹的訓練樣本不完全一樣。

(2)特徵選取的差異性:每個決策樹的n個分類特徵是在所有特徵中隨機選擇的(n是一個需要我們自己調整的引數)

決策樹相當於一個大師,通過自己在資料集中學到的知識對於新的資料進行分類。但是俗話說得好,一個諸葛亮,玩不過三個臭皮匠。隨機森林就是希望構建多個臭皮匠,希望最終的分類效果能夠超過單個大師的一種演算法。
那隨機森林具體如何構建呢?有兩個方面:資料的隨機性選取,以及待選特徵的隨機選取。

1.資料的隨機選取:

首先,從原始的資料集中採取有放回的抽樣,構造子資料集,子資料集的資料量是和原始資料集相同的。不同子資料集的元素可以重複,同一個子資料集中的元素也可以重複。第二,利用子資料集來構建子決策樹,將這個資料放到每個子決策樹中,每個子決策樹輸出一個結果。最後,如果有了新的資料需要通過隨機森林得到分類結果,就可以通過對子決策樹的判斷結果的投票,得到隨機森林的輸出結果了。如下圖,假設隨機森林中有3棵子決策樹,2棵子樹的分類結果是A類,1棵子樹的分類結果是B類,那麼隨機森林的分類結果就是A類。

這裡寫圖片描述

2.待選特徵的隨機選取

與資料集的隨機選取類似,隨機森林中的子樹的每一個分裂過程並未用到所有的待選特徵,而是從所有的待選特徵中隨機選取一定的特徵,之後再在隨機選取的特徵中選取最優的特徵。這樣能夠使得隨機森林中的決策樹都能夠彼此不同,提升系統的多樣性,從而提升分類效能。

下圖中,藍色的方塊代表所有可以被選擇的特徵,也就是目前的待選特徵。黃色的方塊是分裂特徵。左邊是一棵決策樹的特徵選取過程,通過在待選特徵中選取最優的分裂特徵(ID3演算法,C4.5演算法,CART演算法等等),完成分裂。右邊是一個隨機森林中的子樹的特徵選取過程。

這裡寫圖片描述

所以隨機森林需要調整的引數有:

(1) 決策樹的個數

(2) 特徵屬性的個數

(3) 遞迴次數(即決策樹的深度)

python程式碼實現

基於scikit-learn第三方機器學習庫的實現:

# -*- coding: utf-8 -*-
"""
Created on Thu Jul 26 16:38:18 2018

@author: aoanng
"""
from sklearn.model_selection import cross_val_score
from sklearn.datasets import make_blobs
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.tree import DecisionTreeClassifier

##建立100個類共10000個樣本,每個樣本10個特徵
X, y = make_blobs(n_samples=10000, n_features=10, centers=100,random_state=0)

## 決策樹
clf1 = DecisionTreeClassifier(max_depth=None, min_samples_split=2,random_state=0)
scores1 = cross_val_score(clf1, X, y)
print(scores1.mean())

## 隨機森林
clf2 = RandomForestClassifier(n_estimators=10, max_depth=None,min_samples_split=2, random_state=0)
scores2 = cross_val_score(clf2, X, y)
print(scores2.mean())

## ExtraTree分類器集合
clf3 = ExtraTreesClassifier(n_estimators=10, max_depth=None,min_samples_split=2, random_state=0)
scores3 = cross_val_score(clf3, X, y)
print(scores3.mean())

輸出結果:

0.979408793821
0.999607843137
0.999898989899

效能對比:ExtraTree分類器集合 > 隨機森林 > 決策樹

程式碼實現流程:

(1) 匯入檔案並將所有特徵轉換為float形式

(2) 將資料集分成n份,方便交叉驗證

(3) 構造資料子集(隨機取樣),並在指定特徵個數(假設m個,手動調參)下選取最優特徵

(4) 構造決策樹

(5) 建立隨機森林(多個決策樹的結合)

(6) 輸入測試集並進行測試,輸出預測結果

網上別人程式碼:

# -*- 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))))

效能及優缺點

優點:

    1.很多的資料集上表現良好;

    2.能處理高維度資料,並且不用做特徵選擇;

    3.訓練完後,能夠給出那些feature比較重要;

    4.訓練速度快,容易並行化計算。

缺點:

    1.在噪音較大的分類或迴歸問題上會出現過擬合現象;

    2.對於不同級別屬性的資料,級別劃分較多的屬性會對隨機森林有較大影響,則RF在這種資料上產出的數值是不可信的。

參考:
1. 決策樹演算法及python實現
2. 隨機森林演算法學習(RandomForest)
3. 隨機森林的原理分析及Python程式碼實現
4. 隨機森林_百度百科