從0開始實現(xiàn)一個Adaboost分類器(完整代碼)
導讀
日前,通俗易懂的推導了三種集成學習的原理及主要公式,今天本文基于Python從0開始手動實現(xiàn)一個Adaboost分類器,文中提供完整代碼。

弱學習器權(quán)重,影響每個弱學習器的結(jié)果對最終集成學習結(jié)果的影響程度,與該學習器的錯誤率有關(guān)
樣本權(quán)重,這也是Adaboost算法的精髓所在,即每輪訓練弱學習器時不斷優(yōu)化調(diào)整樣本間的權(quán)重,保證前一輪中學習錯誤的樣本在下一輪訓練中受到重點照顧
弱學習器的權(quán)重為:
為學習器錯誤率樣本權(quán)重更新迭代公式為:
具體含義及推導過程詳見:三種集成學習算法原理及核心公式推導
值得指出,在sklearn庫內(nèi)置的Adaboost算法中,當解決分類問題時弱學習器選擇最大深度為1的決策樹(俗稱決策樹樁),解決回歸問題時則選擇最大深度為3的決策樹(CART)。
本文以分類問題為例實現(xiàn)Adaboost算法,所以首先探索實現(xiàn)一個最大深度只有一層的決策樹樁。
簡單起見,假設(shè)樣本為連續(xù)數(shù)值型特征,要實現(xiàn)一個最大深度只有一層決策樹樁,那么實際上無論有多少個特征,也僅會用到其中一個特征作為分類。則問題等價于確定以下三個參數(shù):
確定選擇哪一列特征作為分類依據(jù)
選擇的特征列中,以什么數(shù)值作為二分類的閾值
特征與閾值的判別符號問題,即大于閾值還是小于閾值判斷為正類
由于是分類問題,那么選擇最優(yōu)參數(shù)的依據(jù)不妨可以選擇為Accuracy。當然,由于該決策樹樁需要支持樣本權(quán)重參數(shù),所以這里的Accuracy嚴謹?shù)恼f是指所有分類正確的樣本權(quán)重之和占所有樣本權(quán)重之和的比例,當執(zhí)行樣本權(quán)重歸一化時所有樣本權(quán)重之和為1。
基于此,一個簡單的決策樹樁實現(xiàn)思路就比較清晰了,實現(xiàn)3重循環(huán)依次遍歷尋找最有參數(shù)組合即可。另外,沿襲sklearn標準庫中的做法,這里僅實現(xiàn)fit()、predict()和score()三個核心接口。
詳細代碼如下,配合注解應該比較簡單易懂:
class?DecisionTreeClassifierWithWeight:
????def?__init__(self):
????????self.best_err?=?1??#?最小的加權(quán)錯誤率
????????self.best_fea_id?=?0??#?最優(yōu)特征id
????????self.best_thres?=?0??#?選定特征的最優(yōu)閾值
????????self.best_op?=?1??#?閾值符號,其中?1:?>,?0:?<
????def?fit(self,?X,?y,?sample_weight=None):
????????if?sample_weight?is?None:
????????????sample_weight?=?np.ones(len(X))?/?len(X)
????????n?=?X.shape[1]
????????for?i?in?range(n):
????????????feature?=?X[:,?i]??#?選定特征列
????????????fea_unique?=?np.sort(np.unique(feature))??#?將所有特征值從小到大排序
????????????for?j?in?range(len(fea_unique)-1):
????????????????thres?=?(fea_unique[j]?+?fea_unique[j+1])?/?2??#?逐一設(shè)定可能閾值
????????????????for?op?in?(0,?1):
????????????????????y_?=?2*(feature?>=?thres)-1?if?op==1?else?2*(feature?-1??#?判斷何種符號為最優(yōu)
????????????????????err?=?np.sum((y_?!=?y)*sample_weight)
????????????????????if?err?#?當前參數(shù)組合可以獲得更低錯誤率,更新最優(yōu)參數(shù)
????????????????????????self.best_err?=?err
????????????????????????self.best_op?=?op
????????????????????????self.best_fea_id?=?i
????????????????????????self.best_thres?=?thres
????????return?self
????
????def?predict(self,?X):
????????feature?=?X[:,?self.best_fea_id]
????????return?2*(feature?>=?self.best_thres)-1?if?self.best_op==1?else?2*(feature?-1
????
????def?score(self,?X,?y,?sample_weight=None):
????????y_pre?=?self.predict(X)
????????if?sample_weight?is?not?None:
????????????return?np.sum((y_pre?==?y)*sample_weight)
????????return?np.mean(y_pre?==?y)
這里以sklearn庫中自帶的乳腺癌二分類數(shù)據(jù)集為例,以上述實現(xiàn)的決策樹樁進行訓練和評分,得到最終得分0.867,這對于一個僅有單層決策樹的分類器來說效果還是比較好的。
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
X, y = load_breast_cancer(return_X_y=True)
y = 2*y-1 # 將0/1取值映射為-1/1取值
X_train, X_test, y_train, y_test = train_test_split(X, y)
DecisionTreeClassifierWithWeight().fit(X_train, y_train).score(X_test, y_test)
# 0.8671328671328671
注:按照Adaboost中的算法約定,二分類模型中標簽分別用-1和1代表負類和正類。
在實現(xiàn)決策樹樁作為弱分類器的基礎(chǔ)上,實現(xiàn)Adaboost算法就僅需按照算法流程逐層訓練即可。簡單起見,這里僅設(shè)置超參數(shù)n_estimators用于選擇弱分類器的個數(shù)。為區(qū)分于sklearn中的Adaboost標準內(nèi)置庫,本文將自定義實現(xiàn)的Adaboost分類算法命名為AdaBoostClassifier_,并設(shè)置相同的默認弱學習器數(shù)量超參數(shù)n_estimators=50,其余不做限制。
實質(zhì)上,在逐漸調(diào)整樣本權(quán)重的基礎(chǔ)上,僅需逐層訓練一個最優(yōu)的決策樹樁作為每輪的弱學習器,并保存在一個弱學習器列表中,同步記錄每個弱學習器的權(quán)重系數(shù)。最后,在實現(xiàn)predict接口時,用每個弱學習器逐一完成訓練,而后按其權(quán)重系數(shù)加權(quán)即可得到最終結(jié)果。完整代碼如下:
class AdaBoostClassifier_:
def __init__(self, n_estimators=50):
self.n_estimators = n_estimators
self.estimators = []
self.alphas = []
def fit(self, X, y):
sample_weight = np.ones(len(X)) / len(X) # 初始化樣本權(quán)重為 1/N
for _ in range(self.n_estimators):
dtc = DecisionTreeClassifierWithWeight().fit(X, y, sample_weight) # 訓練弱學習器
alpha = 1/2 * np.log((1-dtc.best_err)/dtc.best_err) # 權(quán)重系數(shù)
y_pred = dtc.predict(X)
sample_weight *= np.exp(-alpha*y_pred*y) # 更新迭代樣本權(quán)重
sample_weight /= np.sum(sample_weight) # 樣本權(quán)重歸一化
self.estimators.append(dtc)
self.alphas.append(alpha)
return self
def predict(self, X):
y_pred = np.empty((len(X), self.n_estimators)) # 預測結(jié)果二維數(shù)組,其中每一列代表一個弱學習器的預測結(jié)果
for i in range(self.n_estimators):
y_pred[:, i] = self.estimators[i].predict(X)
y_pred = y_pred * np.array(self.alphas) # 將預測結(jié)果與訓練權(quán)重乘積作為集成預測結(jié)果
return 2*(np.sum(y_pred, axis=1)>0)-1 # 以0為閾值,判斷并映射為-1和1
def score(self, X, y):
y_pred = self.predict(X)
return np.mean(y_pred==y)
最后,繼續(xù)以乳腺癌二分類數(shù)據(jù)集為例,對比測試自定義實現(xiàn)的AdaBoostClassifier_算法與sklearn標準庫中的AdaBoostClassifer算法性能,得到如下結(jié)果:
from sklearn.ensemble import AdaBoostClassifier
AdaBoostClassifier_().fit(X_train, y_train).score(X_test, y_test)
# 0.986013986013986
AdaBoostClassifier().fit(X_train, y_train).score(X_test, y_test)
# 0.965034965034965
除了訓練效率略低,自定義實現(xiàn)Adaboost算法效果簡直好的不得了。
本文按部就班的實現(xiàn)了一個Adaboost分類算法的baseline,實現(xiàn)了較好的分類效果,但仍有很多需要優(yōu)化的點,例如對回歸算法的支持、更多集成學習參數(shù)的設(shè)置以及特殊訓練情況下的處理等。To be continued……

相關(guān)閱讀:
