动手学深度学习_ResNet

        残差网络最主要的特征就是残差块,残差块用一个公式就可以清楚的表达:

f(x)=x+g(x) 。

        为什么要设计成这样的函数呢?下面的例子将为你解答这一问题

        假设有一类特定的神经网络架构 F,它包括学习速率和其他超参数设置。 对于所有 f\in F,存在一些参数集(例如权重和偏置),这些参数可以通过在合适的数据集上进行训练而获得。 现在假设 f\ast 是我们真正想要找到的函数,如果是 f\ast \in F,那我们可以轻而易举的训练得到它,但通常我们不会那么幸运。 相反,我们将尝试找到一个函数 f_{F} \ast,这是我们在F中的最佳选择。 例如,给定一个具有X特性和y标签的数据集,我们可以尝试通过解决以下优化问题来找到它:  

f_{F}\ast := arg \ \underset{f}{min} L(X,y,f)\ subject \ to \ f \in F

        在构建 f 的时候,我们的 F 通常会有这么两种情况:嵌套函数类和非嵌套函数类。

        可以看到,非嵌套函数类中,虽然 F_{1->6} 拟合能力在不断增强,但是和 f\ast 的距离并不是越来越近。相反,如果采用嵌套类函数,再原有函数基础上不断增加拟合能力,总是可以向 f\ast 逼近的。

        针对这一问题,何恺明等人提出了残差网络(ResNet)。 它在2015年的ImageNet图像识别挑战赛夺魁,并深刻影响了后来的深度神经网络的设计。 残差网络的核心思想是:每个附加层都应该更容易地包含原始函数作为其元素之一。 于是,残差块(residual blocks)便诞生了,这个设计对如何建立深层神经网络产生了深远的影响。 

残差块 residual blocks

        图左是一个正常块,图右是一个残差块,通过从输入引出一条路径直接和正常块相加就实现了残差结构。相比于正常块直接通过卷积拟合 f(x) ,残差块的卷积部分只需要拟合 f(x)-x ,即可。这样网络结构更容易优化,输入通过这么一条跳跃链接也更容易向前传播(其实反向传播也更优化了,我们之后再说)

        我们具体来看一下 ResNet 中的残差块长什么样子:ResNet 沿用了 VGG 完整的 3 × 3 卷积层设计。 残差块里首先有 2 个有相同输出通道数的 3 × 3 卷积层。 每个卷积层后接一个 Batch Normalization 和 ReLU 激活函数。 然后我们通过跨层数据通路,跳过这 2 个卷积运算,将输入直接加在最后的 ReLU 激活函数前。 这样的设计要求 2 个卷积层的输出与输入形状一样,从而使它们可以相加。 如果想改变通道数,就需要引入一个额外的 1 × 1 卷积层来将输入变换成需要的形状后再做相加运算。如下图。

class Residual(nn.Module):  #@save
    def __init__(self, input_channels, num_channels,
                 use_1x1conv=False, strides=1):
        super().__init__()
        self.conv1 = nn.Conv2d(input_channels, num_channels,
                               kernel_size=3, padding=1, stride=strides)
        self.conv2 = nn.Conv2d(num_channels, num_channels,
                               kernel_size=3, padding=1)
        if use_1x1conv:
            self.conv3 = nn.Conv2d(input_channels, num_channels,
                                   kernel_size=1, stride=strides)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(num_channels)
        self.bn2 = nn.BatchNorm2d(num_channels)

    def forward(self, X):
        Y = F.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        Y += X
        return F.relu(Y)

 ResNet 结构

        ResNet-18为例:

        ResNet 的前两层跟之前介绍的 GoogLeNet 中的一样: 在输出通道数为 64 、步长为 2 的   7 × 7卷积层后,接步长为 2 的 3 × 3 的maxpooling 。不同之处在于 ResNet 每个卷积层后增加了Batch Normalization。

b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
                   nn.BatchNorm2d(64), nn.ReLU(),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

        ResNet 则使用 4 个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。 第一个模块的通道数同输入通道数一致。 由于之前已经使用了步幅为 2 的maxpooling,所以无须减小高和宽。 之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。(这里的 4 我画在上图)

def resnet_block(input_channels, num_channels, num_residuals,
                 first_block=False):
    blk = []
    for i in range(num_residuals):
        if i == 0 and not first_block:
            blk.append(Residual(input_channels, num_channels,
                                use_1x1conv=True, strides=2))
        else:
            blk.append(Residual(num_channels, num_channels))
    return blk

b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2))
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))

最后与 GoogLeNet 一样,在 ResNet 中加入全局平均池化层,以及全连接层输出。

net = nn.Sequential(b1, b2, b3, b4, b5,
                    nn.AdaptiveAvgPool2d((1,1)),
                    nn.Flatten(), nn.Linear(512, 10))

ResNet 为什么可以训练非常深的网络

另外在进行前向传播或者反向传播时:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值