ResNet网络

原文地址:

resnet文章

传统思路

在resnet还没有被提出来的时候,通常的思路是通过堆叠更多的层就能实现。但是随着网络深度的增加,就会发现不仅网络模型会变得非常大,而且会暴露出一个问题,即退化问题:随着网络深度的增加,准确率达到饱和,然后迅速下降。作者提出这种下降不是由于过拟合引起的,而且在适当的深度模型上添加更多的层会导致更高的训练误差。

一般认为的是在浅层网络之后堆叠更多的层构成一个深层网络,这个深层网络最差的效果也不能比浅层的网络差,但是得到的结果却恰恰相反。于是作者得出一个结论:不是所有系统都很容易优化,也就是说深层网络不容易被优化。

残差结构的提出:

为了解决作者提出的退化问题,使得网络优化更加容易。
在这里插入图片描述
不希望堆叠几个层直接适合所需的底层映射,而是显式地让这些层适合残差映射。我们如果假设多个非线性层可以渐近逼近复杂函数,那么就相当于假设多个非线性层可以渐近逼近残差函数H(x)−x。形式上,我们将期望的底层映射表示为H(x),让堆叠的非线性层适合F(x)的另一个映射,即H(x)−x。功能快捷键F(x) +x的表达式可以通过具有“快捷连接”的前馈神经网络来实现。快捷连接是指跳过一层或多层的连接。

ResNet 网络

在这里插入图片描述
上式中即可以表示残差模块的计算过程。F函数表示带有BN和非线性激活函数(ReLU)堆叠的两层或三层网络。WsX表示恒等的快捷连接。Ws 主要为了匹配输入向量和输出向量的维度到同一纬度底下。注意:x和F的维度必须相同。
在这里插入图片描述
上图是两层或三层的残差网络架构。

网络架构

从Vgg出发,我们构建一个简单的卷积神经网络。

(i)对于相同的输出特征图尺寸,层具有相同数量的滤波器;
(ii)如果特征图尺寸减半,则滤波器数量加倍,以便保持每层的时间复杂度。我们通过步长为2的卷积层直接执行下采样。

下面的后两个网络以全局平均池化层和具有softmax的1000维全连接层结束。ImageNet的网络架构例子。左:作为参考的VGG-19模型40。中:具有34个参数层的简单网络(36亿FLOPs)。右:具有34个参数层的残差网络(36亿FLOPs)。带点的快捷连接增加了维度。
在这里插入图片描述
基于上述的简单网络,我们插入快捷连接(右),将网络转换为其对应的残差版本。当输入和输出具有相同的维度时(图中的实线快捷连接)时,可以直接使用恒等快捷连接。当维度增加(图中的虚线快捷连接)时,我们考虑两个选项:
A)快捷连接仍然执行恒等映射,额外填充零输入以增加维度。此选项不会引入额外的参数;
B)方程(2)中的投影快捷连接用于匹配维度(由1×1卷积完成)。对于这两个选项,当快捷连接跨越两种尺寸的特征图时,它们执行时步长为2(Fm网格要变小)。
在一般的实现过程中,对于维度的不匹配问题,我们一般都采用第二个解决方案,即通过1×1卷积完成维度匹配问题。

ResNet18和ResNet34 使用的是如下residual模块
# 两层的Residual模块,经过该模块维度不变
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

接下来对BasicBlock进行介绍。
在这里插入图片描述
layer1的结构比较简单,没有downsample。图中方框内便是BasicBlock的主要结构——两个3×3卷积层。每个layer都由若干Block组成,又因为layer1的两个block结构完全相同。
Basic
layer2和layer1就有所不同了,首先64×56×56 的输入进入第1个block的conv1,这个conv1的stride变为2,和layer1不同(图中红圈标注),这是为了降低输入尺寸,减少数据量,输出尺寸为128×28×28。

到第1个block的末尾处,需要在output加上residual,但是输入的尺寸为64×56×56 ,所以在输入和输出之间加一个 1×1 卷积层,stride=2(图中侧边红圈标注),作用是使输入和输出尺寸统一。

由于已经降低了尺寸,第2个block的conv1的stride就设置为1。由于该block没有降低尺寸,residual和输出尺寸相同,所以也没有downsample部分。

layer3和layer4结构和layer2相同,无非就是通道数变多,输出尺寸变小,就不再赘述。

ResNet50和ResNet152 使用的是如下residual模块
# 经过此Residual模块,维度会变化,需要通过1*1卷积进行维度匹配
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 * 4, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(planes * 4)
        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

接下来对Basicblock进行介绍。
和Basicblock不同的一点是,每一个Bottleneck都会在输入和输出之间加上一个卷积层,只不过在layer1中还没有downsample,这点和Basicblock是相同的。至于一定要加上卷积层的原因,就在于Bottleneck的conv3会将输入的通道数扩展成原来的4倍,导致输入一定和输出尺寸不同。
在这里插入图片描述
尺寸为256×56×56 的输入进入layer2的第1个block后,首先要通过conv1将通道数降下来,之后conv2负责将尺寸降低(stride=2,图中从左向右数第2个红圈标注)到输出处,由于尺寸发生变化,需要将输入downsample,同样是通过stride=2的1×1卷积层实现。
之后的3个block(layer2有4个block)就不需要进行downsample了(无论是residual还是输入),从左向右数第3、4个红圈标注,stride均为1。
在这里插入图片描述
layer3和layer4结构和layer2相同,无非就是通道数变多,输出尺寸变小,就不再赘述。

# 整体的ResNet网络结构
class ResNet(nn.Module):

    def __init__(self, block, layers, num_classes=1000):
        self.inplanes = 64
        super(ResNet, self).__init__()
        # 先经过一个普通的卷积模块
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
                               bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        # 下面每一个layer代表一个整体的残差网络模块
        self.layer1 = self._make_layer(block, 64, layers[0])
        # 经过卷积,stride=2 提取道德特征图的维度会减半
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
        self.avgpool = nn.AvgPool2d(7, stride=1)
        self.fc = nn.Linear(512 * block.expansion, num_classes)
		# 初始化参数
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                m.weight.data.normal_(0, math.sqrt(2. / n))
            elif isinstance(m, nn.BatchNorm2d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()

    def _make_layer(self, block, planes, blocks, stride=1):
        downsample = None
        # 快捷连接
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.inplanes, planes * block.expansion,
                          kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(planes * block.expansion),
            )
		# 添加每一个Residual模块
        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample))
        self.inplanes = planes * block.expansion
        # 剩余的Residual模块
        for i in range(1, blocks):
            layers.append(block(self.inplanes, planes))

        return nn.Sequential(*layers)

    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)

        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)

        return x

现在,ResNet网络已经成为处理数据、提取特征的基础性模块

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值