首先,作者抛出了一个问题:
回答这个问题的一个很大的障碍就是众所周知的梯度消失/梯度爆炸,它从一开始就阻止了收敛。现在这个问题主要是通过初始归一化和中间层归一化来解决,可以使数十层的网络收敛。
但是当更深的网络可以收敛的时候,存在一个退化的问题:
这种退化不是过拟合的原因。
针对这种退化的原因,作者想出了一个构造更深模型的方法:
这种构造方式不会使深层网络比浅层网络有更大的训练误差。
让网络学习H(x)-x而不是学习H(x)
这种方式的优势在于:
恒等映射既不增加额外的参数,也不增加运算复杂度。
作者发现:
残差块定义如下:
但是上式只能用于F和x可以直接相加的情况(维度相同),还有一个更普适的式子:
Ws可以用来对x进行线性变换从而匹配维度。
恒等映射(式1)足以解决问题,并且是最经济的,Ws仅在匹配维度时会使用。
F的形式是灵活的,本实验中采用的是2层和3层的,更多层也可以;但如果是1层的,就类似于线性变换层。
在输入和输出具有相同维度的时候,shortcut可以直接使用(图中实线部分);
当维度增加时,有两种情况(图中虚线部分):
当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官方实现残差快的代码,简单易懂。
第一种残差块如上图所示,基础版,由两个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
与基础版的不同之处只在于这里是三个卷积,分别是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