Module delta.extensions.losses

Various helpful loss functions.

Functions

def dice_coef(y_true, y_pred, smooth=1)

Dice = (2|X & Y|)/ (|X|+ |Y|) = 2sum(|A*B|)/(sum(A^2)+sum(B^2)) ref: https://arxiv.org/pdf/1606.04797v1.pdf

def dice_loss(y_true, y_pred)

Dice coefficient as a loss function.

def ms_ssim(y_true, y_pred)

tf.image.ssim_multiscale as a loss function. This loss function requires two dimensional inputs.

def ms_ssim_mse(y_true, y_pred)

Sum of MS-SSIM and Mean Squared Error.

def suggest_filter_size(image1, image2, power_factors, filter_size)

Figure out if we need to shrink the filter to accomodate a smaller input image

def surface_loss(y_true, y_pred)

Classes

class MappedBinaryCrossentropy (mapping, name=None, reduction='auto')

MappedLoss for binary_crossentropy.

This is a base class for losses when the labels of the input images do not match the labels output by the network. For example, if one class in the labels should be ignored, or two classes in the label should map to the same output, or one label should be treated as a probability between two classes. It applies a transform to the output labels and then applies the loss function.

Note that the transform is applied after preprocessing (labels in the config will be transformed to 0-n in order, and nodata will be n+1).

Parameters

mapping
One of: * A list with transforms, where the first entry is what to transform the first label, to etc., i.e., [1, 0] will swap the order of two labels. * A dictionary with classes mapped to transformed values. Classes can be referenced by name or by number (see ClassesConfig.class_id() for class formats).
name : Optional[str]
Optional name for the loss function.
Expand source code
class MappedBinaryCrossentropy(MappedLoss):
    """
    `MappedLoss` for binary_crossentropy.
    """
    def call(self, y_true, y_pred):
        (y_true, y_pred) = self.preprocess(y_true, y_pred)
        return tensorflow.keras.losses.binary_crossentropy(y_true, y_pred)

Ancestors

Methods

def call(self, y_true, y_pred)

Invokes the Loss instance.

Args

y_true
Ground truth values. shape = [batch_size, d0, .. dN], except sparse loss functions such as sparse categorical crossentropy where shape = [batch_size, d0, .. dN-1]
y_pred
The predicted values. shape = [batch_size, d0, .. dN]

Returns

Loss values with the shape [batch_size, d0, .. dN-1].

Inherited members

class MappedCategoricalCrossentropy (mapping, name=None, reduction='auto')

MappedLoss for categorical_crossentropy.

This is a base class for losses when the labels of the input images do not match the labels output by the network. For example, if one class in the labels should be ignored, or two classes in the label should map to the same output, or one label should be treated as a probability between two classes. It applies a transform to the output labels and then applies the loss function.

Note that the transform is applied after preprocessing (labels in the config will be transformed to 0-n in order, and nodata will be n+1).

Parameters

mapping
One of: * A list with transforms, where the first entry is what to transform the first label, to etc., i.e., [1, 0] will swap the order of two labels. * A dictionary with classes mapped to transformed values. Classes can be referenced by name or by number (see ClassesConfig.class_id() for class formats).
name : Optional[str]
Optional name for the loss function.
Expand source code
class MappedCategoricalCrossentropy(MappedLoss):
    """
    `MappedLoss` for categorical_crossentropy.
    """
    def call(self, y_true, y_pred):
        y_true = tf.squeeze(y_true)
        (y_true, y_pred) = self.preprocess(y_true, y_pred)
        return tensorflow.keras.losses.categorical_crossentropy(y_true, y_pred)

Ancestors

Methods

def call(self, y_true, y_pred)

Invokes the Loss instance.

Args

y_true
Ground truth values. shape = [batch_size, d0, .. dN], except sparse loss functions such as sparse categorical crossentropy where shape = [batch_size, d0, .. dN-1]
y_pred
The predicted values. shape = [batch_size, d0, .. dN]

Returns

Loss values with the shape [batch_size, d0, .. dN-1].

Inherited members

class MappedDiceBceMsssim (mapping, name=None, reduction='auto')

MappedLoss for sum of ms_ssim(), dice_loss(), and binary_crossentropy.

This is a base class for losses when the labels of the input images do not match the labels output by the network. For example, if one class in the labels should be ignored, or two classes in the label should map to the same output, or one label should be treated as a probability between two classes. It applies a transform to the output labels and then applies the loss function.

Note that the transform is applied after preprocessing (labels in the config will be transformed to 0-n in order, and nodata will be n+1).

Parameters

mapping
One of: * A list with transforms, where the first entry is what to transform the first label, to etc., i.e., [1, 0] will swap the order of two labels. * A dictionary with classes mapped to transformed values. Classes can be referenced by name or by number (see ClassesConfig.class_id() for class formats).
name : Optional[str]
Optional name for the loss function.
Expand source code
class MappedDiceBceMsssim(MappedLoss):
    """
    `MappedLoss` for sum of `ms_ssim`, `dice_loss`, and `binary_crossentropy`.
    """
    def call(self, y_true, y_pred):
        (y_true, y_pred) = self.preprocess(y_true, y_pred)

        dice = dice_loss(y_true, y_pred)
        bce = tensorflow.keras.losses.binary_crossentropy(y_true, y_pred)
        msssim = ms_ssim(y_true, y_pred) # / tf.cast(tf.size(y_true), tf.float32)
        msssim = tf.expand_dims(tf.expand_dims(msssim, -1), -1)

        return dice + bce + msssim

Ancestors

Methods

def call(self, y_true, y_pred)

Invokes the Loss instance.

Args

y_true
Ground truth values. shape = [batch_size, d0, .. dN], except sparse loss functions such as sparse categorical crossentropy where shape = [batch_size, d0, .. dN-1]
y_pred
The predicted values. shape = [batch_size, d0, .. dN]

Returns

Loss values with the shape [batch_size, d0, .. dN-1].

Inherited members

class MappedDiceLoss (mapping, name=None, reduction='auto')

MappedLoss for dice_loss().

This is a base class for losses when the labels of the input images do not match the labels output by the network. For example, if one class in the labels should be ignored, or two classes in the label should map to the same output, or one label should be treated as a probability between two classes. It applies a transform to the output labels and then applies the loss function.

Note that the transform is applied after preprocessing (labels in the config will be transformed to 0-n in order, and nodata will be n+1).

Parameters

mapping
One of: * A list with transforms, where the first entry is what to transform the first label, to etc., i.e., [1, 0] will swap the order of two labels. * A dictionary with classes mapped to transformed values. Classes can be referenced by name or by number (see ClassesConfig.class_id() for class formats).
name : Optional[str]
Optional name for the loss function.
Expand source code
class MappedDiceLoss(MappedLoss):
    """
    `MappedLoss` for `dice_loss`.
    """
    def call(self, y_true, y_pred):
        (y_true, y_pred) = self.preprocess(y_true, y_pred)
        return dice_loss(y_true, y_pred)

Ancestors

Methods

def call(self, y_true, y_pred)

Invokes the Loss instance.

Args

y_true
Ground truth values. shape = [batch_size, d0, .. dN], except sparse loss functions such as sparse categorical crossentropy where shape = [batch_size, d0, .. dN-1]
y_pred
The predicted values. shape = [batch_size, d0, .. dN]

Returns

Loss values with the shape [batch_size, d0, .. dN-1].

Inherited members

class MappedLoss (mapping, name=None, reduction='auto')

Loss base class.

To be implemented by subclasses: * call(): Contains the logic for loss calculation using y_true, y_pred.

Example subclass implementation:

class MeanSquaredError(Loss):

  def call(self, y_true, y_pred):
    return tf.reduce_mean(tf.math.square(y_pred - y_true), axis=-1)

When using a Loss under a tf.distribute.Strategy, except passing it to Model.compile() for use by Model.fit(), please use reduction types 'SUM' or 'NONE', and reduce losses explicitly. Using 'AUTO' or 'SUM_OVER_BATCH_SIZE' will raise an error when calling the Loss object from a custom training loop or from user-defined code in Layer.call(). Please see this custom training tutorial for more details on this.

This is a base class for losses when the labels of the input images do not match the labels output by the network. For example, if one class in the labels should be ignored, or two classes in the label should map to the same output, or one label should be treated as a probability between two classes. It applies a transform to the output labels and then applies the loss function.

Note that the transform is applied after preprocessing (labels in the config will be transformed to 0-n in order, and nodata will be n+1).

Parameters

mapping
One of: * A list with transforms, where the first entry is what to transform the first label, to etc., i.e., [1, 0] will swap the order of two labels. * A dictionary with classes mapped to transformed values. Classes can be referenced by name or by number (see ClassesConfig.class_id() for class formats).
name : Optional[str]
Optional name for the loss function.
Expand source code
class MappedLoss(tf.keras.losses.Loss): #pylint: disable=abstract-method
    def __init__(self, mapping, name=None, reduction=losses_utils.ReductionV2.AUTO):
        """
        This is a base class for losses when the labels of the input images do not match the labels
        output by the network. For example, if one class in the labels should be ignored, or two
        classes in the label should map to the same output, or one label should be treated as a probability
        between two classes. It applies a transform to the output labels and then applies the loss function.

        Note that the transform is applied after preprocessing (labels in the config will be transformed to 0-n
        in order, and nodata will be n+1).

        Parameters
        ----------
        mapping
            One of:
             * A list with transforms, where the first entry is what to transform the first label, to etc., i.e.,
               [1, 0] will swap the order of two labels.
             * A dictionary with classes mapped to transformed values. Classes can be referenced by name or by
               number (see `delta.imagery.imagery_config.ClassesConfig.class_id` for class formats).
        name: Optional[str]
            Optional name for the loss function.
        """
        super().__init__(name=name, reduction=reduction)
        self._mapping = mapping
        self._nodata_classes = []
        if isinstance(mapping, list):
            map_list = mapping
            # replace nodata
            for (i, me) in enumerate(map_list):
                if me == 'nodata':
                    j = 0
                    while map_list[i] == 'nodata':
                        if j == len(map_list):
                            raise ValueError('All mapping entries are nodata.')
                        if map_list[j] != 'nodata':
                            map_list[i] = map_list[j]
                            break
                        j += 1
                    self._nodata_classes.append(i)
        else:
            # automatically set nodata to 0 (even if there is none it's fine)
            entry = mapping[next(iter(mapping))]
            if np.isscalar(entry):
                map_list = np.zeros((len(config.dataset.classes) + 1,))
            else:
                map_list = np.zeros((len(config.dataset.classes) + 1, len(entry)))
            assert len(mapping) == len(config.dataset.classes), 'Must specify all classes in loss mapping.'
            for k in mapping:
                i = config.dataset.classes.class_id(k)
                if isinstance(mapping[k], (int, float)):
                    map_list[i] = mapping[k]
                elif mapping[k] == 'nodata':
                    self._nodata_classes.append(i)
                else:
                    assert len(mapping[k]) == map_list.shape[1], 'Mapping entry wrong length.'
                    map_list[i, :] = np.asarray(mapping[k])
        self._lookup = tf.constant(map_list, dtype=tf.float32)

    # makes nodata labels 0 in predictions
    def preprocess(self, y_true, y_pred):
        y_true = tf.cast(y_true, tf.int32)

        true_convert = tf.gather(self._lookup, y_true, axis=None)
        nodata_value = config.dataset.classes.class_id('nodata')
        nodata = (y_true == nodata_value)

        # ignore additional nodata classes
        for c in self._nodata_classes:
            nodata = tf.logical_or(nodata, y_true == c)

        while len(nodata.shape) < len(y_pred.shape):
            nodata = tf.expand_dims(nodata, -1)

        # zero all nodata entries
        y_pred = tf.cast(y_pred, tf.float32) * tf.cast(tf.logical_not(nodata), tf.float32)

        true_convert = tf.cast(tf.logical_not(nodata), tf.float32) * true_convert
        return (true_convert, y_pred)

    def get_config(self):
        base_config = super().get_config()
        return {**base_config, 'mapping' : self._mapping}

Ancestors

  • keras.src.losses.Loss

Subclasses

Methods

def get_config(self)

Returns the config dictionary for a Loss instance.

def preprocess(self, y_true, y_pred)
class MappedLossSum (mapping, name=None, reduction='auto', losses=None, weights=None)

MappedLoss for sum of any loss functions.

Parameters

losses : List[Union[str, dict]]
List of loss functions to add.
weights : Union[List[float], None]
Optional list of weights for the corresponding loss functions.
Expand source code
class MappedLossSum(MappedLoss):
    """
    `MappedLoss` for sum of any loss functions.
    """
    def __init__(self, mapping, name=None, reduction=losses_utils.ReductionV2.AUTO, losses=None, weights=None):
        """
        Parameters
        ----------
        losses: List[Union[str, dict]]
            List of loss functions to add.
        weights: Union[List[float], None]
            Optional list of weights for the corresponding loss functions.
        """
        super().__init__(mapping, name=name)
        self._losses = list(map(loss_from_dict, losses))
        if weights is None:
            weights = [1] * len(losses)
        self._weights = weights

    def _get_loss(self, i, y_true, y_pred):
        l = self._losses[i](y_true, y_pred)
        while len(l.shape) < 3:
            l = tf.expand_dims(l, -1)
        return self._weights[i] * l

    def call(self, y_true, y_pred):
        (y_true, y_pred) = self.preprocess(y_true, y_pred)

        total = self._get_loss(0, y_true, y_pred)
        for i in range(1, len(self._losses)):
            total += self._get_loss(i, y_true, y_pred)

        return total

Ancestors

Methods

def call(self, y_true, y_pred)

Invokes the Loss instance.

Args

y_true
Ground truth values. shape = [batch_size, d0, .. dN], except sparse loss functions such as sparse categorical crossentropy where shape = [batch_size, d0, .. dN-1]
y_pred
The predicted values. shape = [batch_size, d0, .. dN]

Returns

Loss values with the shape [batch_size, d0, .. dN-1].

Inherited members

class MappedMsssim (mapping, name=None, reduction='auto')

MappedLoss for ms_ssim().

This is a base class for losses when the labels of the input images do not match the labels output by the network. For example, if one class in the labels should be ignored, or two classes in the label should map to the same output, or one label should be treated as a probability between two classes. It applies a transform to the output labels and then applies the loss function.

Note that the transform is applied after preprocessing (labels in the config will be transformed to 0-n in order, and nodata will be n+1).

Parameters

mapping
One of: * A list with transforms, where the first entry is what to transform the first label, to etc., i.e., [1, 0] will swap the order of two labels. * A dictionary with classes mapped to transformed values. Classes can be referenced by name or by number (see ClassesConfig.class_id() for class formats).
name : Optional[str]
Optional name for the loss function.
Expand source code
class MappedMsssim(MappedLoss):
    """
    `MappedLoss` for `ms_ssim`.
    """
    def call(self, y_true, y_pred):
        (y_true, y_pred) = self.preprocess(y_true, y_pred)
        return ms_ssim(y_true, y_pred)

Ancestors

Methods

def call(self, y_true, y_pred)

Invokes the Loss instance.

Args

y_true
Ground truth values. shape = [batch_size, d0, .. dN], except sparse loss functions such as sparse categorical crossentropy where shape = [batch_size, d0, .. dN-1]
y_pred
The predicted values. shape = [batch_size, d0, .. dN]

Returns

Loss values with the shape [batch_size, d0, .. dN-1].

Inherited members