影像分割必備知識點 | Dice損失 理論+程式碼

忽逢桃林發表於2020-11-25

本文包含程式碼案例和講解,建議收藏,也順便點個贊吧。歡迎各路朋友愛好者加我的微信討論問題:cyx645016617.

在很多關於醫學影像分割的競賽、論文和專案中,發現 Dice 係數(Dice coefficient) 損失函式出現的頻率較多,這裡整理一下。使用影像分割,繞不開Dice損失,這個就好比在目標檢測中繞不開IoU一樣

1 概述

Dice損失和Dice係數(Dice coefficient)是同一個東西,他們的關係是:

\[DiceLoss = 1-DiceCoefficient \]

1.2 Dice 定義

  • Dice係數, 根據 Lee Raymond Dice命名,是一種集合相似度度量函式,通常用於計算兩個樣本的相似度(值範圍為 [0, 1])。

\[DiceCoefficient = \frac{2|X \bigcap Y|}{|X| + |Y|} \]

其中\(|X| \bigcap |Y|\)表示X和Y集合的交集,|X|和|Y|表示其元素個數,對於分割任務而言,|X|和|Y|表示分割的ground truth和predict_mask

此外,我們可以得到Dice Loss的公式:

\[DiceLoss = 1- \frac{2|X \bigcap Y|}{|X| + |Y|} \]

2 手推案例

這個Dice網上有一個非常好二分類的Dice Loss的手推的案例,非常好理解,過程分成兩個部分:

  1. 先計算\(|X|\bigcap|Y|\)
  2. 再計算\(|X|\)\(|Y|\)
    計算loss我們必然已經有了這兩個引數,模型給出的output,也就是預測的mask;資料集中的ground truth(GT),也就是真實的mask。

在很多關於醫學影像分割的競賽、論文和專案中,發現 Dice 係數(Dice coefficient) 損失函式出現的頻率較多,這裡整理一下。使用影像分割,繞不開Dice損失,這個就好比在目標檢測中繞不開IoU一樣

1 概述

Dice損失和Dice係數(Dice coefficient)是同一個東西,他們的關係是:

\[DiceLoss = 1-DiceCoefficient \]

1.2 Dice 定義

  • Dice係數, 根據 Lee Raymond Dice命名,是一種集合相似度度量函式,通常用於計算兩個樣本的相似度(值範圍為 [0, 1])。

\[DiceCoefficient = \frac{2|X \bigcap Y|}{|X| + |Y|} \]

其中\(|X| \bigcap |Y|\)表示X和Y集合的交集,|X|和|Y|表示其元素個數,對於分割任務而言,|X|和|Y|表示分割的ground truth和predict_mask

此外,我們可以得到Dice Loss的公式:

\[DiceLoss = 1- \frac{2|X \bigcap Y|}{|X| + |Y|} \]

2 手推案例

這個Dice網上有一個非常好二分類的Dice Loss的手推的案例,非常好理解,過程分成兩個部分:

  1. 先計算\(|X|\bigcap|Y|\)
  2. 再計算\(|X|\)\(|Y|\)
    計算loss我們必然已經有了這兩個引數,模型給出的output,也就是預測的mask;資料集中的ground truth(GT),也就是真實的mask。

當然還沒完,還要把結果加和:

對於二分類問題,GT分割圖是隻有 0, 1 兩個值的,因此可以有效的將在 Pred 分割圖中未在 GT 分割圖中啟用的所有畫素清零. 對於啟用的畫素,主要是懲罰低置信度的預測,較高值會得到更好的 Dice 係數.

關於計算\(|X|\)\(|Y|\),如下:

其中需要注意的是,一半情況下,這個是直接對所有元素求和,當然有對所有元素先平方再求和的做法。總之就這麼多,非常的簡單好用。不過上面的內容是針對分割二分類的情況,對於多分類的情況和二分類基本相同

3 二分類程式碼實現

在實現的時候,往往會加上一個smooth,防止分母為0的情況出現。所以公式變成:

\[DiceLoss = 1- \frac{2|X \bigcap Y|+smooth}{|X| + |Y|+smooth} \]

一般smooth為1

3.1 PyTorch實現

先是dice coefficient的實現,pred和target的shape為【batch_size,channels,...】,2D和3D的都可以用這個。

def dice_coeff(pred, target):
    smooth = 1.
    num = pred.size(0)
    m1 = pred.view(num, -1)  # Flatten
    m2 = target.view(num, -1)  # Flatten
    intersection = (m1 * m2).sum()
 
    return (2. * intersection + smooth) / (m1.sum() + m2.sum() + smooth)

當然dice loss就是1-dice ceofficient,所以可以寫成:

def dice_coeff(pred, target):
    smooth = 1.
    num = pred.size(0)
    m1 = pred.view(num, -1)  # Flatten
    m2 = target.view(num, -1)  # Flatten
    intersection = (m1 * m2).sum()
 
    return 1-(2. * intersection + smooth) / (m1.sum() + m2.sum() + smooth)

3.2 keras實現

smooth = 1. # 用於防止分母為0.
def dice_coef(y_true, y_pred):
    y_true_f = K.flatten(y_true) # 將 y_true 拉伸為一維.
    y_pred_f = K.flatten(y_pred)
    intersection = K.sum(y_true_f * y_pred_f)
    return (2. * intersection + smooth) / (K.sum(y_true_f * y_true_f) + K.sum(y_pred_f * y_pred_f) + smooth)

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

3.3 tensorflow實現

def dice_coe(output, target, loss_type='jaccard', axis=(1, 2, 3), smooth=1e-5):
    """
    Soft dice (Sørensen or Jaccard) coefficient for comparing the similarity of two batch of data, 
    usually be used for binary image segmentation
    i.e. labels are binary. 
    The coefficient between 0 to 1, 1 means totally match.

    Parameters
    -----------
    output : Tensor
        A distribution with shape: [batch_size, ....], (any dimensions).
    target : Tensor
        The target distribution, format the same with `output`.
    loss_type : str
        ``jaccard`` or ``sorensen``, default is ``jaccard``.
    axis : tuple of int
        All dimensions are reduced, default ``[1,2,3]``.
    smooth : float
        This small value will be added to the numerator and denominator.
            - If both output and target are empty, it makes sure dice is 1.
            - If either output or target are empty (all pixels are background), dice = ```smooth/(small_value + smooth)``, then if smooth is very small, dice close to 0 (even the image values lower than the threshold), so in this case, higher smooth can have a higher dice.

    Examples
    ---------
    >>> outputs = tl.act.pixel_wise_softmax(network.outputs)
    >>> dice_loss = 1 - tl.cost.dice_coe(outputs, y_)

    References
    -----------
    - `Wiki-Dice <https://en.wikipedia.org/wiki/Sørensen–Dice_coefficient>`__

    """
    inse = tf.reduce_sum(output * target, axis=axis)
    if loss_type == 'jaccard':
        l = tf.reduce_sum(output * output, axis=axis)
        r = tf.reduce_sum(target * target, axis=axis)
    elif loss_type == 'sorensen':
        l = tf.reduce_sum(output, axis=axis)
        r = tf.reduce_sum(target, axis=axis)
    else:
        raise Exception("Unknow loss_type")
    dice = (2. * inse + smooth) / (l + r + smooth)
    dice = tf.reduce_mean(dice)
    return dice

4 多分類

假設是一個10分類的任務,那麼我們應該會有一個這樣的模型預測結果:[batch_size,10,width,height],然後我們的ground truth需要改成one hot的形式,也變成[batch_size,10,width,height]。剩下的和二分類的程式碼基本相同了,先ground truth和預測結果對應元素相乘,然後對相乘的結果求和。就是最後需要對每一個類別和每一個樣本都求一次平均就行了。

5 深入探討Dice,IoU


上圖就是我們常見的IoU方法,假設分子的兩個集合,一個集合是Ground Truth,另外一個集合是神經網路給出的預測值。不要被圖中的正方形的形狀限制了想想,對於分割任務來說,一般是畫素級的不規則圖案

如果預測正確,也就是分子中的藍色交匯的部分,稱之為True Positive,屬於True Positive的畫素的數量就是分子的值。分母的值是Ground Truth的所有畫素的數量和預測結果中所有畫素的數量的和再減去重疊的部分的畫素數量。

直接學過recall,precision,混淆矩陣,f1score的朋友一定對FN,TP,TN,FP這些不陌生:

  • 黃色區域:預測為negative,但是GT中是positive的False Negative區域;
  • 紅色區域:預測為positive,但是GT中是Negative的False positive區域;

對於IoU的預測好壞的直觀理解就是:

簡單的說就是,重疊的越多,IoU越接近1,預測效果越好

現在讓我們更好的從IoU過渡到Dice,我們先把IoU的算式寫出來:

\[IoU = \frac{TP}{TP+FP+FN} \]

Dice的算式,結合我們之前講的內容,可以推匯出,\(|X|\bigcap|Y|\)就是TP,\(|X|\)假設是GT的話就是FN+TP,\(|Y|\)假設是預測的mask,就是TP+FP,所以:

\[Dice_coefficient = \frac{2\times TP}{TP+FN + TP + FP} \]

所以我們可以得到Dice和IoU之間的關係了,這裡的之後的Dice預設表示Dice Coefficient

\[IoU = \frac{Dice}{2-Dice} \]

這個函式影像如下圖,我們只關注0~1這個區間就好了,可以發現:

  • IoU和Dice同時為0,同時為1;這很好理解,就是全預測正確和全部預測錯誤
  • 假設在相同的預測情況下,可以發現Dice給出的評價會比IoU高一些,哈哈哈。所以Dice的資料會更加好看一些。

參考文章:

  1. https://www.aiuai.cn/aifarm1159.html
  2. https://blog.csdn.net/py184473894/article/details/90383618

相關文章