VGGNet网络模型

论文下载地址:https://arxiv.org/pdf/1409.1556.pdf

这篇文章是以比赛为目的的,为了解决ImageNet分类和定位的问题,作者作了6组实验,对应于6个不同的模型, 这六个网络深度逐渐增加的同时,也有各自的特点。实验结果表明,最后两组,即VGG16和VGG19在分类和定位任务上效果最好,作者因此斩获2014年分类第二(第一是GoogLeNet),定位任务第一。其中,模型的名称——“VGG”代表了牛津大学的Oxford Visual Geometry Group,该小组隶属于1985年成立的Robotics Research Group,该Group研究范围包括了机器学习到移动机器人。

VGG网络的特点:

小卷积核:作者将卷积核全部替换为3x3,在AlexNet使用了11x11和5x5的大卷积,但是大多数还是3x3的小卷积,使用大的卷积核的原因是原始图像的尺寸很大,会有很多的信息冗余,所以为了使得纹理信息尽早被捕捉到,而后面更深的层数则是害怕会丢失较大局部范围内的特征相关性,后面的3x3是为了捕捉细节的变化,但是大尺寸卷积核虽然可以带来更大的感受野, 也意味着更多的参数,在仅考虑w不考虑b的情况下,5x5卷积核的参数是3x3卷积核的(5x5+1)/(3x3+1)=2.6倍,因此,在《Rethinking the Inception Architecture for Computer Vision》的作者提出了使用两个3x3卷积层来代替单个的5x5卷积层。

再看在计算量上,3x3卷积核也占据优势,假设输入为x,padding=0,stride=1,此时卷积计算公式output=input-kernel+1,5x5:有(x-5+1)^2个输出点,每个输出点对应5x5次乘法和5x5次加法,3x3:有(x-3+1)^2个输出点,每个对应3x3次乘法和3x3次加法,第二个3x3卷积在此基础上继续,求解得到,在x<22/7 和x>10时,都是两个3x3的参数量更小。

说完了计算量我们再来看一下感受野,这里给出一张VGG作者的PPT,作者在VGGNet的实验中只用了两种卷积核大小:1x1和3x3。作者认为两个3x3的卷积堆叠获得的感受野大小,相当一个5x5的卷积;而3个3x3卷积的堆叠获取到的感受野相当于一个7x7的卷积。

小池化核:作者采用了比Alexnet的3x3小的池化核,VGG全部为2x2的池化核;

这里的“小”是相对于AlexNet的3x3的池化核来说的。不过在说池化前,先说一下CS231n的博客里的描述网络结构的layer pattern,一般常见的网络都可以表示为:INPUT -> [[CONV -> RELU]*N -> POOL?]*M -> [FC -> RELU]*K -> FC的形式,其中,?表示pool是一个可选项。这样的pattern因为可以对小卷积核堆叠,很自然也更适合描述深层网络的构建,例如INPUT -> FC表示一个线性分类器。不过从layer pattern中的[[CONV -> RELU]*N -> POOL]*M部分,可以看出卷积层一般后面接完激活函数就紧跟池化层。

2012年的AlexNet,其pooling的kernel size全是奇数,里面所有池化采用kernel size为3x3,stride为2的max-pooling。而VGGNet所使用的max-pooling的kernel size均为2x2,stride为2的max-pooling。pooling kernel size从奇数变为偶数。小kernel带来的是更细节的信息捕获,且是max-pooling更见微的同时进一步知躇。

在当时也有average pooling,但是在图像任务上max-pooling的效果更胜一筹,所以图像大多使用max-pooling。在这里我认为max-pooling更容易捕捉图像上的变化,梯度的变化,带来更大的局部信息差异性,更好地描述边缘、纹理等构成语义的细节信息,这点尤其体现在网络可视化上。

层数更深特征图更宽:基于前两点外,由于卷积核专注于扩大通道数,池化专注于缩小宽和高,使得模型架构上更深更宽的同时,计算量的增加放缓;feature map维度的整体变化过程是:先将local信息压缩,并分摊到channel层级,然后无视channel和local,通过fc这个变换再进一步压缩为稠密的feature map,这样对于分类器而言有好处也有坏处,好处是将local信息隐藏于/压缩到feature map中,坏处是信息压缩都是有损失的,相当于local信息被破坏了(分类器没有考虑到,其实对于图像任务而言,单张feature map上的local信息还是有用的)。

但其实不难发现,卷积只增加feature map的通道数,而池化只减少feature map的宽高。如今也有不少做法用大stride卷积去替代池化,未来可能没有池化。

VGG配置:

 

tensorflow实现模型:

import tensorflow as tf
import numpy as np
import os
import inspect
import time

VGG_MEAN = [103.939, 116.779, 123.68]

class Vgg16:
    def __init__(self, vgg16_npy_path=None):
        if vgg16_npy_path is None:
            path = inspect.getfile(Vgg16)
            path = os.path.abspath(os.path.join(path, os.pardir))
            path = os.path.join(path, 'vgg16.npy')
            vgg16_npy_path = path
            print(path)

        self.data_dict = np.load(vgg16_npy_path, encoding='latin1').item()
                      

    def build(self, rgb):
        start_time = time.time()
        print("build model started")

        rgb_scaled = rgb * 255.0
        red, green, blue = tf.split(axis=3, num_or_size_splits=3, value=rgb_scaled)
        assert red.get_shape().as_list()[1:] == [224, 224, 1]
        assert green.get_shape().as_list()[1:] == [224, 224, 1]
        assert blue.get_shape().as_list()[1:] == [224, 224, 1]
        bgr = tf.concat(axis=3, values=[
            blue - VGG_MEAN[0],
            green - VGG_MEAN[1],
            red - VGG_MEAN[2],
        ])
        assert bgr.get_shape().as_list()[1:] == [224, 224, 3]

        self.conv1_1 = self.conv_layer(bgr, 'conv1_1')
        self.conv1_2 = self.conv_layer(self.conv1_1, 'conv_1_2')
        self.pool1 = self.max_pool(self.conv1_2, 'pool1')

        self.conv1_1 = self.conv_layer(bgr, 'conv1_1')
        self.conv1_2 = self.conv_layer(self.conv1_1, 'conv_1_2')
        self.pool1 = self.max_pool(self.conv1_2, 'pool1')

        self.conv1_1 = self.conv_layer(bgr, 'conv1_1')
        self.conv1_2 = self.conv_layer(self.conv1_1, 'conv_1_2')
        self.pool1 = self.max_pool(self.conv1_2, 'pool1')

        self.conv1_1 = self.conv_layer(bgr, 'conv1_1')
        self.conv1_2 = self.conv_layer(self.conv1_1, 'conv_1_2')
        self.pool1 = self.max_pool(self.conv1_2, 'pool1')

    def conv_layer(self, input, name):
        with tf.variable_scope(name):
            filt = self.get_conv_filter(name)
            conv = tf.nn.conv2d(input=input, filter=filt,strides=[1,1,1,1], padding='SAME')

            conv_biases = self.get_bias(name)
            bias = tf.nn.bias_add(conv, conv_biases)
            relu = tf.nn.relu(bias)
            return relu

    def avg_pool(self, input, name):
        return tf.nn.avg_pool(input, strides=[1,2,2,1], ksize=[1,2,2,1], padding='SAME', name=name)

    def max_pool(self, input, name):
        return tf.nn.max_pool(input, strides=[1,2,2,1], ksize=[1,2,2,1], padding='SAME', name=name)


    def get_conv_filter(self, name):
        return tf.constant(self.data_dict[name][0], name='filter')
    def get_bias(self, name):
        return tf.constant(self.data_dict[name][1], name='biases')

    def fc_layer(self, bottom, name):
        with tf.variable_scope(name):
            shape = bottom.get_shape().as_list()
            dim = 1
            for d in shape[1:]:
                dim *=d

            x = tf.reshape(bottom, [-1, dim])

            weights = self.get_fc_weight(name)
            biases = self.get_bias(name)
            output = tf.nn.bias_add(tf.matmul(x, weights), biases)
            return output

    def get_fc_weight(self, name):
        return tf.constant(self.data_dict[name][0], name='weights')

参考:

https://blog.csdn.net/jyy555555/article/details/80515562

https://blog.csdn.net/qq_37791134/article/details/83474310

猜你喜欢

转载自blog.csdn.net/tianchangsu5948/article/details/83477798