XGBoost中的自定义损失未更新。

huangapple go评论82阅读模式
英文:

Custom loss in XGBoost is not updating

问题

Output

模型运行,但输出似乎卡住了,无论如何都没有变化:

[0]	validation_0-logloss:0.69315	validation_1-logloss:0.69315
[1]	validation_0-logloss:0.69315	validation_1-logloss:0.69315
[2]	validation_0-logloss:0.69315	validation_1-logloss:0.69315
[3]	validation_0-logloss:0.69315	validation_1-logloss:0.69315

Comments

  1. 可能我的导数计算不正确,尽管我已经仔细检查过。然而,即使将grad和hess更改为常数,也没有任何变化。

  2. 这里的Hessian矩阵(这是其数学定义)可能与XGBoost期望的1D数组不符(我认为它是对角线)。然而,由于第1点,即使我将其更改为1D数组,也没有任何变化。

  3. 本质上,这个模型总是预测为零,并且根本不更新。

  4. 更改(虚假)数据集的大小不会导致对数损失的任何变化(更糟糕的是,数字完全相同)。

  5. 有趣的是,验证集和训练集的对数损失是相同的,这是另一个信号,说明某些地方出了问题。

  6. 如果我切换到标准的对数损失(内置的),它会更新(输出是随机的,因为数据集是随机的)。

Question

我的实现有什么问题?XGBoost文档非常难以理解,我真的无法确定是否漏掉了一个简单的基本构建块。

英文:

Context

I am trying to use a custom loss function for an XGBoost binary classifier.

The idea was to implement in XGBoost the soft-Fbeta loss, which I read about here. Simply put: instead of using the standard logloss, use a loss function that directly optimises the Fbeta score.

Caveat

Of course, the Fbeta itself is not differentiable, so it can't be used straight out of the box. However, the idea is to use the probabilities (hence, before thresholding) to create some sort of continuous TP, FP and FN. Find more details in the referenced Medium article.

Attempt

My attempt was the following (inspired by few different people).

import numpy as np
import xgboost as xgb

def gradient(y: np.array, p: np.array, beta: float):

    """Compute the gradient of the loss function. y is the true label, p
    the probability predicted by the model """
    
    # Define the denominator
    D = p.sum() + beta**2 * y.sum() 
    
    # Compute the gradient
    grad = (1 + beta**2) * y / D - (1 + beta**2) * (np.dot(p, y)) / D**2 
        
    return grad

def hessian(y: np.array, p: np.array, beta: float):

    """Compute the Hessian of the loss function. y is the true label, p
    the probability predicted by the model """
    
    # Define the denominator
    D = p.sum() + beta**2 * y.sum() 
    
    # Tensor sum y_i + y_j
    tensor_sum = y + y[:, None]
    
    # Compute the hessian
    hess = (1 + beta**2) / D**2 * (-tensor_sum + 2*np.dot(p, y) / D)
    
    return hess

def f_smooth_loss(beta: float):
    
    """ Custom loss function for maximising F score"""
    def custom_loss(y: np.array, p: np.array):
                
        # Actual custom loss
        b = beta
        
        # Compute grad
        grad = - gradient(y, p, b)
        
        # Compute hessian
        hess = - hessian(y, p, b)
                  
        return grad, hess
        
    return custom_loss

# Random train dataset
X_train = np.random.rand(100, 100)
y_train = np.random.randint(0, 2, 100)

# Random validation dataset
X_validation = np.random.rand(1000, 100)
y_validation = np.random.randint(0, 2, 1000)

# Define a classifier trying to maximise F5 score
model = xgb.XGBClassifier(objective=f_smooth_loss(5))

# Fit
model.fit(X_train, y_train,  eval_set=[(X_train, y_train), (X_validation, y_validation)])

Output

The model runs, but the output is apparently stuck, no matter what:

[0]	validation_0-logloss:0.69315	validation_1-logloss:0.69315
[1]	validation_0-logloss:0.69315	validation_1-logloss:0.69315
[2]	validation_0-logloss:0.69315	validation_1-logloss:0.69315
[3]	validation_0-logloss:0.69315	validation_1-logloss:0.69315

Comments

  1. It is possible my derivatives are not correct, even though I double checked them. However, even changing the grad and hess to constant numbers, nothing changes.

  2. The Hessian here is a matrix (which would be its mathematical definition), but I think XGBoost expects a 1D array (I think it is the diagonal). However, because of point 1., nothing changes even if I change it to a 1d-array

  3. Essentially, this model always predicts zeros, and does not update at all.

  4. Changing the size of the (fake) dataset does not lead to any change in the logloss (even more, the numbers are exactly the same).

  5. Curiously, the logloss is the same in the validation and train, this being yet another signal that there is something deeply wrong somewhere.

  6. If I switch to the standard logloss (built-in), it updates (outputs are random, as the dataset is random).

Question

What is wrong in my implementation? XGB docs are pretty hard to decipher, and I can't really tell if I am missing a simple building block here.

答案1

得分: 2

问题是,根据文档,自定义损失函数需要以下参数作为输入:


....

def f_smooth_loss(beta: float):
    
    """用于最大化F分数的自定义损失函数"""
    def custom_loss(
        predt: np.ndarray,
        dtrain: xgb.DMatrix
    ) -> Tuple[np.ndarray, np.ndarray]:
                
        # 实际的自定义损失
        b = beta
        
        # 计算梯度
        grad = - gradient(dtrain, predt, b)
        
        # 计算Hessian矩阵
        hess = - hessian(dtrain, predt, b)
                  

        return grad, hess
        
    return custom_los


更新:根据所引用的文档,似乎需要在类的.train()方法中传递该函数,而不是在初始化模型时,例如:

xgb.train({'tree_method': 'hist', 'seed': 1994},  # 任何其他树方法都可以。
           dtrain=dtrain,
           num_boost_round=10,
           obj=f_smooth_loss(5))

另外,请注意,.fit()方法是XGBoost作为与其他sklearn对象(例如sklearn.pipeline)交互的接口而存在的包装器,因此它可能缺少此功能,最好使用本机方法.train()

英文:

The problem is that following the docs the custom loss function need the following parameters as input:


....


def f_smooth_loss(beta: float):
    
    """ Custom loss function for maximising F score"""
    def custom_loss(
        predt: np.ndarray,
        dtrain: xgb.DMatrix
    ) -> Tuple[np.ndarray, np.ndarray]:
                
        # Actual custom loss
        b = beta
        
        # Compute grad
        grad = - gradient(dtrain, predt, b)
        
        # Compute hessian
        hess = - hessian(dtrain, predt, b)
                  
        return grad, hess
        
    return custom_los


Update: following the documentation referenced about it seems that you need to pass the function in the .train() of the class not when initializing the model, e.g.:

xgb.train({'tree_method': 'hist', 'seed': 1994},  # any other tree method is fine.
           dtrain=dtrain,
           num_boost_round=10,
           obj=f_smooth_loss(5))

Also, notice that the .fit() method is a wrapper that XGBoost has as a interface to interact with other sklearn objects (e.g. sklearn.pipeline) so it might lack this functionality, so it's better to use the native method .train().

答案2

得分: 0

请将分类器从objective=f_smooth_loss(5)更改为scoring=f_smooth_loss(5)

model = xgb.XGBClassifier(scoring=f_smooth_loss(5))
英文:

Please change the classifier from objective=f_smooth_loss(5) to scoring=f_smooth_loss(5):

model = xgb.XGBClassifier(scoring = f_smooth_loss(5))

huangapple
  • 本文由 发表于 2023年6月15日 19:22:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/76481951.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定