如何用Keras搭建一个多class的卷积神经网络

How to build a multi-class convolutional neural network with Keras

我正在尝试使用带有 Tensorflow 后端的 Keras 实现 U-Net 以执行图像分割任务。我有大小为 (128,96) 的图像作为网络的输入以及大小为 (12288,6) 的蒙版图像,因为它们被展平了。我有 6 个不同的 classes (0-5),它给出了蒙版图像形状的第二部分。它们已使用 to_categorical() 函数编码为单热标签。目前我只使用一张输入图像,也使用同一张作为验证和测试数据。

我想让U-Net进行图像分割,其中class0对应背景。当我现在只训练我的 U-Net 几个时期 (1-10) 时,生成的预测蒙版图像似乎只是给每个像素随机 classes。当我训练网络更长的时间(50+ 个时期)时,所有像素都 class 化为背景。由于我使用相同的图像进行训练和测试,我发现这很奇怪,因为我正在加速网络过度训练。我该如何解决这个问题?我向网络提供蒙版图像和真实图像的方式会不会有问题?

我尝试过手动给网络赋予权重,以减少对背景的重视 classes 并尝试了不同的损失组合、不同的掩模图像整形方式和许多其他方法,但没有取得了不错的成绩。

下面是我的网络代码。它基于取自 this repository 的 U-Net。我设法针对两个 class 案例对其进行了训练,并取得了不错的效果,但我现在不知道如何将其扩展到更多 classes。

def get_unet(self):

    inputs = Input((128, 96,1))
    #Input shape=(?,128,96,1)

    conv1 = Conv2D(64, (3,3), activation = 'relu', padding = 'same',
      kernel_initializer = 'he_normal', input_shape=(None,128,96,6))(inputs)
    #Conv1 shape=(?,128,96,64)
    conv1 = Conv2D(64, (3,3), activation = 'relu', padding = 'same',
          kernel_initializer = 'he_normal')(conv1)
    #Conv1 shape=(?,128,96,64)
    pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)
    #pool1 shape=(?,64,48,64)


    conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same',
         kernel_initializer = 'he_normal')(pool1)
    #Conv2 shape=(?,64,48,128)
    conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same',
         kernel_initializer = 'he_normal')(conv2)
    #Conv2 shape=(?,64,48,128)
    pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)
    #Pool2 shape=(?,32,24,128)

    conv5 = Conv2D(256, (3,3), activation = 'relu', padding = 'same',
         kernel_initializer = 'he_normal')(pool2)
    conv5 = Conv2D(256, (3,3), activation = 'relu', padding = 'same',
         kernel_initializer = 'he_normal')(conv5)

    up8 = Conv2D(128, 2, activation = 'relu', padding = 'same',
        kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv5))
    merge8 = concatenate([conv2,up8], axis = 3)
    conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same',
         kernel_initializer = 'he_normal')(merge8)
    conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same',
         kernel_initializer = 'he_normal')(conv8)


    up9 = Conv2D(64, (2,2), activation = 'relu', padding = 'same',
        kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv8))
    merge9 = concatenate([conv1,up9], axis = 3)
    conv9 = Conv2D(64, (3,3), activation = 'relu', padding = 'same',
        kernel_initializer = 'he_normal')(merge9)
    conv9 = Conv2D(64, (3,3), activation = 'relu', padding = 'same',
        kernel_initializer = 'he_normal')(conv9)
    conv9 = Conv2D(6, (3,3), activation = 'relu', padding = 'same',
        kernel_initializer = 'he_normal')(conv9)

    conv10 = Conv2D(6, (1,1), activation = 'sigmoid')(conv9)
    conv10 = Reshape((128*96,6))(conv10)

    model = Model(input = inputs, output = conv10)
    model.compile(optimizer = Adam(lr = 1e-5), loss = 'binary_crossentropy',
          metrics = ['accuracy'])

    return model

谁能指出我的模型有什么问题?

我没有看到你的预测层,据我所知,它必须是密集层而不是卷积层。 也许这是你的问题。

根据我的经验,还可以使用 U-net 进行分割。它倾向于这样做:

  • 转到全黑或全白
  • 在损失似乎被冻结了很多时间之后,它找到了出路。

我也是用"train just one image"的方法发现收敛了,然后把其他图片加起来就ok了。

但我不得不尝试很多次,唯一一次它运行得相当快的时候是我使用的时候:

  • 最终激活='sigmoid'
  • 损失 = 'binary_crossentropy'

但我没有在任何地方使用 "relu"...也许这会稍微影响收敛速度...?想想 "relu",它只有 0 个或正的结果,这个函数中有一个很大的区域没有梯度。也许有很多 "relu" 个激活会创建很多 "flat" 个没有渐变的区域? (必须仔细考虑才能确认)

使用不同的权重初始化尝试几次(并有耐心等待许多轮)。

有可能你的学习率也太大了。


关于to_categorical():你试过plot/print你的口罩吗?他们真的像您期望的那样吗?

谢谢@Daniel,你的建议最终帮助我让 Unet 正常工作。当 运行 500+ 个时期时,我设法获得了不只是将整个图像分类为背景的结果。此外,我没有使用 kernel_initializer='he_normal',而是使用 kernel_initializer='zeros'kernel_initializer=TruncatedNormal(mean=0.0, stddev=0.07)。我使用了 'sigmoid' 激活函数和 loss='binary_crossentropy'。我为所有隐藏的卷积层保留了 'relu' 激活。我注意到我的网络有时会卡在局部最小值,损失不再改善,所以我需要重新启动。