前言
前边几篇有关卷积网络的博客中介绍了最经典的LeNet网络和AlexNet网络,其主要是将其作为经典的网络模型进行介绍,同时对二者进行了对比。对比发现后者比前者的卷积层多三层,且在卷积核、通道数和构造的顺序上存在很大的不同,但上述二种模型其内部并没有过多的说明如何去构造卷积神经网络,本章以后的几篇分别来写一下使用重复元素的网络(VGG)、网络中的网络(NiN)、含并行连接的网络(GoogLeNet)等较为经典的网络模型及其相关的构造原则与构造方式,并通过代码进行实现(代码参考李沐书籍代码进行调试修改)。
VGG中展示的深度模型设计思路
既然测重的理解点是“深度网络的设计思路”,那么首先需要明确的一点就是:VGG网络的贡献在于其提出了可以通过重复使用简单的基础块来构建深度模型的方法。何为基础块?那就是构成网络模型的基本单元之一,而VGG网络中的基础块,自然是VGG块(包含多层网络的大网络单元),那笔者就从VGG块的构建出发,来介绍整个VGG网络的实现。
VGG块的构建及代码实现
VGG块的搭建特点是:采用多个3*3的卷积核来代替比较大尺度的卷积核(如使用3个3*3卷积核代替7*7卷积核,使用2个3*3卷积核代替5*5卷积核),且这样的3*3卷积核之后接一个步幅为2的MAX池化层,该块中的卷积层保持输入的高和宽不变,池化层对其减半。
同时还有一点需要理解的就是:采用多层堆积的小卷积核代替大卷积核,可以有效地增加网络的深度来实现更小代价(参数使用更少)的复杂模式的学习,一般书中的经典介绍是:在保证其具有相同感知野(图像处理的视野)的条件下,提升了网络深度,在一定程度上提升了神经网络的效果。
下面我们进行代码实现,为了保证代码的完整,首先进行库的导入:
import time
import torch
from torch import nn, optim
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
import sys
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
接下来,定义函数实现VGG基础块的定义
#定义vgg_block函数来实现VGG基础块
def vgg_block(num_convs, in_channels, out_channels):
blk = []
for i in range(num_convs):
if i == 0:
blk.append(nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1))
else:
blk.append(nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1))
blk.append(nn.ReLU())
blk.append(nn.MaxPool2d(kernel_size=2, stride=2)) # 这里会使宽高减半
return nn.Sequential(*blk)
VGG网络的构造及其代码实现
VGG网络与之前的LeNet和AlexNet网络一样,在其卷积部分结束后,其后依然接的是全连接层。值得注意的是其全连接层也是三层,与AlexNet中的全连接层一样。
我们构造一个比较经典的拥有5个VGG卷积块的VGG-11网络.该网络中前2个VGG块为单卷积层(只含有一层卷积层),后3个VGG块为双卷积层(含有两层卷积层),后接3个全连接层,因此总结一下就是该网络包含8层卷积层和3层卷连接层,这也是其经常被称为VGG-11的原因所在。
接下来我们进行网络的代码实现:
这里我们定义一个超参数变量conv_arch,来定义,每个层中的卷积层数和输入及输出通道数:
#构造一个VGG网络,它有5个卷积块,前2块使用单卷积层,而后3块使用双卷积层
conv_arch = ((1, 1, 64), (1, 64, 128), (2, 128, 256), (2, 256, 512), (2, 512, 512)) #num_convs, in_channels, out_channels
# 经过5个vgg_block, 宽高会减半5次, 变成 224/32 = 7
fc_features = 512 * 7 * 7 # c * w * h
fc_hidden_units = 4096 # 任意
实现VGG-11的构建:
#实现VGG-11
def vgg(conv_arch, fc_features, fc_hidden_units=4096):
net = nn.Sequential()
# 卷积层部分
for i, (num_convs, in_channels, out_channels) in enumerate(conv_arch):
# 每经过一个vgg_block都会使宽高减半
net.add_module("vgg_block_" + str(i+1), vgg_block(num_convs, in_channels, out_channels))
# 全连接层部分
net.add_module("fc", nn.Sequential(d2l.FlattenLayer(),
nn.Linear(fc_features, fc_hidden_units),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(fc_hidden_units, fc_hidden_units),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(fc_hidden_units, 10)
))
return net
获取数据和训练模型
PS:由于VGG网络的计算过程较为复杂,为了节省代码运行时间,我们将需要定义的通道数进行缩减(这里采取减半操作)
#获取数据和训练模型
#为了节省时间,构造一个通道数更小,或者说更窄的网络在Fashion-MNIST数据集上进行训练
ratio = 8
small_conv_arch = [(1, 1, 64//ratio), (1, 64//ratio, 128//ratio), (2, 128//ratio, 256//ratio),
(2, 256//ratio, 512//ratio), (2, 512//ratio, 512//ratio)]
net = vgg(small_conv_arch, fc_features // ratio, fc_hidden_units // ratio)
print(net)
查看后得到模型构成如下图:
最终进行模型训练:
#模型训练
batch_size = 64
# 如出现“out of memory”的报错信息,可减小batch_size或resize
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
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)
训练结果如下:
总结
1.VGG-11用5个可重复的卷积块来构造网络,根据块中卷积层个数和输出通道的不同们可以定义出不同的VGG块;
2.VGG和AlexNet一样,相比于LeNet的改变主要是对网络进行了加宽(通道数增加)和加深。