引用翻译:《动手学深度学习》
虽然AlexNet证明了深度卷积神经网络可以取得良好的效果,但它并没有提供一个通用的模板来指导后续研究人员设计新的网络。
这个领域的进展反映了芯片设计中的进展,工程师们从放置晶体管到逻辑元素再到逻辑块。同样,神经网络架构的设计也逐渐变得更加抽象,研究人员从单个神经元到整个层,再到现在的块,层的重复模式。
使用块的想法首先出现在牛津大学的视觉几何小组(VGG)。在他们同名的VGG网络中,通过使用循环和子程序,很容易在任何现代深度学习框架的代码中实现这些重复结构。
经典卷积网络的基本构件是由以下层组成的序列。
(i) 卷积层(有填充以保持分辨率),
(ii) 非线性,如ReLu,一个VGG块由一连串的卷积层组成,然后是一个用于空间降采样的最大池化层。
在最初的VGG论文Simonyan.Zisserman.2014中,作者采用了3×3核的卷积和2×2的最大池化,跨度为2(在每个区块后分辨率减半)。在下面的代码中,我们定义了一个名为vgg_block的函数来实现一个VGG块。该函数需要两个参数,分别对应卷积层的数量num_convs和输出通道的数量num_channels。
import sys
sys.path.insert(0,'../')
import d2l
import torch
import torch.nn as nn
import torch.optim as optim
import time
def vgg_block(num_convs, in_channels, out_channels):
layers=[]
for _ in range(num_convs):
layers.append(nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1))
layers.append(nn.ReLU())
in_channels = out_channels
layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
blk = nn.Sequential(*layers)
return blk
像AlexNet和LeNet一样,VGG网络可以分成两部分:
第一部分主要由卷积层和池化层组成,
第二部分由全连接层组成。网络的卷积部分依次连接了几个vgg_block模块。
下面,变量conv_arch由一个图元列表组成(每个块一个),其中每个图元包含两个值:卷积层的数量和输出通道的数量,这正是调用vgg_block函数所需的参数。全连接模块与AlexNet中的模块相同。
从积木中设计一个网络
最初的VGG网络有5个卷积块,其中前两个各有一个卷积层,后三个各有两个卷积层。第一个模块有64个输出通道,随后的每个模块将输出通道的数量增加一倍,直到达到512个。由于该网络使用了8个卷积层和3个全连接层,所以它通常被称为VGG-11。
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))
下面的代码实现了VGG-11。这是一个简单的问题,就是在conv_arch上执行一个for循环。
class Flatten(torch.nn.Module):
def forward(self, x):
return x.view(x.shape[0], -1)
def vgg(conv_arch):
# The convulational layer part
conv_layers=[]
in_channels=1
for (num_convs, out_channels) in conv_arch:
conv_layers.append(vgg_block(num_convs, in_channels, out_channels))
in_channels = out_channels
net=nn.Sequential(
*conv_layers,
# 全连接层部分
Flatten(),
nn.Linear(in_features=512*7*7, out_features=4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 10)
)
return net
net = vgg(conv_arch)
网络结构:
Sequential(
(0): Sequential(
(0): Conv2d(1, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU()
(2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(1): Sequential(
(0): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU()
(2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(2): Sequential(
(0): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU()
(2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): ReLU()
(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(3): Sequential(
(0): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU()
(2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): ReLU()
(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(4): Sequential(
(0): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU()
(2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): ReLU()
(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(5): Flatten()
(6): Linear(in_features=25088, out_features=4096, bias=True)
(7): ReLU()
(8): Dropout(p=0.5, inplace=False)
(9): Linear(in_features=4096, out_features=4096, bias=True)
(10): ReLU()
(11): Dropout(p=0.5, inplace=False)
(12): Linear(in_features=4096, out_features=10, bias=True)
)
接下来,我们将构建一个高度和宽度为224的单通道数据例子,以观察每一层的输出形状。
X = torch.randn(size=(1,1,224,224), dtype=torch.float32)
for blk in net:
X = blk(X)
print(blk.__class__.__name__,'output shape:\t',X.shape)
Sequential output shape: torch.Size([1, 64, 112, 112])
Sequential output shape: torch.Size([1, 128, 56, 56])
Sequential output shape: torch.Size([1, 256, 28, 28])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 512, 7, 7])
Flatten output shape: torch.Size([1, 25088])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 10])
正如你所看到的,我们在每个区块将高度和宽度减半,最后达到7的高度和宽度,然后再将表征压平,以便由全连接层处理(即[1, 512, 7, 7]各元素乘起来,512 X 7 X 7。
除了使用稍大的学习率外,模型的训练过程与AlexNet相似。
# 定义训练的函数
def train_ch5(net, train_iter, test_iter, criterion, num_epochs, batch_size, device, lr=None):
"""模型训练函数"""
print('training on', device)
net.to(device)
optimizer = optim.SGD(net.parameters(), lr=lr)
for epoch in range(num_epochs):
net.train() # Switch to training mode
n, start = 0, time.time()
train_l_sum = torch.tensor([0.0], dtype=torch.float32, device=device)
train_acc_sum = torch.tensor([0.0], dtype=torch.float32, device=device)
for X, y in train_iter:
optimizer.zero_grad()
X, y = X.to(device), y.to(device)
y_hat = net(X)
loss = criterion(y_hat, y)
loss.backward()
optimizer.step()
with torch.no_grad():
y = y.long()
train_l_sum += loss.float()
train_acc_sum += (torch.sum((torch.argmax(y_hat, dim=1) == y))).float()
n += y.shape[0]
test_acc = evaluate_accuracy(test_iter, net, device)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'\
% (epoch + 1, train_l_sum/n, train_acc_sum/n, test_acc, time.time() - start))
对模型进行训练
lr, num_epochs, batch_size, device = 0.05, 5, 64, d2l.try_gpu()
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
torch.nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
net = net.to(device)
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
criterion = nn.CrossEntropyLoss()
train_ch5(net, train_iter, test_iter, criterion, num_epochs, batch_size, device, lr)
training on cpu
VGG-11使用可重复使用的卷积块构建了一个网络。不同的VGG模型可以通过每个块中的卷积层和输出通道数量的不同来定义。
块的使用导致了网络定义的非常紧凑的表示。它允许有效地设计复杂的网络。
在他们的工作中,Simonyan和Ziserman试验了各种架构。特别是,他们发现几层深而窄的卷积(即3×3)比更少层的宽卷积更有效。
1、与AlexNet相比,VGG的计算速度要慢得多,而且还需要更多的GPU内存。试着分析一下其中的原因。
2、试着将Fashion-MNIST中的图像的高度和宽度从224改为96。这对实验有什么影响?
3、参考:cite:Simonyan.Zisserman.2014中的表1,构建其他常用模型,如VGG-16或VGG-19。