环境使用 Kaggle 里免费建立的 Notebook
教程使用李沐老师的 动手学深度学习 网站和 视频讲解
小技巧:当遇到函数看不懂的时候可以按 Shift+Tab
查看函数详解。
GoogLeNet 吸收了 NiN 中串联网络的思想,并在此基础上做了改进。 这篇论文的一个重点是解决了什么样大小的卷积核最合适的问题。GoogLeNet 的一个观点是,有时使用不同大小的卷积核组合是有利的。
GoogLeNet 论文地址:https://arxiv.org/abs/1409.4842
在GoogLeNet 中,基本的卷积块被称为 Inception 块(Inception block)。这很可能得名于电影《盗梦空间》(Inception),因为电影中的一句话“我们需要走得更深”(“We need to go deeper”)。
Inception 块(小学生才做选择,我全都要):
Inception 块跟单 3 × 3 3 \times 3 3×3 和 5 × 5 5 \times 5 5×5 的卷积层相比有更少的参数个数和设计复杂度。
注:问为什么这么设计?调出来的!
GoogLeNet 简化版本:
5 5 5 段, 9 9 9 个 Inception 块。
段 1 , 2 1,2 1,2:
Inception-BN(V2):使用了 batch normalization
Inception-V3:修改了 Inception 块
!pip install -U d2l
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
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)
第一个模块使用 64 64 64 个通道、 7 × 7 7 \times 7 7×7 卷积层:
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))
第二个模块使用两个卷积层:第一个卷积层是 64 64 64 个通道、 1 × 1 1 \times 1 1×1 卷积层;第二个卷积层使用将通道数量增加三倍的 3 × 3 3 \times 3 3×3 卷积层:
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))
第三个模块串联两个完整的 Inception 块, 第一个Inception 块的输出通道数为 64 + 128 + 32 + 32 = 256 64+128+32+32=256 64+128+32+32=256,四个路径之间的输出通道数量比为 64 : 128 : 32 : 32 = 2 : 4 : 1 : 1 64:128:32:32=2:4:1:1 64:128:32:32=2:4:1:1,第二个 Inception 块的输出通道数增加到 128 + 192 + 96 + 64 = 480 128+192+96+64=480 128+192+96+64=480,四个路径之间的输出通道数量比为 128 : 192 : 96 : 64 = 4 : 6 : 3 : 2 128:192:96:64 = 4:6:3:2 128:192:96:64=4:6:3:2:
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))
第四模块更加复杂, 它串联了 5 5 5 个 Inception 块,其输出通道数分别是:
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))
第五模块包含输出通道数为 256 + 320 + 128 + 128 = 832 256+320+128+128=832 256+320+128+128=832 和 384 + 384 + 128 + 128 = 1024 384+384+128+128=1024 384+384+128+128=1024 的两个 Inception 块,后面紧跟输出层,该模块同 NiN 一样使用全局平均池化层,将每个通道的高和宽变成 1 1 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))
GoogLeNet 模型的计算复杂,而且不如 VGG 那样便于修改通道数。 为了使 Fashion-MNIST 上的训练短小精悍,我们将输入的高和宽从 224 224 224 降到 96 96 96,这简化了计算。下面演示各个模块输出的形状变化:
X = torch.rand(size=(1, 1, 96, 96))
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape:\t', X.shape)
lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())