一、交叉熵损失函数-cross entropy
- 二分类交叉熵损失函数binary_crossentropy
其中,N为像素点个数,为输入实例
的真实类别,
为预测输入实例
属于类别 1 的概率. 对所有样本的对数损失表示对每个样本的对数损失的平均值, 对于完美的分类器, 对数损失为 0。
- 多分类交叉熵损失函数categorical_crossentropy
图像分割中最常用的损失函数是逐像素交叉熵损失。该损失函数分别检查每个像素,将类预测(深度方向的像素向量)与我们的热编码目标向量进行比较。
该损失函数分别检查每个像素,m为类别数(num_label),将类预测(深度方向的像素向量)与我们的热编码目标向量
进行比较。
由此可见,交叉熵的损失函数单独评估每个像素矢量的类预测,然后对所有像素求平均值,所以我们可以认为图像中的像素被平等的学习了。但是,医学图像中常出现类别不均衡(class imbalance)的问题,由此导致训练会被像素较多的类主导,对于较小的物体很难学习到其特征,从而降低网络的有效性。
pytorch代码实现
pytorch自带的nn.CrossEntropyLoss结合了nn.logSoftmax()和nn.NLLLoss()
class CrossEntropy(nn.Module):
def __init__(self,ignore_label=-1,weight=None):
super(CrossEntropy,self).__init__()
self.ignore_label=ignore_label
self.criterion=nn.CrossEntropyLoss(weight=weight,
ignore_index=self.ignore_label)
def forward(self, score,target):
'''
:param score: Tensor[bs,num_classes,256,256]
:param target: Tensor[bs,256,256]
:return:
'''
loss=self.criterion(score,target)
return loss
二.带权重的交叉熵函数-Weighted cross entropy(WCE)
其中,为对预测概率图中每个类别的权重,用于加权在预测图上占比例小的类别对loss函数的贡献
求和符号中i=1到N
三.Dice Coefficient Loss
dice coefficient 源于二分类,本质上是衡量两个样本的重叠部分。该指标范围从0到1,其中“1”表示完整的重叠。 其计算公式为:
其中表示集合A、B 之间的共同元素的个数,
表示 A 中的元素的个数,B也用相似的表示方法。
为了计算预测的分割图的 dice coefficient,将近似为预测图每个类别score和target之间的点乘,并将结果函数中的元素相加。
因为我们的目标是二进制的,因而可以有效地将预测中未在 target mask 中“激活”的所有像素清零。对于剩余的像素,主要是在惩罚低置信度预测; 该表达式的较高值(在分子中)会导致更好的Dice系数。
为了量化计算 和
,部分研究人员直接使用简单的相加, 也有一些做法是取平方求和。
其中,在式子中 Dice系数的分子中有2,因为分母“重复计算” 了两组之间的共同元素。为了形成可以最小化的损失函数,我们将简单地使用1-Dice。这种损失函数被称为 soft dice loss,因为我们直接使用预测概率而不是使用阈值或将它们转换为二进制mask。
关于神经网络输出,分子涉及到我们的预测和 target mask 之间的共同激活,而分母将每个mask中的激活量分开考虑。实际上起到了利用 target mask 的大小来归一化损失的效果,使得 soft dice 损失不会难以从图像中具有较小空间表示的类中学习。
soft dice loss 将每个类别分开考虑,然后平均得到最后结果。
定义如下:
pytorch代码实现:
class SoftDiceLoss(nn.Module):
'''
Soft_Dice = 2*|dot(A, B)| / (|dot(A, A)| + |dot(B, B)| + eps)
eps is a small constant to avoid zero division,
'''
def __init__(self, weight=None):
super(SoftDiceLoss, self).__init__()
self.activation = nn.Softmax2d()
def forward(self, y_preds, y_truths, eps=1e-8):
'''
:param y_preds: [bs,num_classes,768,1024]
:param y_truths: [bs,num_calsses,768,1024]
:param eps:
:return:
'''
bs = y_preds.size(0)
num_classes = y_preds.size(1)
dices_bs = torch.zeros(bs,num_classes)
for idx in range(bs):
y_pred = y_preds[idx] #[num_classes,768,1024]
y_truth = y_truths[idx] #[num_classes,768,1024]
intersection = torch.sum(torch.mul(y_pred, y_truth),dim=(1,2)) + eps/2
union = torch.sum(torch.mul(y_pred, y_pred), dim=(1, 2)) + torch.sum(torch.mul(y_truth, y_truth), dim=(1, 2)) + eps
dices_sub = 2 * intersection / union
dices_bs[idx] = dices_sub
dices = torch.mean(dices_bs,dim=0)
dice = torch.mean(dices)
dice_loss = 1 - dice
return dice_loss
值得注意的是,dice loss比较适用于样本极度不均的情况,一般的情况下,使用 dice loss 会对反向传播造成不利的影响,容易使训练变得不稳定。有时使用dice loss会使训练曲线有时不可信,而且dice loss好的模型并不一定在其他的评价标准上效果更好,例如mean surface distance 或者是Hausdorff surface distance。不可信的原因是梯度,对于softmax或者是log loss其梯度简化而言为p−t,t为目标值,p为预测值。而dice loss为,如果p,t过小则会导致梯度变化剧烈,导致训练困难。
四.Generalized Dice loss
在使用DICE loss时,对小目标是十分不利的,因为在只有前景和背景的情况下,小目标一旦有部分像素预测错误,那么就会导致Dice大幅度的变动,从而导致梯度变化剧烈,训练不稳定。
GDL(the generalized Dice loss)公式如下:
在dice loss基础上增加了给每个类别加权,计算公式如下:
这样起到了平衡各类(包括背景类)目标区域对loss的贡献。
论文中的给出的分割效果:
但是在AnatomyNet中提到GDL面对极度不均衡的情况下,训练的稳定性仍然不能保证。
代码实现:
def generalized_dice_coeff(y_true, y_pred):
#y_true,y_pred shape=[num_label,H,W,C]
num_label=y_pred.shape[0]
w=K.zeros(shape=(num_label,))
w=K.sum(y_true,axis=(1,2,3))
w=1/(w**2+0.000001)
# Compute gen dice coef:
intersection_w = w*K.sum(y_true * y_pred, axis=[1,2,3])
union_w = w*K.sum(y_true+y_pred, axis=[1,2,3])
return K.mean( (2. * intersection_w + smooth) / (union_w + smooth), axis=0)
def generalized_dice_loss(y_true, y_pred):
return 1 - generalized_dice_coeff(y_true, y_pred)
五.IOU Loss
IOU类似于Dice,定义如下:
pytorch代码实现:
class SoftDiceLoss(nn.Module):
'''
Soft_Dice = 2*|dot(A, B)| / (|dot(A, A)| + |dot(B, B)| + eps)
eps is a small constant to avoid zero division,
'''
def __init__(self, weight=None):
super(SoftDiceLoss, self).__init__()
self.activation = nn.Softmax2d()
def forward(self, y_preds, y_truths, eps=1e-8):
'''
:param y_preds: [bs,num_classes,768,1024]
:param y_truths: [bs,num_calsses,768,1024]
:param eps:
:return:
'''
bs = y_preds.size(0)
num_classes = y_preds.size(1)
dices_bs = torch.zeros(bs,num_classes)
for idx in range(bs):
y_pred = y_preds[idx] #[num_classes,768,1024]
y_truth = y_truths[idx] #[num_classes,768,1024]
intersection = torch.sum(torch.mul(y_pred, y_truth),dim=(1,2)) + eps/2
union = torch.sum(torch.mul(y_pred, y_pred), dim=(1, 2)) + torch.sum(torch.mul(y_truth, y_truth), dim=(1, 2)) + eps
ious_sub = intersection / (union-intersection)
ious_bs[idx] = ious_sub
ious = torch.mean(ious_bs,dim=0)
iou = torch.mean(ious)
iou_loss = 1 - iou
return iou_loss
IOU loss的缺点呢同DICE loss是相类似的,训练曲线可能并不可信,训练的过程也可能并不稳定,有时不如使用softmax loss等的曲线有直观性,通常而言softmax loss得到的loss下降曲线较为平滑。