【TensorFlow-windows】(七) CNN之VGG-net的测试

主要内容:
1.CNN之VGG-net的测试
2.该实现中的函数总结

平台:
1.windows 10 64位
2.Anaconda3-4.2.0-Windows-x86_64.exe (当时TF还不支持python3.6,又懒得在高版本的anaconda下配置多个Python环境,于是装了一个3-4.2.0(默认装python3.5),建议装anaconda3的最新版本,TF1.2.0版本已经支持python3.6!)
3.TensorFlow1.1.0

牛津大学计算机视觉组(Visual Geometry Group)出产的VGGnet是 ILSVRC 2014年 分类第二名,定位项目第一名,其VGG-16VGG-19广泛应用于图像领域!
论文《very deep convolutional networks for large-scale image recognition》
特点就是多个小卷积核(3*3)的堆叠来替代大卷积核(5*5,7*7),减少参数的同时增加了特征提取的能力。

来看看论文中给了5个模型的基本情况:
这里写图片描述

今天就用TF写一个VGG-16 ,上图红框中的那个,由于硬件问题,这里和Alex-net一样,只是做了一个前向和反向的耗时计算。(PS: GTX960m的 GPU(2G显存),没办法实现 batch_size为32,全连接层1为4096,全连接层2为4096个神经元的VGG网络,因此我修改了网络大小, batch_size为16, 全连接层1和2 都改为2048个神经元)

# -*- coding: utf-8 -*-
"""
Created on Mon Jun 12 16:36:43 2017

@author: ASUS
"""
from datetime import datetime
import math
import time
import tensorflow as tf

# 定义函数对卷积层进行初始化
# input_op : 输入数据 
# name : 该卷积层的名字,用tf.name_scope()来命名
# kh,kw : 分别是卷积核的高和宽
# n_out : 输出通道数
# dh,dw : 步长的高和宽
# p : 是参数列表,存储VGG所用到的参数
# 采用xavier方法对卷积核权值进行初始化
def conv_op(input_op, name, kh, kw, n_out, dh, dw, p):
    n_in = input_op.get_shape()[-1].value # 获得输入图像的通道数
    with tf.name_scope(name) as scope:
        kernel = tf.get_variable(scope+'w',
            shape = [kh, kw, n_in, n_out], dtype = tf.float32,
            initializer = tf.contrib.layers.xavier_initializer_conv2d())
        #  卷积层计算
        conv = tf.nn.conv2d(input_op, kernel, (1, dh, dw, 1), padding = 'SAME')
        bias_init_val = tf.constant(0.0, shape = [n_out], dtype = tf.float32)
        biases = tf.Variable(bias_init_val, trainable = True, name = 'b')
        z = tf.nn.bias_add(conv, biases)
        activation = tf.nn.relu(z, name = scope)
        p += [kernel, biases]
        return activation

# 定义函数对全连接层进行初始化
# input_op : 输入数据
# name : 该全连接层的名字
# n_out : 输出的通道数
# p : 参数列表 
# 初始化方法用 xavier方法
def fc_op(input_op, name, n_out, p):
    n_in = input_op.get_shape()[-1].value

    with tf.name_scope(name) as scope:
        kernel = tf.get_variable(scope+'w',
            shape = [n_in, n_out], dtype = tf.float32,
            initializer = tf.contrib.layers.xavier_initializer())
        biases = tf.Variable(tf.constant(0.1, shape = [n_out],
            dtype = tf.float32), name = 'b')
        activation = tf.nn.relu_layer(input_op, kernel,  #  ???????????????
            biases, name = scope)
        p += [kernel, biases]
        return activation 

# 定义函数 创建 maxpool层
# input_op : 输入数据 
# name : 该卷积层的名字,用tf.name_scope()来命名
# kh,kw : 分别是卷积核的高和宽
# dh,dw : 步长的高和宽
def mpool_op(input_op, name, kh, kw, dh, dw):
    return tf.nn.max_pool(input_op, ksize = [1,kh,kw,1],
        strides = [1, dh, dw, 1], padding = 'SAME', name = name)

#---------------创建 VGG-16------------------

def inference_op(input_op, keep_prob):
    p = []
    # 第一块 conv1_1-conv1_2-pool1
    conv1_1 = conv_op(input_op, name='conv1_1', kh=3, kw=3,
                n_out = 64, dh = 1, dw = 1, p = p)
    conv1_2 = conv_op(conv1_1, name='conv1_2', kh=3, kw=3,
                n_out = 64, dh = 1, dw = 1, p = p)
    pool1 = mpool_op(conv1_2, name = 'pool1', kh = 2, kw = 2,
                dw = 2, dh = 2)
    # 第二块 conv2_1-conv2_2-pool2
    conv2_1 = conv_op(pool1, name='conv2_1', kh=3, kw=3,
                n_out = 128, dh = 1, dw = 1, p = p)
    conv2_2 = conv_op(conv2_1, name='conv2_2', kh=3, kw=3,
                n_out = 128, dh = 1, dw = 1, p = p)
    pool2 = mpool_op(conv2_2, name = 'pool2', kh = 2, kw = 2,
                dw = 2, dh = 2)
    # 第三块 conv3_1-conv3_2-conv3_3-pool3
    conv3_1 = conv_op(pool2, name='conv3_1', kh=3, kw=3,
                n_out = 256, dh = 1, dw = 1, p = p)
    conv3_2 = conv_op(conv3_1, name='conv3_2', kh=3, kw=3,
                n_out = 256, dh = 1, dw = 1, p = p)
    conv3_3 = conv_op(conv3_2, name='conv3_3', kh=3, kw=3,
                n_out = 256, dh = 1, dw = 1, p = p)
    pool3 = mpool_op(conv3_3, name = 'pool3', kh = 2, kw = 2,
                dw = 2, dh = 2)
    # 第四块 conv4_1-conv4_2-conv4_3-pool4
    conv4_1 = conv_op(pool3, name='conv4_1', kh=3, kw=3,
                n_out = 512, dh = 1, dw = 1, p = p)
    conv4_2 = conv_op(conv4_1, name='conv4_2', kh=3, kw=3,
                n_out = 512, dh = 1, dw = 1, p = p)
    conv4_3 = conv_op(conv4_2, name='conv4_3', kh=3, kw=3,
                n_out = 512, dh = 1, dw = 1, p = p)
    pool4 = mpool_op(conv4_3, name = 'pool4', kh = 2, kw = 2,
                dw = 2, dh = 2)
    # 第五块 conv5_1-conv5_2-conv5_3-pool5
    conv5_1 = conv_op(pool4, name='conv5_1', kh=3, kw=3,
                n_out = 512, dh = 1, dw = 1, p = p)
    conv5_2 = conv_op(conv5_1, name='conv5_2', kh=3, kw=3,
                n_out = 512, dh = 1, dw = 1, p = p)
    conv5_3 = conv_op(conv5_2, name='conv5_3', kh=3, kw=3,
                n_out = 512, dh = 1, dw = 1, p = p)
    pool5 = mpool_op(conv5_3, name = 'pool5', kh = 2, kw = 2,
                dw = 2, dh = 2)
    # 把pool5 ( [7, 7, 512] )  拉成向量
    shp  = pool5.get_shape()
    flattened_shape = shp[1].value * shp[2].value * shp[3].value
    resh1 = tf.reshape(pool5, [-1, flattened_shape], name = 'resh1')

    # 全连接层1 添加了 Droput来防止过拟合    
    fc1 = fc_op(resh1, name = 'fc1', n_out = 2048, p = p)
    fc1_drop = tf.nn.dropout(fc1, keep_prob, name = 'fc1_drop')

    # 全连接层2 添加了 Droput来防止过拟合    
    fc2 = fc_op(fc1_drop, name = 'fc2', n_out = 2048, p = p)
    fc2_drop = tf.nn.dropout(fc2, keep_prob, name = 'fc2_drop')

    # 全连接层3 加一个softmax求给类别的概率
    fc3 = fc_op(fc2_drop, name = 'fc3', n_out = 1000, p = p)
    softmax = tf.nn.softmax(fc3)
    predictions = tf.argmax(softmax, 1)
    return predictions, softmax, fc3, p

# 定义评测函数

def time_tensorflow_run(session, target, feed, info_string):
    num_steps_burn_in = 10
    total_duration = 0.0
    total_duration_squared = 0.0

    for i in range(num_batches + num_steps_burn_in):
        start_time = time.time()
        _ = session.run(target, feed_dict = feed)
        duration = time.time() - start_time
        if i >= num_steps_burn_in:
            if not i  % 10: 
                print('%s: step %d, duration = %.3f' % 
                    (datetime.now(), i-num_steps_burn_in, duration))
            total_duration += duration
            total_duration_squared += duration * duration
    mean_dur = total_duration / num_batches 
    var_dur = total_duration_squared / num_batches - mean_dur * mean_dur
    std_dur = math.sqrt(var_dur)
    print('%s: %s across %d steps, %.3f +/- %.3f sec / batch' %(datetime.now(), info_string, num_batches, mean_dur, std_dur))

# 定义函数 运行网络
def run_benchmark():
    with tf.Graph().as_default():
        image_size = 224
        images = tf.Variable(tf.random_normal(
            [batch_size, image_size, image_size, 3],
            dtype = tf.float32, stddev = 1e-1))

        keep_prob = tf.placeholder(tf.float32)
        predictions, softmax, fc3, p = inference_op(images, keep_prob)

        # 创建session ,初始化参数
        init = tf.global_variables_initializer()
        sess = tf.Session()
        sess.run(init)

        # 评测  forward的耗时
        time_tensorflow_run(sess, predictions, {keep_prob: 1.0}, 'Forward')
        # 评测 forward 和 backward 
        objective = tf.nn.l2_loss(fc3)
        gard = tf.gradients(objective, p)
        time_tensorflow_run(sess,gard,predictions, {keep_prob: 0.5}, 'Forward-backward')

batch_size = 16
num_batches = 100
run_benchmark() 

函数总结(续上篇)

sess = tf.InteractiveSession() 将sess注册为默认的session
tf.placeholder() , Placeholder是输入数据的地方,也称为占位符,通俗的理解就是给输入数据(此例中的图片x)和真实标签(y_)提供一个入口,或者是存放地。(个人理解,可能不太正确,后期对TF有深入认识的话再回来改~~)
tf.Variable() Variable是用来存储模型参数,与存储数据的tensor不同,tensor一旦使用掉就消失
tf.matmul() 矩阵相乘函数
tf.reduce_mean 和tf.reduce_sum 是缩减维度的计算均值,以及缩减维度的求和
tf.argmax() 是寻找tensor中值最大的元素的序号 ,此例中用来判断类别
tf.cast() 用于数据类型转换
————————————–我是分割线(一)———————————–
tf.random_uniform 生成均匀分布的随机数
tf.train.AdamOptimizer() 创建优化器,优化方法为Adam(adaptive moment estimation,Adam优化方法根据损失函数对每个参数的梯度的一阶矩估计和二阶矩估计动态调整针对于每个参数的学习速率)
tf.placeholder “占位符”,只要是对网络的输入,都需要用这个函数这个进行“初始化”
tf.random_normal 生成正态分布
tf.add 和 tf.matmul 数据的相加 、相乘
tf.reduce_sum 缩减维度的求和
tf.pow 求幂函数
tf.subtract 数据的相减
tf.global_variables_initializer 定义全局参数初始化
tf.Session 创建会话.
tf.Variable 创建变量,是用来存储模型参数的变量。是有别于模型的输入数据的
tf.train.AdamOptimizer (learning_rate = 0.001) 采用Adam进行优化,学习率为 0.001
————————————–我是分割线(二)———————————–
1. hidden1_drop = tf.nn.dropout(hidden1, keep_prob) 给 hindden1层增加Droput,返回新的层hidden1_drop,keep_prob是 Droput的比例
2. mnist.train.next_batch() 来详细讲讲 这个函数。一句话概括就是,打乱样本顺序,然后按顺序读取batch_size 个样本 进行返回。
具体看代码及其注释,首先要找到函数定义,在tensorflowcontriblearnpythonlearndatasets 下的mnist.py
————————————–我是分割线(三)———————————–
1. tf.nn.conv2d(x, W, strides = [1, 1, 1, 1], padding =’SAME’)对于这个函数主要理解 strides和padding,首先明确,x是输入,W是卷积核,并且它们的维数都是4(发现strides里有4个元素没,没错!就是一一对应的)
先说一下卷积核W也是一个四维张量,各维度表示的信息是:[filter_height, filter_width, in_channels, out_channels]

输入x,x是一个四维张量 ,各维度表示的信息是:[batch, in_height, in_width, in_channels]

strides里的每个元素就是对应输入x的四个维度的步长,因为第2,3维是图像的长和宽,所以平时用的strides就在这里设置,而第1,4维一般不用到,所以是1

padding只有两种取值方式,一个是 padding=[‘VALID’] 一个是padding=[‘SAME’]
valid:采用丢弃的方式,只要移动一步时,最右边有超出,则这一步不移动,并且剩余的进行丢弃。如下图,图片长13,卷积核长6,步长是5,当移动一步之后,已经卷积核6-11,再移动一步,已经没有足够的像素点了,所以就不能移动,因此 12,13被丢弃。
same:顾名思义,就是保持输入的大小不变,方法是在图像边缘处填充全0的像素
————————————–我是分割线(四)———————————–
1.tf.nn.l2_loss()是对一个Tensor对象求L2 norm,这里用来求权值的L2范数
2.tf.add_to_collection(‘losses’, weight_loss), 把weight_loss 加到名为losses的loss中去
3.tf.nn.lrn,做局部相应归一化,来看看函数的定义

lrn(input, depth_radius=None, bias=None, alpha=None, beta=None,name=None)

函数有6个输入,第一个自然是输入啦,2-5是LRN用到的参数,第一个是depth半径,bias alpha beta是相应的参数,来看看伪代码就清楚了

sqr_sum[a, b, c, d] = sum(input[a, b, c, d - depth_radius : d + depth_radius + 1] ** 2)
output = input / (bias + alpha * sqr_sum) ** beta
2-5的参数是有默认值的,分别是5,1,1,0.5

3.tf.nn.sparse_softmax_cross_entropy_with_logits()就是计算交叉熵的,但是如果是一个batch的,得出的是一个向量,还需要配合tf.reduce_mean()才能计算得出最终的loss(标量)

4.tf.get_collection(‘losses’), 从一个集合中取出全部变量,返回的是一个列表,这里是把losses的都取出来,在上面把 weight_loss和输出层的loss都加到losses中了,这个函数就是把losses中的loss取出来

5.tf.add_n:把一个列表的东西都依次加起来 (Adds all input tensors element-wise.) 输入是一个list (inputs: A list of Tensor objects, each with same shape and type.)
————————————–我是分割线(五)———————————–

1.t.get_shape().as_list() ,t是一个tensor,函数以list的形式返回 tensor的shape
2.tf.name_scope(‘conv1’) as scope ,tf.name_scope是用来命名Variable的,print(parameters)可以看到命名
3. tf.Graph().as_default()能够在这个上下文里面覆盖默认的图,测试了一下 不用这个也可以,但是用上好点,可能是避免冲突吧~
————————————–我是分割线(六)———————————–
1. tensor.get_shape()[-1].value 获得 倒数第一个维度上的维数
2. tf.get_variable(scope+’w’,
shape = [kh, kw, n_in, n_out], dtype = tf.float32,
initializer = tf.contrib.layers.xavier_initializer_conv2d())
来看看函数定义:

def get_variable(name,
                 shape=None,
                 dtype=None,
                 initializer=None,
                 regularizer=None,
                 trainable=True,
                 collections=None,
                 caching_device=None,
                 partitioner=None,
                 validate_shape=True,
                 use_resource=None,
                 custom_getter=None):

这下就清楚多了,除了第一个形参,其余都是带默认值的关键字参数,这样不根据参形参的顺序对参数赋值啦

3.tf.nn.relu_layer(input_op, kernel, biases, name = scope)
这个函数把 卷积操作,加偏置操作,relu操作 集成在一块儿了,不过这个仅限于全连接层这种一维的层,因为输入必须是2D的,具体如下:

def relu_layer(x, weights, biases, name=None):
Args:
x: a 2D tensor. Dimensions typically: batch, in_units
weights: a 2D tensor. Dimensions typically: in_units, out_units
biases: a 1D tensor. Dimensions: out_units
name: A name for the operation (optional). If not specified
“nn_relu_layer” is used.
Returns:
A 2-D Tensor computing relu(matmul(x, weights) + biases).
Dimensions typically: batch, out_units.

原文地址:https://www.cnblogs.com/TensorSense/p/7413312.html