Pytorch Unet深入浅出

1.Unet

作为图像语义分割里比较基本的分割网络,自然不能缺席
毕竟文题也叫Unet的深入浅出啊

1.1语义分割做什么

一开始我认为是这样的
在这里插入图片描述
这么理解是没错的,深度学习确实也是这样端到端的小黑盒,
目前大火的原因也是想让这个小黑盒变白
在一维信号或者特征上非常好理解
输入1*n的数据,n是特征的数量,输出一个预测值,这个值代表某种意思
但是一直不太理解在图像中应该怎么理解
直到某一天看的论文累积够了,灵光一闪,才想明白了
在这里插入图片描述
大致意思是,深度学习给得到是概率,
一维的情况是,二维的情况也是
二维给的是概率图,
在用一些核函数的技巧进入实际问题中之后,
深度学习其实就是线性代数+概率论+高等数学
在Unet的结构图中,通过中间的许多层之后,得到概率图,
根据金标准来对自身的概率进行调整和BP
达到最优解,因此后续还会引入一些优化的算法

1.2 网络结构

FCN作为图像分割的鼻祖,建议先了解一下FCN ,这里也会提到FCN
延续了FCN 的深层次的结构,Unet随着网络结构的加深,图像尺寸也在减小
FCN对encode采取的策略是同level的层,对应像素对应相加,Unet则是叠加(concatenate)
放上Unet的网络结构图
unet

在每一个层中均用到了一个概念——卷积,就不得不详细说一下了
因为以前是研究经典算法的,诸如SIFT特征这样的,所以对高斯金字塔特别熟悉
看到卷积的时候也是笑了,知识总是触类旁通和极大的相似
先说一下高斯金字塔,原po传送门
如图,用不同的卷积核产生不同模糊程度的图像
这个图像人眼看不懂,计算机可以识别
相当于原博客中说的,戴上不同度数的眼镜看到的画面不同
在这里插入图片描述

顺道发散一下,高斯金字塔的做法看着是不是很熟悉,比较新的deeplab啥的也是触类旁通的结构
在这里插入图片描述
unet第一层网络结构从channel 1到 channel 64
就是使用了不同的卷积核,产生了指定数量的channel
在这里插入图片描述

网络的后续的channel的变化也是如此,指定数量之后,得到的每个channel,
又唤做feature map,记录了不同尺度下的图像的特征
可能有人会问为什么指定数量之后就能产生对应数量的channel
这里请参考这篇博客
我这里引用其中的图片来解释是如何通过卷积核来产生指定数量的channel的
在这里插入图片描述

这些特征可以通过不断的调整参数,学习到
所以如何最大可能的使用卷积提取图像的特征,在我看来,是语义分割的关键

在卷积过程中
可以通过设置不同的卷积核以及padding,stride的大小,
控制同一层图像的尺寸不变
同样下采样的尺寸也是可以控制的
具体的公式如下:
n e w s i z e = f − k + 2 p s + 1 newsize = \frac{f-k+2p}{s}+1 newsize=sfk+2p+1
其中k是卷积核尺寸,p是padding的值,s是stride的值
根据上述公式,令k=3, p=1, s=1,有
n e w s i z e = f − 3 + 2 1 + 1 = f newsize = \frac{f-3+2}{1}+1=f newsize=1f3+2+1=f
可以使得图像在卷积过程中的尺寸不发生变化
这是一个经常使用的trick
下采样,常用的maxpooling,通常是使得图像尺寸减小1/2
在这里插入图片描述
这会儿再看网络结构
原论文中k=3,p=0,s=1,所以原始的尺寸是572
根据公式有 572 − 3 + 2 ∗ 0 1 + 1 = 570 \frac{572-3+2*0}{1}+1=570 15723+20+1=570
所以从channel1-64,图像尺寸从572减小到了570
以此类推,蓝色小箭头表示卷积操作,每次图像宽和高减小2
红色小箭头表示maxpooling,图像尺寸减半,
如图中第一个红箭头处,从568减小到了284
在这里插入图片描述
绿色小箭头代表上采样,与红色相对应,这里使图像尺寸增加2倍,如下图中1024到512

在这里插入图片描述
网络的创新之处就是concatenate这个操作了,也就是图中的灰色箭头这个位置
在这里插入图片描述
可以看到,从1024个channel上采样过来的512个channel
和上一个对应有512个channel的同层channel被叠加起来了,
成为了一个新的1024个channel,如图红圈部分
同理类似256+256构成了新的512
与FCN不同的是,FCN在这一步中是直接与之前的进行对应像素相加操作
都是很巧妙的操作
到此,整个Unet的结构细节全部解析完毕
跟着推导一遍尺寸,会受益匪浅,入门其他的网络也会相对容易
给出pytorch实现的Unet模型,用到了我所说的同层尺寸不变的trick
还是老规矩,高度集成,面向对象

import torch.nn as nn


class conv_block(nn.Module):
    def __init__(self, ch_in, ch_out):
        super(conv_block, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(ch_in, ch_out, kernel_size=3, stride=1, padding=1, bias=True),
            nn.BatchNorm2d(ch_out),
            nn.ReLU(inplace=True),
            nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1, bias=True),
            nn.BatchNorm2d(ch_out),
            nn.ReLU(inplace=True)
        )

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

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

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


class U_Net(nn.Module):
    def __init__(self, img_ch=3, output_ch=1):
        super(U_Net, self).__init__()

        self.Maxpool = nn.MaxPool2d(kernel_size=2, stride=2)

        self.Conv1 = conv_block(ch_in=img_ch, ch_out=64)
        self.Conv2 = conv_block(ch_in=64, ch_out=128)
        self.Conv3 = conv_block(ch_in=128, ch_out=256)
        self.Conv4 = conv_block(ch_in=256, ch_out=512)
        self.Conv5 = conv_block(ch_in=512, ch_out=1024)

        self.Up5 = up_conv(ch_in=1024, ch_out=512)
        self.Up_conv5 = conv_block(ch_in=1024, ch_out=512)

        self.Up4 = up_conv(ch_in=512, ch_out=256)
        self.Up_conv4 = conv_block(ch_in=512, ch_out=256)

        self.Up3 = up_conv(ch_in=256, ch_out=128)
        self.Up_conv3 = conv_block(ch_in=256, ch_out=128)

        self.Up2 = up_conv(ch_in=128, ch_out=64)
        self.Up_conv2 = conv_block(ch_in=128, ch_out=64)

        self.Conv_1x1 = nn.Conv2d(64, output_ch, kernel_size=1, stride=1, padding=0)

    def forward(self, x):
        # encoding path
        x1 = self.Conv1(x)

        x2 = self.Maxpool(x1)
        x2 = self.Conv2(x2)

        x3 = self.Maxpool(x2)
        x3 = self.Conv3(x3)

        x4 = self.Maxpool(x3)
        x4 = self.Conv4(x4)

        x5 = self.Maxpool(x4)
        x5 = self.Conv5(x5)

        # decoding + concat path
        d5 = self.Up5(x5)
        d5 = torch.cat((x4, d5), dim=1)

        d5 = self.Up_conv5(d5)

        d4 = self.Up4(d5)
        d4 = torch.cat((x3, d4), dim=1)
        d4 = self.Up_conv4(d4)

        d3 = self.Up3(d4)
        d3 = torch.cat((x2, d3), dim=1)
        d3 = self.Up_conv3(d3)

        d2 = self.Up2(d3)
        d2 = torch.cat((x1, d2), dim=1)
        d2 = self.Up_conv2(d2)

        d1 = self.Conv_1x1(d2)

        return d1

分割线,觉得不错点个赞即可
狂推----深度学习项目架构模板传送门
狂推----启蒙的repo传送门

  • 8
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值