前言
在深度学习火起来之前,提升方法(包括AdaBoost, GBDT, XGBoost)是 kaggle 等比赛中的利器,所以提升方法 (boosting) 是必备的知识点。李航《统计学习方法》第8章——提升方法主要内容:AdaBoost, Boosting Tree, GBDT(这一块原文不够详细,将补充一些)。写本文主要目的是复习(毕竟之前看纸质版做的笔记), 对于证明比较跳跃和勘误的地方我都做了注解,以便初学者快速阅读理解不会卡住,另外本文拓展部分补充了:集成学习。另外李航这本书著作于2012年,陈天奇 的 XGBoost (eXtreme Gradient Boosting) 在2015年惊艳四方,本文暂时不叙述这个,将会另开一篇:XGBoost入门,包括微软的LightGBM,但是本文是 XGBoost 和 LightGBM 的基础,所以要先总结本文。初学者看到前言这么多名词不必畏惧,直接看正文,本人也是从本章正文学习,再拓展出这些的,初学直接看正文,一步一步往下顺。
正文
提升(boosting) 方法是一种常用的统计学习方法, 应用广泛且 有效。 在分类问题中, 它通过改变训练样本的权重, 学习多个分类 器, 并将这些分类器进行线性组合, 提高分类的性能。
本章主要内容
- 提升方法的思路和代表性的提升算法AdaBoost; 通过训练误差分析探讨AdaBoost为什么能够提高学习精度; 并且从 前向分步加法模型的角度解释AdaBoost;
- 然后叙述提升方法更具体的 实例——提升树(boosting tree)和GBDT 。
8.1 提升方法AdaBoost算法
8.1.1 提升方法的基本思路
提升方法的思想
对于一个复杂任务来说, 将多个专 家的判断进行适当的综合所得出的判断, 要比其中任何一个专家单独 的判断好。 实际上, 就是“三个臭皮匠顶个诸葛亮”的道理
历史背景
历史上, Kearns和Valiant首先提出了“强可学习(strongly learnable) ”和“弱可学习(weakly learnable) ”的概念。 指出: 在概率近似正确(probably approximately correct, PAC) 学习的框架中, 一 个概念(一个类) , 如果存在一个多项式的学习算法能够学习它, 并 且正确率很高, 那么就称这个概念是强可学习的; 一个概念, 如果存 在一个多项式的学习算法能够学习它, 学习的正确率仅比随机猜测略 好, 那么就称这个概念是弱可学习的。 非常有趣的是Schapire后来证 明强可学习与弱可学习是等价的, 也就是说, 在PAC学习的框架下, 一个概念是强可学习的充分必要条件是这个概念是弱可学习的。
这样一来, 问题便成为, 在学习中, 如果已经发现了“弱学习算 法”, 那么能否将它提升(boost) 为“强学习算法”。 大家知道, 发现 弱学习算法通常要比发现强学习算法容易得多。 那么如何具体实施提 升, 便成为开发提升方法时所要解决的问题。 关于提升方法的研究很 多, 有很多算法被提出。 最具代表性的是AdaBoost算法(AdaBoost algorithm) 。
对于分类问题而言, 给定一个训练样本集, 求比较粗糙的分类规 则(弱分类器) 要比求精确的分类规则(强分类器) 容易得多。提升方法就是从弱学习算法出发, 反复学习, 得到一系列弱分类器(又称 为基本分类器) , 然后组合这些弱分类器, 构成一个强分类器。
大多数的提升方法都是改变训练数据的概率分布(训练数据的权值分 布) , 针对不同的训练数据分布调用弱学习算法学习一系列弱分类器。
提升方法的核心问题和思想
对提升方法来说, 有两个问题需要回答: 一是在每一轮如 何改变训练数据的权值或概率分布; 二是如何将弱分类器组合成一个强分类器。
- 关于第1个问题, AdaBoost的做法是提高那些被前一轮弱分类器错误分类样本的权值, 而降低那些被正确分类样本的权值。 这样一来, 那些没有得到正确分类的数据, 由于其权值的加大而受到后一轮的弱分类器的更大关注。 于是, 分类问题被一系列的弱分类器“分而治之”。
- 至于第2个问题, 即弱分类器的组合, AdaBoost采取加权多数表决的方法。 具体地, 加大分类误差率小的弱分类器的权值, 使其在表决中起较大的作用, 减小分类误差率大的弱分类器的权值, 使其在表决中起较小的作用。 AdaBoost的巧妙之处就在于它将这些想法自然且有效地实现在一 种算法里。
8.1.2 AdaBoost算法
8.1.3 AdaBoost的例子
8.2 AdaBoost算法的训练误差分析
8.3 AdaBoost算法的解释
8.3.1 前向分步算法
8.3.2 前向分步算法与AdaBoost
8.4 提升树
8.4.1 提升树模型
8.4.2 提升树算法
8.4.3 梯度提升Gradient Boosting
有前面的例子非常容易理解这部分内容,不再赘述,
GBDT
如果看懂前文,那么理解GBDT就简单很多。有一篇博客,清晰易懂,总结很不错,推荐看一下: 梯度提升树(GBDT)原理小结 ,里面的这有一个提示:关于多分类问题:每轮都在拟合概率向量 [类别1概率,类别2概率…,类别k的概率] 的伪残差。
以下摘录自:大名鼎鼎的 Stanford 统计教材 《The Elements of Statistical Learning》。根据《李航统计学习方法》的提升方法这一章的参考附录正是此书,对比之下,异曲同工,这里不再赘述,快速总结一下。
仔细体会这句话:它和梯度下降十分相似,不同在于GBDT 就是在函数空间的“梯度下降”,以前学习ML和DL的梯度下降是在参数空间的梯度下降。 在梯度下降中,不断减去 $\frac{\partial{f(x)}}{\partial{\theta}}$,希望求得 $min_{\theta}f(x)$ ; 同理梯度提升中不断减去$\frac{\partial{L(y,f(x))}}{f(x)}$,希望求得 $min_{f(x)}L(y, f(x))$ 。这里引用 https://wepon.me 的介绍:
注意:《统计学习方法》这一章介绍的都是最原始的提升树算法,实际上有进一步改进的调整:
步进参数 $\nu$ ,专业名词为shrinkage,又称收缩,类似梯度下降算法的学习率,这里为什么称为步进参数,因为前文提到:提升方法是加法模型的前向分布算法,提升树也属于提升方法中一种(基学习器是决策树)。
例如:运用在GBDT中 $f_m(x)=f_{m-1}(x)+\nu\cdot \sum\limits_{j=1}^{J}\gamma_{jm}I(x\in R_{jm})$
正则化手段:subsampling 子抽样(包括:样本子抽样和特征的子抽样),提高泛化能力。
例如:在随机森林中,每棵树的训练样本都是对原始训练集有放回的子抽样,好处如下:
- 如果不进行随机抽样,每棵树的训练集都一样,那么最终训练出的树分类结果也是完全一样的,依然没有解决决策树过拟合问题。随机抽样是为了保证不同决策树之间的多样性,从而提高模型的泛化能力。使得随机森林不容易陷入过拟合,并且具有较好的抗噪能力(比如:对缺省值不敏感)。
- 而随机森林最后分类取决于多棵树(弱分类器)的投票表决,这种表决应该是”求同”。如果是无放回的抽样,那么每棵树的训练样本都是不同的,都是没有交集的,这样每棵树都是”有偏的”,从而影响最终的投票结果。为了保证最终结果的可靠性,同时又要保证模型的泛化能力,需要每一颗树既要“求同“ 又要 ”存异”。
深入理解请看GDBT的论文:Greedy Function Approximation:A Gradient Boosting Machine
拓展—集成学习
实际上,提升方法(boosting) 属于**集成学习(ensemble learning)** 的一种,集成模型还有2类比较出名:bagging 方法(Bagging 由来:Bootstrap aggregating)和 Stacking方法,随机森林属于第二类。由于随机森林不像提升树不用等待上一轮(上一棵树)结果,各棵树都可以独立求解(包括独立进行子抽样),即可以在树这个粒度下并发进行运算求解,在大规模数据下并发性能良好,随机森林比较简单。后来 XGBoost 和 lightGBM 的良好地实现了算法模型GBDT,虽然依然不是在树粒度层面的并发,但是运行时间和效果在kaggle、KDD Cup等比赛中惊艳四方,将会在日后另一篇中总结xgboost。
进一步学习的经典资料
wiki关于集成模型的介绍 https://en.wikipedia.org/wiki/Ensemble_learning
用 Stanford 的统计教材 The Elements of Statistical Learning 进一步学习与补充
- 第9章 Additive Models, Trees, and Related Methods
- 第10章 Boosting and Additive Trees
- 第14 章 Random Forests
- 第15 章 Ensemble learning
知乎探讨:为什么说bagging是减少variance,而boosting是减少bias? - 知乎
https://www.zhihu.com/question/26760839
为什么在实际的kaggle比赛中,GBDT和Random Forest效果非常好
以下来自马超博士的回答 - 知乎
这是一个非常好,也非常值得思考的问题。换一个方式来问这个问题:为什么基于 tree-ensemble 的机器学习方法,在实际的 kaggle 比赛中效果非常好?
通常,解释一个机器学习模型的表现是一件很复杂事情,而这篇文章尽可能用最直观的方式来解释这一问题。
我主要从三个方面来回答楼主这个问题。
- 理论模型 (站在 vc-dimension 的角度)
- 实际数据
- 系统的实现 (主要基于 xgboost)
通常决定一个机器学习模型能不能取得好的效果,以上三个方面的因素缺一不可。
站在理论模型的角度
统计机器学习里经典的 vc-dimension 理论告诉我们:一个机器学习模型想要取得好的效果,这个模型需要满足以下两个条件:
- 模型在我们的训练数据上的表现要不错,也就是 trainning error 要足够小。
- 模型的 vc-dimension 要低。换句话说,就是模型的自由度不能太大,以防overfit.
当然,这是我用大白话描述出来的,真正的 vc-dimension 理论需要经过复杂的数学推导,推出 vc-bound.
vc-dimension 理论其实是从另一个角度刻画了一个我们所熟知的概念,那就是 bias variance trade-off.
好,现在开始让我们想象一个机器学习任务。对于这个任务,一定会有一个 “上帝函数” 可以完美的拟合所有数据(包括训练数据,以及未知的测试数据)。很可惜,这个函数我们肯定是不知道的 (不然就不需要机器学习了)。我们只可能选择一个 “假想函数” 来 逼近 这个 “上帝函数”,我们通常把这个 “假想函数” 叫做 hypothesis.
在这些 hypothesis 里,我们可以选择 svm, 也可以选择 logistic regression. 可以选择单棵决策树,也可以选择 tree-ensemble (gbdt, random forest). 现在的问题就是,为什么 tree-ensemble 在实际中的效果很好呢?
区别就在于 “模型的可控性”。
先说结论,tree-ensemble 这样的模型的可控性是好的,而像 LR 这样的模型的可控性是不够好的(或者说,可控性是没有 tree-ensemble 好的)。为什么会这样?别急,听我慢慢道来。
我们之前说,当我们选择一个 hypothsis 后,就需要在训练数据上进行训练,从而逼近我们的 “上帝函数”。我们都知道,对于 LR 这样的模型。如果 underfit,我们可以通过加 feature,或者通过高次的特征转换来使得我们的模型在训练数据上取得足够高的正确率。而对于 tree-enseble 来说,我们解决这一问题的方法是通过训练更多的 “弱弱” 的 tree. 所以,这两类模型都可以把 training error 做的足够低,也就是说模型的表达能力都是足够的。但是这样就完事了吗?没有,我们还需要让我们的模型的 vc-dimension 低一些。而这里,重点来了。在 tree-ensemble 模型中,通过加 tree 的方式,对于模型的 vc-dimension 的改变是比较小的。而在 LR 中,初始的维数设定,或者说特征的高次转换对于 vc-dimension 的影响都是更大的。换句话说,tree-ensemble 总是用一些 “弱弱” 的树联合起来去逼近 “上帝函数”,一次一小步,总能拟合的比较好。而对于 LR 这样的模型,我们很难去猜到这个“上帝函数”到底长什么样子(到底是2次函数还是3次函数?上帝函数如果是介于2次和3次之间怎么办呢?)。所以,一不小心我们设定的多项式维数高了,模型就 “刹不住车了”。俗话说的好,步子大了,总会扯着蛋。这也就是我们之前说的,tree-ensemble 模型的可控性更好,也即更不容易 overfit.
站在数据的角度
除了理论模型之外, 实际的数据也对我们的算法最终能取得好的效果息息相关。kaggle 比赛选择的都是真实世界中的问题。所以数据多多少少都是有噪音的。而基于树的算法通常抗噪能力更强。比如在树模型中,我们很容易对缺失值进行处理。除此之外,基于树的模型对于 categorical feature 也更加友好。
除了数据噪音之外,feature 的多样性也是 tree-ensemble 模型能够取得更好效果的原因之一。通常在一个kaggle任务中,我们可能有年龄特征,收入特征,性别特征等等从不同 channel 获得的特征。而特征的多样性也正是为什么工业界很少去使用 svm 的一个重要原因之一,因为 svm 本质上是属于一个几何模型,这个模型需要去定义 instance 之间的 kernel 或者 similarity (对于linear svm 来说,这个similarity 就是内积)。这其实和我们在之前说过的问题是相似的,我们无法预先设定一个很好的similarity。这样的数学模型使得 svm 更适合去处理 “同性质”的特征,例如图像特征提取中的 lbp 。而从不同 channel 中来的 feature 则更适合 tree-based model, 这些模型对数据的 distributation 通常并不敏感。
站在系统实现的角度
除了有合适的模型和数据,一个良好的机器学习系统实现往往也是算法最终能否取得好的效果的关键。一个好的机器学习系统实现应该具备以下特征:
- 正确高效的实现某种模型。我真的见过有些机器学习的库实现某种算法是错误的。而高效的实现意味着可以快速验证不同的模型和参数。
- 系统具有灵活、深度的定制功能。
- 系统简单易用。
- 系统具有可扩展性, 可以从容处理更大的数据。
到目前为止,xgboost 是我发现的唯一一个能够很好的满足上述所有要求的 machine learning package. 在此感谢青年才俊 陈天奇。
在效率方面,xgboost 高效的 c++ 实现能够通常能够比其它机器学习库更快的完成训练任务。
在灵活性方面,xgboost 可以深度定制每一个子分类器,并且可以灵活的选择 loss function(logistic,linear,softmax 等等)。除此之外,xgboost还提供了一系列在机器学习比赛中十分有用的功能,例如 early-stop, cv 等等
在易用性方面,xgboost 提供了各种语言的封装,使得不同语言的用户都可以使用这个优秀的系统。
最后,在可扩展性方面,xgboost 提供了分布式训练(底层采用 rabit 接口),并且其分布式版本可以跑在各种平台之上,例如 mpi, yarn, spark 等等。
有了这么多优秀的特性,自然这个系统会吸引更多的人去使用它来参加 kaggle 比赛。
综上所述,理论模型,实际的数据,良好的系统实现,都是使得 tree-ensemble 在实际的 kaggle 比赛中“屡战屡胜”的原因。
用一句话与大家共勉:算法学习要学习算法之间的联系与区别,优缺点和适用场合,这样能做到融会贯通。