You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

XGBoost自定义soft-Fbeta损失函数无法更新问题排查

问题描述

背景

尝试为XGBoost二分类器实现自定义soft-Fbeta损失函数,直接优化Fbeta分数以替代标准logloss。

注意事项

Fbeta本身不可微,需利用模型输出的阈值化前的对数几率构建连续的TP、FP、FN来实现可微的损失。

尝试实现的代码

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

相关说明

  • 即使将gradient和hessian改为常数,结果仍无变化
  • 最初返回的hessian是矩阵形式,改为一维数组后仍无改善
  • 模型始终预测0,完全不更新
  • 修改数据集大小后logloss值完全不变
  • 训练集和验证集logloss完全相同
  • 使用内置logloss时模型可正常更新

核心问题

实现中存在哪些错误?是否遗漏了XGBoost自定义损失的基础规则?


问题分析与修正

1. 核心错误:混淆了logits与概率

XGBoost的自定义objective函数接收的第二个参数不是概率p,而是未经过sigmoid转换的对数几率(logits)。你的代码直接把输入当成概率计算梯度,导致梯度计算完全错误,模型无法更新。必须先将logits转换为概率:

p = 1 / (1 + np.exp(-logits))

2. 海森矩阵的要求错误

XGBoost要求hessian返回一维数组(每个样本的二阶导数,即海森矩阵的对角线元素),而不是完整的矩阵。你的代码返回的是二维矩阵,这会导致XGBoost无法正确处理梯度更新。

3. 损失函数的符号与优化方向

XGBoost是最小化损失函数,而我们的目标是最大化Fbeta分数,因此损失应定义为-Fbeta,同时梯度和hessian的计算要对应这个方向。

修正后的代码

import numpy as np
import xgboost as xgb

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def gradient(y: np.array, logits: np.array, beta: float):
    p = sigmoid(logits)
    tp = np.dot(p, y)
    fp = p.sum() - tp
    fn = y.sum() - tp
    
    # 计算soft Fbeta的梯度(针对概率p)
    dF_dp = (1 + beta**2) * (y * (tp + beta**2 * fn) - tp * p) / (tp + beta**2 * fn)**2
    # 链式法则:dF_dlogits = dF_dp * dp_dlogits
    dp_dlogits = p * (1 - p)
    grad = dF_dp * dp_dlogits
    
    # 因为XGBoost最小化损失,损失是-Fbeta,所以梯度取负
    return -grad

def hessian(y: np.array, logits: np.array, beta: float):
    p = sigmoid(logits)
    tp = np.dot(p, y)
    fp = p.sum() - tp
    fn = y.sum() - tp
    
    dp_dlogits = p * (1 - p)
    # 计算二阶导数(针对logits)
    d2F_dp2 = (1 + beta**2) * ( -y * p * (tp + beta**2 * fn) + tp * y * p + tp * (tp + beta**2 * fn) * (1 - y) ) / (tp + beta**2 * fn)**3
    hess = d2F_dp2 * (dp_dlogits)**2 + dF_dp * dp_dlogits * (1 - 2*p)
    
    # 损失是-Fbeta,海森取负
    return -hess

def f_smooth_loss(beta: float):
    def custom_loss(y: np.array, logits: np.array):
        # 确保y是float类型,避免计算错误
        y = y.astype(np.float32)
        grad = gradient(y, logits, beta)
        hess = hessian(y, logits, beta)
        return grad, hess
    return custom_loss

# 随机数据集
X_train = np.random.rand(100, 100)
y_train = np.random.randint(0, 2, 100)
X_validation = np.random.rand(1000, 100)
y_validation = np.random.randint(0, 2, 1000)

# 定义模型,添加eval_metric避免显示默认logloss
model = xgb.XGBClassifier(
    objective=f_smooth_loss(5),
    eval_metric='logloss',
    learning_rate=0.1,
    n_estimators=10
)

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

额外说明

  • 添加eval_metric='logloss'是为了让训练过程显示logloss,若要监控Fbeta,可自定义评估函数并传入eval_metric
  • 确保输入的y转换为float类型,避免整数运算导致的错误
  • 学习率可根据实际需求调整

内容的提问来源于stack exchange,提问作者GiacomoP

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 02:14:57