首先强调一下,这篇文章适合有很好的基础的人

 

梯度下降

这里不系统讲,只介绍相关的点,便于理解后文

 

先放一个很早以前写的 梯度下降 实现 logistic regression 的代码

def tiduxiajiang():
    """梯度下降算法"""
    alpha=0.000001       # 学习率的确定方法:看权值 w 的数量级,如该脚本中每次迭代w在 10的-5次方左右;线性回归脚本里w在0.07左右
    num=800               # 学习率对算法影响很大,先确定学习率,在确定迭代次数比较好

    w=np.zeros(x.shape[1])  # 初始权值
    sigmoid=lambda x,w:1/(1+np.exp(-np.dot(x,w.T)))   # sigmoid函数

    shoulian=[]
    for i in range(1,num+1):
        lose_function= (-np.dot(y,np.log10(sigmoid(x,w)))-np.dot(np.ones(x.shape[0])-y,np.log10(np.ones(x.shape[0])-sigmoid(x,w))))/x.shape[0] # 损失函数
        # 损失函数取负值, 是因为 本来是求最大值,取负之后是求最小,才能有梯度下降算法,否则要梯度上升
        # 损失函数/样本个数 是必须的 ,而且 这样求导后  grad 才会 也除以 样本个数
        shoulian.append(lose_function)

        grad=np.dot(1/x.shape[0]*x.T,np.dot(x,w.T)-y)
        w=w-np.dot(alpha,grad)
        print(w)
    return w,shoulian

 

我们只需要看一句  w=w-alpha*grad  【学习率x梯度】

做如下理解

 

我们随机选了一个 w0,即初始化参数,w 是 loss function 中的自变量;

然后我们有了一个 loss0;然后我们沿着 loss 在 w0 处的 梯度grad 下降一个 学习率;

w0 走到了 w1,对应 loss 有个 loss1,loss1 要比 loss0 更小,我们减少的损失就是 loss0-loss1,也就是说我们逼近了 loss0-loss1 的距离;

如此往复,即可走到最低点,即 loss 最小值

 

加法模型 

我之前讲过,先看我的博客 加法模型与前向分步算法

 

 重点

是不是有点像梯度下降,对比一下

梯度下降是学习了一个 梯度,从而降低 loss,得到一个 新的 参数; 

加法模型是学习了一个 模型,从而降低 loss,得到一个新的 模型;

所以我们可以理解为 模型 是函数空间的 参数

模型的方向就是 梯度的方向,也就是说 训练一个模型 是在 损失函数的负梯度上 学习了 一个参数,好好体会这句话;在加法模型中,训练一个模型 代表的是什么

 

据说加法模型是在 boosting 之后提出来解释 booting 的,所以 boosting 设计的初衷并没有加法模型,可能只是某些猜想

 

提升树

我之前讲过,先看我的博客 提升树

提升树是以 回归树或者分类树 为基学习器的,他的思路基本跟 加法模型一样

 

注意每次训练决策树时 数据集 是不同的,确切地说是 残差 是不同的 

 

分类提升树

回归很容易理解,那么分类问题呢?分类没有残差啊,貌似搞不清楚

其实分类问题的损失函数是指数函数

那如何使 loss 变小呢?貌似也不是很直观,这也是 提升树 的 缺点

 

在实际工作中,一般不会用到这个模型,甚至 python 都没有自带这个模型,

故这只是个基础模型,了解即可,由于它的缺点从而衍生了梯度提升树,而且在 梯度提升树中讲清楚了 分类问题 如何解决

 

梯度提升树 GDBT

我之前讲过,先看我的博客 回归梯度提升树 、分类与多分类梯度提升树

 

对提升树的改进

梯度提升树最大的改进就是用 负梯度替代了残差;

具体的说是 损失函数的负梯度在当前模型的值替代了残差

 

为什么负梯度可以替代残差?

我们从几个角度解释一下

1)如果损失函数是 mes

可以看到这种情况下,残差就是负梯度;所以 mes 是 负梯度代替残差的 特例

2)残差代表模型学习的目标,负梯度 = 负梯度x1 也是我们学习的目标,【1 我们可理解为学习率】

而且负梯度是 更直接的学习目标

3)从梯度的角度

之前我们讲到 训练一个模型 其实是在 loss 的负梯度上 学习一个参数,真的是啊

4) 损失函数 的一阶泰勒展开

个人比较喜欢这个解释;

这个解释也是 GDBT 和 XGBoost 的关键差异点,XGBoost 是泰勒二阶展开;

 

确切地说,负梯度不是代替了残差,负梯度是为了把损失函数扩展到其他函数 

 

GDBT 算法描述

根据 负梯度 算出所谓的“残差”之后,就可以拟合出一个模型 f(x);

但是我们的 加法模型中是  Fm=Fm-1+βfm(x);

那 β 是多少?我们需要根据 最小化损失函数求得,并且把 βfm(x) 作为 决策树的输出

这个问题在 李航的 《统计学习方法》 中并没说的很明白,所以这里单独强调下

 

GDBT 的算法逻辑对于回归问题比较容易理解,也常用来解决回归问题,但是也能解决分类问题

 

GDBT 分类

这个问题其实我之前也讲过,不过今天讲到这里,我觉得可以稍微深入的讲一讲

 

首先,GDBT 是根据“残差”来拟合决策树,那么分类问题的 label 无法计算残差,解决这个问题的思路有以下几种:

1. 把类别标签转换成 概率,1 代表正例,概率为 1,-1或者0 代表负例,概率为 0,这样 基学习器可以选择回归树,把问题彻底转换成 回归问题

2. 还是上面的思路,只是基学习器还是分类树,只是输出为 概率值

3. 比较官方的作法,构造损失函数,使得负梯度变成连续值

  // 损失函数有两种选择,一种是指数损失,一种是对数似然损失

  // 如果是指数损失,退化为 Adaboost

 

对于 GDBT 二分类,采用类似于逻辑回归的对数似然损失

 其中 y 为 {-1, 1}

其负梯度为

r 为连续值;

有了负梯度,就可以拟合决策树,注意这里拟合的仍然是回归树

 

然后根据 损失函数 线性搜索最佳的 β

c = βft(x)

 

由于上式比较复杂,常用 近似值代替

 

然后迭代 Ft(x);

然后重新计算 负梯度,迭代

 

在 SKlearn 中,就是采用这样方式,所以基学习器是个回归树

 

GDBT 也可以用于多分类,由于本篇幅会很大,这里不再赘述

 

xgboost

我之前讲过,先看我的博客 xgboost

 

xgboost 中对决策树进行了线性表达

f(x)=W[q(x)];W 为每个叶节点的值,[W1,W2,W3...],q 函数把 x 映射到某个叶节点,q(x) 为某个叶节点;

我们用 Ω(f(x)) 表示对这棵决策树的剪枝或者说正则 ;

 

首先我对网上的推导进行补充

 

 

下面的公式网上很多

 

这一系列变换最重要的是把 样本映射到每棵树的叶节点,从而把 nΣ 转换成了 TΣ

 

 

有了损失函数,要使得 loss 最小,一般是求导

 

我主要想说的

以上一系列公式貌似不难理解, 但是这一系列公式到底在干嘛?或者说想干嘛?

 

之前我们用 f(x) 表示决策树,然后推导了上面的一系列公式,最终得到了 min loss,也就是说我们如何得到 min loss,或者说 f(x) 是什么样时 loss 最小;

对了,上面一系列公式就是得到建树依据,类似于 信息增益、基尼系数 等指标;

其中的 G H 都是根据上一棵树得到

 

有了建树原则,xgboost 就很好理解了,挨个建树就行了

 

adaboost

我之前讲过,先看我的博客 adaboostadaboost 进阶

我觉得之前已经讲得很好了,不再赘述

 

这里只说明一个问题:adaboost 每次都训练一棵决策树,样本相同,特征相同,数据完全一致,那每次训练出来的决策树都一样,这能行吗?

这个问题你可能没思考过,数据确实是完全一致的,只是在评价分裂增益时,加入了样本权重,每轮权重都不同,所以同样的数据,得到的最佳分裂方式不同,所以得到的决策树是不同的

 

GDBT vs 提升树

提升树用残差来拟合决策树;

GDBT 用负梯度来拟合决策树;相当于提升树的泛化;

 

GDBT vs xgboost

1. 基学习器不同

GDBT 以 cart 树为基学习器;

xgboost 不仅可以是 cart 树,还可以是线性分类器,此时 xgboost 相当于 L1 L2 正则化的 logistic 回归 或者线性回归

2. 损失函数

GDBT 用 负梯度 代替残差,相当于 loss 的一阶泰勒展开;

xgboost 是 loss 的二阶泰勒展开,同时用到了 一阶导和二阶导,它支持自定义损失函数,只要一阶导和二阶导存在即可

3. 正则化

xgboost 对每棵决策树进行了正则化,而 GDBT 没有

4. learning rate:xgboost 带有 学习率,而 GDBT 可以没有

5. 并行化处理:在训练之前,预先对每个特征内部进行了排序找出候选切割点,然后保存为block结构,后面的迭代中重复地使用这个结构,大大减小计算量。在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特

征的增益计算就可以开多线程进行,即在不同的特征属性上采用多线程并行方式寻找最佳分割点。

6. 列抽样(column subsampling):xgboost借鉴了随机森林的做法,支持列抽样(即每次的输入特征不是全部特征),不仅能降低过拟合,还能减少计算,这也是xgboost异于传统gbdt的一个特性。

7. 忽略缺失值:在寻找splitpoint的时候,不会对该特征为missing的样本进行遍历统计,只对该列特征值为non-missing的样本上对应的特征值进行遍历,通过这个工程技巧来减少了为稀疏离散特征寻找splitpoint的时间开销

8. 指定缺失值的分隔方向:可以为缺失值或者指定的值指定分支的默认方向,为了保证完备性,会分别处理将missing该特征值的样本分配到左叶子结点和右叶子结点的两种情形,分到那个子节点带来的增益大,默认的方向就是哪个子节点,这能大大提升算法

的效率。

 

正则化

下面介绍几种正则化方法,仅介绍思路,每个方法可以适用于多个 boosting 模型

 

验证集 和 Early stopping

boosting 的主要参数是 叶节点个数 和 树的棵树;我们采用类似神经网络的 early stopping 策略

这种方式应该适合所有模型

 

early stopping

思想:一种迭代次数截断的方式来防止过拟合;也就是说在迭代未完成之前停止迭代

具体做法:

1. 划分训练集和验证集

2. 在训练集上进行训练,并在验证集上测试结果,一般是迭代 n 次,再进行测试

3. 如果发现验证集上效果变差,停止迭代,并将停止前的参数作为最终参数

 

好嘛,这不就是之前 深度强化学习 训练神经网络中的 fix target 吗,方法都是通用的

 

learning rate

这种方法有个官方的名字 Shrinkage,其实就是给 每步加上一个学习率

这种方法应该适合所有 boosting 模型

v 代表学习率,v 越小,树的棵树就越多

 

子采样

选择一部分样本来训练决策树,注意是无放回的,

样本数不要太少,也不要太多,一般选择 0.5-0.8 之间

这种方法应该适合所有 boosting 模型 

 

弱学习器的剪枝

很好理解,这种方法应该适合所有 boosting 模型 

 

 

 

参考资料:

 https://www.zhihu.com/question/63560633

https://zhuanlan.zhihu.com/p/59751960

https://www.cnblogs.com/gogoSandy/p/9315785.html

https://zhuanlan.zhihu.com/p/40096769