Pytorch深度学习实战教程(二):UNet语义分割网络

2019年12月3日23:29:08 10 214 °C
摘要

研究一个深度学习算法,可以先看网络结构,看懂网络结构后,再Loss计算方法、训练方法等。本文主要讲解UNet网络结构,以及相应代码的代码编写,其它内容会在后续章节进行说明。

Pytorch深度学习实战教程(二):UNet语义分割网络

一、前言

本文属于Pytorch深度学习语义分割系列教程。

该系列文章的内容有:

  • Pytorch的基本使用
  • 语义分割算法讲解

如果不了解语义分割原理以及开发环境的搭建,请看该系列教程的上一篇文章《Pytorch深度学习实战教程(一):语义分割基础与环境搭建》。

本文的开发环境采用上一篇文章搭建好的Windows环境,环境情况如下:

开发环境:Windows

开发语言:Python3.7.4

框架版本:Pytorch1.3.0

CUDA:10.2

cuDNN:7.6.0

本文主要讲解UNet网络结构,以及相应代码的代码编写

PS:文中出现的所有代码,均可在我的github上下载,欢迎Follow、Star:点击查看

二、UNet网络结构

在语义分割领域,基于深度学习的语义分割算法开山之作是FCN(Fully Convolutional Networks for Semantic Segmentation),而UNet是遵循FCN的原理,并进行了相应的改进,使其适应小样本的简单分割问题。

UNet论文地址:点击查看

研究一个深度学习算法,可以先看网络结构,看懂网络结构后,再Loss计算方法、训练方法等。本文主要针对UNet的网络结构进行讲解,其它内容会在后续章节进行说明。

1、网络结构原理

UNet最早发表在2015的MICCAI会议上,4年多的时间,论文引用量已经达到了9700多次。

UNet成为了大多做医疗影像语义分割任务的baseline,同时也启发了大量研究者对于U型网络结构的研究,发表了一批基于UNet网络结构的改进方法的论文。

UNet网络结构,最主要的两个特点是:U型网络结构和Skip Connection跳层连接。

Pytorch深度学习实战教程(二):UNet语义分割网络

UNet是一个对称的网络结构,左侧为下采样,右侧为上采样。

按照功能可以将左侧的一系列下采样操作称为encoder,将右侧的一系列上采样操作称为decoder。

Skip Connection中间四条灰色的平行线,Skip Connection就是在上采样的过程中,融合下采样过过程中的feature map。

Skip Connection用到的融合的操作也很简单,就是将feature map的通道进行叠加,俗称Concat。

Concat操作也很好理解,举个例子:一本大小为10cm*10cm,厚度为3cm的书A,和一本大小为10cm*10cm,厚度为4cm的书B。

将书A和书B,边缘对齐地摞在一起。这样就得到了,大小为10cm*10cm厚度为7cm的一摞书,类似这种:

Pytorch深度学习实战教程(二):UNet语义分割网络

这种“摞在一起”的操作,就是Concat。

同样道理,对于feature map,一个大小为256*256*64的feature map,即feature map的w(宽)为256,h(高)为256,c(通道数)为64。和一个大小为256*256*32的feature map进行Concat融合,就会得到一个大小为256*256*96的feature map。

在实际使用中,Concat融合的两个feature map的大小不一定相同,例如256*256*64的feature map和240*240*32的feature map进行Concat。

这种时候,就有两种办法:

第一种:将大256*256*64的feature map进行裁剪,裁剪为240*240*64的feature map,比如上下左右,各舍弃8 pixel,裁剪后再进行Concat,得到240*240*96的feature map。

第二种:将小240*240*32的feature map进行padding操作,padding为256*256*32的feature map,比如上下左右,各补8 pixel,padding后再进行Concat,得到256*256*96的feature map。

UNet采用的Concat方案就是第二种,将小的feature map进行padding,padding的方式是补0,一种常规的常量填充。

2、代码

有些朋友可能对Pytorch不太了解,推荐一个快速入门的官方教程。一个小时,你就可以掌握一些基本概念和Pytorch代码编写方法。

Pytorch官方基础:点击查看

我们将整个UNet网络拆分为多个模块进行讲解。

DoubleConv模块:

先看下连续两次的卷积操作。

Pytorch深度学习实战教程(二):UNet语义分割网络

从UNet网络中可以看出,不管是下采样过程还是上采样过程,每一层都会连续进行两次卷积操作,这种操作在UNet网络中重复很多次,可以单独写一个DoubleConv模块:

解释下,上述的Pytorch代码:torch.nn.Sequential是一个时序容器,Modules 会以它们传入的顺序被添加到容器中。比如上述代码的操作顺序:卷积->BN->ReLU->卷积->BN->ReLU。

DoubleConv模块的in_channels和out_channels可以灵活设定,以便扩展使用。

如上图所示的网络,in_channels设为1,out_channels为64。

输入图片大小为572*572,经过步长为1,padding为0的3*3卷积,得到570*570的feature map,再经过一次卷积得到568*568的feature map。

计算公式:O=(H−F+2×P)/S+1

H为输入feature map的大小,O为输出feature map的大小,F为卷积核的大小,P为padding的大小,S为步长。

Down模块:

Pytorch深度学习实战教程(二):UNet语义分割网络

UNet网络一共有4次下采样过程,模块化代码如下:

这里的代码很简单,就是一个maxpool池化层,进行下采样,然后接一个DoubleConv模块。

至此,UNet网络的左半部分的下采样过程的代码都写好了,接下来是右半部分的上采样过程

Up模块:

上采样过程用到的最多的当然就是上采样了,除了常规的上采样操作,还有进行特征的融合。

Pytorch深度学习实战教程(二):UNet语义分割网络

这块的代码实现起来也稍复杂一些:

代码复杂一些,我们可以分开来看,首先是__init__初始化函数里定义的上采样方法以及卷积采用DoubleConv。上采样,定义了两种方法:Upsample和ConvTranspose2d,也就是双线性插值反卷积

双线性插值很好理解,示意图:

Pytorch深度学习实战教程(二):UNet语义分割网络

熟悉双线性插值的朋友对于这幅图应该不陌生,简单地讲:已知Q11、Q12、Q21、Q22四个点坐标,通过Q11和Q21求R1,再通过Q12和Q22求R2,最后通过R1和R2求P,这个过程就是双线性插值。

对于一个feature map而言,其实就是在像素点中间补点,补的点的值是多少,是由相邻像素点的值决定的。

反卷积,顾名思义,就是反着卷积。卷积是让featuer map越来越小,反卷积就是让feature map越来越大,示意图:

Pytorch深度学习实战教程(二):UNet语义分割网络

下面蓝色为原始图片,周围白色的虚线方块为padding结果,通常为0,上面绿色为卷积后的图片。

这个示意图,就是一个从2*2的feature map->4*4的feature map过程。

在forward前向传播函数中,x1接收的是上采样的数据,x2接收的是特征融合的数据。特征融合方法就是,上文提到的,先对小的feature map进行padding,再进行concat。

OutConv模块:

用上述的DoubleConv模块、Down模块、Up模块就可以拼出UNet的主体网络结构了。UNet网络的输出需要根据分割数量,整合输出通道,结果如下图所示:

Pytorch深度学习实战教程(二):UNet语义分割网络

操作很简单,就是channel的变换,上图展示的是分类为2的情况(通道为2)。

虽然这个操作很简单,也就调用一次,为了美观整洁,也封装一下吧。

至此,UNet网络用到的模块都已经写好,我们可以将上述的模块代码都放到一个unet_parts.py文件里,然后再创建unet_model.py,根据UNet网络结构,设置每个模块的输入输出通道个数以及调用顺序,编写如下代码:

使用命令python unet_model.py,如果没有错误,你会得到如下结果:

网络搭建完成,下一步就是使用网络进行训练了,具体实现会在该系列教程的下一篇文章进行讲解。

六、小结

  • 本文主要讲解了UNet网络结构,并对UNet网络进行了模块化梳理。
  • 下篇文章讲解如何使用UNet网络,编写训练代码。

 

PS: 如果觉得本篇本章对您有所帮助,欢迎关注、评论、赞!

文中出现的所有代码,均可在我的github上下载,欢迎Follow、Star:点击查看

参考资料:

https://github.com/milesial/Pytorch-UNet/blob/master/unet/unet_parts.py

https://blog.csdn.net/qq_38906523/article/details/80520950

https://zhuanlan.zhihu.com/p/37618829

weinxin
微信公众号
分享技术,乐享生活:Jack Cui公众号每周五推送“程序员欢乐送”系列资讯类文章,欢迎您的关注!
Jack Cui

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

目前评论:10   其中:访客  5   博主  5

    • avatar yearing1017 来自天朝的朋友 谷歌浏览器 Mac OS X 10_14_6 北京市 移动 2

      cui神,我想问self.down4 = Down(512, 512)
      self.up1 = Up(1024, 256, bilinear),
      这个下采样的out_channle为什么还是512,不该是1024吗? :sad:

        • avatar Jack Cui Admin 来自天朝的朋友 谷歌浏览器 Mac OS X 10_14_4 北京市 百度网讯科技联通节点

          @yearing1017 这应该是原论文有点问题,特种融合采用concat,最下面的一层就应该是512通道+512通道,得到1024通道。
          所以应该是self.down4 = Down(512, 512),原论文中图片中的1024应该改为512,这样上采样过后,512+512才能得到1024通道的feature map,然后经过两次通道压缩,1024->256,然后经过特征融合得到256+256,也就是512通道的feature map,你捋一捋。

            • avatar yearing1017 来自天朝的朋友 谷歌浏览器 Mac OS X 10_14_6 北京市 移动 2

              @Jack Cui 谢谢cui神的回复。
              我还是有些想不通,您说的“原论文图片中的1024改为512”是下采样之后U形结构最下面(第5层)的那个1024吗?
              在Unet论文中说的是 每进行一次上采样,channels都会减半,也就是从第5层第4层上采样,channels减半,第四层得到1024/2=512,然后再和左边的512concat得到1024。
              那么self.down4 = Down(512,1024)才是对的吧?
              而且和前面的down3,down2的参数相比较。。。down4的out_channles应该是1024吧。。。 :sad: 我是不是哪里有错误,cui神

                • avatar Jack Cui Admin 来自天朝的朋友 谷歌浏览器 Mac OS X 10_14_4 北京市 百度网讯科技联通节点

                  @yearing1017 我对论文的网络结构进行了修改注释,这回你再看下。

                    • avatar yearing1017 来自天朝的朋友 谷歌浏览器 Mac OS X 10_14_6 北京市 移动 2

                      @Jack Cui Every step in the expansive path consists of an upsampling of the feature map followed by a 2×2 convolution (“up-convolution”) that halves the number of feature channels, a concatenation with the correspondingly cropped feature map from the contracting path, and two 3×3 convolutions, each fol- lowed by a ReLU.
                      cui神,我好像理解你的意思了,这是unet的原文,它这里说的每次反卷积之后减半channels是不是在每层经历完横向的两次conv之后,才会减半?并不是绿色箭头从下面一层指向上面一层的时候就减半?这样理解的话。。就看懂cui神您的注释图了。。。
                      :mrgreen: cui神,,我冒昧的问一句,您觉的这个官方论文的图真的会有错误吗。。。

                    • avatar Jack Cui Admin 来自天朝的朋友 谷歌浏览器  Android 9 Redmi K20 Pro Build/PKQ1.181121.001 北京市 联通

                      @yearing1017 是的,上采样通道是不变的。
                      关于unet图的对错,我记得在github看过有人说,你可以在github看下别人的实现和issuse

                        • avatar yearing1017 来自天朝的朋友 谷歌浏览器 Mac OS X 10_14_6 北京市 移动 2

                          @Jack Cui https://github.com/JavisPeng/u_net_liver/blob/master/unet.py
                          Cui神,我感觉上面这个链接的代码是对的。。。。您看一下?

                    • avatar Jack Cui Admin 来自天朝的朋友 谷歌浏览器 Mac OS X 10_14_4 北京市 百度网讯科技联通节点

                      @yearing1017 哦哦,是这样,我懂了。
                      用nn.ConvTranspose2d做上采样的时候,因为可以改变通道数,所以可以到1024,然后在上采样的时候,变成512。
                      我这个是用的Upsample做上采样,不能改变通道数,所以只能变到512。
                      后面我都更新下,改下吧,以论文的为主。
                      不过这个模型是可以调整的,实际使用的时候,可以看看那种上采样的结果哪个好。

                        • avatar yearing1017 来自天朝的朋友 谷歌浏览器 Mac OS X 10_14_6 北京市 移动 2

                          @Jack Cui 嗯嗯,谢谢cui神不耐烦回复我hh,期待您的实战篇