TensorFlow的深度学习文章->_->卷积神经网络应用于手写数字识别

作者:陈劲灿 编辑日期: 2017年12月26日 14:32 阅读量: 452 分类: ai
什么是卷积神经网络

首先应该将卷积神经网络分为卷积以及神经网络。

那么什么是卷积? 卷积就是一个数学公式它就是通过两个函数生成第三个函数的数学算子。

那么什么是神经网络? 简单来说就是人工的网络结构。

组合起来就是通过在人工的网络结构中应用卷积叫做卷积神经网络。

卷积、池化,ReLU
什么是卷积?

先看看卷积的公式:

卷积公式

通过公式可以看出通过x(t)与h(t)的卷积生成了y(t)。

是不是看起来好复杂?

来个动态图看看卷积在做什么? 假设我们有一张5x5的图片:

卷积5x5的图片

以及一个3x3的矩阵:

卷积3x3的矩阵

然后图片与矩阵的卷积动态图为:

卷积在做什么

是不是觉得清晰一些了,不过还是不知道它是做什么的????

对于上面的例子来通俗来说卷积就是通过将5x5像素大小的图片与3x3大小的矩阵进行卷积,每一步生成一个新的像素,最终生成压缩的并且没有损失空间信息的新图片。

卷积过程中有步长和边界的概念这里先不讨论。

什么是池化?

池化简单的来说就是进行采样。这个采样的方式包含最大化、平均化、加和。如下面图所示以2步长的最大化表示

池化-最大化图片

为什么要在卷积神经网络中使用池化? 使用池化的目的之一就是降维(减少数据量),另外就是进行特征提取。

ReLU? 什么鬼

先导篇中我们学习了Sigmoid 神经元, 这里使用ReLU神经元(扩展:ReLU的变种Leaky-ReLU、P-ReLU、R-ReLU三种拓展激活函数)

它的表示很简单:f(x)=max(0,x),

这里使用ReLU的原因是使用 ReLU得到的SGD的收敛速度会比 sigmoid/tanh 快。这是因为它是线性的,而且ReLU只需要一个阈值就可以得到激活值,不用去计算复杂的运算从而可以节省运行时间。

不过ReLU也有缺点:

训练过程该函数不适应较大梯度输入,因为在参数更新以后,ReLU的神经元不会再有激活的功能,导致梯度永远都是零。先忽略它的缺点吧。

设计模型

上面讲解了一些概念的东西不过我们始终需要在Tensorflow上应用。下面通过使用两层卷积模型来识别手写数字。

第一层卷积层

一层卷积是由一个卷积连接上一个池化组成的,为什么要这样做?先不管。

Tensorflow中提供了方便的方法来操作卷积函数tf.nn.conv2d,不过这里封装成一个函数方便使用。

1.
2.
def conv2d(x, W):
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

池化也是提供了函数,这里我们使用最大化tf.nn.max_pool

1.
2.
3.
def max_pool_2x2(x):
  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.
 8.
 9.
10.
with tf.name_scope('reshape'):
  x_image = tf.reshape(x, [-1, 28, 28, 1])

with tf.name_scope('conv1'):
  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)

with tf.name_scope('pool1'):
  h_pool1 = max_pool_2x2(h_conv1)

代码中包含特殊的函数tf.name_scope,通俗上来说使用name_scope是为了方便命名管理。 在其内部创建的变量都会显示创建带scope前缀,比如下面这个例子:

1.
2.
3.
4.
5.
6.
7.
8.
9.
import tensorflow as tf

v1 = tf.Variable(tf.zeros([1]), tf.float32, name="v1")

with tf.name_scope("test"):
    v2 = tf.Variable(tf.zeros([1]), tf.float32, name="v2")

print(v1.name)
print(v2.name)

v1:0 test/v2:0 看到区别了吗,当然还有另外一个函数tf.variable_scope 它会配合name_scope使用。

改变输入数据的维度

可以看到第一层name_scope是reshape,之前我们知道一张手写数字识别的图片为28x28像素,那这里为什么需要将这个二维像素改变为四维的?

其实原因很简单主要是为了和第一层卷积的输入维度相匹配。其中第一维-1代表未知 第二维第三维代表图片的宽高,第四维代表图片的通道数。灰度通道为1,rgb为3。

卷积

进行卷积操作,我们需要一个权重W_conv1以及一个偏移b_conv1, 权重是一个[5, 5, 1, 32]的矩阵, patch大小为5x5, 1为通道数, 32为输出特征。 通过conv2d函数获取输入数据预权重的卷积,输出32个特征值

偏移对应着32个输出特征。

ReLU激活函数

使用ReLU神经元获取这一层的最终输出。

池化

将卷积后的输出特征进行最大化池化(max_pool),提取主要特征,并且降低维度。

第二层卷积层

第二层卷积以第一层卷积的最终池化结果h_pool1 为输入,

卷积 卷积还是需要权重W_conv2与偏移b_conv2,权重为[5, 5, 32, 64] patch的大小还是5x5,通过第一层卷积我们知道输出特征为32个,所以第三个维度为32,我们设置输出特征为64.

由于输出特征为64,所以偏移b_conv2也为64。

ReLU激活函数

再次使用ReLU神经元获取这一层的最终输出。

池化

将卷积后的输出特征进行最大化池化(max_pool),提取主要特征,并且降低维度。

最终第二层卷积的代码为:

1.
2.
3.
4.
5.
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])

h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
密集连接层(全连接层)

什么是全连接层?

首先全连接层在整个卷积神经网络中起到“分类器”的作用。我们可以通过全连接层将隐藏层的输出转为1x1的卷积。以达到最终进行分类的目的

通过两层最大池化操作,我们原来的28x28的手写识别图片编程了7x7的图片。并且第二层卷积的h_pool2 具有64个输出特征。

先定义全连接层的权重为:

W_fc1 = weight_variable([7 7 64, 1024])

这里我们设定输出为1024个特征。

对应的偏移为:

b_fc1 = bias_variable([1024])

将h_pool2 进行reshape以达到进行计算的目的:

h_pool2_flat = tf.reshape(h_pool2, [-1, 7764])

最终我们在添加一个ReLU。

1.
2.
3.
4.
5.
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])

h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
Dropout

什么是Dropout?

dropout是指在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率将其暂时从网络中丢弃。dropout是cnn中防止过拟合提高效果的重要手段。

下面是Dropout层的代码:

1.
2.
keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
输出层

通过上面的全连接层我们知道有1024个输出,可是最终我们需要进行识别的数字只有10个,需要再添加一层输出层将其转换为10个输出结果:

1.
2.
3.
4.
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])

y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2

自此我们的模型搭建完毕。

训练模型 以及 评估模型

在上一篇Minst入门中我们知道使用梯度下降算法GradientDescentOptimizer 这个优化算法来不断的修改变量使我们的成本降低。 不过它只是优化算法中最基础的一种,在神经网络中包含了(SDG、Nesterov、Adagrad、AdaDelta、Adam)等优化算法,后面会单独开一篇来回顾。

这里我们使用Adam算法全称为自适应时刻估计方法(Adaptive Moment Estimation),我们现在只使用不管它的具体含义。由Minst入门,我们知道需要计算交叉熵,然后使用优化算法不断的降低成本。下面就是训练的代码:

1.
2.
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

然后通过判断预测值与实际值我们即可以获取到识别的正确率:

1.
2.
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

创建Session然后通过训练数据不断的进行训练:

1.
2.
3.
4.
5.
6.
7.
8.
9.
with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())
  for i in range(20000):
    batch = mnist.train.next_batch(50)
    if i % 100 == 0:
      train_accuracy = accuracy.eval(feed_dict={
          x: batch[0], y_: batch[1], keep_prob: 1.0})
      print('step %d, training accuracy %g' % (i, train_accuracy))
    train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

最终我们可以使用测试数据来校验手写数字识别模型的正确率:

1.
2.
print('test accuracy %g' % accuracy.eval(feed_dict={
      x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))

下面为官方完整代码,需要注意的是修改测试数据路径:

  1.
  2.
  3.
  4.
  5.
  6.
  7.
  8.
  9.
 10.
 11.
 12.
 13.
 14.
 15.
 16.
 17.
 18.
 19.
 20.
 21.
 22.
 23.
 24.
 25.
 26.
 27.
 28.
 29.
 30.
 31.
 32.
 33.
 34.
 35.
 36.
 37.
 38.
 39.
 40.
 41.
 42.
 43.
 44.
 45.
 46.
 47.
 48.
 49.
 50.
 51.
 52.
 53.
 54.
 55.
 56.
 57.
 58.
 59.
 60.
 61.
 62.
 63.
 64.
 65.
 66.
 67.
 68.
 69.
 70.
 71.
 72.
 73.
 74.
 75.
 76.
 77.
 78.
 79.
 80.
 81.
 82.
 83.
 84.
 85.
 86.
 87.
 88.
 89.
 90.
 91.
 92.
 93.
 94.
 95.
 96.
 97.
 98.
 99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import argparse
import sys
import tempfile

from tensorflow.examples.tutorials.mnist import input_data

import tensorflow as tf

FLAGS = None


def deepnn(x):
  """deepnn builds the graph for a deep net for classifying digits.

  Args:
    x: an input tensor with the dimensions (N_examples, 784), where 784 is the
    number of pixels in a standard MNIST image.

  Returns:
    A tuple (y, keep_prob). y is a tensor of shape (N_examples, 10), with values
    equal to the logits of classifying the digit into one of 10 classes (the
    digits 0-9). keep_prob is a scalar placeholder for the probability of
    dropout.
  """
  # Reshape to use within a convolutional neural net.
  # Last dimension is for "features" - there is only one here, since images are
  # grayscale -- it would be 3 for an RGB image, 4 for RGBA, etc.
  with tf.name_scope('reshape'):
    x_image = tf.reshape(x, [-1, 28, 28, 1])

  # First convolutional layer - maps one grayscale image to 32 feature maps.
  with tf.name_scope('conv1'):
    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)

  # Pooling layer - downsamples by 2X.
  with tf.name_scope('pool1'):
    h_pool1 = max_pool_2x2(h_conv1)

  # Second convolutional layer -- maps 32 feature maps to 64.
  with tf.name_scope('conv2'):
    W_conv2 = weight_variable([5, 5, 32, 64])
    b_conv2 = bias_variable([64])
    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)

  # Second pooling layer.
  with tf.name_scope('pool2'):
    h_pool2 = max_pool_2x2(h_conv2)

  # Fully connected layer 1 -- after 2 round of downsampling, our 28x28 image
  # is down to 7x7x64 feature maps -- maps this to 1024 features.
  with tf.name_scope('fc1'):
    W_fc1 = weight_variable([7 * 7 * 64, 1024])
    b_fc1 = bias_variable([1024])

    h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
    h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

  # Dropout - controls the complexity of the model, prevents co-adaptation of
  # features.
  with tf.name_scope('dropout'):
    keep_prob = tf.placeholder(tf.float32)
    h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

  # Map the 1024 features to 10 classes, one for each digit
  with tf.name_scope('fc2'):
    W_fc2 = weight_variable([1024, 10])
    b_fc2 = bias_variable([10])

    y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
  return y_conv, keep_prob


def conv2d(x, W):
  """conv2d returns a 2d convolution layer with full stride."""
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')


def max_pool_2x2(x):
  """max_pool_2x2 downsamples a feature map by 2X."""
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                        strides=[1, 2, 2, 1], padding='SAME')


def weight_variable(shape):
  """weight_variable generates a weight variable of a given shape."""
  initial = tf.truncated_normal(shape, stddev=0.1)
  return tf.Variable(initial)


def bias_variable(shape):
  """bias_variable generates a bias variable of a given shape."""
  initial = tf.constant(0.1, shape=shape)
  return tf.Variable(initial)


def main(_):
  # Import data
  mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)

  # Create the model
  x = tf.placeholder(tf.float32, [None, 784])

  # Define loss and optimizer
  y_ = tf.placeholder(tf.float32, [None, 10])

  # Build the graph for the deep net
  y_conv, keep_prob = deepnn(x)

  with tf.name_scope('loss'):
    cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_,
                                                            logits=y_conv)
  cross_entropy = tf.reduce_mean(cross_entropy)

  with tf.name_scope('adam_optimizer'):
    train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

  with tf.name_scope('accuracy'):
    correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
    correct_prediction = tf.cast(correct_prediction, tf.float32)
  accuracy = tf.reduce_mean(correct_prediction)

  graph_location = tempfile.mkdtemp()
  print('Saving graph to: %s' % graph_location)
  train_writer = tf.summary.FileWriter(graph_location)
  train_writer.add_graph(tf.get_default_graph())

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

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

if __name__ == '__main__':
  parser = argparse.ArgumentParser()
  parser.add_argument('--data_dir', type=str,
                      default='C:/Users/86552/Desktop/mnist/MNIST_data',
                      help='Directory for storing input data')
  FLAGS, unparsed = parser.parse_known_args()
  tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)

几个有助于理解神经网络的网址

卷积神经网络的3D可视化 http://scs.ryerson.ca/~aharley/vis/conv/

一个博主的博客可以去看看 http://nooverfit.com/wp/


上一篇
下一篇
HTTP中使用的Status Code
TensorFlow的深度学习文章->_->训练模型的保存与模型的还原

【敢问,现在的大学生,路在何方?】真诚邀你解答疑惑

你这个名字很可以

太深奥