【模型复现】VGGnet,经典的模块化构建网络,网络结构清晰明了,具体思路建议细看论文

  • AlexNet在LeNet的基础上增加了3个卷积层。但AlexNet作者对它们的卷积窗口、输出通道数和构造顺序均做了大量的调整。虽然AlexNet指明了深度卷积神经网络可以取得出色的结果,但并没有提供简单的规则以指导后来的研究者如何设计新的网络。VGG,它的名字来源于论文作者所在的实验室Visual Geometry Group 。VGG提出了可以通过重复使用简单的基础块来构建深度模型的思路。[1409.1556] Very Deep Convolutional Networks for Large-Scale Image Recognition (arxiv.org)

  • VGG16相比AlexNet的一个改进是采用连续的几个3x3的卷积核代替AlexNet中的较大卷积核(11x11,7x7,5x5)。对于给定的感受野(与输出有关的输入图片的局部大小),采用堆积的小卷积核是优于采用大的卷积核,因为多层非线性层可以增加网络深度来保证学习更复杂的模式,而且代价还比较小(参数更少)。在VGG中,使用了3个3x3卷积核来代替7x7卷积核,使用了2个3x3卷积核来代替5*5卷积核,这样做的主要目的是在保证具有相同感知野的条件下,提升了网络的深度,在一定程度上提升了神经网络的效果。

  • VGG网络的结构非常一致,从头到尾全部使用的是3x3的卷积和2x2的max pooling。几个小滤波器(3x3)卷积层的组合比一个大滤波器(5x5或7x7)卷积层好。VGG耗费更多计算资源,并且使用了更多的参数(这里不是3x3卷积的锅),导致更多的内存占用。其中绝大多数的参数都是来自于第一个全连接层。VGG有3个全连接层。有的文章称:发现这些全连接层即使被去除,对于性能也没有什么影响,这样就显著降低了参数数量。

  • 【模型复现】VGGnet,经典的模块化构建网络,网络结构清晰明了,具体思路建议细看论文_第1张图片

  • 为了解决初始化(权重初始化)等问题,VGG采用的是一种Pre-training的方式,先训练浅层的的简单网络 VGG11,再复用 VGG11 的权重来初始化 VGG13,如此反复训练并初始化 VGG19,能够使训练时收敛的速度更快。

  • 多种VGG网络设计都很统一,都有相同的224×224×3的input+5个maxpool层+3层fc全连接层,区别在于中间的Vgg-block块的设计不同。 下面我们以D列中的VGG-16为例展示具体的layer设计:block和block之间通过maxpool的stride=2,pool size=2进行减半池化;block内部,为了保持卷积层间的shape一致,kernel size统一尺寸为3×3。以VGG-16为例:

    • 第1层输入层: 输入为224×224×3 三通道的图像。
    • 第2层vgg block层: 输入为224×224×3,经过64个kernel size为3×3×3的filter,stride = 1,padding=same卷积后得到shape为224×224×64的block层。
    • 第3层Max-pooling层: 输入为224×224×64,经过pool size=2,stride=2的减半池化后得到尺寸为112×112×64的池化层。
    • 第4层vgg block层: 输入尺寸为112×112×64,经128个3×3×64的filter卷积,得到112×112×128的block层。
    • 第5层Max-pooling层:输入为112×112×128,经pool size = 2,stride = 2减半池化后得到尺寸为56×56×128的池化层。
    • 第6层vgg block层: 输入尺寸为56×56×128,经256个3×3×128的filter卷积,得到56×56×256的block层。
    • 第7层Max-pooling层: 输入为56×56×256,经pool size = 2,stride = 2减半池化后得到尺寸为28×28×256的池化层。
    • 第8层vgg block层: 输入尺寸为28×28×256,经512个3×3×256的filter卷积,得到28×28×512的block层。
    • 第9层Max-pooling层: 输入为28×28×512,经pool size = 2,stride = 2减半池化后得到尺寸为14×14×512的池化层。
    • 第10层vgg block层: 输入尺寸为14×14×512,经512个3×3×512的filter卷积,得到14×14×512的block层。
    • 第11层Max-pooling层: 输入为14×14×512,经pool size = 2,stride = 2减半池化后得到尺寸为7×7×512的池化层。该层后面还隐藏了flatten操作,通过展平得到7×7×512=25088个参数后与之后的全连接层相连。
    • 第12~14层Dense层: 第12~14层神经元个数分别为4096,4096,1000。其中前两层在使用relu后还使用了Dropout对神经元随机失活,最后一层全连接层用softmax输出1000个分类。
    • 全连接层: 除最后一层外的全连接层也都使用了丢弃率0.5的dropout

使用重复元素的网络(VGG)

  • 导包,查看设备配置信息

  • import time
    import torch
    from torch import nn, optim
    import sys
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    print(torch.__version__)
    print(device)
    
  • 1.13.1
    cpu
    
  • 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)
    class FlattenLayer(torch.nn.Module):
        def __init__(self):
            super(FlattenLayer, self).__init__()
        def forward(self, x): # x shape: (batch, *, *, ...)
            return x.view(x.shape[0], -1)  # 直接展平,reshape
    conv_arch = ((1, 1, 64), (1, 64, 128), (2, 128, 256), (2, 256, 512), (2, 512, 512)) # 各层的参数(卷积需要几层,输入通道,输出通道)
    fc_features = 512 * 7 * 7 # 根据卷积层的输出算出来的
    fc_hidden_units = 4096 # 任意自定义的
    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):
            net.add_module("vgg_block_" + str(i+1), vgg_block(num_convs, in_channels, out_channels))
        # 全连接层部分
        net.add_module("fc", nn.Sequential(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
    net = vgg(conv_arch, fc_features, fc_hidden_units)
    X = torch.rand(1, 1, 224, 224)
    # named_children获取一级子模块及其名字(named_modules会返回所有子模块,包括子模块的子模块)
    for name, blk in net.named_children(): 
        X = blk(X)
        print(name, 'output shape: ', X.shape)
    
  • vgg_block_1 output shape:  torch.Size([1, 64, 112, 112])
    vgg_block_2 output shape:  torch.Size([1, 128, 56, 56])
    vgg_block_3 output shape:  torch.Size([1, 256, 28, 28])
    vgg_block_4 output shape:  torch.Size([1, 512, 14, 14])
    vgg_block_5 output shape:  torch.Size([1, 512, 7, 7])
    fc output shape:  torch.Size([1, 10])
    
  • 模型缩放,方便本文环境下作训练

  • ratio = 16  # 搞个小一点的模型,方便训练
    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)
    
  • Sequential(
      (vgg_block_1): Sequential(
        (0): Conv2d(1, 4, 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)
      )
      (vgg_block_2): Sequential(
        (0): Conv2d(4, 8, 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)
      )
      (vgg_block_3): Sequential(
        (0): Conv2d(8, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (1): ReLU()
        (2): Conv2d(16, 16, 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)
      )
      (vgg_block_4): Sequential(
        (0): Conv2d(16, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (1): ReLU()
        (2): Conv2d(32, 32, 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)
      )
      (vgg_block_5): Sequential(
        (0): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (1): ReLU()
        (2): Conv2d(32, 32, 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)
      )
      (fc): Sequential(
        (0): FlattenLayer()
        (1): Linear(in_features=1568, out_features=256, bias=True)
        (2): ReLU()
        (3): Dropout(p=0.5, inplace=False)
        (4): Linear(in_features=256, out_features=256, bias=True)
        (5): ReLU()
        (6): Dropout(p=0.5, inplace=False)
        (7): Linear(in_features=256, out_features=10, bias=True)
      )
    )
    
  • 用缩小模型对mnist数据集进行训练

  • import sys
    import torchvision
    batch_size = 32
    # 如出现“out of memory”的报错信息,可减小batch_size或resize
    def load_data_fashion_mnist(batch_size, resize=None, root='~/Datasets/FashionMNIST'):
        """Download the fashion mnist dataset and then load into memory."""
        trans = []
        if resize:
            trans.append(torchvision.transforms.Resize(size=resize)) # 统一需要进行图像尺度缩放
        trans.append(torchvision.transforms.ToTensor())   # 将图像数据转为tensor张量
        transform = torchvision.transforms.Compose(trans)  # 加一层compose方法,方便传入执行
        mnist_train = torchvision.datasets.FashionMNIST(root=root, train=True, download=True, transform=transform)
        mnist_test = torchvision.datasets.FashionMNIST(root=root, train=False, download=True, transform=transform)
        if sys.platform.startswith('win'):
            num_workers = 0  # 0表示不用额外的进程来加速读取数据
        else:
            num_workers = 4
        train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers)
        test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)
        return train_iter, test_iter
    train_iter, test_iter = load_data_fashion_mnist(batch_size, resize=224)
    def evaluate_accuracy(data_iter, net, device=None):
        if device is None and isinstance(net, torch.nn.Module):
            # 如果没指定device就使用net的device
            device = list(net.parameters())[0].device 
        acc_sum, n = 0.0, 0
        with torch.no_grad():
            for X, y in data_iter:
                if isinstance(net, torch.nn.Module):
                    net.eval() # 评估模式, 这会关闭dropout
                    acc_sum += (net(X.to(device)).argmax(dim=1) == y.to(device)).float().sum().cpu().item()
                    net.train() # 改回训练模式
                else: # 自定义的模型, 3.13节之后不会用到, 不考虑GPU
                    if('is_training' in net.__code__.co_varnames): # 如果有is_training这个参数
                        # 将is_training设置成False
                        acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item() 
                    else:
                        acc_sum += (net(X).argmax(dim=1) == y).float().sum().item() 
                n += y.shape[0]
        return acc_sum / n
    def train_mnist(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs):
        net = net.to(device)
        print("training on ", device)
        loss = torch.nn.CrossEntropyLoss()  # 设置损失函数
        for epoch in range(num_epochs):
            train_l_sum, train_acc_sum, n, batch_count, start = 0.0, 0.0, 0, 0, time.time()
            for X, y in train_iter:
                X = X.to(device)  # 将图像数据传入到设备上
                y = y.to(device)# 将标签数据传入到设备上
                y_hat = net(X)  # 将图像数据传入到网络里,计算输出
                l = loss(y_hat, y)  # 根据之前选的loss计算出损失差值
                optimizer.zero_grad()  # 梯度清零,不影响新一轮得训练
                l.backward()      # 损失反向传播
                optimizer.step() # 这个方法会更新所有的参数
                train_l_sum += l.cpu().item()  # 把每个batch的损失累加起来
                train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
                n += y.shape[0]
                batch_count += 1
            test_acc = evaluate_accuracy(test_iter, net)  # 验证
            print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'
                  % (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start))
    lr, num_epochs = 0.001, 5
    optimizer = torch.optim.Adam(net.parameters(), lr=lr)  # 优化器定义
    train_mnist(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)
    
  • training on  cpu
    epoch 1, loss 2.3030, train acc 0.098, test acc 0.100, time 577.9 sec
    epoch 2, loss 2.3028, train acc 0.100, test acc 0.100, time 556.4 sec
    epoch 3, loss 2.3028, train acc 0.099, test acc 0.100, time 545.4 sec
    epoch 4, loss 2.3028, train acc 0.099, test acc 0.100, time 542.8 sec
    epoch 5, loss 2.3028, train acc 0.098, test acc 0.100, time 544.6 sec
    
  • 上面的训练结果是有问题的,可以看到测试精度一直是0.1,mnist十分类问题相当于乱猜。本文先复现模型,具体情况需要后续自查。

你可能感兴趣的:(深度学习,深度学习,模型复现,特征提取网络,pytorch,计算机视觉)