Unet网络---网络结构和pytorch实现

一、Unet网络

论文地址:https://arxiv.org/pdf/1505.04597.pdf

pytorch代码:https://github.com/milesial/Pytorch-UNet

二、网络结构

话不多说,先上图

        Unet很简单,具体可以看作为左右两个部分,自上而下的编码器Encode和和由下而上的解码器Decode。Unet相较于其他深度学习网络,更常用于计算机视觉领域,因为可以输出输入图像原始尺寸大小的图片,可以在输入图片上实现01分割,分类,语义分割等计算机视觉任务,且大部分只需要更改网络输出层的输出头,即可实现不同任务。Unet网络原文输出为一个2通道数据,可以完成01图像分割任务。

1、编码器(Encoder)

        根据网络结构图,我们发现image图像二维图像输入,经过两次卷积使得通道由channel =1变为channel=64,随后使用Max pool对图像下采样至图像原始大小的一半通道数channel保持不变为64,继续使用两次卷积将通道数扩充为128,重复以上操作。压缩图片大小至32*32,通道为1024。

        编码器负责将输入数据转换为一种更抽象、更紧凑的表示形式,通常是低维度的特征向量或特征图。它通过一系列的层和操作对输入数据进行逐层的处理和提取,以捕捉输入数据中的关键特征和信息。编码器的输出通常被认为是输入数据的高级表示,其中包含了输入数据的重要特征。

        例如,我们描述一张人脸,如果我们只用一个特征即通道数表示人脸信息,我们可以说这张脸是一张圆脸,长脸,瓜子脸。但是我们仍然无法根据这个特征抽象出这个人脸是什么样子,我们可以增加人脸的特征值数量,例如:眼睛为桃花眼,眼间距是50mm,嘴唇宽厚等一系列人脸信息来描述这张人脸,就此我们就获得了这张人脸的特征信息。编码器的一系列卷积池化等操作也在尽可能抽象出输入图片的特征,并用深度学习的语言和可以理解的数据方式存储,以备后续使用。也即我们对人脸进行了编码。

2、解码器(Decode)

        根据网络结构图,解码器自下而上,通过卷积和上采用操作,将图片通道数减少,尺寸不断变大。简单理解就是根据我编码后的结果,不断将特征值所提供的信息展现在输出结果上。解码器则与编码器相反,负责将编码器输出的抽象表示解码为原始输入数据的重建或生成。它通过逆向的层和操作,将编码器输出映射回原始数据空间,并尽可能恢复输入数据的细节和结构。解码器的目标是生成与原始输入数据尽可能相似的重建输出。

        虽然我们没有见过网络输入的人脸,但是我们可以通过人脸的一些特征信息去恢复出一张人脸。

3、跳跃链接(skip)

        在编码器和解码器之间的每个层级,U-Net 使用了跳跃连接来保留和传递更高层级的特征信息。这样可以将低层级的特征信息直接传递给高层级,帮助解码器更好地恢复细节和边缘信息。这样,解码器可以同时利用来自编码器的低层级特征和自身的高层级特征来进行重建。这种跳跃连接的结构使得 U-Net 具有较强的上下文感知能力和细节恢复能力,适用于图像分割任务。在Pytorch中,我们常常使用cat操作实现通道合并即:

torch.cat((y, x), dim=1)

cat操作在维度dim=1,将y与x 实现合并操作(concatenate),在dim维度上,必须保证除了拼接维度上其他维度的大小是相同的。

三、代码实现

1、编码器

import torch
import torch.nn as nn
import torch.nn.functional as F


class DoubleConv(nn.Module):
    """(convolution => [BN] => ReLU) * 2"""

    def __init__(self, in_channels, out_channels, mid_channels=None):
        super().__init__()
        if not mid_channels:
            mid_channels = out_channels
        self.double_conv = nn.Sequential(
            nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(mid_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        return self.double_conv(x)


class Down(nn.Module):
    """Downscaling with maxpool then double conv"""

    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.maxpool_conv = nn.Sequential(
            nn.MaxPool2d(2),
            DoubleConv(in_channels, out_channels)
        )

    def forward(self, x):
        return self.maxpool_conv(x)

2、解码器

class Up(nn.Module):
    """Upscaling then double conv"""

    def __init__(self, in_channels, out_channels, bilinear=True):
        super().__init__()

        # if bilinear, use the normal convolutions to reduce the number of channels
        if bilinear:
            self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
            self.conv = DoubleConv(in_channels, out_channels, in_channels // 2)
        else:
            self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2)
            self.conv = DoubleConv(in_channels, out_channels)

    def forward(self, x1, x2):
        x1 = self.up(x1)
        # input is CHW
        diffY = x2.size()[2] - x1.size()[2]
        diffX = x2.size()[3] - x1.size()[3]

        x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2,
                        diffY // 2, diffY - diffY // 2])
        # if you have padding issues, see
        # https://github.com/HaiyongJiang/U-Net-Pytorch-Unstructured-Buggy/commit/0e854509c2cea854e247a9c615f175f76fbb2e3a
        # https://github.com/xiaopeng-liao/Pytorch-UNet/commit/8ebac70e633bac59fc22bb5195e513d5832fb3bd
        x = torch.cat([x2, x1], dim=1)
        return self.conv(x)


class OutConv(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(OutConv, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)

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

3、分割头

class OutConv(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(OutConv, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)

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

   4、完整网络结构

from .unet_parts import *


class UNet(nn.Module):
    def __init__(self, n_channels, n_classes, bilinear=False):
        super(UNet, self).__init__()
        self.n_channels = n_channels
        self.n_classes = n_classes
        self.bilinear = bilinear

        self.inc = (DoubleConv(n_channels, 64))
        self.down1 = (Down(64, 128))
        self.down2 = (Down(128, 256))
        self.down3 = (Down(256, 512))
        factor = 2 if bilinear else 1
        self.down4 = (Down(512, 1024 // factor))
        self.up1 = (Up(1024, 512 // factor, bilinear))
        self.up2 = (Up(512, 256 // factor, bilinear))
        self.up3 = (Up(256, 128 // factor, bilinear))
        self.up4 = (Up(128, 64, bilinear))
        self.outc = (OutConv(64, n_classes))

    def forward(self, x):
        x1 = self.inc(x)
        x2 = self.down1(x1)
        x3 = self.down2(x2)
        x4 = self.down3(x3)
        x5 = self.down4(x4)
        x = self.up1(x5, x4)
        x = self.up2(x, x3)
        x = self.up3(x, x2)
        x = self.up4(x, x1)
        logits = self.outc(x)
        return logits

    def use_checkpointing(self):
        self.inc = torch.utils.checkpoint(self.inc)
        self.down1 = torch.utils.checkpoint(self.down1)
        self.down2 = torch.utils.checkpoint(self.down2)
        self.down3 = torch.utils.checkpoint(self.down3)
        self.down4 = torch.utils.checkpoint(self.down4)
        self.up1 = torch.utils.checkpoint(self.up1)
        self.up2 = torch.utils.checkpoint(self.up2)
        self.up3 = torch.utils.checkpoint(self.up3)
        self.up4 = torch.utils.checkpoint(self.up4)
        self.outc = torch.utils.checkpoint(self.outc)

扩展

        Unet网络结构简单,参数量少,但是可以获得细节丰富的输出,U形网络设计也为之后的计算机视觉工作提供了很多思路,同时也Unet网络也可以做很多优化改动,例如,可以在跳跃链接(skip)上添加注意力机制(Attention Mechanism)例如:通道-空间注意力机制;修改卷积层卷积核大小和数量可以影响感受野的大小和特征提取的能力。较小的卷积核可以捕捉更细节的特征,而较大的卷积核可以捕捉更全局的特征。

        后面我也会做一些Unet网络优化的例子

绘制UNet网络结构图的方法有很多种,以下是其中两种常用的方法: 方法一:使用神经网络可视化工具 可以使用一些神经网络可视化工具,例如TensorFlow的TensorBoard或Keras的plot_model方法,来绘制UNet网络结构图。以下是使用Keras的plot_model方法的例子: ```python from keras.models import Model from keras.layers import Input, Conv2D, MaxPooling2D, UpSampling2D, Concatenate # 定义UNet网络结构 inputs = Input(shape=(256, 256, 3)) conv1 = Conv2D(64, 3, activation='relu', padding='same')(inputs) conv1 = Conv2D(64, 3, activation='relu', padding='same')(conv1) pool1 = MaxPooling2D(pool_size=(2, 2))(conv1) conv2 = Conv2D(128, 3, activation='relu', padding='same')(pool1) conv2 = Conv2D(128, 3, activation='relu', padding='same')(conv2) pool2 = MaxPooling2D(pool_size=(2, 2))(conv2) conv3 = Conv2D(256, 3, activation='relu', padding='same')(pool2) conv3 = Conv2D(256, 3, activation='relu', padding='same')(conv3) pool3 = MaxPooling2D(pool_size=(2, 2))(conv3) conv4 = Conv2D(512, 3, activation='relu', padding='same')(pool3) conv4 = Conv2D(512, 3, activation='relu', padding='same')(conv4) drop4 = Dropout(0.5)(conv4) pool4 = MaxPooling2D(pool_size=(2, 2))(drop4) conv5 = Conv2D(1024, 3, activation='relu', padding='same')(pool4) conv5 = Conv2D(1024, 3, activation='relu', padding='same')(conv5) drop5 = Dropout(0.5)(conv5) up6 = Conv2D(512, 2, activation='relu', padding='same')(UpSampling2D(size=(2, 2))(drop5)) merge6 = Concatenate()([drop4, up6]) conv6 = Conv2D(512, 3, activation='relu', padding='same')(merge6) conv6 = Conv2D(512, 3, activation='relu', padding='same')(conv6) up7 = Conv2D(256, 2, activation='relu', padding='same')(UpSampling2D(size=(2, 2))(conv6)) merge7 = Concatenate()([conv3, up7]) conv7 = Conv2D(256, 3, activation='relu', padding='same')(merge7) conv7 = Conv2D(256, 3, activation='relu', padding='same')(conv7) up8 = Conv2D(128, 2, activation='relu', padding='same')(UpSampling2D(size=(2, 2))(conv7)) merge8 = Concatenate()([conv2, up8]) conv8 = Conv2D(128, 3, activation='relu', padding='same')(merge8) conv8 = Conv2D(128, 3, activation='relu', padding='same')(conv8) up9 = Conv2D(64, 2, activation='relu', padding='same')(UpSampling2D(size=(2, 2))(conv8)) merge9 = Concatenate()([conv1, up9]) conv9 = Conv2D(64, 3, activation='relu', padding='same')(merge9) conv9 = Conv2D(64, 3, activation='relu', padding='same')(conv9) conv9 = Conv2D(2, 3, activation='relu', padding='same')(conv9) conv10 = Conv2D(1, 1, activation='sigmoid')(conv9) # 定义模型 model = Model(inputs=inputs, outputs=conv10) # 绘制网络结构图 from keras.utils import plot_model plot_model(model, to_file='unet.png', show_shapes=True) ``` 运行上面代码后,将生成一个名为`unet.png`的文件,其中包含UNet网络结构图。 方法二:手工绘制 如果你喜欢手工绘图,可以使用一些绘图工具(例如Visio、PowerPoint等)或者画图纸和笔来手工绘制UNet网络结构图。首先,你需要明确UNet网络的结构,然后按照结构图的要求绘制节点和连接线。例如,对于下面的UNet网络结构: ``` Input | |-----Convolution |-----Convolution |-----Max Pooling | |-----Convolution |-----Convolution |-----Max Pooling | |-----Convolution |-----Convolution |-----Max Pooling | |-----Convolution |-----Convolution |-----Dropout |-----Max Pooling | |-----Convolution |-----Convolution |-----Dropout | |-----Up Sampling |-----Concatenation |-----Convolution |-----Convolution | |-----Up Sampling |-----Concatenation |-----Convolution |-----Convolution | |-----Up Sampling |-----Concatenation |-----Convolution |-----Convolution | |-----Up Sampling |-----Concatenation |-----Convolution |-----Convolution |-----Convolution | |-----Sigmoid ``` 你可以按照以下步骤进行手工绘图: 1. 在画布上绘制一个大圆圈,作为UNet网络的输入节点。 2. 从输入节点向下绘制一条垂直线,然后在垂直线的下方绘制一个小圆圈,表示第一层卷积。 3. 在第一层卷积的小圆圈下方绘制一个小圆圈,表示第二层卷积,然后在第二层卷积的小圆圈下方绘制一个长方形,表示最大池化。 4. 重复步骤2-3,绘制出UNet网络的第二层、第三层和第四层。 5. 在第四层最大池化的下方绘制一个小圆圈,表示第五层卷积,然后在第五层卷积的小圆圈下方绘制一个长方形,表示dropout层。 6. 在dropout层的下方绘制一个小圆圈,表示第六层卷积。 7. 重复步骤6,绘制出UNet网络的第七层和第八层。 8. 在第八层的下方绘制一个长方形,表示上采样层。 9. 在上采样层的右侧绘制一个大圆圈,表示连接层。 10. 在连接层的下方绘制一个小圆圈,表示第九层卷积,然后在第九层卷积的小圆圈下方绘制一个小圆圈,表示第十层卷积。 11. 重复步骤10,绘制出UNet网络的第十一层和第十二层。 12. 在第十二层卷积的下方绘制一个长方形,表示上采样层。 13. 在上采样层的右侧绘制一个大圆圈,表示连接层。 14. 重复步骤10-13,绘制出UNet网络的第十三层到第十八层。 15. 在第十八层卷积的下方绘制一个长方形,表示sigmoid层。 16. 完成绘图。 注意,手工绘图的过程需要根据具体的UNet网络结构来进行,以上步骤仅供参考。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值