Tensorflow搭建卷积神经网络

写在前面

作为深度学习代表算法之一,卷积神经网络(Convolutional Neural Networks, CNN)是一类包含卷积计算而且具有深度结构的前馈神经网络。

理解卷积神经网络,就要理解什么是卷积和神经网络。对于神经网络的概念,在前述的文章中已经详细的介绍过,在这里不再做具体的介绍;而对于卷积这个概念,没有接触过一些信号处理类的专业书籍可能不是特别了解,在这里做一些简单的介绍。

1、什么是卷积

在这里我们引入信号与系统中对卷积的定义。

假设f(t)g(t)是定义域上的两个可积函数,作积分:

得到的新的函数h(t)是函数f(t)和g(t)的卷积。至于具体的卷积定理的了解,请参阅《信号与系统》。

2、卷积神经网络(CNN)

卷积神经网络相比于神经网络,其采用卷积运算提取特征是其最明显的特征。特别是在2012年的ImageNet竞赛中,Hinton和他的学生Alex Krizhevsky设计的卷积神经网络获得了优异的成绩极大的促进了卷积神经网络和深度学习的发展。

卷积神经网络包含输入层、隐藏层和输出层,这一点似乎是和神经网络的基础结构是相似的。但是不同点在于,隐藏层包含了卷积层和池化(pooling)层。图像输入到卷积神经网络后会通过卷积不断地提取特征,每提取一个特征就会增加一个feature map;而池化(pooling)层的作用就是将feature map的大小进行缩减,减小计算量,同时提高所提取的特征带的鲁棒性。通常池化方式大致有两种,分别是最大池化和平均池化。

3、搭建一个卷积神经网络(CNN)

数据集依然采用MNIST数据集。导入Tensorflow模块和MNIST数据集。

1
2
3
4
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

定义权值Weight变量,输入shape,返回变量的参数。和前面的分类示例中的定义不同,在分类示例中使用了tf.random_normal()函数产生随机值作为变量的值;在卷积神经网络这里则使用了tf.truncated_normal()函数产生随机值,随机值服从正态分布。

1
2
3
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(initial)

定义偏差值biase变量,输入shape,返回变量的参数。

1
2
3
def bias_variable(shape):
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial)

在卷积神经网络中,卷积是必不可少的。在这里需要定义卷积函数。在卷积过程中,x为输入的图像数据,W为卷积核,strides为步长,padding为卷积的形式。

函数格式:tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu = None, name = None)

input:做卷积的图像,要求是一个4维的Tensor,类型为float32或float64,shape为[batch, in_height, in_width, in_channels],其中batch表示训练一个batch时的图片数量;in_height表示输入图像的高度;in_width表示输入图像的宽度;in_channels表示输入图像的通道数,灰度图像的值为1,彩色图像的值为3。

filter:卷积神经网络中的卷积核,也是一个Tensor,类型必须和input相同,shape为[filter_height, filter_width, in_channels, out_channels],filter_height表示卷积核的高度;filter_width表示卷积核的宽度;in_channels表示图像的通道数,因为需要对图像的每个通道均做卷积;out_channels表示卷积核的个数。

strides:一个一行四列的向量,[ 1, strides, strides, 1]中间两个值分别代表padding时向x方向运动一步和向y方向运动一步。

padding:表示卷积的形式,是否考虑边界。SAME表示考虑边界,VALID表示不考虑边界。

返回一个Tensor,类型不变,shape仍然是[batch, height, width, channels]这种形式。

1
2
def conv2d (x, W):#x:输入的值;W:weight_variable
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

定义池化函数。

最大池化函数格式:tf.nn.max_pool(value, ksize, strides, padding, name=None)

value:需要池化的输入,一般池化层接在卷积层后面,所以输入通常是feature map,shape为[batch, height, width, channels]。

ksize:池化核大小,取一个四维向量,一般是[1, height, width, 1],height表示池化核的高度,width表示池化核的宽度。

strides:一个一行四列的向量,[ 1, strides, strides, 1]中间两个值分别代表padding时向x方向运动一步和向y方向运动一步。

padding:表示卷积的形式,是否考虑边界。SAME表示考虑边界,VALID表示不考虑边界。

返回一个Tensor,类型不变,shape仍然是[batch, height, width, channels]这种形式。

1
2
def max_pool_2x2(x):#池化核大小为2x2
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

定义交叉熵损失函数。

1
2
3
4
5
6
7
def compute_accuracy(v_xs, v_ys):
global prediction
y_pre = sess.run(prediction, feed_dict={xs: v_xs, keep_prob: 1})
correct_prediction = tf.equal(tf.argmax(y_pre, 1), tf.argmax(v_ys, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
result = sess.run(accuracy, feed_dict={xs:v_xs, ys:v_ys, keep_prob: 1})
return result

定义占位符。这部分的参数设置参考Tensorflow搭建神经网络进阶篇的内容。

1
2
xs = tf.placeholder(tf.float32, [None, 784])
ys = tf.placeholder(tf.float32, [None, 10])

定义dropout的占位符。

1
keep_prob = tf.placeholder(tf.float32)

调整输入值的维度。tf.reshape(xs, [-1, 28, 28, 1])将xs的维度调整。-1表示不先考虑输入值的维度,即不知道xs的大小是多少,但是想将其调整为28*28大小,由于输入的是灰度图像,因此设置为1。调整后的image.shape =(28,28,1)。

1
x_image = tf.reshape(xs, [-1, 28, 28, 1])

建立第一个卷积层。本层卷积核的大小为5x5,输入图像的通道数为1,输出的featuremap为32个。可以发现,卷积神经网络把普通的神经网络的乘法运算变成了卷积运算。W_conv1输出32个featuremap;b_conv1输出长度为32;h_conv1输出shape为28x28x32;h_pool1输出shape为14x14x32。

1
2
3
4
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

同样建立第二个卷积层。上层的输出就是这层的输入,因此卷积核大小为5x5, 本层通道数为32。输入图像的shape为(14,14,32),卷积核的shape为(5,5,32)。

1
2
3
4
W_conv2 = weight_variable([5, 5, 32, 64])#patch 5x5, in_size 32, out_size 64;
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)#output size 14x14x64#卷积
h_pool2 = max_pool_2x2(h_conv2)#output size 7x7x64#池化

建立全连接层。输入图像的shape为(7,7,64),将h_pool2输出的值进行一维转换,此时转换后值的shape为(1,7x7x64),一个行向量。W_fc1的shape为(7x7x64,1024)。全连接层的输出shape为(1,1024)。

1
2
3
4
5
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])#三维数据转换为一维数据
W_fc1 = weight_variable([7*7*64, 1024])
b_fc1 = bias_variable([1024])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
h_fcl_drop = tf.nn.dropout(h_fc1, keep_prob)

建立输出层。输出层的输入是全连接层的输出,全连接层的输出shape为(1,1024)。由于最后输出10个类,因此权重值应为(1024,10)。在这里使用了softmax分类器进行分类。

1
2
3
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
prediction = tf.nn.softmax(tf.matmul(h_fcl_drop, W_fc2) + b_fc2)

计算损失函数,采用交叉熵损失函数。

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

计算优化函数。

1
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

开始运行训练会话。

1
2
3
4
5
6
7
8
9
10
11
12
init = tf.initialize_all_variables()

sess = tf.Session()

sess.run(init)

for i in range(5000):
batch_size = 500
batch_xs, batch_ys = mnist.train.next_batch(batch_size) # 从train的集合中选取batch_size个训练数据
sess.run(train_step, feed_dict={xs:batch_xs, ys:batch_ys})
if i % 50 == 0:
print(compute_accuracy(mnist.test.images, mnist.test.labels))

卷积神经网络训练结果:

卷积神经网络训练结果

和基本的神经网络训练结果进行对比可以发现,卷积神经网络在1000次训练的结果优于基本的神经网络训练5000次的结果,这表现出了卷积神经网络的极大的优势。