【小白】基于Resnet+Unet的图像分割模型(by Pytorch)

(一)Unet

1.概述

1.Unet是目前应用最广泛的图像(语义)分割模型。它采用了encode(编码)+decode(解码)的结构,先对图像进行多次conv(+Bn+Relu)+pooling下采样,再进行upsample上采样,crop之前的低层feature map,与上采样后的feature map进行融合,重复上采样+融合过程直到得到与输入图像尺寸相同的分割图。

因结构形似字母U,命名为U-net。

Unet  框架
如果仍对Unet网络结构有疑惑,可以看这个大佬的文章,写的很棒!:
https://zhuanlan.zhihu.com/p/31428783

2.代码实现

代码可参照:https://github.com/usuyama/pytorch-unet/blob/master/pytorch_unet.py

以下是我对其的注释,以及部分修改(加了Bn层):

import torch
import torch.nn as nn

def double_conv(in_channels, out_channels):    #双层卷积模型,神经网络最基本的框架
    return nn.Sequential(
        nn.Conv2d(in_channels, out_channels, 3, padding=1),
        nn.BatchNorm2d(out_channels),          #加入Bn层提高网络泛化能力(防止过拟合),加收敛速度
        nn.ReLU(inplace=True),
        nn.Conv2d(out_channels, out_channels, 3, padding=1),  #3指kernel_size,即卷积核3*3
        nn.BatchNorm2d(out_channels),  
        nn.ReLU(inplace=True)
    )   


class UNet(nn.Module):

    def __init__(self, n_class):
        super().__init__()
                
        self.dconv_down1 = double_conv(3, 64)
        self.dconv_down2 = double_conv(64, 128)
        self.dconv_down3 = double_conv(128, 256)
        self.dconv_down4 = double_conv(256, 512)        

        self.maxpool = nn.MaxPool2d(2)
        self.upsample = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)        
        
        self.dconv_up3 = double_conv(256 + 512, 256)  #torch.cat后输入深度变深
        self.dconv_up2 = double_conv(128 + 256, 128)
        self.dconv_up1 = double_conv(128 + 64, 64)
        
        self.conv_last = nn.Conv2d(64, n_class, 1)
        
        
    def forward(self, x):
    	#encode
        conv1 = self.dconv_down1(x)
        x = self.maxpool(conv1)

        conv2 = self.dconv_down2(x)
        x = self.maxpool(conv2)
        
        conv3 = self.dconv_down3(x)
        x = self.maxpool(conv3)   
        
        x = self.dconv_down4(x)
        
        #decode
        x = self.upsample(x)
        #因为使用了3*3卷积核和 padding=1 的组合,所以卷积过程图像尺寸不发生改变,所以省去了crop操作!     
        x = torch.cat([x, conv3], dim=1)
        
        x = self.dconv_up3(x)
        x = self.upsample(x)        
        x = torch.cat([x, conv2], dim=1)       

        x = self.dconv_up2(x)
        x = self.upsample(x)        
        x = torch.cat([x, conv1], dim=1)   
        
        x = self.dconv_up1(x)
        
        out = self.conv_last(x)
        
        return out

(二)Resnet

1.概述

1.1 深度退化问题
从经验上来看,网络越深可以提取更复杂的特征,所以当模型越深理论上可以得到更好的预测结果,但实验发现深度网络出现了模型退化问题,如图:
顶顶顶顶顶的
这不会是过拟合问题,因为56层网络的训练误差同样高。我们知道深层网络存在着梯度消失或者爆炸的问题,这使得深度学习模型很难训练。但是现在已经存在一些技术手段如BatchNorm来缓解这个问题。因此,出现深度网络的退化问题是非常令人诧异的

1.2 残差学习
如果你有一个浅层的网络,想要在此基础上继续训练更深的网络,一个极端情况是这些增加的层什么也不学习,仅仅复制浅层网络的特征,即这样新层是恒等映射(Identity mapping)。在这种情况下,深层网络应该至少和浅层网络性能一样,也不应该出现退化现象

这个有趣的假设让何博士灵感爆发,他提出了残差学习来解决退化问题。对于一个堆积层结构(几层堆积而成)当输入为 X 时其学习到的特征记为 H(X) ,现在我们希望其可以学习到残差 F(X),这样其实原始的学习特征是 F(X)=H(X)-X 。之所以这样是因为残差学习相比原始特征直接学习更容易。当残差为0时,此时堆积层仅仅做了恒等映射,至少网络性能不会下降,实际上残差不会为0,这也会使得堆积层在输入特征基础上学习到新的特征,从而拥有更好的性能。残差学习的结构如图4所示。这有点类似与电路中的“短路”,所以是一种短路连接(shortcut connection)

在这里插入图片描述
即通俗来讲:
传统学习:我们的神经网络训练对X训练,得出H(X)作为输出
残差学习:对X训练,得到F(X),将H(X)=F(X)+X作为输出

框架如下:
黑色圆弧箭头表示一个残差,虚线箭头表示用步长为2的卷积核进行下采样
相同颜色的残差表示一个‘’块‘’(block)
在这里插入图片描述

2.代码实现

代码可参照:https://github.com/weiaicunzai/pytorch-cifar100/blob/master/models/resnet.py

我个人喜欢↑的代码风格,但他写的好像不是个标准的resnet34,他的conv1没采用上图中7×7的卷积核和pool下采样(当然网络没有标准对错,你也可以自己魔改,7×7可以用三个3×3替代,只用一个3×3也许对你的网络效果更好,这没有定论。)

以下是我将该代码修改为标准resnet34:

import torch
import torch.nn as nn

class BasicBlock(nn.Module):
    """Basic Block for resnet 18 and resnet 34
    """

    #BasicBlock and BottleNeck block 
    #have different output size
    #we use class attribute expansion
    #to distinct
    expansion = 1

    def __init__(self, in_channels, out_channels, stride=1):
        super().__init__()

        #residual function
        self.residual_function = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_channels, out_channels * BasicBlock.expansion, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(out_channels * BasicBlock.expansion)
        )

        #shortcut
        self.shortcut = nn.Sequential()

        #the shortcut output dimension is not the same with residual function
        #use 1*1 convolution to match the dimension
        if stride != 1 or in_channels != BasicBlock.expansion * out_channels:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channels, out_channels * BasicBlock.expansion, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(out_channels * BasicBlock.expansion)
            )
        
    def forward(self, x):
        return nn.ReLU(inplace=True)(self.residual_function(x) + self.shortcut(x))

class BottleNeck(nn
  • 102
    点赞
  • 679
    收藏
    觉得还不错? 一键收藏
  • 41
    评论
以下是SE-ASPP-ResNet-UNet模型的代码示例: ```python import torch import torch.nn as nn import torch.nn.functional as F class SELayer(nn.Module): def __init__(self, channel, reduction=16): super(SELayer, self).__init__() self.avg_pool = nn.AdaptiveAvgPool2d(1) self.fc = nn.Sequential( nn.Linear(channel, channel // reduction, bias=False), nn.ReLU(inplace=True), nn.Linear(channel // reduction, channel, bias=False), nn.Sigmoid() ) def forward(self, x): b, c, _, _ = x.size() y = self.avg_pool(x).view(b, c) y = self.fc(y).view(b, c, 1, 1) return x * y class ASPP(nn.Module): def __init__(self, in_channels, out_channels=256): super(ASPP, self).__init__() self.conv1 = nn.Conv2d(in_channels, out_channels, 1) self.conv2 = nn.Conv2d(in_channels, out_channels, 3, padding=6, dilation=6) self.conv3 = nn.Conv2d(in_channels, out_channels, 3, padding=12, dilation=12) self.conv4 = nn.Conv2d(in_channels, out_channels, 3, padding=18, dilation=18) self.avg_pool = nn.AdaptiveAvgPool2d(1) self.conv = nn.Conv2d(in_channels + 4 * out_channels, out_channels, 1) self.bn = nn.BatchNorm2d(out_channels) self.relu = nn.ReLU() def forward(self, x): feat1 = self.conv1(x) feat2 = self.conv2(x) feat3 = self.conv3(x) feat4 = self.conv4(x) feat5 = self.avg_pool(x) feat5 = F.upsample_bilinear(feat5, size=feat4.size()[2:]) x = torch.cat((feat1, feat2, feat3, feat4, feat5), dim=1) x = self.conv(x) x = self.bn(x) x = self.relu(x) return x class SEASPPResNet(nn.Module): def __init__(self): super(SEASPPResNet, self).__init__() self.resnet = nn.Sequential( nn.Conv2d(3, 64, 7, stride=2, padding=3), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(3, stride=2, padding=1), nn.Conv2d(64, 64, 1), nn.BatchNorm2d(64), nn.ReLU(), nn.Conv2d(64, 64, 3, stride=2, padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.Conv2d(64, 256, 1), SELayer(256), ) self.aspp = ASPP(256, 256) def forward(self, x): x = self.resnet(x) x = self.aspp(x) return x class UNet(nn.Module): def __init__(self, in_channels, out_channels): super(UNet, self).__init__() self.down1 = nn.Sequential( nn.Conv2d(in_channels, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.Conv2d(64, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(), ) self.down2 = nn.Sequential( nn.MaxPool2d(2), nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(), nn.Conv2d(128, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(), ) self.down3 = nn.Sequential( nn.MaxPool2d(2), nn.Conv2d(128, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(), nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(), ) self.down4 = nn.Sequential( nn.MaxPool2d(2), nn.Conv2d(256, 512, 3, padding=1), nn.BatchNorm2d(512), nn.ReLU(), nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512), nn.ReLU(), ) self.up1 = nn.Sequential( nn.ConvTranspose2d(512, 256, 2, stride=2), nn.Conv2d(512, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(), nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(), ) self.up2 = nn.Sequential( nn.ConvTranspose2d(256, 128, 2, stride=2), nn.Conv2d(256, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(), nn.Conv2d(128, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(), ) self.up3 = nn.Sequential( nn.ConvTranspose2d(128, 64, 2, stride=2), nn.Conv2d(128, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.Conv2d(64, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(), ) self.up4 = nn.Sequential( nn.ConvTranspose2d(64, 32, 2, stride=2), nn.Conv2d(64, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.Conv2d(32, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(), ) self.out = nn.Conv2d(32, out_channels, 1) def forward(self, x): x1 = self.down1(x) x2 = self.down2(x1) x3 = self.down3(x2) x4 = self.down4(x3) x = self.up1(x4) x = torch.cat([x, x3], dim=1) x = self.up2(x) x = torch.cat([x, x2], dim=1) x = self.up3(x) x = torch.cat([x, x1], dim=1) x = self.up4(x) x = self.out(x) return x class SEASPPResNetUNet(nn.Module): def __init__(self, in_channels, out_channels): super(SEASPPResNetUNet, self).__init__() self.se_aspp_resnet = SEASPPResNet() self.unet = UNet(256, out_channels) def forward(self, x): x = self.se_aspp_resnet(x) x = self.unet(x) return x ``` 这是一个使用PyTorch实现的SE-ASPP-ResNet-UNet模型,其中包含SELayer、ASPP、SEASPPResNetUNet和SEASPPResNetUNet五个类。模型的输入是一个RGB图像,输出是一个大小为out_channels的向量,表示图像属于每个类别的概率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值