Pytorch总结十二之 深度神经网络模型:NIN、GooLeNet、ResNet、DenseNet
引言:
上篇文章介绍的LeNet、AlexNet
和VGG
在设计上的共同之处是:先以由卷积层构成的模块充分抽取空间特征,再以由全连接层构成的模块来输出分类结果。其中,AlexNet
和VGG
对LeNet
的改进主要在于如何对这两个模块加宽(增加通道数)和加深。
1.NIN块
- NIN块提出了另外一个思路,即串联多个由卷积层 和全连接层构成的小网络来构建一个深层网络。
1.1 NIN块
- 卷积层的输入和输出通常是四维数组(样本、通道、高、宽),而全连接层的输入和输出则通常是二维数组(样本、特征)。如果想在全连接层上再接上卷积层,则需要将全连接层的输出变换为四维。形如(多输入通道和多输出通道)里介绍的
1x1
卷积层,它可以看成全连接层,其中空间维度(高和宽)上的每个元素相当于样本,通道相当于特征。因此,NiN
使用1x1卷积层来替代全连接层,从而是空间信息能够自然传递到后边的层中去,下图对比了NiN
同AlexNet
和VGG
等网络再结构上的主要区别。
NiN
块是NiN
中的基础块,它有一个卷积层加两个充当全连接层的1x1
卷积层串联而成。其中第一个卷积层的超参数可以自行设置,而第二个和第三个卷积层的超参数一般是固定的。
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')
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), #充当全连接层的卷积层1
nn.ReLU(),
nn.Conv2d(out_channels,out_channels,kernel_size=1), #充当全连接层的卷积层2
nn.ReLU())
return blk
1.2 NiN模型
NiN
是在AlexNet
问世不久后提出的。它们的卷积层设定有类似之处。NiN
使⽤卷积窗⼝形状分别为11x11
、5x5
和3x3
的卷积层,相应的输出通道数也与AlexNet
中的⼀致。每个NiN
块后接⼀个步幅为2
、窗⼝形状为3x3
的最⼤池化层。- 除使⽤
NiN
块以外,NiN
还有⼀个设计与AlexNet
显著不同:NiN
去掉了AlexNet
最后的3个全连接层,取⽽代之地,NiN
使⽤了输出通道数等于标签类别数的NiN
块,然后使⽤全局平均池化层对每个通道中所有元素求平均并直接⽤于分类。这⾥的全局平均池化层即窗⼝形状等于输⼊空间维形状的平均池化层。NiN
的这个设计的好处是可以显著减⼩模型参数尺⼨,从⽽缓解过拟合。然⽽,该设计有时会造成获得有效模型的训练时间的增加。
# 已保存在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:]) #import torch.nn.functional as F
net=nn.Sequential(
nin_block(in_channels=1,out_channels=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),
GlobalAvgPool2d(),
#将四维的输出转为二维的输出,其形状为(批量大小,10)
d2l.FlattenLayer())
#构建一个样本查看每一层的输出形状
X=torch.rand((1,1,224,224))
for name,blk in net.named_children():
X=blk(X)
print(name,'output shape:',X.shape)
output:
1.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)
output:
1.4 小结
NiN
重复使⽤由卷积层和代替全连接层的1x1
卷积层构成的NiN
块来构建深层⽹络。NiN
去除了容易造成过拟合的全连接输出层,⽽是将其替换成输出通道数等于标签类别数的NiN
块和全局平均池化层。NiN
的以上设计思想影响了后⾯⼀系列卷积神经⽹络的设计。
2.含并行连结的网路(GoogLeNet)
在2014年的ImageNet
图像识别挑战赛中,⼀个名叫GoogLeNet
的⽹络结构⼤放异彩 [1]。它虽然在名字上向LeNet
致敬,但在⽹络结构上已经很难看到LeNet
的影⼦。GoogLeNet
吸收了NiN
中⽹络串联⽹络的思想,并在此基础上做了很⼤改进。在随后的⼏年⾥,研究⼈员对GoogLeNet
进⾏了数次改进,本节将介绍这个模型系列的第⼀个版本。
2.1 INCEPTION块
GoogLeNet
中的基础卷积块叫作Inception
块,得名于同名电影《盗梦空间》(Inception)。与上⼀
节介绍的NiN
块相⽐,这个基础块在结构上更加复杂,如下图所示:
由上图可以看出,Inception
块里有4条并行的线路。前3条线路使用窗口分别为1x1,3x3 和 5x5
的卷积层来抽取不同空间尺寸下的信息,其中中间2个线路对输入先做1x1
卷积来减少输入通道数,以降低模型复杂度。第四条线路则使用3x3
最大池化层,后接1x1
卷积层来改变通道数。4条线路都使用了合适的填充来使输入与输出的高和宽移植,最后将每条线路的输出在通道维上连结,并输入接下来的层中去。
Inception
块中可以⾃定义的超参数是每个层的输出通道数,我们以此来控制模型复杂度。
#test_googlenet
import time
import torch
from torch import nn,optim
import torch.nn.functional as F
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
device=torch.device('cuda' if torch.cuda.is_available() else 'cpu')
class Inception(nn.Module):
# c1 - c4为每条线路⾥的层的输出通道数
def __init__(self,in_c,c1,c2,c3,c4):
super(Inception,self).__init__()
# 线路1,单1 x 1卷积层
self.p1_1 = nn.Conv2d(in_c, c1, kernel_size=1)
# 线路2,1 x 1卷积层后接3 x 3卷积层
self.p2_1 = nn.Conv2d(in_c, c2[0], kernel_size=1)
self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3,padding=1)
# 线路3,1 x 1卷积层后接5 x 5卷积层
self.p3_1 = nn.Conv2d(in_c, c3[0], kernel_size=1)
self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5,padding=2)
# 线路4,3 x 3最⼤池化层后接1 x 1卷积层
self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1,padding=1)
self.p4_2 = nn.Conv2d(in_c, 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) # 在通道维上连结输出
2.2 GoogLeNet模型
GoogLeNet
跟VGG
⼀样,在主体卷积部分中使⽤5个模块(block),每个模块之间使⽤步幅为2的3x3
最⼤池化层来减⼩输出⾼宽。第⼀模块使⽤⼀个64通道的7x7
卷积层。
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))
- 第⼆模块使⽤2个卷积层:⾸先是64通道的 卷积层,然后是将通道增⼤3倍的
3x3
卷积层。它对
应Inception
块中的第⼆条线路。
b2=nn.Sequential(nn.Conv2d(64,64,kernel_size=1),
nn.Conv2d(64,192,kernel_size=3,padding=1),
nn.MaxPool2d(kernel_size=3,stride=2,padding=1))
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))
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))
b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
Inception(832, 384, (192, 384), (48, 128), 128),
d2l.GlobalAvgPool2d())
net=nn.Sequential(b1,b2,b3,b4,b5,d2l.FlattenLayer(),nn.Linear(1024,10))
GoogLeNet
模型的计算复杂,⽽且不如VGG
那样便于修改通道数。本节⾥我们将输⼊的⾼和宽从224
降到96来简化计算。下⾯演示各个模块之间的输出的形状变化。
net=nn.Sequential(b1,b2,b3,b4,b5,d2l.FlattenLayer(),nn.Linear(1024,10))
X=torch.rand(1,1,96,96)
for blk in net.children():
X=blk(X)
print('output shape:',X.shape)
output:
2.3 获取数据和训练模型
我们使⽤⾼和宽均为96像素的图像来训练GoogLeNet
模型。训练使⽤的图像依然来⾃Fashion-MNIST
数据集。
batch_size = 128
# 如出现“out of memory”的报错信息,可减⼩batch_size或resize
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer,
device, num_epochs)
2.4 小结
Inception
块相当于⼀个有4条线路的⼦⽹络。它通过不同窗⼝形状的卷积层和最⼤池化层来并⾏抽取信息,并使⽤1x1
卷积层减少通道数从⽽降低模型复杂度。GoogLeNet
将多个设计精细的Inception
块和其他层串联起来。其中Inception
块的通道数分配之⽐是在ImageNet数据集上通过⼤量的实验得来的。GoogLeNet
和它的后继者们⼀度是ImageNet
上最⾼效的模型之⼀:在类似的测试精度下,它们的计算复杂度往往更低。