Source code for eazygrad.functions.loss
from __future__ import annotations
import numpy as np
from .math import log
from ..utils import check
from .._tensor import _Tensor
from ..grad import operations, dag
from .specials import logsumexp
[docs]
def mse_loss(predicted: _Tensor, target: _Tensor) -> _Tensor:
"""
Compute the mean squared error loss.
Parameters
----------
predicted : _Tensor
Predicted values.
target : _Tensor
Target values with the same shape as ``predicted``.
Returns
-------
_Tensor
Scalar loss tensor.
See Also
--------
`torch.nn.functional.mse_loss <https://pytorch.org/docs/stable/generated/torch.nn.functional.mse_loss.html>`_
"""
return ((predicted - target) ** 2).mean()
[docs]
def nll_loss(predicted: _Tensor, target: _Tensor) -> _Tensor:
"""
Compute the negative log-likelihood loss from log-probabilities.
Parameters
----------
predicted : _Tensor
Log-probabilities of shape ``(N, C)``.
target : _Tensor
Integer class indices of shape ``(N,)``.
Returns
-------
_Tensor
Scalar loss tensor.
See Also
--------
`torch.nn.functional.nll_loss <https://pytorch.org/docs/stable/generated/torch.nn.functional.nll_loss.html>`_
"""
correct_probs = predicted[np.arange(predicted.shape[0]), target._array]
return -correct_probs.mean()
[docs]
def bce_with_logits_loss(logits: _Tensor, target: _Tensor) -> _Tensor:
"""
Compute binary cross-entropy loss from unnormalized logits.
Parameters
----------
logits : _Tensor
Input logits.
target : _Tensor
Target tensor with values typically in ``[0, 1]`` and the same shape as
``logits``.
Returns
-------
_Tensor
Scalar loss tensor.
Notes
-----
This implementation uses a numerically stable formulation and internally
averages the per-element loss.
See Also
--------
`torch.nn.functional.binary_cross_entropy_with_logits <https://pytorch.org/docs/stable/generated/torch.nn.functional.binary_cross_entropy_with_logits.html>`_
"""
if not isinstance(logits, _Tensor):
raise TypeError(f"Expected predicted to be an eazygrad tensor, got {type(logits)}")
if not isinstance(target, _Tensor):
raise TypeError(f"Expected predicted to be an eazygrad tensor, got {type(target)}")
# Maybe type promotion for exp and sum
dtype = logits.dtype
f64_array = logits._array.astype(np.float64, copy=False)
# Numerically stable computation of BCE with logits
bce = np.clip(f64_array, a_min=0, a_max=None) - f64_array*target._array + np.log1p(np.exp(-np.abs(f64_array)))
requires_grad = logits.requires_grad
# Recast to input dtype
result = _Tensor(bce, requires_grad=requires_grad, dtype=dtype)
if requires_grad :
result.node_id = dag.create_node(
parents_id = [logits.node_id],
operation = operations.BinaryCrossEntropy(logits=f64_array, target=target._array),
result = result
)
return result.mean()
[docs]
def bce_loss(predicted: _Tensor, target: _Tensor) -> _Tensor:
"""
Compute binary cross-entropy loss from probabilities.
Parameters
----------
predicted : _Tensor
Predicted probabilities.
target : _Tensor
Target tensor with the same shape as ``predicted``.
Returns
-------
_Tensor
Scalar loss tensor.
See Also
--------
`torch.nn.functional.binary_cross_entropy <https://pytorch.org/docs/stable/generated/torch.nn.functional.binary_cross_entropy.html>`_
"""
return -(target * log(predicted) + (1 - target) * log(1 - predicted)).mean()
[docs]
def cross_entropy_loss(predicted: _Tensor, target: _Tensor) -> _Tensor:
"""
Compute cross-entropy loss from class logits.
Parameters
----------
predicted : _Tensor
Logits of shape ``(N, C)``.
target : _Tensor
Targets of shape ``(N,)`` for integer class indices or shape ``(N, C)``
for soft targets.
Returns
-------
_Tensor
Scalar loss tensor.
Notes
-----
The loss is computed with a numerically stable log-sum-exp formulation.
See Also
--------
`torch.nn.functional.cross_entropy <https://pytorch.org/docs/stable/generated/torch.nn.functional.cross_entropy.html>`_
"""
# Expect predicted to be Tensor with logits shape (N, C)
# target should be Tensor with class indices or target distribution
# -* if target is class indices shape is (N,)
# -* if target is distribution shape is (N, C)
if not isinstance(predicted, _Tensor):
raise TypeError(f"Expected predicted to be an eazygrad tensor, got {type(predicted)}")
if not isinstance(target, _Tensor):
raise TypeError(f"Expected predicted to be an eazygrad tensor, got {type(target)}")
if predicted.ndim != 2:
raise ValueError("Expected predicted to have shape (N, C)")
# Numerically stable computation of cross entropy loss
if target.shape == (predicted.shape[0],):
# Target is class indices
if not np.issubdtype(target._array.dtype, np.integer):
raise TypeError("Target class indices must be an integer tensor")
target_idx = target._array.astype(np.int64, copy=False)
if np.any(target_idx < 0) or np.any(target_idx >= predicted.shape[1]):
raise ValueError("Target class indices are out of range")
target_logits = predicted[np.arange(predicted.shape[0]), target_idx]
cross_entropy = logsumexp(predicted, dim=-1, keepdims=False) - target_logits
elif target.shape == predicted.shape:
# distribution / soft targets
lse = logsumexp(predicted, dim=-1, keepdims=False) # (N,)
expected_logit = (target * predicted).sum(dim=-1) # (N,)
cross_entropy = lse - expected_logit
else:
raise ValueError("Target shape is not compatible with predicted shape")
return cross_entropy.mean()