首先明确一点,gbdt 无论用于分类还是回归一直都是使用的CART 回归树。不会因为我们所选择的任务是分类任务就选用分类树,这里面的核心是因为gbdt 每轮的训练是在上一轮的训练的残差基础之上进行训练的。这里的残差就是当前模型的负梯度值 。这个要求每轮迭代的时候,弱分类器的输出的结果相减是有意义的。残差相减是有意义的。
如果选用的弱分类器是分类树,类别相减是没有意义的。上一轮输出的是样本 x 属于 A类,本一轮训练输出的是样本 x 属于 B类。 A 和 B 很多时候甚至都没有比较的意义,A 类- B类是没有意义的。
因为我们真正关注的:1期望损失函数能够不断的减小。2是希望损失函数能够不断地尽可能快的减小。但如何使尽可能快的减小呐?
让损失函数沿着梯度方向的下降,这个事gbdt的核心,利用损失函数的负梯度在当前模型的值作为回归问题提升树算法中的残差的近似值去拟合一个回归树。gbdt每轮迭代的时候,都去拟合损失函数在当前模型下的负梯度。
这样每轮迭代的时候都能够让损失函数尽可能快的减小,尽可能的收敛到局部最优解或者全局最优解。(之前提升树模型中采用残差作为下一个树的拟合对象,也是由损失函数求导得出的,为了使损失函数尽快收敛到局部最优或者全局最优。)
GBDT如何选择特征:GDDT选择特征也就是CART Tree生成的过程,这里有个前提,gbdt的默认弱分类器就是选择CART TREE。当然也可以选择其他多分类器,选择的前提就是该弱分类器是低方差和高偏差。框架服从Boosting框架即可。
选择特征过程就是CART Tree的生成过程,首先在目前的所有M个特征选择一个特征j,作为二叉树的第一个节点,然后对特征j的值选择一个切分点m,一个样本的特征j的值如果小于m,则分为一类,大于则分为一类,其他节点生成过程是一样的。
-
- 如何衡量我们找到的特征 m和切分点 j 是最优的呢? 我们用定义一个函数 FindLossAndSplit 来展示一下求解过程:
1 def findLossAndSplit(x,y): 2 # 我们用 x 来表示训练数据 3 # 我们用 y 来表示训练数据的label 4 # x[i]表示训练数据的第i个特征 5 # x_i 表示第i个训练样本 6 7 # minLoss 表示最小的损失 8 minLoss = Integet.max_value 9 # feature 表示是训练的数据第几纬度的特征 10 feature = 0 11 # split 表示切分点的个数 12 split = 0 13 14 # M 表示 样本x的特征个数 15 for j in range(0,M): 16 # 该维特征下,特征值的每个切分点,这里具体的切分方式可以自己定义 17 for c in range(0,x[j]): 18 L = 0 19 # 第一类 20 R1 = {x|x[j] <= c} 21 # 第二类 22 R2 = {x|x[j] > c} 23 # 属于第一类样本的y值的平均值 24 y1 = ave{y|x 属于 R1} 25 # 属于第二类样本的y值的平均值 26 y2 = ave{y| x 属于 R2} 27 # 遍历所有的样本,找到 loss funtion 的值 28 for x_1 in all x 29 if x_1 属于 R1: 30 L += (y_1 - y1)^2 31 else: 32 L += (y_1 - y2)^2 33 if L < minLoss: 34 minLoss = L 35 feature = i 36 split = c 37 return minLoss,feature ,split
- gbdt 如何构建特征 ?
其实说gbdt 能够构建特征并非很准确,gbdt 本身是不能产生特征的,但是我们可以利用gbdt去产生特征的组合。在CTR预估中,工业界一般会采用逻辑回归去进行处理,在我的上一篇博文当中已经说过,逻辑回归本身是适合处理线性可分的数据,如果我们想让逻辑回归处理非线性的数据,其中一种方式便是组合不同特征,增强逻辑回归对非线性分布的拟合能力。
长久以来,我们都是通过人工的先验知识或者实验来获得有效的组合特征,但是很多时候,使用人工经验知识来组合特征过于耗费人力,造成了机器学习当中一个很奇特的现象:有多少人工就有多少智能。关键是这样通过人工去组合特征并不一定能够提升模型的效果。所以我们的从业者或者学界一直都有一个趋势便是通过算法自动,高效的寻找到有效的特征组合。Facebook 在2014年 发表的一篇论文便是这种尝试下的产物,利用gbdt去产生有效的特征组合,以便用于逻辑回归的训练,提升模型最终的效果。
图 2:用GBDT 构造特征
如图 2所示,我们 使用 GBDT 生成了两棵树,两颗树一共有五个叶子节点。我们将样本 X 输入到两颗树当中去,样本X 落在了第一棵树的第二个叶子节点,第二颗树的第一个叶子节点,于是我们便可以依次构建一个五纬的特征向量,每一个纬度代表了一个叶子节点,样本落在这个叶子节点上面的话那么值为1,没有落在该叶子节点的话,那么值为 0.
于是对于该样本,我们可以得到一个向量[0,1,0,1,0] 作为该样本的组合特征,和原来的特征一起输入到逻辑回归当中进行训练。实验证明这样会得到比较显著的效果提升。
- gbdt 如何构建特征 ?
其实说gbdt 能够构建特征并非很准确,gbdt 本身是不能产生特征的,但是我们可以利用gbdt去产生特征的组合。在CTR预估中,工业界一般会采用逻辑回归去进行处理,在我的上一篇博文当中已经说过,逻辑回归本身是适合处理线性可分的数据,如果我们想让逻辑回归处理非线性的数据,其中一种方式便是组合不同特征,增强逻辑回归对非线性分布的拟合能力。
长久以来,我们都是通过人工的先验知识或者实验来获得有效的组合特征,但是很多时候,使用人工经验知识来组合特征过于耗费人力,造成了机器学习当中一个很奇特的现象:有多少人工就有多少智能。关键是这样通过人工去组合特征并不一定能够提升模型的效果。所以我们的从业者或者学界一直都有一个趋势便是通过算法自动,高效的寻找到有效的特征组合。Facebook 在2014年 发表的一篇论文便是这种尝试下的产物,利用gbdt去产生有效的特征组合,以便用于逻辑回归的训练,提升模型最终的效果。
图 2:用GBDT 构造特征
如图 2所示,我们 使用 GBDT 生成了两棵树,两颗树一共有五个叶子节点。我们将样本 X 输入到两颗树当中去,样本X 落在了第一棵树的第二个叶子节点,第二颗树的第一个叶子节点,于是我们便可以依次构建一个五纬的特征向量,每一个纬度代表了一个叶子节点,样本落在这个叶子节点上面的话那么值为1,没有落在该叶子节点的话,那么值为 0.
于是对于该样本,我们可以得到一个向量[0,1,0,1,0] 作为该样本的组合特征,和原来的特征一起输入到逻辑回归当中进行训练。实验证明这样会得到比较显著的效果提升。
- GBDT 如何用于分类 ?
- GBDT 多分类举例说明
上面的理论阐述可能仍旧过于难懂,我们下面将拿Iris 数据集中的六个数据作为例子,来展示gbdt 多分类的过程。
-
-
图四 Iris 数据集样本编号 花萼长度(cm) 花萼宽度(cm) 花瓣长度(cm) 花瓣宽度 花的种类 1 5.1 3.5 1.4 0.2 山鸢尾 2 4.9 3.0 1.4 0.2 山鸢尾 3 7.0 3.2 4.7 1.4 杂色鸢尾 4 6.4 3.2 4.5 1.5 杂色鸢尾 5 6.3 3.3 6.0 2.5 维吉尼亚鸢尾 6 5.8 2.7 5.1 1.9 维吉尼亚鸢尾
-
这是一个有6个样本的三分类问题。我们需要根据这个花的花萼长度,花萼宽度,花瓣长度,花瓣宽度来判断这个花属于山鸢尾,杂色鸢尾,还是维吉尼亚鸢尾。具体应用到gbdt多分类算法上面。我们用一个三维向量来标志样本的label。[1,0,0] 表示样本属于山鸢尾,[0,1,0] 表示样本属于杂色鸢尾,[0,0,1] 表示属于维吉尼亚鸢尾。
gbdt 的多分类是针对每个类都独立训练一个 CART Tree。所以这里,我们将针对山鸢尾类别训练一个 CART Tree 1。杂色鸢尾训练一个 CART Tree 2 。维吉尼亚鸢尾训练一个CART Tree 3,这三个树相互独立。
我们以样本 1 为例。针对 CART Tree1 的训练样本是[5.1,3.5,1.4,0.2][5.1,3.5,1.4,0.2],label 是 1,最终输入到模型当中的为[5.1,3.5,1.4,0.2,1][5.1,3.5,1.4,0.2,1]。针对 CART Tree2 的训练样本也是[5.1,3.5,1.4,0.2][5.1,3.5,1.4,0.2],但是label 为 0,最终输入模型的为[5.1,3.5,1.4,0.2,0][5.1,3.5,1.4,0.2,0]. 针对 CART Tree 3的训练样本也是[5.1,3.5,1.4,0.2][5.1,3.5,1.4,0.2],label 也为0,最终输入模型当中的为[5.1,3.5,1.4,0.2,0][5.1,3.5,1.4,0.2,0].
下面我们来看 CART Tree1 是如何生成的,其他树 CART Tree2 , CART Tree 3的生成方式是一样的。CART Tree的生成过程是从这四个特征中找一个特征做为CART Tree1 的节点。比如花萼长度做为节点。6个样本当中花萼长度 大于5.1 cm的就是 A类,小于等于 5.1 cm 的是B类。生成的过程其实非常简单,问题 1.是哪个特征最合适? 2.是这个特征的什么特征值作为切分点? 即使我们已经确定了花萼长度做为节点。花萼长度本身也有很多值。在这里我们的方式是遍历所有的可能性,找到一个最好的特征和它对应的最优特征值可以让当前式子的值最小。
我们以第一个特征的第一个特征值为例。R1 为所有样本中花萼长度小于 5.1 cm 的样本集合,R2 为所有样本当中花萼长度大于等于 5.1cm 的样本集合。所以 R1={2}R1={2},R2={1,3,4,5,6}R2={1,3,4,5,6}.
图 5 节点分裂示意图
y1 为 R1 所有样本的label 的均值 1/1=11/1=1。y2 为 R2 所有样本的label 的均值 (1+0+0+0+0)/5=0.2(1+0+0+0+0)/5=0.2。
下面便开始针对所有的样本计算这个式子的值。样本1 属于 R2 计算的值为(1−0.2)2(1−0.2)2, 样本2 属于R1 计算的值为(1−1)2(1−1)2, 样本 3,4,5,6同理都是 属于 R2的 所以值是(0−0.2)2(0−0.2)2. 把这六个值加起来,便是山鸢尾类型在特征1的第一个特征值的损失值。这里算出来(1-0.2)^2+ (1-1)^2 + (0-0.2)^2+(0-0.2)^2+(0-0.2)^2 = 0.8.
接着我们计算第一个特征的第二个特征值,计算方式同上,R1 为所有样本中 花萼长度小于 4.9 cm 的样本集合,R2 为所有样本当中 花萼长度大于等于 4.9 cm 的样本集合.所以 R1={}R1={},R1={1,2,3,4,5,6}R1={1,2,3,4,5,6}. y1 为 R1 所有样本的label 的均值 = 0。y2 为 R2 所有样本的label 的均值 (1+1+0+0+0+0)/6=0.3333(1+1+0+0+0+0)/6=0.3333。
图 6 第一个特征的第二个特侦值的节点分裂情况
我们需要针对所有的样本,样本1 属于 R2, 计算的值为(1−0.333)2(1−0.333)2, 样本2 属于R2 ,计算的值为(1−0.333)2(1−0.333)2, 样本 3,4,5,6同理都是 属于 R2的, 所以值是(0−0.333)2(0−0.333)2. 把这六个值加起来山鸢尾类型在特征1 的第二个特征值的损失值。这里算出来 (1-0.333)^2+ (1-0.333)^2 + (0-0.333)^2+(0-0.333)^2+(0-0.333)^2 = 2.1333. 这里的损失值大于 特征一的第一个特征值的损失值,所以我们不取这个特征的特征值。
图 7 所有情况说明
这样我们可以遍历所有特征的所有特征值,找到让这个式子最小的特征以及其对应的特征值,一共有24种情况,4个特征*每个特征有6个特征值。在这里我们算出来让这个式子最小的特征花萼长度,特征值为5.1 cm。这个时候损失函数最小为 0.8。
1 # 定义训练数据 2 train_data = [[5.1,3.5,1.4,0.2],[4.9,3.0,1.4,0.2],[7.0,3.2,4.7,1.4],[6.4,3.2,4.5,1.5],[6.3,3.3,6.0,2.5],[5.8,2.7,5.1,1.9]] 4 # 定义label 5 label_data = [[1,0,0],[1,0,0],[0,1,0],[0,1,0],[0,0,1],[0,0,1]] 6 # index 表示的第几类 7 def findBestLossAndSplit(train_data,label_data,index): 8 sample_numbers = len(label_data) 9 feature_numbers = len(train_data[0]) 10 current_label = [] 12 # define the minLoss 13 minLoss = 10000000 15 # feature represents the dimensions of the feature 16 feature = 0 18 # split represents the detail split value 19 split = 0 21 # get current label 22 for label_index in range(0,len(label_data)): 23 current_label.append(label_data[label_index][index]) 24 25 # trans all features 26 for feature_index in range(0,feature_numbers): 27 ## current feature value 28 current_value = [] 29 30 for sample_index in range(0,sample_numbers): 31 current_value.append(train_data[sample_index][feature_index]) 32 L = 0 33 ## different split value 34 print current_value 35 for index in range(0,len(current_value)): 36 R1 = [] 37 R2 = [] 38 y1 = 0 39 y2 = 0 41 for index_1 in range(0,len(current_value)): 42 if current_value[index_1] < current_value[index]: 43 R1.append(index_1) 44 else: 45 R2.append(index_1) 46 47 ## calculate the samples for first class 48 sum_y = 0 49 for index_R1 in R1: 50 sum_y += current_label[index_R1] 51 if len(R1) != 0: 52 y1 = float(sum_y) / float(len(R1)) 53 else: 54 y1 = 0 55 56 ## calculate the samples for second class 57 sum_y = 0 58 for index_R2 in R2: 59 sum_y += current_label[index_R2] 60 if len(R2) != 0: 61 y2 = float(sum_y) / float(len(R2)) 62 else: 63 y2 = 0 64 65 ## trans all samples to find minium loss and best split 66 for index_2 in range(0,len(current_value)): 67 if index_2 in R1: 68 L += float((current_label[index_2]-y1))*float((current_label[index_2]-y1)) 69 else: 70 L += float((current_label[index_2]-y2))*float((current_label[index_2]-y2)) 71 72 if L < minLoss: 73 feature = feature_index 74 split = current_value[index] 75 minLoss = L 76 print "minLoss" 77 print minLoss 78 print "split" 79 print split 80 print "feature" 81 print feature 82 return minLoss,split,feature 83 84 findBestLossAndSplit(train_data,label_data,0)
- 3 总结
目前,我们总结了 gbdt 的算法的流程,gbdt如何选择特征,如何产生特征的组合,以及gbdt 如何用于分类,这个目前可以认为是gbdt 最经常问到的四个部分。至于剩余的问题,因为篇幅的问题,我们准备再开一个篇幅来进行总结。
GBDT是一个加和模型,
GBDT步骤:
GBDT思想用于构造特征:
输入一个样本之后一定会落在一个叶子节点上,将落在第一个树上叶子节点上标记为1,其它叶子结点赋值为0,并将其残差作为输入加入第二个树,会落在其中一个叶子节点上,从而可得出特征的组合,再将其特征放入LR中进行拟合,从而产生比GBDT效果更好的结果,这就叫做模型融合。