VGG卷积神经网络模型加载与运行

1,VGG概述

VGG Net由牛津大学的视觉几何组(Visual Geometry Group)和 Google DeepMind公司的研究员一起研发的的深度卷积神经网络,在 ILSVRC 2014 上取得了第二名的成绩,将 Top-5错误率降到7.3%。它主要的贡献是展示出网络的深度(depth)是算法优良性能的关键部分。他们最好的网络包含了16个卷积/全连接层。网络的结构非常一致,从头到尾全部使用的是3x3的卷积和2x2的汇聚。VGGNet不好的一点是它耗费更多计算资源,并且使用了更多的参数,导致更多的内存占用(140M)。其中绝大多数的参数都是来自于第一个全连接层。后来发现这些全连接层即使被去除,对于性能也没有什么影响,这样就显著降低了参数数量。

目前使用比较多的网络结构主要有ResNet(152-1000层),GooleNet(22层),VGGNet(19层),大多数模型都是基于这几个模型上改进,采用新的优化算法,多模型融合等。到目前为止,VGG Net 依然经常被用来提取图像特征。

2,GVV19的结构图
VGG卷积神经网络模型加载与运行_第1张图片VGG卷积神经网络模型加载与运行_第2张图片
输入是大小为224224的RGB图像,预处理(preprocession)时计算出三个通道的平均值,在每个像素上减去平均值(处理后迭代更少,更快收敛)。图像经过一系列卷积层处理,在卷积层中使用了非常小的33卷积核,在有些卷积层里则使用了11的卷积核。卷积层步长(stride)设置为1个像素,33卷积层的填充(padding)设置为1个像素。池化层采用max pooling,共有5层,在一部分卷积层后,max-pooling的窗口是2*2,步长设置为2。卷积层之后是三个全连接层(fully-connected layers,FC)。前两个全连接层均有4096个通道,第三个全连接层有1000个通道,用来分类。所有网络的全连接层配置相同。全连接层后是Softmax,用来分类。所有隐藏层(每个conv层中间)都使用ReLU作为激活函数。

VGGNet不使用局部响应标准化(LRN),这种标准化并不能在ILSVRC数据集上提升性能,却导致更多的内存消耗和计算时间(LRN:Local Response Normalization,局部响应归一化,用于增强网络的泛化能力)。

3,优化讨论

VGG卷积神经网络模型加载与运行_第3张图片
1、选择采用33的卷积核是因为33是最小的能够捕捉像素8邻域信息的的尺寸。

2、使用1*1的卷积核目的是在不影响输入输出的维度情况下,对输入进行形变,再通过ReLU进行非线性处理,提高决策函数的非线性。

3、2个33卷积堆叠等于1个55卷积,3个33堆叠等于1个77卷积,感受野大小不变,而采用更多层、更小的卷积核可以引入更多非线性(更多的隐藏层,从而带来更多非线性函数),提高决策函数判决力,并且带来更少参数。唯一的不足是,在进行反向传播时,中间的卷积层可能会导致占用更多的内存。

4、每个VGG网络都有3个FC层,5个池化层,1个softmax层。

5、在FC层中间采用dropout层,防止过拟合,如下图:
VGG卷积神经网络模型加载与运行_第4张图片左边的图为一个完全的全连接层,右边为应用dropout后的全连接层。
我们知道,典型的神经网络其训练流程是将输入通过网络进行正向传导,然后将误差进行反向传播。dropout就是针对这一过程之中,随机地删除隐藏层的部分单元,进行上述过程。步骤为:
(1)随机删除网络中的一些隐藏神经元,保持输入输出神经元不变;
(2)将输入通过修改后的网络进行前向传播,然后将误差通过修改后的网络进行反向传播;
(3)对于另外一批的训练样本,重复上述操作(1)。

Dropout可以有效防止过拟合,原因是:
(1)达到了一种vote的作用。对于单个神经网络而言,将其进行分批,即使不同的训练集可能会产生不同程度的过拟合,但是我们如果将其公用一个损失函数,相当于对其同时进行了优化,取了平均,因此可以较为有效地防止过拟合的发生。
(2)减少神经元之间复杂的共适应性。当隐藏层神经元被随机删除之后,使得全连接网络具有了一定的稀疏化,从而有效地减轻了不同特征的协同效应。也就是说,有些特征可能会依赖于固定关系的隐含节点的共同作用,而通过dropout的话,就有效地阻止了某些特征在其他特征存在下才有效果的情况,增加了神经网络的鲁棒性。
6、如今用得最多的是VGG16(13层conv + 3层FC)和VGG19(16层conv + 3层FC),注意算层数时不算maxpool层和softmax层,只算conv层和fc层。

4,模型训练

训练采用多尺度训练(Multi-scale),将原始图像缩放到不同尺寸 S,然后再随机裁切224*224的图片,并且对图片进行水平翻转和随机RGB色差调整,这样能增加很多数据量,对于防止模型过拟合有很不错的效果。

初始对原始图片进行裁剪时,原始图片的最小边不宜过小,这样的话,裁剪到224*224的时候,就相当于几乎覆盖了整个图片,这样对原始图片进行不同的随机裁剪得到的图片就基本上没差别,就失去了增加数据集的意义,但同时也不宜过大,这样的话,裁剪到的图片只含有目标的一小部分,也不是很好。

针对上述裁剪的问题,提出的两种解决办法:
(1) 固定最小边的尺寸为256;
(2) 随机从[256,512]的确定范围内进行抽样,这样原始图片尺寸不一,有利于训练,这个方法叫做尺度抖动(scale jittering),有利于训练集增强。

5,模型测试

将全连接层等效替换为卷积层进行测试,原因是:
卷积层和全连接层的唯一区别就是卷积层的神经元和输入是局部联系的,并且同一个通道(channel)内的不同神经元共享权值(weight)。卷积层和全连接层的计算实际上相同,因此可以将全连接层转换为卷积层,只要将卷积核大小设置为输入空间大小即可:例如输入为77512,第一层全连接层输出4096;我们可以将其看作卷积核大小为77,步长为1,没有填充,输出为11*4096的卷积层。这样的好处在于输入图像的大小不再受限制,因此可以高效地对图像作滑动窗式预测;而且全连接层的计算量比较大,等效卷积层的计算量减小了,这样既达到了目的又十分高效。

6,存在的问题

(1)虽然 VGGNet 减少了卷积层参数,但实际上其参数空间比 AlexNet 大,其中绝大多数的参数都是来自于第一个全连接层,耗费更多计算资源。在随后的 NIN 中发现将这些全连接层替换为全局平均池化,对于性能影响不大,同时显著降低了参数数量。
(2)采用 Pre-trained 方法训练的 VGG model(主要是 D 和 E),相对其他的方法参数空间很大,所以训练一个 VGG 模型通常要花费更长的时间,所幸有公开的 Pre-trained model 让我们很方便的使用。Imagenet VGG-19参数下载地址:http://www.vlfeat.org/matconvnet/models/beta16/imagenet-vgg-verydeep-19.mat

7,模型加载与运行

加载Imagenet VGG-19完整代码如下:

import scipy.io    #读写.mat文件(可以加载和保存matlab文件)
import numpy as np 
import os #os模块提供了多数操作系统的功能接口函数。当os模块被导入后,它会自适应于不同的操作系统平台,根据不同的平台进行相应的操作,在python编程时,经常和文件、目录打交道,所以离不了os模块
import scipy.misc  #是npy文件的图片,都可以用指定的格式保存
import matplotlib.pyplot as plt 
import tensorflow as tf 

#Functions for VGG
def _conv_layer(input, weights, bias):#卷积操作
    conv = tf.nn.conv2d(input, tf.constant(weights), strides=(1, 1, 1, 1),
            padding='SAME')
    return tf.nn.bias_add(conv, bias)
def _pool_layer(input):#池化操作
    return tf.nn.max_pool(input, ksize=(1, 2, 2, 1), strides=(1, 2, 2, 1),
            padding='SAME')
def preprocess(image, mean_pixel): #预处理(减均值)
    return image - mean_pixel
def unprocess(image, mean_pixel):#不进行预处理
    return image + mean_pixel
def imread(path):     #读取模型
    return scipy.misc.imread(path).astype(np.float)
def imsave(path, img):  #读取图像
    img = np.clip(img, 0, 255).astype(np.uint8)
    scipy.misc.imsave(path, img)


# Network for VGG  (完成前向传播) 
def net(data_path, input_image):
    layers = (
        'conv1_1', 'relu1_1', 'conv1_2', 'relu1_2', 'pool1',
        'conv2_1', 'relu2_1', 'conv2_2', 'relu2_2', 'pool2',
        'conv3_1', 'relu3_1', 'conv3_2', 'relu3_2', 'conv3_3',
        'relu3_3', 'conv3_4', 'relu3_4', 'pool3',
        'conv4_1', 'relu4_1', 'conv4_2', 'relu4_2', 'conv4_3',
        'relu4_3', 'conv4_4', 'relu4_4', 'pool4',
        'conv5_1', 'relu5_1', 'conv5_2', 'relu5_2', 'conv5_3',
        'relu5_3', 'conv5_4', 'relu5_4'
    )#定义各卷积层的参数(16层conv)
    data = scipy.io.loadmat(data_path)#加载.mat文件(模型参数)
    mean = data['normalization'][0][0][0]#保存三个通道的均值
    mean_pixel = np.mean(mean, axis=(0, 1))
    weights = data['layers'][0]
    net = {}#定义字典结构(保存前向传播的结果)
    current = input_image#当前的输入
    for i, name in enumerate(layers):
        kind = name[:4]#取名字的前4个字母
        if kind == 'conv':
            kernels, bias = weights[i][0][0][0][0] #w,b的一个元组
            # matconvnet: weights are [width, height, in_channels, out_channels]
            # tensorflow: weights are [height, width, in_channels, out_channels]
            kernels = np.transpose(kernels, (1, 0, 2, 3))#转换成tensorflow模式
            bias = bias.reshape(-1)
            current = _conv_layer(current, kernels, bias)
        elif kind == 'relu':
            current = tf.nn.relu(current)
        elif kind == 'pool':
            current = _pool_layer(current)
        net[name] = current
    assert len(net) == len(layers)
    return net, mean_pixel, layers

cwd  = os.getcwd()#得到当前路径
VGG_PATH = cwd + "/source/imagenet-vgg-19.mat"#imagenet-vgg-19.mat的绝对路径
IMG_PATH = cwd + "/source/lena.png"#lena.png的绝对路径
input_image = imread(IMG_PATH) #读取imagenet-vgg-19.mat文件
shape = (1,input_image.shape[0],input_image.shape[1],input_image.shape[2]) 
with tf.Session() as sess:
    image = tf.placeholder('float', shape=shape)
    nets, mean_pixel, all_layers = net(VGG_PATH, image)
    input_image_pre = np.array([preprocess(input_image, mean_pixel)])#预处理
    layers = all_layers # For all layers 
    # layers = ('relu2_1', 'relu3_1', 'relu4_1')
    for i, layer in enumerate(layers):
        print ("[%d/%d] %s" % (i+1, len(layers), layer))
        features = nets[layer].eval(feed_dict={image: input_image_pre})#当前层前向传播的结果
        
        #print (" Type of 'features' is ", type(features))
        #print (" Shape of 'features' is %s" % (features.shape,))
        # Plot response 
        if 1:
            plt.figure(i+1, figsize=(10, 5))
            plt.matshow(features[0, :, :, 0], cmap=plt.cm.gray, fignum=i+1)
            plt.title("" + layer)
            plt.colorbar()
            plt.show()

运行结果:
VGG卷积神经网络模型加载与运行_第5张图片VGG卷积神经网络模型加载与运行_第6张图片
……

你可能感兴趣的:(深度学习与计算机视觉)