机器学习笔记:ResNet 及残差连接

1 为什么会有ResNet?

       自从深度神经网络在ImageNet大放异彩之后,后来问世的深度神经网络就朝着网络层数越来越深的方向发展。直觉上我们不难得出结论:增加网络深度后,网络可以进行更加复杂的特征提取,因此更深的模型可以取得更好的结果。【不同层可以获得不同的视觉特征】

        但事实并非如此,人们发现随着网络深度的增加,模型精度并不总是提升,并且这个问题显然不是由过拟合(overfitting)造成的,因为网络加深后不仅测试误差变高了,它的训练误差竟然也变高了。【注:过拟合应该是训练误差很小,但是测试误差很大;这里训练误差都很大,说明不是过拟合的问题】

 

        这可能是因为更深的网络会伴随梯度消失/爆炸问题,从而阻碍网络的收敛。这种加深网络深度但网络性能却下降的现象被称为退化问题(degradation problem)。

        从上图我们可以看出,当传统神经网络的层数从20增加为56时,网络的训练误差和测试误差均出现了明显的增长,也就是说,网络的性能随着深度的增加出现了明显的退化。

        ResNet就是为了解决这种退化问题而诞生的。

2 ResNet原理

        随着网络层数的增加,梯度爆炸和梯度消失问题严重制约了神经网络的性能,研究人员通过提出包括Batch normalization在内的方法,已经在一定程度上缓解了这个问题,但依然不足以满足需求。

2.1 恒等映射(Identity mapping)

        ResNet提出了使用恒等映射(Identity mapping)来解决这个问题。

        问题解决的标志是:增加网络层数,但训练误差不增加。

        那怎么构建恒等映射呢?

        简单地说,原先的网络输入x,希望输出H(x)。

        现在我们改一改,我们令H(x)=F(x)+x,那么我们的网络就只需要学习输出一个残差F(x)=H(x)-x。

        ResNet作者提出,学习残差F(x)=H(x)-x会比直接学习原始特征H(x)简单的多。【同时相比于没有残差的网络,ResNer没有增加多少复杂度和计算量】

2.2 skip connection 

 

 3 ResNet(各变体)网络结构

        ResNet有很多变体,比较著名的有五种主要形式:Res18,Res34,Res50,Res101,Res152。

        

        如上图所示,ResNet及其变体主要包括三个主要部分:输入部分(input)+中间卷积(stage1~stage4)+输出部分(output)

3.1 网络整体结构

        以ResNet18为例: 

class ResNet(nn.Module):
    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)
        #输入部分

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        #卷积部分
        #ResNet18和其他的ResNet网络的区别主要就在这四层里面

        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        #输出部分

        return x

# 生成一个res18网络(看是否有预训练的参数)
def resnet18(pretrained=False, **kwargs):
    model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
    if pretrained:
        model.load_state_dict(model_zoo.load_url(model_urls['resnet18']))
    return model

 3.2 网络输入部分

        self.conv1 = nn.Conv2d(
            3, 
            64, 
            kernel_size=7, 
            stride=2, 
            padding=3, 
            bias=False)
'''
size=7*7,stride=2的卷积核
'''
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(
            kernel_size=3, 
            stride=2, 
            padding=1
'''
size=3x3, stride=2的最大池化
'''
            )

pytorch 笔记:torch.nn.Conv2d_UQI-LIUWJ的博客-CSDN博客pytorch笔记:torch.nn.MaxPool2d_UQI-LIUWJ的博客-CSDN博客中,我们说到,经过Conv2d和MaxPool2d之后的channel数为:

        在ResNet中,输入部分是一个224x224的图像,经过输入部分之后,变成了56x56大小的特征图,极大减少了存储所需大小。

 

3.3 中间卷积部分

        中间卷积部分主要是下图中的蓝框部分,通过3*3卷积的堆叠来实现信息的提取。红框中的[2, 2, 2, 2]和[3, 4, 6, 3]等则代表了bolck的重复堆叠次数。 

preview

——>每一层卷积层:减一半feature map的长和宽,增加输出channel一倍

——>第一个卷积层(conv1——,又被称为”stem“)

         红框前面的是卷积核的大小,以及输出的channel数

        刚刚我们调用的resnet18( )函数中有一句

model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)

        这里的[2, 2, 2, 2]与图中红框是一致的,如果你将这行代码改为 ResNet(BasicBlock, [3, 4, 6, 3], **kwargs), 那你就会得到一个res34网络。

3.3.1 FLOPs

机器学习笔记:FLOPs_UQI-LIUWJ的博客-CSDN博客_flop 机器学习 

不难发现Resnet 34的复杂度是Resnet18的一倍;后面Resnet 50,Resnet 101,Resnet 152也是接近倍数关系,但是Resnet 50和Resnet 34 之间的运算复杂度就很接近,这个是因为Resnet 50开始采用了bottleneck 技术(这个在后文中会说)

3.4  残差块

        下面我们来具体看一下一个残差块是怎么实现的。

        如下图所示的basic-block,输入数据分成两条路,一条路经过两个3*3卷积,另一条路直接短接,二者相加经过relu输出,十分简单。

class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        self.conv1 = conv3x3(
            inplanes, 
            planes, 
            stride)
        self.bn1 = nn.BatchNorm2d(planes)
        self.relu = nn.ReLU(inplace=True)

        self.conv2 = conv3x3(
            planes, 
            planes)
        self.bn2 = nn.BatchNorm2d(planes)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        identity = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        if self.downsample is not None:
            identity = self.downsample(x)

        out += identity
        out = self.relu(out)

        return out

preview

 3.5 输出部分

         网络输出部分很简单,通过全局自适应平滑池化,把所有的特征图拉成1*1。

        对于res18来说,就是1x512x7x7 的输入数据拉成 1x512x1x1,然后接全连接层输出,输出节点个数与预测类别个数一致。

        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512 * block.expansion, num_classes)

4 bottleneck 结构 

        ResNet50起,就采用Bottleneck结构,主要是引入1x1卷积。

        我们来看一下这里的1x1卷积有什么作用:

  • 对通道数进行升维和降维(跨通道信息整合),实现了多个特征图的线性组合,同时保持了原有的特征图大小;【1*1卷积不会影响空间的尺寸,它只影响channel的数量】
  • 相比于其他尺寸的卷积核,可以极大地降低运算复杂度;【CNN的FLOPs为k*k*c*H*W*o,所以如果不用bottleneck结构的话,当输入和输出的channel都变成4倍的时候,FLOPs变成16倍 机器学习笔记:FLOPs_UQI-LIUWJ的博客-CSDN博客_flop 机器学习
  • 如果使用两个3x3卷积堆叠,只有一个relu,但使用1x1卷积就会有两个relu,引入了更多的非线性映射;

 

        我们来计算一下1*1卷积的计算量优势:首先看上图右边的bottleneck结构,对于256维的输入特征,参数数目:1x1x256x64+3x3x64x64+1x1x64x256=69632.

        如果同样的输入输出维度但不使用1x1卷积,而使用两个3x3卷积的话,参数数目为(3x3x256x256)x2=1179648。

        简单计算下就知道了,使用了1x1卷积的bottleneck将计算量简化为原有的5.9%,收益超高。

 参考文献:ResNet及其变种的结构梳理、有效性分析与代码解读 - 知乎 (zhihu.com)

5 Batch-normalization和Relu,孰先孰后?

这个不确定,这里说一下ResNet中先BN再Relu的优点吧

先BN的话,会有一半左右的输出小于0,那么经过Relu之后的结果就是0,网络可以剪掉很多枝

6 实验效果

 

发现加了残差链接之后,越深的网络确实效果越好了

这里的“陡降”指代的是学习率乘了0.1

Top-1 error指的是得分最高的类别不是正确类别的概率,可以看到加了残差之后效果确实也是变好了的

 

越深效果越好。

Top-5 error指的是得分最高的五个类别不含有正确类别的概率

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UQI-LIUWJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值