Source code for fynance.models.loss.hybrid

#!/usr/bin/env python3
# coding: utf-8

""" Multi-objective hybrid loss combining two financial losses. """

from __future__ import annotations

# Built-in packages
import math

# Third-party packages
import torch

# Local packages
from ._base import BaseLoss

__all__ = ['HybridLoss']


[docs] class HybridLoss(BaseLoss): r""" Convex combination of two losses: :math:`\alpha L_a + (1-\alpha) L_b`. Combines two differentiable objectives (e.g. :class:`SharpeLoss` and :class:`DirectionalAccuracyLoss`) with a weight ``alpha``. The weight can be fixed or made **learnable** (an ``nn.Parameter`` passed through a sigmoid so it stays in ``(0, 1)`` and is optimized jointly with the model). Parameters ---------- loss_a, loss_b : BaseLoss The two component losses (any callables ``(y_pred, y_true) -> scalar``). alpha : float, optional Weight of ``loss_a`` in ``[0, 1]``. Default 0.5. learnable : bool, optional If True, ``alpha`` becomes a learnable parameter. Default False. **kwargs Forwarded to :class:`BaseLoss`. """ def __init__(self, loss_a, loss_b, alpha: float = 0.5, learnable: bool = False, **kwargs): super().__init__(**kwargs) self.loss_a = loss_a self.loss_b = loss_b self.learnable = learnable if learnable: a = min(max(alpha, 1e-4), 1 - 1e-4) self._alpha_raw = torch.nn.Parameter( torch.tensor(math.log(a / (1 - a))) ) else: self.alpha = alpha
[docs] def forward( self, y_pred: torch.Tensor, y_true: torch.Tensor | None = None, ) -> torch.Tensor: """ Compute the weighted sum of the two losses (scalar). """ a = torch.sigmoid(self._alpha_raw) if self.learnable else self.alpha return a * self.loss_a(y_pred, y_true) + (1 - a) * self.loss_b(y_pred, y_true)