1. 历史及演进
提升学习算法,又常常被称为Boosting,其主要思想是集成多个弱分类器,然后线性组合成为强分类器。为什么弱分类算法可以通过线性组合形成强分类算法?其实这是有一定的理论基础的。1988年,Kearns和Valiant首先提出了“强可学习”和“弱可学习”的概念,他们指出,在概率近似正确(Probably Approximately Correct, PAC)学习的框架中,一个概念,如果存在一个多项式的学习算法能够学习它,并且正确率很高,那么就称这个概念是强可学习的;如果正确率只是稍比随机猜测好,那么就称这个概念是弱可学习的。在两年之后,Schapire证明了强可学习和弱可学习是等价的,也就是说,在PAC学习的框架下,一个概念的强可学习可以由弱可学习转换得到。但在此后很长一段时间里,都没有人能设计一种算法验证PAC理论。直到1996年,Schapire提出了AdaBoost算法才将这种僵局打破。AdaBoost算法将多个不同的决策树用一种非随机的方式组合起来,比传统决策树精度更高也更加稳定。AdaBoost的出现不仅拯救了有些式微的决策树算法,而且还开创了集成学习算法的先河。在AdaBoost的影响下,Friedman在1999年提出了梯度提升决策树算法,这种算法完美地将梯度提升算法和CART算法结合起来,不仅可以用于回归问题,同样也可以应用于分类问题中。GBDT被很多人认为是传统机器学习中位居Top3的算法。即使这样,GBDT也并不完美,相比于AdaBoost,因为后一棵树的训练依赖于前一棵树的残差,所以其并不能进行并行训练。XGBoost是近年来针对大规模机器学习需求对GBDT提出的改进方案。XGBoost是2016年由华盛顿大学在读博士生陈天奇发布的开源框架,相关论文 XGBoost: A Scalable Tree Boosting System 也发表在机器学习与数据挖掘***会议KDD2016上。XGBoost较传统的GBDT算法,加入了正则项,能够更好地防止模型过拟合,并且可以并行分布式计算,极大地提高了精度和训练效率。
2. AdaBoost
AdaBoost的关键在于如何生成多个不同的弱分类器,其主要思想是在下一轮迭代中强化那些被误分类的样例,而弱化那些被正确分类的样例。在实际设计时,通过调整每个样例的权重来达到给予不同样例不同关注程度的目的。每一轮的权重调整都是基于上一轮分类器对所有样例分类情况的误差评估,而每个分类器的权重则是由该分类器对样例总体精度决定。
这里我们以最简单的二类分类问题为例对AdaBoost算法进行介绍,其他分类和回归问题与此类似。假定二类分类问题的训练数据集为:$(X,y)=\{(x_1,y_1), (x_2,y_2),...,(x_n,y_n)\}$。其中每个训练样本由特征向量和标记组成,特征向量$x_i \in \mathbb{R}^d, i=1,2,...,n$,$d$为特征向量的维度,标记$y_i \in \{\pm1\}, i=1,2,...,n$。下面将介绍AdaBoost的详细算法流程。
1. 初始化训练数据的权值:
$$W_1=(w_{1,1},w_{1,2},...,w_{1,i},...,w_{1,n}),w_{1,i}=\frac{1}{n},i=1,2,...,n$$
在最开始的时候,我们并不知道训练数据的权值分布,只能假定每个训练样例在初始的基本分类器中的作用相同,所以设置每个样例的权重相同。
2. 对当前的权值分布$W_m$,使用训练数据进行训练,得到当前轮的基本分类器:
$$F_m(x):x\rightarrow \{\pm1\}$$
使用具有权重的训练数据进行训练,需要对训练方法的公式稍加修改。
3. 计算$F_m(x)$在训练数据集上的分类误差率:
$$e_m=P\left(F_m(x)\neq y_i\right)=\sum_{i=1}^{n}w_{m,i}\,I\left(F_m(x_i)\neq y_i\right)$$
$$ I\left(F_m(x_i)\neq y_i\right)=\left\{
\begin{aligned}
1&& F_m(x_i)\neq y_i\\
0&& F_m(x_i)=y_i\\
\end{aligned}
\right.
$$
因为每个训练样例都有权重,所以我们只需要将那些错误分类的样例的权重全部相加即可计算得出基本分类器在训练数据上的分类误差率。
4. 计算当前分类器$F_m(x)$的权重:
$$\alpha_m=\frac{1}{2}\ln\left(\frac{1-e_m}{e_m}\right)$$
可以看出,当$e_m<\frac{1}{2}$时,$\alpha_m>0$,并且随着$e_m$的减小而增大。即,分类误差率越大的权重应该越小,反而精度越高的权重应该越大。这其实很符合现实规律,比如三个人共同筹资创建一个公司,甲出资$30$万元,乙出资$15$万元,丙出资$5$万元,按出资比例,甲占股$60\%$,乙占股$30\%$,丙占股$10\%$。在公司股东会表决某项决议的时候,甲拥有$60\%$的投票权,而乙和丙分别拥有$30\%$和$10\%$的投票权。
5. 使用$w_{m,i}$,$\alpha_m$,$F_m(x_i)$和$y_i$更新样例$i$的权重:
$$w_{m+1,i}=\frac{w_{m,i}}{Z_m}exp\left(-\alpha_m \, y_i \,F_m\,(x_i)\right)$$
$Z_m$为规范化因子,使$W_{m+1}$称为一个合理的概率分布:
$$Z_m=\sum_{i=1}^{n}w_{m,i}\,exp\left(-\alpha_m \, y_i \,F_m\,(x_i)\right)$$
更新样本权重时,主要根据样例在当前基本分类器中的分类情况,如果$y_i=1$,$F_m(x)$越趋近于$1$的时候,$exp\left(-\alpha_m \, y_i \,F_m\,(x_i)\right)$越小,反而如果越接近$-1$时越大。这也正符合当前轮分得越差的样例在下一轮越受到重视的设计。
6. 对$m=1,2,...,M$($M$是基本分类器的个数),重复步骤2~5;
7. 构建基本分类器的线性组合$f(x)$及其最终分类器$F(x)$:
$$f(x)=\sum_{m=1}^{M}\alpha_m\, F_m(x)$$
$$F(x)=sign(f(x))=sign\left(\sum_{m=1}^{M}\alpha_m \,F_m(x)\right)$$
$sign(x)$为符号函数,满足以下性质:
$$ sign(x)=\left\{
\begin{aligned}
1&& x\geq 0\\
-1&& x<0\\
\end{aligned}
\right.
$$
你可能已经注意到,$F_m(x)$的系数计算及训练数据的权值分布直接定义了两个公式,并不能一眼看出其中的含义,那么为什么会想到这么定义呢?其实AdaBoost算法还有另外一种解释方法,即可以认为其是模型为加法模型、损失函数为指数函数、学习算法为前向分布算法时的二类分类学习方法。具体内容可参考李航的《统计机器学习》第$8$章 提升方法。
2. GBDT
GBDT是Gradient Boosting Decision Tree的简称,很多时候又称为MART(Multiple Additive Regession Tree)。不同于经典的集成学习算法Adaboost利用前一轮学习器的误差来更新下一轮学习的样本权重,GBDT每次都拟合上一轮分类器产生的残差。举个例子便于理解,比如一个人的年龄是50岁,第一棵树拟合的结果是35岁,第一轮的残差为15岁;然后第二棵数拟合的结果是10岁,两棵树相加总的拟合结果是45岁,第二轮的残差为5岁;第三棵数拟合的结果为2岁,三棵树相加拟合的结果是47岁,第三轮的残差是3岁......只要如此不断地进行下去,拟合结果就可以达到50岁,拟合残差的过程就是训练数据的过程。
对于一个给定的数据集$\{x_i,y_i\}, i=1,2,...,m$,其中特征向量$x_i \in \mathbb{R}^n$,标签$y_i \in \mathbb{R}$,可以用$x_{ij}, j=1,2,...,d来表示x_i的第j个特征值$。对于一个典型的回归决策树问题,需要遍历所有特征$j$的全部阈值$t$,找到最优的$j$和$t$使下面的等式最小化:
$$S_j=\sum_{i \in L}(y_i-\mu_L)^2+\sum_{i \in R}(y_i-\mu_R)^2$$
其中$x_{ij} \leq t$的所有样本落入左子树$L$中,其中$x_{ij} > t$的所有样本落入右子树$R$中,$\mu_L(\mu_R)$表示左子树(右子树)所有样例标签值的均值。如果这就是一棵最简单的拥有一个根节点、两个叶子节点的二叉回归树,那么只需要根据最优阈值切分为左右子树,并且分别计算左右子树的值$\gamma_l,l=1,2$即可。如果将划分子树的过程继续进行$L-1$次即可得到一棵包含$L$个叶子节点的回归树。
上面公式使用最小二乘法计算拟合误差,所以通过上面方法得到的模型又称为最小二乘回归树。其实不管误差的计算方式如何,我们都可以拟合出相应的回归树,唯一的区别是梯度的计算不同而已。
GBDT使用线性组合的方式将拟合的树结合起来,作为最后的输出:
$$F_n(x)=\sum_{i=1}^{N}\alpha_if_i(x)$$
$f_i(x)$是单棵回归树函数,$\alpha_i$是第$i$棵回归树的权重,一般采用相等权重即可。
在这里我们需要弄清楚为什么拟合残差就能不断减少拟合误差。假设拟合误差$C$是拟合函数$F_n$的函数$C(F_n)$。那么:
$$\delta C \approx \frac{\partial{C(F_n)}}{\partial{F_n}}\delta F_n$$
如果取$\delta F_n=-\eta \frac{\partial{C}}{\partial{F_n}}$,就可以得到$\delta C<0$。其中$\eta$是学习率,为正实数。所以只要函数$F_n$拟合误差函数的负梯度就可以不断降低拟合误差的值。
设标签向量$y=[y_1,y_2,...,y_m]^T$,如果用最小二乘的方式表示拟合误差,则:$$C=\frac{1}{2}(F_n-y)^2$$
那么$\delta F_n=-\eta \frac{\partial{C}}{\partial{F_n}}=-\eta (F_n-y)$。$F_n-y$其实就是每一轮拟合后的残差,所以拟合残差就可以不断减少拟合误差。
3. XGBoost
XGBoost,全称为eXtreme Gradient Boosting,是目前唯一能与LightGBM相媲美的提升树算法。XGBoost的出现解决了传统GBDT两个很明显的缺点:容易过拟合和对于大数据训练效率低。既然在一定程度上解决了过拟合问题,模型的训练精度也得到了很大的提升。在XGBoost刚分布的半年内,曾经一度包揽了Kaggle比赛的冠军,实力可见一斑。那么XGBoost是怎样解决过拟合问题从而提升模型精度的呢?
我们先从一般的机器学习问题入手,然后再逐步深入了解XGBoost的原理。定义机器学习问题的损失函数如下:
$$Obj(\Theta)=L(\Theta)+\Omega(\Theta)$$
其中的$L(\Theta)$表示训练误差,衡量当前机器学习模型与训练数据的拟合程度,训练误差越低表示模型拟合得越好,但并不一定代表模型的泛化能力就强,因为很有可能是因为模型在训练数据集上过拟合导致的。$\Omega(\Theta)$表示模型正则化项,衡量了模型本身的复杂程度。在“以简为美”的理念下,一般更倾向于选择简单的模型,认为越简单的模型越可能是数据真正的规律。同时优化这两项,就可以找到模型复杂度和模型对训练数据拟合程度之间的一个tradeoff。
对于有$K$棵树的GBDT模型来说,可以定义模型的损失函数如下:
$$Obj(\Theta)=\sum_{i=1}^{N}l(y_i,\hat{y_i})+\sum_{k=1}^{K}\Omega(f_k)$$
其中$\hat{y_i}=\sum_{k=1}^{K}f_k(x_i)$表示模型对样例$i$的预测值,$y_i$则表示样例$i$的标签。$\Omega(f_k)$表示第$i$棵树的复杂度。可以说以上的定义合乎情理,同时考虑了模型的拟合程度和树模型的总复杂度。
如何定义每棵树的拟合函数$f_k$呢?我们以陈天奇PPT里的图为例:
$f_k(x)=w_{q(x)}$,$w \in \mathbb{R}^T$,$T$为叶子节点个数,$q(x)$将样例映射到叶子节点。$f_k(x)$将每个样例映射到叶子节点,并且赋予一定的权重,但并没有定义树形结构,所以在后面优化时只能采取启发式的方法构建树。
在XGBoost中,定义单棵树的正则化项为:
$$\Omega(f_k)=\gamma T+\frac{1}{2}\lambda\sum_{j=1}^{T}w_{j}^{2}$$
第一项为树的叶子节点个数,第二项为所有叶子节点分数的$L2$正则化项。
很明显,以上定义的损失函数并不能直接求导,所以也就不能直接使用SGD等梯度下降算法进行求解,GBDT是一种可行的解法。
实际上,可以将GBDT看成是一种加法训练模型,在第$k$轮时,模型函数由第$k-1$轮和当前轮的拟合函数相加构成,也即:
$$\hat{y_i}^{(k)}=\hat{y_i}^{(k-1)}+f_k(x_i)$$
所以,可以改成损失函数为:
$$Obj^{(k)}=\sum_{i=1}^{N}l\left(y_i,\hat{y_i}^{(k)}\right)+\sum_{i=1}^{k}\Omega(f_i)=\sum_{i=1}^{N}l\left(y_i,\hat{y_i}^{(k-1)}+f_k(x_i)\right)+\Omega(f_k)+const$$
考虑最简单的平方误差,损失函数改写为:
$$Obj^{(k)}=\sum_{i=1}^{N}\left[y_i-(\hat{y_i}^{(k-1)}+f_k(x_i))\right]^2+\Omega(f_k)+const=\sum_{i=1}^{N}\left[2(\hat{y_i}^{(k-1)}-y_i)f_k(x_i)+f_k(x_i)^2\right]+\Omega(f_k)+const$$
回忆一下泰勒展开公式,二阶泰勒展开式为:
$$f(x+\Delta{x})\simeq f(x)+f'(x)\Delta{x}+\frac{1}{2}f''(x)\Delta{x}^2$$
那么目标损失函数可以改写为:
$$Obj^{(k)}\simeq \sum_{i=1}^{N}\left[l(y_i,\hat{y_i}^{(k-1)})+g_if_k(x_i)+\frac{1}{2}h_if_k^2(x_i)\right]+\Omega(f_k)+const$$
其中,
$$g_i=\frac{\partial l(y_i,\hat{y_i}^{(k-1)})}{\partial \hat{y_i}^{(k-1)}}$$
$$h_i=\frac{\partial^2 l(y_i,\hat{y_i}^{(k-1)})}{\partial (\hat{y_i}^{(k-1)})^2}$$
移除常数项,得到:
$$Obj^{(k)}=\sum_{i=1}^{N}\left[g_if_k(x_i)+\frac{1}{2}h_if_k^2(x_i)\right]+\Omega(f_k)$$
如果叶子节点$j$里的样例集合用$I_j$表示,可以改成损失函数如下所示:
$$Obj^{(k)}=\sum_{i=1}^{N}\left[g_if_k(x_i)+\frac{1}{2}h_if_k^2(x_i)\right]+\Omega(f_k)=\sum_{i=1}^{N}\left[g_iw_{q(x_i)}+\frac{1}{2}h_iw_{q(x_i)}^2\right]+\gamma T+\lambda \frac{1}{2}\sum_{j=1}^{T}w_j^2=\sum_{j=1}^{T}\left[(\sum_{i\in I_j}g_i)w_j+\frac{1}{2}(\sum_{i \in I_j}h_i+\lambda)w_j^2\right]+\gamma T$$
令$G_j=\sum_{i\in I_j}g_i, H_j=\sum_{i \in I_j}h_i$,则:
$$Obj^{(k)}=\sum_{j=1}^{T}\left[G_jw_j+\frac{1}{2}(H_j+\lambda)w_j^2\right]+\gamma T$$
如果上式中当前树形结构已经确定,那么可以在每个叶子节点优化权重,可以得到:
$$w_j^*=-\frac{G_j}{H_j+\lambda}$$
$$Obj=-\frac{1}{2}\sum_{j=1}^{T}\frac{G_j^2}{H_j+\lambda}+\gamma T$$
$Obj$评价了一棵树的好坏,值越小树模型越好。但是问题来了,如果构建这棵树呢?构建树的方式似乎可以有很多很多种,遍历所有的树形结构肯定是不可取的。回想一下在我们最开始学二叉树的时候,知道树是一层一层地构建出来的,这里其实也可以定义一个切分规则,采用一种启发式的方法,从上到下,一层层地将树结构构建出来。
决策树ID3采用信息增益划分节点,回归树使用基尼指数划分节点,这里可以根据损失函数来定义节点划分标准:
$$Gain=\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}-\gamma$$
第一项为左子树的cost,第二项为右子树的cost,第三项为划分前的cost,最后一项定义为由于节点划分带给整棵树的复杂度提升,可以作为是否切分节点的标准。有了节点划分标准,就可以将树结构迭代地构建出来。
XGBoost可以说完美地将正则化融入到GBDT模型中。