Loading files
b150d1354This file is pinned to the selected release. Download the original to open it in your design tool.
import math
from typing import Optional
import torch
import torch.nn.functional as F
__all__ = [
"focal_loss_with_logits",
"sigmoid_focal_loss",
"soft_jaccard_score",
"soft_dice_score",
"wing_loss",
]
def focal_loss_with_logits(
input: torch.Tensor,
target: torch.Tensor,
gamma=2.0,
alpha: Optional[float] = 0.25,
reduction="mean",
normalized=False,
threshold: Optional[float] = None,
) -> torch.Tensor:
"""Compute binary focal loss between target and output logits.
See :class:`~pytorch_toolbelt.losses.FocalLoss` for details.
Args:
input: Tensor of arbitrary shape
target: Tensor of the same shape as input
reduction (string, optional): Specifies the reduction to apply to the output:
'none' | 'mean' | 'sum' | 'batchwise_mean'. 'none': no reduction will be applied,
'mean': the sum of the output will be divided by the number of
elements in the output, 'sum': the output will be summed. Note: :attr:`size_average`
and :attr:`reduce` are in the process of being deprecated, and in the meantime,
specifying either of those two args will override :attr:`reduction`.
'batchwise_mean' computes mean loss per sample in batch. Default: 'mean'
normalized (bool): Compute normalized focal loss (https://arxiv.org/pdf/1909.07829.pdf).
threshold (float, optional): Compute reduced focal loss (https://arxiv.org/abs/1903.01347).
References::
https://github.com/open-mmlab/mmdetection/blob/master/mmdet/core/loss/losses.py
"""
target = target.type(input.type())
logpt = -F.binary_cross_entropy_with_logits(input, target, reduction="none")
pt = torch.exp(logpt)
# compute the loss
if threshold is None:
focal_term = (1 - pt).pow(gamma)
else:
focal_term = ((1.0 - pt) / threshold).pow(gamma)
focal_term[pt < threshold] = 1
loss = -focal_term * logpt
if alpha is not None:
loss = loss * (alpha * target + (1 - alpha) * (1 - target))
if normalized:
norm_factor = focal_term.sum()
loss = loss / norm_factor
if reduction == "mean":
loss = loss.mean()
if reduction == "sum":
loss = loss.sum()
if reduction == "batchwise_mean":
loss = loss.sum(0)
return loss
# TODO: Mark as deprecated and emit warning
sigmoid_focal_loss = focal_loss_with_logits
# TODO: Mark as deprecated and emit warning
def reduced_focal_loss(
input: torch.Tensor,
target: torch.Tensor,
threshold=0.5,
gamma=2.0,
reduction="mean",
):
return focal_loss_with_logits(
input, target, alpha=None, gamma=gamma, reduction=reduction, threshold=threshold
)
def soft_jaccard_score(
y_pred: torch.Tensor, y_true: torch.Tensor, smooth=0.0, eps=1e-7, dims=None
) -> torch.Tensor:
"""
:param y_pred:
:param y_true:
:param smooth:
:param eps:
:return:
Shape:
- Input: :math:`(N, NC, *)` where :math:`*` means
any number of additional dimensions
- Target: :math:`(N, NC, *)`, same shape as the input
- Output: scalar.
"""
assert y_pred.size() == y_true.size()
if dims is not None:
intersection = torch.sum(y_pred * y_true, dim=dims)
cardinality = torch.sum(y_pred + y_true, dim=dims)
else:
intersection = torch.sum(y_pred * y_true)
cardinality = torch.sum(y_pred + y_true)
union = cardinality - intersection
jaccard_score = (intersection + smooth) / (union + smooth + eps)
return jaccard_score
def soft_dice_score(
y_pred: torch.Tensor, y_true: torch.Tensor, smooth=0, eps=1e-7, dims=None
) -> torch.Tensor:
"""
:param y_pred:
:param y_true:
:param smooth:
:param eps:
:return:
Shape:
- Input: :math:`(N, NC, *)` where :math:`*` means any number
of additional dimensions
- Target: :math:`(N, NC, *)`, same shape as the input
- Output: scalar.
"""
assert y_pred.size() == y_true.size()
if dims is not None:
intersection = torch.sum(y_pred * y_true, dim=dims)
cardinality = torch.sum(y_pred + y_true, dim=dims)
else:
intersection = torch.sum(y_pred * y_true)
cardinality = torch.sum(y_pred + y_true)
dice_score = (2.0 * intersection + smooth) / (cardinality + smooth + eps)
return dice_score
def tversky_score(y_pred: torch.Tensor, y_true: torch.Tensor, alpha, smooth=0, eps=1e-7, dims=None
) -> torch.Tensor:
assert y_pred.size() == y_true.size()
if dims is not None:
true_pos = torch.sum(y_pred * y_true, dim=dims)
false_neg = torch.sum(y_true * (1-y_pred), dim=dims)
false_pos = torch.sum((1-y_true)*y_pred, dim=dims)
else:
true_pos = torch.sum(y_pred * y_true)
false_neg = torch.sum(y_true * (1-y_pred))
false_pos = torch.sum((1-y_true)*y_pred)
return (true_pos + smooth)/(true_pos + alpha*false_neg + (1-alpha)*false_pos + smooth + eps)
def wing_loss(
prediction: torch.Tensor,
target: torch.Tensor,
width=5,
curvature=0.5,
reduction="mean",
):
"""
https://arxiv.org/pdf/1711.06753.pdf
:param prediction:
:param target:
:param width:
:param curvature:
:param reduction:
:return:
"""
diff_abs = (target - prediction).abs()
loss = diff_abs.clone()
idx_smaller = diff_abs < width
idx_bigger = diff_abs >= width
loss[idx_smaller] = width * torch.log(1 + diff_abs[idx_smaller] / curvature)
C = width - width * math.log(1 + width / curvature)
loss[idx_bigger] = loss[idx_bigger] - C
if reduction == "sum":
loss = loss.sum()
if reduction == "mean":
loss = loss.mean()
return loss