XGBoost中多类别Focal Loss无法有效训练问题排查
XGBoost自定义多类别Focal Loss后预测概率始终均等的问题
我有一个包含60个特征变量、最后一列为4分类目标的数据集,由于数据极度不平衡,希望在XGBoost中实现自定义多类别Focal Loss损失函数。参考XGBoost官方文档编写了如下目标函数,并用SymPy验证了梯度与海森矩阵的正确性:
import numpy as np import xgboost from scipy.special import log_softmax, softmax from sklearn.utils import compute_sample_weight from sklearn.model_selection import train_test_split num_classes = 4 def focal_loss_multiclass_objective(inputs: np.ndarray, dtrain: xgboost.DMatrix, alpha=1, gamma=2, num_classes=4): # 将logits转换为概率和对数概率 log_prob = log_softmax(inputs) prob = np.exp(log_prob) print(f"prob: {prob}") # 对正确类别进行独热编码 targets = dtrain.get_label().astype(np.int64) targets_one_hot = np.identity(num_classes)[targets] # 梯度计算 grad = -alpha*targets_one_hot*((1-prob)**(gamma-1)) * (-gamma*log_prob + (1-prob)/(prob+1e-6)) print(f"grad: {grad}") # 海森矩阵计算 hess = -alpha*targets_one_hot*(gamma*(gamma-1)*((1-prob)**(gamma-2)) * log_prob - 2*gamma*((1-prob)**(gamma-1))/(prob+1e-6) - ((1-prob)**gamma)/(prob+1e-6)**2) print("hess: ", hess) return grad, hess
训练过程中,划分了训练集与测试集,使用平衡样本权重构建DMatrix,并设置了合理的模型参数:
X = df.iloc[:, 0:60] y = df.iloc[:, -1] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) # 样本权重:平衡类别权重 sample_weights = compute_sample_weight('balanced', y_train) print(np.unique(sample_weights)) # 创建DMatrix对象 dtrain = xgboost.DMatrix(X_train, label=y_train, weight=sample_weights) dtest = xgboost.DMatrix(X_test, label=y_test) params = { 'num_class': num_classes, #'disable_default_eval_metric': True, 'eval_metric': 'mlogloss', 'eta': 0.3, 'max_depth': 6, 'subsample': 0.8, 'colsample_bytree': 0.8, 'tree_method': 'auto' } # 训练模型 model = xgboost.train(params, dtrain, num_boost_round=100, obj=focal_loss_multiclass_objective)
但训练完成后,模型对测试集的预测概率始终为每类25%,无法有效完成训练,请问问题出在哪里?
预测代码如下:
y_pred_logits = model.predict(dtest, output_margin=True) y_prob = softmax(y_pred_logits, axis=1) print(y_pred_logits) print(y_prob)
问题原因与修复方案
1. 梯度/海森矩阵维度不匹配(核心问题)
XGBoost多分类场景下,输入的inputs是(n_samples, num_classes)的二维数组,但XGBoost要求返回的梯度和海森矩阵必须是扁平化的一维数组(将每个样本的num_classes个梯度值按顺序拼接)。当前代码直接返回二维数组,导致XGBoost无法正确解析梯度信息,模型无法更新,始终停留在初始的均等概率状态。
修复:在返回前将梯度和海森矩阵扁平化:
return grad.flatten(), hess.flatten()
2. Focal Loss公式推导不完整
当前代码仅计算了正确类别的梯度,忽略了其他类别的梯度项。多类别Focal Loss的梯度需要覆盖所有类别:
- 对于正确类别$t$,梯度需反映该类预测概率的误差
- 对于非正确类别$j$,梯度需体现其对正确类别概率的影响
建议重新推导标准多类别Focal Loss的梯度与海森矩阵,确保所有类别的梯度都被正确计算。
3. 样本权重与Loss参数重复应用
你同时在DMatrix中设置了weight=sample_weights,又在自定义Loss中保留了alpha参数,这会导致权重被重复叠加,可能使梯度值异常,干扰模型收敛。建议二选一:
- 去掉自定义Loss中的
alpha,仅使用DMatrix的样本权重 - 将
alpha替换为类别专属权重数组,而非全局统一值
4. 训练策略优化
修复上述问题后,可补充以下优化:
- 添加早停策略,避免过拟合或迭代不足:
watchlist = [(dtrain, 'train'), (dtest, 'test')] model = xgboost.train(params, dtrain, num_boost_round=500, obj=focal_loss_multiclass_objective, evals=watchlist, early_stopping_rounds=50)
- 暂时关闭默认评估指标(开启
disable_default_eval_metric=True),避免自定义Loss与默认指标的逻辑冲突
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

