700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Tensorflow卷积神经网络识别MINST手写数字

Tensorflow卷积神经网络识别MINST手写数字

时间:2018-05-29 09:44:33

相关推荐

Tensorflow卷积神经网络识别MINST手写数字

开发环境:

Ubuntu16.04+Tensorflow1.5.0-GPU+CUDN9.0+CUDNN7.0

如果是Debian系列的系统,请参考这篇博客进行安装。

所有完整代码的github地址为:/StudentErick/tensorflow_cnn_mnist/tree/master

卷积神经网络的架构

关于卷积神经网络的一般架构的理解,参考这篇博客

在这里,给出本次实验需要用到的神经网络的架构:

输入的是一个28*28的二维灰度图片,其中每一个小单元的取值n∈[0,255]n∈[0,255],表示这个像素的灰度值。不过,MNIST数据集合把这个图片压缩成了一个[1,784]的矩阵。因此,在读取数据的时候,需要进行一次图片恢复成28*28的操作。

本次实验用的模型连同输入和输出层共有8层(图上把卷积与池化视为一层,我们拆分来看),分别是:

1. 输入层,28*28的灰度图片

2. 第一个卷积层,把1卷积为28*28*32的数据集,并把该数据集传入Relu激活函数。卷积过滤器的尺寸是5*5,每次移动的步长是1.

3. 第一个最大池化层,把2进行最大池化操作,输出14*14*32的数据集。采取的池化的过滤器的尺寸是2*2,每次移动的步长是2 。

4. 第二个卷积层,把3再次进行卷积操作,输出14*14*64的数据集,并把数据集传入Relu激活函数。卷积过滤器的尺寸是5*5,移动的步长是1

5. 第二个最大池化层,把4进行最大池化操作,输出7*7*64的数据集。池化的过滤器的尺寸是2*2,每次移动的步长是2.

6. 该层是把5中所有的数据集展开成一个7*7*64=3136的一维向量(图片中的数据是错的!!!),并输入Relu激活函数

7. 该层新建了1000个神经元,与6经过激活后输出的所有神经元进行全连接。该层不进行Softmax处理!!!

8. 10个最终预测结果,与9所有的神经元进行全连接,并把该层输入 进Softmax函数,作为最终的预测概率分布。

Tensorflow完成网络架构

加载数据操作

要把一维向量恢复成28*28的操作。[-1, 28, 28, 1]是让计算机自动调整维数,在这篇博客中提到,调整合适的数据格式,这种方式经常用到。

# 导入MNIST数据集from tensorflow.examples.tutorials.mnist import input_datamnist = input_data.read_data_sets("MNIST_data/", one_hot=True)# 输入的数据,None可以根据batch-size的结果自动调整x_input = tf.placeholder(dtype=tf.float32, shape=[None, 784], name="x_input")# 预测结果y_true = tf.placeholder(dtype=tf.float32, shape=[None, 1])# 把输入的每张图片的数据进行恢复X_input = tf.reshape(x_input, shape=[-1, 28, 28, 1])

设置权重和偏置项操作

这里的权重相当于卷积层或者池化层的过滤器。抽象成函数,那么在每次设置的过滤器时,仅需调整函数的参数即可。

def create_weights(shape):'''随机初始化权重:param shape: 权重张量的形状:return: 初始化够的张量'''return tf.Variable(tf.truncated_normal(shape=shape, stddev=0.05))def create_biases(shape):'''随机初始化偏置项:param shape::return:'''return tf.Variable(tf.constant(0.5, shape=shape))

创建卷积层操作

关于卷积层的各个参数的意义,可以参照这篇关于卷积神经网络架构的博客

def create_conv_layer(input_layer,in_channels,filter_size,stride_size,out_channels,use_relu=True):'''创建卷积层:param input_layer: 前一层的输入:param in_channels: 输入的channel个数:param filter_size: filter的边长:param stride_size: stride的步长:param out_channels: 输出的channel个数:param use_relu: 是否使用relu:return: 当前层卷积后的结果'''weights = create_weights([filter_size, filter_size,in_channels, out_channels])biases = create_weights([out_channels])layer = tf.nn.conv2d(input=input_layer,filter=weights,strides=[1, stride_size, stride_size, 1],padding='SAME')layer += biasesif use_relu is True:layer = tf.nn.relu(layer)return layerpython

创建最大池化层

池化层一般情况下,紧跟着卷积层,池化是减少神经元数量的关键操作。关于各个参数的意义,同样参照卷积层提到的那篇博客。

def create_max_pooling_layer(input_layer, #filter_size, #stride_size): #''':param input_layer: 输出层:param filter_size: filter的边长:param stride_size: stride的步长:return: 卷积层最大池化后的结果'''input_layer = tf.nn.max_pool(input_layer,[1, filter_size, filter_size, 1],[1, stride_size, stride_size, 1],padding='SAME')return input_layer

对池化层进行flatten

全连接一般在最后一个池化操作后处理,它把池化层所有的元素展开成一维向量,这样方便后面的全连接操作。关于张量变形的操作,参照这篇博客。另外说明一点,get_shape()函数不包括第一维,因为第一维是batch-size,如果增加了这一维,就成了一次展开所有输入的训练图片了,没有意义。

def create_flatten_layer(layer):'''把输入的layer进行flatten操作:param layer: 输入的layer:return: flatten操作后的结果'''layer_num = layer.get_shape()[1:4].num_elements()layer = tf.reshape(layer, [-1, layer_num])return layer

创建全连接层

全连接层就是通过普通的矩阵乘法实现,注意权重的维数等于[输入的维数,输出的维数],乘法时是输入矩阵乘以权重,并添加上偏置项

def create_fc_layer(layer, in_num, out_num, use_relu):'''创建全连接层:param layer: 输入的层:return: 全连接后的输出层'''weight = create_weights(shape=[in_num, out_num])biases = create_biases([out_num])layer = tf.matmul(layer, weight) + biasesif use_relu is True:return tf.nn.relu(layer)return layer

最终的预测结果

把全连接的最后一层的维数设置成需要分类的实体的个数,然后把全连接的最后一层通过Softmax函数,输出的向量的各个维就是属于各类的概率,并选择概率最大的作为最终的预测结果。

网络向前传播的架构

经过前边的分析,网络各层的架构可以表示为:

# 输入的数据,None可以根据batch-size的结果自动调整x_input = tf.placeholder(dtype=tf.float32, shape=[None, 784], name="x_input")# 预测结果y_true = tf.placeholder(dtype=tf.float32, shape=[None, 1])# 把输入的每张图片的数据进行恢复X_input = tf.reshape(x_input, shape=[-1, 28, 28, 1])# 第一个卷积层layer_conv1 = create_conv_layer(input_layer=X_input,in_channels=1,filter_size=5,stride_size=1,out_channels=32,use_relu=True)# 第一个卷积层的池化层layer_pool1 = create_max_pooling_layer(input_layer=layer_conv1,filter_size=2,stride_size=2)# 第二个卷积层layer_conv2 = create_conv_layer(input_layer=layer_pool1,in_channels=32,filter_size=2,stride_size=1,out_channels=64)# 第二个卷积层池化后的结果layer_pool2 = create_conv_layer(input_layer=layer_conv2,in_channels=64,filter_size=2,stride_size=2,out_channels=64,use_relu=True)# layer_pool2进行flatten展开,得到3136维的列向量fc_layer1 = create_flatten_layer(layer=layer_pool2)# 全连接,输出1000维的向量,并经过relu函数激活fc_layer2 = create_fc_layer(layer=fc_layer1,in_num=3136,out_num=1000,use_relu=True)# 全连接,输出10维向量,作为最终预测的结果fc_layer3 = create_fc_layer(layer=fc_layer2,in_num=1000,out_num=10,use_relu=False)# 预测结果和真实值y_pred = tf.nn.softmax(fc_layer3)y_true = tf.placeholder(dtype=tf.int64, shape=[None, 10], name="y_true")

损失函数的定义与预测精度的设置

损失函数使用交叉熵表示两个向量的相似程度。注意,Tensorflow中的tf.nn.softmax_cross_entropy_with_logits函数计算完交叉熵后,返回的是一个张量,需对张量进行reduce_mean操作。还有,该函数本身就自带有Softmax操作了,因此不能把经过Softmax操作后的数据传入该函数,否则会导致结果不准确!!!!由此可见,我们在这里传入的数据是未经过Softmax的输出层fc_layer3,而经过Softmax操作输出的y_pred用于做精确度计算的。

# 计算交叉熵,注意这里的logits是fc_layer3,因为这个函数本身自带有Softmax的操作cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=fc_layer3,labels=y_true)# 损失函数cost = tf.reduce_mean(cross_entropy)# 随机梯度下降优化optimizer = tf.train.AdamOptimizer(learning_rate=1e-3).minimize(cost)

精度计算就是预测正确的数目占预测总数的百分比。首先,我们要明确y_pred的结构:

ypred=⎡⎣⎢⎢⎢⎢⎢⎢⎢y(1)1y(1)1⋮y(m)1y(1)2y(2)2⋮y(m)2⋯⋯⋱⋯y(1)10y(2)10⋮y(m)10⎤⎦⎥⎥⎥⎥⎥⎥⎥ypred=[y1(1)y2(1)⋯y10(1)y1(1)y2(2)⋯y10(2)⋮⋮⋱⋮y1(m)y2(m)⋯y10(m)]

矩阵总共有mm行,表示这一批次里面有m" role="presentation">m个图片作为输入;总共有10列,表示每个元素的属于0-9的概率。我们要计算的精度是这mm个元素中,预测正确的百分比。思路是:每一行保留概率最大的那一个元素所在的列数,得到一个列向量ypred_cls∈Rm" role="presentation">ypred_clsRm,并把这个向量与标准结果矩阵ytrue∈Rm×10ytrue∈Rm×10进行比较,计算两者对应元素相等的个数占维数的百分比即可。不过,在这里需要注意,MNIST数据集中,给出的编号向量也是ypredypred这种格式的,因此我们还需要对MNIST的数据集进行一次转化!Tensorflow的原文是这么定义真实数据标签的:

注意红色部分。因此需要对交叉熵进行一次reduce_mean的操作

# 多批次计算的时候是二维的,在这里提取出第列的,作为比较y_pred_cls = tf.argmax(y_pred, axis=1) # 预测结果y_true_cls = tf.argmax(y_true, axis=1) # MNIST标准结果# 计算精准度correction_prediction = tf.equal(y_pred_cls, y_true_cls)accuracy = tf.reduce_mean(tf.cast(correction_prediction, tf.float32), name="accuracy")

存储模型和加载模型进行识别

关于存储模型的基本操作,请参考这篇博客

首先,需要定义一个类,用于存储模型,在这里,我们存储这个计算图所有的数据,所以不添加参数。

# 存储模型的对象saver = tf.train.Saver()

在训练迭代完成之后,保存整个模型:

# 把当前计算图存储为my_test_modelsaver.save(sess, './my_test_model')

保存结束后,会在同级目录下多出几个文件:

在这之前所有的操作都是在train.py这个文件中的。之后,我们新建一个predict.py文件,用于加载之前保存的数据:

saver = tf.train.import_meta_graph('my_test_model.meta')saver.restore(sess, tf.train.latest_checkpoint('./'))

加载完成之后,在加载之前的占位符、操作等。注意一点,占位符需要重新feed数据!

graph = tf.get_default_graph() x_input = graph.get_tensor_by_name("x_input:0")y_true = graph.get_tensor_by_name("y_true:0")accuracy = graph.get_tensor_by_name("accuracy:0")xs, ys = mnist.train.next_batch(5000)print(sess.run(accuracy, feed_dict={x_input: xs, y_true: ys}))

注意上述代码中,没有使用官方推荐的mnist.test.image喂取x_inputminist.test.lables喂取y_true,因为使用的GPU版本会耗尽内存。因此仅仅使用了5000组训练数据。真正合理的做法可以使用一个输入输出的队列来处理,本文暂时不做这种介绍。上述模型的识别正确率可以到97.92%

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。