前言:在有些比赛中,需要根据自己的需求来自定义目标函数和评估函数,就自己而言,目标函数需要自定义的场景不太多。为了充分发挥xgboost的框架作用,很多时候自定义评估函数的需求相对强烈。在之前的博文中提到复现Eve的时候,那篇文章主要是基于Keras,自定义了Optimizer,称为Eve。对于主流框架,模块即插即拔,因此我们需要插拔的手艺。文章主要从DMLC的demo出发,针对二分类问题,推导对数似然损失函数的导数,自定义评估函数。
二分类问题的对数似然损失函数如下:
一阶导数为:
二阶导数为:
下面是DMLC中给出的一个DEMO:
#!/usr/bin/python | |
import numpy as np | |
import xgboost as xgb | |
### | |
# advanced: customized loss function | |
# | |
print ('start running example to used customized objective function') | |
dtrain = xgb.DMatrix('agaricus.txt.train') | |
dtest = xgb.DMatrix('agaricus.txt.test') | |
# note: for customized objective function, we leave objective as default | |
# note: what we are getting is margin value in prediction | |
# you must know what you are doing | |
param = {'max_depth': 2, 'eta': 1, 'silent': 1} | |
watchlist = [(dtest, 'eval'), (dtrain, 'train')] | |
num_round = 2 | |
# user define objective function, given prediction, return gradient and second order gradient | |
# this is log likelihood loss | |
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 | |
# user defined evaluation function, return a pair metric_name, result | |
# NOTE: when you do customized loss function, the default prediction value is margin | |
# this may make builtin evaluation metric not function properly | |
# for example, we are doing logistic loss, the prediction is score before logistic transformation | |
# the builtin evaluation error assumes input is after logistic transformation | |
# Take this in mind when you use the customization, and maybe you need write customized evaluation function | |
def evalerror(preds, dtrain): | |
labels = dtrain.get_label() | |
# return a pair metric_name, result | |
# since preds are margin(before logistic transformation, cutoff at 0) | |
return 'error', float(sum(labels != (preds > 0.0))) / len(labels) | |
# training with customized objective, we can also do step by step training | |
# simply look at xgboost.py's implementation of train | |
bst = xgb.train(param, dtrain, num_round, watchlist, logregobj, evalerror) |
xgboost.train中有两个重要参数: obj和feval。其中obj是目标函数,feval是评估函数。目标函数是模型优化的目标,直接用来衡量预测值和真实值之间的差距,常见的例如对数似然损失函数,均方差函数等。评估函数是对预测结果的评估,例如 R2-score等。
读上述代码,自定义了对数似然损失函数,在文档中说,自定义的函数通常要定义目标函数的一阶和二阶导数,但是二阶导数可以不定义。XGBOOST在实现的时候用到了目标函数的二阶导数信息,不同于其他的GBDT实现,只用一阶导数信息。在函数定义中,代码给定了grad和hess。上述结果是化简后的定义,没有化简的定义如下(代码来自参考1):
def custom_loss(y_pre,D_label): #别人的自定义损失函数 | |
label=D_label.get_label() | |
penalty=2.0 | |
grad=-label/y_pre+penalty*(1-label)/(1-y_pre) #梯度 | |
hess=label/(y_pre**2)+penalty*(1-label)/(1-y_pre)**2 #2阶导 | |
return grad,hess |
feval的定义是应该预测为正的样本中预测错误的样本占总样本数目的比例。从直觉上来说,feval的定义是根据我们对预测结果的关注点来决定的。
通常的时候,可以把目标函数做为评估函数,比如RMSE。在有些数据挖掘比赛中,对于存在一阶导数和二阶导数的评估函数,看到过有些同学直接将评估函数作为目标函数。
下面是feval的f1_score定义,在二分类模型的预测评估中,当召回率(原来样本)和精确率(预测样本)发生冲突时有效评估模型质量的方式:
def f1_error(preds,dtrain): | |
label=dtrain.get_label() | |
preds = 1.0/(1.0+np.exp(-preds)) | |
pred = [int(i >= 0.5) for i in preds] | |
tp = sum([int(i == 1 and j == 1) for i,j in zip(pred,label)]) | |
precision=float(tp)/sum(pred) | |
recall=float(tp)/sum(label) | |
return 'f1-score',2 * ( precision*recall/(precision+recall) ) |
总结: 自定义是框架使用的高级技能,为了实现同样的目标,自定义不是必要的。但是通过自定义,可以用更少的代码更好的发挥框架的功能,比如,在每轮训练后打印评估函数的值。类似的事情如GridSearch实现网格搜索,并行调参。当然可以自己写cross-validation和并行处理,但是基于框架所提供的API,可以享受到诸多方便。
参考:
比赛中的评估函数相对特别,作者写了自己的feval。在文章中给了一个自定义obj,是对数似然损失没有化简的情形。