深度学习500问——Chapter09:图像分割(2)

文章目录


9.4 U-Net

卷积网络被大规模应用在分类任务中,输出的结果是整个图像的类标签。然而,在许多视觉任务,尤其是生物医学图像处理领域,目标输出应该包括目标类别的位置,并且每个像素都应该有类标签。另外,在生物医学图像往往缺少训练图片。所以,Ciresan等人训练了一个卷积神经网络,用滑动窗口提供像素的周围区域(patch)作为输入来预测每个像素的类标签。

这个网络有两个优点:

第一,输出结果可以定位出目标类别的位置;

第二,由于输入的训练数据是patches,这样就相当于进行了数据增广,解决了生物医学图像数量少的问题。

但是,这个方法也有两个很明显的缺点:

第一,它很慢,因为这个网络必须训练每个patch,并且因为patch间的重叠有很多的冗余(冗余会造成什么影响呢?卷积核里面的W,就是提取特征的权重,两个块如果重叠的部分太多,这个权重会被同一些特征训练两次,造成资源的浪费,减慢训练时间和效率,虽然说会有一些冗余,训练集大了,准确率不就高了吗?可是你这个是相同的图片啊,重叠的东西都是相同的,举个例子,我用一张相同的图片训练20次,按照这个意思也是增大了训练集啊,可是会出现什么结果呢,很显然,会导致过拟合,也就是对你这个图片识别很准,别的 图片就不一定了)。

第二,定位准确性和获取上下文信息不可兼得。大的patches需要更多的max-pooling层,这样减小了定位准确性(为什么?因为你是对以这个像素为中心的点进行分类,如果patch太大,最后经过全连接层的前一层大小肯定是不变的,如果你patch大就需要更多的pooling达到这个大小,而pooling层越多,丢失信息的信息也越多;小的patches只能看到很小的局部信息,包含的背景信息不够)。

这篇论文建立了一个更好全卷积的方法。我们定义和扩展了这个方法它使用更少的训练图片单产生更精确的分割。

(1)使用全卷积神经网络。(全卷积神经网络就是卷积取代了全连接层,全连接层必须固定图像大小而卷积不用,所以这个策略使得你可以输入任意尺寸的图片,而且输出也是图片,所以这是一个端到端的网络)。

(2)左边的网络是收缩路径:使用卷积核max pooling。

(3)右边的网络是扩张路径:使用上采样产生的特征图与左侧收缩路径对应层产生的特征图进行concatenate操作。(pooling层会丢失图像信息和降低图像分辨率且是不可逆的操作,对图像分割任务有一些影响,对图像分类任务的影响不大,为什么要做上采样?因为上采样可以补足一些图片的信息,但是信息补充的肯定不完全,所以还需要与左边的分辨率比较高的图片相连接起来(直接复制过来再裁剪到与上采样图片一样大小),这就相当于在高分辨和更抽象特征当中做一个折中,中,因为随着卷积次数增多,提取的特征也更加有效,更加抽象,上采样的图片是经历多次卷积后的图片,肯定是比较高效和抽象的图片,然后它与左边不怎么抽象但更高分辨率的特征图片进行连接)。

(4)最后再经过两次反卷积操作,生成特征图,再用两个 1x1 的卷积做分类得到最后的两张 heatmap,例如第一张表示的是第一类的得分,第二张表示的是第二类的得分 heatmap,然后作为softmax函数的输入,算出概率比较大的softmax类,选择它作为输入给交叉熵进行反向传播训练。

下面是U-Net模型的代码实现:

def get_unet():
    inputs = Input((img_rows, img_cols, 1))
    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)
    # pool1 = Dropout(0.25)(pool1)
    # pool1 = BatchNormalization()(pool1)

    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)
    # pool2 = Dropout(0.5)(pool2)
    # pool2 = BatchNormalization()(pool2)

    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)
    # pool3 = Dropout(0.5)(pool3)
    # pool3 = BatchNormalization()(pool3)

    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)
    # pool4 = Dropout(0.5)(pool4)
    # pool4 = BatchNormalization()(pool4)

    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], axis=3)
    # up6 = Dropout(0.5)(up6)
    # up6 = BatchNormalization()(up6)
    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], axis=3)
    # up7 = Dropout(0.5)(up7)
    # up7 = BatchNormalization()(up7)
    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], axis=3)
    # up8 = Dropout(0.5)(up8)
    # up8 = BatchNormalization()(up8)
    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], axis=3)
    # up9 = Dropout(0.5)(up9)
    # up9 = BatchNormalization()(up9)
    conv9 = Conv2D(32, (3, 3), activation='relu', padding='same')(up9)
    conv9 = Conv2D(32, (3, 3), activation='relu', padding='same')(conv9)

    # conv9 = Dropout(0.5)(conv9)

    conv10 = Conv2D(1, (1, 1), activation='sigmoid')(conv9)

    model = Model(inputs=[inputs], outputs=[conv10])

    model.compile(optimizer=Adam(lr=1e-5),
                  loss=dice_coef_loss, metrics=[dice_coef])

    return model

9.5 SegNet

可训练的图像分割引擎,包含一个encoder网络,一个对应的decoder网络,衔接像素级分类层,解码网络与VGG16的13层卷积层相同。解码网络是将低分辨率的编码特征图映射到全分辨率的特征图。解码网络使用最大池化层的池化索引进行非线性上采样,上采样过程就不需要学习。上采样得到的稀疏图与可训练的滤波器卷积得到紧密的特征图。

        使用池化层索引进行上采样的优势:

        1)提升边缘刻画度。

        2)减少训练的参数。

        3)这种上采样模式可以包含到任何编码-解码网络中。

SegNet网络的结构如下图所示:

SegNet网络结构如图1所示,Input为输入图片,Output为输出分割的图像,不同颜色代表不同的分类。语义分割的重要性就在于不仅告诉你图片中某个东西是什么,而且告知你它在图片中的位置。我们可以看到的是一个对称网络,由中间绿色pooling层与红色upsampling 层作为分割,左边是卷积提取高维特征,并通过pooling使图片变小,SegNet作者称为Encoder,右边是反卷积(在这里反卷积与卷积没有区别)与upsampling,通过反卷积使得图像分类后特征得以重现,upsampling使图像变大,SegNet作者称为Decoder,最后通过Softmax,输出不同分类的最大值。这就是大致的SegNet过程,下面对这个过程里面使用到的方法进行介绍。

 编码网络与滤波器族卷积得到特征图,进行BN,ReLU,最大池化。最大池化是为了获得空间小位移的平移不变。最大池化和下采样损失了边缘细节,因此,在编码过程中保存边缘信息很重要。考虑到内存原因,只保存最大池化索引,如最大特征值的位置。
SegNet解码技术如下图所示:

 解码网络使用保存的最大池化索引上采样,得到稀疏的特征图,将特征图与可训练的解码滤波器族卷积得到致密的特征图。之后进行BN。高维的特征图输入soft-max层,对每个像素进行分类,得到每个像素属于K类的概率。 图3中右边是FCN的解码技术,FCN对编码的特征图进行降维,降维后输入到解码网络,解码网络中,上采样使用反卷积实现,上采样的特征图与降维的编码图进行element-wise add得到最终的解码特征图。FCN解码模型需要存储编码特征图,在嵌入式设备中内存紧张。
   SegNet的Encoder过程中,卷积的作用是提取特征,SegNet使用的卷积为same卷积(详见卷积神经网络CNN(1)),即卷积后不改变图片大小;在Decoder过程中,同样使用same卷积,不过卷积的作用是为upsampling变大的图像丰富信息,使得在Pooling过程丢失的信息可以通过学习在Decoder得到。SegNet中的卷积与传统CNN的卷积并没有区别。

9.6 空洞卷积(Dilated Convolutions)

在图像分割领域,图像输入到CNN(典型的网络比如FCN[3])中,FCN先像传统的CNN那样对图像做卷积再pooling,降低图像尺寸的同时增大感受野,但是由于图像分割预测是pixel-wise的输出,所以要将pooling后较小的图像尺寸 upsampling 到原始的图像尺寸进行预测(upsampling一般采用deconv反卷积操作,deconv可参见知乎答案如何理解深度学习中的deconvolution network),之前的pooling操作使得每个pixel预测都能看到较大的感受野信息。因此图像分割FCN中有两个关键,一个是pooling减小图像尺寸增大感受野,另一个是upsampling扩大图像尺寸。在减小再增大尺寸的过程中,肯定有一些信息损失掉了,那么能不能设计一种新的操作,不通过pooling也能有较大的感受野看到更多的信息呢?答案就是 dilated conv。

以前的CNN主要问题总结:

        (1)u-sampling / pooling layer

        (2)内部数据结构丢失;空间层级化信息丢失。

        (3)小物体信息无法重建(假设有四个pooling layer,则任何小于 2^4 = 16 pixel的物体信息将理论上无法重建)。

举例如下:

Dilated Convolution with a 3x3 kernel and dilated rate2

下面看一下dilated conv原始论文[4]中的示意图:

(a)图对应3x3的1-dilated conv,和普通的卷积操作一样;(b)图对应3x3的 2-dilated conv,实际的卷积kernel size还是3x3,但是空洞为1,也就是对于一个 7x7 的图像patch,只有9个红色的点和 3x3的kernel发生卷积操作,其余的点略过。也可以理解为kernel的size为 7x7,但是只有图中的9个点的权重不为0,其余都为0。可以看到虽然kernel size只有 3x3,但是这个卷积的感受野已经增大到了7x7(如果考虑到这个 2-dilated conv的前一层是一个 1-dilated conv的话,那么每个红点就是1-dilated的卷积输出,所以感受野为 3x3,所以 1-dilated 和 2-dilated 合起来就能达到 7x7 的conv);(c)图是4-dilated conv操作,同理跟在两个1-dilated和2-dilated conv的后面,能达到15x15的感受野。对比传统的conv操作,3层3x3的卷积加起来,stride为1的话,只能达到(kernel-1) * layer+1=7的感受野,也就是和层数layer成线性关系,而dilated conv的感受野是指数级的增长。

  dilated的好处是不做pooling损失信息的情况下,加大了感受野,让每个卷积输出都包含较大范围的信息。在图像需要全局信息或者语音文本需要较长的sequence信息依赖的问题中,都能很好的应用dilated conv,比如图像分割、语音合成WaveNet、机器翻译ByteNet中。

9.7 RefineNet

网络结构:

RefineNet block 的作用就是把不同的resolution level的feature map进行融合。网络结构如下:

最左边一栏就是FCN的encoder部分(文中是用ResNet),先把pretrained ResNet按 feature map的分辨率分成四个ResNet blocks,然后向右把四个blocks分别作为 4个patch通过RefineNet block进行融合refine,最后得到一个refined feature map(接softmax再双线性插值输出。)注意除了 RefineNet-4,所有的RefineNet block都是二输入的,用于融合不同level做refine,而单输入的RefineNet-4可以看作是先对ResNet的一个 task adaptation。

RefineNet Block

接下来仔细看一下RefineNet block,可以看到主要组成部分是Residual convolution unit,Multi-resolution fusion,Chained residual pooling,Output convolutions,切记这个block作用是融合多个 level 的feature map,但具体的实现应该是和输入个数、shape 无关的。

Residual convolution unit就是普通的去除了BN的residual unit。

Multi-resolution fusion是先对多输入的feature map都用一个卷积层进行adaptation(都化到最小的feature map的shape),再上采样再做element-wise的相加。注意如果是像RefinNet-4那样的单输入block这一部分就直接pass了;

Chained residual pooling 中的ReLU对接下来池化的有效性很重要,还可以使模型对学习率的变化没这么敏感。这个链式结构能从很大范围区域上获取背景context。另外,这个结构中大量使用了identity mapping这样的连接,无论长距离或者短距离,这样的结构允许梯度从一个block直接向其他任一block传播。

Output convolutions 就是输出前再加一个RCU。

  • 31
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JOYCE_Leo16

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值