model-tester / src /focal.py
Arkm20's picture
Create focal.py
dd5586d verified
"""
focal.py — Focal loss objective and eval for LightGBM.
Extracted from sniper_v7_1.py so SniperModel can import them
without depending on __main__.
"""
import numpy as np
from scipy.special import expit
# These are read from Config at training time; we keep the same defaults.
FOCAL_GAMMA = 2.0
FOCAL_ALPHA = 0.25
def focal_loss_objective(y_pred, dtrain):
y_true = dtrain.get_label()
gamma = FOCAL_GAMMA
alpha = FOCAL_ALPHA
p = np.clip(expit(y_pred), 1e-7, 1 - 1e-7)
p_t = np.where(y_true == 1, p, 1 - p)
alpha_t = np.where(y_true == 1, alpha, 1 - alpha)
log_p_t = np.log(p_t + 1e-10)
dp_t_dz = np.where(y_true == 1, p * (1 - p), -p * (1 - p))
term1 = -gamma * log_p_t
term2 = (1 - p_t) / (p_t + 1e-10)
grad = -alpha_t * dp_t_dz * ((1 - p_t) ** (gamma - 1)) * (term1 + term2)
abs_grad = np.abs(grad)
hess = np.maximum(abs_grad * (2.0 - abs_grad), 1e-5)
return grad, hess
def focal_loss_eval(y_pred, dtrain):
y_true = dtrain.get_label()
p = np.clip(expit(y_pred), 1e-7, 1 - 1e-7)
p_t = np.where(y_true == 1, p, 1 - p)
alpha_t = np.where(y_true == 1, FOCAL_ALPHA, 1 - FOCAL_ALPHA)
loss = -alpha_t * (1 - p_t) ** FOCAL_GAMMA * np.log(p_t + 1e-10)
return "focal_loss", float(np.mean(loss)), False