Tensorflow/Keras 中 Dice Loss 的正确实现

Des*_*wal 11 machine-learning computer-vision deep-learning keras tensorflow

我一直在尝试尝试基于区域的:骰子损失,但互联网上有很多不同程度的变化,我找不到两个相同的实现。问题是所有这些都会产生不同的结果。以下是我找到的实现。有些使用作者在本文中称为smoothing的因子,有些在分子和分母中使用它,使用一种实现等等。epsilonGamma

有人可以帮助我正确实施吗?

import tensorflow as tf
import tensorflow.keras.backend as K
import numpy as np

def dice_loss1(y_true, y_pred, smooth=1e-6):
    '''
    https://www.kaggle.com/code/bigironsphere/loss-function-library-keras-pytorch/notebook
    '''
    y_pred = tf.convert_to_tensor(y_pred)
    y_true = tf.cast(y_true, y_pred.dtype)
    smooth = tf.cast(smooth, y_pred.dtype)
    
    y_pred = K.flatten(y_pred)
    y_true = K.flatten(y_true)
    
    intersection = K.sum(K.dot(y_true, y_pred))    
    dice_coef = (2*intersection + smooth) / (K.sum(y_true) + K.sum(y_pred) + smooth)
    dice_loss = 1-dice_coef
    return dice_loss
    

def dice_loss2(y_true, y_pred, smooth=1e-6): # Only Smooth
    """
    https://gist.github.com/wassname/7793e2058c5c9dacb5212c0ac0b18a8a
    """
    y_pred = tf.convert_to_tensor(y_pred)
    y_true = tf.cast(y_true, y_pred.dtype)
    smooth = tf.cast(smooth, y_pred.dtype)
    
    intersection = K.sum(K.abs(y_true * y_pred), axis=-1)
    dice_coef  = (2. * intersection + smooth) / (K.sum(K.square(y_true),-1) + K.sum(K.square(y_pred),-1) + smooth)
    return 1- dice_coef


def dice_loss3(y_true, y_pred): # No gamma, no smooth
    '''
    https://lars76.github.io/2018/09/27/loss-functions-for-segmentation.html
    '''
    y_pred = tf.convert_to_tensor(y_pred)
    y_true = tf.cast(y_true, y_pred.dtype)
    
    y_pred = tf.math.sigmoid(y_pred)
    numerator = 2 * tf.reduce_sum(y_true * y_pred)
    denominator = tf.reduce_sum(y_true + y_pred)

    return 1 - numerator / denominator


def dice_loss4(y_true, y_pred, smooth=1e-6, gama=1): # Gama + Smooth is used
    '''
    https://dev.to/_aadidev/3-common-loss-functions-for-image-segmentation-545o
    '''
    y_pred = tf.convert_to_tensor(y_pred)
    y_true = tf.cast(y_true, y_pred.dtype)
    smooth = tf.cast(smooth, y_pred.dtype)
    gama = tf.cast(gama, y_pred.dtype)

    nominator = 2 * tf.reduce_sum(tf.multiply(y_pred, y_true)) + smooth
    denominator = tf.reduce_sum(y_pred ** gama) + tf.reduce_sum(y_true ** gama) + smooth

    result = 1 - tf.divide(nominator, denominator)
    return result

y_true = np.array([[0,0,1,0],
                   [0,0,1,0],
                   [0,0,1.,0.]])

y_pred = np.array([[0,0,0.9,0],
                   [0,0,0.1,0],
                   [1,1,0.1,1.]])

# print(dice_loss1(y_true, y_pred)) # Gives you error in K.dot()
print(dice_loss2(y_true, y_pred))
print(dice_loss3(y_true, y_pred)) # provides array of values
print(dice_loss4(y_true, y_pred))
Run Code Online (Sandbox Code Playgroud)

dan*_*all 21

我利用骰子损失的变体进行脑肿瘤分割。我用于此类结果的骰子系数的实现是:

def dice_coef(y_true, y_pred, smooth=100):        
    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. * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth)
    return dice
Run Code Online (Sandbox Code Playgroud)

为了使其成为损失,需要将其做成我们想要最小化的函数。这可以通过将其设为负数来实现:

def dice_coef_loss(y_true, y_pred):
    return -dice_coef(y_true, y_pred)
Run Code Online (Sandbox Code Playgroud)

或从 1 中减去它:

def dice_coef_loss(y_true, y_pred):
    return 1 - dice_coef(y_true, y_pred)
Run Code Online (Sandbox Code Playgroud)

或应用一些其他函数然后取负 - 例如,取负对数(这可以平滑梯度):

def dice_coef_loss(y_true, y_pred):
    return -K.log(dice_coef(y_true, y_pred))
Run Code Online (Sandbox Code Playgroud)

该变量smooth代表您在其他实现中使用各种名称(smoothingepsilon等)观察到的结果。为了清楚起见,这个平滑变量的存在是为了处理地面事实具有很少白色(或没有)白色像素的情况(假设白色像素属于对象的类或边界,具体取决于您的实现)。

如果smooth设置得太低,当真实图像具有很少到 0 的白色像素并且预测图像具有一些非零数量的白色像素时,模型将受到更严重的惩罚。设置smooth较高意味着如果预测图像具有少量白色像素,而地面实况没有白色像素,则损失值会较低。不过,根据模型需要达到的积极程度,较低的值可能会更好。

这是一个说明性示例:

import numpy as np
import tensorflow as tf
from tensorflow.keras import backend as K


def dice_coef(y_true, y_pred, smooth):
    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. * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth)
    return dice


def dice_coef_loss(y_true, y_pred, smooth):
    return 1 - dice_coef(y_true, y_pred, smooth)


if __name__ == '__main__':
    smooth = 10e-6
    y_pred = np.zeros((1, 128, 128))
    # one pixel is set to 1
    y_pred[0, 0, 0] = 1
    y_pred = tf.convert_to_tensor(y_pred, dtype=tf.float32)
    y_true = tf.zeros((1, 128, 128), dtype=tf.float32)
    print(dice_coef(y_true, y_pred, smooth=smooth))
    print(dice_coef_loss(y_true, y_pred, smooth=smooth))

Run Code Online (Sandbox Code Playgroud)

将打印出:

tf.Tensor(9.9999e-06, shape=(), dtype=float32)
tf.Tensor(0.99999, shape=(), dtype=float32)
Run Code Online (Sandbox Code Playgroud)

但如果smooth设置为 100:

tf.Tensor(0.990099, shape=(), dtype=float32)
tf.Tensor(0.009900987, shape=(), dtype=float32)
Run Code Online (Sandbox Code Playgroud)

显示损失减少到 0.009,而不是 0.99。

为了完整起见,如果您有多个分割通道(B X W X H X K,其中B是批量大小,WH图像的尺寸, 是K不同的分割通道),则适用相同的概念,但可以按如下方式实现:

def dice_coef_multilabel(y_true, y_pred, M, smooth):
    dice = 0
    for index in range(M):
        dice += dice_coef(y_true[:,:,:,index], y_pred[:,:,:,index], smooth)
    return dice
Run Code Online (Sandbox Code Playgroud)

并且可以通过求反或减法将其转换为损失函数,方法与原样相同dice_coefsmooth如果您提供列表或其他序列(例如;smooth_list),也可以按频道进行调整:

def dice_coef_multilabel(y_true, y_pred, M, smooth_list):
    dice = 0
    for index in range(M):
        dice += dice_coef(y_true[:,:,:,index], y_pred[:,:,:,index], smooth_list[index])
    return dice
Run Code Online (Sandbox Code Playgroud)

  • 非常感谢您特别针对多标签分类的一些见解 (2认同)