Keras:使用 Dice 系数损失函数,验证损失不断提高

4

问题

我正在进行两类图像分割,并希望使用Dice系数的损失函数。但是验证损失没有改善。如何解决这些问题?

操作步骤

使用One-hot编码方法,处理标签图像并不包括背景标签。

代码

X的形状为(数据数量,256,256,1)#灰度

y的形状为(数据数量,256,256,2)#两类别且不包括背景标签

one_hot_y = np.zeros((len(y), image_height, image_width, 2))
for i in range(len(y)):
  one_hot = to_categorical(y[i])
  one_hot_y[i] = one_hot[:,:,1:] 
one_hot_y.shape  #->  (566, 256, 256, 2)

#### <-- Unet Model --> ####

from tensorflow import keras
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, Concatenate, Conv2DTranspose
from keras import Model

def unet(image_height, image_width, num_classes):
    # inputs = Input(input_size)
    inputs = Input(shape=(image_height, image_width, 1),name='U-net')
    
    conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(inputs)
    conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(conv1)
    pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)

    conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(pool1)
    conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv2)
    pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)

    conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(pool2)
    conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv3)
    pool3 = MaxPooling2D(pool_size=(2, 2))(conv3)

    conv4 = Conv2D(256, (3, 3), activation='relu', padding='same')(pool3)
    conv4 = Conv2D(256, (3, 3), activation='relu', padding='same')(conv4)
    pool4 = MaxPooling2D(pool_size=(2, 2))(conv4)

    conv5 = Conv2D(512, (3, 3), activation='relu', padding='same')(pool4)
    conv5 = Conv2D(512, (3, 3), activation='relu', padding='same')(conv5)

    up6 = Concatenate()([Conv2DTranspose(256, (2, 2), strides=(2, 2), padding='same')(conv5), conv4])
    conv6 = Conv2D(256, (3, 3), activation='relu', padding='same')(up6)
    conv6 = Conv2D(256, (3, 3), activation='relu', padding='same')(conv6)

    up7 = Concatenate()([Conv2DTranspose(128, (2, 2), strides=(2, 2), padding='same')(conv6), conv3])
    conv7 = Conv2D(128, (3, 3), activation='relu', padding='same')(up7)
    conv7 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv7)

    up8 = Concatenate()([Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same')(conv7), conv2])
    conv8 = Conv2D(64, (3, 3), activation='relu', padding='same')(up8)
    conv8 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv8)

    up9 = Concatenate()([Conv2DTranspose(32, (2, 2), strides=(2, 2), padding='same')(conv8), conv1])
    conv9 = Conv2D(32, (3, 3), activation='relu', padding='same')(up9)
    conv9 = Conv2D(32, (3, 3), activation='relu', padding='same')(conv9)

    outputs = Conv2D(num_classes, (1, 1), activation='softmax')(conv9)
    
    return Model(inputs=[inputs], outputs=[outputs])```


#### <-- Dice Score --> ####

from tensorflow.keras import backend as K
def dice_coef(y_true, y_pred):
  y_true_f = K.flatten(y_true)
  y_pred_f = K.flatten(y_pred)
  intersection = K.sum(y_true_f * y_pred_f)
  return (2. * intersection + 0.0001) / (K.sum(y_true_f) + K.sum(y_pred_f) + 0.0001)

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


#### <-- Fit the Model --> ####

from tensorflow.keras import optimizers
adam = optimizers.Adam(learning_rate=0.0001)
unet_model.compile(optimizer=adam, loss=[dice_coef_loss],metrics=[dice_coef])
hist = unet_model.fit(X_train,y_train, epochs=epochs, batch_size=batch_size,validation_data=(X_val,y_val), callbacks=[checkpoint,earlystopping])
3个回答

0

我无法说出为什么你的代码不起作用,但我可以告诉你我的做法。区别在于我排除了背景,并将目标编码在Dice系数计算函数内。

然后我定义我的Dice系数如下:

def dice_coef(y_true, y_pred, smooth=1):
    # flatten
    y_true_f = K.flatten(y_true)
    y_pred_f = K.flatten(y_pred)
    # one-hot encoding y with 3 labels : 0=background, 1=label1, 2=label2
    y_true_f = K.one_hot(K.cast(y_true_f, np.uint8), 3)
    y_pred_f = K.one_hot(K.cast(y_pred_f, np.uint8), 3)
    # calculate intersection and union exluding background using y[:,1:]
    intersection = K.sum(y_true_f[:,1:]* y_pred_f[:,1:], axis=[-1])
    union = K.sum(y_true_f[:,1:], axis=[-1]) + K.sum(y_pred_f[:,1:], axis=[-1])
    # apply dice formula
    dice = K.mean((2. * intersection + smooth)/(union + smooth), axis=0)
    return dice

def dice_loss(y_true, y_pred):
    return 1-dice_coef

谢谢您的评论!我已经将我的代码更改为您的dice coef计算函数。但是在拟合模型时出现了“ValueError”。我认为我必须修复某个地方才能使其正常工作。 然而,您和我之间的区别在于,您在dice_coef函数中进行了一次独热编码,而我在拟合模型之前进行了编码。因此,我想知道独热编码步骤的顺序是否会影响结果。 - Code_B
你成功地适应了这段代码吗?我选择对原始数据进行独热编码,因为我通常在拟合执行编码的模型时使用数据增强。但是由于你没有这样做,我认为你可以使用已经编码的数据,并删除两行K.one_hot。 - Lue Mar
是的,我尝试删除了两行K.one_hot并训练了模型。然而验证损失没有改善。 在使用U-net模型时,我可以将y(标签)设置为没有背景的两个类别吗?这样它就有(数据数量,图像高度,图像宽度,类别数(2))。在拟合模型之前,我将y数据设置为此形状。 - Code_B
好的...你的结果是什么样子的?你确定问题来自于你的损失函数,而不是过度拟合之类的吗? - Lue Mar
嗯...让我检查一下..我不确定问题是否来自损失函数,但是当我使用“分类交叉熵”作为损失函数时,模型的训练效果非常好。好的...我以后再去检查它。当我使用另一个损失函数时它起作用了。谢谢你的帮助! - Code_B

0

我尝试复制您的经验。我使用了Oxford-IIIT宠物数据库,其标签有三个类别:1:前景,2:背景,3:未分类。如果像您一样删除类别1(“前景”),则val_loss在迭代过程中不会改变。另一方面,如果删除“未分类”类别,则优化似乎可以工作。模型无法区分“背景”和“未分类”,这是可以想象的。
此外,在计算Dice系数时存在小错误:在分母中,您需要取平方的总和。对于y_true来说没有任何变化,但对于y_pred来说确实有所改变。


谢谢你的评论。 也许我的解释不足以理解。它几乎与您所说的相同,但我所做的类似于实例分割。我的图像中有两个实例,就像一张图像中有一只狗和一只猫。然后一个图像有三个标签,每个标签代表0-背景,1-狗,2-猫。经过One Hot编码后,它将有三个通道(我不确定这样说是否正确),我删除的是图像中的“0-背景”标签。当我像这样进行训练时,val_loss没有变化。 - Code_B
关于Dice系数的计算,您的意思是我必须将分母中的平方和相加,因此公式应该是“(2. * intersection + 0.0001) / ((K.sum(y_true_f) + K.sum(y_pred_f))**2 + 0.0001)”吗?这样正确吗? - Code_B
其实我不太确定,在大多数文档中它们并没有使用方括号,但是在维基百科上提供了一般的非二进制向量公式:2* |a . b| / |a|**2 + |b|**2。 - elbe
好的,谢谢!我会尝试的。 - Code_B

-1

在理解了下面的代码之前,我也对这个问题感到困惑!!!!

import numpy as np
from PIL import Image
from keras import backend as K


def dice_loss(y_true, y_pred):
    y_true_f = K.flatten(y_true)
    y_pred_f = K.flatten(y_pred)
    intersection = K.sum(y_true_f* y_pred_f)
    val = (2. * intersection + K.epsilon()) / (K.sum(y_true_f * y_true_f) + K.sum(y_pred_f * y_pred_f) + K.epsilon())
    return 1. - val


arr1 = np.array([[[9.6,0.6,0.3],
                  [0.3,0.5,0.5]],
                 [[0.5,0.5,0.5],
                  [0.5,0.5,0.5]],
                 [[0.5,0.5,0.5],
                 [0.5,0.5,0.5]],
                 [[0.5,0.5,0.5],
                 [0.5,0.5,0.5]]])

arr2= np.array([[[9.6,0.6,0.3],
                  [0.3,0.5,0.5]],
                 [[0.5,0.5,0.5],
                  [0.5,0.5,0.5]],
                 [[0.5,0.5,0.5],
                 [0.5,0.5,0.5]],
                 [[0.5,0.5,0.5],
                 [0.5,0.5,0.5]]])

loss = dice_loss(arr1,arr2)
print(loss)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接