Skip to content
Snippets Groups Projects
loss.py 6.81 KiB
Newer Older
  • Learn to ignore specific revisions
  • johannes bilk's avatar
    johannes bilk committed
    import numpy as np
    from numpy.typing import ArrayLike
    from abc import ABC, abstractmethod
    
    
    class LossFunction(ABC):
        """
        Base class for all loss functions.
        """
        __slots__ = ['name', 'prediction', 'target']
    
        def __init__(self) -> None:
            self.name = self.__class__.__name__
    
        def forward(self, prediction: ArrayLike, target: ArrayLike) -> float:
            """
            Computes the loss for the given prediction and target values.
            """
            self.prediction = prediction
            self.target = target
            loss = self._function(prediction, target)
            return np.mean(loss)
    
        def __call__(self, prediction: ArrayLike, target: ArrayLike) -> float:
            """
            Allows the instance of the LossFunction class to be called like a function.
            """
            return self.forward(prediction, target)
    
        def backward(self) -> np.ndarray:
            """
            Computes the derivative of the loss with respect to the predicted values.
            """
            return self._derivative()
    
        @abstractmethod
        def _function(self, prediction: ArrayLike, target: ArrayLike) -> float:
            """
            Computes the loss for the given prediction and target values.
            """
            pass
    
        @abstractmethod
        def _derivative(self) -> np.ndarray:
            """
            Computes the derivative of the loss with respect to the predicted values.
            """
            pass
    
        def __str__(self) -> str:
            """
            used for print the layer in a human readable manner
            """
            return self.name
    
    
    class MAELoss(LossFunction):
        """
        Mean Absolute Error (MAE) loss function for regression tasks.
        """
        __slots__ = []
    
        def __init__(self) -> None:
            super().__init__()
    
        def _function(self, prediction: ArrayLike, target: ArrayLike) -> float:
            """
            Computes the mean absolute error between the predicted and target values.
            """
            return np.abs(target - prediction)
    
        def _derivative(self) -> np.ndarray:
            """
            Computes the derivative of the mean absolute error with respect to the predicted values.
            """
            return np.sign(self.prediction - self.target) / self.target.size
    
    
    class MSELoss(LossFunction):
        """
        Mean Squared Error (MSE) loss function for regression tasks.
        """
        __slots__ = []
    
        def __init__(self) -> None:
            super().__init__()
    
        def _function(self, prediction: ArrayLike, target: ArrayLike) -> float:
            """
            Computes the mean squared error between the predicted and target values.
            """
            return np.power(target - prediction, 2)
    
        def _derivative(self) -> np.ndarray:
            """
            Computes the derivative of the mean squared error with respect to the predicted values.
            """
            return 2 * (self.prediction - self.target) / self.target.size
    
    
    class HuberLoss(LossFunction):
        """
        Class for implementing the Huber loss function for regression tasks.
        """
        __slots__ = ['delta']
    
        def __init__(self, delta: float = 1.0) -> None:
            super().__init__()
            self.delta = delta
    
        def _function(self, prediction: ArrayLike, target: ArrayLike) -> float:
            """
            Calculates the Huber loss value based on the prediction and target arrays.
            """
            diff = target - prediction
            return np.where(np.abs(diff) < self.delta, 0.5 * np.square(diff), self.delta * np.abs(diff) - 0.5 * self.delta ** 2)
    
        def _derivative(self) -> np.ndarray:
            """
            Calculates the derivative of the Huber loss function with respect to the prediction array.
            """
            diff = self.prediction - self.target
            return np.where(np.abs(diff) < self.delta, diff, self.delta * np.sign(diff))
    
    
    class NLLLoss(LossFunction):
        """
        intended for classification
        """
        __slots__ = ['epsilon']
    
        def __init__(self, epsilon: float = 1e-8) -> None:
            super().__init__()
            self.epsilon = epsilon
    
        def _function(self, prediction: ArrayLike, target: ArrayLike) -> float:
            """
            Compute the negative log-likelihood loss for binary classification.
            """
            return - (target * np.log(prediction + self.epsilon) + (1 - target) * np.log(1 - prediction + self.epsilon))
    
        def _derivative(self) -> np.ndarray:
            """
            Compute the derivative of the negative log-likelihood loss.
            """
            return (self.prediction - self.target) / (self.prediction * (1 - self.prediction) + self.epsilon)
    
    
    class CrossEntropyLoss(LossFunction):
        """
        intended for classification
        """
        __slots__ = ['epsilon']
    
        def __init__(self, epsilon: float = 1e-8) -> None:
            super().__init__()
            self.epsilon = epsilon # stability parameter
    
        def _function(self, prediction: ArrayLike, target: ArrayLike) -> float:
            """
            Compute cross entropy loss for binary classification.
            """
            confidence = np.sum(prediction * target, axis=1)
            return - np.log(confidence + self.epsilon)
    
        def _derivative(self) -> np.ndarray:
            """
            Compute the derivative of cross entropy loss.
            """
            return (self.prediction - self.target) / self.target.size
    
    
    class FocalLoss(LossFunction):
        """
        intended for classification
        this is an alternative to cross entropy loss...
        it could be that the derivative is wrong
        """
        __slots__ = ['focus', 'epsilon']
    
        def __init__(self, focus: float = 1.5, epsilon: float = 1e-8):
            super().__init__()
            self.focus = focus
            self.epsilon = epsilon # stability parameter
    
        def _function(self, prediction: ArrayLike, target: ArrayLike) -> float:
            """
            Compute focal loss for binary classification.
            """
            confidence = np.sum(prediction * target, axis=1)
            return - self._power(1 - confidence, self.focus) * np.log(confidence + self.epsilon)
    
        def _derivative(self) -> np.ndarray:
            """
            Compute the derivative of focal loss.
            """
            pt = np.sum(self.prediction * self.target, axis=1) # p_t
            term1 = - (1 - pt)**self.focus * (-np.log(pt) - 1)
            term2 = self.focus * (1 - pt)**(self.focus - 1) * np.log(pt)
            return - (term1 + term2)
    
    
    class HellingerLoss(LossFunction):
        """
        intended for classification
        """
        __slots__ = ['epsilon']
    
        def __init__(self, epsilon: float = 1e-8) -> None:
            super().__init__()
            self.epsilon = epsilon # stability parameter
    
        def _function(self, prediction: ArrayLike, target: ArrayLike) -> float:
            """
            Compute hellinger loss for binary classification.
            """
            return np.power(np.sqrt(target) - np.sqrt(prediction), 2)
    
        def _derivative(self) -> np.ndarray:
            """
            Compute the derivative of hellinger loss.
            """
            p_sqrt = np.sqrt(self.prediction)
            q_sqrt = np.sqrt(self.target)
            return (1/2*np.sqrt(2)) * (q_sqrt - p_sqrt) / p_sqrt