以下内容为结合李沐老师的课程和教材补充的学习笔记,以及对课后练习的一些思考,自留回顾,也供同学之人交流参考。
本节课程地址:26 网络中的网络 NiN【动手学深度学习v2】_哔哩哔哩_bilibili
本节教材地址:7.3. 网络中的网络(NiN) — 动手学深度学习 2.0.0 documentation (d2l.ai)
本节开源代码:...>d2l-zh>pytorch>chapter_multilayer-perceptrons>nin.ipynb
网络中的网络(NiN)
LeNet、AlexNet和VGG都有一个共同的设计模式:通过一系列的卷积层与汇聚层来提取空间结构特征;然后通过全连接层对特征的表征进行处理。 AlexNet和VGG对LeNet的改进主要在于如何扩大和加深这两个模块。 或者,可以想象在这个过程的早期使用全连接层。然而,如果使用了全连接层,可能会完全放弃表征的空间结构。 网络中的网络(NiN)提供了一个非常简单的解决方案:在每个像素的通道上分别使用多层感知机 (href="https://zh.d2l.ai/chapter_references/zreferences.html#id93">Linet al., 2013)。
(NiN块)
回想一下,卷积层的输入和输出由四维张量组成,张量的每个轴分别对应样本、通道、高度和宽度。 另外,全连接层的输入和输出通常是分别对应于样本和特征的二维张量。 NiN的想法是在每个像素位置(针对每个高度和宽度)应用一个全连接层。 如果我们将权重连接到每个空间位置,我们可以将其视为 1×1 卷积层(如6.4节 中所述),或作为在每个像素位置上独立作用的全连接层。 从另一个角度看,即将空间维度中的每个像素视为单个样本,将通道维度视为不同特征(feature)。
图7.3.1 说明了VGG和NiN及它们的块之间主要架构差异。 NiN块以一个普通卷积层开始,后面是两个1×1 的卷积层。这两个 1×1 卷积层充当带有ReLU激活函数的逐像素全连接层。 第一层的卷积窗口形状通常由用户设置。 随后的卷积窗口形状固定为 1×1。
7.3.1 对比 VGG 和 NiN 及它们的块之间主要架构差异。
import torch
from torch import nn
from d2l import torch as d2ldef 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())
[NiN模型]
最初的NiN网络是在AlexNet后不久提出的,显然从中得到了一些启示。 NiN使用窗口形状为 11×11 、 5×5 和 3×3 的卷积层,输出通道数量与AlexNet中的相同。 每个NiN块后有一个最大汇聚层,汇聚窗口形状为3×3,步幅为2。
NiN和AlexNet之间的一个显著区别是NiN完全取消了全连接层。 相反,NiN使用一个NiN块,其输出通道数等于标签类别的数量。最后放一个全局平均汇聚层(global average pooling layer),生成一个对数几率 (logits)。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),# 标签类别数是10nin_block(384, 10, kernel_size=3, strides=1, padding=1),nn.AdaptiveAvgPool2d((1, 1)),# 将四维的输出转成二维的输出,其形状为(批量大小,10)nn.Flatten())
我们创建一个数据样本来[查看每个块的输出形状]。
X = torch.rand(size=(1, 1, 224, 224))
for layer in net:X = layer(X)print(layer.__class__.__name__,'output shape:\t', X.shape)
输出结果:
Sequential output shape: torch.Size([1, 96, 54, 54])
MaxPool2d output shape: torch.Size([1, 96, 26, 26])
Sequential output shape: torch.Size([1, 256, 26, 26])
MaxPool2d output shape: torch.Size([1, 256, 12, 12])
Sequential output shape: torch.Size([1, 384, 12, 12])
MaxPool2d output shape: torch.Size([1, 384, 5, 5])
Dropout output shape: torch.Size([1, 384, 5, 5])
Sequential output shape: torch.Size([1, 10, 5, 5])
AdaptiveAvgPool2d output shape: torch.Size([1, 10, 1, 1])
Flatten output shape: torch.Size([1, 10])
[训练模型]
和以前一样,我们使用Fashion-MNIST来训练模型。训练NiN与训练AlexNet、VGG时相似。
lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
输出结果:
loss 0.352, train acc 0.871, test acc 0.874
1721.5 examples/sec on cuda:0
小结
- NiN使用由一个卷积层和多个 1×1 卷积层组成的块。该块可以在卷积神经网络中使用,以允许更多的每像素非线性。
- NiN去除了容易造成过拟合的全连接层,将它们替换为全局平均汇聚层(即在所有位置上进行求和)。该汇聚层通道数量为所需的输出数量(例如,Fashion-MNIST的输出为10)。
- 移除全连接层可减少过拟合,同时显著减少NiN的参数。
- NiN的设计影响了许多后续卷积神经网络的设计。
练习
- 调整NiN的超参数,以提高分类准确性。
解:
代码如下:
lr, num_epochs, batch_size = 0.05, 20, 128
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
输出结果:
loss 0.281, train acc 0.896, test acc 0.885
1719.9 examples/sec on cuda:0
2. 为什么NiN块中有两个 1×1 卷积层?删除其中一个,然后观察和分析实验现象。
解:
第一个 1×1 卷积层作用是降维:用于减少特征图的通道数,从而降低网络的参数数量和计算复杂度,有助于防止过拟合,并使网络更加高效。
第二个 1×1 卷积层作用是增加网络深度和像素的非线性性:对第一个卷积层的输出进行非线性变换,有助于网络学习更复杂的特征表示。通过这种方式,网络可以在保持参数数量相对较低的同时,增加其表达能力。
同时,1×1 卷积层可以融合每个像素点不同通道的特征,因此也具有特征融合的作用。
删除其中一个 1×1 卷积层后,训练损失略上升,训练和测试精度略下降,可能导致网络学习特征能力下降、非线性性降低,从而导致精度略下降。
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())
lr, num_epochs, batch_size = 0.1, 10, 128
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
输出结果:
loss 0.360, train acc 0.867, test acc 0.869
2208.1 examples/sec on cuda:0
3. 计算NiN的资源使用情况。
1)参数的数量是多少?
2)计算量是多少?
3)训练期间需要多少显存?
4)预测期间需要多少显存?
解:
计算过程如下:
对于一张图(1, 224, 224)而言:
1)参数的数量为:1.92 MB;
2)计算量为:1.55 GB;
3)训练期间的显存为参数占用显存+模型占用显存:
参数占用显存:1.92 * 4 * 2 = 15.38 MB (float32占4 bytes,1 for params, 1 for SGD)
模型占用显存:6.35 * 4 * 2 = 50.80 MB (float32占4 bytes,1 for forward, 1 for backward)
总占用显存:15.38 + 50.80 = 66.18 MB
4)预测期间的显存为参数占用显存+模型占用显存:
参数占用显存:1.92 * 4 = 7.69 MB (float32占4 bytes,only for params)
模型占用显存:6.35 * 4 = 25.40 MB (float32占4 bytes,only for forward)
总占用显存:7.69 + 25.40 = 33.09 MB
4. 一次性直接将 384×5×5 的表示缩减为 10×5×5 的表示,会存在哪些问题?
解:
一次性直接将 384×5×5 的表示缩减为 10×5×5 的表示,即不经过两个 1×1 卷积层降维,可能存在的问题有:
1)可能会丢失大量的信息。
2)可能过拟合增大:当特征维度降低太多时,网络可能无法捕捉到足够的特征来泛化到新的数据上。
3)可能学习能力下降:大幅度降低特征维度会限制网络的学习能力,使其难以学习复杂的函数映射。
4)可能破坏特征融合:不同层的特征图会通过 卷积层进行融合,以提取更加抽象的特征。如果直接大幅度减少特征维度,可能会破坏这种特征融合的过程,影响网络性能。
5)可能会导致梯度消失或梯度爆炸。