Loading files
from typing import List
import torch
import torch.nn.functional as F
from pytorch_toolbelt.utils.torch_utils import to_tensor
from torch import Tensor
from torch.nn.modules.loss import _Loss
from .functional import soft_dice_score
__all__ = ["DiceLoss"]
BINARY_MODE = "binary"
MULTICLASS_MODE = "multiclass"
MULTILABEL_MODE = "multilabel"
class DiceLoss(_Loss):
"""
Implementation of Dice loss for image segmentation task.
It supports binary, multiclass and multilabel cases
"""
def __init__(
self,
mode: str,
classes: List[int] = None,
log_loss=False,
from_logits=True,
smooth=0,
eps=1e-7,
):
"""
:param mode: Metric mode {'binary', 'multiclass', 'multilabel'}
:param classes: Optional list of classes that contribute in loss computation; By default, all channels are included.
:param log_loss: If True, loss computed as `-log(jaccard)`; otherwise `1 - jaccard`
:param from_logits: If True assumes input is raw logits
:param smooth:
:param eps: Small epsilon for numerical stability
"""
assert mode in {BINARY_MODE, MULTILABEL_MODE, MULTICLASS_MODE}
super(DiceLoss, self).__init__()
self.mode = mode
if classes is not None:
assert (
mode != BINARY_MODE
), "Masking classes is not supported with mode=binary"
classes = to_tensor(classes, dtype=torch.long)
self.classes = classes
self.from_logits = from_logits
self.smooth = smooth
self.eps = eps
self.log_loss = log_loss
def forward(self, y_pred: Tensor, y_true: Tensor) -> Tensor:
"""
:param y_pred: NxCxHxW
:param y_true: NxHxW
:return: scalar
"""
assert y_true.size(0) == y_pred.size(0)
if self.from_logits:
# Apply activations to get [0..1] class probabilities
if self.mode == MULTICLASS_MODE:
y_pred = y_pred.softmax(dim=1)
else:
y_pred = y_pred.sigmoid()
bs = y_true.size(0)
num_classes = y_pred.size(1)
dims = (0, 2)
if self.mode == BINARY_MODE:
y_true = y_true.view(bs, 1, -1)
y_pred = y_pred.view(bs, 1, -1)
if self.mode == MULTICLASS_MODE:
y_true = y_true.view(bs, -1)
y_pred = y_pred.view(bs, num_classes, -1)
y_true = F.one_hot(y_true, num_classes) # N,H*W -> N,H*W, C
y_true = y_true.permute(0, 2, 1) # H, C, H*W
if self.mode == MULTILABEL_MODE:
y_true = y_true.view(bs, num_classes, -1)
y_pred = y_pred.view(bs, num_classes, -1)
scores = soft_dice_score(
y_pred, y_true.type(y_pred.dtype), self.smooth, self.eps, dims=dims
)
if self.log_loss:
loss = -torch.log(scores)
else:
loss = 1 - scores
# IoU loss is defined for non-empty classes
# So we zero contribution of channel that does not have true pixels
# NOTE: A better workaround would be to use loss term `mean(y_pred)`
# for this case, however it will be a modified jaccard loss
mask = (y_true.sum(dims) > 0).float()
loss = loss * mask
if self.classes is not None:
loss = loss[self.classes]
return loss.mean()
b150d1354This file is pinned to the selected release. Download the original to open it in your design tool.