xgbboost & lightgbm

1. xgboost原理

1.1训练集样本信息

xgbboost & lightgbm

1.2 logistic 及其损失函数

  • logistic表达式: y^=log(p1p)\hat{y} = log(\frac{p}{1-p}) 对上式做数学变换得到p=11+exp(y^)p = \frac{1}{1+exp(-\hat{y})}y^\hat{y}是一个累加和,如y^=i=1kfi(xi)\hat{y} = \sum\limits_{i = 1}^{k}f_i(x_i), 即y^\hat{y}由k个f(x)求和得到,上式变为p=11+exp(i=1Kfi(x))p = \frac{1}{1+exp(-\sum\limits_{i =1}^{K}f_i(x))}
  • logistic损失函数:极大似然估计,具体到每个样本上,实际是典型的二项分布概率:
    xgbboost & lightgbm
    转化为对数形式:
    xgbboost & lightgbm
    考虑到损失函数数值含义为最优点对应最小值,所以对数似然取负数(极大似然是正):
    xgbboost & lightgbm
    以上是一个样本,对于所有样本:
    xgbboost & lightgbm

1.3 xgboost推导(结合logistic及其损失函数)

xgbboost & lightgbm
公式解释:第一个公式直观上可以理解,但是fkf_k是什么呢,表达式如何?f本质是泛函,在这里仅表示一种映射关系。xgboost中一棵决策树中每个叶子结点都会赋一个值ω\omega,ω\omega具体是什么,后续会讲。f就是将一个样本xix_i经过f后(经过决策树划分进一个叶子结点)变成ωi\omega_i(即下图叶子结点对应的红框数字)。ωq(x)\omega_q(x)中q表示树的结构,即这棵决策树长什么样子。ωq(x)\omega_q(x)表示,样本x经过一棵结构为q的决策树后变成了ω\omega,联系上述,这个q其实就是刚才的f,f或者q现在不是以前常见的数学解析书,而是一个树状的结构图。下图可帮助理解:
xgbboost & lightgbm

  • 3.此外为防止过拟合,确定损失函数时需要加一个正则化项:

xgbboost & lightgbm
l(yi^,y)l(\hat{y_i},y)是损失函数的表达式,本文档即logistic损失函数表达式,i表示第i个样本,k表示第k棵树,T表示一棵决策树中叶子结点的个数。该式不仅对叶子结点进行了限制,还加入了回归中常用的L2范数。提醒一点,这里的y^\hat y是当前预测出的值,已经考虑了所有棵树的累加。

  • 4.上文提到,xgboost核心思想是所有棵树的累加和,那么对于第t棵树(构造多棵树的过程是一棵一棵地构造,因此将目标放到当前这棵树上),预测值应该等于前t-1棵树的预测值加上当前这棵树预测出来的数值。因此上式可改写为:
    xgbboost & lightgbm
    其中i让然表示第i个样本,yi,yi^(t1)y_i, \hat{y_i}^{(t-1)}表示真实值与前t-1棵树预测值对应的损失(由损失函数确定),提醒:这里的yi^(t1)\hat{y_i}^{(t-1)}已经包含了前t-1棵树每个数的正则化项值,因此后面的Ω(ft)\Omega(f_t)仅表示第t棵树,即当前树的正则化项。中间一项不再赘述
  • 5.大学我们都学过泰勒公式:
    xgbboost & lightgbm
    此外,还需要插播一个知识点,即参数空间和函数空间
    xgbboost & lightgbm
    上式是梯度下降的表示, αα 表示步长, gtgt 表示参数的一阶导。
    xgboost用的就是函数空间的概念,但是不影响泰勒公式,迭代形式的泰勒公式还是上述,只是里面的参数为函数空间表达形式。如果这里实在不能理解,可以先放弃,记住下面泰勒展开后的函数空间即可。
  • 6.将刚才的xgboost损失函数用迭代形式的泰勒公式进行二阶展开:

xgbboost & lightgbm
其中gig_i表示前t-1棵树的损失函数对y^t1\hat{y}^{t-1}进行求导,对于logistic损失函数一阶导公式为f(yi^)yi^=yi(111+eyi^)+(1yi)11+eyi^\partial{\frac{f(\hat{y_i})}{\hat{y_i}}}=-y_i(1- \frac{1}{1+e^{-\hat{y_i}}})+(1-y_i)\frac{1}{1+e^{-\hat{y_i}}},将参数yi^(t1)\hat{y_i}^{(t-1)}带进去后,gigi表达式为:yi(111+eyi^1t)+(1yi)11+eyi^t1-y_i(1- \frac{1}{1+e^{-\hat{y_i}^{1-t}}})+(1-y_i)\frac{1}{1+e^{-\hat{y_i}^{t-1}}}
xgbboost & lightgbm
即假设y^i\hat y_i初始值为0,所以第一棵树计算时,只需将yi^(t1)=0\hat{y_i}^{(t-1)}=0带入上述公式。这样就可以求得gig_i,hih_i方法一样,不再赘述。再次提醒,这里gig_i是对yi^(t1)\hat{y_i}^{(t-1)}求导,无需关注yi^(t1)\hat{y_i}^{(t-1)}究竟是由前面的树怎么得来的,回来以前的数学解析式,虽然y^=ωx\hat{y} = \omega*x,但无需考虑对x求导,这里仅仅是对y^\hat{y}求导,xgboost中仅仅是对yi^(t1)\hat{y_i}^{(t-1)}求导

  • 7.对于第t棵树,前面t-1棵树的预测值是已知值,因此l(yi,yi^t1)l(y_i, \hat{y_i}^{t-1})是常数,所以上式可进行简化如下:
    xgbboost & lightgbm
  • 8.观察上式,上面求和中i表示第i个样本,但是后面正则化项xgbboost & lightgbm中t表示第t个叶子结点,那么怎么能将两者统一呢?思考,每个样本最终会落到一个叶子结点上,因此样本i是不是可以转化为t呢,即一个叶子结点是由m个样本构成,借助这种思路,上式可变为:
    xgbboost & lightgbm
    这里Ij={iq(Xi)=j}I_j = \left\{ i|q(X_i) = j\right\}表示第j个叶子结点样本的集合,iIjgi\sum\limits_{i\in I_j}g_i(也可以用GjG_j表示)表示第j个结点上所有样本gig_i的和。下图可方便理解
    xgbboost & lightgbm
  • 9.好了,损失函数的表达式出来了,怎样达到最小值呢,很简单对ωj\omega_j求偏导,就知道当前树对应叶子结点的值和最小的损失函数了。计算得到:xgbboost & lightgbm将其带回损失函数,得到最小的损失为
    xgbboost & lightgbm
  • 10.虽然最小的损失有了,但是树的结构还没确定,且这是一个NP难问题,那么怎样确定树的结构呢?回顾CART回归树的建立,也是一个NP难问题,所以建树的方法采取的是贪婪的方式,即遍历特征中所有可能的划分点计算回归树中定义的指标最小值,将指标达到最小值时划分点最为最终的特征选择标准。xgboost也是这样的思路,只是现在的指标如下

xgbboost & lightgbm现以年龄为例,如果选择age为10岁做为划分点,则ID1为左边的子样本,2,3,4,5为右边的样本。第一棵树时样本的前t-1棵树(实际没有)预测值初始化为0,将标签为yes的样本(1,2,3号样本)带入一阶导公式gig_i,结果为1(111+e0+(11)11+e0=12)-1*(1-\frac{1}{1+e^{-0}}+(1-1)*\frac{1}{1+e^{-0}}=-\frac{1}{2}) ,二阶导结果为14\frac{1}{4},将标签为no的样本(4,5)带入一阶导公式为12\frac{1}{2},二阶导公式为14\frac{1}{4},因此选择age=10为划分点时Gain为如下结果:
xgbboost & lightgbm
按照此规则依次计算age=20……gender=male……,在此不再举例。

2.xgboost 优化

2.1步长(shrinkage)

同之前的GBDT一样,XGBoost也可以加入步长η(有的也叫收缩率Shrinkage),这也是防止过拟合的好方法:xgbboost & lightgbm
通常步长 η 取值为0.1。当然GBDT也可以采用这个

2.2 行、列抽样

XGBoost借鉴随机森林也使用了列抽样(在每一次分裂中使用特征抽样),进一步防止过拟合,并加速训练和预测过程。
此外,在实现中还有行抽样(样本抽样)。

2.3 特征选择的优化

前面提到过,XGBoost每一步选能使分裂后增益最大的分裂点进行分裂。而分裂点的选取之前是枚举所有分割点,这称为精确的贪心法(Exact Greedy Algorithm).当数据量十分庞大,以致于不能全部放入内存时,Exact Greedy 算法就会很慢。因此XGBoost引入了近似的算法。即对每一个特征进行「值」采样。原来需要对每一个特征的每一个可能分割点进行尝试,采样之后只针对采样的点(分位数)进行分割尝试,这种方法很明显可以减少计算量,采样密度越小计算的越快,拟合程度也会越差,所以采样还可以防止过拟合。
xgbboost & lightgbm
如下图Algorithm1是精确的贪心法算法;2为近似的算法
xgbboost & lightgbm
xgbboost & lightgbm
xgbboost & lightgbm
那么,现在有两个问题:

  • 1.如何选取候选切分点Sk={sk1,sk2,⋯skl}呢?(即选择特征的哪些分位数作为备选的分裂点呢?)
  • 2.什么时候进行候选切分点的选取?

针对问题2:
第二个方法执行采样的方式有两个,一种是global模式,一种是local模式。global模式是执行树生成之前采一次样,后面不再更新( 学习每棵树前, 提出候选切分点);local模式是每次split之后都再次进行一次采样(每次分裂前, 重新提出候选切分点)。不同的模式和不同的采样密度带来的效果如下图:
xgbboost & lightgbm
桶的个数等于 1 / eps, 可以看出:
全局切分点的个数够多的时候,和Exact greedy算法性能相当。
局部切分点个数不需要那么多,因为每一次分裂都重新进行了选择。

2.4 切分点的选取 – Weighted Quantile Sketch

上一节中提出了两个问题,但仅只解决了第二个,下面对第一个问题进行解答,选择哪些候选切分点。
对于问题1,可以采用分位数,也可以直接构造梯度统计的近似直方图等。
简单的分位数就是先把数值进行排序,然后根据你采用的几分位数把数据分为几份即可。而XGBoost不单单是采用简单的分位数的方法,而是对分位数进行加权(使用二阶梯度h),称为:Weighted Quantile Sketch。PS:上面的那个例子采用的是没有使用二阶导加权的分位数。
对特征k构造multi-set 的数据集:Dk=(x1k,h1),(x2k,h2),,(xnk,hn)D_k=(x_{1k},h_1),(x_2k,h_2),…,(x_{nk},h_n), 其中 xikx_{ik} 表示样本i的特征k的取值,而hih_i则为对应的二阶梯度。
xgbboost & lightgbm
式中分子是x小于z的所有样本对应的h之和(即小于z的样本加权和,权重为h),分母为所有样本的加权和。该式表达了第k个特征小于z的样本比例,和之前的分位数挺相似,不过这里是按照二阶梯度进行累计。
而候选切分点sk1,sk2,,skl{s_{k1},s_{k2},⋯,s_{kl}}要求:
xgbboost & lightgbm
即让相邻两个候选分裂点代入rk(z)r_k(z)中,相差不超过某个值ε,由此,最终会切分出1/ε个桶,如下面例子:
xgbboost & lightgbm
选取ε = 1/3,会得到三个桶,h总和1.8,因此sk1=0.6,sk2=1.2s_{k1} = 0.6, s_{k2} = 1.2
到这里似乎就讲完了,但是仍然有个问题,为什么选择h而不是g或者其他呢?证明如下:
xgbboost & lightgbm

2.5 稀疏矩阵(缺失值)处理

有很多种原因可能导致特征的稀疏(缺失),所以当遇到样本某个维度的特征缺失的时候,就不能知道这个样本会落在左孩子还是右孩子。xgboost处理缺失值的方法和其他树模型不同。根据作者TianqiChen在论文[1]中章节3.4的介绍,xgboost把缺失值当做稀疏矩阵来对待,本身在节点分裂时不考虑缺失值的数值,但确定分裂的特征后,缺失值数据会被分到左子树和右子树呢?本文的处理策略是落在哪个孩子得分高,就放到哪里。如果训练中没有数据缺失,预测时出现了数据缺失,那么默认被分类到右子树。具体的介绍可以参考:xgbboost & lightgbm

2.6 列排序优化

注意到每一次Split Finding的时候是需要对当前节点中的样本按照特征值进行排序的,其实这个排序可以在初始化的时候进行一次就够了,比如有n个样本m维特征,只需要预先生成一个n*m的矩阵,第m列表示的是按照第m个特征从小到大对样本的排序索引。 通过指针存储,一个指针也就是四个字节,耗费的空间有限。
上面遍历排序索引的过程可以并行进行,并且执行Split Finding的时候,也可以同时多个维度并发寻找得分最高的切分点。
xgbboost & lightgbm

2.7 缓存优化

xgbboost & lightgbm
在approximate 算法中,对Block的大小进行了合理的设置。定义Block的大小为Block中最多的样本数。设置合适的大小是很重要的,设置过大则容易导致命中率低,过小则容易导致并行化效率不高。经过实验,发现2^16比较好。

3 xgboost 使用及参数

3.1 初识xgboost

3.1.1 xgboost使用方法

xgb有两种使用方式,一种是使用其自带的建模方式,另一种是为了迎合sklearn(因为很多人习惯了sklearn的fit、predict方式),创建了sklearn接口。

  • ①使用xgboost自带的数据集格式 + xgboost自带的建模方式
    • 把数据读取成xgb.DMatrix格式(libsvm/dataframe.values给定X和Y)
    • 准备好一个watch_list(训练集和验证集,xgb在进行拟合的时候可以同时对训练集和验证集进行操作,得到训练集和验证集的error)
    • xgb.train(dtrain)
    • xgb.predict(dtest)
  • ②使用pandas的DataFrame格式 + xgboost的sklearn接口
    • estimator = xgb.XGBClassifier()/xgb.XGBRegressor()
    • estimator.fit(df_train.values, df_target.values)
    • estimator.predict(df_val.values, )
      附:libsvm格式:xgbboost & lightgbm
      当某一特征为0时,不会显示,这就解决了稀疏矩阵的存储问题。

3.1.2 xgboost方法1代码

# 基本例子,从libsvm文件中读取数据,做二分类
# 数据是libsvm的格式
#1 3:1 10:1 11:1 21:1 30:1 34:1 36:1 40:1 41:1 53:1 58:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 105:1 117:1 124:1
#0 3:1 10:1 20:1 21:1 23:1 34:1 36:1 39:1 41:1 53:1 56:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 106:1 116:1 120:1
#0 1:1 10:1 19:1 21:1 24:1 34:1 36:1 39:1 42:1 53:1 56:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 106:1 116:1 122:1

# 数据存储格式本身就为libsvm,直接DMatrix读取
dtrain = xgb.DMatrix('./data/agaricus.txt.train')  # 特征矩阵和标签在一起,下面拟合的时候直接传入dtrain,不用分别传入特征矩阵和标签。
dtest = xgb.DMatrix('./data/agaricus.txt.test')

#超参数设定
param = {'max_depth':2, 'eta':1, 'silent':1, 'objective':'binary:logistic' } # 之后会详细介绍参数

# 设定watchlist用于查看模型状态
watchlist  = [(dtest,'eval'), (dtrain,'train')] # 'eval' 和'dtrain'都为注释,方法见参数讲解
num_round = 2 # 参数
# 模型拟合(训练)
bst = xgb.train(param, dtrain, num_round, watchlist)

# 使用模型预测
preds = bst.predict(dtest)

# 判断准确率
labels = dtest.get_label()
print ('错误类为%f' % \
       (sum(1 for i in range(len(preds)) if int(preds[i]>0.5)!=labels[i]) /float(len(preds))))

# 模型存储
bst.save_model('./model/0001.model')

该代码运行结果:
xgbboost & lightgbm

3.1.3 xgboost方法1代码扩展

上一小节的数据存储格式本身为libsvm,如果不是libsvm,而是dataframe的格式呢?此时要先将数据进行转化,仍然使用DMatrix。代码如下:

# 基本例子,从csv文件中读取数据,做二分类

# 用pandas读入数据
data = pd.read_csv('./data/Pima-Indians-Diabetes.csv')

# 做数据切分
train, test = train_test_split(data)

# 转换成Dmatrix格式
feature_columns = ['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age'] # 获取特征的名称
target_column = 'Outcome'  # 获取标签的名称
xgtrain = xgb.DMatrix(train[feature_columns].values, train[target_column].values) # 将特征矩阵和标签的数据转化为libsvm。上述DMatrix特征矩阵与标签在一起,这样操作也放到了一起
xgtest = xgb.DMatrix(test[feature_columns].values, test[target_column].values)

# 上述数据格式转化成功后,下面拟合、预测的过程和上一节一样

#参数设定
param = {'max_depth':5, 'eta':0.1, 'silent':1, 'subsample':0.7, 'colsample_bytree':0.7, 'objective':'binary:logistic' }

# 设定watchlist用于查看模型状态
watchlist  = [(xgtest,'eval'), (xgtrain,'train')]
num_round = 10
bst = xgb.train(param, xgtrain, num_round, watchlist)

# 使用模型预测
preds = bst.predict(xgtest)

# 判断准确率
labels = xgtest.get_label()
print ('错误类为%f' % \
       (sum(1 for i in range(len(preds)) if int(preds[i]>0.5)!=labels[i]) /float(len(preds))))

# 模型存储
bst.save_model('./model/0002.model')

该代码运行结果:
xgbboost & lightgbm

3.1.4 xgboost 使用方法2代码

该方法和sklearn一样,数据格式为csv(dataframe),直接读取,然后fit、predict等

# 基本例子,从csv文件中读取数据,做二分类

# 用pandas读入数据
data = pd.read_csv('./data/Pima-Indians-Diabetes.csv')

# 做数据切分
train, test = train_test_split(data)

# 取出特征X和目标y的部分
feature_columns = ['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age']
target_column = 'Outcome'
train_X = train[feature_columns].values
train_y = train[target_column].values
test_X = test[feature_columns].values
test_y = test[target_column].values

# 初始化模型
xgb_classifier = xgb.XGBClassifier(n_estimators=20,\
                                   max_depth=4, \
                                   learning_rate=0.1, \
                                   subsample=0.7, \
                                   colsample_bytree=0.7)

# 拟合模型
xgb_classifier.fit(train_X, train_y)

# 使用模型预测
preds = xgb_classifier.predict(test_X)

# 判断准确率
print ('错误类为%f' %((preds!=test_y).sum()/float(test_y.shape[0])))

# 模型存储
joblib.dump(xgb_classifier, './model/0003.model')

3.1.5 xgboost 的交叉验证

(1)xgb第一种使用方法下有自带的cv,代码如下:

# 计算正负样本比,调整样本权重
def fpreproc(dtrain, dtest, param):
    label = dtrain.get_label()
    ratio = float(np.sum(label == 0)) / np.sum(label==1)
    param['scale_pos_weight'] = ratio
    return (dtrain, dtest, param)

# 先做预处理,计算样本权重,再做交叉验证
xgb.cv(param, dtrain, num_round, nfold=5,
       metrics={'auc'}, seed = 0, fpreproc = fpreproc)

结果如下:xgbboost & lightgbm
该方式下不能直接得出最佳的参数,而是给出上述指标,可以判断出在第几轮的时候效果比较好,个人理解可以粗略知道轮数这个参数的大概取值。
(2)此外使用方法2是采用的sklearn的方式,因此可以结合sklearn中的Gridsearchcv等使用。

3.1.6 自定义损失函数与评估准则

xgb中有自带的损失函数和评估准则,同时也可以自己指定,但该操作仅限于第一种使用方法,且自定义损失函数需要写出损失函数的一阶导和二阶导。

# 自定义损失函数,需要提供损失函数的一阶导和二阶导
def logregobj(preds, dtrain):
    labels = dtrain.get_label()
    preds = 1.0 / (1.0 + np.exp(-preds))
    grad = preds - labels
    hess = preds * (1.0-preds)
    return grad, hess

# 自定义评估准则,评估预估值和标准答案之间的差距
def evalerror(preds, dtrain):
    labels = dtrain.get_label()
    return 'error', float(sum(labels != (preds > 0.0))) / len(labels)

watchlist  = [(dtest,'eval'), (dtrain,'train')]
param = {'max_depth':3, 'eta':0.1, 'silent':1}
num_round = 5
# 自定义损失函数训练
bst = xgb.train(param, dtrain, num_round, watchlist, logregobj, evalerror)
# 交叉验证
xgb.cv(param, dtrain, num_round, nfold = 5, seed = 0,
       obj = logregobj, feval=evalerror)

3.1.7 早停

xgb因为可以同时传入训练集和验证集观察模型效果,因此有一个早停操作,即在训练集上学习模型,一颗一颗树添加,在验证集上看效果,当验证集效果不再提升,停止树的添加与生长。如当传入的轮数为100轮,early_stoping为10,当树生长到第10棵时验证集效果不提升,此后连续10棵树都没有提升,这时候停止拟合,即不会拟合100轮。下方代码为使用sklearn的方式(第一种方式在下方参数会讲到):

# 在训练集上学习模型,一颗一颗树添加,在验证集上看效果,当验证集效果不再提升,停止树的添加与生长
from sklearn.datasets import load_iris, load_digits, load_boston
from sklearn.model_selection import train_test_split
import xgboost as xgb
digits = load_digits()
X = digits['data']
y = digits['target']
X_train, X_val, y_train, y_val = train_test_split(X, y, random_state=0)
clf = xgb.XGBClassifier()
clf.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="auc",
        eval_set=[(X_val, y_val)]) 

前面提到cv,可以粗略估计轮数,配合现在的early_stoping,可以比较精准的得到模型要构建几棵树。

3.1.8使用前n棵树预测

之前我们使用early_stoping,假如树在第10棵的时候效果最好,11-20验证集效果就不再提升了(假设early_stoping为10),因此我们建模只需要前10棵树,预测的时候也只需要前10棵树。可以使用ntree_limit这个参数,代码如下:

# 基本例子,从csv文件中读取数据,做二分类

# 用pandas读入数据
data = pd.read_csv('./data/Pima-Indians-Diabetes.csv')

# 做数据切分
train, test = train_test_split(data)

# 转换成Dmatrix格式
feature_columns = ['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age']
target_column = 'Outcome'
xgtrain = xgb.DMatrix(train[feature_columns].values, train[target_column].values)
xgtest = xgb.DMatrix(test[feature_columns].values, test[target_column].values)

#参数设定
param = {'max_depth':5, 'eta':0.1, 'silent':1, 'subsample':0.7, 'colsample_bytree':0.7, 'objective':'binary:logistic' }

# 设定watchlist用于查看模型状态
watchlist  = [(xgtest,'eval'), (xgtrain,'train')]
num_round = 10
bst = xgb.train(param, xgtrain, num_round, watchlist)

# 只用第1颗树预测
ypred1 = bst.predict(xgtest, ntree_limit=1)
# 用前9颗树预测
ypred2 = bst.predict(xgtest, ntree_limit=9)
label = xgtest.get_label()
print ('用前1颗树预测的错误率为 %f' % (np.sum((ypred1>0.5)!=label) /float(len(label))))
print ('用前9颗树预测的错误率为 %f' % (np.sum((ypred2>0.5)!=label) /float(len(label))))

3.1.9 特征重要度

xgb也可以像决策树一样得到特征重要度,相关绘图代码如下

iris = load_iris()
y = iris['target']
X = iris['data']
xgb_model = xgb.XGBClassifier().fit(X,y)

print('特征排序:')
feature_names=['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
feature_importances = xgb_model.feature_importances_
indices = np.argsort(feature_importances)[::-1]

for index in indices:
    print("特征 %s 重要度为 %f" %(feature_names[index], feature_importances[index]))

%matplotlib inline
import matplotlib.pyplot as plt
plt.figure(figsize=(16,8))
plt.title("feature importances")
plt.bar(range(len(feature_importances)), feature_importances[indices], color='b')
plt.xticks(range(len(feature_importances)), np.array(feature_names)[indices], color='b')

结果:
xgbboost & lightgbm

3.2 xgb主要参数

xgbboost & lightgbm

4.xgboost 常见面试问题

  1. xgboost与GBDT区别
  • 算法层面:
    • 传统GBDT以CART作为基分类器,xgboost还支持线性分类器,这个时候xgboost相当于带L1和L2正则化项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题)。
    • 节点分裂的方式不同,gbdt是用gini系数,xgboost是经过优化推导后的。
    • XGB加了正则项,普通GBDT没有。防止过拟合。xgbboost & lightgbm
    • xgboost损失函数是误差部分是二阶泰勒展开,GBDT 是一阶泰勒展开。所以损失函数定义的更精确。
    • 对每颗子树增加一个参数,使得每颗子树的权重降低,防止过拟合,增加这个参数叫shrinkage方法。
    • 对特征进行降采样(列抽样),灵感来源于随机森林,除了能降低计算量外,还能防止过拟合。
    • 实现了利用分捅/分位数方法,实现了全局和局部的近似分裂点算法,降低了计算量,并且在eps参数设置合理的情况下,能达到穷举法几乎一样的性能。
    • 提出并实现了特征带权重的分位数的方法
    • 增加处理缺失值的方案(通过枚举所有缺失值在当前节点是进入左子树,还是进入右子树更优来决定一个处理缺失值默认的方向)。
  • 系统层面:
    • 对每个特征进行分块(block)并排序,使得在寻找最佳分裂点的时候能够并行化计算。这是xgboost比一般GBDT更快的一个重要原因。xgbboost & lightgbm
    • 通过设置合理的block的大小,充分利用了CPU缓存进行读取加速(cache-aware access)。使得数据读取的速度更快。因为太小的block的尺寸使得多线程中每个线程负载太小降低了并行效率。太大的block尺寸会导致CPU的缓存获取miss掉。xgbboost & lightgbm
    • out-of-core 通过将block压缩(block compressoin)并存储到硬盘上,并且通过将block分区到多个硬盘上(block Sharding)实现了更大的IO 读写速度,因此,因为加入了硬盘存储block读写的部分不仅仅使得xgboost处理大数据量的能力有所提升,并且通过提高IO的吞吐量使得xgboost相比一般实利用这种技术实现大数据计算的框架更快。xgbboost & lightgbm
  1. 为什么xgboost要用泰勒展开,优势在哪里?
    xgboost使用了一阶和二阶偏导,二阶导数有利于梯度下降的更快更准。使用泰勒展开取得函数做自变量得二阶导数形式,可以在不选定损失函数具体形式的情况下,仅仅依靠输入数据的值就可以进行叶子分裂优化计算,本质上也就把损失函数的选取和模型算法优化/参数选择分开了,这种去耦合增加了xgboost的适用性,使得它按需选取损失函数,可以用于分类,也可以用于回归
  2. XGBoost如何解决缺失值问题?
    前面已经做过解答,自学习。

5.从xgboost到lightgbm

XGB、LGB 都是 GBDT 的方法。因此原理基本一样,不同点如下: