#!/usr/bin/python3
from typing import Any, Callable
import tensorflow as tf
from tensorflow.python.keras import backend as K
[docs]class ModelMetrics:
"""Util Class to hold all metric methods for model evaluation.
Static methods are used to calculate metrics without instantiating the class.
"""
[docs] @staticmethod
def dice_coef(y_true: tf.Tensor, y_pred: tf.Tensor) -> float:
"""Count Sorensen-Dice coefficient for output and ground-truth image.
Parameters
----------
y_true: tf.Tensor
tensor of true pixel values
y_pred: tf.Tensor
tensor of predicted pixel values
Returns
-------
float
dice coefficient calculated on predicted and input class values.
Example
-------
unagi.utils.metric_utils.ModelMetrics.dice_coef(y_true, y_pred)
"""
y_true_f = K.flatten(y_true)
y_pred_f = K.flatten(y_pred)
intersection = K.sum(y_true_f * y_pred_f)
dice = (2.0 * intersection + 1.0) / (K.sum(y_true_f) + K.sum(y_pred_f) + 1.0)
return dice
[docs] @staticmethod
def dice_coef_loss(y_true: tf.Tensor, y_pred: tf.Tensor) -> float:
"""Loss of Sorensen-Dice coefficient for output and ground-truth image.
Parameters
----------
y_true: tf.Tensor
tensor of true pixel values
y_pred: tf.Tensor
tensor of predicted pixel values
Returns
-------
float
dice loss calculated from dice coefficient.
See Also
--------
dice_coef()
Example
-------
unagi.utils.metric_utils.ModelMetrics.dice_coef_loss(y_true, y_pred)
"""
return 1 - ModelMetrics.dice_coef(y_true, y_pred)
[docs] @staticmethod
def jacard_coef(y_true: tf.Tensor, y_pred: tf.Tensor) -> float:
"""Count Jaccard coefficient for output and ground-truth image.
Parameters
----------
y_true: tf.Tensor
tensor of true pixel values
y_pred: tf.Tensor
tensor of predicted pixel values
Returns
-------
float
Jaccard coefficient calculated on predicted and input class values.
Example
-------
unagi.utils.metric_utils.ModelMetrics.jacard_coef(y_true, y_pred)
"""
y_true_f = K.flatten(y_true)
y_pred_f = K.flatten(y_pred)
intersection = K.sum(y_true_f * y_pred_f)
return (intersection + 1.0) / (
K.sum(y_true_f) + K.sum(y_pred_f) - intersection + 1.0
)
[docs] @staticmethod
def jacard_coef_loss(y_true: tf.Tensor, y_pred: tf.Tensor) -> float:
"""Calculates loss based on Jaccard coefficient for
output and ground-truth image.
Parameters
----------
y_true: tf.Tensor
tensor of true pixel values
y_pred: tf.Tensor
tensor of predicted pixel values
Returns
-------
float
Jaccard loss calculated from Jaccard coefficient.
See Also
--------
jacard_coef()
Example
-------
unagi.utils.metric_utils.ModelMetrics.jacard_coef_loss(y_true, y_pred)
"""
return 1 - ModelMetrics.jacard_coef(y_true, y_pred)
[docs] @staticmethod
def weighted_cross_entropy(pos_weight: float = 0.2) -> Callable[[Any, Any], float]:
"""Calculates weighted cross entropy loss on predicted pixels.
Parameters
----------
y_true: tf.Tensor
tensor of true pixel values
y_pred: tf.Tensor
tensor of predicted pixel values
Returns
-------
Callable[[Any, Any], float]
loss value calculated based on the weight factor
References
----------
[1] keras implementation :
lars76.github.io/neural-networks/object-detection/losses-for-segmentation/
Example
-------
unagi.utils.metric_utils.ModelMetrics.weighted_cross_entropy(y_true, y_pred)
"""
def convert_to_logits(y_pred: tf.Tensor) -> tf.Tensor:
y_pred = tf.clip_by_value(
y_pred, tf.keras.backend.epsilon(), 1 - tf.keras.backend.epsilon()
)
return tf.math.log(y_pred / (1 - y_pred))
def loss(y_true: tf.Tensor, y_pred: tf.Tensor) -> float:
y_pred = convert_to_logits(y_pred)
loss = tf.nn.weighted_cross_entropy_with_logits(
logits=y_pred, labels=y_true, pos_weight=pos_weight
)
# or reduce_sum and/or axis=-1
return tf.reduce_mean(input_tensor=loss * (1 - pos_weight))
return loss
[docs] @staticmethod
def focal_loss(
alpha: float = 0.25, gamma: float = 2.0
) -> Callable[[Any, Any], float]:
"""Calculates focal loss on predicted pixels.
Parameters
----------
alpha: float
tensor of true pixel values
gamma: float
tensor of predicted pixel values
Returns
-------
Callable[[Any, Any], float]
focal loss value calculated predicted pixels.
References
----------
[1] keras implementation :
lars76.github.io/neural-networks/object-detection/losses-for-segmentation/
[2] oiginal paper : Focal Loss for Dense Object Detection
https://arxiv.org/abs/1708.02002
Example
-------
unagi.utils.metric_utils.ModelMetrics.focal_loss()
"""
def focal_loss_with_logits(
logits: tf.Tensor,
targets: tf.Tensor,
alpha: float,
gamma: float,
y_pred: tf.Tensor,
) -> tf.Tensor:
"""Calculates focal loss based on aplha and gamma."""
weight_a = alpha * (1 - y_pred) ** gamma * targets
weight_b = (1 - alpha) * y_pred**gamma * (1 - targets)
return (tf.math.log1p(tf.exp(-tf.abs(logits))) + tf.nn.relu(-logits)) * (
weight_a + weight_b
) + logits * weight_b
def loss(y_true: tf.Tensor, y_pred: tf.Tensor) -> float:
"""Calculates final focal loss value."""
y_pred = tf.clip_by_value(
y_pred, tf.keras.backend.epsilon(), 1 - tf.keras.backend.epsilon()
)
logits = tf.math.log(y_pred / (1 - y_pred))
loss = focal_loss_with_logits(
logits=logits, targets=y_true, alpha=alpha, gamma=gamma, y_pred=y_pred
)
# or reduce_sum and/or axis=-1
return tf.reduce_mean(input_tensor=loss)
return loss
[docs] @staticmethod
def tversky(y_true: tf.Tensor, y_pred: tf.Tensor) -> float:
"""Calculates tversky index based on predicted pixels.
Parameters
----------
y_true: tf.Tensor
tensor of true pixel values
y_pred: tf.Tensor
tensor of predicted pixel valuesass
Returns
-------
float
tversky index value calculated on predicted pixels.
Example
-------
unagi.utils.metric_utils.ModelMetrics.tversky(y_true, y_pred)
"""
smooth = 1
y_true_pos = K.flatten(y_true)
y_pred_pos = K.flatten(y_pred)
true_pos = K.sum(y_true_pos * y_pred_pos)
false_neg = K.sum(y_true_pos * (1 - y_pred_pos))
false_pos = K.sum((1 - y_true_pos) * y_pred_pos)
alpha = 0.7
return (true_pos + smooth) / (
true_pos + alpha * false_neg + (1 - alpha) * false_pos + smooth
)
[docs] @staticmethod
def tversky_loss(y_true: tf.Tensor, y_pred: tf.Tensor) -> float:
"""Calculates tversky loss based on tversky index for predicted pixels.
Parameters
----------
y_true: tf.Tensor
tensor of true pixel values
y_pred: tf.Tensor
tensor of predicted pixel valuesass
Returns
-------
float
tversky loss value calculated on tversky index for predicted pixels.
Example
-------
unagi.utils.metric_utils.ModelMetrics.tversky_loss(y_true, y_pred)
"""
return 1 - ModelMetrics.tversky(y_true, y_pred)
[docs] @staticmethod
def focal_tversky(y_true: tf.Tensor, y_pred: tf.Tensor) -> float:
"""Calculates focal-tversky loss based on tversky index for predicted pixels.
Parameters
----------
y_true: tf.Tensor
tensor of true pixel values
y_pred: tf.Tensor
tensor of predicted pixel valuesass
Returns
-------
float
focal-tversky loss value calculated on tversky index for predicted pixels.
References
----------
[1] keras implementation :
https://github.com/nabsabraham/focal-tversky-unet
[2] oiginal paper : A Novel Focal Tversky loss function with improved Attention
U-Net for lesion segmentation.
https://arxiv.org/abs/1810.07842
Example
-------
unagi.utils.metric_utils.ModelMetrics.focal_tversky(y_true, y_pred)
"""
pt_1 = ModelMetrics.tversky(y_true, y_pred)
gamma = 0.75
return K.pow((1 - pt_1), gamma)