我一直在尝试基于区域:骰子丢失,但在互联网上有很多不同程度的变化,我找不到两个相同的实现。问题是,所有这些都产生了不同的结果。下面是我找到的实现。有的用smoothing
因子,本文称其为epsilon
,有的用它作分子和分母,有的用Gamma
等。
有没有人可以帮我正确的实现。
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))
我利用骰子损失的变化来分割脑肿瘤。我使用的骰子系数的实现是:
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
为了使其成为损失,需要将其变成我们想要最小化的函数。这可以通过设置为否定来实现:
def dice_coef_loss(y_true, y_pred):
return -dice_coef(y_true, y_pred)
或从1中减去
def dice_coef_loss(y_true, y_pred):
return 1 - dice_coef(y_true, y_pred)
或应用其他函数然后取负值-例如,取负对数(可以平滑梯度):
def dice_coef_loss(y_true, y_pred):
return -K.log(dice_coef(y_true, y_pred))
变量smooth
代表您在其他实现中的观察结果,具有不同的名称(smoothing
、epsilon
等)。为了清楚起见,这个平滑变量的存在是为了处理ground truth只有很少(或没有)白色像素的情况(假设白色像素属于一个类或对象的边界,这取决于你的实现)。
如果smooth
设置过低,当ground truth有少量到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))
将输出:
tf.Tensor(9.9999e-06, shape=(), dtype=float32)
tf.Tensor(0.99999, shape=(), dtype=float32)
但是如果smooth
设置为100:
tf.Tensor(0.990099, shape=(), dtype=float32)
tf.Tensor(0.009900987, shape=(), dtype=float32)
显示损失减少到0.009而不是0.99。
为了完整起见,如果你有多个分割通道(B X W X H X K
,其中B
是批处理大小,W
和H
是图像的尺寸,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
与dice_coef
一样,可以通过取负或相减的方法转化为损失函数。smooth
也可以调优每个通道,如果你提供一个列表或一些其他序列(例如;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
只是想说,根据你的输入缩放,你可能会因为那里的差异而得到负的Dice损失。如果你的掩码都是0和1,并且你的预测掩码值来自于一个输出范围为0-1的sigmoid激活函数,这可能会发生。为了避免这种情况,我在骰子损失函数中设置了预测y值的阈值。我不确定这是不是其他人的处理方式,但这对我来说真的很有效。
def dice_coeff(y_true, y_pred):
smooth = 100
# Flatten
y_true_f = tf.cast(tf.reshape(y_true, [-1]),'float32')
y_pred_f = tf.cast(tf.reshape(y_pred > 0.5, [-1]),'float32')
intersection = tf.reduce_sum(tf.math.multiply(y_true_f,y_pred_f))
score = (2. * intersection + smooth) / (tf.reduce_sum(y_true_f) + tf.reduce_sum(y_pred_f) + smooth)
return score