ID3
ID3是用信息增益作为分割的准则,信息增益=信息熵-条件熵:
其中
∣
C
k
∣
D
\frac{|C_k|}{D}
D∣Ck∣表示第k类样本的数据占数据集D样本总数的比例。
假设每个记录有一个属性“ID”,若按照ID来进行分割的话,由于ID是唯一的,因此在这一个属性上,能够取得的特征值等于样本的数目,也就是说ID的特征值很多。那么无论以哪个ID为划分,叶子结点的值只会有一个,纯度很大,得到的信息增益会很大,但这样划分出来的决策树是没意义的。由此可见,ID3决策树偏向于取值较多的属性进行分割,存在一定的偏好,所以搞了ID4.5作为优化
ID4.5
信息增益比率通过引入一个被称作分裂信息(Split information)的项来惩罚取值较多的属性。
上式,分子计算与ID3一样,分母是由属性A的特征值个数决定的,个数越多,IV值越大,信息增益率越小,这样就可以避免模型偏好特征值多的属性,但是聪明的人一看就会发现,如果简单的按照这个规则来分割,模型又会偏向特征数少的特征。因此C4.5决策树先从候选划分属性中找出信息增益高于平均水平的属性,在从中选择增益率最高的。
CART
CART是一棵二叉树,采用二元切分法,每次把数据切成两份,分别进入左子树、右子树。而且每个非叶子节点都有两个孩子,所以CART的叶子节点比非叶子多1。相比ID3和C4.5,CART应用要多一些,既可以用于分类也可以用于回归。CART分类时,使用基尼指数(Gini)来选择最好的数据分割的特征,gini描述的是纯度,与信息熵的含义相似。CART中每一次迭代都会降低GINI系数。
G
i
n
i
(
D
)
=
1
−
∑
i
=
0
n
(
∣
C
i
∣
∣
D
∣
)
2
G
i
n
i
(
D
∣
A
)
=
∑
i
=
0
n
∣
D
i
∣
∣
D
∣
G
i
n
i
(
D
i
)
Gini(D) = 1-\sum_{i=0}^{n}{(\frac{|C_i|}{|D|})^2} \\ Gini(D|A)=\sum_{i=0}^{n}\frac{|D_i|}{|D|}Gini(D_i)
Gini(D)=1−i=0∑n(∣D∣∣Ci∣)2Gini(D∣A)=i=0∑n∣D∣∣Di∣Gini(Di)
Ci表示D中Category的数量
Di表示以A是属性值划分成n个分支里第i个分支的数目
Gini(D)反映了数据集D的纯度,值越小,纯度越高。我们在候选集合中选择使得划分后基尼指数最小的属性作为最优化分属性。
决策树剪枝
典型的剪枝有两种:
- 预剪枝:例如树高到一定程度,停止树的生长;样本数量小于一定数值,停止树的生长等等
- 后剪枝:典型的方法有代价剪枝(Cost Complexity Prune,CCP),就是把决策树所有的子树都列出来,拿去挨个剪,剪了以后看误差增加率,误差增加率不高的子树就剪掉
Bagging
Bagging是Bootstrap Aggregate的简称,意思就是再抽样,即每一次从原始数据中根据均匀概率分布有放回的抽取和原始数据大小相同的样本集合,样本点可能出现重复,然后对每一次产生的训练集构造一个分类器,再对分类器进行组合。
Bagging是减小方差的优化。假设有n个完全独立的模型,每个方差为
σ
2
\sigma^2
σ2,也就是说
V
a
r
(
X
i
)
=
σ
2
Var(X_i)=\sigma^2
Var(Xi)=σ2,那么
V
a
r
(
1
n
∑
i
=
1
n
X
i
)
=
1
n
2
V
a
r
(
∑
i
=
1
n
X
i
)
=
σ
2
n
Var(\frac{1}{n}\sum_{i=1}^nX_i)=\frac{1}{n^2}Var(\sum_{i=1}^n{X_i})=\frac{\sigma^2}{n}
Var(n1i=1∑nXi)=n21Var(i=1∑nXi)=nσ2
但是Bagging的过程中有放回,假设单模型的相关系数是p,那么
V
a
r
(
1
n
∑
i
=
1
n
X
i
)
=
p
σ
2
+
(
1
−
p
)
σ
2
n
Var(\frac{1}{n}\sum_{i=1}^nX_i)=p\sigma^2+(1-p)\frac{\sigma^2}{n}
Var(n1i=1∑nXi)=pσ2+(1−p)nσ2,随着n增大,最终趋向于
p
n
σ
2
\frac{p}{n}\sigma^2
npσ2,因此Bagging会是减小方差的优化
Boosting
提升是减少偏差的优化。在训练好一个弱分类器后,我们需要计算弱分类器的错误或者残差,作为下一个分类器的输入。这个过程本身就是在不断减小损失函数,来使模型不断逼近“靶心”,使得模型偏差不断降低。
GBDT
GBDT是以决策树(CART)为基学习器的GB算法,是迭代树,而不是分类树。Boost是"提升"的意思,一般Boosting算法都是一个迭代的过程,每一次新的训练都是为了改进上一次的结果
GBDT的核心就在于:每一棵树学的是之前所有树结论和的残差,这个残差就是一个加预测值后能得真实值的累加量。比如A的真实年龄是18岁,但第一棵树的预测年龄是12岁,差了6岁,即残差为6岁。那么在第二棵树里我们把A的年龄设为6岁去学习,如果第二棵树真的能把A分到6岁的叶子节点,那累加两棵树的结论就是A的真实年龄;如果第二棵树的结论是5岁,则A仍然存在1岁的残差,第三棵树里A的年龄就变成1岁,继续学习。
XGBoost
Tianqi Chen的原始论文写的非常清楚: https://arxiv.org/pdf/1603.02754.pdf
XGBoost对目标函数和数值计算做了大量优化的工作,假设现在有t-1棵树,那么第t棵树就是拟合现在的残差,让残差达到最小。用 F t − 1 ( x i ) F_{t-1}(x_i) Ft−1(xi)表示现有的t-1棵树的最优解,用 f t ( x i ) f_t(x_i) ft(xi)表示当前这棵树,用l(x,y)表示两个结果之间的loss,那么残差的函数 L t L_t Lt表示成
L
t
=
∑
i
l
(
y
i
,
F
t
(
x
i
)
)
+
Ω
(
f
t
)
L
t
=
∑
i
l
(
y
i
,
F
t
−
1
(
x
i
)
+
f
t
(
x
i
)
)
+
Ω
(
f
t
)
L_t=\sum_{i}l(y_i,F_{t}(x_i))+\Omega(f_t) \\ L_t=\sum_{i}l(y_i,F_{t-1}(x_i)+f_t(x_i))+\Omega(f_t)
Lt=i∑l(yi,Ft(xi))+Ω(ft)Lt=i∑l(yi,Ft−1(xi)+ft(xi))+Ω(ft)
现在
f
t
(
x
i
)
f_t(x_i)
ft(xi)函数在l函数里面,用泰勒展开把
f
t
(
x
i
)
f_t(x_i)
ft(xi)甩在l函数外面,泰勒展开的公式是
f
(
x
+
Δ
x
)
≈
f
(
x
)
+
Δ
x
f
′
(
x
)
+
1
2
Δ
x
f
′
′
(
x
)
f(x+\Delta x) \approx f(x)+\Delta xf^{'}(x)+\frac{1}{2}\Delta xf^{''}(x)
f(x+Δx)≈f(x)+Δxf′(x)+21Δxf′′(x)
现在把
f
t
(
x
i
)
f_t(x_i)
ft(xi)看成泰勒展开公式里的
Δ
x
\Delta x
Δx,把整个l看成
f
(
x
+
Δ
x
)
f(x+\Delta x)
f(x+Δx),那么可以得到
L
t
=
∑
i
(
l
[
y
i
,
F
t
−
1
(
x
i
)
]
+
g
i
f
t
(
x
i
)
+
1
2
h
i
f
t
2
(
x
i
)
)
+
Ω
(
f
t
)
L_t=\sum_{i}(l[y_i,F_{t-1}(x_i)]+g_if_t(x_i)+\frac{1}{2}h_if_t^2(x_i))+\Omega(f_t)
Lt=i∑(l[yi,Ft−1(xi)]+gift(xi)+21hift2(xi))+Ω(ft)
而
∑
i
(
l
[
y
i
,
F
t
−
1
(
x
i
)
]
)
\sum_{i}(l[y_i,F_{t-1}(x_i)])
∑i(l[yi,Ft−1(xi)])其实已知了,目前是个const,
g
i
g_i
gi和
h
i
h_i
hi分别是一阶导和二阶导数。因此直接考虑l函数外面针对
f
t
f_t
ft这颗树的情况就可以了。假设
f
t
f_t
ft这棵树有T个叶子节点,每个叶子节点决定了一定数量的训练数据的归属。同时给正则项赋值为
Ω
(
f
t
)
=
γ
T
+
1
2
λ
∑
j
=
1
T
f
t
2
(
x
i
)
\Omega(f_t)=\gamma T+\frac{1}{2}\lambda\sum_{j=1}^Tf_t^2(x_i)
Ω(ft)=γT+21λ∑j=1Tft2(xi),其实就是加了二阶正则和一个常数,一会儿从最终的表达式可以看出为啥要加这两项,同时我们用
w
j
w_j
wj来代替
f
t
(
x
i
)
f_t(x_i)
ft(xi),
w
j
w_j
wj表示第j个叶子节点上若干训练数据对应的预测值的和。所以现在的
L
t
L_t
Lt是:
L
t
=
∑
j
=
1
T
(
G
j
w
j
+
1
2
(
H
j
+
λ
)
w
j
2
)
+
γ
T
L_t=\sum_{j=1}^T(G_jw_j+\frac{1}{2}(H_j+\lambda )w_j^2)+\gamma T
Lt=j=1∑T(Gjwj+21(Hj+λ)wj2)+γT
注意这里的
G
j
G_j
Gj就是某个叶子节点上所有训练样本的
g
i
g_i
gi的和,
H
j
H_j
Hj就是某个叶子节点上所有训练样本的
h
i
h_i
hi的和,某个叶节点预测值的和
w
j
w_j
wj肯定正比于所有梯度的和,也是说这个叶子节点上的所有训练数据给一个同样的微分dx,整个叶子节点预测值的变化dy是和梯度和成正比的,所以直接求和就可以。
给
w
j
w_j
wj求偏导得到最优的
w
j
∗
w_j^*
wj∗如下表达式:
w
j
∗
=
−
G
j
H
j
+
λ
w_j^*=-\frac{G_j}{H_j+\lambda}
wj∗=−Hj+λGj
把
w
j
∗
w_j^*
wj∗代回到
L
t
L_t
Lt,可以得到
L
t
∗
=
−
1
2
∑
j
=
1
T
G
j
2
H
j
+
λ
+
γ
T
L_t^*=-\frac{1}{2}\sum_{j=1}^T\frac{G_j^2}{H_j+\lambda}+\gamma T
Lt∗=−21j=1∑THj+λGj2+γT
所以分裂前后的损失函数的差值是
G
a
i
n
=
G
L
2
H
L
+
λ
+
G
R
2
H
R
+
λ
−
(
G
L
+
G
R
)
2
H
L
+
H
R
+
λ
−
γ
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
Gain=HL+λGL2+HR+λGR2−HL+HR+λ(GL+GR)2−γ
XGBoost采用最大化这个差值作为准则来进行决策树的构建,因此GBDT和XGBoost的区别可以总结成几个方面:
- GBDT是机器学习算法,XGBoost是该算法的一个具体实现
- XGBoost加入了正则项来控制模型复杂度,从而防止过拟合,提高模型的泛化能力
- XGBoost的代价函数用了二阶导数,GBDT只用了一阶导数
- 传统GBDT用CART作为基分类器,XGBoost支持多种基分类器,例如线性分类器
- 传统GBDT每轮迭代要用全部的数据,XGBoost只是采样
- 传统GBDT没有设计对缺失值进行处理的策略,XGBoost能够有设计这方面的策略,具体为:
-在某列特征上寻找分裂节点时,不会对缺失的样本进行遍历,只会对非缺失样本上的特征值进行遍历,这样减少了为稀疏离散特征寻找分裂节点的时间开销。
-另外,训练时为了保证完备性,对于含有缺失值的样本,会分别把它分配到左叶子节点和右叶子节点,然后再选择分裂后增益最大的那个方向,作为预测时特征值缺失样本的默认分支方向。
-如果训练集中没有缺失值,但是测试集中有,那么默认将缺失值划分到右叶子节点方向。 - 特征维度上的并行化。XGBoost预先将每个特征按特征值排好序,存储为块结构,分裂结点时可以采用多线程并行查找每个特征的最佳分割点,极大提升训练速度。利用列块进行并行计算:在我们训练过程中我们主要是做分支处理,分支处理就要对每一列(特征)找出适合的分裂点。通常来说,我们更青睐使用csc存储,这样我们就方便取出来。再者我们在分支的时候都会预先对数据按照其特征值进行排序。所以我们将数据按照列存储成一个数据块方便我们在分支的时候并行处理。所以我们要知道XGB的并行计算的粒度不在树上,而是在特征上,尤其是不同分支节点上(leaf-wise)。当然这也成为XGB的一个问题所在,需要额外的空间存储pre-sort的数据。而且每次分支后,我们都要找处落在下一个子节点上的样本,并组织好它。后来就有了LightGBM,下次我再将其整理出来。
XGBoost使用了二阶导,请问是谁对谁的二阶导?
参考上面的推导, g i g_i gi和 h i h_i hi分别是一阶导和二阶导数,表示的是loss函数 l ( y i , F t − 1 ( x i ) + f t ( x i ) ) l(y_i,F_{t-1}(x_i)+f_t(x_i)) l(yi,Ft−1(xi)+ft(xi))对当前这棵残差树 f t ( x i ) f_t(x_i) ft(xi)的一阶导和二阶导
给一个有n个样本,m维特征的数据集,如果用LR算法,那么梯度是几维?
对权重w有m维,对bias有1维,因此是m+1维。
给一个有n个样本,m维特征的数据集,如果用XGBoost算法,那么梯度是几维?
对于XGBoost,是需要对每个样本都算一遍梯度,参考 https://arxiv.org/pdf/1603.02754.pdf 里的图。因为GBDT求导是关于所有生成树的和,而所有生成树的和是一维的数值,又总共有n个sample,所以梯度是n维的。
gbdt多分类在n step的情况下训练了多少颗树
参考 https://zhuanlan.zhihu.com/p/91652813 , 将GBDT应用于二分类问题需要考虑逻辑回归模型,同理,对于GBDT多分类问题则需要考虑以下Softmax模型。对于k分类来说,每一轮的训练实际上是训练了k棵树去拟合softmax的每一个分支模型的负梯度。因此一共是k*n棵树
XGBoost算法防止过拟合的方法有哪些?
- 在目标函数中添加了正则化。叶子节点个数+叶子节点权重的L2正则化。
列抽样。训练时只使用一部分的特征。 - 子采样。每轮计算可以不使用全部样本,类似bagging。
- early stopping。如果经过固定的迭代次数后,并没有在验证集上改善性能,停止训练过程。
- shrinkage。调小学习率增加树的数量,为了给后面的训练留出更多的空间。
XGBoost中的一棵树的停止生长条件
- 当树达到最大深度时,停止建树,因为树的深度太深容易出现过拟合,这里需要设置一个超参数max_depth。
- 当新引入的一次分裂所带来的增益Gain<0时,放弃当前的分裂。这是训练损失和模型结构复杂度的博弈过程。
- 当引入一次分裂后,重新计算新生成的左、右两个叶子结点的样本权重和。如果任一个叶子结点的样本权重低于某一个阈值,也会放弃此次分裂。这涉及到一个超参数:最小样本权重和,是指如果一个叶子节点包含的样本数量太少也会放弃分裂,防止树分的太细。
XGBoost如何选择最佳分裂点?
XGBoost在训练前预先将特征按照特征值进行了排序,并存储为block结构,以后在结点分裂时可以重复使用该结构。
因此,可以采用特征并行的方法利用多个线程分别计算每个特征的最佳分割点,根据每次分裂后产生的增益,最终选择增益最大的那个特征的特征值作为最佳分裂点。如果在计算每个特征的最佳分割点时,对每个样本都进行遍历,计算复杂度会很大,这种全局扫描的方法并不适用大数据的场景。XGBoost还提供了一种直方图近似算法,对特征排序后仅选择常数个候选分裂位置作为候选分裂点,极大提升了结点分裂时的计算效率。
XGBoost可以做特征选择,它是如何评价特征重要性的?
特征重要性可以用来做模型可解释性,这在风控等领域是非常重要的方面。xgboost实现中Booster类get_score方法输出特征重要性,其中importance_type参数支持三种特征重要性的计算方法:
- importance_type=weight(默认值),特征重要性使用特征在所有树中作为划分属性的次数。
- importance_type=gain,特征重要性使用特征在作为划分属性时loss平均的降低量。
- importance_type=cover,特征重要性使用特征在作为划分属性时对样本的覆盖度。
总结过程中参考了以下书和博客,对一些内容作了展开,在此表示感谢:
- https://zhuanlan.zhihu.com/p/34534004
- 葫芦书
- https://zhuanlan.zhihu.com/p/81368182
- 知乎文章https://www.zhihu.com/question/63728763/answer/355319175
- 知乎文章https://zhuanlan.zhihu.com/p/36794802