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

创新点: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)

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
深度学习是一种机器学习的分支,其目标是通过模仿人脑神经网络的工作方式来模拟和理解人类的智能行为。TensorFlow是一个强大的深度学习框架,它提供了丰富的工具和函数来构建和训练神经网络模型。 在图像领域中,一项有趣的实验是图像风格迁移。这是一种技术,将一张图像的风格应用于另一张图像,创造出一幅以第一幅图像风格为基础的新图像。其中VGG-19是一种深度卷积神经网络模型,被广泛用于图像识别和图像风格迁移任务。 图像风格迁移实验基于VGG-19模型的步骤如下:首先,我们需要将待迁移的风格图像和内容图像加载到模型中。然后,通过计算内容图像和目标图像之间的差异来定义一个损失函数。该损失函数包括内容损失和风格损失两部分,内容损失用于保留内容图像的特征,风格损失用于学习风格图像的特征。 接下来,我们使用梯度下降的优化算法来最小化损失函数,从而生成目标图像。在每次迭代中,我们根据当前生成的图像的梯度来更新输入图像。 在实验过程中,我们可以观察到生成图像逐渐采用了风格图像的特征,并保留了内容图像的主要结构。通过调整不同的参数和迭代次数,我们可以获得不同风格和质量的图像。 综上所述,VGG-19模型的图像风格迁移实验利用了深度学习和TensorFlow的强大功能。它为我们提供了一种有趣的方式来创造具有不同风格的图像,并有助于我们更深入地理解和应用深度学习的原理和技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值