xgboost内置了足够丰富的目标函数(objective function),正常来说是能够应付日常需求的,如果~万一~你有特殊需求,它也可以自定义目标函数,或者叫损失函数(loss function)

xgboost的documentation里有介绍如何自定义目标函数,https://xgboost.readthedocs.io/en/stable/tutorials/custom_metric_obj.html

Squared Log Error

按照文档中Squared Log Error (SLE),记录一下这个过程

Squared Log Error:

$$\frac{1}{2}\left [ \log (\hat{y} + 1) – \log (y + 1) \right ]^2$$

其中$y$是实际值,$\hat{y}$是预测值,$\log$里面加1是避免出现$\log(0)$的情况。我们可以直接用损失函数来评估模型的好坏,或者用metric来评估,比如下面这个,文档里叫它Root Mean Squared Log Error(RMSLE)

$$\sqrt{\frac{1}{N}\left [ \log (\hat{y} + 1) – \log (y + 1) \right ]^2}$$

函数实现

可以把我们自己写的目标函数丢给xgboost.train方法,这样训练过程中目标函数和metric就会按照我们自定义的进行fit。看一下documentation中xgboost.train的部分:

https://xgboost.readthedocs.io/en/stable/python/python_api.html

正如你想象的那样,obj参数接收的是自定义的目标函数,feval是自定义的metric,目标函数(obj)接收两个参数:一个是predt, np.ndarray格式的,表示前面$i-1$轮迭代后输出的预测值$\hat{y}$,可以标记为:

$$F_{i-1}(x) = \sum_{i-1}f_i(x)$$

其中$f_i$是基学习器。另一个dtrain, xgb.DMatrix格式的,装了一些训练集的信息,features并不会传送过来,因为太大了,并且计算object也用不上。需要返回两个np.ndarray的变量,一个是gradient, 另一个是hessian,即一阶和二阶导数。如果你看过上篇的内容,可能会有疑问为什么要用二阶导数?这个在后面的文章再介绍吧~

Gradient:

$$\frac{\partial \text{obj}}{\partial \hat{y}} = \frac{\log(\hat{y} + 1 – \log(y+1)}{\hat{y} + 1}$$

Hessian:

$$\frac{\partial ^2\text{obj}}{\partial \hat{y}^2} = \frac{1 + \log (\hat{y} + 1) – \log(y+1)}{(\hat{y} + 1)^2}$$

用Python实现上述两个公式:

def gradient(y_pred, y_true):
    numerator = np.log(y_pred + 1) - np.log(y_true + 1)
    denominator = y_pred + 1
    return numerator / denominator

def hessian(y_pred, y_true):
    numerator = 1 + np.log(y_true + 1) - np.log(y_pred + 1)
    denominator = np.power(y_pred + 1, 2)
    return numerator / denominator

按照API的要求实现obj函数:

def objective_function(pred, dtrain):
    y_true = dtrain.get_label()
    grad = gradient(pred, y_true)
    hess = hessian(pred, y_true)
    return grad, hess

顺便按照要求把metric也实现了:

def evaluate_function(pred, dtrain):
    y_true = dtrain.get_label()
    n = len(pred)
    evaluate_name = 'rmsle'
    evaluate_value = np.sqrt(np.mean(np.power(np.log(pred + 1) - np.log(y_true + 1), 2)))
    return evaluate_name, evaluate_value

然后在train模型的时候像这样装填一下我们自定义的obj和feval

bst = xgb.train(param, 
                train_data, 
                num_boost_round=2, 
                evals=[(test_data, 'test')], 
                verbose_eval=True, 
                obj = objective_function,
                feval = evaluate_function
               )

大概就是这些了,xgboost只是个引子,后面我们展开聊一下LightGBM自定义目标函数的事。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注