| """ |
| Misc Losses |
| |
| Author: Xiaoyang Wu (xiaoyang.wu.cs@gmail.com) |
| Please cite our work if the code is helpful to you. |
| """ |
|
|
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
| from .builder import LOSSES |
|
|
|
|
| @LOSSES.register_module() |
| class CrossEntropyLoss(nn.Module): |
| def __init__( |
| self, |
| weight=None, |
| size_average=None, |
| reduce=None, |
| reduction="mean", |
| label_smoothing=0.0, |
| loss_weight=1.0, |
| ignore_index=-1, |
| ): |
| super(CrossEntropyLoss, self).__init__() |
| weight = torch.tensor(weight).cuda() if weight is not None else None |
| self.loss_weight = loss_weight |
| self.loss = nn.CrossEntropyLoss( |
| weight=weight, |
| size_average=size_average, |
| ignore_index=ignore_index, |
| reduce=reduce, |
| reduction=reduction, |
| label_smoothing=label_smoothing, |
| ) |
|
|
| def forward(self, pred, target): |
| return self.loss(pred, target) * self.loss_weight |
|
|
|
|
| @LOSSES.register_module() |
| class SmoothCELoss(nn.Module): |
| def __init__(self, smoothing_ratio=0.1): |
| super(SmoothCELoss, self).__init__() |
| self.smoothing_ratio = smoothing_ratio |
|
|
| def forward(self, pred, target): |
| eps = self.smoothing_ratio |
| n_class = pred.size(1) |
| one_hot = torch.zeros_like(pred).scatter(1, target.view(-1, 1), 1) |
| one_hot = one_hot * (1 - eps) + (1 - one_hot) * eps / (n_class - 1) |
| log_prb = F.log_softmax(pred, dim=1) |
| loss = -(one_hot * log_prb).total(dim=1) |
| loss = loss[torch.isfinite(loss)].mean() |
| return loss |
|
|
|
|
| @LOSSES.register_module() |
| class BinaryFocalLoss(nn.Module): |
| def __init__(self, gamma=2.0, alpha=0.5, logits=True, reduce=True, loss_weight=1.0): |
| """Binary Focal Loss |
| <https://arxiv.org/abs/1708.02002>` |
| """ |
| super(BinaryFocalLoss, self).__init__() |
| assert 0 < alpha < 1 |
| self.gamma = gamma |
| self.alpha = alpha |
| self.logits = logits |
| self.reduce = reduce |
| self.loss_weight = loss_weight |
|
|
| def forward(self, pred, target, **kwargs): |
| """Forward function. |
| Args: |
| pred (torch.Tensor): The prediction with shape (N) |
| target (torch.Tensor): The ground truth. If containing class |
| indices, shape (N) where each value is 0≤targets[i]≤1, If containing class probabilities, |
| same shape as the input. |
| Returns: |
| torch.Tensor: The calculated loss |
| """ |
| if self.logits: |
| bce = F.binary_cross_entropy_with_logits(pred, target, reduction="none") |
| else: |
| bce = F.binary_cross_entropy(pred, target, reduction="none") |
| pt = torch.exp(-bce) |
| alpha = self.alpha * target + (1 - self.alpha) * (1 - target) |
| focal_loss = alpha * (1 - pt) ** self.gamma * bce |
|
|
| if self.reduce: |
| focal_loss = torch.mean(focal_loss) |
| return focal_loss * self.loss_weight |
|
|
|
|
| @LOSSES.register_module() |
| class FocalLoss(nn.Module): |
| def __init__( |
| self, gamma=2.0, alpha=0.5, reduction="mean", loss_weight=1.0, ignore_index=-1 |
| ): |
| """Focal Loss |
| <https://arxiv.org/abs/1708.02002>` |
| """ |
| super(FocalLoss, self).__init__() |
| assert reduction in ( |
| "mean", |
| "sum", |
| ), "AssertionError: reduction should be 'mean' or 'sum'" |
| assert isinstance( |
| alpha, (float, list) |
| ), "AssertionError: alpha should be of type float" |
| assert isinstance(gamma, float), "AssertionError: gamma should be of type float" |
| assert isinstance( |
| loss_weight, float |
| ), "AssertionError: loss_weight should be of type float" |
| assert isinstance(ignore_index, int), "ignore_index must be of type int" |
| self.gamma = gamma |
| self.alpha = alpha |
| self.reduction = reduction |
| self.loss_weight = loss_weight |
| self.ignore_index = ignore_index |
|
|
| def forward(self, pred, target, **kwargs): |
| """Forward function. |
| Args: |
| pred (torch.Tensor): The prediction with shape (N, C) where C = number of classes. |
| target (torch.Tensor): The ground truth. If containing class |
| indices, shape (N) where each value is 0≤targets[i]≤C−1, If containing class probabilities, |
| same shape as the input. |
| Returns: |
| torch.Tensor: The calculated loss |
| """ |
| |
| pred = pred.transpose(0, 1) |
| |
| pred = pred.reshape(pred.size(0), -1) |
| |
| pred = pred.transpose(0, 1).contiguous() |
| |
| target = target.view(-1).contiguous() |
| assert pred.size(0) == target.size( |
| 0 |
| ), "The shape of pred doesn't match the shape of target" |
| valid_mask = target != self.ignore_index |
| target = target[valid_mask] |
| pred = pred[valid_mask] |
|
|
| if len(target) == 0: |
| return 0.0 |
|
|
| num_classes = pred.size(1) |
| target = F.one_hot(target, num_classes=num_classes) |
|
|
| alpha = self.alpha |
| if isinstance(alpha, list): |
| alpha = pred.new_tensor(alpha) |
| pred_sigmoid = pred.sigmoid() |
| target = target.type_as(pred) |
| one_minus_pt = (1 - pred_sigmoid) * target + pred_sigmoid * (1 - target) |
| focal_weight = (alpha * target + (1 - alpha) * (1 - target)) * one_minus_pt.pow( |
| self.gamma |
| ) |
|
|
| loss = ( |
| F.binary_cross_entropy_with_logits(pred, target, reduction="none") |
| * focal_weight |
| ) |
| if self.reduction == "mean": |
| loss = loss.mean() |
| elif self.reduction == "sum": |
| loss = loss.total() |
| return self.loss_weight * loss |
|
|
|
|
| @LOSSES.register_module() |
| class ClassBalancedFocalLoss(nn.Module): |
| def __init__( |
| self, |
| gamma=2.0, |
| alpha=None, |
| class_counts=None, |
| beta=0.999, |
| reduction="mean", |
| loss_weight=1.0, |
| ignore_index=-1, |
| eps=1e-12, |
| ): |
| super(ClassBalancedFocalLoss, self).__init__() |
| assert reduction in ("mean", "sum", "none") |
| self.gamma = float(gamma) |
| self.beta = float(beta) |
| self.reduction = reduction |
| self.loss_weight = float(loss_weight) |
| self.ignore_index = int(ignore_index) |
| self.eps = float(eps) |
| if alpha is not None and class_counts is not None: |
| raise ValueError("Specify either alpha or class_counts, not both.") |
| if alpha is not None: |
| self.alpha = torch.tensor(alpha, dtype=torch.float32) |
| elif class_counts is not None: |
| counts = torch.tensor(class_counts, dtype=torch.float32).clamp_min(1.0) |
| effective_num = 1.0 - torch.pow(torch.full_like(counts, self.beta), counts) |
| alpha = (1.0 - self.beta) / effective_num.clamp_min(self.eps) |
| alpha = alpha / alpha.sum() * alpha.numel() |
| self.alpha = alpha |
| else: |
| self.alpha = None |
|
|
| def forward(self, pred, target): |
| target = target.view(-1).contiguous() |
| pred = pred.reshape(-1, pred.shape[-1]) if pred.dim() > 2 else pred |
| valid_mask = target != self.ignore_index |
| if not torch.any(valid_mask): |
| return pred.sum() * 0.0 |
| pred = pred[valid_mask] |
| target = target[valid_mask].long() |
|
|
| log_prob = F.log_softmax(pred, dim=1) |
| prob = log_prob.exp() |
| ce = F.nll_loss(log_prob, target, reduction="none") |
| pt = prob.gather(1, target.unsqueeze(1)).squeeze(1).clamp_min(self.eps) |
| focal = torch.pow(1.0 - pt, self.gamma) |
| loss = focal * ce |
| if self.alpha is not None: |
| alpha = self.alpha.to(device=pred.device, dtype=pred.dtype) |
| loss = loss * alpha.gather(0, target) |
| if self.reduction == "mean": |
| loss = loss.mean() |
| elif self.reduction == "sum": |
| loss = loss.sum() |
| return loss * self.loss_weight |
|
|
|
|
| @LOSSES.register_module() |
| class DiceLoss(nn.Module): |
| def __init__(self, smooth=1, exponent=2, loss_weight=1.0, ignore_index=-1): |
| """DiceLoss. |
| This loss is proposed in `V-Net: Fully Convolutional Neural Networks for |
| Volumetric Medical Image Segmentation <https://arxiv.org/abs/1606.04797>`_. |
| """ |
| super(DiceLoss, self).__init__() |
| self.smooth = smooth |
| self.exponent = exponent |
| self.loss_weight = loss_weight |
| self.ignore_index = ignore_index |
|
|
| def forward(self, pred, target, **kwargs): |
| |
| pred = pred.transpose(0, 1) |
| |
| pred = pred.reshape(pred.size(0), -1) |
| |
| pred = pred.transpose(0, 1).contiguous() |
| |
| target = target.view(-1).contiguous() |
| assert pred.size(0) == target.size( |
| 0 |
| ), "The shape of pred doesn't match the shape of target" |
| valid_mask = target != self.ignore_index |
| target = target[valid_mask] |
| pred = pred[valid_mask] |
|
|
| pred = F.softmax(pred, dim=1) |
| num_classes = pred.shape[1] |
| target = F.one_hot( |
| torch.clamp(target.long(), 0, num_classes - 1), num_classes=num_classes |
| ) |
|
|
| total_loss = 0 |
| for i in range(num_classes): |
| if i != self.ignore_index: |
| num = torch.sum(torch.mul(pred[:, i], target[:, i])) * 2 + self.smooth |
| den = ( |
| torch.sum( |
| pred[:, i].pow(self.exponent) + target[:, i].pow(self.exponent) |
| ) |
| + self.smooth |
| ) |
| dice_loss = 1 - num / den |
| total_loss += dice_loss |
| loss = total_loss / num_classes |
| return self.loss_weight * loss |
|
|