【文献阅读及pytorch实践】ResNet:Deep Residual Learning for Image Recognition

第一部分.文献阅读

首先,作者抛出了一个问题:

学习更好的网络就像堆叠更多的层那么简单吗?

回答这个问题的一个很大的障碍就是众所周知的梯度消失/梯度爆炸,它从一开始就阻止了收敛。现在这个问题主要是通过初始归一化和中间层归一化来解决,可以使数十层的网络收敛。

但是当更深的网络可以收敛的时候,存在一个退化的问题:

当网络深度逐渐增加时,精度达到饱和,然后开始退化(目前还不是很懂这个问题的本质原因)

这种退化不是过拟合的原因。

针对这种退化的原因,作者想出了一个构造更深模型的方法:

在浅层网络的基础上构造恒等映射层

这种构造方式不会使深层网络比浅层网络有更大的训练误差。
在这里插入图片描述
让网络学习H(x)-x而不是学习H(x)

这种方式的优势在于:

一个极端的情况,假设当前精度已饱和,进行恒等映射是最优的,那么将残差学习为0比堆叠一堆层去学习恒等映射要容易

恒等映射既不增加额外的参数,也不增加运算复杂度。

作者发现:

1.深度残差网络很容易去优化,但是与之对应的“朴素”网络(仅堆叠层)在深度增加的时候有更高的训练误差
2.深度残差网络可以简单的从大幅增加的网络深度中获取更高的精度,比之前的网络都好

在这里插入图片描述
残差块定义如下:
在这里插入图片描述
但是上式只能用于F和x可以直接相加的情况(维度相同),还有一个更普适的式子:
在这里插入图片描述
Ws可以用来对x进行线性变换从而匹配维度。
恒等映射(式1)足以解决问题,并且是最经济的,Ws仅在匹配维度时会使用。

在这里插入图片描述
F的形式是灵活的,本实验中采用的是2层和3层的,更多层也可以;但如果是1层的,就类似于线性变换层。

在这里插入图片描述
在输入和输出具有相同维度的时候,shortcut可以直接使用(图中实线部分);
当维度增加时,有两种情况(图中虚线部分):

1.依旧为恒等映射,额外的维度用0补充,此方法不引入额外的参数;
2.通过1*1卷积来匹配维度;

当shortcut跨越两种不同尺寸的feature map时,步长设为2。

具体实验细节如下:

训练:

输入为224*224的图片,从图片和他的镜像中随机采样,并且每个像素减去均值;
使用标准颜色增强;
每次卷积后都做一次BN;
batch size设为256;
lr从0.1开始,当错误率停滞时,lr/10,最终训练了600000轮;
weight decay=10,momentum=0.9,不使用dropout;

测试:

使用10-crop测试(详见AlexNet)

第二部分.pytorch实践

这里主要看了pytorch官方实现残差快的代码,简单易懂。
在这里插入图片描述
第一种残差块如上图所示,基础版,由两个3 * 3的卷积组成。

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):
        residual = 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:
            residual = self.downsample(x)
 
        out += residual
        out = self.relu(out)
 
        return out
expansion是残差结构中输出维度是输入维度的多少倍,BasicBlock没有升维,所以expansion = 1
残差结构是在求和之后才经过ReLU层

在这里插入图片描述
与基础版的不同之处只在于这里是三个卷积,分别是1x1,3x3,1x1,分别用来压缩维度,卷积处理,恢复维度,接着就是网络主体了。

class Bottleneck(nn.Module):
    expansion = 4
 
    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
                               padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)
        self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(planes * self.expansion)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.stride = stride
 
    def forward(self, x):
        residual = x
 
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
 
        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)
 
        out = self.conv3(out)
        out = self.bn3(out)
 
        if self.downsample is not None:
            residual = self.downsample(x)
 
        out += residual
        out = self.relu(out)
 
        return out
inplane是输入的通道数,plane是输出的通道数,在basic中expansion是1,此时完全忽略expansion这个东西,输出的通道数就是plane,然而bottleneck就是不走寻常路,它的任务就是要对通道数进行压缩,再放大,于是,plane不再代表输出的通道数,而是block内部压缩后的通道数,输出通道数变为plane * expansion。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值