李沐深度学习笔记———NiN,GoogLeNet,ResNet

李沐深度学习笔记———NiN,GoogLeNet,ResNet


前言

本文为jjw对李沐大神所讲NiN,GoogLeNet,ResNet网络的理解以及笔记。


一、NiN网络

1.前言

由于全连接层所需的参数个数太多,花销太大。所以有人提出使用1×1的卷积网络来替换全连接层,这样就引入了NiN网络。

2.网络架构图

在这里插入图片描述
首先NiN网络是由4个NiN块组成的,每个块由一个卷积和两个1×1的卷积组成(将其通道数扩大),然后经过一个MaxPool层(将其宽高减半),最后接一个AdaptiveAvgPool2d层将每个通道求平均数,按通道的维度累加在一起。有多少类别就输出多少通道数。

3.代码实现

1.NiN块的实现

def nin_block(in_channels, out_channels, kernel_size, strides, padding):
    return nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding),
nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU())

2.NiN 网络的实现

net = nn.Sequential(
nin_block(1, 96, kernel_size=11, strides=4, padding=0),
nn.MaxPool2d(3, stride=2),
nin_block(96, 256, kernel_size=5, strides=1, padding=2),
nn.MaxPool2d(3, stride=2),
nin_block(256, 384, kernel_size=3, strides=1, padding=1),
nn.MaxPool2d(3, stride=2),
nn.Dropout(0.5),
# 标签类别数是10
nin_block(384, 10, kernel_size=3, strides=1, padding=1),
nn.AdaptiveAvgPool2d((1, 1)),
# 这个层将原来的输出 转变为 1*通道数*1*1的形状
# 将四维的输出转成二维的输出,其形状为(批量大小,10)
nn.Flatten())

4.总结

NiN使用由一个卷积层和多个1×1卷积层组成的块。该块可以在卷积神经网络中使用,以允许更多的每像素非线性。

NiN去除了容易造成过拟合的全连接层,将它们替换为全局平均汇聚层(即在所有位置上进行求和)。该汇聚层通道数量为所需的输出数量(例如,Fashion-MNIST的输出为10)。

移除全连接层可减少过拟合,同时显著减少NiN的参数。

NiN的设计影响了许多后续卷积神经网络的设计。

二、GoogLeNet

1.前言

在2014年的ImageNet图像识别挑战赛中,一个名叫GoogLeNet的网络架构大放异彩。 GoogLeNet吸收了NiN中串联网络的思想,并在此基础上做了改进。 这篇论文的一个重点是解决了什么样大小的卷积核最合适的问题。

2.网络架构图

1.卷积块 Inception块

在这里插入图片描述
一个卷积块包含了四个分支。

  1. 直接使用1×1卷积将其通道数降低
  2. 先经过一个1×1的卷积将其通道数降低,然后经过3×3的卷积去学习特征
  3. 先经过一个1×1的卷积将其通道数降低,然后经过5×5的卷积去学习特征
  4. 先使用一个MaxPool层,再使用1×1的卷积
    值得一提的是,这个Inception块并没有改变输入数据的高宽,最后我们将每条线路的输出在通道维度上连结,并构成Inception块的输出。

2.网络整体模型

在这里插入图片描述
它用了7个Inception块,输出完跟一个MaxPool层减少高宽。

3.代码实现

1.Inception块

class Inception(nn.Module):
    # c1--c4是每条路径的输出通道数
    def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
        super(Inception, self).__init__(**kwargs)
        # 线路1,单1x1卷积层
        self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
        # 线路2,1x1卷积层后接3x3卷积层
        self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
        self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
        # 线路3,1x1卷积层后接5x5卷积层
        self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
        self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
        # 线路4,3x3最大汇聚层后接1x1卷积层
        self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
        self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)

    def forward(self, x):
        p1 = F.relu(self.p1_1(x))
        p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
        p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
        p4 = F.relu(self.p4_2(self.p4_1(x)))
        # 在通道维度上连结输出
        return torch.cat((p1, p2, p3, p4), dim=1)

最后将其按通道的维度叠加起来,使用torch.cat

2.完整网络

b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
                   nn.ReLU(),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
                   nn.ReLU(),
                   nn.Conv2d(64, 192, kernel_size=3, padding=1),
                   nn.ReLU(),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32),
                   Inception(256, 128, (128, 192), (32, 96), 64),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64),
                   Inception(512, 160, (112, 224), (24, 64), 64),
                   Inception(512, 128, (128, 256), (24, 64), 64),
                   Inception(512, 112, (144, 288), (32, 64), 64),
                   Inception(528, 256, (160, 320), (32, 128), 128),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
                   Inception(832, 384, (192, 384), (48, 128), 128),
                   nn.AdaptiveAvgPool2d((1,1)),
                   nn.Flatten())

net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))

其中b3,b4,b5,分别就是使用了Inception的三个块

4.总结

Inception块相当于一个有4条路径的子网络。它通过不同窗口形状的卷积层和最大汇聚层来并行抽取信息,并使用
卷积层减少每像素级别上的通道维数从而降低模型复杂度。

GoogLeNet将多个设计精细的Inception块与其他层(卷积层、全连接层)串联起来。其中Inception块的通道数分配之比是在ImageNet数据集上通过大量的实验得来的。

GoogLeNet和它的后继者们一度是ImageNet上最有效的模型之一:它以较低的计算复杂度提供了类似的测试精度。

但是这种方法可以会让模型学到一些杂质。

三、ResNet

1.前言

由于网络层越深,它越容易学到一些其他的东西(不利于精度)。
所以为了使每次加深网络不至于让其学歪,何恺明等人提出了残差网络(ResNet)。 残差网络的核心思想是:每个附加层都应该更容易地包含原始函数作为其元素之一。

2.网络架构图

1.残差快

在这里插入图片描述
让输出的F(X) = F(X) +X
这样使梯度传播的更块,更大了。而且不至于让网络学歪。
在这里插入图片描述
两种残差块,一种让其直接+x,另一种让输入经过1×1的卷积后再相加。

2.完整网络

在这里插入图片描述
其类似于GoogLeNet,只不过是每个Inception块换为了残差块,并且每个残差块加入了批量归一化层。

3.代码实现

1.残差块

注意这里的1×1卷积是用于X的,所以conv3的第一个参数为input_channels

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)
        
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

这里的first_block表示是不是为第一个使用残差块的 块。
然后除了第一个大块的其他块 每一个块的第一个残差块,都要使用对x使用1*1卷积并且让其strides=2,使高宽减半。

2.完整网络

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))
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))
net = nn.Sequential(b1, b2, b3, b4, b5,
                    nn.AdaptiveAvgPool2d((1,1)),
                    nn.Flatten(), nn.Linear(512, 10))

注意对b2进行了特殊处理,这里b1已经让宽高减半了,我们不希望降的那么狠,所以将first_block置为True,使其不需要降高宽,不使用1×1卷积。

4.总结

残差映射可以更容易地学习同一函数,例如将权重层中的参数近似为零。

利用残差块(residual blocks)可以训练出一个有效的深层神经网络:输入可以通过层间的残余连接更快地向前传播。

残差网络(ResNet)对随后的深层神经网络设计产生了深远影响。

总结

以上就是今天要分享的内容,本人研0小白,有问题还望各位指正。
本文所有图片和代码都来自李沐-深度学习。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值