監督學習集成模型——隨機森林

Boosting與Bagging

Boosting和Bagging都是機器學習中一種集成學習框架。集成學習的意思是將多個弱分類器組合成一個強分類器,這個強分類器能取所有弱分類器之所長,達到相對的最優性能。

Boosting的一般過程如下。以分類問題爲例,給定一個訓練集,訓練弱分類器要比訓練強分類器容易很多,從第一個弱分類器開始,Boosting通過訓練多個弱分類器,並在訓練過程中不斷改變訓練樣本的概率分佈,使得每次訓練時算法會更加關注上一個弱分類器的錯誤。通過組合多個這樣的弱分類器,便可以獲得這樣一個近乎完美的強分類器。

Bagging是區別於Boosting的一種集成學習框架,通過對數據集自身採樣獲取不同子集,並且對每個子集訓練基分類器來進行模型集成。Bagging是一種並行化集成學習方法。其核心概念在於自助採樣(Bootstrap Sampling),給定包含m個樣本的數據集,有放回的隨機抽取一個樣本放入採樣集中,經過m次採樣,可得到一個和原始數據集一樣大小的採樣集。我們可以採樣得到T個包含m個樣本的採樣集,然後基於每個採樣集訓練出一個基學習器,最後將這些基學習器進行組合。這便是Bagging的主要思想。

image

可以清楚的看到,Bagging是並行的框架,而Boosting則是序列框架(但也可以實現並行)。

隨機森林的基本原理

隨機森林,就是有很多棵決策樹構建起來的森林,因爲構建過程中的隨機性,故而稱之爲隨機森林。隨機森林算法是Bagging框架的一個典型代表。隨機森林的算法過程,簡單來說就是兩個隨機性。具體如下:

  • 假設有M個樣本,有放回的隨機選擇M個樣本(每次隨機選擇一個放回後繼續選)。

  • 假設樣本有N個特徵,在決策時的每個節點需要分裂時,隨機地從這N個特徵中選取n個特徵,滿足n<<N,從這n個特徵中選擇特徵進行節點分裂。

  • 基於抽樣的M個樣本n個特徵按照節點分裂的方式構建決策樹。

  • 按照1~3步構建大量決策樹組成隨機森林,然後將每棵樹的結果進行綜合(分類使用投票法,迴歸可使用均值法)。

    所以,當我們熟悉了Bagging的基本思想和決策樹構建的過程後,隨機森林就很好理解了。

基於NumPy的隨機森林算法實現

定義自助抽樣函數

給定輸入輸出數據集和決策樹棵樹,通過隨機抽樣的方式構造多個抽樣子集。

# 自助抽樣選擇訓練數據子集
def bootstrap_sampling(X, y):
    # 合併數據輸入和標籤
    X_y = np.concatenate([X, y.reshape(-1,1)], axis=1)
    # 打亂數據
    np.random.shuffle(X_y)
    # 樣本量
    n_samples = X.shape[0]
    # 初始化抽樣子集列表
    sampling_subsets = []

    for _ in range(n_estimators):
        # 第一個隨機性,行抽樣
        idx1 = np.random.choice(n_samples, n_samples, replace=True)
        bootstrap_Xy = X_y[idx1, :]
        bootstrap_X = bootstrap_Xy[:, :-1]
        bootstrap_y = bootstrap_Xy[:, -1]
        sampling_subsets.append([bootstrap_X, bootstrap_y])
    return sampling_subsets

這裏以分類樹爲例構造隨機森林。定義一個trees的隨機森林決策樹列表,通過遍歷構造每棵樹的方法來構造隨機森林。

class ClassificationTree(BinaryDecisionTree):
    ### 定義基尼不純度計算過程
    def _calculate_gini_impurity(self, y, y1, y2):
        p = len(y1) / len(y)
        gini = calculate_gini(y)
        gini_impurity = p * calculate_gini(y1) + (1-p) * calculate_gini(y2)
        return gini_impurity
    
    ### 多數投票
    def _majority_vote(self, y):
        most_common = None
        max_count = 0
        for label in np.unique(y):
            # 統計多數
            count = len(y[y == label])
            if count > max_count:
                most_common = label
                max_count = count
        return most_common
    
    # 分類樹擬合
    def fit(self, X, y):
        self.impurity_calculation = self._calculate_gini_impurity
        self._leaf_value_calculation = self._majority_vote
        super(ClassificationTree, self).fit(X, y)

# 樹的棵數
n_estimators = 10
trees = []
# 基於決策樹構建森林
for _ in range(n_estimators):
    tree = ClassificationTree(min_samples_split=2, min_gini_impurity=999,
                              max_depth=3)
    trees.append(tree)

基於trees這個決策樹列表,來定義隨機森林的訓練方法。訓練時每次自助抽樣獲得一個子集並遍歷擬合trees列表中的每一棵樹,最後得到的是包含訓練好的每顆決策樹構成的隨機森林模型。

# 隨機森林訓練
def fit(X, y):
    # 對森林中每棵樹訓練一個雙隨機抽樣子集
    n_features = X.shape[1]
    sub_sets = bootstrap_sampling(X, y)
    # 遍歷擬合每一棵樹
    for i in range(n_estimators):
        sub_X, sub_y = sub_sets[i]
        # 第二個隨機性,列抽樣
        idx2 = np.random.choice(n_features, max_features, replace=True)
        sub_X = sub_X[:, idx2]
        trees[i].fit(sub_X, sub_y)
        trees[i].feature_indices = idx2
        print('The {}th tree is trained done...'.format(i+1))

將上述過程進行封裝,分別定義自助抽樣方法、隨機森林訓練方法和預測方法。完整代碼如下:

class RandomForest():
    def __init__(self, n_estimators=100, min_samples_split=2, min_gain=0,
                 max_depth=float("inf"), max_features=None):
        # 樹的棵樹
        self.n_estimators = n_estimators
        # 樹最小分裂樣本數
        self.min_samples_split = min_samples_split
        # 最小增益
        self.min_gain = min_gain
        # 樹最大深度
        self.max_depth = max_depth
        # 所使用最大特徵數
        self.max_features = max_features

        self.trees = []
        # 基於決策樹構建森林
        for _ in range(self.n_estimators):
            tree = ClassificationTree(min_samples_split=self.min_samples_split, min_impurity=self.min_gain,
                                      max_depth=self.max_depth)
            self.trees.append(tree)
            
    # 自助抽樣
    def bootstrap_sampling(self, X, y):
        X_y = np.concatenate([X, y.reshape(-1,1)], axis=1)
        np.random.shuffle(X_y)
        n_samples = X.shape[0]
        sampling_subsets = []

        for _ in range(self.n_estimators):
            # 第一個隨機性,行抽樣
            idx1 = np.random.choice(n_samples, n_samples, replace=True)
            bootstrap_Xy = X_y[idx1, :]
            bootstrap_X = bootstrap_Xy[:, :-1]
            bootstrap_y = bootstrap_Xy[:, -1]
            sampling_subsets.append([bootstrap_X, bootstrap_y])
        return sampling_subsets
            
    # 隨機森林訓練
    def fit(self, X, y):
        # 對森林中每棵樹訓練一個雙隨機抽樣子集
        sub_sets = self.bootstrap_sampling(X, y)
        n_features = X.shape[1]
        # 設置max_feature
        if self.max_features == None:
            self.max_features = int(np.sqrt(n_features))
        
        for i in range(self.n_estimators):
            # 第二個隨機性,列抽樣
            sub_X, sub_y = sub_sets[i]
            idx2 = np.random.choice(n_features, self.max_features, replace=True)
            sub_X = sub_X[:, idx2]
            self.trees[i].fit(sub_X, sub_y)
            # 保存每次列抽樣的列索引,方便預測時每棵樹調用
            self.trees[i].feature_indices = idx2
            print('The {}th tree is trained done...'.format(i+1))
    
    # 隨機森林預測
    def predict(self, X):
        # 初始化預測結果列表
        y_preds = []
        # 遍歷預測
        for i in range(self.n_estimators):
            idx = self.trees[i].feature_indices
            sub_X = X[:, idx]
            y_pred = self.trees[i].predict(sub_X)
            y_preds.append(y_pred)
        # 對分類結果進行集成    
        y_preds = np.array(y_preds).T
        res = []
        # 取多數類爲預測類
        for j in y_preds:
            res.append(np.bincount(j.astype('int')).argmax())
        return res

數據測試

import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
# 生成模擬二分類數據集
X, y = make_classification(n_samples=1000, n_features=20, n_redundant=0, n_informative=2,
                           random_state=1, n_clusters_per_class=1)
rng = np.random.RandomState(2)
X += 2 * rng.uniform(size=X.shape)
# 劃分數據集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
# 創建隨機森林模型實例
rf = RandomForest(n_estimators=10, max_features=15)
# 模型訓練
rf.fit(X_train, y_train)
# 模型預測
y_pred = rf.predict(X_test)
print(accuracy_score(y_test, y_pred))

基於sklearn的隨機森林算法實現

from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(max_depth=3, random_state=0)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print(accuracy_score(y_test, y_pred))
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章