Source code for boost_loss.regression.regression

from __future__ import annotations

from logging import getLogger

import attrs
import numpy as np
from numpy.typing import NDArray

from ..base import LossBase

LOG = getLogger(__name__)


# cannot freeze due to FrozenInstanceError in catboost
[docs]@attrs.define() class LNLoss(LossBase): """LNLoss = |y_true - y_pred|^n - x < 1 is not recommended because the loss is not convex. - x >> 2 is not recommended because the gradient is too steep.""" n: float """The exponent of the loss.""" divide_n_loss: bool = False """Whether to divide the loss by n. Generally False is used.""" divide_n_grad: bool = True """Whether to divide the gradient by n. Generally True is used.""" @property def name(self) -> str: return f"l{self.n:.2f}"
[docs] def loss(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.abs(y_pred - y_true) ** self.n / (self.n if self.divide_n_loss else 1)
[docs] def grad(self, y_true: NDArray, y_pred: NDArray) -> NDArray: y_diff = y_pred - y_true return ( np.abs(y_diff) ** (self.n - 1) * np.sign(y_diff) * self.n / (self.n if self.divide_n_grad else 1) )
[docs] def hess(self, y_true: NDArray, y_pred: NDArray) -> NDArray: y_diff = y_pred - y_true return ( (self.n - 1) * np.abs(y_diff) ** (self.n - 2) * self.n / (self.n if self.divide_n_grad else 1) )
[docs]class L1Loss(LNLoss): """L1 loss = |y_true - y_pred|.""" def __init__( self, *, divide_n_loss: bool = False, divide_n_grad: bool = True ) -> None: """L1 loss. Parameters ---------- divide_n_loss : bool, optional Whether to divide the loss by n, by default False divide_n_grad : bool, optional Whether to divide the gradient by n, by default True """ super().__init__(n=1, divide_n_loss=divide_n_loss, divide_n_grad=divide_n_grad)
[docs] def hess(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.ones_like(y_pred - y_true) # zero hess is not allowed
[docs]class L2Loss(LNLoss): """L2 loss = |y_true - y_pred|^2""" def __init__( self, *, divide_n_loss: bool = False, divide_n_grad: bool = True ) -> None: """L2 loss. Parameters ---------- divide_n_loss : bool, optional Whether to divide the loss by n, by default False divide_n_grad : bool, optional Whether to divide the gradient by n, by default True """ super().__init__(n=2, divide_n_loss=divide_n_loss, divide_n_grad=divide_n_grad)
[docs]class LogCoshLoss(LossBase): """LogCosh loss = log(cosh(y_true - y_pred))"""
[docs] def loss(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.log(np.cosh(y_pred - y_true))
[docs] def grad(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.tanh(y_pred - y_true)
[docs] def hess(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.cosh(y_pred - y_true) ** -2
[docs]class HuberLoss(LossBase): """Huber loss = 0.5 (y_true - y_pred)^2 if |y_true - y_pred| <= delta else delta * (|y_true - y_pred| - 0.5 * delta)""" def __init__(self, delta: float = 1.0) -> None: """Huber loss. Parameters ---------- delta : float, optional The parameter delta, by default 1.0 """ self.delta = delta
[docs] def loss(self, y_true: NDArray, y_pred: NDArray) -> NDArray: y_diff = y_pred - y_true return np.where( np.abs(y_diff) <= self.delta, 0.5 * y_diff**2, self.delta * (np.abs(y_diff) - 0.5 * self.delta), )
[docs] def grad(self, y_true: NDArray, y_pred: NDArray) -> NDArray: y_diff = y_pred - y_true return np.where( np.abs(y_diff) <= self.delta, y_diff, np.sign(y_diff) * self.delta, )
[docs] def hess(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.ones_like(y_pred - y_true) # zero hess is not allowed
# return np.where( # np.abs(y_pred - y_true) <= self.delta, # np.ones_like(y_true), # np.zeros_like(y_true), # )
[docs]class FairLoss(LossBase): """Fair loss = c^2/2 * (abs(y_true - y_pred) - c * log(1 + abs(y_true - y_pred)/c))""" def __init__(self, c: float = 1.0) -> None: """Fair loss. Parameters ---------- c : float, optional The parameter c, by default 1.0 """ self.c = c
[docs] def loss(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return ( self.c**2 / 2 * ( np.abs(y_true - y_pred) - self.c * np.log(1 + np.abs(y_true - y_pred) / self.c) ) )
[docs] def grad(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return ( self.c**2 / 2 * (np.sign(y_pred - y_true) - self.c / (self.c + np.abs(y_pred - y_true))) )
[docs] def hess(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return ( self.c**2 / 2 * (np.zeros_like(y_true) + self.c / (self.c + np.abs(y_pred - y_true)) ** 2) )
[docs]class PoissonLoss(LossBase): """Poisson loss = y_pred - y_true * log(y_pred)"""
[docs] def loss(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return y_pred - y_true * np.log(y_pred)
[docs] def grad(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return 1 - y_true / y_pred
[docs] def hess(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return y_true / y_pred**2
[docs]class LogLoss(LossBase): """Log loss = log(1 + exp(-y_true * y_pred))"""
[docs] def loss(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.log(1 + np.exp(-y_true * y_pred))
[docs] def grad(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return -y_true / (1 + np.exp(y_true * y_pred))
[docs] def hess(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return ( y_true**2 * np.exp(y_true * y_pred) / (1 + np.exp(y_true * y_pred)) ** 2 )
[docs]class MSLELoss(LossBase): """Mean squared logarithmic error loss = (log(1 + y_true) - log(1 + y_pred))^2"""
[docs] def loss(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.square(np.log1p(y_true) - np.log1p(y_pred))
[docs] def grad(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return -2 * (np.log1p(y_true) - np.log1p(y_pred)) / (1 + y_pred)
[docs] def hess(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return 2 * (np.log1p(y_true) - np.log1p(y_pred)) / (1 + y_pred) ** 2
[docs]class MAPELoss(LossBase): """Mean absolute percentage error loss = abs(y_true - y_pred) / y_true"""
[docs] def loss(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.abs(y_true - y_pred) / y_true
[docs] def grad(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.sign(y_pred - y_true) / y_true
[docs] def hess(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.zeros_like(y_true)
[docs]class SMAPELoss(LossBase): """Symmetric mean absolute percentage error loss = abs(y_true - y_pred) / ((abs(y_true) + abs(y_pred))/2)"""
[docs] def loss(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.abs(y_true - y_pred) / ((np.abs(y_true) + np.abs(y_pred)) / 2)
[docs] def grad(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.sign(y_pred - y_true) / ((np.abs(y_true) + np.abs(y_pred)) / 2)
[docs] def hess(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return np.zeros_like(y_true)
[docs]class TweedieLoss(LossBase): """Tweedie loss = -y_true * y_pred^(2-p) / (2-p) + y_pred^(1-p) / (1-p)""" def __init__(self, p: float = 1.5) -> None: """Tweedie loss. Parameters ---------- p : float, optional The parameter p, by default 1.5 """ self.p = p
[docs] def loss(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return -y_true * y_pred ** (2 - self.p) / (2 - self.p) + y_pred ** ( 1 - self.p ) / (1 - self.p)
[docs] def grad(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return -y_true * (2 - self.p) * y_pred ** (1 - self.p) + y_pred ** (-self.p)
[docs] def hess(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return y_true * (2 - self.p) * (1 - self.p) * y_pred ** ( -self.p - 1 ) - self.p * y_pred ** (-self.p - 1)
[docs]class GammaLoss(LossBase): """Gamma loss = y_true / y_pred - log(y_true / y_pred) - 1"""
[docs] def loss(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return y_true / y_pred - np.log(y_true / y_pred) - 1
[docs] def grad(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return -y_true / y_pred**2 + 1 / y_pred
[docs] def hess(self, y_true: NDArray, y_pred: NDArray) -> NDArray: return 2 * y_true / y_pred**3 - 1 / y_pred**2