https://zhuanlan.zhihu.com/p/349717627
ResNet与残差块
深度卷积网络的瓶颈:
理论上,增加网络层数后,网络可以进行更加复杂的特征模式的提取,所以当模型更深时可以取得更好的结果。但VGG、GoogLeNet等网络单纯增加层数遇到了一些瓶颈:简单增加卷积层,训练误差不但没有降低,反而越来越高。在CIFAR-10、ImageNet等数据集上,单纯叠加3×3卷积,何恺明等[1]人发现,训练和测试误差都变大了。这主要是因为深层网络存在着梯度消失或者爆炸的问题,模型层数越多,越难训练。
残差块:
但是神经网络的ReLU激活函数恰恰不能保证“什么都不学习”。残差网络的初衷就是尽量让模型结构有“什么都不做”的能力,这样就不会因为网络层数的叠加导致梯度消失或爆炸。
现有H(x) = F(x)+x
, 只要F(x)=0
,那么H(x)=x
,H(x)就是恒等映射,也就是有了“什么都不做”的能力。ResNet基于这一思想提出了一种残差网络的结构,其中输入x
可以传递到输出,传递的过程被称为ShortCut
。
同时,下图里有两个权重层,即F(x)部分。假如“什么都不学习”是最优的,或者说H(x)=x
是最优的,那么理论上来说,F(x)学习到的目标值为0即可;如果H(x)=x
不是最优,那么基于神经网络强大的学习能力,F(x)可以尽可能去拟合我们期望的值。
BasicBlock
ResNet中使用的一种网络结构,在resnet18和resnet34中使用了BasicBlock:
输入输出通道数均为64,残差基础块中两个3×3卷积层参数量是:
BasicBlock类中计算了残差,该类继承了nn.Module。
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
bottleNeck
ResNet-34核心部分均使用3×3卷积层,总层数相对没那么多,对于更深的网络,作者们提出了另一种残差基础块。(在resnet50、resnet101、resnet152使用了Bottlenect
构造网络.)
Bottleneck Block
中使用了1×1卷积层。如输入通道数为256,1×1卷积层会将通道数先降为64,经过3×3卷积层后,再将通道数升为256。1×1卷积层的优势是在更深的网络中,用较小的参数量处理通道数很大的输入。
在Bottleneck Block
中,输入输出通道数均为256,残差基础块中的参数量是:
与BasicBlock
比较,使用1×1卷积层,参数量减少了。当然,使用这样的设计,也是因为更深的网络对显存和算力都有更高的要求,在算力有限的情况下,深层网络中的残差基础块应该减少算力消耗。
代码:
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(Bottleneck, self).__init__()
self.conv1 = conv1x1(inplanes, planes)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = conv3x3(planes, planes, stride)
self.bn2 = nn.BatchNorm2d(planes)
self.conv3 = conv1x1(planes, planes * self.expansion)
self.bn3 = nn.BatchNorm2d(planes * self.expansion)
self.relu = nn.ReLU(inplace=True)
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)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = self.relu(out)
return out