• 【神经网络】【VggNet】


    1、引言

    卷积神经网络是当前最热门的技术,我想深入地学习这门技术,从他的发展历史开始,了解神经网络算法的兴衰起伏;同时了解他在发展过程中的**里程碑式算法**,能更好的把握神经网络发展的未来趋势,了解神经网络的特征。
    之前的LeNet为以后的神经网络模型打下了一个基础的框架,真正让神经网络模型在外界广泛引起关注的还是AlexNet,在AlexNet之后也出现了相应对他的改进,或多或少会有一些效果。但是ZFNet是在AlexNet上的改进,他的论文对神经网络的各个层级的作用,做了十分详细的阐述,为如何优化模型,怎样“有理有据”地调节参数指出了一个方向,这是他最大的一个贡献。VggNet也是在2014年的ImageNet的定位赛和分类赛上获得了第一名和第二名,他在原来卷积神经网络结构的基础上,大大增加了网络的深度,最后取得了不错的成绩。
    VggNet论文原文下载地址
    VggNet论文中文翻译

    2、VGGNet提出背景

    VGG Net由牛津大学的视觉几何组(Visual Geometry Group)和 Google DeepMind公司的研究员一起研发的的深度卷积神经网络,在 ILSVRC 2014 上取得了第二名的成绩,将 Top-5错误率降到7.3%。它主要的贡献是展示出网络的深度(depth)是算法优良性能的关键部分。目前使用比较多的网络结构主要有ResNet(152-1000层),GooleNet(22层),VGGNet(19层),大多数模型都是基于这几个模型上改进,采用新的优化算法,多模型融合等。到目前为止,VGG Net 依然经常被用来提取图像特征。
    注:
    ImageNet是一个在2009年创建的图像数据集,从2010年开始到2017年举办了七届的ImageNet 挑战赛——ImageNet Large Scale Visual Recognition ChallengeI (LSVRC),在这个挑战赛上诞生了AlexNet、ZFNet、OverFeat、VGG、Inception、ResNet、WideResNet、FractalNet、DenseNet、ResNeXt、DPN、SENet 等经典模型。

    3、VGGNet的模型详解

    在这里插入图片描述
    VGG 的结构与 AlexNet 类似,区别是深度更深,但形式上更加简单。VGG由5层卷积层、3层全连接层、1层softmax输出层构成,层与层之间使用maxpool(最大化池)分开,所有隐藏层的激活单元都采用ReLU函数。作者在原论文中,根据卷积层不同的子层数量,设计了A、A-LRN、B、C、D、E这6种网络结构。
    在这里插入图片描述

    VGG16总共包含16个子层,第1层卷积层由2个conv3-64组成,第2层卷积层由2个conv3-128组成,第3层卷积层由3个conv3-256组成,第4层卷积层由3个conv3-512组成,第5层卷积层由3个conv3-512组成,然后是2个FC4096,1个FC1000。总共16层,这也就是VGG16名字的由来。

    3.1 输入层

    VGG输入图片的尺寸是224x224x3。

    3.2 第1层卷积层

    在这里插入图片描述

    第1层卷积层由2个conv3-64组成。
    该层的处理流程是:卷积–>ReLU–> 卷积–>ReLU–>池化
    卷积:输入是224x224x3,使用64个3x3x3的卷积核进行卷积,padding=1,stride=1,根据公式:
    (input_size + 2 x padding - kernel_size) / stride + 1=(224+2 x 1-3)/1+1=224
    得到输出是224x224x64。
    ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。
    卷积:输入是224x224x64,使用64个3x3x64的卷积核进行卷积,padding=1,stride=1,根据公式:
    (input_size + 2 x padding - kernel_size) / stride + 1=(224+2 x 1-3)/1+1=224
    得到输出是224 x 224 x 64。
    ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。
    池化:使用2 x 2,stride=2的池化单元进行最大池化操作(max pooling)。根据公式:
    (224+2 x 0-2)/2+1=112
    每组得到的输出为112 x 112 x 64。

    3.3 第2层卷积层

    在这里插入图片描述

    第2层卷积层由2个conv3-128组成。
    该层的处理流程是:卷积–>ReLU–> 卷积–>ReLU–>池化
    卷积:输入是112x112x64,使用128个3x3x64的卷积核进行卷积,padding=1,stride=1,根据公式:
    (input_size + 2 x padding - kernel_size) / stride + 1=(112+2 x 1-3)/1+1=112
    得到输出是112x112x128。
    ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。
    卷积:输入是112x112x128,使用128个3x3x128的卷积核进行卷积,padding=1,stride=1,根据公式:
    (input_size + 2 x padding - kernel_size) / stride + 1=(112+2 x 1-3)/1+1=112
    得到输出是112x112x128。
    ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。
    池化:使用2 x 2,stride=2的池化单元进行最大池化操作(max pooling)。根据公式:
    (112+2*0-2)/2+1=56
    每组得到的输出为56 x 56 x 128。

    3.4 第3层卷积层

    在这里插入图片描述

    第3层卷积层由3个conv3-256组成。
    该层的处理流程是:卷积–>ReLU–> 卷积–>ReLU–>池化
    卷积:输入是56x56x128,使用256个3x3x128的卷积核进行卷积,padding=1,stride=1,根据公式:
    (input_size + 2 x padding - kernel_size) / stride + 1=(56+2 x 1-3)/1+1=56
    得到输出是56x56x256。
    ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。
    卷积:输入是56 x 56 x 256,使用256个3 x 3 x 256的卷积核进行卷积,padding=1,stride=1,根据公式:
    (input_size + 2 x padding - kernel_size) / stride + 1=(56+2 x 1-3)/1+1=56
    得到输出是56x56x256。
    ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。
    池化:使用2x2,stride=2的池化单元进行最大池化操作(max pooling)。根据公式:
    (56+2 x 0-2)/2+1=28
    每组得到的输出为28 x 28 x 256。

    3.5 第4层卷积层

    在这里插入图片描述

    第4层卷积层由3个conv3-512组成。
    该层的处理流程是:卷积–>ReLU–> 卷积–>ReLU–>池化
    **卷积:**输入是28x28x256,使用512个3x3x256的卷积核进行卷积,padding=1,stride=1,根据公式:
    (input_size + 2 x padding - kernel_size) / stride + 1=(28+2 x 1-3)/1+1=28
    得到输出是28x28x512。
    **ReLU:**将卷积层输出的FeatureMap输入到ReLU函数中。
    卷积:输入是28x28x512,使用512个3x3x512的卷积核进行卷积,padding=1,stride=1,根据公式:
    (input_size + 2 x padding - kernel_size) / stride + 1=(28+2 x 1-3)/1+1=28
    得到输出是28x28x512。
    ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。
    池化:使用2x2,stride=2的池化单元进行最大池化操作(max pooling)。根据公式:
    (28+2 x 0-2)/2+1=14
    每组得到的输出为14x14x512。

    3.6 第5层卷积层

    在这里插入图片描述

    第5层卷积层由3个conv3-512组成。
    该层的处理流程是:卷积–>ReLU–> 卷积–>ReLU–>池化
    卷积:输入是14x14x512,使用512个3x3x512的卷积核进行卷积,padding=1,stride=1,根据公式:
    (input_size + 2 x padding - kernel_size) / stride + 1=(14+2x1-3)/1+1=14
    得到输出是14x14x512。
    ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。
    卷积:输入是14x14x512,使用512个3x3x512的卷积核进行卷积,padding=1,stride=1,根据公式:
    (input_size + 2 x padding - kernel_size) / stride + 1=(14+2x1-3)/1+1=14
    得到输出是14x14x512。
    ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。
    池化:使用2x2,stride=2的池化单元进行最大池化操作(max pooling)。
    根据公式:(14+2x0-2)/2+1=7
    每组得到的输出为7x7x512。

    3.7 第1层全连接层

    在这里插入图片描述

    第1层全连接层FC4096由4096个神经元组成。
    该层的处理流程是:FC–>ReLU–>Dropout
    **FC:**输入是7x7x512的FeatureMap,展开为77512的一维向量,即77512个神经元,输出为4096个神经元。
    **ReLU:**这4096个神经元的运算结果通过ReLU激活函数中。
    Dropout:随机的断开全连接层某些神经元的连接,通过不激活某些神经元的方式防止过拟合。

    3.8 第2层全连接层

    在这里插入图片描述

    第2层全连接层FC4096由4096个神经元组成。
    该层的处理流程是:FC–>ReLU–>Dropout
    **FC:**输入是4096个神经元,输出为4096个神经元。
    ReLU:这4096个神经元的运算结果通过ReLU激活函数中。
    **Dropout:**随机的断开全连接层某些神经元的连接,通过不激活某些神经元的方式防止过拟合。

    3.9 第3层全连接层

    在这里插入图片描述

    第3层全连接层FC1000由1000个神经元组成,对应ImageNet数据集的1000个类别。
    该层的处理流程是:FC。
    FC:输入是4096个神经元,输出为1000个神经元。

    3.10 softmax层

    在这里插入图片描述

    该层的流程为:Softmax
    Softmax:这1000个神经元的运算结果通过Softmax函数中,输出1000个类别对应的预测概率值。

    4、VGGNet的创新之处

    4.1结构简单

    VGGNet的结构十分简洁,由5个卷积层、3个全连接层和1个softmax层构成,层与层之间使用最大池化连接,隐藏层之间使用的激活函数全都是ReLU。并且网络的参数也是整齐划一的,赏心悦目。

    4.2使用小卷积核

    VGGNet使用含有多个小型的3×3卷积核的卷积层来代替卷积核较大的卷积层。2个3×3的卷积核堆叠的感受野相当于一个5×5的卷积核的感受野,而3个3×3的卷积核堆叠的感受野则相当于一个7×7的卷积核的感受野。因此,采用多个小型卷积核,既能减少参数的数量,又能增强网络的非线性映射从而提升网络的表达能力。
    为什么可以增加网络的非线性?我们知道激活函数的作用就是给神经网络增加非线性因素,使其可以拟合任意的函数,每个卷积操作后都会通过ReLU激活,ReLU函数就是一个非线性函数。下图展示了为什么使用2个3x3的卷积核可以代替5×5卷积核。
    在这里插入图片描述

    总结一下,使用多个3×3卷积堆叠的作用有两个:一是在不影响感受野的前提下减少了参数;二是增加了网络的非线性。

    4.3使用小滤波器

    与AlexNet相比,VGGNet在池化层全部采用的是2×2的小滤波器。

    4.4通道数更多,特征度更宽

    VGGNet的第一层有64个通道,后面的每一层都对通道进行了翻倍,最多达到了512个通道。由于每个通道都代表着一个feature map,这样就使更多的信息可以被提取出来。

    4.5将全连接层转换为卷积层

    这个特征是体现在VGGNet的测试阶段。在进行网络测试时,将训练阶段的3个全连接层替换为3个卷积层,使测试得到的网络没有全连接的限制,能够接收任意宽和高的输入。如果后面3个层都是全连接层,那么在测试阶段就只能将测试的图像全部缩放到固定尺寸,这样就不便于多尺度测试工作的开展

    在这里插入图片描述
    为什么这样替换之后就可以处理任意尺寸的输入图像了呢?因为1×1卷积一个很重要的作用就是调整通道数。如果下一层输入的特征图需要控制通道数为N,那么设置N个1×1卷积核就可以完成通道数的调整。比如最后需要1000个神经元用于分出1000个类别,那就在最后一层的前面使用1000个1×1的卷积核,这样的到的结果就是(1, 1, 1000)正好可以匹配。
    在这里插入图片描述

    5、VGGNet的代码实现

    from torchvision import transforms
    from torch.utils.data import Dataset, DataLoader
    from PIL import Image
    import numpy as np
    
    
    def Myloader(path):
        return Image.open(path).convert('RGB')
    
    
    # get a list of paths and labels.
    def init_process(path, lens):
        data = []
        name = find_label(path)
        for i in range(lens[0], lens[1]):
            data.append([path % i, name])
    
        return data
    
    
    class MyDataset(Dataset):
        def __init__(self, data, transform, loader):
            self.data = data
            self.transform = transform
            self.loader = loader
    
        def __getitem__(self, item):
            img, label = self.data[item]
            img = self.loader(img)
            img = self.transform(img)
            return img, label
    
        def __len__(self):
            return len(self.data)
    
    
    def find_label(str):
        """
        Find image tags based on file paths.
    
        :param str: file path
        :return: image label
        """
        first, last = 0, 0
        for i in range(len(str) - 1, -1, -1):
            if str[i] == '%' and str[i - 1] == '.':
                last = i - 1
            if (str[i] == 'c' or str[i] == 'd') and str[i - 1] == '/':
                first = i
                break
    
        name = str[first:last]
        if name == 'dog':
            return 1
        else:
            return 0
    
    
    def load_data():
        print('data processing...')
        transform = transforms.Compose([
            transforms.RandomHorizontalFlip(p=0.3),
            transforms.RandomVerticalFlip(p=0.3),
            transforms.Resize((256, 256)),
            transforms.ToTensor(),
            transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))  # normalization
        ])
        path1 = 'data/training_data/cats/cat.%d.jpg'
        data1 = init_process(path1, [0, 500])
        path2 = 'data/training_data/dogs/dog.%d.jpg'
        data2 = init_process(path2, [0, 500])
        path3 = 'data/testing_data/cats/cat.%d.jpg'
        data3 = init_process(path3, [1000, 1200])
        path4 = 'data/testing_data/dogs/dog.%d.jpg'
        data4 = init_process(path4, [1000, 1200])
        data = data1 + data2 + data3 + data4   # 1400
        # shuffle
        np.random.shuffle(data)
        # train, val, test = 900 + 200 + 300
        train_data, val_data, test_data = data[:900], data[900:1100], data[1100:]
        train_data = MyDataset(train_data, transform=transform, loader=Myloader)
        Dtr = DataLoader(dataset=train_data, batch_size=50, shuffle=True, num_workers=0)
        val_data = MyDataset(val_data, transform=transform, loader=Myloader)
        Val = DataLoader(dataset=val_data, batch_size=50, shuffle=True, num_workers=0)
        test_data = MyDataset(test_data, transform=transform, loader=Myloader)
        Dte = DataLoader(dataset=test_data, batch_size=50, shuffle=True, num_workers=0)
    
        return Dtr, Val, Dte
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    import copy
    import os
    import random
    
    import numpy as np
    import torch
    import torch.nn as nn
    from torch import optim
    from torch.autograd import Variable
    from tqdm import tqdm
    
    from data_process import load_data
    import torch.nn.functional as F
    
    from torchvision.models import vgg16
    model_vgg = vgg16(pretrained=True)
    model_vgg.classifier._modules['6'] = nn.Sequential(nn.Linear(4096, 2))
    print(model_vgg)
    
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    
    def setup_seed(seed):
        os.environ['PYTHONHASHSEED'] = str(seed)
        torch.manual_seed(seed)
        torch.cuda.manual_seed_all(seed)
        np.random.seed(seed)
        random.seed(seed)
        torch.backends.cudnn.deterministic = True
    
    setup_seed(20)
    
    
    def get_val_loss(model, Val):
        model.eval()
        criterion = nn.CrossEntropyLoss().to(device)
        val_loss = []
        for (data, target) in Val:
            data, target = Variable(data).to(device), Variable(target.long()).to(device)
            output = model(data)
            loss = criterion(output, target)
            val_loss.append(loss.cpu().item())
    
        return np.mean(val_loss)
    
    
    def train(model_vgg):
        path = "model/vggnet.pkl"
        lr = 0.0001
        Dtr, Val, Dte = load_data()
        model = model_vgg.to(device)
        print('train...')
        epoch_num = 5
        best_model = model
        min_epochs = 5
        min_val_loss = 5
        optimizer = optim.Adam(model.parameters(), lr=lr)
        criterion = nn.CrossEntropyLoss().to(device)
        # criterion = nn.BCELoss().to(device)
        for epoch in tqdm(range(epoch_num), ascii=True):
            train_loss = []
            for batch_idx, (data, target) in enumerate(Dtr, 0):
                data, target = Variable(data).to(device), Variable(target.long()).to(device)
                # target = target.view(target.shape[0], -1)
                optimizer.zero_grad()
                output = model(data)
                loss = criterion(output, target)
                loss.backward()
                optimizer.step()
                train_loss.append(loss.cpu().item())
            # validation
            val_loss = get_val_loss(model, Val)
            model.train()
            if epoch + 1 > min_epochs and val_loss < min_val_loss:
                min_val_loss = val_loss
                best_model = copy.deepcopy(model)
    
            tqdm.write('Epoch {:03d} train_loss {:.5f} val_loss {:.5f}'.format(epoch, np.mean(train_loss), val_loss))
    
        torch.save(best_model.state_dict(), path)
        return best_model
    
    
    def test(model_vgg):
        Dtr, Val, Dte = load_data()
        path = "model/vggnet.pkl"
        model = model_vgg
        #device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        model.load_state_dict(torch.load(path),False)
        model = model.to(device)
    
        model.eval()
        total = 0
        current = 0
        for (data, target) in Dte:
            data, target = data.to(device), target.to(device)
            outputs = model(data)
            predicted = torch.max(outputs.data, 1)[1].data
            total += target.size(0)
            current += (predicted == target).sum()
    
        print('Accuracy:%d%%' % (100 * current / total))
    
    
    if __name__ == '__main__':
        train(model_vgg)
        test(model_vgg)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106

    6、总结

    书山有路勤为径,学海无涯苦作舟。

    7、参考文章

    7.1 手撕 CNN 经典网络之 VGGNet(理论篇)
    7.2 PyTorch搭建预训练AlexNet、DenseNet、ResNet、VGG实现猫狗图片分类

  • 相关阅读:
    面试准备-中文面试问答(非技术)
    Haproxy
    Vue3 中有场景是 reactive 能做而 ref 做不了的吗?
    R语言使用gt包和gtExtras包优雅地、漂亮地显示表格数据:使用gt包可视化表格数据,使其易于阅读和理解
    Java 知识点总结笔记(篇2)
    参数查阅~~~9.18
    深度学习(PyTorch)——循环神经网络(RNN)基础篇四
    C++广搜例题+代码+讲解(2)
    Linux 错误处理(字符设备基础三)
    2023-11-Rust
  • 原文地址:https://blog.csdn.net/zwh1298454060/article/details/134087494