卷积神经网络(CNN)已经成为计算机视觉领域的基础模型。经典的网络如 LeNet、AlexNet 和 VGG,虽然各自有所创新,但它们通常遵循一个共同的设计模式:通过一系列卷积层和池化层提取特征,之后使用全连接层进行分类或回归。随着网络的加深,参数量也不断增大,这会导致计算资源的需求增加以及可能的过拟合问题。
网络中的网络(NiN)是一种创新的网络设计,它提出了一种替代传统全连接层的新方式,以减少模型的参数量并提高性能。接下来,我们将逐步了解 NiN 的基本思想、架构和如何使用它。
1. NiN块:卷积与全连接的结合

对比 VGG 和 NiN 及它们的块之间主要架构差异
1.1 传统卷积层与全连接层
在常规的卷积神经网络中,卷积层的输入和输出是四维张量,通常表示为(N,C,H,W),其中:
- N:样本数量(batch size)
 - C:通道数(channel)
 - H:图像的高度(height)
 - W:图像的宽度(width)
 
而全连接层的输入输出通常是二维张量,表示为 (N,F),其中 F 是特征的数量。传统的卷积神经网络将卷积层和全连接层分开,前者提取图像的空间特征,后者负责将这些特征转化为最终的分类输出。
1.2 NiN块的创新
NiN 的创新之处在于,它在每个像素位置应用了全连接层。这意味着,NiN 将每个像素的通道视为一个特征,并且对每个像素独立地应用多个神经网络层。这种设计在网络中每个位置都保留了空间结构信息,而不仅仅是对整个图像进行处理。
具体来说,NiN 块开始于一个常规的卷积层,然后紧接着两个卷积层,这两个卷积层充当了具有 ReLU 激活函数的逐像素全连接层。第一层的卷积窗口形状可以由用户设定,而后续的卷积层通常使用 1×1的卷积窗口。
NiN 的块结构示例如下:
import torch
from torch import nn
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()
    )
通过这种设计,NiN 能够在每个像素位置进行独立的特征提取和非线性变换。
2. NiN模型:去除全连接层
2.1 NiN的网络结构
NiN 的设计灵感来自于 AlexNet,但与 AlexNet 和 VGG 不同,NiN 完全去除了传统的全连接层。取而代之的是,通过一个 NiN 块输出通道的数量设置为类别数量,最后加一个全局平均池化层(Global Average Pooling, GAP),将特征图的每个通道的空间信息汇聚成一个数字,作为输出的 logits。
这种设计的优势在于:
- 减少了参数量,避免了过多的全连接层。
 - 降低了过拟合的风险,尤其是在数据较少的情况下。
 
2.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),
    nin_block(384, 10, kernel_size=3, strides=1, padding=1),
    nn.AdaptiveAvgPool2d(output_size=(1, 1)),
    nn.Flatten()
)
nn.AdaptiveAvgPool2d 是一种池化层,用于对输入的特征图进行自适应平均池化。
- 主要特点:无论输入的大小如何,输出的特征图大小固定为用户指定的目标尺寸。
 - output_size:定义池化层输出的目标尺寸,可以是:
- 单个整数:输出为正方形,尺寸为 (output_size, output_size)。
 - 元组 (h, w):输出为矩形,尺寸为 (h, w)。
 
 
2.3 计算每个层的输出形状
通过输入一个随机生成的图像样本,我们可以查看每个层的输出形状:
X = torch.randn(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])
3. 训练模型
训练 NiN 模型与其他网络类似,可以使用经典的数据集如 Fashion-MNIST。在训练过程中,我们使用优化算法(如 SGD)更新模型的参数,直到模型收敛。以下是训练模型的代码:
lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(128, resize=224)
d2l.train_ch6(net, train_iter, test_iter, num_epochs,lr, d2l.try_gpu())


4. 小结
- NiN 结构:通过将卷积层和全连接层结合,NiN 提供了一种更灵活的方式来处理每个像素位置的特征,从而提高了网络的表现力。
 - 去除全连接层:NiN 取消了传统的全连接层,改为全局平均池化(GAP),显著减少了模型的参数数量,降低了过拟合的风险。
 - 应用与影响:NiN 的设计为后续的卷积神经网络架构(如 GoogLeNet 和 ResNet)提供了启示,影响了深度学习的研究和应用。
 
NiN 的设计虽然简单,但却为卷积神经网络架构的优化提供了新的思路,尤其在减少参数量和提高训练效率方面有着重要的作用。