前⼏节介绍的LeNet、AlexNet和VGG在设计上的共同之处是:先以由卷积层构成的模块充分抽取空间特征,再以由全连接层构成的模块来输出分类结果。其中,AlexNet和VGG对LeNet的改进主要在于如何对这两个模块加宽(增加通道数)和加深。本节我们介绍⽹络中的⽹络(NiN)。它提出了另外⼀个思路,即串联多个由卷积层和“全连接”层构成的⼩网络来构建⼀个深层网络。
目录
1. NIN块
我们知道,卷积层的输⼊和输出通常是四维数组(样本,通道,高,宽),而全连接层的输入和输出则通常是二维数组(样本,特征)。如果想在全连接层后再接上卷积层,则需要将全连接层的输出变换为四维。回忆在(多输入通道和多输出通道)⾥介绍的1*1卷积层。它可以看成全连接层,其中空间维度(高和宽)上的每个元素相当于样本,通道相当于特征。因此,NiN使⽤1*1卷积层来替代全连接层,从⽽使空间信息能够自然传递到后⾯的层中去。下图对⽐了NiN同AlexNet和VGG等网络在结构上的主要区别。
NiN块是NiN中的基础块。它由⼀个卷积层加两个充当全连接层的1*1卷积层串联而成。其中第⼀个卷积层的超参数可以自⾏行设置,而第二和第三个卷积层的超参数⼀般是固定的。
import time
import torch
from torch import nn, optim
import sys
sys.path.append(".")
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(torch.__version__)
print(device)
def nin_block(in_channels, out_channels, kernel_size, stride, padding):
blk = nn.Sequential(nn.Conv2d(in_channels, out_channels, kernel_size, stride, 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())
return blk
2. NIN模型
NiN是在AlexNet问世不久后提出的。它们的卷积层设定有类似之处。NiN使用卷积窗口形状分别为11*11、5*5和3*3的卷积层,相应的输出通道数也与AlexNet中的一致。每个NiN块后接一个步幅为2、窗⼝形状为3*3的最大池化层。
除使用NiN块以外,NiN还有⼀个设计与AlexNet显著不同:NiN去掉了AlexNet最后的3个全连接层,取而代之地,NiN使用了输出通道数等于标签类别数的NiN块,然后使用全局平均池化层对每个通道中所有元素求平均并直接用于分类。这⾥的全局平均池化层即窗口形状等于输入空间维形状的平均池化层。 NiN的这个设计的好处是可以显著减小模型参数尺寸,从⽽缓解过拟合。然而,该设计有时会造成获得有效模型的训练时间的增加。
net = nn.Sequential(
nin_block(1, 96, kernel_size=11, stride=4, padding=0),
nn.MaxPool2d(kernel_size=3, stride=2),
nin_block(96, 256, kernel_size=5, stride=1, padding=2),
nn.MaxPool2d(kernel_size=3, stride=2),
nin_block(256, 384, kernel_size=3, stride=1, padding=1),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Dropout(0.5),
# 标签类别数是10
nin_block(384, 10, kernel_size=3, stride=1, padding=1),
# 全局平均池化层可通过将窗口形状设置成输入的高和宽实现
nn.AvgPool2d(kernel_size=5), #(batch_size,1,1,10)
# 将四维的输出转成二维的输出,其形状为(批量大小, 10)
d2l.FlattenLayer())
也可以自己实现一个全局平局池化函数:
# 可以把本函数保存在d2lzh_pytorch
class GlobalAvgPool2d(nn.Module):
# 全局平均池化层可通过将池化窗口形状设置成输⼊的高和宽实现
def __init__(self):
super(GlobalAvgPool2d, self).__init__()
def forward(self, x):
return F.avg_pool2d(x, kernel_size=x.size()[2:])
我们构建⼀个数据样本来查看每一层的输出形状。
X = torch.rand(1, 1, 224, 224)
for name, blk in net.named_children(): #一级子模块
X = blk(X)
print(name, 'output shape: ', X.shape)
3. 获取数据和训练模型
我们依然使用Fashion-MNIST数据集来训练模型。NiN的训练与AlexNet和VGG的类似,但这⾥使用的学习率更大。
batch_size = 128
# 如出现“out of memory”的报错信息,可减小batch_size或resize
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
lr, num_epochs = 0.002, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)
4. 小结
1)NiN重复使用由卷积层和代替全连接层的1*1卷积层构成的NiN块来构建深层网络。
2)NiN去除了容易造成过拟合的全连接输出层,而是将其替换成输出通道数等于标签类别数的NiN 块和全局平均池化层。
3)NiN的以上设计思想影响了后⾯一系列卷积神经⽹络的设计。