深度学习之Resnet

深度学习入门小菜鸟,希望像做笔记记录自己学的东西,也希望能帮助到同样入门的人,更希望大佬们帮忙纠错啦~侵权立删。目录文章目录一、提出原因1、堆叠网络造成的问题2、解决深度网络的退化问题二、残差结构三、Resnet网络结构1.原理分析2、结构分析3、代码分析(内含分析和注释)一、提出原因1、堆叠网络造成的问题传统的想法是如果我们堆叠很多很多层,或许能让网络变得更好。然而现实却是:堆叠网络后网络难以收敛,而且梯度爆炸(梯度消失)在一开始就阻碍网络…

深度学习入门小菜鸟,希望像做笔记记录自己学的东西,也希望能帮助到同样入门的人,更希望大佬们帮忙纠错啦~侵权立删。

目录

文章目录

一、提出原因 

1、堆叠网络造成的问题

2、解决深度网络的退化问题

二、残差结构

三、Resnet网络结构

1.原理分析

 2、结构分析

3、代码分析(内含分析和注释)

一、提出原因 

1、堆叠网络造成的问题

传统的想法是如果我们堆叠很多很多层,或许能让网络变得更好。

然而现实却是:堆叠网络后网络难以收敛,而且梯度爆炸(梯度消失)在一开始就阻碍网络的收敛,让网络难以训练,得到适当的参数。

2、解决深度网络的退化问题

理论上来讲,增加网络的层数,网络可以进行更加复杂的特征提取,可以取得更好的结果。但是实验发现深度网络出现了退化问题,如下图所示(图片来源:Resnet的论文)。网络深度增加时,网络准确度出现饱和,之后甚至还快速下降。而且这种下降不是因为过拟合引起的,而是因为在适当的深度模型上添加更多的层会导致了更高的训练误差,从而使其下降。

深度学习之Resnet

二、残差结构

为了解决以上两个问题,残差结构应运而生。

原理图如图所示(图片来源:Resnet的论文):它的结构很像电路中的短路,因此被称为短路连接(shortcut)。

深度学习之Resnet

 对于这整个堆叠结构,当输入x时,它学习到的特征记为H(x),因此我们希望他学习到的残差为F(x)= H(x)- x;那么我们最初学习到的特征就是H(x)= F(x)+ x。这样做的目的是:让经过堆积结构学到的(F(x))和最原始的x进行联合获取特征。残差学习相比原始特征直接学习更容易。取一个最极端的结果来看,就是F(x)= 0(堆积结构啥也没学到),这里堆积层仅仅做了恒等映射,至少网络性能不会下降,实际上残差不会为0,这也会使得堆积层在输入特征基础上学习到新的特征,从而拥有更好的性能。并且将残差置为零比通过一堆非线性层来拟合恒等映射更容易。

三、Resnet网络结构

1.原理分析

 ResNet网络是在VGG19网络的基础上进行修改的,并且通过短路机制加入了残差单元。

设计规则:

(1)对于相同的输出特征图尺寸,层具有相同数量的滤波器

(2)当feature map大小降低一半时,feature map的数量增加一倍【过滤器(可以看作是卷积核的集合)的数量增加一倍】,这保持了网络层的复杂度。然后通过步长为2的卷积层直接执行下采样。

网络结构具体如下图所示(左为VGG-19,中为34个参数层的简单网络,右为34个参数层的残差网络):

深度学习之Resnet

ResNet相比普通网络每两层间增加了短路机制,这就形成了残差学习,其中实线表示快捷连接,虚线表示feature map数量发生了改变。

有两种情况需要考虑:

(1)输入和输出具有相同的维度时(对应实线部分):

直接使用恒等快捷连接

深度学习之Resnet

(2)维度增加(当快捷连接跨越两种尺寸的特征图时,它们执行时步长为2):

①快捷连接仍然执行恒等映射,额外填充零输入以增加维度。这样就不会引入额外的参数。

②用下面公式的投影快捷连接用于匹配维度(由1×1卷积完成)

深度学习之Resnet

 Resnet网络结构的更多细节如下图所示:

深度学习之Resnet

2、结构分析

Resnet结构分层为:Layer–Block–Stage–Network

(1)Layer是最小的结构,ResNet-18就代表网络有18层。

(2)Block是由两层或者三层conv层堆叠形成的。一般来说,50层以下用下图左侧的双层block,50层以上用下图右侧的三层block,其中右侧的这个block叫做BottleNeck(瓶颈结构)。

深度学习之Resnet

(3)stage 是由数个Block堆叠形成的。如下图黄圈圈(3个Block组成)所示:

深度学习之Resnet

 (4)以Resnet-50为例,如上图,该网络是由stage之前的两层(conv7x7, maxpooling)和4个stage(共48层)组成的。

3、代码分析(内含分析和注释)

(1)网络总括

class ResNet(nn.Module):
    def __init__(self,block, layers, num_class=1000, norm_layer=None):
        super(ResNet, self).__init__()
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        self._norm_layer = norm_layer

        self.inplanes = 64

        #对应第一层conv1
        self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride = 2, padding = 3, bias=False)#7*7卷积
        self.bn1 = norm_layer(self.inplanes)#正态分布(归一化操作)

        self.relu = nn.ReLU(inplace=True)#激活

        #对应第二层
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2)#3*3最大池化池化

        # stages部分,分别第1,2,3,4个stage
        self.layer1 = self._make_layer(block, 64, layers[0])
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)

        #对应最后一层
        self.gap = nn.AdaptiveAvgPool2d((1,1))#平均池化

        self.linear = nn.Linear(512*block.expansion, num_class)#线性

        for m in self.modules():
            if isinstance(m, nn.Conv2d): #isinstance()函数来判断一个对象是否是一个已知的类型,类似 type(),其实就是没有正态分布的话咱就kaiming均匀分布。
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')#kaiming均匀分布
            elif isinstance(m, nn.BatchNorm2d):#是正态分布
                nn.init.constant_(m.weight, 1)#torch.nn.init.constant(tensor, val),用val的值填充输入的张量或变量,即变成全1分布
                nn.init.constant_(m.bias, 0)#偏置变为0
    def forward(self,x):
        #第一层
        out = self.conv1(x)
        out = self.bn1(out)
        #第二层
        out = self.maxpool(out)
        #4个stages
        out = self.layer1(out)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)

        out = self.gap(out)
        out = nn.Flatten(out)#推平
        out = self.linear(out)

        return out

(2)内含函数(将多个block拼接成一个stage)

def _make_layer(self, block, planes, blocks, stride = 1):

    #将多个block拼接成一个stage,实现block之间的衔接;block指前面定义的BottleNeck或BasicBlock;planes指进行变换的通道数;blocks指这个stage中block的数目;stride:filter在原图上扫描时,需要跳跃的格数

        norm_layer = self._norm_layer
        downsample = None
        if stride !=1 or self.inplanes != block.expansion *planes:
        #形状不匹配的两种情况:(1)通道数目不匹配,(2)stride导致的feature map的尺寸不匹配
        
            downsample= nn.Sequential(
            conv1x1(self.inplanes, block.expansion *planes, stride = stride),
            norm_layer(block.expansion *planes)
                                )# self.inplanes是block的输入通道数,planes是做3x3卷积的空间的通道数,expansion是残差结构中输出维度是输入维度的多少倍,同一个stage内,第一个block,inplanes=planes, 输出为planes*block.expansion
        # 第二个block开始,该block的inplanes等于上一层的输出的通道数planes*block.expansion(类似于卷积后的结果进入下一个卷积时,前一个卷积得到的output的输出为下一个卷积的input)

        layers = []
        layers.append(block(self.inplanes, blocks[0], stride=stride, downsample=downsample, norm_layer=norm_layer))
        #在一个stage中,只对stage中的第一个block中使用下采样(stride!=1),其他的block都不影响feature map的维度的
        self.inplanes = planes * self.expansion
        for i in range(1, blocks):
            layers.append(block(self.inplanes, blocks[0], stride=stride, norm_layer=norm_layer))#这里就没有downsample了

        return nn.Sequential(*layers)# 变量名前加星号表示Unpack解包

版权声明:本文为博主tt丫原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/weixin_55073640/article/details/122552814

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2022年1月18日 下午9:07
下一篇 2022年1月18日 下午9:26

相关推荐