基本构成
boosted tree作为有监督学习算法有几个重要部分:模型、参数、目标函数、优化算法
模型
模型指给定输入x如何去预测输出y
参数
参数指我们需要学习的东西,在线性模型中,参数指我们的线性系数w
目标函数
目标函数:损失 + 正则,教我们如何去寻找一个比较好的参数
一般的目标函数包含下面两项:
Bias-variance tradeoff,Bias可以理解为假设我们有无限多数据的时候,可以训练出最好的模型所拿到的误差。而Variance是因为我们只有有限数据,其中随机性带来的误差。
误差函数尽量去拟合训练数据,正则化项则鼓励更加简单的模型。因为当模型简单之后,有限数据拟合出来结果的随机性比较小,不容易过拟合,使得最后模型的预测更加稳定。
优化算法
给定目标函数之后怎么学的问题
Boosted Tree
基学习器:分类和回归树(CART)
CART会把输入根据输入的属性分配到各个叶子节点,而每个叶子节点上面都会对应一个实数分数。
Tree Ensemble构成
一个CART往往过于简单无法有效地预测,因此一个更加强力的模型叫做tree ensemble。
用两棵树来进行预测。我们对于每个样本的预测结果就是每棵树预测分数的和。
tree ensemble
预测函数:
目标函数:
模型学习:additive training
第一部分是训练误差,第二部分是每棵树的复杂度的和。
每一次保留原来的模型不变,加入一个新的函数f到我们的模型中。
如何选择每一轮加入什么f呢?
选取一个f来使得我们的目标函数尽量最大地降低(加入f后的预测结果与实际结果误差减少)。
对于l是平方误差时:
对于l不是平方误差的情况:
采用如下的泰勒展开近似来定义一个近似的目标函数
移除常数项(真实值与上一轮的预测值之差),目标函数只依赖于每个数据点的在误差函数上的一阶导数和二阶导数
树的复杂度
以上是目标函数中训练误差的部分,接下来定义树的复杂度。
对于f的定义做一下细化,把树拆分成结构函数q(输入x输出叶子节点索引)和叶子权重部分w(输入叶子节点索引输出叶子节点分数),结构函数q把输入映射到叶子的索引号上面去,而w给定了每个索引号对应的叶子分数是什么。
定义一棵树的复杂度如下:
一棵树里面叶子节点的个数,以及每个树叶子节点上面输出分数的L2模平方。
目标函数改写:
其中I被定义为每个叶子上面样本集合Ij={i|q(xi)=ji} (每个叶子节点里面样本集合);
f(xi)等价于求出w(q(xi))的值(每一个样本所在叶子索引的分数) ;T为叶子节点数量。
定义Gj(每个叶子节点里面一阶梯度的和)Hj(每个叶子节点里面二阶梯度的和):
目标函数改写:
求偏导得出:
Obj代表了当我们指定一个树的结构的时候,我们在目标上面最多减少多少,可叫做结构分数(structure score),Obj计算示例:
枚举所有不同树结构的贪心法
exact greedy algorithm 贪心算法获取最优切分点
利用这个打分函数来寻找出一个最优结构的树,加入到我们的模型中,再重复这样的操作。
常用的方法是贪心法,每一次尝试去对已有的叶子加入一个分割。对于一个具体的分割方案,计算增益:
对于每次扩展,如何高效地枚举所有的分割呢
假设我们要枚举所有 x
引入新叶子的惩罚项
优化这个目标对应了树的剪枝, 当引入的分割带来的增益小于一个阀值的时候,我们可以剪掉这个分割。
这样根据推导引入了分裂节点的选取计算分数和叶子的惩罚项,替代了回归树的基尼系数与剪枝操作。
当我们正式地推导目标的时候,像计算分数和剪枝这样的策略都会自然地出现,而不再是一种因为heuristic(启发式)而进行的操作了。
缩减与列抽样
缩减,每一个树生成结果乘以一个步长系数 防止过拟合
列采样样 类似随机森林每个树特征抽样 防止过拟合
划分点查找算法
贪心算法
算法1 exact greedy algorithm—贪心算法获取最优切分点
approximate algorithm近似算法
算法2 利用Sk
核心思想:
通过特征的分布,按照分布式加权直方图算法确定一组候选分裂点,通过遍历所有的候选分裂点来找到最佳分裂点。
在寻找split point的时候,不会枚举所有的特征值,而会对特征值进行聚合统计,然后形成若干个bucket(桶),只将bucket边界上的特征值作为split point的候选,从而获得性能提升。
Weighted Quantile Sketch—分布式加权直方图算法
如何找Sk
统计每个特征里面点的权值确定候选切割点(理解为按照一定顺序排成直方图相邻候选点不超过阈值控制直方图每个宽度)
参考
处理稀疏特征分裂算法
对于稀疏性的离散特征,在寻找split point的时候,不会对该特征为missing的样本进行遍历统计,只对该列特征值为non-missing的样本上对应的特征值进行遍历,通过这个工程技巧来减少了为稀疏离散特征寻找split point的时间开销。在逻辑实现上,为了保证完备性,会分别处理将missing该特征值的样本分配到左叶子结点和右叶子结点的两种情形。可以为缺失值或者指定的值指定分支的默认方向,这能大大提升算法的效率,paper提到50倍。
并行化处理
在训练之前,预先对每个特征内部进行了排序找出候选切割点,然后保存为block结构,后面的迭代中重复地使用这个结构,大大减小计算量。在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行,即在不同的特征属性上采用多线程并行方式寻找最佳分割点。
特征列排序后以块的形式存储在内存中,在迭代中可以重复使用;虽然boosting算法迭代必须串行,但是在处理每个特征列时可以做到并行。
优化导致每个样本的梯度信息在内存中不连续,直接累加有可能会导致cache-miss,所以xgboost先将样本的统计信息取到线程的内部buffer,然后再进行小批量的累加。
按照特征列方式存储能优化寻找最佳的分割点,但是当以行计算梯度数据时会导致内存的不连续访问,严重时会导致cache miss,降低算法效率。paper中提到,可先将数据收集到线程内部的缓存中,然后再计算,提高算法的效率。
参数
自定义损失函数
- 损失函数举例
- 针对Logistic loss求一阶二阶梯度
- 代码示例
#!/usr/bin/python
import numpy as np
import xgboost as xgb
###
# 定制损失函数
print ('开始运行示例,使用自定义的目标函数')dtrain = xgb.DMatrix('../data/agaricus.txt.train')
dtest = xgb.DMatrix('../data/agaricus.txt.test')param = {'max_depth': 2, 'eta': 1, 'silent': 1}
watchlist = [(dtest, 'eval'), (dtrain, 'train')]
num_round = 2# 用户定义目标函数,给出预测,返回梯度和二阶梯度
def logregobj(preds, dtrain):labels = dtrain.get_label()preds = 1.0 / (1.0 + np.exp(-preds))grad = preds - labelshess = 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)# 训练定制的目标
bst = xgb.train(param, dtrain, num_round, watchlist, logregobj, evalerror)
Xgboost参数
官方
常用参数:
一般参数:
booster[default=gbtree]选择基分类器 gbtree、gblinear 树或线性分类器
silent [default=0] 是否输出详细信息 0不输出 1输出
nthread [default to maximum number of threads available if not set]线程数默认最大
Tree Booster参数:
1. eta [default=0.3]:shrinkage参数,用于更新叶子节点权重时,乘以该系数,避免步长过大。参数值越大,越可能无法收敛。把学习率 eta 设置的小一些,小学习率可以使得后面的学习更加仔细。
2. min_child_weight [default=1]:这个参数默认是 1,是每个叶子里面 h 的和至少是多少,对正负样本不均衡时的 0-1 分类而言,假设 h 在 0.01 附近,min_child_weight 为 1 意味着叶子节点中最少需要包含 100 个样本。这个参数非常影响结果,控制叶子节点中二阶导的和的最小值,该参数值越小,越容易 overfitting。
3. max_depth [default=6]: 每颗树的最大深度,树高越深,越容易过拟合。
4. gamma [default=0]:在树的叶子节点上作进一步分区所需的最小损失减少。越大,算法越保守。[0,∞]
5. max_delta_step [default=0]:这个参数在更新步骤中起作用,如果取0表示没有约束,如果取正值则使得更新步骤更加保守。可以防止做太大的更新步子,使更新更加平缓。 通常,这个参数是不需要的,但它可能有助于逻辑回归时,类是非常不平衡。设置它的值为1-10可能有助于控制更新。
6. subsample [default=1]:样本随机采样,较低的值使得算法更加保守,防止过拟合,但是太小的值也会造成欠拟合。
7. colsample_bytree [default=1]:列采样,对每棵树的生成用的特征进行列采样.一般设置为: 0.5-1
8. lambda [default=1]:控制模型复杂度的权重值的L2正则化项参数,参数越大,模型越不容易过拟合。
9. alpha [default=0]:控制模型复杂程度的权重值的 L1 正则项参数,参数值越大,模型越不容易过拟合。
10. scale_pos_weight [default=1]如果取值大于0的话,在类别样本不平衡的情况下有助于快速收敛。
11. tree_method[default=’auto’]可选 {‘auto’, ‘exact’, ‘approx’} 贪心算法(小数据集)/近似算法(大数据集)
学习任务参数:
objective [ default=reg:linear ]定义最小化损失函数类型
最常用的值有:
binary:logistic 二分类的逻辑回归,返回预测的概率(不是类别)。
multi:softmax 使用softmax的多分类器,返回预测的类别(不是概率)。
在这种情况下,你还需要多设一个参数:num_class(类别数目)。
multi:softprob 和multi:softmax参数一样,但是返回的是每个数据属于各个类别的概率。
seed [ default=0 ]随机种子
eval_metric[根据目标objective默认]
对于有效数据的度量方法。
对于回归问题,默认值是rmse,对于分类问题,默认值是error。
典型值有:
rmse 均方根误差( ∑Ni=1ϵ2N‾‾‾‾‾‾‾√ )
mae 平均绝对误差( ∑Ni=1|ϵ|N )
logloss 负对数似然函数值
error 二分类错误率(阈值为0.5)
merror 多分类错误率
mlogloss 多分类logloss损失函数
auc 曲线下面积
命令行参数:
num_round 迭代次数/树的个数
GBDT与XGBoost区别
- 目标函数通过二阶泰勒展开式做近似。传统GBDT在优化时只用到一阶导数信息,xgboost则对代价函数进行了二阶泰勒展开,同时用到了一阶和二阶导数。注:支持自定义代价函数,只要函数可一阶和二阶求导。
- 定义了树的复杂度,即xgboost在代价函数里加入了正则项,用于控制模型的复杂度,正则项里包含了树的叶子节点个数、每个叶子节点上输出的score的L2模的平方和。代替了剪枝。
- 分裂结点处通过结构打分和分割损失动态生长。结构分数代替了回归树的误差平方和。
- 分裂结点特征分割点选取使用了近似算法-可并行的近似直方图算法。树节点在进行分裂时,我们需要计算每个特征的每个分割点对应的增益,即用贪心法枚举所有可能的分割点。当数据无法一次载入内存或者在分布式情况下,贪心算法效率就会变得很低,所以xgboost还提出了一种可并行的近似直方图算法,用于高效地生成候选的分割点。用于加速和减小内存消耗。
- 可以处理稀疏、缺失数据(节点分裂算法能自动利用特征的稀疏性),可以学习出它的分裂方向,加快稀疏计算速度。
- 列抽样(column subsampling)[传统GBDT没有],Shrinkage(缩减),相当于学习速率(xgboost中的eta)[传统GBDT也有]
- 支持并行化处理。xgboost的并行是在特征粒度上的,在训练之前,预先对特征进行了排序,然后保存为block结构,后面的迭代中重复地使用这个结构,大大减小计算量。在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行,即在不同的特征属性上采用多线程并行方式寻找最佳分割点。
- 传统GBDT以CART作为基分类器,xgboost还支持线性分类器,这个时候xgboost相当于带L1和L2正则化项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题)。通过booster [default=gbtree]设置参数:gbtree: tree-based models/gblinear: linear models。