[ML] 機器學習技法:第十講 Random Forest

ML:基礎技法學習
Package:scikit-learn
課程:機器學習技法
簡介:第十講 Random Forest

Random Forest Algorithm

RF = bagging + random-combination C&RT — randomness everywhere
  • function RandomForest(D)
    • for t=1,2,,T
      1. 利用 bootstrapping 從 D 取出 size-N' 的 D~t
      2. gt= DTree(D~t)
        • function DTree(D~t)
          • if(滿足終止條件)
            • 回傳 gt
          • else
            • 學習 b(Φ(x)) 並用此切割 DDc
              •  Φ(x)=Px
                • P 為低維度投影矩陣
                • row i of P 是隨機挑選 feature 組合
            • Gc=DTree(Dc)
            • 回傳 G(x)=c=1C[b(x)=c]Gc(x)
    • 回傳 G(x)=Uniform({gt})
Bagging 可以降低變異量,Decision Tree 對於不同的資料又非常敏感
特別是 fully-grown Tree 變異量相當大
那為何不把兩者合在一起呢?
於是 random forest (RF) = bagging + fully-grown C&RT decision tree
理論上越多樣化,bagging 越好
所以改為只從 xd 個 features,且 dd 運算更有效率
在計算每個 b(x) 時,都將之改為 b(Φ(x))
當 sampling index i1,i2,,id,則 Φ(x)=(xi1,xi2,,xid)
這樣不僅可以增加多樣性,還可提升計算效率
換個方向來看,這不就是 Φ(x)=Px,只是 P 的每一個 row i pi 是 natural basis
但這樣只能切水平或垂直刀,若將 pi 改成 d 個 features 的線性組合 piTx
連斜刀也能切出來了,就更加多樣性
事實上這跟 perceptron 類似,w 移除 (w0) 就是 pi
而大於 threshold(w0) 是一邊,小於 threshold(w0) 是另一邊
也就是 decision stump 選個點切開分類,依此分類
優勢
  • 容易平行化(bagging)
  • 繼承 Decision Tree 的優點
    • 容易解釋
    • multiclass 實現簡單
    • categorical features 應用簡單
    • missing features 處理簡單
    • non-linear training (and testing) 有效率
  • 降低 fully-grown tree 的缺點
    • 容易 overfit

Out-Of-Bag Estimate (OOB)

Gm=RFm(D)m=argmin1mM EmEm=Eoob(RFm(D))Eoob for self-validation—像是決定 d (多少個 features 做線性組合)
且無需再用全部的資料重新訓練一次,得到最後的結果
從下圖,可看到不同的 gt,因為 bootstrapping 的關係,所以有些 data 並不會被挑到
那麼這些 data 是否可以拿來做為 validation 用呢?
對一個 gt而言,一個 data 完全不會被選中的機率是 (11N)N
假設 N=N,且 N 很大時
(11N)N=(N1N)N=(1NN1)N=(1N1+1N1)N=(11+1N1)N=1(1+1N1)N[ex=limn(1+xn)n]1e 那麼不會被選中資料數約為 1eN,約是三分之一,甚至比做 validation 的五分之一還多
拿這些資料對 gt 做 validate,當然可以,但很少這麼做,因為需要的是對 G 做 validate
那如果將擁有同樣未選中資料的 gt 做 bagging 再做 validate 呢?
像是圖中的 (xN,yN),其 GN(x)=avg(g2,g3,gt)
再將所有 Gn(x)的錯誤做平均,感覺就是 Leave One Out,因此定義
Eoob(G)=1Nn=1Nerr(yn,Gn(xn)) 在實際應用,Eoob 通常也是很準確的
而且不用在跟之前一樣,特別割出 Dval 做驗證,然後最後還得對所有資料重新訓練一次,如左圖

Feature Selection

  • 優點
    • 令後續的演算法更有效率 
    • 移除不必要的 features,排除雜訊
    • 剩下的 features 也許可解釋
  • 缺點 
    • 計算量過多,從 NN 個,共有 CNN 個組合
    • 假如沒做好的話,會選到看似很好其實並不然的組合
      • 容易 overfit
      • 無法解釋 features 的關聯性
Linear Model wTx
importance(i)=|wi|
組合太多,計算量太過龐大,若如何改為只看單一項的重要性呢? importance(i) for i=1,2,,d
並依此選出排名較靠前的 features
而事實上 linear model 的 w 不就具有此特性,將重要的放大,不重要的縮小
importance(i)=|wi|
RF,實務上相當常用
importance(i)=Eoob(G)Eoob(p)(G) Eoob(p)(G) 對於 xn,i 的 permutation 來自 OOB 資料
對於 non-linear model 無法使用權重值,因為 features 是交雜在一起的,無法輕易分析
所以換個角度來看
若這個 feature 很重要,那麼加入一點 noise 應該會造成整體的表現變差
那要加入什麼樣的 noise?
  • uniform, Gaussian, ...
    • 會造成原有的分佈特性跑掉,也就是 P(xi) 被改變
  • bootstrap, permutation(重新洗牌順序)
    • 維持原本的分佈特性
在此使用的是 permutation test
importance(i)=performance(D)performance(D(p))
D(p) 來自原有的 D 對於 {xn,i} 做洗牌,也就是資料不變,但位置被改變了
此方法在統計學上常用,適用任意的 non-linear models
記住,此處的 performance,指的是 validation 的結果
因此在 RF 中,importance(i)=Eoob(G)Eoob(G(p))
但若是如此,還是得針對 D(p) 重新訓練
若改為這樣呢?importance(i)=Eoob(G)Eoob(p)(G)
G 不變,只針對 OOB 做 permutation
這樣的話,就不用再重新訓練,可直接跑 Eoob 的流程

實際例子為,若資料中的第 i 項,無論是哪一個 data 皆是常數,那麼 importance(i)=0
因為無論怎麼做 permutation 皆是一樣的值,自然重要性為 0
或換個角度來看,一個常數值,但對應的 y 都不一樣,不就表示此項無任何貢獻

實際例子

左圖為只替 C&RT 加入 features 的線性組合,也就是 random combination
右圖為 RF,但其 N=N2,也就是 bootstrapping 只抽出一半的資料量
可看到 RF 更加的 smooth 與 large margin
左圖為 t=21 的 tree
右圖為 G,可看到 noise 的影響已被去除掉大部分

注意事項

因為 RF 本質是隨機的演算法,需要足夠的 trees,才能達到穩定
如何決定何謂足夠的 trees,看一下 G 的穩定性
像是 Eoob 每次的結果,增減 tree 是否會有變化

程式碼

使用 b(x) 時的 features 不一定越少越好
  1. import matplotlib.pyplot as plt
  2.  
  3. from collections import OrderedDict
  4. from sklearn.datasets import make_classification
  5. from sklearn.ensemble import RandomForestClassifier
  6.  
  7. RANDOM_STATE = 123
  8.  
  9. # 建立 500 筆資料,有 25 個 features,15 個重要 features,10 個雜訊 features
  10. # 每個類別的資料個別分為兩大群,也就是分佈在兩個不同的位置
  11. X, y = make_classification(n_samples=500, n_features=25,
  12. n_clusters_per_class=2, n_informative=15,
  13. random_state=RANDOM_STATE)
  14.  
  15. # 設定 max_features,每次做 b(x) 所看得 feature 數量
  16. # 設定 warm_start=True,可重覆利用先前做過的 solution 加快速度
  17. ensemble_clfs = [
  18. ("RandomForestClassifier, max_features='sqrt'",
  19. RandomForestClassifier(warm_start=True, oob_score=True,
  20. max_features="sqrt",
  21. random_state=RANDOM_STATE)),
  22. ("RandomForestClassifier, max_features='log2'",
  23. RandomForestClassifier(warm_start=True, max_features='log2',
  24. oob_score=True,
  25. random_state=RANDOM_STATE)),
  26. ("RandomForestClassifier, max_features=None",
  27. RandomForestClassifier(warm_start=True, max_features=None,
  28. oob_score=True,
  29. random_state=RANDOM_STATE))
  30. ]
  31.  
  32. # 建立一有序 dictionary for 記錄使用
  33. error_rate = OrderedDict((label, []) for label, _ in ensemble_clfs)
  34.  
  35. # 設定 tree 的上下限數量
  36. min_estimators = 15
  37. max_estimators = 300
  38.  
  39. for label, clf in ensemble_clfs:
  40. for i in range(min_estimators, max_estimators + 1, 2):
  41. clf.set_params(n_estimators=i)
  42. clf.fit(X, y)
  43.  
  44. # 記錄大小為 i 的 RF 的 oob error
  45. oob_error = 1 - clf.oob_score_
  46. error_rate[label].append((i, oob_error))
  47.  
  48. # Generate the "OOB error rate" vs. "n_estimators" plot.
  49. for label, clf_err in error_rate.items():
  50. xs, ys = zip(*clf_err)
  51. plt.plot(xs, ys, label=label)
  52.  
  53. plt.xlim(min_estimators, max_estimators)
  54. plt.xlabel("n_estimators (n trees)")
  55. plt.ylabel("OOB error rate")
  56. plt.legend(loc="upper right")
  57. plt.show()
feature selection 挑出的 features 可能比實際的多
  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3.  
  4. from sklearn.datasets import make_classification
  5. from sklearn.ensemble import RandomForestClassifier
  6.  
  7. RANDOM_STATE = 123
  8. IMPORTANT_FEATURE = 3
  9.  
  10. # 建立 1000 筆資料,有 25 個 features,3 個重要 features,22 個雜訊 features
  11. # 每個類別的資料個別分為兩大群,也就是分佈在兩個不同的位置
  12. X, y = make_classification(n_samples=1000, n_features=25,
  13. n_clusters_per_class=2, n_informative=IMPORTANT_FEATURE,
  14. random_state=RANDOM_STATE)
  15.  
  16. # 建立一個大小為 250 顆 tree 的 RF
  17. forest = RandomForestClassifier(n_estimators=250,
  18. random_state=RANDOM_STATE)
  19.  
  20. forest.fit(X, y)
  21. importances = forest.feature_importances_
  22. # 計算所有 tree 的 feature 標準差
  23. std = np.std([tree.feature_importances_ for tree in forest.estimators_], axis=0)
  24. # 由小到大排序並回傳對應的 index,再將之反向,也就是改為由大到小的排序
  25. indices = np.argsort(importances)[::-1]
  26.  
  27. # 印出排序後的 feature 重要性
  28. print("Feature ranking:")
  29. for f in range(X.shape[1]):
  30. print("%d. feature %d (%f)" % (f + 1, indices[f], importances[indices[f]]))
  31.  
  32. # 畫圖
  33. plt.figure()
  34. plt.title("{} Feature importances in Real".format(IMPORTANT_FEATURE))
  35. plt.bar(range(X.shape[1]), importances[indices],
  36. color="r", yerr=std[indices], align="center")
  37. plt.xticks(range(X.shape[1]), indices)
  38. plt.xlim([-1, X.shape[1]])
  39. plt.show()

參考

sklearn.ensemble.RandomForestClassifier
sklearn.ensemble.RandomForestRegressor
Feature importances with forests of trees

留言