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

Posted UQI-LIUWJ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了机器学习笔记:ResNet 及残差连接相关的知识,希望对你有一定的参考价值。

1 为什么会有ResNet?

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

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

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

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

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

2 ResNet原理

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

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

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

        那怎么构建恒等映射呢?

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

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

        ResNet作者提出,学习残差F(x)=H(x)-x会比直接学习原始特征H(x)简单的多。

 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的重复堆叠次数。 

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

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

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

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

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

 

 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卷积有什么作用:

  • 对通道数进行升维和降维(跨通道信息整合),实现了多个特征图的线性组合,同时保持了原有的特征图大小;
  • 相比于其他尺寸的卷积核,可以极大地降低运算复杂度;
  • 如果使用两个3x3卷积堆叠,只有一个relu,但使用1x1卷积就会有两个relu,引入了更多的非线性映射;

 

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

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

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

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

 

以上是关于机器学习笔记:ResNet 及残差连接的主要内容,如果未能解决你的问题,请参考以下文章

论文笔记系列:主干网络-- ResNet

论文笔记系列:主干网络-- ResNet

论文笔记系列:经典主干网络-- ResNet

残差网络ResNet笔记

NTU 课程 7454 CNN进阶

从零学习PyTorch 如何残差网络resnet作为pre-model +代码讲解+残差网络resnet是个啥