论文学习:U-Net++

U-Net++:

​ 一种基于嵌套和密集跳跃连接的新分割体系结构,这种设计的跳跃连接降低编码和解码子网络的特征图之间的间隙,这个分割体系结构基于这样的假设:当将来自编码器网络的高分辨率特征图与来自解码器网络的对应语义丰富的特征图进行融合之前,逐渐丰富模型时,该模型可以有效的捕捉到前景对象的细粒度细节,当来自解码器和编码器网络的特征映射在语义上相似时,网络将处理更容易的学习任务。

基于U-net改进的一种网络

其改进思路如下:

1.为什么降到X(4,0)才开始上采样

对于不同深度的U-net表现,并不是越深越好,不同层次的特征的重要性对于不同的数据集是不一样的,所以并不是原文中的4层U-net就一定对所有数据集的分割问题表现最优。

使用浅层和深层的特征,利用不同深度的U-net来各自抓取不同层次的特征

这个网络(上图)的好处是不管哪个深度的特征有效,都用上,让网络自己去学习不同深度的特征的重要性,其次它共享了一个特征提取器,也就是不需要训练一堆U-net,只需要训练一个encode,它不同层次的特征可以由不同的解码路径来还原,编码路径还是可以灵活的使用不同的backbone;但是它也存在缺点:这个网络结构是不能被训练的,因为不会由任何梯度出现在红色三角形内,因为它和算loss_func的地方在反向传播是断开的。

解决这个问题的方法:1.加入深度监督2.把结构改为如图

但这个网络把U-net原来的长连接去掉了,U-net的长连接的重要性包含3点:1.解决梯度消失;2.学习低级别的特征;3.恢复在下采样过程中丢失的信息。

所以说这个长连接是必要的,它联系了输入图像的很多信息,有助于还原降采样所带来的信息损失,在一定程度上与残差的操作(residual)很类似,x+f(x)。所以这样就想到长连接和短链接结合的方式。这样就得到U-net++的网络结构

与U-net网络不同的是:

重要思想:在编码器和解码器特征图融合之前弥合它们之间的语义间隙

  • 在跳跃路径上有卷积层(绿色),用来弥合编码和解码特征图的语义间隙
  • 在跳跃路径上有紧密的跳过连接(蓝色),从而改善了梯度流
  • 加入了深度监督(红色),可以进行模型修剪并进行改进或者最坏情况下,可以达到与仅使用一个损耗层相当的性能

重设计的跳跃路径

U-net:编码器的特征图直接在解码器中接收

U-net++:解码器在接收编码器特征图的路径中经历密集的卷积层,这些卷积层数取决于金字塔等级的块。

​ 密集卷积块使编码器特征图的语义级别更接近在解码器中等待的特征图的语义级别,这样优化器更容易优化。

在这里插入图片描述

每个结点的输出:

在这里插入图片描述

H(·)是紧跟激活函数后的卷积操作,U(·)是上采样层,[]是融合层

深度监督

深度监督可以使模型以两种模式运行:

1)精确模式,其中对所有细分分支的输出求平均值;

2)快速模式,其中仅从分割分支之一中选择最终分割图,其选择决定了模型修剪的程度和速度增益。

在训练过程中在各个leve的子网络中加深度监督,可以带的好处:剪枝

由于嵌套的跳跃路径,Unet++可以在多个语义级别上产生全分辨率特征图

作者的思想:

测试时,剪掉的部分对剩余表结构不做影响,训练时,剪掉部分对剩余部分有影响

(也就是说在测试阶段由于输入的图像只有前向传播,剪掉的这部分对前面的速出完全没有影响,而在训练阶段既有前向又有反向传播,被剪掉的部分时会帮助其他部分做权重更新的。)

在深度监督过程中,每个子网络的输出就是图像的分割结果了,如果其中的子网络凤娥结果足够好,可以随意的裁剪剩余的部分。

为什么要在测试时剪枝,而不是直接拿剪完的L1,L2,L3训练

剪掉的那部分对训练时的反向传播时时有贡献的,如果直接拿L1,L2,L3训练,就相当于只训练不同深度的U-NET,最后的结果会很差

如何去决定剪多少

训练模型时将数据分为训练集验证集和测试集,训练集是一定拟合好的,测试集当然是不能碰的,那么根据子网络在验证集的结果来决定剪多少。具体的结果作者也给出了。

在这里插入图片描述

从结果看出网络并需要很深就可以在一些数据集取得很好的效果。

将二进制交叉熵和Dice系数作为上述四个语义级别的每个损失函数:

在这里插入图片描述

其中^yb和yb分别指平均预测概率和平均真实概率,N是batchsize

**主网络代码:
基于pytorch:

class conv_block_nested(nn.Module):
    
    def __init__(self, in_ch, mid_ch, out_ch):
        super(conv_block_nested, self).__init__()
        self.activation = nn.ReLU(inplace=True)
        self.conv1 = nn.Conv2d(in_ch, mid_ch, kernel_size=3, padding=1, bias=True)
        self.bn1 = nn.BatchNorm2d(mid_ch)
        self.conv2 = nn.Conv2d(mid_ch, out_ch, kernel_size=3, padding=1, bias=True)
        self.bn2 = nn.BatchNorm2d(out_ch)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.activation(x)
        
        x = self.conv2(x)
        x = self.bn2(x)
        output = self.activation(x)

        return output
    
#Nested Unet

class NestedUNet(nn.Module):
    """
    Implementation of this paper:
    https://arxiv.org/pdf/1807.10165.pdf
    """
    def __init__(self, in_ch=3, out_ch=1):
        super(NestedUNet, self).__init__()

        n1 = 64
        filters = [n1, n1 * 2, n1 * 4, n1 * 8, n1 * 16]

        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
        self.Up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)

        #backbone中下采样的各个级别层
        self.conv0_0 = conv_block_nested(in_ch, filters[0], filters[0])
        self.conv1_0 = conv_block_nested(filters[0], filters[1], filters[1])
        self.conv2_0 = conv_block_nested(filters[1], filters[2], filters[2])
        self.conv3_0 = conv_block_nested(filters[2], filters[3], filters[3])
        self.conv4_0 = conv_block_nested(filters[3], filters[4], filters[4])

        #x^(0,1),x^(1,1),x^(2,1),x^(3,1)层
        self.conv0_1 = conv_block_nested(filters[0] + filters[1], filters[0], filters[0])
        self.conv1_1 = conv_block_nested(filters[1] + filters[2], filters[1], filters[1])
        self.conv2_1 = conv_block_nested(filters[2] + filters[3], filters[2], filters[2])
        self.conv3_1 = conv_block_nested(filters[3] + filters[4], filters[3], filters[3])

        ##x^(0,2),x^(1,2),x^(2,2)层
        self.conv0_2 = conv_block_nested(filters[0]*2 + filters[1], filters[0], filters[0])
        self.conv1_2 = conv_block_nested(filters[1]*2 + filters[2], filters[1], filters[1])
        self.conv2_2 = conv_block_nested(filters[2]*2 + filters[3], filters[2], filters[2])

        #x^(0,3),x^(1,3)层
        self.conv0_3 = conv_block_nested(filters[0]*3 + filters[1], filters[0], filters[0])
        self.conv1_3 = conv_block_nested(filters[1]*3 + filters[2], filters[1], filters[1])

        #x^(0,4)层
        self.conv0_4 = conv_block_nested(filters[0]*4 + filters[1], filters[0], filters[0])

        self.final = nn.Conv2d(filters[0], out_ch, kernel_size=1)


    def forward(self, x):
        
        x0_0 = self.conv0_0(x)
        x1_0 = self.conv1_0(self.pool(x0_0))
        x0_1 = self.conv0_1(torch.cat([x0_0, self.Up(x1_0)], 1))

        x2_0 = self.conv2_0(self.pool(x1_0))
        x1_1 = self.conv1_1(torch.cat([x1_0, self.Up(x2_0)], 1))
        x0_2 = self.conv0_2(torch.cat([x0_0, x0_1, self.Up(x1_1)], 1))

        x3_0 = self.conv3_0(self.pool(x2_0))
        x2_1 = self.conv2_1(torch.cat([x2_0, self.Up(x3_0)], 1))
        x1_2 = self.conv1_2(torch.cat([x1_0, x1_1, self.Up(x2_1)], 1))
        x0_3 = self.conv0_3(torch.cat([x0_0, x0_1, x0_2, self.Up(x1_2)], 1))

        x4_0 = self.conv4_0(self.pool(x3_0))
        x3_1 = self.conv3_1(torch.cat([x3_0, self.Up(x4_0)], 1))
        x2_2 = self.conv2_2(torch.cat([x2_0, x2_1, self.Up(x3_1)], 1))
        x1_3 = self.conv1_3(torch.cat([x1_0, x1_1, x1_2, self.Up(x2_2)], 1))
        x0_4 = self.conv0_4(torch.cat([x0_0, x0_1, x0_2, x0_3, self.Up(x1_3)], 1))

        output = self.final(x0_4)
        return output















  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值