使用区块的网络(VGG)——【torch学习笔记】

使用区块的网络(VGG-11)

引用翻译:《动手学深度学习》

虽然AlexNet证明了深度卷积神经网络可以取得良好的效果,但它并没有提供一个通用的模板来指导后续研究人员设计新的网络。

这个领域的进展反映了芯片设计中的进展,工程师们从放置晶体管到逻辑元素再到逻辑块。同样,神经网络架构的设计也逐渐变得更加抽象,研究人员从单个神经元到整个层,再到现在的块,层的重复模式。

使用块的想法首先出现在牛津大学的视觉几何小组(VGG)。在他们同名的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

二、VGG网络

像AlexNet和LeNet一样,VGG网络可以分成两部分:

第一部分主要由卷积层和池化层组成,

第二部分由全连接层组成。网络的卷积部分依次连接了几个vgg_block模块。

下面,变量conv_arch由一个图元列表组成(每个块一个),其中每个图元包含两个值:卷积层的数量和输出通道的数量,这正是调用vgg_block函数所需的参数。全连接模块与AlexNet中的模块相同。

使用区块的网络(VGG)——【torch学习笔记】_第1张图片

从积木中设计一个网络

最初的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)比更少层的宽卷积更有效。

五、特点:

  • 结构简洁。VGG由5层卷积层、3层全连接层、softmax输出层构成,层与层之间使用max-pooling分开,所有隐层的激活单元都采用ReLU函数。
  • 小卷积核和多卷积子层。VGG使用多个较小卷积核(3x3)的卷积层代替一个卷积核较大的卷积层,一方面可以减少参数,另一方面相当于进行了更多的非线性映射,可以增加网络的拟合/表达能力。VGG通过降低卷积核的大小(3x3),增加卷积子层数来达到同样的性能。
  • 小池化核。相比AlexNet的3x3的池化核,VGG全部采用2x2的池化核。
  • 通道数多。VGG网络第一层的通道数为64,后面每层都进行了翻倍,最多到512个通道,通道数的增加,使得更多的信息可以被提取出来。
  • 层数更深、特征图更宽。使用连续的小卷积核代替大的卷积核,网络的深度更深,并且对边缘进行填充,卷积的过程并不会降低图像尺寸。
  • 全连接转卷积(测试阶段)。在网络测试阶段将训练阶段的三个全连接替换为三个卷积,使得测试得到的全卷积网络因为没有全连接的限制,因而可以接收任意宽或高为的输入。

六、练习

1、与AlexNet相比,VGG的计算速度要慢得多,而且还需要更多的GPU内存。试着分析一下其中的原因。

2、试着将Fashion-MNIST中的图像的高度和宽度从224改为96。这对实验有什么影响?

3、参考:cite:Simonyan.Zisserman.2014中的表1,构建其他常用模型,如VGG-16或VGG-19。

你可能感兴趣的:(深度学习——torch学习笔记,torch,神经网络,深度学习,卷积神经网络,VGG)