hscredit.core.models.losses.focal_loss 源代码

"""
Focal Loss - 处理类别不平衡的损失函数

Focal Loss通过降低易分类样本的权重,专注于难分类样本,特别适合金融风控场景中的
不平衡数据问题(如坏账率通常很低)。
"""

import numpy as np
from typing import Optional
from .base import BaseLoss


[文档] class FocalLoss(BaseLoss): """Focal Loss,通过调整样本权重来解决类别不平衡问题。 数学公式: FL(p_t) = -α_t * (1 - p_t)^γ * log(p_t) 其中: p_t = p if y=1 else 1-p α_t = α if y=1 else 1-α :param alpha: 正样本权重,默认为0.25,用于平衡正负样本的总体权重 :param gamma: 聚焦参数,默认为2.0,控制易分类样本的权重衰减程度 - gamma=0: 等价于标准交叉熵 - gamma越大,易分类样本权重越小 :param name: 损失函数名称,默认为"focal_loss" **参考样例** >>> import numpy as np >>> from hscredit.core.models.losses import FocalLoss >>> >>> # 创建损失函数 >>> loss = FocalLoss(alpha=0.75, gamma=2.0) >>> >>> # 计算损失 >>> y_true = np.array([0, 0, 1, 1]) >>> y_pred = np.array([0.1, 0.4, 0.6, 0.9]) >>> loss_value = loss(y_true, y_pred) >>> >>> # 在XGBoost中使用 >>> import xgboost as xgb >>> dtrain = xgb.DMatrix(X_train, label=y_train) >>> params = {'objective': 'binary:logistic'} >>> bst = xgb.train(params, dtrain, obj=loss.to_xgboost(), num_boost_round=100) **引用** Lin, T.-Y., Goyal, P., Girshick, R., He, K., & Dollár, P. (2017). *Focal Loss for Dense Object Detection.* ICCV 2017. https://arxiv.org/abs/1708.02002 """ def __init__( self, alpha: float = 0.25, gamma: float = 2.0, name: str = "focal_loss" ): super().__init__(name) self.alpha = alpha self.gamma = gamma def __call__( self, y_true: np.ndarray, y_pred: np.ndarray ) -> float: """计算Focal Loss。 :param y_true: 真实标签, shape (n_samples,) :param y_pred: 预测概率, shape (n_samples,) :return: 平均损失值 """ # 确保概率在[0, 1]范围内 y_pred = np.clip(y_pred, 1e-7, 1 - 1e-7) # 计算p_t p_t = np.where(y_true == 1, y_pred, 1 - y_pred) # 计算alpha_t alpha_t = np.where(y_true == 1, self.alpha, 1 - self.alpha) # 计算focal weight focal_weight = (1 - p_t) ** self.gamma # 计算交叉熵 ce_loss = -np.log(p_t) # 计算focal loss focal_loss = alpha_t * focal_weight * ce_loss return np.mean(focal_loss)
[文档] def gradient( self, y_true: np.ndarray, y_pred: np.ndarray ) -> np.ndarray: """计算Focal Loss的梯度(一阶导数)。 推导过程: d(FL)/d(p) = d/dp [ -α_t * (1-p_t)^γ * log(p_t) ] :param y_true: 真实标签 :param y_pred: 预测概率 :return: 梯度数组 """ # 确保概率在合理范围内 y_pred = np.clip(y_pred, 1e-7, 1 - 1e-7) # 计算alpha_t alpha_t = np.where(y_true == 1, self.alpha, 1 - self.alpha) # 计算梯度 grad = np.zeros_like(y_pred) # 正样本梯度 pos_mask = y_true == 1 if np.any(pos_mask): p = y_pred[pos_mask] grad[pos_mask] = ( alpha_t[pos_mask] * ( self.gamma * (1 - p) ** (self.gamma - 1) * np.log(p) - (1 - p) ** self.gamma / p ) ) # 负样本梯度 neg_mask = y_true == 0 if np.any(neg_mask): p = y_pred[neg_mask] grad[neg_mask] = ( alpha_t[neg_mask] * ( -self.gamma * p ** (self.gamma - 1) * np.log(1 - p) + p ** self.gamma / (1 - p) ) ) return grad
[文档] def hessian( self, y_true: np.ndarray, y_pred: np.ndarray ) -> np.ndarray: """计算Focal Loss的二阶导数。 :param y_true: 真实标签 :param y_pred: 预测概率 :return: 二阶导数数组 """ # 确保概率在合理范围内 y_pred = np.clip(y_pred, 1e-7, 1 - 1e-7) # 计算alpha_t alpha_t = np.where(y_true == 1, self.alpha, 1 - self.alpha) # 计算二阶导数(简化版本) hess = np.zeros_like(y_pred) # 正样本二阶导 pos_mask = y_true == 1 if np.any(pos_mask): p = y_pred[pos_mask] hess[pos_mask] = alpha_t[pos_mask] * ( self.gamma * (self.gamma - 1) * (1 - p) ** (self.gamma - 2) * np.log(p) + 2 * self.gamma * (1 - p) ** (self.gamma - 1) / p + (1 - p) ** self.gamma / (p ** 2) ) # 负样本二阶导 neg_mask = y_true == 0 if np.any(neg_mask): p = y_pred[neg_mask] hess[neg_mask] = alpha_t[neg_mask] * ( self.gamma * (self.gamma - 1) * p ** (self.gamma - 2) * np.log(1 - p) + 2 * self.gamma * p ** (self.gamma - 1) / (1 - p) + p ** self.gamma / ((1 - p) ** 2) ) # 确保二阶导为正 hess = np.abs(hess) + 1e-6 return hess