提升方法的基本思路就是组合多个弱学习算法构成一个强的学习算法。
AdaBoost算法
AdaBoost算法的思路是,每次用一个弱学习算法对数据进行学习时,更加关注由上一个算法学习后被分类错误的数据。最后将各个弱学习算法按不同权值加和组成强学习算法。具体算法如下:
1. 初始化训练数据的权值
2. 对m=1,2,…,M
(a) 使用具有权值分布 Dm D m 的数据集进行学习,得到基本分类器 Gm(x):X→{−1,+1} G m ( x ) : X → { − 1 , + 1 }
(b) 计算 Gm(x) G m ( x ) 在训练数据集上的分类误差率
(c) 计算 Gm(x) G m ( x ) 的系数
(d) 更新数据集的权值分布
3. 构建基本分类器的线性组合
AdaBoost算法最终分类器的训练误差界为
由上式可知,如果存在 γ>0 γ > 0 ,对所有 m m 有 ,则
前向分步算法
考虑加法模型
1. 初始化 f0(x)=0 f 0 ( x ) = 0
2. 对m=1,2,…,M
(a) 极小化损失函数
(b) 更新
3. 得到加法模型
AdaBoost算法是前向分步算法的特例,模型是由基本分类器组成的加法模型,损失函数是指数函数。证明见P145-P146
提升树
提升树是以分类树或回归树为基本分类器的提升方法。
采用前向分步算法,初始提升树 f0(x)=0 f 0 ( x ) = 0 ,第m步模型 fm(x)=fm−1(x)+T(x;Θm) f m ( x ) = f m − 1 ( x ) + T ( x ; Θ m ) 。通过经验风险最小化确定下一颗决策树的参数
但是对于其他的损失函数而言,每一步的优化就不会那么容易。Freidman提出了梯度提升算法。这是利用最速下降法的近似方法,其关键是利用损失函数的负梯度在当前模型的值作为回归问题提升树算法中的残差近似值,拟合一个回归树。
这种思路与传统Boost对正确、错误样本进行加权有着很大的区别,它是每次都会拟合上一轮模型学习的“残差”,每个新的模型是为了使得之前模型的残差往梯度方向减少。
这个就是 GBDT回归算法的基本思路,对于GBDT分类算法,由于样本输出不是连续的值,而是离散的类别,导致我们无法直接从输出类别去拟合类别输出的误差。为了解决这个问题,主要有两个方法,一个是用指数损失函数,此时GBDT退化为Adaboost算法。另一种方法是用类似于逻辑回归的对数似然损失函数的方法。也就是说,我们用的是类别的预测概率值和真实概率值的差来拟合损失。
对于树深度的设置,知乎上有一个很好的问答 知乎链接 。这里作简要说明。
【问】xgboost/gbdt在调参时为什么树的深度很少就能达到很高的精度?但是用DecisionTree/RandomForest的时候需要把树的深度调到15或更高。
【答】一句话的解释,来自周志华老师的机器学习教科书( 机器学习-周志华):Boosting主要关注降低偏差,因此Boosting能基于泛化性能相当弱的学习器构建出很强的集成;Bagging主要关注降低方差,因此它在不剪枝的决策树、神经网络等学习器上效用更为明显。
随机森林(random forest)和GBDT都是属于集成学习(ensemble learning)的范畴。集成学习下有两个重要的策略Bagging和Boosting。
Bagging算法是这样做的:每个分类器都随机从原样本中做有放回的采样,然后分别在这些采样后的样本上训练分类器,然后再把这些分类器组合起来。简单的多数投票一般就可以。其代表算法是随机森林。Boosting的意思是这样,他通过迭代地训练一系列的分类器,每个分类器采用的样本分布都和上一轮的学习结果有关。其代表算法是AdaBoost, GBDT。
对于Bagging算法来说,由于我们会并行地训练很多不同的分类器的目的就是防止或拟合 ,所以对于每个基分类器来说,目标就是如何降低偏差(bias),只需要关注最小化损失即可。所以我们会采用深度很深甚至不剪枝的决策树。
对于Boosting来说,每一步我们都会在上一轮的基础上更加拟合原数据,所以可以保证偏差(bias),所以对于每个基分类器来说,问题就在于如何选择variance更小的分类器,即更简单的分类器,所以我们选择了深度很浅的决策树。
【问】为什么说bagging是减少variance,而boosting是减少bias?知乎连接
XGBoost
主要摘自陈天齐的PPT
基本模型,假设总共有K颗树
当使用平方误差损失函数时,就是普通的GBM;用log loss时,就是LogitBoost。
XGBoost和GBDT比较大的不同就是目标函数的定义,XGBoost利用泰勒展开做了一个近似。
Obj(t)=∑Ni=1l(yi,y^(t−1)i+ft(xi))+Ω(ft)+constant O b j ( t ) = ∑ i = 1 N l ( y i , y ^ i ( t − 1 ) + f t ( x i ) ) + Ω ( f t ) + c o n s t a n t
≃∑Ni=1[l(yi,y^(t−1)i)+gift(xi)+12hift(xi)2]+Ω(ft)+constant ≃ ∑ i = 1 N [ l ( y i , y ^ i ( t − 1 ) ) + g i f t ( x i ) + 1 2 h i f t ( x i ) 2 ] + Ω ( f t ) + c o n s t a n t
其中, gi=∂y^(t−1)l(yi,y^(t−1)) g i = ∂ y ^ ( t − 1 ) l ( y i , y ^ ( t − 1 ) ) , hi=∂2y^(t−1)l(yi,y^(t−1)) h i = ∂ y ^ ( t − 1 ) 2 l ( y i , y ^ ( t − 1 ) )
XGBoost中
ft(x)
f
t
(
x
)
和
Ω(ft)
Ω
(
f
t
)
定义如下
q(x)定义样本属于哪个叶子节点,w(z)定义叶子节点的值
然后接着定义Obj,首先定义叶子节点j的样本
Ij={i|q(xi)=j}
I
j
=
{
i
|
q
(
x
i
)
=
j
}
Obj(t)≃∑Ni=1[gift(xi)+12hif2t(xi)]+Ω(ft)
O
b
j
(
t
)
≃
∑
i
=
1
N
[
g
i
f
t
(
x
i
)
+
1
2
h
i
f
t
2
(
x
i
)
]
+
Ω
(
f
t
)
=∑Ni=1[giwq(xi)+12hiw2q(xi)]+γT+λ12∑Tj=1w2j
=
∑
i
=
1
N
[
g
i
w
q
(
x
i
)
+
1
2
h
i
w
q
(
x
i
)
2
]
+
γ
T
+
λ
1
2
∑
j
=
1
T
w
j
2
=∑Tj=1[(∑i∈Ijgi)wj+12(∑i∈Ijhi+λ)w2j]+γT
=
∑
j
=
1
T
[
(
∑
i
∈
I
j
g
i
)
w
j
+
1
2
(
∑
i
∈
I
j
h
i
+
λ
)
w
j
2
]
+
γ
T
他把原来对样本的加和变成了对叶子节点的加和,原先是对每一个样本计算它属于的叶子节点的值和
gi
g
i
相乘,变换后,变成了计算这颗树的每个叶子节点上样本的和(因为这个叶子节点上样本的
wj
w
j
是一样的,相当于提取公因式)
这时定义
Gj=∑i∈Ijgi
G
j
=
∑
i
∈
I
j
g
i
,
Hj=∑i∈Ijhi
H
j
=
∑
i
∈
I
j
h
i
则Obj变为一个关于
wj
w
j
的一元二次函数
Obj(t)=∑Tj=1[Gjwj+12(Hj+λ)w2j]+γT
O
b
j
(
t
)
=
∑
j
=
1
T
[
G
j
w
j
+
1
2
(
H
j
+
λ
)
w
j
2
]
+
γ
T
极值点为
w∗j=−GjHj+λ
w
j
∗
=
−
G
j
H
j
+
λ
,目标函数为
Obj=−12∑Tj=1G2jHj+λ+γT
O
b
j
=
−
1
2
∑
j
=
1
T
G
j
2
H
j
+
λ
+
γ
T
至此,目标函数定义完毕。现在需要枚举所有可能的树结构。具体做法:
从深度为0的树开始,对于每个节点,枚举所有特征。先将样本按该特征排序,然后遍历所有可能的切分点寻找最优切分点。依据是
Gain=12[G2LHL+λ+G2RHR+λ−(GL+GR)2HL+HR+λ]−γ
G
a
i
n
=
1
2
[
G
L
2
H
L
+
λ
+
G
R
2
H
R
+
λ
−
(
G
L
+
G
R
)
2
H
L
+
H
R
+
λ
]
−
γ
(切分后左孩子的分数加右孩子的分数减不切分的分数)。Gain的值越大,分裂后L减小得越多。因为加入了
γ
γ
新叶子的惩罚值,引入分割不一定会使得情况变好。优化这个目标对应了树的剪枝, 当引入的分割带来的增益小于一个阈值的时候,我们可以剪掉这个分割。
除了上面这种精确算法,还有一种近似算法,对于每个特征只考虑分位点,减少复杂度。比如这种三分位数
实际上XGBoost不是按照样本个数进行分位,而是以二阶导数值作为权重,比如
为什么用
hi
h
i
加权?把目标函数整理成以下形式,可以看出
hi
h
i
有对loss加权的作用
总的算法流程:每轮迭代增加一颗新树,根据上述方法计算最优树,加入到模型中 y(t)=y(t−1)+ϵft(xi) y ( t ) = y ( t − 1 ) + ϵ f t ( x i ) ( ϵ ϵ 的作用是每一步都不要完全优化,防止过拟合)
对于深度为K的树,时间复杂度为O(n d K logn),每次排序需要O(n logn),遍历d个特征,树深度为K。在论文中提出了并行化方案,由于提升方法是串行的,理论上不能并行化,但是XGB算法中,最耗时间的是样本的排序,论文提出一种叫做block的存储方式,将样本先按特征排好序存放在block中,每轮迭代使用时直接读取,这样时间复杂度降为O(n)。由于block按特征大小顺序存储,相应的样本梯度信息是分散的,造成内存的不连续访问,降低CPU cache的命中率。XGBoost预存数据到buffer中,再统计梯度信息。
GBDT和XGBoost区别
这里摘取知乎上wepon的回答,一定要看!!! 知乎上wepon的回答
GBDT在函数空间中利用梯度下降法进行优化;XGBoost在函数空间中用牛顿法进行优化。梯度下降法可以看做是将损失函数进行一阶泰勒展开:选取
θ
θ
初值,然后不断迭代(
θt=θt−1+Δθ
θ
t
=
θ
t
−
1
+
Δ
θ
)使损失函数
L(θ)
L
(
θ
)
最小化。将
L(θt)
L
(
θ
t
)
在
θt−1
θ
t
−
1
处进行一阶泰勒展开。
L(θt)=L(θt−1+Δθ)≃L(θt−1)+L′(θt−1)Δθ
L
(
θ
t
)
=
L
(
θ
t
−
1
+
Δ
θ
)
≃
L
(
θ
t
−
1
)
+
L
′
(
θ
t
−
1
)
Δ
θ
要使得
L(θt)<L(θt−1)
L
(
θ
t
)
<
L
(
θ
t
−
1
)
,可取
Δθ=−αL′(θt−1)
Δ
θ
=
−
α
L
′
(
θ
t
−
1
)
,则
θt=θt−1−αL′(θt−1)
θ
t
=
θ
t
−
1
−
α
L
′
(
θ
t
−
1
)
牛顿法是将
L(θt)
L
(
θ
t
)
在
θt−1
θ
t
−
1
处进行二阶泰勒展开。
L(θt)≃L(θt−1)+L′(θt−1)Δθ+L′′(θt−1)Δθ22
L
(
θ
t
)
≃
L
(
θ
t
−
1
)
+
L
′
(
θ
t
−
1
)
Δ
θ
+
L
″
(
θ
t
−
1
)
Δ
θ
2
2
要使
L(θt)
L
(
θ
t
)
极小,得
Δθ=−gh
Δ
θ
=
−
g
h
,其中g,h分别是一阶和二阶导数。
相比于原始的GBDT,XGBoost的目标函数加了正则项。
正则项解释方式
wepon的解释
1. 通过偏差方差分解去解释
2. PAC-learning泛化界解释
在知乎上找到有关Stein‘s Pheonomenon的解释,没怎么看懂!
3. Bayes角度看,正则相当于对模型引入了先验分布。
LightGBM
相比XGB,LGB速度更快,内存占用更低。
LightGBM的改进
1. 直方图算法
把连续的浮点特征值离散化成k个整数,同时构造一个宽度为k的直方图。在遍历数据的时候,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。
2. 直方图差加速
一个叶子的直方图可以由他的父亲节点的直方图和兄弟节点的直方图做差得到。选取哪个叶子
参考
gbdt算法原理与系统设计简介
xgboost原理
XGBoost Reliable Large-scale Tree Boosting System
BoostedTree
正则化
LightGBM论文
LightGBM github