Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Added the pseudo quantile Huber loss #13

Merged
merged 2 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mqboost/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class ModelName(BaseEnum):
class ObjectiveName(BaseEnum):
check: str = "check"
huber: str = "huber"
phuber: str = "phuber"


class TypeName(BaseEnum):
Expand Down
41 changes: 38 additions & 3 deletions mqboost/objective.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ def _grad_rho(u: np.ndarray, alpha: float) -> np.ndarray:
return (u < 0).astype(int) - alpha


def _hess_rho(u: np.ndarray, alpha: float) -> np.ndarray:
RektPunk marked this conversation as resolved.
Show resolved Hide resolved
_h = np.ones_like(u)
return _h


def _rho(u: np.ndarray, alpha: float) -> np.ndarray:
"""
Compute the check loss function.
Expand Down Expand Up @@ -62,6 +67,18 @@ def _grad_huber(u: np.ndarray, alpha: float, delta: float) -> np.ndarray:
return _r * _smaller_delta + _g * _bigger_delta


def _grad_phuber(u: np.ndarray, alpha: float, delta: float) -> np.ndarray:
RektPunk marked this conversation as resolved.
Show resolved Hide resolved
scale = delta**2 + u**2
_g = -abs(_grad_rho(u, alpha)) * u / scale ** (1 / 2)
return _g


def _hess_phuber(u: np.ndarray, alpha: float, delta: float) -> np.ndarray:
RektPunk marked this conversation as resolved.
Show resolved Hide resolved
scale = 1 + (u / delta) ** 2
_h = (1 / delta) * abs(_grad_rho(u, alpha)) / (scale ** (3 / 2))
return _h


def _train_pred_reshape(
y_pred: np.ndarray,
dtrain: DtrainLike,
Expand All @@ -85,6 +102,7 @@ def _compute_grads_hess(
dtrain: DtrainLike,
alphas: List[float],
grad_fn: Callable[[np.ndarray, float, Any], np.ndarray],
hess_fn: Callable[[np.ndarray, float, Any], np.ndarray],
**kwargs: Any,
) -> Tuple[np.ndarray, np.ndarray]:
"""
Expand All @@ -94,6 +112,7 @@ def _compute_grads_hess(
dtrain (DtrainLike): The training data.
alphas (List[float]): List of quantile levels.
grad_fn (Callable): The gradient function to be used.
hess_fn (Callable): The Hessian function to be used.
**kwargs (Any): Additional arguments for the gradient function.

Returns:
Expand All @@ -104,15 +123,26 @@ def _compute_grads_hess(
y_pred=y_pred, dtrain=dtrain, len_alpha=_len_alpha
)
grads = []
hess = []
for alpha_inx in range(len(alphas)):
_err_for_alpha = _y_train[alpha_inx] - _y_pred[alpha_inx]
_grad = grad_fn(u=_err_for_alpha, alpha=alphas[alpha_inx], **kwargs)
_hess = hess_fn(u=_err_for_alpha, alpha=alphas[alpha_inx], **kwargs)
grads.append(_grad)
return np.concatenate(grads), np.ones_like(y_pred)
hess.append(_hess)

return np.concatenate(grads), np.concatenate(hess)

check_loss_grad_hess: Callable = partial(_compute_grads_hess, grad_fn=_grad_rho)
huber_loss_grad_hess: Callable = partial(_compute_grads_hess, grad_fn=_grad_huber)

check_loss_grad_hess: Callable = partial(
_compute_grads_hess, grad_fn=_grad_rho, hess_fn=_hess_rho
)
huber_loss_grad_hess: Callable = partial(
_compute_grads_hess, grad_fn=_grad_huber, hess_fn=_hess_rho
)
phuber_loss_grad_hess: Callable = partial(
_compute_grads_hess, grad_fn=_grad_phuber, hess_fn=_hess_phuber
)


def _eval_check_loss(
Expand Down Expand Up @@ -209,6 +239,11 @@ def __init__(
self._fobj = partial(huber_loss_grad_hess, alphas=alphas, delta=self._delta)
elif objective == ObjectiveName.check:
self._fobj = partial(check_loss_grad_hess, alphas=alphas)
elif objective == ObjectiveName.phuber:
self._delta = delta_validate(delta=delta)
self._fobj = partial(
phuber_loss_grad_hess, alphas=alphas, delta=self._delta
)

self._eval_name = CHECK_LOSS
if model == ModelName.lightgbm:
Expand Down
2 changes: 1 addition & 1 deletion mqboost/regressor.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def __init__(
params: Dict[str, Any],
model: str = ModelName.lightgbm.value,
objective: str = ObjectiveName.check.value,
delta: float = 0.05,
delta: float = 0.01,
) -> None:
"""Initialize the MQRegressor."""
self._params = params
Expand Down