VGGNet学习笔记

一、简介

VGGNet由牛津大学的视觉几何组(Visual Geometry Group)提出,获得了2014年ILSVRC竞赛的分类任务第二名和定位任务第一名。主要贡献在于证明了使用3x3小卷积核,增加网络深度可以有效提升模型性能,并且对于其他数据集也有很好的泛化性能。VGGNet探索了卷积神经网络的深度与其性m能之间的关系,成功地构筑了16~19层深的卷积神经网络,证明了增加网络的深度能够在一定程度上影响网络最终的性能,使错误率大幅下降,同时拓展性又很强,迁移到其它图片数据上的泛化性也非常好,到目前为止,VGG仍然被用来提取图像特征。

论文链接 :Very deep convolutional networks for large-scale image recognition

二、网络结构

VGGNet一共有6种不同的网络结构,分别是A、A-LRN、B、C、D,这6种网络结构相似,都是由5层卷积层、3层全连接层组成,其中区别在于每个卷积层的子层数量不同,从A至E依次增加(子层数量从1到4),总的网络深度从11层到19层(添加的层以粗体显示),表格中的卷积层参数表示为conv<感受野大小>-<通道数>,例如con3-128,表示使用3x3的卷积核,通道数为128。为了简洁起见,在表格中不显示ReLU激活功能。其中,网络结构D就是著名的VGG16,网络结构E就是著名的VGG19。

以VGG16为例进行分析,其网络结构图如下:

输入是大小为224*224的RGB图像,预处理(preprocession)时计算出三个通道的平均值,在每个像素上减去平均值(处理后迭代更少,更快收敛)。

图像经过一系列卷积层处理,在卷积层中使用了非常小的3x3卷积核,在有些卷积层里则使用了1x1的卷积核。

卷积层步长(stride)设置为1个像素,3x3卷积层的填充(padding)设置为1个像素。池化层采用max pooling,共有5层,在一部分卷积层后,max-pooling的窗口是2x2,步长设置为2。

卷积层之后是三个全连接层(fully-connected layers,FC)。前两个全连接层均有4096个通道,第三个全连接层有1000个通道,用来分类。所有网络的全连接层配置相同。

全连接层后是Softmax,用来分类。

所有隐藏层(每个conv层中间)都使用ReLU作为激活函数。VGGNet不使用局部响应标准化(LRN),这种标准化并不能在ILSVRC数据集上提升性能,却导致更多的内存消耗和计算时间(LRN:Local Response Normalization,局部响应归一化,用于增强网络的泛化能力)。

VGG16详细处理过程如下:
1、输入224x224x3的图片,经64个3x3的卷积核作两次卷积+ReLU,卷积后的尺寸变为224x224x64
2、作max pooling(最大化池化),池化单元尺寸为2x2(效果为图像尺寸减半),池化后的尺寸变为112x112x64
3、经128个3x3的卷积核作两次卷积+ReLU,尺寸变为112x112x128
4、作2x2的max pooling池化,尺寸变为56x56x128
5、经256个3x3的卷积核作三次卷积+ReLU,尺寸变为56x56x256
6、作2x2的max pooling池化,尺寸变为28x28x256
7、经512个3x3的卷积核作三次卷积+ReLU,尺寸变为28x28x512
8、作2x2的max pooling池化,尺寸变为14x14x512
9、经512个3x3的卷积核作三次卷积+ReLU,尺寸变为14x14x512
10、作2x2的max pooling池化,尺寸变为7x7x512
11、与两层1x1x4096,一层1x1x1000进行全连接+ReLU(共三层)
12、通过softmax输出1000个预测结果

简化结构图如下:

三、VGGNet网络特点

1、结构简洁
VGG结构由5层卷积层、3层全连接层、softmax输出层构成,层与层之间使用max-pooling(最大池化)分开,所有隐层的激活单元都采用ReLU函数。
2、小卷积核和多卷积子层
VGG使用多个较小卷积核(3x3)的卷积层代替一个卷积核较大的卷积层,一方面可以减少参数,另一方面相当于进行了更多的非线性映射,可以增加网络的拟合/表达能力。
小卷积核是VGG的一个重要特点,虽然VGG是在模仿AlexNet的网络结构,但没有采用AlexNet中比较大的卷积核尺寸(如7x7),而是通过降低卷积核的大小(3x3),增加卷积子层数来达到同样的性能(VGG:从1到4卷积子层,AlexNet:1子层)。
VGG的作者认为两个3x3的卷积堆叠获得的感受野大小,相当一个5x5的卷积;而3个3x3卷积的堆叠获取到的感受野相当于一个7x7的卷积。这样可以增加非线性映射,也能很好地减少参数(例如7x7的参数为49个,而3个3x3的参数为27)
3、小池化核
相比AlexNet的3x3的池化核,VGG全部采用2x2的池化核。
4、通道数多
VGG网络第一层的通道数为64,后面每层都进行了翻倍,最多到512个通道,通道数的增加,使得更多的信息可以被提取出来。
5、层数更深、特征图更宽
由于卷积核专注于扩大通道数、池化专注于缩小宽和高,使得模型架构上更深更宽的同时,控制了计算量的增加规模。
6、全连接转卷积(测试阶段)
这也是VGG的一个特点,在网络测试阶段将训练阶段的三个全连接替换为三个卷积,使得测试得到的全卷积网络因为没有全连接的限制,因而可以接收任意宽或高为的输入,这在测试阶段很重要。
如输入图像是224x224x3,若后面三个层都是全连接,那么在测试阶段就只能将测试的图像全部都要缩放大小到224x224x3,才能符合后面全连接层的输入数量要求,这样就不便于测试工作的开展。
而“全连接转卷积”,替换过程如下:

例如7x7x512的层要跟4096个神经元的层做全连接,则替换为对7x7x512的层作通道数为4096、卷积核为1x1的卷积。

四、存在的问题

1、虽然 VGGNet 减少了卷积层参数,但实际上其参数空间比 AlexNet 大,其中绝大多数的参数都是来自于第一个全连接层,耗费更多计算资源。在随后的 NIN 中发现将这些全连接层替换为全局平均池化,对于性能影响不大,同时显著降低了参数数量。

2、采用 Pre-trained 方法训练的 VGG model(主要是 D 和 E),相对其他的方法参数空间很大,所以训练一个 VGG 模型通常要花费更长的时间,所幸有公开的 Pre-trained model 让我们很方便的使用。

五、Pytorch实现

#encoding=utf-8

import time
import torch
import torch.nn as nn
import torchvision
import torch.optim as optim

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 构造一个将句子展开的层
class FlattenLayer(nn.Module):
    def __init__(self):
        super(FlattenLayer, self).__init__()
    def forward(self, x):
        return x.view(x.shape[0], -1)

# 构造一个VGG网络的块
def vgg_block(num_convs,in_channels,out_channels):
    vgg_blocks = []
    for i in range(num_convs):
        if i == 0:
            vgg_blocks.append(nn.Conv2d(in_channels,out_channels,kernel_size=3,padding=1))
        else:
            vgg_blocks.append(nn.Conv2d(out_channels,out_channels,kernel_size=3,padding=1))
        vgg_blocks.append(nn.ReLU())
    vgg_blocks.append(nn.MaxPool2d(kernel_size=2,stride=2))
    # * 表示对List进行解码
    return nn.Sequential(*vgg_blocks)

# 下面构造完整的VGG网络
# 整个VGG由5个块组成
# 第1,2个块 是单个的VGG,输入—输出通道(1,64)  (64,128)
# 第3,4,5块是 两个VGG构成,输入—输出通道 (128 256) (256 512) (512 512)
conv_arch = ((1,1,64),(1,64,128),(2,128,256),(2,256,512),(2,512,512))
input_features = 512 * 7 * 7
hidden_features = 4096

def vgg(conv_arch,input_features,hidden_features=4096):
    net = nn.Sequential()
    for i,(num_convs,in_channels,out_channels) in enumerate(conv_arch):
        net.add_module('vgg_block_'+str(i),vgg_block(num_convs,in_channels,out_channels))
    net.add_module('fc',nn.Sequential(FlattenLayer(),
                                      nn.Linear(input_features,hidden_features),
                                      nn.ReLU(),
                                      nn.Dropout(0.5),
                                      nn.Linear(hidden_features,hidden_features),
                                      nn.ReLU(),
                                      nn.Dropout(0.5),
                                      nn.Linear(hidden_features,10)))
    return net
#定义数据加载
def load_data(batch_size,resize=None,root='~/Datasets/FashionMNIST'):
    trans = []
    if resize:
        trans.append(torchvision.transforms.Resize(size=resize))
    trans.append(torchvision.transforms.ToTensor())
    transform = torchvision.transforms.Compose(trans)
    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)
    train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=4)
    test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=4)
    return train_iter,test_iter
#定义准确率估计
def evaluate_accuracy(data_iter,net,device=None):
    if device is None and isinstance(net,torch.nn.Module):
        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()
                acc_sum += (net(X.to(device)).argmax(dim=1) == y.to(device)).float().sum().cpu().item()
                net.train()
            else:
                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(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_loss_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)
            loss_value = loss(y_hat,y)
            optimizer.zero_grad()
            loss_value.backward()
            optimizer.step()
            train_loss_sum += loss_value
            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, loss_value / batch_count, train_acc_sum / n, test_acc, time.time() - start))


#实例化
net = vgg(conv_arch,input_features,hidden_features)

#获取数据集
batch_size = 1
train_iter,test_iter = load_data(batch_size,resize=224)

# 定义超参数
lr = 0.001
num_epoches = 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)

if __name__ == '__main__':
    train(net, train_iter, test_iter, batch_size, optimizer, device, num_epoches)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值