关于小目标的分割问题的损失函数

问题描述:

往往一幅图像中只有一个或者两个目标,而且目标的像素比例比较小,使网络训练较为困难,一般可能有三种的解决方式:

1. 选择合适的loss function, 对网络进行合理的优化,关注较小的目标。
2. 改变网络结构,使用attention机制。(类别判断作为辅助)
3. 与2的基本原理一致,属于attention。即先检测目标区域,裁剪后进行分割。

损失函数

交叉熵系列

交叉熵损失

对于二分类而言,损失函数定义如下:

l o s s ( Y , P ) = − 1 n ∑ i = 1 n [ y i l n p i + ( 1 − y i ) l n ( 1 − p i ) ] loss(Y,P) = -\frac{1}{n}\sum^n\nolimits_{i=1}[y_ilnp_i+(1-y_i)ln(1-p_i)] loss(Y,P)=n1i=1n[yilnpi+(1yi)ln(1pi)]
其中, y i y_i yi为输入实例 x i x_i xi的真实类别, p i p_i pi为预测输入实例 x i x_i xi属于1的概率。对所有样本的对数损失表示对每个样本的对数损失的平均值。

缺陷:同等的关注每一个类别,易受类别不均的影响,在分割领域尤其如此。

在Keras中,这个损失函数是binary_crossentropy(y_true, y_pred)

在TensorFlow中,函数是softmax_cross_entropy_with_logits_v2

WCE loss

带权重的交叉熵损失----Weighted cross-entropy
Y Y Y为标准的分割图,其中 y i y_i yi为label分割图中的某一个像素的真实值。 P P P为预测的概率图, p i p_i pi为像素的预测概率值,背景像素图的概率值则为 1 − P 1-P 1P
则,只有两个类别的带权重的交叉熵为:

w c e _ l o s s ( Y , P ) = − 1 n ∑ i = 1 n [ β y i l n p i + ( 1 − y i ) l n ( 1 − p i ) ] wce\_loss(Y,P) = -\frac{1}{n}\sum^n\nolimits_{i=1}[{\beta}y_ilnp_i+(1-y_i)ln(1-p_i)] wce_loss(Y,P)=n1i=1n[βyilnpi+(1yi)ln(1pi)]
β {\beta} β为权重,

β = − N − ∑ n p n ∑ n p n {\beta} = -\frac{N-\sum_np_n}{\sum_np_n} β=npnNnpn
缺点:需要人为的调整困难样本的权重,增加调参难度。

在TensorFlow中,函数是weighted_cross_entropy_with_logits
在Keras中,需要自己实现这个函数

def weighted_cross_entropy(beta):
  def convert_to_logits(y_pred):
      # see https://github.com/tensorflow/tensorflow/blob/r1.10/tensorflow/python/keras/backend.py#L3525
      y_pred = tf.clip_by_value(y_pred, tf.keras.backend.epsilon(), 1 - tf.keras.backend.epsilon())

      return tf.log(y_pred / (1 - y_pred))

  def loss(y_true, y_pred):
    y_pred = convert_to_logits(y_pred)
    loss = tf.nn.weighted_cross_entropy_with_logits(logits=y_pred, targets=y_true, pos_weight=beta)

    # or reduce_sum and/or axis=-1
    return tf.reduce_mean(loss)

  return loss

Balanced cross entropy(BCE)

对负样本也加权,定义为:
b c e _ l o s s ( Y , P ) = − 1 n ∑ i = 1 n [ β y i l n p i + ( 1 − β ) ( 1 − y i ) l n ( 1 − p i ) ] bce\_loss(Y,P) = -\frac{1}{n}\sum^n\nolimits_{i=1}[{\beta}y_ilnp_i+(1-{\beta})(1-y_i)ln(1-p_i)] bce_loss(Y,P)=n1i=1n[βyilnpi+(1β)(1yi)ln(1pi)]

在Keras中可以这样实现:

def balanced_cross_entropy(beta):
  def convert_to_logits(y_pred):
      # see https://github.com/tensorflow/tensorflow/blob/r1.10/tensorflow/python/keras/backend.py#L3525
      y_pred = tf.clip_by_value(y_pred, tf.keras.backend.epsilon(), 1 - tf.keras.backend.epsilon())

      return tf.log(y_pred / (1 - y_pred))

  def loss(y_true, y_pred):
    y_pred = convert_to_logits(y_pred)
    pos_weight = beta / (1 - beta)
    loss = tf.nn.weighted_cross_entropy_with_logits(logits=y_pred, targets=y_true, pos_weight=pos_weight)

    # or reduce_sum and/or axis=-1
    return tf.reduce_mean(loss * (1 - beta))

  return loss

β = 1 {\beta}=1 β=1时,pos_weight的分母无法定义(当 β {\beta} β 不是一个固定值时这种情况会发生)。这个时候可以为 β {\beta} β加一个小的值 ε {\varepsilon} ε

Focal loss

能否使网络主动学习困难样本呢?
focal loss的提出是在目标检测领域,为了解决正负样本比例严重失衡的问题。是由log loss改进而来的,为了于log loss进行对比,公式如下:

f o c a l _ l o s s ( Y , P ) = − 1 n ∑ i = 1 n [ α y i ( 1 − p i ) γ l n p i + ( 1 − α ) ( 1 − y i ) p i γ l n ( 1 − p i ) ] focal\_loss(Y,P) = -\frac{1}{n}\sum^n\nolimits_{i=1}[{\alpha}y_i(1-p_i)^{\gamma }lnp_i+(1-{\alpha})(1-y_i)p^{\gamma}_iln(1-p_i)] focal_loss(Y,P)=n1i=1n[αyi(1pi)γlnpi+(1α)(1yi)piγln(1pi)]

在BCE上多了 ( 1 − p i ) γ (1-p_i)^{\gamma} (1pi)γ

其目的是关注难例(也就是给难分类的样本较大的权重)。其基本思想就是,对于类别极度不均衡的情况下,网络如果在log loss下会倾向于只预测负样本,并且负样本的预测概率 p i p_i pi也会非常的高,回传的梯度也很大。但是如果添加 ( 1 − p i ) γ (1-p_i)^{\gamma} (1pi)γ则会使预测概率大的样本得到的loss变小,而预测概率小的样本,loss变得大,从而加强对正样本的关注度。可以改善目标不均衡的现象,对此情况比 binary_crossentropy 要好很多。
目前在图像分割上只是适应于二分类。

缺点:引入了额外参数,增加了调参难度。

在keras上的代码如下:

from keras import backend as K
'''
Compatible with tensorflow backend
'''
def focal_loss(gamma=2., alpha=.25):
	def focal_loss_fixed(y_true, y_pred):
		pt_1 = tf.where(tf.equal(y_true, 1), y_pred, tf.ones_like(y_pred))
        pt_0 = tf.where(tf.equal(y_true, 0), y_pred, tf.zeros_like(y_pred))
        return -K.sum(alpha * K.pow(1. - pt_1, gamma) * K.log(pt_1))-K.sum((1-alpha) * K.pow( pt_0, gamma) * K.log(1. - pt_0))
        # return -K.mean(alpha * K.pow(1. - pt_1, gamma) * K.log(pt_1)) - K.mean((1 - alpha) * K.pow(pt_0, gamma) * K.log(1. - pt_0)) #与其他keras中自定义的损失函数保持一致

	return focal_loss_fixed

使用方法:

model_prn.compile(optimizer=optimizer, loss=[focal_loss(alpha=.25, gamma=2)])

Overlap measures

Dice Loss / F1 score

dice loss的提出是在V-net中,其中的一段原因描述是在感兴趣的解剖结构仅占扫描的非常小的区域,从而使学习过程陷入损失函数的局部最小值。所以要加大前景区域的权重。
Dice可理解为是两个轮廓区域的相似程度。用A和B表示两个轮廓区域所包含的点击,定义为:

D I C E _ l o s s ( A , B ) = 2 ∣ A ∩ B ∣ ∣ A ∣ + ∣ B ∣ DICE\_loss(A,B)=2\frac{{\left| {A \cap B} \right|}}{{\left| A \right| + \left| B \right|}} DICE_loss(A,B)=2A+BAB
也可表示为:

D I C E _ l o s s = 2 T P 2 T P + F N + F P DICE\_loss=\frac{2TP}{2TP+FN+FP} DICE_loss=2TP+FN+FP2TP
其中,TP,FP,FN分别是真阳性,假阳性,假阴性的个数。

在二分类中:
4 D I C E _ l o s s ( P , r ) 2 = 1 − ∑ n = 1 N p n r n + ε ∑ n = 1 N p n + r n + ε − ∑ n = 1 N ( 1 − p n ) ( 1 − r n ) + ε ∑ n = 1 N 2 − p n − r n + ε DICE\_loss(P,r)_2=1-\frac{\sum^N_{n=1}p_nr_n+\varepsilon}{\sum^N_{n=1}p_n+r_n+\varepsilon}-\frac{\sum^N_{n=1}(1-p_n)(1-r_n)+\varepsilon}{\sum^N_{n=1}2-p_n-r_n+\varepsilon} DICE_loss(P,r)2=1n=1Npn+rn+εn=1Npnrn+εn=1N2pnrn+εn=1N(1pn)(1rn)+ε$
代码:

def dice_coef(y_true, y_pred, smooth=1):
    intersection = K.sum(y_true * y_pred, axis=[1,2,3])
    union = K.sum(y_true, axis=[1,2,3]) + K.sum(y_pred, axis=[1,2,3])
    return K.mean( (2. * intersection + smooth) / (union + smooth), axis=0)
def dice_coef_loss(y_true, y_pred):
	1 - dice_coef(y_true, y_pred, smooth=1)

总结:

①. 有时使用dice loss会使训练曲线不可信,而且dice loss好的模型并不一定在其他的评价标准上效果好。例如:mean surface distance 或者 hausdorff surface distance。
不可信的原因是梯度,对于softmax或者是log loss,其梯度可以简化为 p − t p-t pt,t为目标值,p为预测值。而dice loss为 2 t 2 ( p + t ) 2 \frac{2t^2}{(p+t)^2} (p+t)22t2。如果p,t过小,则会导致梯度变化剧烈,导致训练困难。
②. 属于直接在评价标准上进行优化。
③. 对于样本不均衡的场景下确实好用。

IOU(Jaccard Index)

类比于DICE loss,也是直接针对评价标准进行优化。
在图像分割领域上,IOU被定义为:
I O U = T P T P + F P + F N IOU = \frac{TP}{TP+FP+FN} IOU=TP+FP+FNTP
而TP,FP,FN分别是真阳性,假阳性,假阴性的个数。

作为loss function,定义为:
I O U ( X , Y ) = I ( X ) U ( X ) IOU(X,Y) = \frac{I(X)}{U(X)} IOU(X,Y)=U(X)I(X)
其中: I ( x ) = X ∗ Y I(x) = X*Y I(x)=XY, u ( x ) = X + Y − X ∗ Y u(x) = X+Y-X*Y u(x)=X+YXY
X是预测值,而Y是真实标签。

代码:

## intersection over union
def IoU(y_true, y_pred, eps=1e-6):
    if np.max(y_true) == 0.0:
        return IoU(1-y_true, 1-y_pred) ## empty image; calc IoU of zeros
    intersection = K.sum(y_true * y_pred, axis=[1,2,3])
    union = K.sum(y_true, axis=[1,2,3]) + K.sum(y_pred, axis=[1,2,3]) - intersection
    return -K.mean( (intersection + eps) / (union + eps), axis=0)

缺点:

同DICE loss类似,训练曲线不可信,训练过程不稳定。不如直接使用softmax loss等的曲线有直观性,通常而言,softmax得到的loss下降曲线较为平滑。

Tversky loss

tversky 系数是Dice系数和Jaccard系数的一种推广。
T ( A , B ) = ∣ A ∩ B ∣ ∣ A ∩ B ∣ + α ∣ A − B ∣ + β ∣ B − A ∣ T(A,B) = \frac{|{A \cap B}|}{|{A \cap B}| + \alpha|A-B| + \beta|B-A|} T(A,B)=AB+αAB+βBAAB
A和B分别是真实值和预测值。当设置 α = β = 0.5 \alpha = \beta=0.5 α=β=0.5时,Tversky系数与Dice loss相同。当设置 α = β = 1 \alpha = \beta=1 α=β=1时,Tversky系数相当于Jaccard系数。 α \alpha α和\beta 分 别 控 制 假 阳 性 ( 分别控制假阳性( (|A-B| , 即 F P ) 和 假 阴 性 ( ,即FP)和假阴性( ,FP)(|B-A| , 即 F N ) 。 通 过 调 整 ,即FN)。通过调整 ,FN)\alpha 和 β 和\beta β可以控制假阳性和假阴性之间的平衡。

补充----Jaccard系数:
J ( A , B ) = ∣ A ∩ B ∣ ∣ A ∪ B ∣ = ∣ A ∩ B ∣ ∣ A ∣ + ∣ B ∣ − ∣ A ∩ B ∣ J(A,B) = \frac{|{A \cap B}|}{|{A \cup B}|} = \frac{|{A \cap B}|}{|A| + |B| - |{A \cap B}|} J(A,B)=ABAB=A+BABAB
代码:

def tversky(y_true, y_pred):
    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)
def tversky_loss(y_true, y_pred):
    return 1 - tversky(y_true,y_pred)
def focal_tversky(y_true,y_pred):
    pt_1 = tversky(y_true, y_pred)
    gamma = 0.75
    return K.pow((1-pt_1), gamma)

敏感性–特异性 loss

敏感性相当于召回率,表示正确分割目标的能力:
s e n s i t i v i t y = T P T P + F N sensitivity = \frac{TP}{TP+FN} sensitivity=TP+FNTP
特异性,表示正确分割背景的能力:
s p e c i f i c i t y = T N T N + F P specificity = \frac{TN}{TN+FP} specificity=TN+FPTN
Sensitivity - Specificity (SS)提出是在:
S S = λ ∑ n = 1 N ( r n − p n ) 2 r n ∑ n = 1 N r n + ε + ( 1 − λ ) ∑ n = 1 N ( r n − p n ) 2 ( 1 − r n ) ∑ n = 1 N ( 1 − r n ) + ε SS = \lambda \frac{{\sum\nolimits_{n = 1}^N {{{\left( {{r_n} - {p_n}} \right)}^2}{r_n}} }}{{\sum\nolimits_{n = 1}^N {{r_n} + \varepsilon } }} + \left( {1 - \lambda } \right)\frac{{\sum\nolimits_{n = 1}^N {{{\left( {{r_n} - {p_n}} \right)}^2}\left( {1 - {r_n}} \right)} }}{{\sum\nolimits_{n = 1}^N {\left( {1 - {r_n}} \right) + \varepsilon } }} SS=λn=1Nrn+εn=1N(rnpn)2rn+(1λ)n=1N(1rn)+εn=1N(rnpn)2(1rn)
其中公式左边是识别的错误率,即:1-sensitivity,而不是正确率。所以设置 λ \lambda λ为0.05.其中 ( r n − p n ) 2 ({r_n} - {p_n})^2 (rnpn)2是为了得到平滑的梯度。

Brosch, T., Yoo, Y., Tang, L.Y., Li, D.K., Traboulsee, A., Tam, R.: Deep convolutional encoder networks for multiple sclerosis lesion segmentation. In: MICCAI 2015. pp. 3–11. Springer (2015)

Generalized Dice loss

使用Dice loss时,其中一个缺点是对小目标不利。在只有前景和背景的情况下,小目标一旦有部分像素预测错误,就会导致Dice loss的大幅度变动,从而导致梯度变化剧烈,训练不稳定。当分割具有多个区域时,一般针对每一类会有一个Dice,而Generalized Dice index将多个类别的dice进行整合,使用一个指标对结果进行量化。

对于两个类别的情况:
G D L = 1 − 2 ∑ l = 1 2 w l ∑ n r l n p l n ∑ l = 1 2 w l ∑ n r l n + p l n GDL = 1- 2\frac{\sum^2_{l=1}w_l\sum_nr_{ln}p_{ln}}{\sum^2_{l=1}w_l\sum_nr_{ln}+p_{ln}} GDL=12l=12wlnrln+plnl=12wlnrlnpln
其中, w l w_l wl是每个类别的权重, r l n r_{ln} rln为类别 l l l在第 n n n个像素的标准值(GT),而 p l n p_{ln} pln为相应的预测概率值。
w l = 1 ( ∑ n = 1 N r l n ) 2 w_l = \frac{1}{(\sum^N_{n=1}r_{ln})^2} wl=(n=1Nrln)21
类别数越大,值越小。

代码:

def generalized_dice_coeff(y_true, y_pred):
    Ncl = y_pred.shape[-1]
    w = K.zeros(shape=(Ncl,))
    w = K.sum(y_true, axis=(0,1,2))
    w = 1/(w**2+0.000001)
    # Compute gen dice coef:
    numerator = y_true*y_pred
    numerator = w*K.sum(numerator,(0,1,2,3))
    numerator = K.sum(numerator)
    denominator = y_true+y_pred
    denominator = w*K.sum(denominator,(0,1,2,3))
    denominator = K.sum(denominator)
    gen_dice_coef = 2*numerator/denominator
    return gen_dice_coef
def generalized_dice_loss(y_true, y_pred):
    return 1 - generalized_dice_coeff(y_true, y_pred)

但是在AnatomyNet中提到GDL面对极度不均衡的情况下,训练的稳定性仍然不能保证。

Crum, W., Camara, O., Hill, D.: Generalized Overlap Measures for Evaluation and Validation in Medical Image Analysis. IEEE TMI 25(11), 1451–1461 (nov 2006)

以上本质上都是根据评测标准设计的loss function,有时候普遍会受到目标太小的影响,导致训练的不稳定;对比可知,直接使用log loss等的loss曲线一般都是相比较光滑的。

损失函数组合

BCE+Dice loss

Binary Cross Entropy + Dice loss
添加二分类交叉熵损失函数,在数据较为平衡的情况下有改善作用,但是在数据极度不均衡的情况下,交叉熵损失会在几个训练之后远小于Dice损失,效果会损失。

import keras.backend as K
from keras.losses import binary_crossentropy
def dice_coef(y_true, y_pred, smooth=1):
    intersection = K.sum(y_true * y_pred, axis=[1,2,3]) ##y_true与y_pred都是矩阵!(Unet)
    union = K.sum(y_true, axis=[1,2,3]) + K.sum(y_pred, axis=[1,2,3])
    return K.mean( (2. * intersection + smooth) / (union + smooth), axis=0)
def dice_p_bce(in_gt, in_pred):
    return 1e-3*binary_crossentropy(in_gt, in_pred) - dice_coef(in_gt, in_pred)

Dice+Focal loss

处理小目标的分割。直接使用dice会使得训练的稳定性降低,因此此处再添加上Focal loss。

L = L D I C E + λ L F o c a l = C − ∑ c = 0 C T P n ( c ) T P n ( c ) + α F N p ( c ) + β F P p ( c ) − λ 1 N ∑ c = 0 C ∑ n = 1 N g n ( c ) ( 1 − p n ( c ) ) 2 log ⁡ ( p n ( c ) ) L = L_{DICE} + \lambda L_{Focal} \\ = C - \sum\limits_{c = 0}^C {\frac{{T{P_n}(c)}}{{T{P_n}(c) + \alpha F{N_p}(c) + \beta F{P_p}(c)}}} - \lambda \frac{1}{N}\sum\limits_{c = 0}^C {\sum\limits_{n = 1}^N {{g_n}(c){{(1 - {p_n}(c))}^2}\log ({p_n}(c))} } L=LDICE+λLFocal=Cc=0CTPn(c)+αFNp(c)+βFPp(c)TPn(c)λN1c=0Cn=1Ngn(c)(1pn(c))2log(pn(c))
其中,
T P n ( c ) = ∑ n = 1 N p n ( c ) g n ( c ) T{P_n}(c)= \sum_{n=1}^N p_n(c)g_n(c) TPn(c)=n=1Npn(c)gn(c) F N p ( c ) = ∑ n = 1 N ( 1 − p n ( c ) ) g n ( c ) F{N_p}(c)= \sum_{n=1}^N (1 - p_n(c))g_n(c) FNp(c)=n=1N(1pn(c))gn(c)
F P n ( c ) = ∑ n = 1 N p n ( c ) ( 1 − g n ( c ) ) F{P_n}(c)= \sum_{n=1}^N p_n(c)(1 - g_n(c)) FPn(c)=n=1Npn(c)(1gn(c)) 分别代表c的真阳性,假阴性和假阳性。定义 α = β = 0.5 \alpha = \beta = 0.5 α=β=0.5,此时Tversky系数就是Dice系数,为Dice loss。

AnatomyNet: Deep Learning for Fast and Fully Automated Whole-volume Segmentation of Head and Neck Anatomy: https://arxiv.org/pdf/1808.05238.pdf

Exponential Logarithmic loss

与加权的DICE LOSS进行对比,此方法觉得这种情况跟不同标签之间的相对尺寸无关,但是可以通过标签频率来进行平衡。其损失公式如下:

L E X P = w d i c e ∗ L d i c e + w c r o s s ∗ L c r o s s L_{EXP} = w_{dice}*L_{dice} + w_{cross} * L_{cross} LEXP=wdiceLdice+wcrossLcross
L d i c e L_{dice} Ldice为指数lod Dice损失, L c r o s s L_{cross} Lcross为指数交叉熵损失。

L d i c e = E [ ( − l n ( D i c e i ) ) γ D i c e ] L_{dice} = E[(-ln(Dice_i))^{\gamma_{Dice}}] Ldice=E[(ln(Dicei))γDice]

交叉熵损失:
L C E = − l o g ( p l ( x ) ) L_{CE} = -log(p_l(x)) LCE=log(pl(x))

新的指数交叉熵损失:
L C r o s s = w l ( − l n ( p l ( x ) ) ) γ C r o s s L_{Cross} = w_l(-ln(p_l(x)))^{\gamma_{Cross}} LCross=wl(ln(pl(x)))γCross
其中, w l = ( ∑ k f k f l ) 0.5 w_l = (\frac{\sum_kf_k}{f_l})^{0.5} wl=(flkfk)0.5, f k f_k fk为标签k出现的频率, w l w_l wl可以减小出现频率较高的类别权重。 w l w_l wl f l f_l fl的增大而变小,即减小权重,反之如果 f l f_l fl太大,那么权重会变小。而 γ D i c e \gamma_{Dice} γDice γ C r o s s \gamma_{Cross} γCross可以提升非线性的作用。

缺点:
新增了4个参数权重,分别是 w D i c e w_{Dice} wDice, w C r o s s w_{Cross} wCross, γ D i c e \gamma_{Dice} γDice γ C r o s s \gamma_{Cross} γCross,给调参带来不小的麻烦。

参考链接:
https://blog.csdn.net/m0_37477175/article/details/83004746
https://zhuanlan.zhihu.com/p/89194726
https://www.zhihu.com/question/297255242/answer/505965855

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值