机器学习_Adaboost(重温&手推)

笔者这次是温习和回顾集成算法,所以又自己手推实现了一遍Adaboost。之前有两篇关于Adaboost的文章:

本次的不同之处有两点:

  • 温习而非初学
  • sklearn接口方式推导

一、集成学习及AdaBoost的优缺点

1.1 关于集成学习

在《多样性团队》一书提及了一个研究领域——感知盲点。每个人都通过某种参照框架来感知和理解世界,但我们看不到自己的参照架构本身。感知盲点的存在说明:我们往往意识不到自己能从其他人身上学到不少东西。这也解释了人口多样性(人种、性别、年龄、阶层等方面的差异)在某些情况下能够增加群体智慧
对于算法模型而言也是同样的,一个模型所感知的样本间的区别是有限的,所以我们需要增加模型“团队“的多样性,让不同模型对于相同样本给出不同决策并融合(加权平均,stacking),同样的我们也可以多个专家团队从不同方面进行决策再汇总(Bagging),也可以在一个专家的判断基础上再进行多次判断(Boosting)。上面提及的是,多样性在某些情况下能够增加群体智慧,即算法团队的多样性和准确性的结合具有一定的难度,所以我们需要一个较为合理的损失函数来让算法收敛。
本文只要关注的Boosting,是采用的指数损失作为收敛目标。
l o s s = e − y t r u e ∗ y p r e d loss = e^{-y_{true} * y_{pred}} loss=eytrueypred
[注:]

  • 参照西瓜书

1.2 AdaBoost优缺点

优点:泛化错误率低,易于编码,可以应用在大部分分类器上, 无参数调整
缺点:对离群点敏感

二、Adaboost框架手推

2.1 基模型拟合

基于样本权重训练模型

estimator.fit(X, y, sample_weight=sample_weight.flatten())

样本权重作用于基模型

样本的权重如何在模型中起作用,这个其实涉及到损失函数的优化,针对不同情况改变损失函数构成部分的权重
针对平方误差损失函数,我们增加模型的泛化能力可以增加一个正则项(限制权重不至于过大)
m s e = ∑ i = 1 n ( y i t r u e − w i ∗ x i ) 2 + 1 2 ∑ j = 1 m ( w i ) 2 mse=\sum_{i=1}^{n}(y_i^{true} - w_i * x_i)^2 + \frac{1}{2}\sum_{j=1}^{m}(w_i)^2 mse=i=1n(yitruewixi)2+21j=1m(wi)2

针对交叉熵,有Focal loss的算法(感兴趣同学可以自己找下论文看下),调整正负样本的损失权重( α \alpha α ),增加难分类样本的损失( ( 1 − p ) γ (1-p)^\gamma (1p)γ )
f l = − y α ( 1 − p ) γ l o g ( p ) − ( 1 − y ) ( 1 − α ) p γ l o g ( 1 − p ) fl = - y \alpha (1-p)^\gamma log(p) - (1-y)(1-\alpha )p^\gamma log(1-p) fl=yα(1p)γlog(p)(1y)(1α)pγlog(1p)

而我们样本权重其实有点类似于Focal loss中增加难分类样本的损失,我们是直接对不同样本增加权重,而非基于预测结果增加权重。对于交叉熵而言我们的损失可以改为:
l o s s w e i g h t = ∑ i = 1 n w x i ( − y i l o g ( p i ) − ( 1 − y i ) l o g ( 1 − p i ) ) loss_{weight}= \sum_{i=1}^{n} w_{x_i}(-y_ilog(p_i) - (1-y_i)log(1-p_i)) lossweight=i=1nwxi(yilog(pi)(1yi)log(1pi))
然后基于链式求导,梯度下降收敛权重

# 以logistic为例
def fit_on_batch(self, x, y, sample_weight):
    pred = self.predict(x)
    loss_sigmoid_devaration = (-y * 1 / (pred + self.epsilon) +(1-y)* 1 / (1 - pred + self.epsilon)) * sample_weight / sum(sample_weight)
     
    sigmoid_linear_devaration = pred - pred * pred
    
    w_d = x.T.dot(loss_sigmoid_devaration * sigmoid_linear_devaration)
    self.w -= self.learning_rate * w_d

对于决策时的基尼指数(Gini index)而言,权重的更改迭代具有天然优势,对于特征的基尼指数会发生变化,因此对于树的分裂产生了影响。
G i n i ( D v ) = 1 − ∑ k = 1 m p k 2 ;       p k = ∑ i n x w e i g h t ( x ∈ k ) Gini(D_v)=1 - \sum_{k=1}^{m}p^2_k; \ \ \ \ \ p_k=\sum_i^{n} x_{weight} (x \in k) Gini(Dv)=1k=1mpk2;     pk=inxweight(xk)
G i n i _ i n d e x ( D t , F e a t u r e a ) = ∑ v = 1 m D v w e i g h t _ s u m D G i n i ( D v ) Gini\_index(D_t, Feature_a)=\sum_{v=1}^{m}\frac{D_v^{weight\_sum}}{D}Gini(D_v) Gini_index(Dt,Featurea)=v=1mDDvweight_sumGini(Dv)

2.2 模型误差计算&基分类器权重

基分类器的错分情况
基分类器权重: α = 1 2 l n ( 1 − e r r o r e r r o r ) \alpha=\frac{1}{2}ln(\frac{1-error}{error}) α=21ln(error1error) 从表达式中可看出是 增大了准确率高的基分类器权重

def iboost_error(self, y_true, iboost_pred, sample_weight):
    incorrect = y_true != iboost_pred
    # print(f'incorrect.shape: {incorrect.shape}')
    return np.average(incorrect.flatten(), weights=sample_weight.flatten())

def iboost_weight(self, error):
    return 0.5 * np.log( (1-error) / error)

2.3 样本权重刷新

权重的更新依旧是指数函数推导出来的: D t + 1 = D t ∗ e − α t   ∗   y t r u e   ∗   y p r e d D_{t+1}=D_{t}*e^{-\alpha_{t}\ *\ y_{true}\ *\ y_{pred}} Dt+1=Dteαt  ytrue  ypred ,从表达式的含义来看,我们是想增大错误分类的权重,所以在实现的时候可以进行简略, 即不对分类正确的实例进行权重更改,标准化之后分类正确的实例依旧会减少。

D t + 1 = { D t ∗ e α t    = > D t ∗ e α t y t r u e = y p r e d D t ∗ e − α t = > D t y t r u e ≠ y p r e d D_{t+1}= \begin{cases} D_{t}*e^{\alpha_{t}} \ \ => D_{t}*e^{\alpha_{t}}& y_{true}=y_{pred} \\ D_{t}*e^{-\alpha_{t}} => D_{t}& y_{true}\not=y_{pred} \end{cases} Dt+1={Dteαt  =>DteαtDteαt=>Dtytrue=ypredytrue=ypred

def flushed_sample_weight(self, y_true, iboost_pred, sample_weight):
    iboost_error = self.iboost_error(y_true, iboost_pred, sample_weight)
    if iboost_error == 0:
        return sample_weight, 1, 0

    iboost_w = self.iboost_weight(iboost_error)
    # 保证权重为正,且简略权重更新
    sample_weight *= np.exp(iboost_w * (y_true != iboost_pred) * (sample_weight > 0))
    return sample_weight / np.sum(sample_weight), iboost_w, iboost_error

2.4 基于上述手推框架的Adaboost实现

详细脚本可以查看笔者的github:优快云/Adaboost.py

from collections import namedtuple
from sklearn.tree import DecisionTreeClassifier
model_info = namedtuple('model_info', 'model weight error')

# 基分类器想x1拟合->计算误差&计算模型权重-> 更新样本权重 -> 基分类器想x2拟合 .....
def _boost(self, estimator, X, y, sample_weight, print_flag=False):
    try:
        estimator.fit(X, y, sample_weight=sample_weight.flatten(), verbose=200 if print_flag else np.inf)
    except:
        estimator.fit(X, y, sample_weight=sample_weight.flatten())
    y_pred = (estimator.predict(X) > 0.5).reshape((-1, 1)) * 1
    # 更新权重, 计算当前模型的误差, 计算当前模型的权重
    sample_weight, iboost_w, iboost_error = self.flushed_sample_weight(y, y_pred, sample_weight)
    
    # 模型保存
    iboost_model = model_info( model = estimator, weight = iboost_w * self.learning_rate, error = iboost_error)
    self.boost_models.append(iboost_model)
    return iboost_model, sample_weight

三、基于Logistic的Adaboost 和 基于决策时的Adaboost的比对

一般情况下:

  • 基于logistic的Ababoost的训练结果相对于基分类器提升较少
  • 基于决策树的Ababoost的训练结果相对于基分类器提升较大
  • 基于决策树的Ababoost的训练结果优于基于logistic的adaboost

大家可以自己去尝试各种基模型的adaboost。

[test-0] dtree-f1:0.82449; my-lr f1: 0.83465; lr-f1:0.82305;
        my-adaboost-dtree:0.89167; my-adaboost-lr:0.83137; adaboost-sklearn:0.82500


[test-1] dtree-f1:0.74265; my-lr f1: 0.76471; lr-f1:0.77043;
        my-adaboost-dtree:0.84252; my-adaboost-lr:0.76471; adaboost-sklearn:0.79518


[test-2] dtree-f1:0.75090; my-lr f1: 0.81911; lr-f1:0.83154;
        my-adaboost-dtree:0.88727; my-adaboost-lr:0.82712; adaboost-sklearn:0.82090


[test-3] dtree-f1:0.81618; my-lr f1: 0.89062; lr-f1:0.90909;
        my-adaboost-dtree:0.92481; my-adaboost-lr:0.89062; adaboost-sklearn:0.89552

def test(n):
    print('--'*25)
    print('loading data ...')
    X, y = make_classification(n_samples=1000, n_features=13, n_informative=8, n_classes=2)
    x_tr, x_te, y_tr, y_te = train_test_split(X, y, test_size=0.25)
    y_tr = y_tr.flatten()
    y_te = y_te.flatten()
    print('--'*25)
    print('Compare my logistic ...')
    m_lr = BaseLogistic(batch_size=256, epochs=500, epsilon=1e-15, learning_rate=0.1, early_stopping=20)
    m_lr.fit(x_tr, y_tr, np.ones(len(y_tr), dtype=np.float64) / len(y_tr), verbose=200)
    pred_te = m_lr.predict(x_te)
    f1_ = f1_score(y_te, pred_te>0.5)

    lr = LogisticRegression()
    lr.fit(x_tr, y_tr)
    lr_pred = lr.predict(x_te)
    f1_lr = f1_score(y_te, lr_pred>0.5)

    print(f'my-lr f1: {f1_:.3f}; lr-f1:{f1_lr:.3f}')

    print('--'*25)
    print('test adaboost ...')
    tree_ = DecisionTreeClassifier(max_depth=3)
    tree_.fit(x_tr, y_tr)
    tree_pred = tree_.predict(x_te)
    tree_adb = f1_score(y_te, tree_pred>0.5)
	# adaboost-基分类器决策树
    adb = SampleAdaboost(50)
    adb.fit(x_tr, y_tr)
    adb_pred = adb.predict(x_te)
    f1_adb = f1_score(y_te, adb_pred>0.5)
	# adaboost-基分类器logistic
    adb_lr = SampleAdaboost(50, base_model='logistic')
    adb_lr.fit(x_tr, y_tr)
    adb_lr_pred = adb_lr.predict(x_te)
    adb_lr_f1 = f1_score(y_te, adb_lr_pred>0.5)
	# adaboost-基分类器决策树-sklearn
    adb_sk = AdaBoostClassifier()
    adb_sk.fit(x_tr, y_tr)
    adb_sk_pred = adb_sk.predict(x_te)
    adb_sk_f1 = f1_score(y_te, adb_sk_pred>0.5)


    info_out = f'[test-{n}] dtree-f1:{tree_adb:.5f}; my-lr f1: {f1_:.5f}; lr-f1:{f1_lr:.5f}; \n\tmy-adaboost-dtree:{f1_adb:.5f}; my-adaboost-lr:{adb_lr_f1:.5f}; adaboost-sklearn:{adb_sk_f1:.5f}'
    print(info_out)
    print('Done')
    return info_out

欢迎大家指正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Scc_hy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值