简单卷积神经网络的tensorflow实现

一般一个卷积神经网络由多个卷积层构成,在卷基层内部通常会有如下几个操作:
1)图像通过多个卷积核滤波,添加偏置,提取局部特征每个卷积核会映射出一个新的2D图像。
2)卷积核的滤波结果输出到激活函数中,激活函数通常选ReLU【书,参考37】
3)对激活函数的结果进行池化操作,池化就是对图像分割成不同的小区域后取平均值或最大值。一般取最大值。
上述几个步骤就构成了最常见的卷积层。在池化的后面还可以加上batch normalization等操作。
一个卷积层中可以有不同的卷积核,而每一个卷积核都对应一个滤波后映射出的新图像,同一个新图像的每一个像素都来自完全相同的卷积核。这种卷积核的权值共享可以有效降低模型负责度,减轻过拟合,减少计算量。

卷积神经网络结构

建立卷积神经网络对手写数字识别问题进行优化,构建由两个卷积层(包含池化层),两个全连接层构成的卷积神经网络。输入图像是28×28的单通道数据,输出是10×1的one_hot编码的向量。
第一层:卷积核大小是[5,5],输入通道1,输出通道32,padding选择SAME模式,激活函数为relu。
第二层:池化层,池化核大小是[2,2],步长[2,2]。
第三层:卷积核大小是[5,5],输入通道32,输出通道64,padding选择SAME模式,激活函数为relu。
第四层:池化层,设置同上。
第五层:全连接层,首先将图像数据矩阵flatten化,变成1维向量,输出维度是1024, 之后dropout掉一定的数据防止过拟合
第六层:全连接层,输出维度10,激活函数为softmax
损失函数:交叉熵

具体实现如下:

导入包和数据

首先导入需要的包和数据

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_DATA/', one_hot=True)

定义卷积核

卷积核是指定维数的张量,卷积核的大小为:[filter_height, filter_width, in_channels, out_channels]
这里用tf.Variable()定义卷积核,定义的时候会指定初始化的操作。

def weight_variable(shape):
    initial = tf.truncated_normal(shape,stddev=0.1)
    weight= tf.Variable(initial_value=initial)
return weight

定义偏置

def bias_variable(shape):
    initial = tf.constant(0.1,shape=shape)
    bias = tf.Variable(initial_value=initial)
    return bias

对输入做卷积操作

def conv_op(in_tensor, kernel, strides=[1,1,1,1], padding=’SAME’, name=’conv’):
    conv_out = tf.nn.conv2d(in_tensor,kernel,strides, padding,name=name)
return conv_out

输入的张量维度是[batch, in_height,in_width,in_channels]
在具体操作中,程序会将输入张量展开为[batch, out_height, out_width, filter_hightfilter_widthin_channels]
并将卷积核展开为[filter_heightfilter_widthin_channels, out_channels ]
输入张量展开后与卷积核的展开相乘,得到的结果的维度是:[batch, out_height, out_width, out_channels],和输入张量的维度保持一致。也就是说,在tensorflow构建的图中,张量始终都是以始终是:[batch, height,width,channels]的维度流动。
步长strides通常定义为strides=[1, dh, dw, 1], 也就是strides[0]=strides[3]=1. dh和dw分别是垂直和水平方向卷积核移动的步长。
padding就是是否要做边界填充,如果要,padding=‘SAME’,如果不用,padding=‘VALID’

叠加上偏置并输出到激活函数

h_conv1 = tf.nn.relu(conv_op(x_image, W_conv1)+b_conv1)
之后输入到激活函数,直接调用tensorflow中的tf.nn.relu()函数就可以,激活函数的输出依然是:[batch, out_height, out_width, out_channels]

tf.nn.relu(features, name = None)

输入参数:

  • features: 一个Tensor。数据类型必须是:float32,float64,int32,int64,uint8,int16,int8。
  • name: (可选)为这个操作取一个名字。

输出参数:

  • 一个Tensor,数据类型和features相同。

在tensorflow中,加法运算有好几种,例如专门的偏置叠加tf.bias_add,还有tf.add,也可以直接用a+b,具体区别见后。

池化层

池化操作是:对输入的张量进行区域扫描求最大值或者平均值。一般求最大值。tensorflow有tf.nn.max_pool()函数可以方便地实现池化操作。池化尺寸kh×kw, 步长是dh×dw,padding设置为‘SAME’。

def max_pool_2x2(in_tensor, ksize=[1,2,2,1], strides=[1,2,2,1],padding=’SAME’,name=’max_pooling’):
    max_pool = tf.nn.max_pool(in_tensor, ksize, strides, padding, name=name)
return max_pool

这里ksize=[1,kh,kd,1], strides=[1,dh,dw,1]

目标函数:交叉熵

 cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y_conv),reduction_indices=[1]))

自此,就定义好了前向计算的所有操作。后向计算采用AdamOptimizer函数,学习率1e-4

最后,给出卷积操作的实现代码:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Jul  9 12:16:52 2017
mnist by simple cnn
@author: 
"""

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets('MNIST_DATA/', one_hot=True)

sess = tf.InteractiveSession()

# ======================================================== #
#                       SIMPLE CNN
# ======================================================== #

# define conv kernel
def weight_variable(shape):
    initial = tf.truncated_normal(shape,stddev=0.1)
    weight = tf.Variable(initial_value=initial)
    return weight

# define conv bias
def bias_variable(shape):
    initial = tf.constant(0.1,shape=shape)
    bias = tf.Variable(initial_value=initial)
    return bias

# define a simple conv operation 
def conv_op(in_tensor, kernel, strides=[1,1,1,1], padding='SAME'):
    conv_out = tf.nn.conv2d(in_tensor, kernel, strides=strides, padding=padding)
    return conv_out

# define max pooling operation
def max_pool_2x2(in_tensor,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME'):
    max_pool = tf.nn.max_pool(in_tensor, ksize, strides, padding)
    return max_pool

def simple_cnn():
    x = tf.placeholder(tf.float32, [None, 784])
    y_ = tf.placeholder(tf.float32, [None, 10])
    x_image = tf.reshape(x,[-1, 28, 28, 1]) # 28*28 pic of 1 channel

    # cnn structure
    w1 = [5,5,1,32]
    b1 = [32]
    w2 = [5,5,32,64]
    b2 = [64]
    wfc1 = [7*7*64,1024]
    bfc1 = [1024]
    wfc2 = [1024,10]
    bfc2 = [10]

    # 1st layer
    W_conv1 = weight_variable(w1)
    b_conv1 = bias_variable(b1)
    h_conv1 = tf.nn.relu(conv_op(x_image, W_conv1)+b_conv1)
    h_pool1 = max_pool_2x2(h_conv1)
    # 2nd layer
    W_conv2 = weight_variable(w2)
    b_conv2 = bias_variable(b2)
    h_conv2 = tf.nn.relu(conv_op(h_pool1, W_conv2)+b_conv2)
    h_pool2 = max_pool_2x2(h_conv2)
    # fc1
    h_pool2_flat = tf.reshape(h_pool2,[-1,7*7*64])
    W_fc1 = weight_variable(wfc1)
    b_fc1 = bias_variable(bfc1)
    h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1)+b_fc1)
    # drop out
    keep_prob = tf.placeholder(tf.float32)
    h_fc1_drop = tf.nn.dropout(h_fc1,keep_prob)
    # fc2
    W_fc2 = weight_variable(wfc2)
    b_fc2 = bias_variable(bfc2)
    y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2)+b_fc2)
    # loss function
    cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y_conv),reduction_indices=[1]))
    train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

    # estimate accuarcy
    correct_prediction = tf.equal(tf.arg_max(y_conv,1), tf.arg_max(y_,1))
    accuarcy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))    

    tf.global_variables_initializer().run()
    for i in range(20000):
        batch = mnist.train.next_batch(50)
        if i%100 == 0:
            train_accuracy = accuarcy.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0}) # testify, or prediction
            print('step %d, training accuarcy %g'%(i, train_accuracy))
        train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5})

    print("test accuracy %g"%accuarcy.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1.0}))
    return

if __name__ == '__main__':
    simple_cnn()

padding

padding的选项:

  • valid就是指卷积核是以整个卷积核为基础从边沿对齐图像的边沿开始,任何边沿到达图像另一边时就停止卷积运算;
  • same则是以卷积核的中间点为基础,中间点扫过真个图像才结束。自然,same的模式下,卷积后的输出和原输入的维度是一致的。

tf.cast

估计模型的准确度时,用到了函数tf.cast
tf.cast(x, dtype, name=None)数据类型转换,将x或者x.values转换为dtype
a=tf.constant(value=[1.1,2.3],dtype=tf.float32)
b=tf.cast(a,dtype=tf.int32)
sess = tf.Session()
print(sess.run(a)) #输出[ 1.10000002 2.29999995]
print(sess.run(b)) #输出[1 2]

tf.add(a,b)、tf.bias_add(a,b)和 a+b

1) tf.add(a, b) 与 a+b

在神经网络前向传播的过程中,经常可见如下两种形式的代码:
tf.add(tf.matmul(x, w), b)
tf.matmul(x, w) + b
简而言之,就是 tf.add(a, b) 与 a + b二者的区别,类似的也有,tf.assign 与 =(赋值运算符)的差异。
在计算精度上,二者并没有差别。运算符重载的形式a+b,会在内部转换为,a.add(b),而a.add(b)会再一次地映射为tf.add。

2. tf.nn.bias_add 与 tf.add

tf.nn.bias_add 是 tf.add 的一个特例,也即 tf.add 支持的操作比 tf.nn.bias_add 更多。二者均支持 broadcasting(广播机制),也即两个操作数最后一个维度保持一致。
除了支持最后一个维度保持一致的两个操作数相加外,tf.add 还支持第二个操作数是一维的情况。
参考:
《tensorflow实战》
http://blog.csdn.net/wangjian1204/article/details/53291619
In tensorflow what is the difference between tf.add and operator (+)?(https://stackoverflow.com/questions/37900780/in-tensorflow-what-is-the-difference-between-tf-add-and-operator)


为您推荐了相关的技术文章:

  1. TensorFlow 教程 #02 - 卷积神经网络
  2. 独家 | 一文读懂深度学习(附学习资源)
  3. 深度神经网络的数学基础,对你来说会不会太难?
  4. GAN学习指南:从原理入门到制作生成Demo
  5. tensorflow CNN 卷积神经网络中的卷积层和池化层的代码和效果图

原文链接: www.jianshu.com