LESSON 12.1&12.2 梯度提升树的基本思想与实现&迭代过程中的参数(1):GBDT的初始化与多分类

1966-朱同学

发表文章数:241

热门标签

, , ,
首页 » 数据结构 » 正文

目录
一 梯度提升树的基本思想
 1 梯度提升树 pk AdaBoost
 2 GradientBoosting回归与分类的实现
二 梯度提升树的参数
 1 迭代过程
   1.1 初始预测结果 𝐻0 的设置
   1.2 使用回归器完成分类任务
   1.3 GBDT的8种损失函数
 2 弱评估器结构
   2.1 梯度提升树种的弱评估器复杂度
   2.2 弗里德曼均方误差
 3 梯度提升树的提前停止机制
 4 梯度提升树的袋外数据
 5 缺失参数class_weight与n_jobs
三 梯度提升树的参数空间与自动优化
 1 GBDT的参数空间
 2 基于TPE对GBDT进行优化
四 原理进阶:梯度提升回归树的求解流程
 1 GBDT的基本数学流程
 2 初始化H0过程中的常数C是什么?
 3 伪残差、残差与梯度有什么关系?
 4 证明:拟合伪残差的合理性

一 梯度提升树的基本思想

1 梯度提升树 pk AdaBoost

梯度提升树(Gradient Boosting Decision Tree,GBDT)是提升法中的代表性算法,它即是当代强力的XGBoost、LGBM等算法的基石,也是工业界应用最多、在实际场景中表现最稳定的机器学习算法之一。在最初被提出来时,GBDT被写作梯度提升机器(Gradient Boosting Machine,GBM),它融合了Bagging与Boosting的思想、扬长避短,可以接受各类弱评估器作为输入,在后来弱评估器基本被定义为决策树后,才慢慢改名叫做梯度提升树。受Boosting算法首个发扬光大之作AdaBoost的启发,GBDT中自然也包含Boosting三要素:

  • 损失函数 𝐿(𝑥,𝑦) :用以衡量模型预测结果与真实结果的差异
  • 弱评估器 𝑓(𝑥) :(一般为)决策树,不同的boosting算法使用不同的建树过程
  • 综合集成结果 𝐻(𝑥) :即集成算法具体如何输出集成结果

同时,GBDT也遵循boosting算法的基本流程进行建模:LESSON 12.1&12.2 梯度提升树的基本思想与实现&迭代过程中的参数(1):GBDT的初始化与多分类
但与AdaBoost不同的是,GBDT在整体建树过程中做出了以下几个关键的改变:

  • 弱评估器

GBDT的弱评估器输出类型不再与整体集成算法输出类型一致。对于AdaBoost或随机森林算法来说,当集成算法执行的是回归任务时,弱评估器也是回归器,当集成算法执行分类任务时,弱评估器也是分类器。但对于GBDT而言,无论GBDT整体在执行回归/分类/排序任务,弱评估器一定是回归器。GBDT通过sigmoid或softmax函数输出具体的分类结果,但实际弱评估器一定是回归器。

  • 损失函数 𝐿(𝑥,𝑦)

在GBDT当中,损失函数范围不再局限于固定或单一的某个损失函数,而从数学原理上推广到了任意可微的函数。因此GBDT算法中可选的损失函数非常多,GBDT实际计算的数学过程也与损失函数的表达式无关。

  • 拟合残差

GBDT依然自适应调整弱评估器的构建,但却不像AdaBoost一样通过调整数据分布来间接影响后续弱评估器。相对的,GBDT通过修改后续弱评估器的拟合目标来直接影响后续弱评估器的结构
具体地来说,在AdaBoost当中,每次建立弱评估器之前需要修改样本权重,且用于建立弱评估器的是样本

X

X

X以及对应的

y

y

y,在GBDT当中,我们不修改样本权重,但每次用于建立弱评估器的是样本

X

X

X以及当下集成输出

H

(

x

i

)

H(x_i)

H(xi)与真实标签

y

y

y的差异(

y

H

(

x

i

)

y – H(x_i)

yH(xi))。这个差异在数学上被称之为残差(Residual),因此GBDT不修改样本权重,而是通过拟合残差来影响后续弱评估器结构

-抽样思想

GBDT加入了随机森林中随机抽样的思想,在每次建树之前,允许对样本和特征进行抽样来增大弱评估器之间的独立性(也因此可以有袋外数据集)。虽然Boosting算法不会大规模地依赖于类似于Bagging的方式来降低方差,但由于Boosting算法的输出结果是弱评估器结果的加权求和,因此Boosting原则上也可以获得由“平均”带来的小方差红利。当弱评估器表现不太稳定时,采用与随机森林相似的方式可以进一步增加Boosting算法的稳定性。

除了以上四个改变之外,GBDT的求解流程与AdaBoost大致相同。因此,如果你对AdaBoost的流程相当熟悉,GBDT的建模过程并不难懂。sklearn当中集成了GBDT分类与GBDT回归,我们使用如下两个类来调用它们:

class sklearn.ensemble.GradientBoostingClassifier(*, loss=‘deviance’, learning_rate=0.1, n_estimators=100, subsample=1.0, criterion=‘friedman_mse’, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_depth=3, min_impurity_decrease=0.0, init=None, random_state=None, max_features=None, verbose=0, max_leaf_nodes=None, warm_start=False, validation_fraction=0.1, n_iter_no_change=None, tol=0.0001, ccp_alpha=0.0)

class sklearn.ensemble.GradientBoostingRegressor(*, loss=‘squared_error’, learning_rate=0.1, n_estimators=100, subsample=1.0, criterion=‘friedman_mse’, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_depth=3, min_impurity_decrease=0.0, init=None, random_state=None, max_features=None, alpha=0.9, verbose=0, max_leaf_nodes=None, warm_start=False, validation_fraction=0.1, n_iter_no_change=None, tol=0.0001, ccp_alpha=0.0)

比起AdaBoost,GBDT的超参数数量增加了不少,但与其他集成算法一样,GBDT回归器与GBDT分类器的超参数高度一致(实际上,对GBDT来说,是完全一致)。在课程当中,我们将重点介绍GBDT独有的参数,以及GBDT分类器与GBDT回归器中表现不一致的参数。

2 GradientBoosting的实现

import matplotlib.pyplot as plt
from sklearn.ensemble import GradientBoostingRegressor as GBR
from sklearn.ensemble import GradientBoostingClassifier as GBC
from sklearn.ensemble import AdaBoostRegressor as ABR
from sklearn.ensemble import RandomForestRegressor as RFR
from sklearn.model_selection import cross_validate, KFold

data = pd.read_csv(r"D:/Pythonwork/2021ML/PART 2 Ensembles/datasets/House Price/train_encode.csv",index_col=0)
data.head()

LESSON 12.1&12.2 梯度提升树的基本思想与实现&迭代过程中的参数(1):GBDT的初始化与多分类

#回归数据
X = data.iloc[:,:-1]
y = data.iloc[:,-1]
X.shape
#(1460, 80)
y.describe()
#count      1460.000000
#mean     180921.195890
#std       79442.502883
#min       34900.000000
#25%      129975.000000
#50%      163000.000000
#75%      214000.000000
#max      755000.000000
#Name: SalePrice, dtype: float64

#定义所需的交叉验证方式
cv = KFold(n_splits=5,shuffle=True,random_state=1412)

def RMSE(result,name):
    return abs(result[name].mean())
  • 梯度提升回归树
gbr = GBR(random_state=1412) #实例化
result_gbdt = cross_validate(gbr,X,y,cv=cv
                             ,scoring="neg_root_mean_squared_error" #负根均方误差
                             ,return_train_score=True
                             ,verbose=True
                             ,n_jobs=-1)
#[Parallel(n_jobs=-1)]: Using backend LokyBackend with 16 concurrent workers.
#[Parallel(n_jobs=-1)]: Done   5 out of   5 | elapsed:    0.4s finished
RMSE(result_gbdt,"train_score")
#13990.790813889864
RMSE(result_gbdt,"test_score")
#28783.954343252786
  • 梯度提升回归与其他算法的对比
modelname = ["GBDT","RF","AdaBoost","RF-TPE","Ada-TPE"]

models = [GBR(random_state=1412)
         ,RFR(random_state=1412,n_jobs=-1)
         ,ABR(random_state=1412)
         ,RFR(n_estimators=89, max_depth=22, max_features=14, min_impurity_decrease=0
              ,random_state=1412, verbose=False, n_jobs=-1)
         ,ABR(n_estimators=39, learning_rate=0.94,loss="exponential"
              ,random_state=1412)]

colors = ["green","gray","orange","red","blue"]

for name,model in zip(modelname,models):
    start = time.time()
    result = cross_validate(model,X,y,cv=cv,scoring="neg_root_mean_squared_error"
                            ,return_train_score=True
                            ,verbose=False
                            ,n_jobs=-1)
    end = time.time()-start
    print(name)
    print("/t train_score:{:.3f}".format(RMSE(result,"train_score")))
    print("/t test_score:{:.3f}".format(RMSE(result,"test_score")))
    print("/t time:{:.2f}s".format(end))
    print("/n")
#GBDT
#    train_score:13990.791
#    test_score:28783.954
#    time:0.49s
#RF
#    train_score:11177.272
#    test_score:30571.267
#    time:0.70s
#AdaBoost
#    train_score:27062.107
#    test_score:35345.931
#    time:0.25s
#RF-TPE
#    train_score:11208.818
#    test_score:28346.673
#    time:0.22s
#Ada-TPE
#    train_score:27401.542
#    test_score:35169.730
#    time:0.23s

LESSON 12.1&12.2 梯度提升树的基本思想与实现&迭代过程中的参数(1):GBDT的初始化与多分类
先来看默认参数下所有算法的表现。当不进行调参时,随机森林的运行时间最长、AdaBoost最快,GBDT居中,但考虑到AdaBoost的n_estimators参数的默认值为50,而GBDT和随机森林的n_estimators默认值都为100,可以认为AdaBoost的运行速度与GBDT相差不多从结果来看,未调参状态下GBDT的结果是最好的,其结果甚至与经过TPE精密调参后的随机森林结果相差不多,而AdaBoost经过调参后没有太多改变,可以说AdaBoost极其缺乏调参空间、并且学习能力严重不足

基于以上信息,我们可以观察三个算法的过拟合情况:

xaxis = range(1,6)
plt.figure(figsize=(8,6),dpi=80)

for name,model,color in zip(modelname[:3],models[:3],colors[:3]):
    result = cross_validate(model,X,y,cv=cv,scoring="neg_root_mean_squared_error"
                            ,return_train_score=True
                            ,verbose=False
                            ,n_jobs=-1)
    plt.plot(xaxis,abs(result["train_score"]), color=color, label = name+"_Train")
    plt.plot(xaxis,abs(result["test_score"]), color=color, linestyle="--",label = name+"_Test")

plt.xticks([1,2,3,4,5])
plt.xlabel("CVcounts",fontsize=16)
plt.ylabel("RMSE",fontsize=16)
plt.title("RF vs GBDT vs AdaBoost")
plt.legend()
plt.show()

LESSON 12.1&12.2 梯度提升树的基本思想与实现&迭代过程中的参数(1):GBDT的初始化与多分类
不难发现,AdaBoost是过拟合程度最轻的,这也反映出它没有调参空间的事实,而GBDT与随机森林过拟合程度差不多,不过GBDT的过拟合程度相对较轻一些,这是因为Boosting算法的原理决定了Boosting算法更加不容易过拟合,这一点在后续讲解GBDT的参数时我们会详细说明。

我们可以绘制随机森林调参前后、以及AdaBoost调参前后的结果对比:

xaxis = range(1,6)
plt.figure(figsize=(8,6),dpi=80)

for name,model,color in zip(modelname[2:5:2],models[2:5:2],colors[2:5:2]):
    result = cross_validate(model,X,y,cv=cv,scoring="neg_root_mean_squared_error"
                            ,return_train_score=True
                            ,verbose=False
                            ,n_jobs=-1)
    plt.plot(xaxis,abs(result["train_score"]), color=color, label = name+"_Train")
    plt.plot(xaxis,abs(result["test_score"]), color=color, linestyle="--",label = name+"_Test")

plt.xticks([1,2,3,4,5])
plt.xlabel("CVcounts",fontsize=16)
plt.ylabel("RMSE",fontsize=16)
plt.title("AdaBoost vs AdaBoost-TPE")
plt.legend()
plt.show()

LESSON 12.1&12.2 梯度提升树的基本思想与实现&迭代过程中的参数(1):GBDT的初始化与多分类

xaxis = range(1,6)
plt.figure(figsize=(8,6),dpi=80)

for name,model,color in zip(modelname[1:4:2],models[1:4:2],colors[1:4:2]):
    result = cross_validate(model,X,y,cv=cv,scoring="neg_root_mean_squared_error"
                            ,return_train_score=True
                            ,verbose=False
                            ,n_jobs=-1)
    plt.plot(xaxis,abs(result["train_score"]), color=color, label = name+"_Train")
    plt.plot(xaxis,abs(result["test_score"]), color=color, linestyle="--",label = name+"_Test")

plt.xticks([1,2,3,4,5])
plt.xlabel("CVcounts",fontsize=16)
plt.ylabel("RMSE",fontsize=16)
plt.title("RF vs RF-TPE")
plt.legend()
plt.show()

LESSON 12.1&12.2 梯度提升树的基本思想与实现&迭代过程中的参数(1):GBDT的初始化与多分类
不难发现,AdaBoost在经过精密调参后,并没有太多改变,而随机森林调参后过拟合程度明显降低,测试集上的结果明显提升,这是随机森林在潜力和根本原则上都比AdaBoost要强大的表现。那GBDT的表现如何呢?GBDT在默认参数上的结果接近经过TPE调参后的随机森林,我们来看看这两个算法的对比:

xaxis = range(1,6)
plt.figure(figsize=(8,6),dpi=80)

for name,model,color in zip(modelname[:5:3],models[:5:3],colors[:5:3]):
    result = cross_validate(model,X,y,cv=cv,scoring="neg_root_mean_squared_error"
                            ,return_train_score=True
                            ,verbose=False
                            ,n_jobs=-1)
    plt.plot(xaxis,abs(result["train_score"]), color=color, label = name+"_Train")
    plt.plot(xaxis,abs(result["test_score"]), color=color, linestyle="--",label = name+"_Test")

plt.xticks([1,2,3,4,5])
plt.xlabel("CVcounts",fontsize=16)
plt.ylabel("RMSE",fontsize=16)
plt.title("GBDT vs RF-TPE")
plt.legend()
plt.show()

LESSON 12.1&12.2 梯度提升树的基本思想与实现&迭代过程中的参数(1):GBDT的初始化与多分类
不难发现,GBDT的过拟合程度是轻于优化后的随机森林的。并且,在大部分交叉验证的结果下,GBDT的效果都接近或好于优化后的随机森林。在cv=2时GBDT的表现远不如森林,一次糟糕的表现拉低了GBDT的整体表现,否则GBDT可能在默认参数上表现出比优化后的随机森林更好的结果。如果我们可以通过调参优化让GBDT的表现更加稳定,GBDT可能会出现惊人的表现。

  • 梯度提升树分类
#分类数据
X_clf = data.iloc[:,:-2]
y_clf = data.iloc[:,-2]

np.unique(y_clf) #6分类
#array([0., 1., 2., 3., 4., 5.])

#GBDT分类的实现
clf = GBC(random_state=1412) #实例化
cv = KFold(n_splits=5,shuffle=True,random_state=1412)
result_clf = cross_validate(clf,X_clf,y_clf,cv=cv
                            ,return_train_score=True
                            ,verbose=True
                            ,n_jobs=-1)                        
#[Parallel(n_jobs=-1)]: Using backend LokyBackend with 16 concurrent workers.
#[Parallel(n_jobs=-1)]: Done   5 out of   5 | elapsed:    5.5s finished
result_clf
#{'fit_time': array([3.47425294, 3.40723777, 3.38023067, 3.39023256, 3.41823983]),
#'score_time': array([0.0040009 , 0.00400043, 0.00400066, 0.00300074, 0.00400186]),
#'test_score': array([0.89726027, 0.8869863 , 0.90410959, 0.8869863 , 0.90753425]),
#'train_score': array([0.99058219, 0.99315068, 0.99229452, 0.99143836, 0.99143836])}
result_clf["train_score"].mean()
#0.9919520547945206
result_clf["test_score"].mean()
#0.8979452054794521

二 梯度提升树的参数

class sklearn.ensemble.GradientBoostingClassifier(*, loss=‘deviance’, learning_rate=0.1, n_estimators=100, subsample=1.0, criterion=‘friedman_mse’, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_depth=3, min_impurity_decrease=0.0, init=None, random_state=None, max_features=None, verbose=0, max_leaf_nodes=None, warm_start=False, validation_fraction=0.1, n_iter_no_change=None, tol=0.0001, ccp_alpha=0.0)

class sklearn.ensemble.GradientBoostingRegressor(*, loss=‘squared_error’, learning_rate=0.1, n_estimators=100, subsample=1.0, criterion=‘friedman_mse’, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_depth=3, min_impurity_decrease=0.0, init=None, random_state=None, max_features=None, alpha=0.9, verbose=0, max_leaf_nodes=None, warm_start=False, validation_fraction=0.1, n_iter_no_change=None, tol=0.0001, ccp_alpha=0.0)

与随机森林一样,由于GBDT超参数数量较多,因此我们可以将GBDT的参数分为以下5大类别,其中标注为绿色的参数包括了我们未曾学过的知识、需要重点讲解:LESSON 12.1&12.2 梯度提升树的基本思想与实现&迭代过程中的参数(1):GBDT的初始化与多分类

1 迭代过程

之前我们提到过,GBDT的整体建模流程与AdaBoost高度相似,因此GBDT当中也有设置具体迭代次数(弱评估器次数)的参数n_estimators与学习率参数learning_rate,这两个参数的含义、以及对集成算法的影响与AdaBoost当中完全一致。

具体地来说,对于样本

x

i

x_i

xi,集成算法当中一共有

T

T

T棵树,则参数n_estimators的取值为T。假设现在正在建立第

t

t

t个弱评估器,则则第

t

t

t个弱评估器上

x

i

x_i

xi的结果可以表示为

f

t

(

x

i

)

f_t(x_i)

ft(xi)。假设整个Boosting算法对样本

x

i

x_i

xi输出的结果为

H

(

x

i

)

H(x_i)

H(xi),则该结果一般可以被表示为t=1~t=T过程当中,所有弱评估器结果的加权求和:

H

(

x

i

)

=

t

=

1

T

ϕ

t

f

t

(

x

i

)

H(x_i) = /sum_{t=1}^/boldsymbol{/color{red}T}/phi_tf_t(x_i)

H(xi)=t=1Tϕtft(xi)

其中,

ϕ

t

/phi_t

ϕt为第t棵树的权重。对于第

t

t

t次迭代来说,则有:

H

t

(

x

i

)

=

H

t

1

(

x

i

)

+

ϕ

t

f

t

(

x

i

)

H_t(x_i) = H_{t-1}(x_i) + /phi_tf_t(x_i)

Ht(xi)=Ht1(xi)+ϕtft(xi)

在这个一般过程中,每次将本轮建好的决策树加入之前的建树结果时,可以在权重

ϕ

/phi

ϕ前面增加参数

η

/color{red}/eta

η,表示为第t棵树加入整体集成算法时的学习率,对标参数learning_rate

H

t

(

x

i

)

=

H

t

1

(

x

i

)

+

η

ϕ

t

f

t

(

x

i

)

H_t(x_i) = H_{t-1}(x_i) + /boldsymbol{/color{red}/eta} /phi_tf_t(x_i)

Ht(xi)=Ht1(xi)+ηϕtft(xi)

该学习率参数控制Boosting集成过程中

H

(

x

i

)

H(x_i)

H(xi)的增长速度,是相当关键的参数。当学习率很大时,

H

(

x

i

)

H(x_i)

H(xi)增长得更快,我们所需的n_estimators更少,当学习率较小时,

H

(

x

i

)

H(x_i)

H(xi)增长较慢,我们所需的n_estimators就更多,因此boosting算法往往会需要在n_estimatorslearning_rate中做出权衡。

这两个参数的使用方法与AdaBoost中也完全一致,故此处不再赘述,后续我们会直接使用这两个参数进行调参。

1.1 初始预测结果

H

0

H_0

H0的设置

在上述过程中,我们建立第一个弱评估器时有:

H

1

(

x

i

)

=

H

0

(

x

i

)

+

ϕ

1

f

1

(

x

i

)

H_1(x_i) = H_{0}(x_i) + /phi_1f_1(x_i)

H1(xi)=H0(xi)+ϕ1f1(xi)

由于没有第0棵树的存在,因此

H

0

(

x

i

)

H_0(x_i)

H0(xi)的值在数学过程及算法具体实现过程中都需要进行单独的确定,这一确定过程由参数init确定。

  • 参数init:输入计算初始预测结果

    H

    0

    H_0

    H0的估计器对象。

在该参数中,可以输入任意评估器、字符串"zero"、或者None对象,默认为None对象。

当输入任意评估器时,评估器必须要具备fit以及predict_proba功能,即我们可以使用决策树、逻辑回归等可以输出概率的模型。如果输出一个已经训练过、且精细化调参后的模型,将会给GBDT树打下坚实的基础。

填写为字符串"zero",则代表令

H

0

=

0

H_0 = 0

H0=0来开始迭代。

不填写,或填写为None对象,sklearn则会自动选择类DummyEstimator中的某种默认方式进行预测作为

H

0

H_0

H0的结果。DummyEstimator类是sklearn中设置的使用超简单规则进行预测的类,其中最常见的规则是直接从训练集标签中随机抽样出结果作为预测标签,也有选择众数作为预测标签等选项。

一般在GBDT类的使用过程中,我们不会主动调节参数init,但是当我们有足够的算力支持超参数搜索时,我们可以在init上进行选择。

from sklearn.tree import DecisionTreeRegressor as DTR
tree_reg = DTR(random_state=1412)
rf = RFR(n_estimators=89, max_depth=22, max_features=14, min_impurity_decrease=0
              ,random_state=1412, verbose=False, n_jobs=-1)

for init in [tree_reg,rf,"zero",None]:
    reg = GBR(init = init,random_state=1412)
    cv = KFold(n_splits=5,shuffle=True,random_state=1412)
    result_reg = cross_validate(reg,X,y,cv=cv,scoring="neg_root_mean_squared_error"
                                ,return_train_score=True
                                ,verbose=False
                                ,n_jobs=-1)
    print("/n")
    print(RMSE(result_reg,"train_score"))
    print(RMSE(result_reg,"test_score"))
#0.0
#42065.93924112058
#单颗决策树
#
#5669.291478825804
#27171.244181270857
#随机森林
#
#13990.791639702458
#28739.882050269225
#zero
#
#13990.790813889864
#28783.954343252786
#none

不难发现,初始参数的具体输入会对模型的最终结果造成巨大影响,在init中输入训练好的模型会加重GBDT的过拟合,但同时也可能得到更好的测试集结果。我们甚至可以无限套娃,让init参数中输入被训练好的GBDT模型,当然,这样做的结果往往是过拟合被放大到无法挽回了。通常来说,我们还是会选择"zero"作为init的输入。

与参数init相对的属性就是init_,当模型被拟合完毕之后,我们可以使用该属性来返回输出

H

0

H_0

H0的评估器对象。

reg = GBR(init = None,random_state=1412)
reg.fit(X,y).init_ #返回sklearn中的玩具评估器DummyRegressor
#DummyRegressor()
reg = GBR(init = rf,random_state=1412)
reg.fit(X,y).init_
#RandomForestRegressor(max_depth=22, max_features=14, min_impurity_decrease=0,
#                      n_estimators=89, n_jobs=-1, random_state=1412,
#                      verbose=False)

当然,在init中的值是我们自己输入的值的情况下,属性init_略显鸡肋,但我们或许会预见需要该属性的具体场景,例如在建模过程中进行监控打印时、或在大量初始化模型中选择最佳初始化模型时。

1.2 使用回归器完成分类任务

GBDT与AdaBoost及随机森林的关键区别之一,是GBDT中所有的弱评估器都是回归树,因此在实际调用梯度提升树完成分类任务时,需要softmax函数或sigmoid函数对回归树输出的结果进行处理。因此,对于二分类情况来说,集成算法对样本

x

i

x_i

xi输出的结果为:

H

(

x

i

)

=

t

=

1

T

ϕ

t

f

t

(

x

i

)

H(x_i) = /sum_{t=1}^/boldsymbol{/color{red}T}/phi_tf_t(x_i)

H(xi)=t=1Tϕtft(xi)

p

(

y

^

i

=

1

x

i

)

=

σ

(

H

(

x

i

)

)

p(/hat{y}_i = 1 |x_i) = /sigma(H(x_i))

p(y^i=1xi)=σ(H(xi))

其中

σ

/sigma

σ是sigmoid函数,当

p

(

y

^

i

=

1

x

i

)

p(/hat{y}_i = 1 |x_i)

p(y^i=1xi)大于0.5时,样本

x

i

x_i

xi的预测类别为1,反之则为0。

而对多分类来说,情况就比较复杂了。在讲解AdaBoost时我们说明过,二分类当中我们只需求解一个概率

P

(

Y

=

1

)

P(Y=1)

P(Y=1),因为

P

(

Y

=

0

)

=

1

P

(

Y

=

1

)

P(Y=0) = 1 – P(Y=1)

P(Y=0)=1P(Y=1),因此

P

(

Y

=

1

)

P(Y=1)

P(Y=1)大于0.5时预测标签为1,否则预测标签为0。但在多分类当中,我们必须求解出所有标签类别所对应的概率,在所有这些概率当中,最大概率所对应的标签才是多分类的预测标签。GBDT对于多分类也只能输出集成算法回归结果

H

(

x

i

)

H(x_i)

H(xi),因此我们需要使用softmax函数帮助我们将回归值转化为概率,而Softmax函数是接受K个连续型结果,并输出K个相对概率的函数

一般我们在使用softmax函数时,3分类问题则需要向softmax函数输入3个值,4分类问题则需要向softmax函数输入4个值,以此类推,最终softmax函数输出的是与输入值同等数量的相对概率,而多分类算法的预测标签是相对概率最高的类别。因此,在使用softmax函数前,我们需要准备好与类别数量相当的

H

(

x

i

)

H(x_i)

H(xi)

具体来说,当现在的问题是

K

K

K分类、且每个类别为

[

1

,

2

,

3…

k

]

[1,2,3…k]

[1,2,3...k]时,我们则分别按照

y

=

1

,

y

=

2

,

.

.

.

,

y

=

k

y = 1, y = 2,…,y = k

y=1,y=2,...,y=k进行建模,总共建立

K

K

K棵树,每棵树输出的结果为:

H

1

(

x

i

)

,

H

2

(

x

i

)

,

.

.

.

,

H

k

(

x

i

)

H^1(x_i), H^2(x_i),…,H^k(x_i)

H1(xi),H2(xi),...,Hk(xi)

总共

K

K

K个输出结果。然后,我们分别将

H

1

(

x

i

)

H^1(x_i)

H1(xi)

H

k

(

x

i

)

H^k(x_i)

Hk(xi)的结果输入softmax,来计算出每个标签类别所对应的概率。具体地来说,softmax函数的表达式为:

S

o

f

t

m

a

x

(

H

k

(

x

)

)

=

e

H

k

(

x

)

k

=

1

K

e

H

k

(

x

)

Softmax(H^k(x)) = /frac{e^{H^k(x)}}{/sum_{k=1}^Ke^{H_k(x)}}

Softmax(Hk(x))=k=1KeHk(x)eHk(x)

其中

e

e

e为自然常数,

H

H

H是集成算法的输出结果,

K

K

K表示标签中的类别总数为

K

K

K,如三分类时

K

=

3

K=3

K=3,四分类时

K

=

4

K=4

K=4

k

k

k表示任意标签类别,

H

k

H_k

Hk则表示以类别

k

k

k为真实标签进行训练而得出的

H

H

H。不难发现,Softmax函数的分子是多分类状况下某一个标签类别的H(x)的指数函数,而分母时多分类状况下所有标签类别的H(x)的指数函数之和,因此Softmax函数的结果代表了样本的预测标签为类别

k

k

k的概率。假设现在是三分类[1,2,3],则样本

i

i

i被分类为1类的概率为:

p

1

(

x

i

)

=

e

H

1

(

x

)

k

=

1

K

e

H

k

(

x

)

=

e

H

1

(

x

)

e

H

1

(

x

)

+

e

H

2

(

x

)

+

e

H

3

(

x

)

/begin{aligned} p^1(x_i) &= /frac{e^{H^1(x)}}{/sum_{k=1}^Ke^{H_k(x)}} // &= /frac{e^{H^1(x)}}{e^{H^1(x)}+e^{H^2(x)}+e^{H^3(x)}}// /end{aligned}

p1(xi)=k=1KeHk(x)eH1(x)=eH1(x)+eH2(x)+eH3(x)eH1(x)

最终得到

K

K

K个相对概率

p

k

(

x

i

)

p^k(x_i)

pk(xi),并求解出相对概率最高的类别。不难发现,当执行多分类时,这一计算流程中涉及到的计算量以及弱评估器数量都会远远超出二分类以及回归类问题。实际上,在执行多分类任务时,如果我们要求模型迭代10次,模型则会按照实际的多分类标签数n_classes建立10 * n_classes个弱评估器。对于这一现象,我们可以通过属性n_estimators_以及属性estimators_查看到。

  • n_estimators_:实际迭代次数,estimators_:实际建立的弱评估器数量
clf = GBC(n_estimators=10 #迭代次数为10次
          ,random_state=1412)
X_clf.shape #查看X与y的结果
#(1460, 79)
np.unique(y_clf) #多分类,现在为6分类
#array([0., 1., 2., 3., 4., 5.])
clf = clf.fit(X_clf,y_clf)

clf.n_estimators_ #实际迭代数量为10
#10
clf.estimators_.shape #但每次迭代时其实建立了6个评估器
#(10, 6)
clf.estimators_[0] #其中一次迭代中建立的全部评估器
# array([DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                              random_state=RandomState(MT19937) at 0x204E8C6B140),
#        DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                              random_state=RandomState(MT19937) at 0x204E8C6B140),
#        DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                              random_state=RandomState(MT19937) at 0x204E8C6B140),
#        DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                              random_state=RandomState(MT19937) at 0x204E8C6B140),
#        DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                              random_state=RandomState(MT19937) at 0x204E8C6B140),
#        DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                              random_state=RandomState(MT19937) at 0x204E8C6B140)],
#       dtype=object)
  • 如果是二分类则不会出现这种现象
from sklearn.datasets import load_breast_cancer
X_clf2 = load_breast_cancer().data
y_clf2 = load_breast_cancer().target
np.unique(y_clf2)
#array([0, 1])
clf = GBC(n_estimators=10,random_state=1412)
clf = clf.fit(X_clf2,y_clf2)

clf.n_estimators_
#10
clf.estimators_
# array([[DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                               random_state=RandomState(MT19937) at 0x204E9485840)],
#        [DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                               random_state=RandomState(MT19937) at 0x204E9485840)],
#        [DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                               random_state=RandomState(MT19937) at 0x204E9485840)],
#        [DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                               random_state=RandomState(MT19937) at 0x204E9485840)],
#        [DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                               random_state=RandomState(MT19937) at 0x204E9485840)],
#        [DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                               random_state=RandomState(MT19937) at 0x204E9485840)],
#        [DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                               random_state=RandomState(MT19937) at 0x204E9485840)],
#        [DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                               random_state=RandomState(MT19937) at 0x204E9485840)],
#        [DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                               random_state=RandomState(MT19937) at 0x204E9485840)],
#        [DecisionTreeRegressor(criterion='friedman_mse', max_depth=3,
#                               random_state=RandomState(MT19937) at 0x204E9485840)]],
#       dtype=object)

这一现象只在弱评估器为回归器的各类boosting算法中出现,对于弱评估器可以是回归树也可以是分类树的随机森林、Adaboost来说,多分类时每个类别对应的概率是在叶子节点上自然生成的。因为有此区别,因此多分类问题在随机森林上的计算可能会表现得更快。

1.3 GBDT的8种损失函数

作为基于AdaBoost改进的Boosting算法,GBDT的功绩之一是将损失函数从有限的指数损失、MSE等推广到了任意可微函数,因此GBDT的损失函数选择异常丰富,因此我们可以在调参时加入损失函数作为需要调整的参数进行考量。在sklearn中,控制具体损失函数的参数为loss

GBDT中的损失函数因GBDT具体执行的预测任务而存在区别,同时也因标签的分布而存在区别。对于梯度提升分类树来说,loss的备选项有如下几种:

  • 分类器中的loss:字符串型,可输入"deviance", “exponential”,默认值=“deviance”

其中"deviance"直译为偏差,特指逻辑回归的损失函数——交叉熵损失,而"exponential"则特指AdaBoost中使用的指数损失函数。对任意样本

i

i

i而言,

y

i

y_i

yi为真实标签,

y

i

^

/hat{y_i}

yi^为预测标签,

H

(

x

i

)

H(x_i)

H(xi)为集成算法输出结果,

p

(

x

i

)

p(x_i)

p(xi)为基于

H

(

x

i

)

H(x_i)

H(xi)和sigmoid/softmax函数计算的概率值。则各个损失的表达式为:

二分类交叉熵损失——

L

=

(

y

log

p

(

x

)

+

(

1

y

)

log

(

1

p

(

x

)

)

)

L = -/left( y/log p(x) + (1 – y)/log(1 – p(x)) /right)

L=(ylogp(x)+(1y)log(1p(x)))

注意,log当中输入的一定是概率值。对于逻辑回归来说,概率就是算法的输出,因此我们可以认为逻辑回归中

p

=

H

(

x

)

p = H(x)

p=H(x),但对于GBDT来说,

p

(

x

i

)

=

S

i

g

m

o

i

d

(

H

(

x

i

)

)

p(x_i) = Sigmoid(H(x_i))

p(xi)=Sigmoid(H(xi)),这一点一定要注意。

多分类交叉熵损失,总共有K个类别——

L

=

k

=

1

K

y

k

log

(

P

k

(

x

)

)

L = -/sum_{k=1}^Ky^*_k/log(P^k(x))

L=k=1Kyklog(Pk(x))

其中,

P

k

(

x

)

P^k(x)

Pk(x)是概率值,对于多分类GBDT来说,

p

k

(

x

)

=

S

o

f

t

m

a

x

(

H

k

(

x

)

)

p^k(x) = Softmax(H^k(x))

pk(x)=Softmax(Hk(x))

y

y^*

y是由真实标签转化后的向量。例如,在3分类情况下,真实标签

y

i

y_i

yi为2时,

y

y^*

y为[

y

1

y^*_{1}

y1,

y

2

y^*_{2}

y2,

y

3

y^*_{3}

y3],取值分别为:

y

1

y^*_{1}

y1

y

2

y^*_{2}

y2

y

3

y^*_{3}

y3

0

0

0

1

1

1

0

0

0

这一转化过程与AdaBoost中多分类指数损失中的转化高度相似。

二分类指数损失——

L

=

e

y

H

(

x

)

L = e^{-yH(x)}

L=eyH(x)

多分类指数损失,总共有K个类别——

L

=

e

x

p

(

1

K

y

H

(

x

)

)

=

e

x

p

(

1

K

(

y

1

H

1

(

x

)

+

y

2

H

2

(

x

)

 

+

 

.

.

.

+

y

k

H

k

(

x

)

)

)

/begin{aligned} L &=exp /left( -/frac{1}{K}/boldsymbol{y^* · H^*(x)} /right) // & = exp /left( -/frac{1}{K}(y^1H^1(x)+y^2H^2(x) / + / … + y^kH^k(x)) /right) /end{aligned}

L=exp(K1yH(x))=exp(K1(y1H1(x)+y2H2(x) + ...+ykHk(x)))

需要注意,指数损失中的

y

y^*

y与交叉熵损失中的

y

y^*

y不是同样的向量。我们已经在逻辑回归的章节中详解过交叉熵损失,在AdaBoost的章节当中详解过指数损失,因此这里便不再展开赘述了。需要注意的是,一般梯度提升分类器默认使用交叉熵损失,如果使用指数损失,则相当于执行没有权重调整的AdaBoost算法。

对于梯度提升回归树来说,loss的备选项有如下几种:

  • 回归器中的loss:字符串型,可输入{“squared_error”, “absolute_error”, “huber”, “quantile”},默认值=“squared_error”

其中’squared_error’是指回归的平方误差,'absolute_error’指的是回归的绝对误差,这是一个鲁棒的损失函数。'huber’是以上两者的结合。'quantile’则表示使用分位数回归中的弹球损失pinball_loss。对任意样本

i

i

i而言,

y

i

y_i

yi为真实标签,

H

(

x

i

)

H(x_i)

H(xi)为预测标签,则各个损失的表达式为:

平方误差——

L

=

(

y

i

H

(

x

i

)

)

2

L = /sum{(y_i – H(x_i))^2}

L=(yiH(xi))2

绝对误差——

L

=

y

i

H

(

x

i

)

L = /sum{|y_i – H(x_i)|}

L=yiH(xi)

Huber损失——

L

=

l

(

y

i

,

H

(

x

i

)

)

L = /sum{l(y_i,H(x_i))}

L=l(yi,H(xi))

其中KaTeX parse error: No such environment: split at position 11: l = /begin{̲s̲p̲l̲i̲t̲}̲ /begin{cases}…

quantile损失——

L

=

l

(

y

i

,

H

(

x

i

)

)

L = /sum{l(y_i,H(x_i))}

L=l(yi,H(xi))

其中

l

=

{

α

(

y

i

H

(

x

i

)

)

,

y

i

H

(

x

i

)

>

0

0

,

y

i

H

(

x

i

)

=

0

,

α

(

0

,

1

)

(

1

α

)

(

y

i

H

(

x

i

)

)

,

y

i

H

(

x

i

)

< 0 l=/left/{/begin{array}{ll} /alpha/left(y_{i}-H/left(x_{i}/right)/right), & y_{i}-H/left(x_{i}/right)>0 // 0, & y_{i}-H/left(x_{i}/right)=0, /quad /alpha /in(0,1) // (1-/alpha)/left(y_{i}-H/left(x_{i}/right)/right), & y_{i}-H/left(x_{i}/right)<0 /end{array}/right. l=α(yiH(xi)),0,(1α)(yiH(xi)),yiH(xi)>0yiH(xi)=0,α(0,1)yiH(xi)<0

其中

α

/alpha

α是需要我们自己设置的超参数,由参数alpha控制。在huber损失中,alpha是阈值,在quantile损失中,alpha用于辅助计算损失函数的输出结果,默认为0.9。

=更新警告=
在sklearn1.0版本及后续版本当中,损失函数"ls"与"lad"被删除了,其中"ls"的功能被"squared_error"取代,而"lad"被"absolute_error"取代。如果你在运行代码时,发现你的参数默认值、参数名称与课件中不相同,或者在运行过程中出现报错、警告等现象,你可能需要更新你的sklearn。

对于相同的样本、相同的差异,不同损失函数给出的损失值不同

yi = 10
Hx = 8

绝对 - 2
平方 - 4 - 差异>1,误差被放大,差异<1,误差是会被缩小
  • 如何选择不同的损失函数?

GBDT是工业应用最广泛的模型,工业数据大部分都极度偏态、具有长尾,因此GBDT必须考虑离群值带来的影响。数据中的离群值会极大程度地影响模型地构建,当离群值在标签当中、而我们是依赖于减小损失函数来逐渐构建算法时,这种影响会前所未有地大。因此Boosting是天生更容易被离群值影响的模型、也更擅长学习离群值的模型。

LESSON 12.1&12.2 梯度提升树的基本思想与实现&迭代过程中的参数(1):GBDT的初始化与多分类
LESSON 12.1&12.2 梯度提升树的基本思想与实现&迭代过程中的参数(1):GBDT的初始化与多分类

举例来说,若离群值的标签为1000,大部分正常样本的标签在0.1~0.2之间,算法一定会异常努力地学习离群值的规律,因为将离群值预测错误会带来巨大的损失。在这种状况下,最终迭代出的算法可能是严重偏离大部分数据的规律的。同样,我们也会遇见很多离群值对我们很关键的业务场景:例如,电商中的金额离群用户可能是VIP用户,风控中信用分离群的用户可能是高风险用户,这种状况下我们反而更关注将离群值预测正确。不同的损失函数可以帮助我们解决不同的问题——

  • 当高度关注离群值、并且希望努力将离群值预测正确时,选择平方误差

    这在工业中是大部分的情况。在实际进行预测时,离群值往往比较难以预测,因此离群样本的预测值和真实值之间的差异一般会较大。MSE作为预测值和真实值差值的平方,会放大离群值的影响,会让算法更加向学习离群值的方向进化,这可以帮助算法更好地预测离群值。

  • 努力排除离群值的影响、更关注非离群值的时候,选择绝对误差

    MAE对一切样本都一视同仁,对所有的差异都只求绝对值,因此会保留样本差异最原始的状态。相比其MSE,MAE对离群值完全不敏感,这可以有效地降低GBDT在离群值上的注意力。

  • 试图平衡离群值与非离群值、没有偏好时,选择Huber或者Quantileloss

    Huberloss损失结合了MSE与MAE,在Huber的公式中,当预测值与真实值的差异大于阈值时,则取绝对值,小于阈值时,则取平方。在真实数据中,部分离群值的差异会大于阈值,部分离群值的差异会小于阈值,因此比起全部取绝对值的MAE,Huberloss会将部分离群值的真实预测差异求平方,相当于放大了离群值的影响(但这种影响又不像在MSE那样大)。因此HuberLoss是位于MSE和MAE之间的、对离群值相对不敏感的损失。

  • 属性loss_

拜师教育学员文章:作者:1966-朱同学, 转载或复制请以 超链接形式 并注明出处 拜师资源博客
原文地址:《LESSON 12.1&12.2 梯度提升树的基本思想与实现&迭代过程中的参数(1):GBDT的初始化与多分类》 发布于2022-01-25

分享到:
赞(0) 打赏

评论 抢沙发

评论前必须登录!

  注册



长按图片转发给朋友

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

Vieu3.3主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录