VGG-通过重复使用简单的基础块来构建深度模型

本文深入剖析了VGG块的设计,涉及卷积层堆叠、最大池化、深度影响及与AlexNet的对比,展示了在Fashion-MNIST上的应用。
摘要由CSDN通过智能技术生成

创新点:VGG块

1.模型结构

VGG块的组成规律是:连续使用数个相同的填充为1、窗口形状为 3x3的卷积层后接上一个步幅为 2、窗口形状为 2x2 的最大池化层。卷积层保持输入的高和宽不变,而池化层则对其减半。

VGG有两种结构,分别是VGG16(16指的是conv+fc的总层数是16,不包括max pool的层数!)和VGG19,两者并没有本质上的区别,只是网络深度不一样

VGG16:整个网络有5个vgg-block块和5个maxpool层逐个相连,然后进入FC层,直到最后1000路softmax输出

VGG都有相同的224×224×3的input+5个maxpool层+3层fc全连接层,区别在于中间的Vgg-block块的设计不同

输入层 输入为224×224×3 三通道的图像。

第1个vgg block

  1. conv层 输入为224×224×3,经过64kernel size为3×3×3的filter,stride = 1,padding=same卷积后得到shape为224×224×64的block层(指由conv构成的vgg-block)。
  2. Max-pooling层: 输入为224×224×64,经过pool size=2,stride=2   减半池化后得到尺寸为112×112×64的池化层

第2个vgg block

  1. conv层: 输入尺寸为112×112×64,经1283×3×64的filter卷积,得到112×112×128的block层。
  2. Max-pooling输入为112×112×128,经pool size = 2,stride = 2  减半池化后得到尺寸为56×56×128的池化层。

第3个vgg block

  1. conv层: 输入尺寸为56×56×128,经2563×3×128的filter卷积,得到56×56×256的block层。
  2. Max-pooling层: 输入为56×56×256,经pool size = 2,stride = 2 减半池化后得到尺寸为28×28×256的池化层。

第4个vgg block

  1. conv层: 输入尺寸为28×28×256,经5123×3×256的filter卷积,得到28×28×512的block层。
  2. Max-pooling层: 输入为28×28×512,经pool size = 2,stride = 2 减半池化后得到尺寸为14×14×512的池化层。

第5个vgg block

  1. conv层:输入尺寸为14×14×512,经5123×3×512的filter卷积,得到14×14×512的block层。
  2. Max-pooling层: 输入为14×14×512,经pool size = 2,stride = 2  减半池化后得到尺寸为7×7×512的池化层。该层后面还隐藏了flatten操作,通过展平得到7×7×512=25088个参数后与之后的全连接层相连。

3个fc全连接层

  1. 第14~16层: 第14~16层神经元个数分别为4096,4096,1000。其中前两层在使用relu后还使用了Dropout对神经元随机失活,最后一层全连接层用softmax输出1000个分类

2.特点

        1.vgg-block内的卷积层都是同结构的 意味着输入和输出的尺寸一样,且卷积层可以堆叠复用,通过统一的size为3×3的kernel size + stride1 + padding(same)实现。 

        2.maxpool层将前一层(vgg-block层)的特征缩减一半 使得尺寸缩减的很规整,从224-112-56-28-14-7。通过pool size2 + stride2实现 

        3.深度较深,参数量够大 较深的网络层数使得训练得到的模型分类效果优秀,但是较大的参数对训练和模型保存提出了更大的资源要求。

        4.较小的filter size/kernel size **这里全局的kernel size都为3×3,相比以前的网络模型来说,尺寸足够小。

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

3.改进:

  1. 相比AlexNet的一个改进是采用连续的几个3x3的卷积核代替AlexNet中的较大卷积核(11x11,7x7,5x5)使用了3个3x3卷积核来代替7x7卷积核,使用了2个3x3卷积核来代替5*5卷积核,这样做的主要目的是在保证具有相同感知野的条件下,提升了网络的深度,在一定程度上提升了神经网络的效果
  2. AlexNet相比,深度更深,参数更多(1.38亿),效果和可移植性更好。
  3. VGG的网络结构中(除一个特例外)都不包含AlexNet中提及的LRN(局部响应正则化),因为作者发现其并不能提高性能,反而会带来内存消耗和计算时间的增加
为什么使用2个3x3卷积核可以来代替5*5卷积核?

         5x5卷积看做一个小的全连接网络在5x5区域滑动,可以先用一个3x3的卷积滤波器卷积,然后再用一个全连接层连接这个3x3卷积输出,这个全连接层也可以看做一个3x3卷积层。这样就可以用两个3x3卷积级联(叠加)起来代替一个 5x5卷积

为什么这里使用了filter size为3×3叠加卷积层的block?而不是直接用单层的7×7的卷积层?

    1.  3个卷积层叠加,带来了3次relu的非线性校正,比使用一个relu的单层layer更具有识别力;

  1.      2.  降低了参数量,7×7×C×C 比3×3×C×C的参数量大81%。

4. 总结:

VGGNet通过在传统卷积神经网络模型(AlexNet)上的拓展,发现除了较为复杂的模型结构的设计外,深度对于提高模型准确率很重要

VGG和之前的AlexNet相比,深度更深,参数更多,效果和可移植性更好,且模型设计的简洁而规律,从而被广泛使用。

  1. 小尺寸的filter(3×3)不仅使参数更少,效果也并不弱于大尺寸filter如5×5
  2. 数据增强和图像裁剪如multi scale等方式有助于提高网络准确率
  3. AlexNet中的局部响应归一化作用不大

5.代码实现

import torch
from torch import nn, optim
import torchvision
import sys
from time import time
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print("using {} device.".format(device))  

定义VGG块 

'''定义能够指定卷积层的数量和输入输出通道数的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)

搭建VGG11模型

# 指定了每个VGG块里卷积层个数和输入输出通道数,前2块使用单卷积层,而后3块使用双卷积层
# 第一块的输入输出通道分别是1(因为下面要使用的Fashion-MNIST数据的通道数为1)和64
conv_arch = ((1, 1, 64), (1, 64, 128), (2, 128, 256),(2, 256, 512), (2, 512, 512))
fc_features = 512*7*7 #经过5个vgg_block,宽高会减半5次,变成224/2**5=224/32=7
fc_hidden_units = 4096

'''对 x 的形状转换 '''
class FlattenLayer(nn.Module):
    def __init__(self):
        super(FlattenLayer, self).__init__()
    def forward(self, x):
        return x.view(x.shape[0], -1)
    
def vgg11(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 = vgg11(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)

 测试

#构造一个通道数更小,或者说更窄的网络在Fashion-MNIST数据集上进行训练
ratio = 4
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 = vgg11(small_conv_arch, fc_features//ratio, fc_hidden_units//ratio)
print(net)

 读取数据集
'''读取数据集'''
def load_data_fashion_mnist(batch_size, resize=None):
    
    if sys.platform.startswith('win'):
        num_workers = 0  # 0表示不用额外的进程来加速读取数据
    else:
        num_workers = 4
        
    '''定义数据预处理的转换函数列表'''    
    trans = []
    if resize: #判断是否需要进行图像尺寸调整(resize)
        trans.append(torchvision.transforms.Resize(size=resize)) 
        #将torchvision.transforms.Resize转换函数添加到转换函数列表trans中,并指定目标尺寸为resize
    trans.append(torchvision.transforms.ToTensor())
    # 将torchvision.transforms.ToTensor转换函数添加到转换函数列表trans中。这个函数用于将图像数据转换为张量,并且按照通道顺序排列(RGB)
    transform = torchvision.transforms.Compose(trans) 
    #通过torchvision.transforms.Compose函数将转换函数列表trans组合成一个转换操作
    
    mnist_train = torchvision.datasets.FashionMNIST(root='data/FashionMNIST',
                                                    train=True,
                                                    download=True,
                                                    transform=transform)
    mnist_test = torchvision.datasets.FashionMNIST(root='data/FashionMNIST',
                                                   train=False,
                                                   download=True,
                                                   transform=transform)
    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_train,
                                             batch_size=batch_size,
                                             shuffle=False,
                                             num_workers=num_workers) 
    return train_iter, test_iter
'''确保计算使用的数据和模型同在内存或显存上'''
def train_ch5(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= 0.0, 0.0, 0,0
        # 分别表示训练损失总和、训练准确度总和、样本数
        start = time()
        for X, y in train_iter:
            X = X.to(device)
            y = y.to(device)
            '''前向传播'''
            y_hat = net(X) 
            #由于变量 l 并不是一个标量,所以我们可以调用 .sum() 将其求和得到一个标量
            '''计算损失'''
            l = loss(y_hat, y).sum()
            '''梯度清零'''
            if optimizer is not None:
                optimizer.zero_grad()
            elif params is not None and params[0].grad is not None:
                for param in params:
                    param.grad.data.zero_()
            '''反向传播'''
            l.backward() # 运行 l.backward() 得到该变量有关模型参数的梯度
            
            if optimizer is None:
                d2l.sgd(params, lr, batch_size)
            else: 
                '''更新参数'''
                optimizer.step()
            '''计算精度'''  
            train_l_sum += l.cpu().item()  
            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()-start))
训练模型
'''训练模型'''
batch_size = 256
train_iter, test_iter = load_data_fashion_mnist(batch_size, resize=224)
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
train_ch5(net, train_iter, test_iter, batch_size, optimizer,device, num_epochs)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值