深度学习与TensorFlow实战(十)卷积神经网络—VGG(16)神经网络

架构:
训练输入:固定尺寸224x224的RGB图像。
预处理:每个像素减去训练集上的RGB均值。
卷积核:一系列3X3卷积核堆叠,步长为1,采用padding保持卷积后图像空间分辨率不变。
空间池化:紧随卷积堆的最大池化,为2x2滑动窗口,步长2.
全连接层:特征提取后,接三个全连接层,前两个为4096,第三个为1000通道,最后是一个softmax层,输出概率。
所有隐藏层都用非线性修正relu。

详细配置
这里写图片描述

Input Layer:224*224*3图像

Conv1 Layer:包含64个卷积核,kernal size:3*3,stride:1,padding:2

                        卷积后大小:224*224*64

Conv2 Layer:包含64个卷积核,kernal size:3*3,stride:1,padding:2

                        卷积后大小:224*224*64

Pool1 Layer:包含128个卷积核,kernal size:2*2,stride:2,padding:0

                        卷积后大小:112*112*128

Conv3 Layer:包含128个卷积核,kernal size:3*3,stride:1,padding:2

                        卷积后大小:112*112*128

Conv4 Layer:包含128个卷积核,kernal size:3*3,stride:1,padding:2

                        卷积后大小:112*112*128

Pool2 Layer:包含256个卷积核,kernal size:2*2,stride:2,padding:0

                        卷积后大小:56*56*256

Conv5 Layer:包含256个卷积核,kernal size:3*3,stride:1,padding:2

                        卷积后大小:56*56*256

Conv6 Layer:包含256个卷积核,kernal size:3*3,stride:1,padding:2

                        卷积后大小:56*56*256

Conv7 Layer:包含256个卷积核,kernal size:3*3,stride:1,padding:2

                        卷积后大小:56*56*256

Pool3 Layer:包含512个卷积核,kernal size:2*2,stride:2,padding:0

                        卷积后大小:28*28*512

Conv8 Layer:包含512个卷积核,kernal size:3*3,stride:1,padding:2

                        卷积后大小:28*28*512

Conv9 Layer:包含512个卷积核,kernal size:3*3,stride:1,padding:2

                        卷积后大小:28*28*512

Conv10 Layer:包含512个卷积核,kernal size:3*3,stride:1,padding:2

                        卷积后大小:28*28*512

Pool4 Layer:包含512个卷积核,kernal size:2*2,stride:2,padding:0

                        卷积后大小:14*14*128

Conv11 Layer:包含512个卷积核,kernal size:3*3,stride:1,padding:2

                        卷积后大小:14*14*512

Conv12 Layer:包含512个卷积核,kernal size:3*3,stride:1,padding:2

                        卷积后大小:14*14*512

Conv13 Layer:包含512个卷积核,kernal size:3*3,stride:1,padding:2

                        卷积后大小:14*14*512

Pool5 Layer:包含512个卷积核,kernal size:2*2,stride:2,padding:0

                        卷积后大小:7*7*512

FullConect Layer1:这里用1*1卷积核,包含4096个卷积核,

FullConect Layer2:这里用1*1卷积核,包含4096个卷积核,

FullConect Layer3:这里用1*1卷积核,包含4096个卷积核,

Softmax
由于池化只是下采样,没有权重,一般默认不计算层数

13层卷积+3层全连接 共16层(激活函数均为ReLU)

表1中每一列代表不同的网络,只有深度不同(层数计算不包含池化层)。卷积的通道数量很小,第一层仅64通道,美经过一次最大池化,通道数翻倍,直到数量达到512通道。
表2展示了每一种模型的参数数量,尽管网络加深,但权重未大幅增加,因为参数量主要集中在全连接层。
这里写图片描述

讨论:
两个3X3卷积核相当于5X5卷积核的感受域,三个3X3卷积核相当于一个7X7卷积核的感受域。
优点:三个卷积堆叠具有三个非线性修正层,使模型更具有判别性;其次三个3X3卷积参数量更少,相当于在7X7卷积核加入了正则化。

分类框架:
训练:
训练方法基本与AlexNet一致,除了多尺度训练图像采样方法不一致。
训练采用mini-batch梯度下降法,batch size=256;
采用动量优化算法,momentum=0.9;
采用L2正则化方法,惩罚系数0.00005,dropout比率设为0.5
初始学习率0.001,当验证集准确率不再提到时,学习率衰减为原来0.1倍,总共下降三次;
总迭代次数370K;
数据增强采用随机剪裁,水平翻转,RGB颜色变化;
设置训练图片大小的两种方法;
定义S代表经过各向同性缩放的训练图像最小边。
第一种方法针对单尺寸图像训练,S=256或384,输入图片从随机剪裁224X224大小的图片,原则上S可以任意不小于224的值。
第二种方法是多尺度训练,每一张图像单独从[Smin,Smax]中随机选取S来进行尺寸缩放,由于图像中目标物体尺寸不定,因此训练中采用这种方法是有效的,可以看做一种尺寸抖动的训练数据集。
论文提到,网络权重的初始化非常重要,由于深度网络深度的不稳定性,不适合的初始化会阻碍网络的学习。因此我们先训练浅层网络,再用训练好的浅层网络去初始化深层网络。
测试:
测试阶段,对于已经训练好的卷积网络和一张输入图像,采用以下方法分类:
首先,图像的最小边被各向同性的缩放到预定尺寸Q;
然后,将原先的全连接层该成卷积层,在未剪裁的全图像上运用卷积网络,输出是一个输入图像尺寸相关的分类得分图输出通道数与类别数相同;
最后,对分类得分图进行空间平均化,得到固定尺寸的分类得分向量。
我们同样对测试集做数据增强,采用水平翻转,最后取原始图像和翻转图像的softmax分类概率的平均值最为最终得分。
由于测试阶段采用全卷积网络,无需对输入图像进行剪裁,相对多重剪裁效率更高。但多重剪裁评估和运用全卷积的密集评估是互补的,有助于性能提升。
分类实验
单尺寸评估
表3展示单一测试尺寸上的卷积网络性能
这里写图片描述

表4展示多个测试尺寸上的卷积网络性能
这里写图片描述

表5展示多重剪裁与密集网络对比,并展示两者相融的效果
这里写图片描述

卷积模型的融合
这部分讨论不同模型融合的性能,计算多个模型的softmax分类概率的平均值来对他们的输出进行组合,由于模型的互补性,性能有所提高,这也用于比赛的最佳结果。

表6展示多个卷积网络融合的结果
这里写图片描述

表7展示对当前最好算法的对比
这里写图片描述

结论:
本文评估了非常深的卷积网络在大规模图像分类上的性能。结果表明深度有利于分类准确率的提升。

VGG实现代码重点讲解

x = tf.placeholder(tf.float32,shape =[BATCH_SIZE,IMAGE_PIXELS])

tf.placeholder:用于传入真实训练样本/ 测试/ 真实特征/ 待处理特征。只是占位,不必给出初值。

用sess.run的feed_dict参数以字典形式喂入x:, y_: sess.run(feed_dict = {x: ,y_: })

BATCH_SIZE:一次传入的个数。 IMAGE_PIXELS:图像像素。

例:x = tf.placeholder(“float”,[1,224,224,3])

BATCH_SIZE为1,表示一次传入一个。图像像素为[224,224,3]。

w =tf.Variable(tf.random_normal()):从正态分布中给出权重w的随机值。

b = tf.Variable(tf.zeros()):统一将偏置b初始化为0。

注意:以上两行函数Variable中的V要大写,Variable必须给初值。

np.load np.save:将数组以二进制格式保存到磁盘,扩展名为.npy 。

.item():遍历(键值对)。

tf.shape(a)和a.get_shape()比较

相同点:都可以得到tensor a的尺寸

不同点:tf.shape()中 a 的数据类型可以是 tensor, list, array;

而a.get_shape()中a的数据类型只能是tensor,且返回的是一个元组(tuple)。
例如:

例:import tensorflow as tf     

import numpy as np

    x=tf.constant([[1,2,3],[4,5,6]]   

 y=[[1,2,3],[4,5,6]]    

z=np.arange(24).reshape([2,3,4]))

    sess=tf.Session()     #tf.shape()     

x_shape=tf.shape(x)          

# x_shape 是一个tensor     

y_shape=tf.shape(y)          # <tf.Tensor 'Shape_2:0' shape=(2,) dtype=int32>    

z_shape=tf.shape(z)         #  <tf.Tensor 'Shape_5:0'shape=(3,) dtype=int32>     

print sess.run(x_shape)      # 结果:[2 3]     

print sess.run(y_shape)      # 结果:[2 3]     

print sess.run(z_shape)      # 结果:[2 3 4] 

#a.get_shape()    

x_shape=x.get_shape()        # 返回的是 TensorShape([Dimension(2),Dimension(3)]),不能使用 sess.run(),因为返回的不是tensor 或string,而是元组

x_shape=x.get_shape().as_list()  #可以使用 as_list()得到具体的尺寸,x_shape=[2 3]

y_shape=y.get_shape()       # AttributeError: 'list' object hasno attribute 'get_shape'   

z_shape=z.get_shape()       #AttributeError: 'numpy.ndarray' object has no attribute 'get_shape'
#改变tensor的形状

# tensor ‘t’ is [1, 2, 3, 4, 5, 6, 7, 8, 9]

# tensor ‘t’ has shape [9] reshape(t, [3, 3])==> 

[[1, 2, 3],

[4, 5, 6],

[7, 8, 9]]

#如果shape有元素[-1],表示在该维度打平至一维

# -1 将自动推导得为 9:

reshape(t, [2, -1]) ==> 

[[1, 1, 1, 2, 2, 2, 3, 3, 3],

[4, 4, 4, 5, 5, 5, 6, 6, 6]]

np.argsort(列表):对列表从小到大排序。

OS模块 os.getcwd():返回当前工作目录。

os.path.join(path1[,path2[,......]]):

返回值:将多个路径组合后返回。 注意:第一个绝对路径之前的参数将被忽略。

例:

>>>import os

>>> vgg16_path = os.path.join(os.getcwd(),"vgg16.npy")

#当前目录/vgg16.npy,索引到vgg16.npy文件

np.save:写数组到文件(未压缩二进制形式),文件默认的扩展名是.npy 。

np.save("名.npy",某数组):将某数组写入“名.npy”文件。

某变量 =np.load("名.npy",encoding= " ").item():将“名.npy”文件读出给某变量。

encoding = " "  可以不写‘latin1’、‘ASCII’、‘bytes’,默认为’ASCII’。 例:

>>> import numpy as np

A = np.arange(15).reshape(3,5)

>>> A array([[ 0, 1,  2,  3, 4],  [ 5,  6, 7,  8,  9],  [10, 11, 12, 13,14]])

>>> np.save("A.npy",A)  #如果文件路径末尾没有扩展名.npy,该扩展名会被自动加上。

>>> B=np.load("A.npy")

>>> B array([[ 0, 1,  2,  3, 4],   [ 5,  6, 7,  8,  9],  [10, 11, 12, 13,14]])

tf.split(dimension,num_split, input):

dimension:输入张量的哪一个维度,如果是0就表示对第0维度进行切割。

num_split:切割的数量,如果是2就表示输入张量被切成2份,每一份是一个列表。 例:

import tensorflow as tf; import numpyas np;

A = [[1,2,3],[4,5,6]] x = tf.split(1,3, A)

 with tf.Session() as sess:

 c = sess.run(x)  for ele in c:   print ele

输出:

[[1]

 [4]]

[[2]

 [5]]

[[3]

 [6]]

tf.concat(concat_dim, values):

沿着某一维度连结tensor:

t1 = [[1, 2, 3], [4, 5, 6]]

t2 = [[7, 8, 9], [10, 11, 12]]tf.concat(0, [t1, t2]) ==> [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11,12]]

 tf.concat(1, [t1, t2]) ==>[[1, 2, 3, 7, 8, 9], [4, 5, 6, 10, 11, 12]]

如果想沿着tensor一新轴连结打包,那么可以:

tf.concat(axis, [tf.expand_dims(t,axis) for t in tensors]) 等同于tf.pack(tensors, axis=axis)

fig = plt.figure("图名字"):实例化图对象。

ax = fig.add_subplot(m n  k):将画布分割成m行n列,图像画在从左到右从上到下的第k块。

VGG16源码结构包括:
这里写图片描述

这里写图片描述

app.py代码展示:

import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import vgg16
import utils
from Nclasses import labels

img_path = input('Input the path and image name:')
img_ready = utils.load_image(img_path)#图像预处理

fig = plt.figure(u"Top-5 预测结果")

with tf.Session() as sess:
    images = tf.placeholder(tf.float32, [1, 224, 224, 3])
    vgg = vgg16.Vgg16()#实例化
    vgg.forward(images)
    #喂入神经网络,得到预测输出
    probability = sess.run(vgg.prob, feed_dict={images: img_ready})
    #np.argsort函数返回预测值(probability的数据结构[[各个预测类别的概率值,]]由小到大的索引值
    top5 = np.argsort(probability[0])[-1:-6:-1]#获取概率最大的五个索引值从大到小
    print("top5:", top5)

    #定义两个list,对应概率值和实际标签
    values = []
    bar_label = []
    for n, i in enumerate(top5):#枚举上面的五个索引值
        print("n:", n)
        print("i:", i)
        values.append(probability[0][i])#将索引值对应的预测概率取出并放入value
        bar_label.append(labels[i])#根据索引值取出对应的实际标签放入进去
        print(i, ":", labels[i], "----", utils.percent(probability[0][i]))#打印某个类别的概率

    ax = fig.add_subplot(111)#将画分为一行一列,并将下图放入其中
    #bar()函数绘制柱状图,参数range是柱状图的下标,value表示柱状高的列表(也就是五个预测概率值)
    #tick_label是每个柱子上显示的标签(实际对应的标签),width是柱子的宽度,fc是柱子的颜色)
    ax.bar(range(len(values)), values, tick_label=bar_label, width=0.5, fc='g')
    ax.set_ylabel(u'probabilityit')
    ax.set_title(u'Top-5')
    for a, b in zip(range(len(values)), values):
        #在每个柱子的顶端添加对应的预测概率值,a,b表示坐标,b+0.0005表示要把文本信息放置在高于每个柱子顶端0.0005的位置
        ax.text(a, b + 0.0005, utils.percent(b), ha='center', va='bottom', fontsize=7)
    plt.show()

这里写图片描述

vgg16.py代码:

import inspect
import os
import numpy as np
import tensorflow as tf
import time
import matplotlib.pyplot as plt

VGG_MEAN = [103.939, 116.779, 123.68]#样本RGB的平均值


class Vgg16():
    def __init__(self, vgg16_path=None):
        if vgg16_path is None:
            vgg16_path = os.path.join(os.getcwd(), "vgg16.npy")#方法用于返回当前工作目录,训练好的 VGG模型 参数,需要提前导入
            #print(vgg16_path)
            self.data_dict= np.load(vgg16_path, encoding='latin1').item()#遍历模型参数 npy 文件,使用字典的 item()函数访问字典的每个元素,并且导入参数到 data_dict

        for x in self.data_dict:#遍历data_dict中的每个键
            print(x)

    #定义获取卷积核的函数
    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 max_pool_2x2(self, x, name):
        return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name=name)

     #定义卷积运算
    def conv_layer(self, x, name):
        with tf.variable_scope(name):#根据命名空间找到对应卷积层的网络参数
            w = self.get_conv_filter(name)#读到盖层的卷积核
            conv = tf.nn.conv2d(x, w, [1, 1, 1, 1], padding='SAME')#卷积计算
            conv_biases = self.get_bias(name)#读到偏执项
            result = tf.nn.relu(tf.nn.bias_add(conv, conv_biases))#加上偏执,激活
            return result

    #定义获取权重的函数
    def get_fc_weight(self, name):
        return tf.constant(self.data_dict[name][0], name="weights")

    #定义全连接层的前向传播计算
    def fc_layer(self, x, name):
        with tf.variable_scope(name):#根据命名空间找到对应全连接层的网络参数
            shape = x.get_shape().as_list()#获取盖层的维度信息列表
            print("fc_layer shape",shape)
            dim = 1
            for i in shape[1:]:
                dim *= i#将盖层的维度相乘

            #改变特征图的形状,也就是将得到的多维特征做拉伸操作,只在进入全连接层做该操作
            x = tf.reshape(x, [-1, dim])
            w = self.get_fc_weight(name)#读到权重
            b = self.get_bias(name)#读到偏执

            result = tf.nn.bias_add(tf.matmul(x, w), b)#对该层输入做加权求和,加上偏执
            return result


    #定义前向传播函数
    def forward(self, images):
        print("build model started")
        start_time = time.time()#获取前向传播的开始时间
        rgb_scaled = images * 255.0#逐像素x255(VGG论文)

        red, green, blue = tf.split(rgb_scaled, 3, 3)
        bgr = tf.concat([
            blue - VGG_MEAN[0],
            green - VGG_MEAN[1],
            red - VGG_MEAN[2]], 3)

        #构建VGG网络,13层卷积,3层全连接,5段卷积池化,并逐层根据命名空间读取到网络参数

        self.conv1_1 = self.conv_layer(bgr, "conv1_1")
        self.conv1_2 = self.conv_layer(self.conv1_1, "conv1_2")
        self.pool1 = self.max_pool_2x2(self.conv1_2, "pool1")

        self.conv2_1 = self.conv_layer(self.pool1, "conv2_1")
        self.conv2_2 = self.conv_layer(self.conv2_1, "conv2_2")
        self.pool2 = self.max_pool_2x2(self.conv2_2, "pool2")

        self.conv3_1 = self.conv_layer(self.pool2, "conv3_1")
        self.conv3_2 = self.conv_layer(self.conv3_1, "conv3_2")
        self.conv3_3 = self.conv_layer(self.conv3_2, "conv3_3")
        self.pool3 = self.max_pool_2x2(self.conv3_3, "pool3")

        self.conv4_1 = self.conv_layer(self.pool3, "conv4_1")
        self.conv4_2 = self.conv_layer(self.conv4_1, "conv4_2")
        self.conv4_3 = self.conv_layer(self.conv4_2, "conv4_3")
        self.pool4 = self.max_pool_2x2(self.conv4_3, "pool4")

        self.conv5_1 = self.conv_layer(self.pool4, "conv5_1")
        self.conv5_2 = self.conv_layer(self.conv5_1, "conv5_2")
        self.conv5_3 = self.conv_layer(self.conv5_2, "conv5_3")
        self.pool5 = self.max_pool_2x2(self.conv5_3, "pool5")

        self.fc6 = self.fc_layer(self.pool5, "fc6")
        self.relu6 = tf.nn.relu(self.fc6)
        self.fc7 = 
  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值