Appearance
网络中的网络(NiN)
一、概述
之前我们学的 LeNet、AlexNet、VGG 都是 “卷积层 + 汇聚层 + 全连接层” 的结构,全连接层会把卷积层输出的空间结构(比如图像的高度、宽度)完全展平成一维,丢失了空间位置信息。而网络中的网络(NiN)提出了一个新的思路:在每个像素位置上应用多层感知机,也就是用 1×1 卷积层来代替全连接层,这样既能提取特征,又能保留空间结构,同时还能减少模型的参数数量。
二、NiN 块:网络的基本单元
NiN 的核心是 NiN 块,它的结构很简单:
一个普通的卷积层:用来提取空间特征,卷积窗口的形状可以自己设置(比如 11×11、5×5、3×3)。
两个 1×1 的卷积层:这两个 1×1 卷积层就像是 “逐像素的全连接层”,每个 1×1 卷积层后面都带 ReLU 激活函数,用来提取每个像素位置上的通道特征,相当于在每个像素位置上做一次全连接层的计算,但是保留了空间的高度和宽度信息。
对比 VGG 的块(卷积层 + 汇聚层),NiN 块用 1×1 卷积层代替了全连接层,这样不会丢失空间结构,能更好地保留图像的位置信息。
三、NiN 模型:取消全连接层,用全局平均汇聚层
NiN 的模型结构是参考 AlexNet 设计的,但是有一个很大的区别:它完全取消了全连接层,改用全局平均汇聚层,具体结构如下:
多个 NiN 块:每个 NiN 块后面跟着一个 3×3 的最大汇聚层,步幅为 2,用来缩小图像尺寸。
最后一个 NiN 块:输出通道数等于分类的类别数(比如 Fashion-MNIST 是 10 类,输出通道数就是 10)。
全局平均汇聚层:把每个通道的所有像素值取平均,这样每个通道就得到一个值,输出形状是 (批量大小,类别数),不用全连接层就能得到分类的对数几率(logits)。
Dropout 层:在最后一个 NiN 块之前加了 Dropout 层,用来防止过拟合。
这样设计的好处是:
大大减少了模型的参数数量,因为全连接层的参数很多,取消之后参数数量大幅减少。
避免了全连接层带来的过拟合问题。 不过缺点是:训练时间可能会变长,因为全局平均汇聚层的计算方式和全连接层不同。
四、代码实现(PyTorch 版)
1. 定义 NiN 块
首先定义 NiN 块的函数,包含一个普通卷积层和两个 1×1 卷积层:
python
import torch
from torch import nn
from d2l import torch as d2l
def nin_block(in_channels, out_channels, kernel_size, strides, padding):
# 普通卷积层 + 两个1×1卷积层,都带ReLU激活
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 模型
然后构建完整的 NiN 模型:
python
def nin():
net = nn.Sequential(
# 第一个NiN块,卷积窗口11×11,步幅4,输出通道96
nin_block(1, 96, kernel_size=11, strides=4, padding=0),
nn.MaxPool2d(3, stride=2),
# 第二个NiN块,卷积窗口5×5,步幅1,输出通道256
nin_block(96, 256, kernel_size=5, strides=1, padding=2),
nn.MaxPool2d(3, stride=2),
# 第三个NiN块,卷积窗口3×3,步幅1,输出通道384
nin_block(256, 384, kernel_size=3, strides=1, padding=1),
nn.MaxPool2d(3, stride=2),
# Dropout层,防止过拟合
nn.Dropout(0.5),
# 第四个NiN块,输出通道10,对应Fashion-MNIST的10个类别
nin_block(384, 10, kernel_size=3, strides=1, padding=1),
# 全局平均汇聚层,把每个通道的像素值平均成1个值
nn.AdaptiveAvgPool2d((1, 1)),
# 把四维输出转成二维,形状是(批量大小, 10)
nn.Flatten()
)
return net
# 创建模型
net = nin()3. 测试模型输出形状
我们用一个随机的输入来测试每个层的输出形状,看看模型的结构是否正确:
python
# 输入是1张224×224的单通道灰度图
X = torch.rand(size=(1, 1, 224, 224))
for layer in net:
X = layer(X)
print(layer.__class__.__name__, '输出形状:\t', X.shape)运行结果如下:
Plain
Sequential 输出形状: torch.Size([1, 96, 54, 54])
MaxPool2d 输出形状: torch.Size([1, 96, 26, 26])
Sequential 输出形状: torch.Size([1, 256, 26, 26])
MaxPool2d 输出形状: torch.Size([1, 256, 12, 12])
Sequential 输出形状: torch.Size([1, 384, 12, 12])
MaxPool2d 输出形状: torch.Size([1, 384, 5, 5])
Dropout 输出形状: torch.Size([1, 384, 5, 5])
Sequential 输出形状: torch.Size([1, 10, 5, 5])
AdaptiveAvgPool2d 输出形状: torch.Size([1, 10, 1, 1])
Flatten 输出形状: torch.Size([1, 10])可以看到,最后输出的形状是 (1, 10),符合我们的预期,每个输入样本会输出 10 个类别的预测值。
五、训练模型
我们用 Fashion-MNIST 数据集来训练 NiN 模型,训练参数如下:
- 学习率:0.1
- 训练轮数:10
- 批量大小:128
- 输入图像 resize 到 224×224(因为 NiN 是为 224×224 的图像设计的)
训练代码如下:
python
lr, num_epochs, batch_size = 0.1, 10, 128
# 加载数据集,把图像resize到224×224
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())训练完成后,结果大概如下:
Plain
loss 0.563, train acc 0.786, test acc 0.790
3087.6 examples/sec on cuda:0可以看到,训练准确率大概是 0.786,测试准确率大概是 0.790,速度是每秒 3087.6 个样本,在 GPU 上训练的速度还是比较快的。
六、小结
NiN 的核心是 NiN 块:由一个普通卷积层和两个 1×1 卷积层组成,1×1 卷积层相当于逐像素的全连接层,既能提取特征,又能保留空间结构。
NiN 取消了全连接层,改用全局平均汇聚层,这样大大减少了模型的参数数量,避免了全连接层带来的过拟合问题,但是训练时间可能会变长。
NiN 的设计影响了很多后续的卷积神经网络,比如 GoogLeNet(Inception)就借鉴了 NiN 的 1×1 卷积层的思路。
(注:文档部分内容可能由 AI 生成) 源地址