背景
我正在尝试对 XGBoost 二元分类器使用自定义损失函数。
这个想法是在 XGBoost 中实现 soft-Fbeta 损失,我在here读到了相关内容。简单地说:不使用标准的对数损失,而是使用直接优化 Fbeta 分数的损失函数。
注意事项
当然,Fbeta本身是不可微分的,所以不能直接开箱即用。然而,我们的想法是使用概率(因此,在阈值化之前)来创建某种连续的 TP、FP 和 FN。在参考的 Medium 文章中查找更多详细信息。
尝试
我的尝试如下(受到几个不同人的启发)。
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)])
输出
模型运行,但输出显然卡住了,无论如何:
[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
评论
我的导数可能不正确,即使我仔细检查了它们。然而,即使将 grad 和 hess 更改为常数,也没有任何变化。
这里的 Hessian 矩阵是一个矩阵(这将是它的数学定义),但我认为 XGBoost 需要一个一维数组(我认为它是对角线)。然而,由于第 1 点,即使我将其更改为一维数组,也不会发生任何变化
本质上,这个模型总是预测零,并且根本不更新。
更改(假)数据集的大小不会导致对数损失发生任何变化(甚至,数字完全相同)。
奇怪的是,验证和训练中的对数损失是相同的,这是另一个信号,表明某处存在严重错误。
如果我切换到标准对数损失(内置),它会更新(输出是随机的,因为数据集是随机的)。
问题
我的实施有什么问题? XGB 文档非常难以解读,我真的无法判断我是否缺少一个简单的构建块。
问题是,按照docs,自定义损失函数需要以下参数作为输入:
....
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
更新:根据引用的文档似乎您需要在类的
.train()
中传递函数,而不是在初始化模型时传递函数,例如:
xgb.train({'tree_method': 'hist', 'seed': 1994}, # any other tree method is fine.
dtrain=dtrain,
num_boost_round=10,
obj=f_smooth_loss(5))
另外,请注意
.fit()
方法是 XGBoost 的包装器,作为与其他 sklearn 对象(例如 sklearn.pipeline)交互的接口,因此它可能缺少此功能,因此最好使用本机方法 .train()
。
请将分类器从
objective=f_smooth_loss(5)
更改为 scoring=f_smooth_loss(5)
:
model = xgb.XGBClassifier(scoring = f_smooth_loss(5))