(GBDT是通过梯度下降(作为残差 的近似)更新的决策树集成的boosting模型)
首先明确一点,gbdt 无论用于分类还是回归一直都是使用的CART 回归树。不会因为我们所选择的任务是分类任务就选用分类树,这里面的核心是因为gbdt 每轮的训练是在上一轮的训练的残差基础之上进行训练的。这里的残差就是当前模型的负梯度值 。这个要求每轮迭代的时候,弱分类器的输出的结果相减是有意义的。残差相减是有意义的。
如果选用的弱分类器是分类树,类别相减是没有意义的。上一轮输出的是样本 x 属于 A类,本一轮训练输出的是样本 x 属于 B类。 A 和 B 很多时候甚至都没有比较的意义,A 类- B类是没有意义的。
我们具体到分类这个任务上面来,我们假设样本 X 总共有 K类。来了一个样本 x,我们需要使用gbdt来判断 x 属于样本的哪一类。
图三 gbdt 多分类算法流程
第一步 我们在训练的时候,是针对样本 X 每个可能的类都训练一个分类回归树。举例说明,目前样本有三类,也就是 K = 3。样本 x 属于 第二类。那么针对该样本 x 的分类结果,其实我们可以用一个 三维向量 [0,1,0] 来表示。0表示样本不属于该类,1表示样本属于该类。由于样本已经属于第二类了,所以第二类对应的向量维度为1,其他位置为0。
针对样本有 三类的情况,我们实质上是在每轮的训练的时候是同时训练三颗树。第一颗树针对样本x的第一类,输入为(x,0)(x,0)。第二颗树输入针对 样本x 的第二类,输入为(x,1)(x,1)。第三颗树针对样本x 的第三类,输入为(x,0)(x,0)
在这里每颗树的训练过程其实就是就是我们之前已经提到过的CATR TREE 的生成过程。在此处我们参照之前的生成树的程序 即可以就解出三颗树,以及三颗树对x 类别的预测值f1(x),f2(x),f3(x)f1(x),f2(x),f3(x)。那么在此类训练中,我们仿照多分类的逻辑回归 ,使用softmax 来产生概率,则属于类别 1 的概率
p1=exp(f1(x))/∑k=13exp(fk(x))p1=exp(f1(x))/∑k=13exp(fk(x))
并且我们我们可以针对类别1 求出 残差y11(x)=0−p1(x)y11(x)=0−p1(x);类别2 求出残差y22(x)=1−p2(x)y22(x)=1−p2(x);类别3 求出残差y33(x)=0−p3(x)y33(x)=0−p3(x).
然后开始第二轮训练 针对第一类 输入为(x,y11(x)y11(x)), 针对第二类输入为(x,y22(x))y22(x)), 针对 第三类输入为 (x,y33(x)y33(x)).继续训练出三颗树。一直迭代M轮。每轮构建 3颗树。
所以当K =3。我们其实应该有三个式子
F1M(x)=∑m=1MC1m^I(xϵR1m)F1M(x)=∑m=1MC1m^I(xϵR1m)
F2M(x)=∑m=1MC2m^I(xϵR2m)F2M(x)=∑m=1MC2m^I(xϵR2m)
F3M(x)=∑m=1MC3m^I(xϵR3m)F3M(x)=∑m=1MC3m^I(xϵR3m)
当训练完毕以后,新来一个样本 x1 ,我们需要预测该样本的类别的时候,便可以有这三个式子产生三个值,f1(x),f2(x),f3(x)f1(x),f2(x),f3(x)。样本属于 某个类别c的概率为
pc=exp(fc(x))/∑k=13exp(fk(x))pc=exp(fc(x))/∑k=13exp(fk(x))
上面的理论阐述可能仍旧过于难懂,我们下面将拿Iris 数据集中的六个数据作为例子,来展示gbdt 多分类的过程。
样本编号 | 花萼长度(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-0.2)^2= 0.84
接着我们计算第一个特征的第二个特征值,计算方式同上,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 +(0-0.333)^2 = 2.244189. 这里的损失值大于 特征一的第一个特征值的损失值,所以我们不取这个特征的特征值。
图 7 所有情况说明
这样我们可以遍历所有特征的所有特征值,找到让这个式子最小的特征以及其对应的特征值,一共有24种情况,4个特征*每个特征有6个特征值。在这里我们算出来让这个式子最小的特征花萼长度,特征值为5.1 cm。这个时候损失函数最小为 0.8。
于是我们的预测函数此时也可以得到:
f(x)=∑xϵR1y1∗I(xϵR1)+∑xϵR2y2∗I(xϵR2)f(x)=∑xϵR1y1∗I(xϵR1)+∑xϵR2y2∗I(xϵR2)
此处 R1 = {2},R2 = {1,3,4,5,6},y1 = 1,y2 = 0.2。训练完以后的最终式子为
f1(x)=∑xϵR11∗I(xϵR1)+∑xϵR20.2∗I(xϵR2)f1(x)=∑xϵR11∗I(xϵR1)+∑xϵR20.2∗I(xϵR2)
借由这个式子,我们得到对样本属于类别1 的预测值 f1(x)=1+0.2∗5=2f1(x)=1+0.2∗5=2。同理我们可以得到对样本属于类别2,3的预测值f2(x)f2(x),f3(x)f3(x).样本属于类别1的概率 即为
p1=exp(f1(x))/∑k=13exp(fk(x))p1=exp(f1(x))/∑k=13exp(fk(x))
下面我们用代码来实现整个找特征的过程,大家可以自己再对照代码看看。
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]] 3 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 = [] 11 12 # define the minLoss 13 minLoss = 10000000 14 15 # feature represents the dimensions of the feature 16 feature = 0 17 18 # split represents the detail split value 19 split = 0 20 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 40 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)