【论文阅读】U-Net: Convolutional Networks for BiomedicalImage Segmentation

U-Net:生物医学图像分割的卷积网络

paper:https://arxiv.org/abs/1505.04597

目录

1. 简介

2. 结构

3. 代码

4. 相关知识


1. 简介

UNet 属于 FCN 的一种变体,它的U型结构解决了FCN无法上下文的信息和位置信息的弊端。可以说是最常用、最简单的一种分割模型,它简单、高效、易懂、容易构建,且可以从小数据集中训练。UNet 的初衷是为了解决医学图像分割的问题。被大量应用在语义分割领域。

UNet主要创新点

  • 将低级特征图与后面的高级特征图进行融合操作
  • 完全对称的U型结构使得前后特征融合更为彻底,使得高分辨率信息与低分辨率信息在目标图片中增加
  • 结合了下采样时的低分辨率信息(提供物体类别识别依据)和上采样时的高分辨率信息(提供精准分割定位依据),此外还通过融合操作(跳跃结构)填补底层信息以提高分割精度.(分辨率就是图片的尺寸)

2. 结构

左边为特征提取网络(编码器),右边为特征融合网络(解码器)

高分辨率—encoder—低分辨率—decoder—高分辨率

利用前面编码的抽象特征来恢复到原图尺寸的过程, 最终得到分割结果(掩码图片)

由一层反卷积+特征拼接concat+两个3x3的卷积层(ReLU)反复构成,一共经过4次这样的操作,与特征提取网络刚好相对应,最后接一层1*1卷积,降维处理,即将通道数降低至特定的数量,得到目标图,具体内容可以参考这篇文章 一文读懂卷积神经网络中的1x1卷积核
FCN与UNet特征融合操作对比解析

FCN是通过特征图对应像素值的相加来融合特征的

  • Encoder:左半部分,特征提取网络,由两个 3x3 的卷积层(ReLU)+ 一个 2x2 的 maxpooling 层组成一个下采样模块,重复四次。作用是特征提取(获取局部特征,并做图片级分类),得到抽象语义特征。
  • Decoder:右半部分,特征融合网络,由一个上采样的卷积层 + 特征拼接 concat + 两个 3x3 的卷积层(ReLU)构成一个上采样模块,重复四次。一共经过4次这样的操作,与特征提取网络相对应。最后接一层1*1卷积,降维处理,得到目标图。用上采样产生的特征图与左侧特征图进行concatenate。作用是利用前面编码的抽象特征来恢复到原图尺寸的过程, 最终得到分割结果(掩码图片)
  • 最后再经过两次卷积操作,生成特征图,再用两个卷积核大小为1*1的卷积做分类得到最后的两张heatmap,例如第一张表示第一类的得分,第二张表示第二类的得分heatmap,然后作为softmax函数的输入,算出概率比较大的softmax,然后再进行loss,反向传播计算。

Encoder 由卷积操作和下采样操作组成,所用卷积结构统一为 3x3 的卷积核,padding=0 ,striding=1。没有 padding 所以每次卷积之后特征图的 H 和 W 变小了,在跳连(Skip connection)时需注意特征图的维度。

Decoder 用以恢复特征图的原始分辨率,除了卷积以外,关键步骤就是上采样与跳层连接。上采样常用转置卷积和插值两种方式实现。在插值实现方式中,双线性插值(bilinear)的综合表现较好也较为常见 。UNet 中的跳层连接通过拼接将底层的位置信息与深层的语义信息相融合。

  • FCN:通过特征图对应像素值的相加来融合特征。相加方式:特征图维度没有变化,但每个维度包含了更多特征。对于普通分类任务这种不需要从特征图复原到原始分辨率的任务来说,这是一个高效的选择;
  • UNet :通过通道数的拼接,以形成更厚的特征(会更佳消耗显存)。拼接方式:保留了更多的维度/位置信息,这使得后面的网络层可在浅层特征与深层特征间自由选择,这对语义分割任务来说更具优势。

(pooling层会丢失图像信息和降低图像分辨率且是永久性的,对于图像分割任务有一些影响,对图像分类任务的影响不大,为什么要做上采样呢?上采样可以让包含高级抽象特征低分辨率图片在保留高级抽象特征的同时变为高分辨率,然后再与左边低级表层特征高分辨率图片进行concatenate操作)



3. 代码

unet是通过同维度矩阵拼接来融合特征的:

torch代码:

concat2 = torch.cat([convt1,conv4],dim=1)
# dim = 1 意味着在第1维度方向(第1维也就是列为4的方向)进行叠加
# 对于更高维的数据,也就是在dim = x 时,即x所对应维度方向进行叠加

 模型torch代码解析

import torch.nn as nn
import torch.nn.functional as F
import torch.utils.data
import torch



"""
    构造下采样模块--右边特征融合基础模块    
"""


class conv_block(nn.Module):
    """
    Convolution Block
    """

    def __init__(self, in_ch, out_ch):
        super(conv_block, self).__init__()

        self.conv = nn.Sequential(
            nn.Conv2d(in_ch, out_ch, kernel_size=3, stride=1, padding=1, bias=True),
            # 在卷积神经网络的卷积层之后总会添加BatchNorm2d进行数据的归一化处理,这使得数据在进行Relu之前不会因为数据过大而导致网络性能的不稳定
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_ch, out_ch, kernel_size=3, stride=1, padding=1, bias=True),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True))

    def forward(self, x):
        x = self.conv(x)
        return x


"""
    构造上采样模块--左边特征提取基础模块    
"""
class up_conv(nn.Module):
    """
    Up Convolution Block
    """

    def __init__(self, in_ch, out_ch):
        super(up_conv, self).__init__()
        self.up = nn.Sequential(
            nn.Upsample(scale_factor=2),
            nn.Conv2d(in_ch, out_ch, kernel_size=3, stride=1, padding=1, bias=True),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        x = self.up(x)
        return x

"""
    模型主架构
"""

class U_Net(nn.Module):
    """
    UNet - Basic Implementation
    Paper : https://arxiv.org/abs/1505.04597
    """

    # 输入是3个通道的RGB图,输出是0或1——因为我的任务是2分类任务
    def __init__(self, in_ch=3, out_ch=2):
        super(U_Net, self).__init__()

        # 卷积参数设置
        n1 = 64
        filters = [n1, n1 * 2, n1 * 4, n1 * 8, n1 * 16]

        # 最大池化层
        self.Maxpool1 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.Maxpool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.Maxpool3 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.Maxpool4 = nn.MaxPool2d(kernel_size=2, stride=2)

        # 左边特征提取卷积层
        self.Conv1 = conv_block(in_ch, filters[0])
        self.Conv2 = conv_block(filters[0], filters[1])
        self.Conv3 = conv_block(filters[1], filters[2])
        self.Conv4 = conv_block(filters[2], filters[3])
        self.Conv5 = conv_block(filters[3], filters[4])

        # 右边特征融合反卷积层
        self.Up5 = up_conv(filters[4], filters[3])
        self.Up_conv5 = conv_block(filters[4], filters[3])

        self.Up4 = up_conv(filters[3], filters[2])
        self.Up_conv4 = conv_block(filters[3], filters[2])

        self.Up3 = up_conv(filters[2], filters[1])
        self.Up_conv3 = conv_block(filters[2], filters[1])

        self.Up2 = up_conv(filters[1], filters[0])
        self.Up_conv2 = conv_block(filters[1], filters[0])

        self.Conv = nn.Conv2d(filters[0], out_ch, kernel_size=1, stride=1, padding=0)

	# 前向计算,输出一张与原图相同尺寸的图片矩阵
    def forward(self, x):
        e1 = self.Conv1(x)

        e2 = self.Maxpool1(e1)
        e2 = self.Conv2(e2)

        e3 = self.Maxpool2(e2)
        e3 = self.Conv3(e3)

        e4 = self.Maxpool3(e3)
        e4 = self.Conv4(e4)

        e5 = self.Maxpool4(e4)
        e5 = self.Conv5(e5)

        d5 = self.Up5(e5)
        d5 = torch.cat((e4, d5), dim=1)  # 将e4特征图与d5特征图横向拼接

        d5 = self.Up_conv5(d5)

        d4 = self.Up4(d5)
        d4 = torch.cat((e3, d4), dim=1)  # 将e3特征图与d4特征图横向拼接
        d4 = self.Up_conv4(d4)

        d3 = self.Up3(d4)
        d3 = torch.cat((e2, d3), dim=1)  # 将e2特征图与d3特征图横向拼接
        d3 = self.Up_conv3(d3)

        d2 = self.Up2(d3)
        d2 = torch.cat((e1, d2), dim=1)  # 将e1特征图与d1特征图横向拼接
        d2 = self.Up_conv2(d2)

        out = self.Conv(d2)


        return out

4. 相关知识

编解码结构

编码和解码(encoder-decoder)结构,在2006年就被Hinton提出来发表在了nature上。当时提出的主要作用并不是分割,而是压缩图像和去噪声。输入是一幅图,经过下采样的编码,得到一串比原先图像更小的特征,相当于压缩,然后再经过一个解码,理想状况就是能还原到原来的图像。这样的话我们存一幅图的时候就只需要存一个特征和一个解码器即可。同理,这个思路也可以用在原图像去噪,做法就是在训练的阶段在原图人为地加上噪声,然后放到这个编码解码器中,目标是可以还原得到原图。

在UNet与FCN的目标任务中,是得到一张Mask掩码图,实现端到端(由图得到图)。和FCN相比,U-Net的第一个特点是完全对称,也就是左边和右边是很类似的,而FCN的解码器部分相对简单,只用了一个反卷积的操作,之后并没有跟上卷积结构。

全卷积结构

UNet和FCN一样, 是全卷积形式, 没有全连接层(即没有固定图的尺寸)——全连接层输入是提前固定好的,所以容易适应很多输入尺寸大小

跳跃结构,即特征融合操作

UNet相比FCN,跳跃结构更多,更彻底,每一层下采样都与后面每一次上采样对应,一个经验的解释(大量实验)就是跳级连接能够保证特征更加精细。UNet是拼接操作,而FCN是加操作。

对高层语义特征与底层空间信息的理解

越底层的特征蕴含的空间信息(分割定位特征)更多,语义特征(就是类别判断特征,像素点可以分到哪一个类别中去)更少,越高级的特征蕴含的空间信息更少,语义特征更多

  • 底层特征图片更偏向于组成图像的基本单元,如点,线,边缘轮廓

高层抽象的特征就更抽象,更近似于表示的是图像的语义信息


1x1卷积核

卷积核(convolutional kernel):可以看作对某个局部的加权求和。对应局部感知,原理是在观察某个物体时我们既不能观察每个像素也不能一次观察整体,而是先从局部开始认识,这就对应了卷积。卷积核的大小一般有1x1,3x3和5x5的尺寸(一般是奇数x奇数)。

卷积核的个数就对应输出的通道数(channels)。对于输入的每个通道,输出每个通道上的卷积核是不一样的。比如输入是28x28x192,用3x3的卷积核,卷积通道数为128,那么卷积的参数有3x3x192x128。前两维对应的每个卷积里面的参数,后两维对应卷积个数(卷积核的权值共享只在每个单独通道上有效,至于通道与通道间的对应的卷积核是独立不共享的,所以是192x128)。

池化(pooling):卷积特征往往对应某个局部的特征。要得到global的特征需要将全局的特征执行一个aggregation(聚合)。池化就是这样一个操作,对于每个卷积通道,将更大尺寸上的卷积特征进行pooling就可以得到更有全局性的特征。pooling的另外一个作用就是升维或者降维,1x1的卷积也有相似的作用。

由于 1×1 并不会改变 height 和 width,改变通道的第一个最直观的结果,就是可以将原本的数据量进行增加或者减少。这里看其他文章或者博客中都称之为升维、降维。但我觉得维度并没有改变,改变的只是 height × width × channels 中的 channels 这一个维度的大小而已。

增加非线性

1*1卷积核,可以在保持feature map尺度不变的(即不损失分辨率)的前提下大幅增加非线性特性(利用后接的非线性激活函数),把网络做的很deep。

跨通道信息交互(channal 的变换)

例子:使用1x1卷积核,实现降维和升维的操作其实就是channel间信息的线性组合变化,3x3,64channels的卷积核后面添加一个1x1,28channels的卷积核,就变成了3x3,28channels的卷积核,原来的64个channels就可以理解为跨通道线性组合变成了28channels,这就是通道间的信息交互[7]。

注意:只是在channel维度上做线性组合,W和H上是共享权值的sliding window

参考:unet模型及代码解析_静待缘起的博客-CSDN博客

UNet详解(附图文和代码实现)_liiiiiiiiiiiiike的博客-CSDN博客

一文读懂卷积神经网络中的1x1卷积核 - 知乎

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值