【问题标题】:Keras: Using Dice coefficient Loss Function, val loss is not improvingKeras:使用 Dice 系数损失函数,val loss 没有改善
【发布时间】:2021-11-08 02:24:28
【问题描述】:

问题

我正在做两类图像分割,我想使用骰子系数的损失函数。但是验证损失没有得到改善。如何解决这些问题?

我做了什么

采用one-hot编码的方法,处理标签图像,并且没有包含背景标签。

代码

X 的形状是 (num of data, 256, 256, 1) # graysacle

y的形状是(num of data, 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])

【问题讨论】:

    标签: python tensorflow keras deep-learning


    【解决方案1】:

    我试图复制您的经验。我使用 Oxford-IIIT Pets 数据库,其标签分为三类:1:前景,2:背景,3:未分类。如果像您一样删除了第 1 类(“前景”),则 val_loss 在迭代期间不会更改。另一方面,如果“未分类”类被删除,优化似乎工作。该模型无法区分“背景”和“未分类”,这是可以想象的。
    此外,骰子系数的计算有一个小误差:分母中需要取平方的和。它不会改变 y_true 的任何东西,但它会改变 y_pred。

    【讨论】:

    • 感谢您的评论。可能我的解释还不够理解。和你说的差不多,但是我做的和实例分割差不多。我的图像中有两个实例,就像您在一张图像中有一只狗和一只猫一样。然后一张图片有三个标签,每个标签代表0-背景,1-狗,2-猫。在 One Hot Encoding 之后,它将具有三个通道(我不确定这样说是否正确),而我删除的是图像中的“0-background”标签。当我这样训练时,val_loss 不会改变。
    • 关于骰子系数的计算,你的意思是我必须取分母中的平方和,所以公式必须是“(2.*交点+0.0001)/((K.总和(y_true_f)+ K.sum(y_pred_f))**2 + 0.0001)”?对吗?
    • 实际上我不确定,在大多数文档中,他们没有放置正方形,但在 Wikipedia 上,提供了非二进制向量的通用公式: 2* |a 。乙| / |a|**2 + |b|**2
    • 好的,谢谢!我会试试的。
    【解决方案2】:

    我不能说为什么你的代码不起作用,但我可以告诉你我是怎么做的。不同之处在于我在骰子系数计算函数中排除了背景并编码了目标。

    然后我定义我的骰子系数如下:

    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
    

    【讨论】:

    • 感谢您的评论!!我将代码更改为您的骰子系数计算函数。但是适合模型时有ValueError。我想我必须修复某个地方才能使其正常工作。然而,你和我的区别在于你在 dice_coef 函数中进行了一次热编码,而我在拟合模型之前就这样做了。所以我想知道的是 one-hot 编码步骤的顺序会影响结果。
    • 你实现了对这段代码的适配吗?我选择 not 对原始数据进行一次热编码,因为我通常在拟合进行编码的模型时使用数据增强。但是由于您不这样做,我认为您可以使用已经编码的数据,只需使用 K.one_hot 删除这两行
    • 是的,我尝试用 K.one_hot 删除这两行并训练模型。然而,验证损失并没有改善。使用 U-net 模型时,我可以将 y(label) 设为没有背景的两个类,因此它具有(数据数、图像高度、图像宽度、类数(2))。在拟合模型之前,我将 y 数据作为这个形状。
    • 好的...你的结果是什么样的?您确定问题出在您的损失函数上,并且您没有处于过拟合情况或类似情况吗?
    • 嗯.. 让我检查一下.. 我不是 100% 确定问题来自损失函数,但是当我使用“分类交叉熵”作为损失时,模型训练得很好。好吧..我必须稍后再检查。当我使用另一个损失函数时它可以工作。谢谢你的帮助!!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-04
    • 1970-01-01
    • 2019-10-05
    • 1970-01-01
    • 1970-01-01
    • 2018-10-10
    • 2021-03-12
    相关资源
    最近更新 更多