Source code for inet.losses.giou_loss

"""
Wrapper implementation for GIoU-Loss.

Fork of tensorflow_addons.losses.giou_loss
https://github.com/tensorflow/addons/blob/b2dafcfa74c5de268b8a5c53813bc0b89cadf386/tensorflow_addons/losses/giou_loss.py

Forked to bypass version issue with TF 2.4 and unavailable package tensorflow_addons.
"""
from typing import Optional

import tensorflow as tf
from tensorflow.python.keras.utils.tf_utils import is_tensor_or_variable


[docs]class LossFunctionWrapper(tf.keras.losses.Loss): """Wraps a loss function in the `Loss` class.""" def __init__( self, fn, reduction=tf.keras.losses.Reduction.AUTO, name=None, **kwargs ): """Initializes `LossFunctionWrapper` class. Args: fn: The loss function to wrap, with signature `fn(y_true, y_pred, **kwargs)`. reduction: (Optional) Type of `tf.keras.losses.Reduction` to apply to loss. Default value is `AUTO`. `AUTO` indicates that the reduction option will be determined by the usage context. For almost all cases this defaults to `SUM_OVER_BATCH_SIZE`. When used with `tf.distribute.Strategy`, outside of built-in training loops such as `tf.keras` `compile` and `fit`, using `AUTO` or `SUM_OVER_BATCH_SIZE` will raise an error. Please see this custom training [tutorial]( https://www.tensorflow.org/tutorials/distribute/custom_training) for more details. name: (Optional) name for the loss. **kwargs: The keyword arguments that are passed on to `fn`. """ super().__init__(reduction=reduction, name=name) ## The underlying function self.fn = fn ## kwargs forwarded to `fn` self._fn_kwargs = kwargs
[docs] def call(self, y_true, y_pred): """Invokes the `LossFunctionWrapper` instance. Args: y_true: Ground truth values. y_pred: The predicted values. Returns: Loss values per sample. """ return self.fn(y_true, y_pred, **self._fn_kwargs)
[docs] def get_config(self): """Configuration getter for LossWrapper""" config = {} for k, v in iter(self._fn_kwargs.items()): config[k] = tf.keras.backend.eval(v) if is_tensor_or_variable(v) else v base_config = super().get_config() return {**base_config, **config}
def _calculate_giou(b1, b2, mode: str = 'giou') -> tf.Tensor: """ Args: b1: bounding box. The coordinates of the each bounding box in boxes are encoded as [y_min, x_min, y_max, x_max]. b2: the other bounding box. The coordinates of the each bounding box in boxes are encoded as [y_min, x_min, y_max, x_max]. mode: one of ['giou', 'iou'], decided to calculate GIoU or IoU loss. Returns: GIoU loss float `Tensor`. """ zero = tf.convert_to_tensor(0.0, b1.dtype) b1_ymin, b1_xmin, b1_ymax, b1_xmax = tf.unstack(b1, 4, axis=-1) b2_ymin, b2_xmin, b2_ymax, b2_xmax = tf.unstack(b2, 4, axis=-1) b1_width = tf.maximum(zero, b1_xmax - b1_xmin) b1_height = tf.maximum(zero, b1_ymax - b1_ymin) b2_width = tf.maximum(zero, b2_xmax - b2_xmin) b2_height = tf.maximum(zero, b2_ymax - b2_ymin) b1_area = b1_width * b1_height b2_area = b2_width * b2_height intersect_ymin = tf.maximum(b1_ymin, b2_ymin) intersect_xmin = tf.maximum(b1_xmin, b2_xmin) intersect_ymax = tf.minimum(b1_ymax, b2_ymax) intersect_xmax = tf.minimum(b1_xmax, b2_xmax) intersect_width = tf.maximum(zero, intersect_xmax - intersect_xmin) intersect_height = tf.maximum(zero, intersect_ymax - intersect_ymin) intersect_area = intersect_width * intersect_height union_area = b1_area + b2_area - intersect_area iou = tf.math.divide_no_nan(intersect_area, union_area) if mode == 'iou': return iou enclose_ymin = tf.minimum(b1_ymin, b2_ymin) enclose_xmin = tf.minimum(b1_xmin, b2_xmin) enclose_ymax = tf.maximum(b1_ymax, b2_ymax) enclose_xmax = tf.maximum(b1_xmax, b2_xmax) enclose_width = tf.maximum(zero, enclose_xmax - enclose_xmin) enclose_height = tf.maximum(zero, enclose_ymax - enclose_ymin) enclose_area = enclose_width * enclose_height giou = iou - tf.math.divide_no_nan((enclose_area - union_area), enclose_area) return giou
[docs]def tf_giou_loss(y_true, y_pred, mode: str = 'giou') -> tf.Tensor: """ Implements the GIoU loss function. GIoU loss was first introduced in the [Generalized Intersection over Union: A Metric and A Loss for Bounding Box Regression] (https://giou.stanford.edu/GIoU.pdf). GIoU is an enhancement for models which use IoU in object detection. :param y_true: true targets tensor. The coordinates of the each bounding box in boxes are encoded as [y_min, x_min, y_max, x_max]. :param y_pred: predictions tensor. The coordinates of the each bounding box in boxes are encoded as [y_min, x_min, y_max, x_max]. :param mode: one of ['giou', 'iou'], decided to calculate GIoU or IoU loss. :returns: GIoU loss float `Tensor`. """ if mode not in ['giou', 'iou']: raise ValueError("Value of mode should be 'iou' or 'giou'") y_pred = tf.convert_to_tensor(y_pred) if not y_pred.dtype.is_floating: y_pred = tf.cast(y_pred, tf.float32) y_true = tf.cast(y_true, y_pred.dtype) giou = tf.squeeze(_calculate_giou(y_pred, y_true, mode)) return 1 - giou
""" END Fork """
[docs]class GIoULoss(LossFunctionWrapper): """ GIoULoss as class instance """ def __init__(self, mode: str = 'giou', reduction: str = tf.keras.losses.Reduction.AUTO, name: Optional[str] = 'giou_loss'): """ :param mode: either 'giou' or 'iou' :param reduction: tf.keras reduction :param name: verbose name for loss """ super().__init__(giou_loss, name=name, reduction=reduction, mode=mode)
[docs]def convert_values(data) -> tf.Tensor: """ converts values of shape [y, x, h, w] into [y_min, x_min, y_max, x_max] :param data: bounding box coordinates :return: converted bounding box coordinates """ y, x, h, w = tf.unstack(data, 4, axis=-1) element_vals = tf.stack([y, x, tf.add(y, h), tf.add(x, w)], axis=-1) return element_vals
[docs]def giou_loss(bb1, bb2, mode: str = 'giou') -> tf.Tensor: """ fork of `tensorflow_addons.losses.giou_loss.giou_loss` internally converts `[y, x, h, w] -> [y_min, x_min, y_max, x_max]` :param bb1: ground truth bounding box :param bb2: predicted bounding box :param mode: Mode to use, either 'giou' (default) or 'iou' :return: computed giou-loss """ return tf_giou_loss( y_true=convert_values(bb1), y_pred=convert_values(bb2), mode=mode )