一、问题描述
二、数据描述
三、网络结构
1. 输入层
2. 卷积层
3. 池化层
4. 全连接层
5. 输出层
四、示例网络结构
五、程序解读
1. 加载MNIST数据集
2. 创建Session和占位符
3. 定义卷积层1的权重和偏置量
4. 卷积层1
5. 池化层1
6. 定义卷积层2的权重和偏置量
7. 卷积层2和池化层2
8. 全连接层
9. 输出层
六、参考资料
七、源码
利用卷积神经网络将MNIST数据集的28×28像素的灰度手写数字图片识别为相应的数字。
MNIST数据集是28×28像素的灰度手写数字图片,其中数字的范围从0到9
具体如下所示(参考自Tensorflow官方文档):
文件 |
内容 |
train-images-idx3-ubyte.gz |
训练集图片,55000张训练图片, 5000张验证图片 |
train-labels-idx1-ubyte.gz |
训练集图片对应的数字标签 |
t10k-images-idx3-ubyte.gz |
测试集图片,10000张图片 |
t10k-labels-idx1-ubyte.gz |
测试集图片对应的数字标签 |
卷积神经网络一般包含以下几层:
输入层:用于将数据输入到神经网络中
卷积层:使用卷积核提取特征
激励层:对卷积操作的线性运算进行非线性映射
池化层:卷积得到的特征图进行稀疏处理,减少数据量
全连接层:在网络的末端进行重新拟合,恢复特征,减少特征的损失
输出层:输出结果
卷积神经网络中输入层的结构可以是多维的,例如MNIST数据集中是28×28像素的灰度图片,因此输入为28×28的的二维矩阵。
卷积层是使用卷积核提取特征,在卷积层中需要理解局部感受野和共享权值。
局部感受野:类似于一个滑动窗口,以窗口的范围去提取对应范围的神经元携带的特征。
共享权值:根据局部感受野提取特征,原始数据中的一部分神经元与卷积层中的一个神经元相连接,每一条线对应一个权重,而在卷积层中,对于同一个卷积核,权重是相同的。
上图为卷积操作示意图(图片来源于网络,侵删),其中Image表示图片数据矩阵,游走的窗口为卷积核矩阵,x0、x1表示的是权重,一个N×N的图像经过M×M的卷积核卷积后将得到(N-M+1)×(N-M+1)的输出。
卷积后输出的矩阵数据成为特征映射图,一个卷积核输出一个特征映射图,卷积操作是一种线性计算,因此通常在卷积后进行一次非线性映射。
池化层是将卷积得到的特征映射图进行稀疏处理,减少数据量,操作与卷积基本相似,不同的是卷积操作是一种线性计算,而池化的计算方法更多样化,一般有如下计算方式:
最大池化:取样池中的最大值作为池化结果
均值池化:取样池中的平均值作为池化结果
还有重叠池化、均方池化、归一化池化等方法。
在网络的末端对提取后的特征进行恢复,重新拟合,减少因为特征提取而造成的特征丢失。全连接层的神经元数需要根据经验和实验结果进行反复调参。
输出层用于将最终的结果输出,针对不同的问题,输出层的结构也不相同,例如MNIST数据集识别问题中,输出层为有10个神经元的向量。
示例模型包括输入层、两个卷积层、两个池化层、全连接层和输出层,其中卷积和池化操作的特征图输出大小计算公式为:
ImageWidth:图片宽度
Padding:边缘补齐像素数
KernelSize:卷积核宽度
Stride:移动步长
具体模型结构如下所示:
Tensorflow中使用图来表示计算任务,在会话(Session)中执行图,使用 tensor 表示数据.通过变量(Variable)维护状态,使用 feed 和 fetch 可以为任意的操作赋值或者从其中获取数据.
mnist =input_data.read_data_sets("MNIST_data", one_hot=True)
命令会自动下载MNIST数据集,存放在"MNIST_data"目录下,也可以手动下载数据集后放入此目录下。执行read_data_sets()函数后将会返回一个DataSet实例,其中包含训练数据、验证数据和测试数据。
sess =tf.InteractiveSession()
x =tf.placeholder("float", shape=[None, 784])
y_ =tf.placeholder("float", shape=[None, 10])
x和y_都是tensor,其中x表示输入数据,由于是28×28像素的灰度图片,因此输入为784维的向量。y_表示模型输出,为0-9的数字,因此是10维的向量。
w_conv1 = tf.Variable(tf.truncated_normal([5,5, 1, 32], stddev=0.1))
b_conv1 =tf.Variable(tf.constant(0.1, shape=[32]))
卷积操作的计算公式为:W × X + b
[5, 5, 1,32]表示卷积核的大小为5×5,输出为32,即共有32个卷积核,卷积操作会产生32个特征映射图。
其中w_conv1表示权重W,由正太分布截取得出。b_conv1表示偏置量,初始值均为0.1,由于卷积操作会输出32个特征图,因此偏置量的维度为32。
x_image =tf.reshape(x, [-1,28,28,1])
将输入tensor x 调整成为28×28矩阵形式。
r_conv1 = tf.nn.conv2d(x_image,w_conv1, strides=[1, 1, 1, 1], padding='SAME') + b_conv1
h_conv1 = tf.nn.relu(r_conv1)
进行卷积操作W × X + b,得到线性变化的结果r_conv1,再利用Tensorflow的relu规则进行非线性映射,出的卷积的结果h_conv1。
h_pool1 = tf.nn.max_pool(h_conv1,ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
采用了最大池化方法,其中ksize表示取样池的大小,strides表示步长,padding表示边缘补齐方法,SAME方式会在图片边缘补0,补齐边缘像素为1,最终得出池化结果h_pool1。
w_conv2 =tf.Variable(tf.truncated_normal([5, 5, 32, 64], stddev=0.1))
b_conv2 =tf.Variable(tf.constant(0.1, shape=[64]))
卷积层2的输入为32张特征映射图,有64个卷积核,最终将输出64个特征映射图。
r_conv2 = tf.nn.conv2d(h_pool1,w_conv2, strides=[1, 1, 1, 1], padding='SAME') + b_conv1
h_conv2 =tf.nn.relu(r_conv2)
h_pool2 = tf.nn.max_pool(h_conv2,ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
经过卷积层2和池化层2后,得到64张7×7的特征映射图。
W_fc1 =tf.Variable(tf.truncated_normal([7 * 7 * 64, 1024], stddev=0.1))
b_fc1 =tf.Variable(tf.constant(0.1, shape=[1024]))
全连接层设有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)
将第二层池化后的数据调整为7×7×64的向量,与全连接层的权重进行矩阵相乘,然后进行非线性映射得到1024维的向量。
W_fc2 = tf.Variable(tf.truncated_normal([1024,10], stddev=0.1))
b_fc2 = tf.Variable(tf.constant(0.1,shape=[10]))
y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2) + b_fc2)
输出层为10维的向量,通过softmax函数输出。
深度学习(四)卷积神经网络入门学习(1)
深度学习之卷积神经网络CNN及tensorflow代码实现示例
TensorFlow 官方文档
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import tensorflow as tf
# 加载测试数据的读写工具包,加载测试手写数据,目录MNIST_data是用来存放下载网络上的训练和测试数据的。
# 这里,参考我前面的博文,由于网络原因,测试数据,我单独下载后,放在当前目录的MNIST_data目录了。
import tensorflow.examples.tutorials.mnist.input_data as input_data
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
# 创建一个交互式的Session。
sess = tf.InteractiveSession()
# 创建两个占位符,数据类型是float。x占位符的形状是[None,784],即用来存放图像数据的变量,图像有多少张
# 是不关注的。但是图像的数据维度有784围。怎么来的,因为MNIST处理的图片都是28*28的大小,将一个二维图像
# 展平后,放入一个长度为784的数组中。
# y_占位符的形状类似x,只是维度只有10,因为输出结果是0-9的数字,所以只有10种结构。
x = tf.placeholder("float", shape=[None, 784])
y_ = tf.placeholder("float", shape=[None, 10])
# 通过函数的形式定义权重变量。变量的初始值,来自于截取正态分布中的数据。
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(initial)
# 通过函数的形式定义偏置量变量,偏置的初始值都是0.1,形状由shape定义。
def bias_variable(shape):
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial)
# 定义卷积函数,其中x是输入,W是权重,也可以理解成卷积核,strides表示步长,或者说是滑动速率,包含长宽方向
# 的步长。padding表示补齐数据。 目前有两种补齐方式,一种是SAME,表示补齐操作后(在原始图像周围补充0),实
# 际卷积中,参与计算的原始图像数据都会参与。一种是VALID,补齐操作后,进行卷积过程中,原始图片中右边或者底部
# 的像素数据可能出现丢弃的情况。
def conv2d(x, w):
return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME')
# 这步定义函数进行池化操作,在卷积运算中,是一种数据下采样的操作,降低数据量,聚类数据的有效手段。常见的
# 池化操作包含最大值池化和均值池化。这里的2*2池化,就是每4个值中取一个,池化操作的数据区域边缘不重叠。
# 函数原型:def max_pool(value, ksize, strides, padding, data_format="NHWC", name=None)。对ksize和strides
# 定义的理解要基于data_format进行。默认NHWC,表示4维数据,[batch,height,width,channels]. 下面函数中的ksize,
# strides中,每次处理都是一张图片,对应的处理数据是一个通道(例如,只是黑白图片)。长宽都是2,表明是2*2的
# 池化区域,也反应出下采样的速度。
def max_pool_2x2(x):
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
# 定义第一层卷积核。shape在这里,对应卷积核filter。
# 其中filter的结构为:[filter_height, filter_width, in_channels, out_channels]。这里,卷积核的高和宽都是5,
# 输入通道1,输出通道数为32,也就是说,有32个卷积核参与卷积。
W_conv1 = weight_variable([5, 5, 1, 32])
# 偏置量定义,偏置的维度是32.
b_conv1 = bias_variable([32])
# 将输入tensor进行形状调整,调整成为一个28*28的图片,因为输入的时候x是一个[None,784],有与reshape的输入项shape
# 是[-1,28,28,1],后续三个维度数据28,28,1相乘后得到784,所以,-1值在reshape函数中的特殊含义就可以映射程None。即
# 输入图片的数量batch。
x_image = tf.reshape(x, [-1,28,28,1])
# 将2维卷积的值加上一个偏置后的tensor,进行relu操作,一种激活函数,关于激活函数,有很多内容需要研究,在此不表。
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
# 对激活函数返回结果进行下采样池化操作。
h_pool1 = max_pool_2x2(h_conv1)
# 第二层卷积,卷积核大小5*5,输入通道有32个,输出通道有64个,从输出通道数看,第二层的卷积单元有64个。
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)
# 图片尺寸减小到7x7,加入一个有1024个神经元的全连接层,用于处理整个图片。把池化层输出的张量reshape成一些
# 向量,乘上权重矩阵,加上偏置,然后对其使用ReLU激活操作。
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
# 将第二层池化后的数据进行变形
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
# 进行矩阵乘,加偏置后进行relu激活
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
keep_prob = tf.placeholder("float")
# 对第二层卷积经过relu后的结果,基于tensor值keep_prob进行保留或者丢弃相关维度上的数据。这个是为了防止过拟合,快速收敛。
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
# 最后,添加一个softmax层,就像前面的单层softmax regression一样。softmax是一个多选择分类函数,其作用和sigmoid这个2值
# 分类作用地位一样,在我们这个例子里面,softmax输出是10个。
y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
# 实际值y_与预测值y_conv的自然对数求乘积,在对应的维度上上求和,该值作为梯度下降法的输入
cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
# 下面基于步长1e-4来求梯度,梯度下降方法为AdamOptimizer。
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
# 首先分别在训练值y_conv以及实际标签值y_的第一个轴向取最大值,比较是否相等
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
# 对correct_prediction值进行浮点化转换,然后求均值,得到精度。
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
# 先通过tf执行全局变量的初始化,然后启用session运行图。
sess.run(tf.global_variables_initializer())
for i in range(20000):
# 从mnist的train数据集中取出50批数据,返回的batch其实是一个列表,元素0表示图像数据,元素1表示标签值
batch = mnist.train.next_batch(50)
if i % 100 == 0:
# 计算精度,通过所取的batch中的图像数据以及标签值还有dropout参数,带入到accuracy定义时所涉及到的相关变量中,进行
# session的运算,得到一个输出,也就是通过已知的训练图片数据和标签值进行似然估计,然后基于梯度下降,进行权值训练。
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))
# 此步主要是用来训练W和bias用的。基于似然估计函数进行梯度下降,收敛后,就等于W和bias都训练好了。
train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
# 对测试图片和测试标签值以及给定的keep_prob进行feed操作,进行计算求出识别率。就相当于前面训练好的W和bias作为已知参数。
print("cf accuracy %g"%accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))