在機器學習中,随機森林是一個包含多個決策樹的分類器, 并且其輸出的類别是由個别樹輸出的類别的衆數而定。 發展出推論出随機森林的算法。 而 "Random Forests" 是他們的商标。 這個術語是1995年由貝爾實驗室的Tin Kam Ho所提出的随機決策森林而來的。這個方法則是結合 Breimans 的 "Bootstrap aggregating" 想法和 Ho 的"random subspace method"以建造決策樹的集合。
常見思路:
- 用N來表示訓練用例(樣本)的個數, M表示特征數目 。
- 輸入特征數目m,用于确定決策樹上一個節點的決策結果;其中m應遠小于M。
- 從N個訓練用例(樣本)中以有放回抽樣的方式,取樣N次,形成一個訓練集(即bootstrap取樣), 并用未抽到的用例(樣本)作預測 ,評估其誤差。
- 對于每一個節點,随機選擇m個特征,決策樹上每個節點的決定都是基于這些特征确定的。根據這m個特征,計算其最佳的分裂方式。
- 每棵樹都會完整成長而不會剪枝, 這有可能在建完一棵正常樹狀分類器後會被采用 。
Bagging是并行式內建學習方法最典型的代表架構。其核心概念在于自助采樣(Bootstrap Sampling),給定包含m個樣本的資料集,有放回的随機抽取一個樣本放入采樣集中,經過m次采樣,可得到一個和原始資料集一樣大小的采樣集。我們可以采樣得到T個包含m個樣本的采樣集,然後基于每個采樣集訓練出一個基學習器,最後将這些基學習器進行組合。這便是Bagging的主要思想。Bagging與Boosting圖示如下:
可以清楚的看到,Bagging是并行的架構,而Boosting則是序列架構(但也可以實作并行)。随機森林(Random Forest)就沒有太多難以了解的地方了。所謂随機森林,就是有很多棵決策樹建構起來的森林,因為建構過程中的随機性,故而稱之為随機森林。随機森林算法是Bagging架構的一個典型代表。
使用numpy來手動實作一個随機森林算法。随機森林算法本身是實作思路我們是非常清晰的,但其原始建構需要大量搭建決策樹的工作,比如定義樹節點、建構基本決策樹、在基本決策樹基礎上建構分類樹和回歸樹等。
在此基礎上,随機森林算法的建構主要包括随機選取樣本、随機選取特征、構造森林并拟合其中的每棵樹、基于每棵樹的預測結果給出随機森林的預測結果。
import numpy as np
# 該子產品為自定義子產品,封裝了建構決策樹的基本方法
from ClassificationTree import *
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
# 樹的棵數
n_estimators = 10
# 列抽樣最大特征數
max_features = 15
# 生成模拟二分類資料集
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)
print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)
定義第一個随機性,行抽樣選取樣本
在用模型拟合之前,嘗試主成分分析(PCA)也是常見的做法。但是,為什麼還要增加這一步呢?難道随機森林的目的不是幫助我們更輕松地了解特征重要性嗎?
當我們分析随機森林模型的「特征重要性」時,PCA 會使每個「特征」的解釋變得更加困難。但是 PCA 會進行降維操作,這可以減少随機森林要處理的特征數量,是以 PCA 可能有助于加快随機森林模型的訓練速度。
請注意,計算成本高是随機森林的最大缺點之一(運作模型可能需要很長時間)。尤其是當你使用數百甚至上千個預測特征時,PCA 就變得非常重要。是以,如果隻想簡單地擁有最佳性能的模型,并且可以犧牲解釋特征的重要性,那麼 PCA 可能會很有用。
# 自助抽樣選擇訓練資料子集
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 = []
# 基于決策樹建構森林
for _ in range(n_estimators):
tree = ClassificationTree(min_samples_split=2, min_impurity=0,
max_depth=3)
trees.append(tree)
定義訓練函數,對随機森林中每棵樹進行拟合。
# 随機森林訓練
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))
要了解和使用各種選項,有關如何計算它們的更多資訊是有用的。 大多數選項取決于随機林生成的兩個資料對象。
當通過替換采樣繪制目前樹的訓練集時,大約三分之一的情況被遺漏在樣本之外。 随着樹木被添加到森林中,該oob(out-of-bag)資料用于獲得對分類錯誤的運作無偏估計。 它還用于獲得變量重要性的估計。
在建構每個樹之後,所有資料都在樹下運作,并且為每對案例計算鄰近度。 如果兩個案例占用相同的終端節點,則它們的接近度增加一。 在運作結束時,通過除以樹的數量來标準化鄰近度。 Proximities用于替換缺失資料,定位異常值,以及生成資料的低維視圖。(論文裡的東西就是如此的生澀,難懂)。
我們将上述過程進行封裝,分别定義自助抽樣方法、随機森林訓練方法和預測方法。完整代碼如下:
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
基于上述随機森林算法封裝來對模拟資料集進行訓練并驗證:
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也為我們提供了随機森林算法的api,我們也嘗試一下與numpy手寫的進行效果對比:
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))
優點: - 在目前的很多資料集上,相對其他算法有着很大的優勢,表現良好。
- 它能夠處理很高次元(feature很多)的資料,并且不用做特征選擇(因為特征子集是随機選擇的)。
- 在訓練完後,它能夠給出哪些feature比較重要。
- 訓練速度快,容易做成并行化方法(訓練時樹與樹之間是互相獨立的)。
- 在訓練過程中,能夠檢測到feature間的互相影響。
- 對于不平衡的資料集來說,它可以平衡誤差。
- 如果有很大一部分的特征遺失,仍可以維持準确度。
- 随機森林已經被證明在某些 噪音較大 的分類或回歸問題上會過拟合。
- 對于有不同取值的屬性的資料,取值劃分較多的屬性會對随機森林産生更大的影響,是以随機森林在這種資料上産出的屬性權值是不可信的。