标签: 机器学习 集成学习 GBM GBDT XGBoost前端
梯度提高(Gradient boosting)是一种用于回归、分类和排序任务的机器学习技术,属于Boosting算法族的一部分。Boosting是一族可将弱学习器提高为强学习器的算法,属于集成学习(ensemble learning)的范畴。Boosting方法基于这样一种思想:对于一个复杂任务来讲,将多个专家的判断进行适当的综合所得出的判断,要比其中任何一个专家单独的判断要好。通俗地说,就是“三个臭皮匠顶个诸葛亮”的道理。梯度提高同其余boosting方法同样,经过集成(ensemble)多个弱学习器,一般是决策树,来构建最终的预测模型。python
Boosting、bagging和stacking是集成学习的三种主要方法。不一样于bagging方法,boosting方法经过分步迭代(stage-wise)的方式来构建模型,在迭代的每一步构建的弱学习器都是为了弥补已有模型的不足。Boosting族算法的著名表明是AdaBoost,AdaBoost算法经过给已有模型预测错误的样本更高的权重,使得先前的学习器作错的训练样本在后续受到更多的关注的方式来弥补已有模型的不足。与AdaBoost算法不一样,梯度提高方法在迭代的每一步构建一个可以沿着梯度最陡的方向下降损失(steepest-descent)的学习器来弥补已有模型的不足。经典的AdaBoost算法只能处理采用指数损失函数的二分类学习任务,而梯度提高方法经过设置不一样的可微损失函数能够处理各种学习任务(多分类、回归、Ranking等),应用范围大大扩展。另外一方面,AdaBoost算法对异常点(outlier)比较敏感,而梯度提高算法经过引入bagging思想、加入正则项等方法可以有效地抵御训练数据中的噪音,具备更好的健壮性。这也是为何梯度提高算法(尤为是采用决策树做为弱学习器的GBDT算法)如此流行的缘由,有种观点认为GBDT是性能最好的机器学习算法,这固然有点过于激进又固步自封的味道,但一般各种机器学习算法比赛的赢家们都很是青睐GBDT算法,因而可知该算法的实力不可小觑。git
基于梯度提高算法的学习器叫作GBM(Gradient Boosting Machine)。理论上,GBM能够选择各类不一样的学习算法做为基学习器。现实中,用得最多的基学习器是决策树。为何梯度提高方法倾向于选择决策树(一般是CART树)做为基学习器呢?这与决策树算法自身的优势有很大的关系。决策树能够认为是if-then规则的集合,易于理解,可解释性强,预测速度快。同时,决策树算法相比于其余的算法须要更少的特征工程,好比能够不用作特征标准化,能够很好的处理字段缺失的数据,也能够不用关心特征间是否相互依赖等。决策树可以自动组合多个特征,它能够毫无压力地处理特征间的交互关系而且是非参数化的,所以你没必要担忧异常值或者数据是否线性可分(举个例子,决策树能轻松处理好类别A在某个特征维度x的末端,类别B在中间,而后类别A又出如今特征维度x前端的状况)。不过,单独使用决策树算法时,有容易过拟合缺点。所幸的是,经过各类方法,抑制决策树的复杂性,下降单颗决策树的拟合能力,再经过梯度提高的方法集成多个决策树,最终可以很好的解决过拟合的问题。因而可知,梯度提高方法和决策树学习算法能够互相取长补短,是一对完美的搭档。至于抑制单颗决策树的复杂度的方法有不少,好比限制树的最大深度、限制叶子节点的最少样本数量、限制节点分裂时的最少样本数量、吸取bagging的思想对训练样本采样(subsample),在学习单颗决策树时只使用一部分训练样本、借鉴随机森林的思路在学习单颗决策树时只采样一部分特征、在目标函数中添加正则项惩罚复杂的树结构等。如今主流的GBDT算法实现中这些方法基本上都有实现,所以GBDT算法的超参数仍是比较多的,应用过程当中须要精心调参,并用交叉验证的方法选择最佳参数。github
本文对GBDT算法原理进行介绍,从机器学习的关键元素出发,一步一步推导出GBDT算法背后的理论基础,读者能够从这个过程当中了解到GBDT算法的前因后果。对于该算法的工程实现,本文也有较好的指导意义,实际上对机器学习关键概念元素的区分对应了软件工程中的“开放封闭原则”的思想,基于此思想的实现将会具备很好的模块独立性和扩展性。算法
先复习下监督学习的关键概念:模型(model)、参数(parameters)、目标函数(objective function)缓存
模型就是所要学习的条件几率分布或者决策函数,它决定了在给定特征向量\(x\)时如何预测出目标\(y\)。定义\(x_i \in R^d\) 为训练集中的第\(i\)个训练样本,则线性模型(linear model)能够表示为:\(\hat{y}_i = \sum_j{w_j x_{ij}}\)。模型预测的分数\(\hat{y}_i\)在不一样的任务中有不一样的解释。例如在逻辑回归任务中,\(1/(1+exp(-\hat{y}_i))\)表示模型预测为正例的几率;而在排序学习任务中,\(\hat{y}_i\)表示排序分。app
参数就是咱们要从数据中学习获得的内容。模型一般是由一个参数向量决定的函数。例如,线性模型的参数能够表示为:\(\Theta=\{w_j|j=1,\cdots,d\}\)。机器学习
目标函数一般定义为以下形式:\[ Obj(\Theta)=L(\Theta)+\Omega(\Theta)\]
其中,\(L(\Theta)\)是损失函数,用来衡量模型拟合训练数据的好坏程度;\(\Omega(\Theta)\)称之为正则项,用来衡量学习到的模型的复杂度。训练集上的损失(Loss)定义为:\(L=\sum_{i=1}^n l(y_i, \hat{y}_i)\)。经常使用的损失函数有平方损失(square loss): \(l(y_i, \hat{y}_i)=(y_i - \hat{y}_i)^2\);Logistic损失: \(l(y_i, \hat{y}_i)=y_i ln(1+e^{y_i}) + (1-y_i)ln(1+e^{\hat{y}_i})\)。经常使用的正则项有L1范数\(\Omega(w)=\lambda \Vert w \Vert_1\)和L2范数\(\Omega(w)=\lambda \Vert w \Vert_2\)。Ridge regression就是指使用平方损失和L2范数正则项的线性回归模型;Lasso regression就是指使用平方损失和L1范数正则项的线性回归模型;逻辑回归(Logistic regression)指使用logistic损失和L2范数或L1范数正则项的线性模型。ide
目标函数之因此定义为损失函数和正则项两部分,是为了尽量平衡模型的误差和方差(Bias Variance Trade-off)。最小化目标函数意味着同时最小化损失函数和正则项,损失函数最小化代表模型可以较好的拟合训练数据,通常也预示着模型可以较好地拟合真实数据(groud true);另外一方面,对正则项的优化鼓励算法学习到较简单的模型,简单模型通常在测试样本上的预测结果比较稳定、方差较小(奥坎姆剃刀原则)。也就是说,优化损失函数尽可能使模型走出欠拟合的状态,优化正则项尽可能使模型避免过拟合。函数
从概念上区分模型、参数和目标函数给学习算法的工程实现带来了益处,使得机器学习的各个组成部分之间耦合尽可能松散。
GBDT算法能够当作是由K棵树组成的加法模型:\[\hat{y}_i=\sum_{k=1}^K f_k(x_i), f_k \in F \tag 0\]
其中\(F\)为全部树组成的函数空间,以回归任务为例,回归树能够看做为一个把特征向量映射为某个score的函数。该模型的参数为:\(\Theta=\{f_1,f_2, \cdots, f_K \}\)。于通常的机器学习算法不一样的是,加法模型不是学习d维空间中的权重,而是直接学习函数(决策树)集合。
上述加法模型的目标函数定义为:\(Obj=\sum_{i=1}^n l(y_i, \hat{y}_i) + \sum_{k=1}^K \Omega(f_k)\),其中\(\Omega\)表示决策树的复杂度,那么该如何定义树的复杂度呢?好比,能够考虑树的节点数量、树的深度或者叶子节点所对应的分数的L2范数等等。
如何来学习加法模型呢?
解这一优化问题,能够用前向分布算法(forward stagewise algorithm)。由于学习的是加法模型,若是可以从前日后,每一步只学习一个基函数及其系数(结构),逐步逼近优化目标函数,那么就能够简化复杂度。这一学习过程称之为Boosting。具体地,咱们从一个常量预测开始,每次学习一个新的函数,过程以下:
\[ \begin{split} \hat{y}_i^0 &= 0 \\ \hat{y}_i^1 &= f_1(x_i) = \hat{y}_i^0 + f_1(x_i) \\ \hat{y}_i^2 &= f_1(x_i) + f_2(x_i) = \hat{y}_i^1 + f_2(x_i) \\ & \cdots \\ \hat{y}_i^t &= \sum_{k=1}^t f_k(x_i) = \hat{y}_i^{t-1} + f_t(x_i) \\ \end{split} \]
那么,在每一步如何决定哪个函数\(f\)被加入呢?指导原则仍是最小化目标函数。
在第\(t\)步,模型对\(x_i\)的预测为:\(\hat{y}_i^t= \hat{y}_i^{t-1} + f_t(x_i)\),其中\(f_t(x_i)\)为这一轮咱们要学习的函数(决策树)。这个时候目标函数能够写为:
\[ \begin{split} Obj^{(t)} &= \sum_{i=1}^nl(y_i, \hat{y}_i^t) + \sum_{i=i}^t \Omega(f_i) \\ &= \sum_{i=1}^n l\left(y_i, \hat{y}_i^{t-1} + f_t(x_i) \right) + \Omega(f_t) + constant \end{split}\tag{1} \]
举例说明,假设损失函数为平方损失(square loss),则目标函数为:
\[ \begin{split} Obj^{(t)} &= \sum_{i=1}^n \left(y_i - (\hat{y}_i^{t-1} + f_t(x_i)) \right)^2 + \Omega(f_t) + constant \\ &= \sum_{i=1}^n \left[2(\hat{y}_i^{t-1} - y_i)f_t(x_i) + f_t(x_i)^2 \right] + \Omega(f_t) + constant \end{split}\tag{2} \]
其中,\((\hat{y}_i^{t-1} - y_i)\)称之为残差(residual)。所以,使用平方损失函数时,GBDT算法的每一步在生成决策树时只须要拟合前面的模型的残差。
泰勒公式:设\(n\)是一个正整数,若是定义在一个包含\(a\)的区间上的函数\(f\)在\(a\)点处\(n+1\)次可导,那么对于这个区间上的任意\(x\)都有:\(\displaystyle f(x)=\sum _{n=0}^{N}\frac{f^{(n)}(a)}{n!}(x-a)^ n+R_ n(x)\),其中的多项式称为函数在\(a\)处的泰勒展开式,\(R_ n(x)\)是泰勒公式的余项且是\((x-a)^ n\)的高阶无穷小。
----维基百科
根据泰勒公式把函数\(f(x+\Delta x)\)在点\(x\)处二阶展开,可获得以下等式:
\[f(x+\Delta x) \approx f(x) + f'(x)\Delta x + \frac12 f''(x)\Delta x^2 \tag 3\]
由等式(1)可知,目标函数是关于变量\(\hat{y}_i^{t-1} + f_t(x_i)\)的函数,若把变量\(\hat{y}_i^{t-1}\)当作是等式(3)中的\(x\),把变量\(f_t(x_i)\)当作是等式(3)中的\(\Delta x\),则等式(1)可转化为:
\[ Obj^{(t)} = \sum_{i=1}^n \left[ l(y_i, \hat{y}_i^{t-1}) + g_if_t(x_i) + \frac12h_if_t^2(x_i) \right] + \Omega(f_t) + constant \tag 4\]
其中,\(g_i\)定义为损失函数的一阶导数,即\(g_i=\partial_{\hat{y}^{t-1}}l(y_i,\hat{y}^{t-1})\);\(h_i\)定义为损失函数的二阶导数,即\(h_i=\partial_{\hat{y}^{t-1}}^2l(y_i,\hat{y}^{t-1})\)。
假设损失函数为平方损失函数,则\(g_i=\partial_{\hat{y}^{t-1}}(\hat{y}^{t-1} - y_i)^2 = 2(\hat{y}^{t-1} - y_i)\),\(h_i=\partial_{\hat{y}^{t-1}}^2(\hat{y}^{t-1} - y_i)^2 = 2\),把\(g_i\)和\(h_i\)代入等式(4)即得等式(2)。
因为函数中的常量在函数最小化的过程当中不起做用,所以咱们能够从等式(4)中移除掉常量项,得:
\[ Obj^{(t)} \approx \sum_{i=1}^n \left[ g_if_t(x_i) + \frac12h_if_t^2(x_i) \right] + \Omega(f_t) \tag 5\]
因为要学习的函数仅仅依赖于目标函数,从等式(5)能够看出只需为学习任务定义好损失函数,并为每一个训练样本计算出损失函数的一阶导数和二阶导数,经过在训练样本集上最小化等式(5)便可求得每步要学习的函数\(f(x)\),从而根据加法模型等式(0)可得最终要学习的模型。
一颗生成好的决策树,假设其叶子节点个数为\(T\),该决策树是由全部叶子节点对应的值组成的向量\(w \in R^T\),以及一个把特征向量映射到叶子节点索引(Index)的函数\(q:R^d \to \{1,2,\cdots,T\}\)组成的。所以,策树能够定义为\(f_t(x)=w_{q(x)}\)。
决策树的复杂度能够由正则项\(\Omega(f_t)=\gamma T + \frac12 \lambda \sum_{j=1}^T w_j^2\)来定义,即决策树模型的复杂度由生成的树的叶子节点数量和叶子节点对应的值向量的L2范数决定。
定义集合\(I_j=\{ i \vert q(x_i)=j \}\)为全部被划分到叶子节点\(j\)的训练样本的集合。等式(5)能够根据树的叶子节点从新组织为T个独立的二次函数的和:
\[ \begin{split} Obj^{(t)} &\approx \sum_{i=1}^n \left[ g_if_t(x_i) + \frac12h_if_t^2(x_i) \right] + \Omega(f_t) \\ &= \sum_{i=1}^n \left[ g_iw_{q(x_i)} + \frac12h_iw_{q(x_i)}^2 \right] + \gamma T + \frac12 \lambda \sum_{j=1}^T w_j^2 \\ &= \sum_{j=1}^T \left[(\sum_{i \in I_j}g_i)w_j + \frac12(\sum_{i \in I_j}h_i + \lambda)w_j^2 \right] + \gamma T \end{split}\tag 6 \]
定义\(G_j=\sum_{i \in I_j}g_i\),\(H_j=\sum_{i \in I_j}h_i\),则等式(6)可写为:
\[Obj^{(t)} = \sum_{j=1}^T \left[G_iw_j + \frac12(H_i + \lambda)w_j^2 \right] + \gamma T\]
假设树的结构是固定的,即函数\(q(x)\)肯定,令函数\(Obj^{(t)}\)的一阶导数等于0,便可求得叶子节点\(j\)对应的值为:\[w_j^*=-\frac{G_j}{H_j+\lambda} \tag 7\] 此时,目标函数的值为\[Obj = -\frac12 \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T \tag 8\]
综上,为了便于理解,单颗决策树的学习过程能够大体描述为:
然而,可能的树结构数量是无穷的,因此实际上咱们不可能枚举全部可能的树结构。一般状况下,咱们采用贪心策略来生成决策树的每一个节点。
在上述算法的第二步,样本排序的时间复杂度为\(O(n \log n)\),假设公用K个特征,那么生成一颗深度为K的树的时间复杂度为\(O(dKn\log n)\)。具体实现能够进一步优化计算复杂度,好比能够缓存每一个特征的排序结果等。
如何计算每次分裂的收益呢?假设当前节点记为\(C\),分裂以后左孩子节点记为\(L\),右孩子节点记为\(R\),则该分裂得到的收益定义为当前节点的目标函数值减去左右两个孩子节点的目标函数值之和:\(Gain=Obj_C-Obj_L-Obj_R\),具体地,根据等式(8)可得:\[Gain=\frac12 \left[ \frac{G_L^2}{H_L+\lambda} + \frac{G_R^2}{H_R+\lambda} - \frac{(G_L+G_R)^2}{H_L+H_R+\lambda}\right] - \gamma\] 其中,\(-\gamma\)项表示由于增长了树的复杂性(该分裂增长了一个叶子节点)带来的惩罚。
最后,总结一下GBDT的学习算法:
一般在第四步,咱们把模型更新公式替换为:\(\hat{y}_i^t = \hat{y}_i^{t-1} + \epsilon f_t(x_i)\),其中\(\epsilon\)称之为步长或者学习率。增长\(\epsilon\)因子的目的是为了不模型过拟合。
[1] Gradient Boosting 的更多内容
[2] XGBoost 是一个优秀的GBDT开源软件库,有多种语言接口
[3] Pyramid 是一个基于Java语言的机器学习库,里面也有GBDT算法的介绍和实现
[4] Friedman的论文《Greedy function approximation: a gradient boosting machine》是比较早的GBDT算法文献,可是比较晦涩难懂,不适合初学者,高阶选手能够进一步学习
[5] "A Gentle Introduction to Gradient Boosting"是关于Gradient Boosting的一个通俗易懂的解释,比较适合初学者或者是已经对GBDT算法原理印象不深的从业者
[6] 关于GBDT算法调参的经验和技巧能够参考这两篇博文:《GBM调参指南》、
《XGBoost调参指南》,做者使用的算法实现工具来自于著名的Python机器学习工具scikit-learn
[7] GBDT算法在搜索引擎排序中的应用能够查看这篇论文《Web-Search Ranking with Initialized Gradient Boosted Regression Trees》,这篇论文提出了一个很是有意思的方法,用一个已经训练好的随机森林模型做为GBDT算法的初始化,再用GBDT算法优化最终的模型,取得了很好的效果