Resnet网络理解及其pytorch实现

1,为什么提出ResNet网络?

在不断加深的神经网络的深度时,会出现退化的问题,即准确率会先上升然后达到饱和,再持续增加深度后其准确率下降了。这不是由于过拟合导致的,因为这不仅导致训练集上误差增大,测试集上误差也增大。原因是随着网络越来越深,训练变得越来越难,网络的优化变得越来越难。理论上,层度越深的网络其学习到语义信息越丰富,提取的特征效果会越好;但是,实际上,由于深度增加导致网络难以训练,产生网络退出问题,其效果还没有浅层网络的好。而残差网络的出现解决了这个问题。

2,残差网络的创新点

文章的基本思想是:在理想化的深层神经网络中,如果后面的网络层是恒等映射(残差块),那么随着残差块的增加(深度增加)至少网络的效果不会比浅层的差。

(1)引入了残差结构,设想残差映射比起普通的堆叠层的映射更容易优化

(2)使用了zero padding和1x1投影的方式解决了分辨率和深度不一样的特征图相加的问题。

3,残差结构

残差是指实际观察值和估计值之间的差,残差块的输入为x,拟合的输出为H(x),如果我们直接把输入x直接传到输出作为观测结果,那么需要学习的残差就是F(x)=H(x)-x.

残差块的计算方式:F(x)=W_2\cdot relu(W_1x)

残差块的输出:relu(H(x))=relu(F(x)+x)

残差块的优化目标:预测输出H(x)=x,即输出H(x)与x的差别;使残差F(x)=H(x)-x,尽量使F(x)为0

残差为什么更容易实现优化:

一般网络实现恒等映射的方式是拟合一个函数使H(x)=x,但是直接拟合这个恒等隐式难度很大。而引入残差后,它将拟合函数H(x)=x转化为F(x)=H(x)-x形式,通过在残差块中加入batchnorm normalization等方式,可以使网络更为容易实现F(x)=0,从而实现H(x)=x,即实现了恒等映射。

同时,引入残差块后也使映射对输出变化更敏感。设H_1(x)是加入skip连接前的网络映射,H_2(x)是加入skip连接后的网络映射。对于输入x=5,设此时H1(5)=5.1,H2(5)=F(5)+5=5.1,即F(5)=0.1.当输出变为5.2时,H1(x)由5.1变为5.2,F(x)由0.1变为0.2,明显后者输出变化对权重调整作用大,效果更好。残差的思想是去掉相同主体部分,突出微小部分的变化。

4.ResNet网络结构

左边是我们之前提到的残差块,右边则是瓶颈结构了。之所以称为瓶颈结构,因为它两端深度(channel)大,中间小,就像是一个瓶口的颈部。
那为什么要提出这种结构呢?其实主要是解决在更加深的残差网络101或者152中,参数数目的问题。我们从resnet34中看到,特征图的厚度是逐渐加深的,在resnent34中最后几层卷积是512的厚度,那到了resnet152,岂不是上万的厚度,带来的参数数目是巨大的。所以我们看右边的图,输入先经过一个1x1的卷积核降维,将一个256维的输入降到了64维,再使用3x3的卷积提特征,再用1x1的卷积核升维。这样就到了减少参数数目的目的。

本文中用pytorch实现上述表格中Resnet18的结构:

整个网络结构的搭建如下:先构建残差块(BasicBlock),然后在搭建ResNet18网络,网络中有4个layer,每个layer有两个basicblock。其图示结构可以参考以下链接:

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

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

#BasicBlock块构建
class BasicBlock(nn.Module):
    expansion=1
    
    def __init__(self,inplanes,planes,stride):
        super(BasicBlock,self).__init__()
        self.s=stride
        
        self.conv1=nn.Conv2d(inplanes,planes,kernel_size=3,stride=stride,padding=1,bias=False)
        self.bn1=nn.BatchNorm2d(planes)
        self.conv2=nn.Conv2d(planes,planes,kernel_size=3,stride=1,padding=1,bias=False)
        self.bn2=nn.BatchNorm2d(planes)
        self.shortcut=nn.Sequential()
        if stride!=1 or inplanes!=self.expansion*planes:
            self.shortcut=nn.Sequential(nn.Conv2d(inplanes,planes*self.expansion,kernel_size=1,stride=stride,bias=False),
                                      nn.BatchNorm2d(planes*self.expansion))
            
        
        
    def forward(self,x):
        
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
      
        cut=self.shortcut(x)
        out += self.shortcut(x)
        out = F.relu(out)
        return out

#ResNet18g构建
class ResNet(nn.Module):
    
    def __init__(self,block,num_block):
        super(ResNet,self).__init__()
        self.in_planes=64
        self.conv1=nn.Conv2d(3,64,kernel_size=7,stride=2,padding=3,bias=False)
        self.bn1=nn.BatchNorm2d(64)
        
        self.layer1=self._make_layer(block,64,num_block[0],stride=1)
        self.layer2=self._make_layer(block,128,num_block[1],stride=2)
        self.layer3=self._make_layer(block,256,num_block[2],stride=2)
        self.layer4=self._make_layer(block,512,num_block[3],stride=2)
        
        self.fc=nn.Linear(512*block.expansion,10)
        
    def _make_layer(self,block,planes,numblock,stride):
        strides=[stride]+[1]*(numblock-1)
        layers=[]
        for stride in strides:
            layers.append(block(self.in_planes,planes,stride))
            self.in_planes=block.expansion*planes
        return nn.Sequential(*layers)#*返回迭代的元素
    
    def forward(self,x):
        out=F.relu(self.bn1(self.conv1(x)))
        out=F.max_pool2d(out,[3,3],stride=2,padding=1)
        out=self.layer1(out)
        out=self.layer2(out)
        out=self.layer3(out)
        out=self.layer4(out)
        out=F.avg_pool2d(out,4)#1*512*512
        out=out.view(out.size(0),-1)
        out=self.fc(out)
        return out
    

网络调用:

net=ResNet(BasicBlock,[2,2,2,2])
input=torch.randn(1,3,224,224)
#input=torch.randn(1,64,56,56)
y=net(input)
print(y)

结果:

torch.Size([1, 512])
tensor([[ 0.0466, -0.3549, -0.7361, -0.0907, -0.6479,  0.3554, -0.0383,  0.3438,
         -0.3191,  0.0304]], grad_fn=<AddmmBackward>)

 残差的深层含义,引入他人博客的

设F为一个残差单元,x是上一个残差单元的输出,代表提取的特征,残差单元的输出为F(x)+x。要知道反向传播是损失函数代表的空间中往更优的方向走。每一次迭代理论上说参数应该更新为满足优化损失函数的数值。我们就当做这个设想成立。现在设想残差单元的输出F(x)+x这个特征比x要差,那么反向传播时,F中的参数该往什么方向上更新呢,当然是往数值更小的方向更新,尽量使得F(X)为0或者近似0。因为这样能保证F(x)+x至少不会比x更差。如果F(x)为0了,或者近似0,说明当前残差单元进行了恒等映射,所得到的特征和上一个单元的特征一样,那么可以说当前单元得到的特征至少不会比上一层的特征差。又因为残差又不可能为0,其数值是往优化损失函数的方向上更新的,所以特征会越来越好。
原文链接:https://blog.csdn.net/qq_34914551/article/details/88088182 

 残差的应用:

在许多其他论文中看到过引用残差的思想,例如在图像或者点云去噪中,通过网络学习到噪音的分布,然后在将网络输入减去学习到的噪音分布从而学习到原始的图像。即,把原始的噪音图像看作为P=\widehat{P}+n,n表示噪音分布,P^表示无噪音图像。通过残差学习加噪之后的图像与原始图像之间的差的分布。

参考:

https://winycg.blog.csdn.net/article/details/86709991

https://blog.csdn.net/qq_34914551/article/details/88088182

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值