问题描述
背景
- MNIST是一个包含大量经过正则化、中心化处理的手写数字及其标签的数据集,其中包括大小为60000的训练集和10000的测试集。
- softmax函数,\(softmax(x_i)=\frac{e^{x_i}}{\sum_{j=1}^{j=n}{e^{x_j}}}\)是一个归一化指数函数,可以将神经网络输出的一维向量归一化为各分量和为1的向量。于是归一化之后的向量便可作为n个离散变量的概率分布,从而实现多分类。
- TensorFlow是一个开源的机器学习平台。TensorFlow框架中的一核心概念是计算图,其中的每一个节点代表一个operation,简称op;数据用tensor表示;在被称为session的上下文中执行该计算图。整体的运行方式有点像搭建水管线路:通过op搭建出一个线路,打开水管开关session,使tensor流入线路中。
问题
本次实验在TensorFlow中文社区指导下,基于TensorFlow框架,用MNIST数据集训练两个机器学习模型:
- 具有单一线性层的softmax回归模型;
- 和具有多层卷积神经网络的softmax回归模型。
并对以上两个模型的学习效果进行对比。
方法过程
模型的建立
数学推导
MNIST数据集中的手写数字图片均为\(28\times28\)的灰度图。在此模型中我们暂时不考虑各像素之间的相对位置关系,于是可以将其视为一个\([1, 748]\)的张量,则整个训练集中的手写数字图片部分可以视为一个\([6000,748]\)的张量,其中第一位为图片编号索引,第二位为像素编号索引。在映射时对于\(R^{28}\times{R^{28}}\rightarrow{R}\times{R^{748}}\)的映射方式不做限制,只需保证所有的数据均经过同样的映射即可。
对于人类来说,我们在书写数字的过程中,要保证自己写的数字能被认出,至少需要保证结构相同。这里的“结构”是指哪一块应该出现什么形状,比如数字9就应该由上面的一个圈和下面连接圈的一竖组成;更进一步地,“结构”是指哪一部分像素应该被填充,哪一部分像素应该为空。
对于机器学习也是同理。
我们定义\(w_{i,j}\)代表第\(j\)块像素被认为是数字\(i\)的可能性高低,其中$0\leq{j<748},0\leq{i\leq9} $。
\(w_{i,j}\)组成形状为形状为\([748, 10]\)的张量\(W=\left[\begin{matrix}w_{1,0}&w_{1,1}&...&w_{1,9}\\{w_{2,0}}&w_{2,1}&...&w_{2,9}\\{...}&...&...&... \\{w_{748,0}}&w_{748,1}&...&w_{748,9}\end{matrix}\right]\)。
定义\(b_{i}\)表示数字\(i\)的偏置量,即线性回归的截距,则\(b_{i}\)组成形状为\([1,10]\)的张量\(b=[b_0,b_1,...,b_9]\)。
对于数据集中的图片,定义其对应的\([1,748]\)张量为\(a\),则\(a\times{W}+B\)为\([1,10]\)的张量,其中的每个分量代表该图片是数字\(i\)的可能性。
定义\([1,10]\)张量\(y=softmax(a\times{W}+b)\),则\(y\)为经过softmax函数归一化激活之后的得到的该图片为数字\(i\)的概率分布。
\(W\) 和 \(b\) 的取值是我们的模型需要学习部分。学习的目标是使基于此模型计算出的\(y\)与标签值\({y\_}\)相差最小。这里的相差值我们通过交叉熵\({cross\_entroy}=-\sum_i^n y_i\_\times\log(y_i)\)衡量。即目标为,最小化\({corss\_entroy}\),其中最小化的方法为神经网络中常用的反向传播(BP)优化梯度下降(GD)。
梯度下降求出交叉熵\(cross\_entroy\)随每个权重\(w_{i,j}\)变化的情况\(\frac{\partial{cross\_entroy}} {\partial{w_{i,j}}}\),每次更优解即\(w_{i,j}'=w_{i,j}-\alpha\times\frac{\partial{cross\_entroy}}{\partial{w_{i,j}}}\).
而\(\frac{\partial{cross\_entroy}}{\partial{w_{i,j}}}\)则通过函数求导的链式法则,从输出层开始进行反向传播。
卷积神经网络(CNN)的推导与上述基本相同,区别在于CNN利用了图片的局部性,在增加神经网络层数的同时保证参数数量不会大幅增长。
正确率计算
对于建立好的模型,我们通过对测试集数据进行多分类,并与测试集数据标签相对照得出模型正确率。
设\(y\)中最大分量所在的位置为\(index\),说明该图片最有可能是数字\(index\),于是认为该数字被预测为\(index\)。将\(index\)与该图片的标签进行对比,可获该次预测结果的正确性判断\(correct\_predicton\),多次预测的统计结果即为模型的正确率\(accuracy\)。
具有单一线性层的softmax回归模型
此模型是一个单层神经网络,只包含输入层和输出层,又称感知器。
输入层
输入层只负责输入,并不进行计算。
首先从MNSIT读入数据
from tensorflow.examples.tutorials.mnist import input_data import tensorflow as tf mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
其中
one_hot
选项意为独热码表示,即若图片中的数字为5,则获取到的对应标签为一个\([1, 10]\)的张量,其中只有代表数字5的分量为1,其余均为0。接下来定义输入层张量
x = tf.palceholder("float", [None, 784])
表示输入一个第一维长度未知,第二维长度为784,各分量均为浮点数的张量x。
placeholder表示占位符,是一个可变常量,需在调用session的run方法时赋值。
输出层
输出层包括计算神经元之间的连接和激活。
神经元间的连接强度即为\(W\), 计算神经元件的连接即计算\(x\times{W}+B\);激活即用非线性函数作用于计算结果,使之不再是简单的矩阵相乘,这里用非线性函数softmax作用于op,产生归一化的离散概率分布\(y\)。
W = tf.Variable(tf.zeros([784, 10])) b = tf.Variable(tf.zeros([1, 10])) y = tf.nn.softmax(tf.matmul(x, W) + b) y_ = tf.placeholder("float", [None, 10]) cross_entropy = -tf.reduce_sum(y_ * tf.log(y)) train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy) # 使用幅度为0.01的梯度下降算法最小化交叉熵
其中
tf.reduce_sun
是求和函数。Variable表示变量,\(W\) 和 \(b\) 即我们的模型需要学习的内容。Variable需要在session运行前进行初始化。模型正确率计算
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
其中
tf.argmax
函数的作用是按行寻找张量中最大分量的位置。tf.reduce_mean
是求平均值函数。
具有多层卷积神经网络的softmax回归模型
多层卷积神经网络的结构一般为\((卷积层\times{n}+池化层)\times{m}+全连接层\),此模型中神经网络也符合上述结构,具体为\(卷积层+池化层+卷积层+池化层+全连接层\)。
初始化
def weight_variable(shape): # 连接初始化函数 initial = tf.truncated_normal(shape, stddev=0.1) return tf.Variable(initial) def bias_variable(shape): # 偏置初始化函数 initial = tf.constant(0.1, shape=shape) return tf.Variable(initial)
在多层卷积神经网络中有大量需要初始化的权重,所以定义以上两个初始化函数,实现代码重用。
其中,
tf.truncated_normal
函数的作用是产生服从正态分布的shape形状的张量,与默认均值0的差的绝对值不超过标准差stddev的两倍。这样取值是因为此模型使用relu激发函数\(relu(x)=\max(x,0)\),需要取稍大于0的数作为初始值,若全部取0则容易出现"死神经元",即神经元输出恒为0的情况。tf.constant
函数的作用是产生shape形的各分量值均为0.1的张量。卷积与池化
def conv2d(x, W): # 卷积函数 return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') def max_pool_2x2(x): # 最大池化函数 return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
卷积中有许多参数可调,此模型中使用默认参数,即步长为1,边缘为0,这样将得出一个和输入形状相同的输出。
池化一般接在一个或多个卷积op之后,此模型中池化模板大小为\(2 \times 2\)。
函数
tf.nn.cov2d
的原型是tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)
。一般都要设置前4个参数:input
指需要做卷积的输入图像,要求是一个tensor,具有形如[batch, in_height, in_width, in_channels]的形状。
各分量的具体含义是:batch_in,训练图片数;in_height,图片高度;in_width,图片宽度;in_channels,图片通道数。
filter
指卷积神经网络中的卷积核,要求是一个tensor,具有形如[filter_height, filter_width, in_channels, out_channels]的形状。
各分量的具体含义是:filter_height,卷积核的高度;filter_width,卷积核的宽度;in_channels,图像通道数;out_channels,卷积核的个数。其中in_channels需与input中的in_channels相同。
strides
指卷积时在图像每一维的步长,是一个长度为4的一维向量。
padding
指卷积的边缘的填充方式,有两种选择:SAME,填充0;VALID,不填充,即忽略。
返回结果也是一个形如input的tensor。
函数
tf.maxpool
是最大池化函数,用于在对图片包含信息量影响不大的情况下,对张量进行压缩。压缩方式为取ksize形状的张量中的分量的最大值作为新的分量。strides参数和padding参数与
tf.nn.cov2d
中对应参数含义相同。第一层网络
第一层网络包括一层卷积和一层池化。
首先初始化卷积核。
W_conv1 = weight_variable([5, 5, 1, 32]) # 高度5,宽度5,输入通道数1,输出通道数32 b_conv1 = bias_variable([32])
在使用这一层之前首先需要对输入图片x进行形状重塑,使之符合卷积输入的4维张量。
x_image = tf.reshape(x, [-1,28,28,1])
tf.reshape
函数将输入数据集x重塑成一个图片数量不变,图片高度28,宽度28,通道数为1的新集合。其中第一维的-1代表函数会自动计算图片数量,无需传入具体值;第四维取1因为图片是灰度图,只有一个通道。h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) h_pool1 = max_pool_2x2(h_conv1)
然后依次进行卷积、激活、池化,完成第一层神经网络。
第二层网络
第一层神经网络池化后的数据为输入进行第二层神经网络。
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_cov2)
全连接层
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)
全连接层是必不可少的,它在多层神经网络的最后对整张图片的信息进行整合。
数据集中的\(28\times28\)的图片在经过两次池化过后大小变为\(7\times7\),这是建立全连接对整张图片进行处理。处理时前需要将第二层神经网络池化后的数据集形状进行重塑。
减少过拟合
我们的学习是基于训练集的,但可能由于训练集不能保证随机抽样,从而具有某些特性,使得训练的结果和训练集符合较好,但和整体的数据符合并不好,这时称模型过拟合。
为了减少过拟合,我们采用如下操作:
keep_prob = tf.placeholder("float") h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
tf.dropout
函数原型def dropout(x, keep_prob, noise_shape=None, seed=None, name=None)
,其作用可以概括为,输入张量x中的各分量,有keep_prob的概率保留下来,其余变为0。相当于在同层神经元中挑选一部分,本次暂停工作。tf.dropout
函数只在训练阶段启用,测试时keep_prob应设为1;且一般用于大型神经网络。输出层
W_fc2 = weight_variable([1024, 10]) b_fc2 = bias_variable([10]) y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2) cross_entropy = -tf.reduce_sum(y_ * tf.log(y_conv)) train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
此模型中使用ADAM优化的最速梯度下降算法来实现最小化交叉熵。
模型正确率计算
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) 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}) # %g print a group 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}))
每次随机抽取50的训练集,每100次输出一次日志。
accuracy.eval()
相当于sess.run(accuracy, ...)
。
结果展示
具有单一线性层的softmax回归模型
代码实现
from tensorflow.examples.tutorials.mnist import input_data import tensorflow as tf def main(): mnist = input_data.read_data_sets("MNIST_data", one_hot=True) x = tf.placeholder("float", [None, 784]) W = tf.Variable(tf.zeros([784, 10])) b = tf.Variable(tf.zeros([1, 10])) y = tf.nn.softmax(tf.matmul(x, W) + b) y_ = tf.placeholder("float", [None, 10]) cross_entropy = -tf.reduce_sum(y_ * tf.log(y)) train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy) correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) init = tf.global_variables_initializer() sess = tf.Session() sess.run(init) for i in range(1000): batch_xs, batch_ys = mnist.train.next_batch(100) sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys}) print("The accuracy of this model is:", sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})) main()
正确率计算结果
单层神经网络模型处理MNIST数据集正确率
单层神经网络模型的正确率为91.5%左右。
具有多层卷积神经网络的softmax回归模型
代码实现
from tensorflow.examples.tutorials.mnist import input_data import tensorflow as tf def weight_variable(shape): initial = tf.truncated_normal(shape, stddev=0.1) return tf.Variable(initial) def bias_variable(shape): initial = tf.constant(0.1, shape=shape) return tf.Variable(initial) def conv2d(x, W): return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') def max_pool_2x2(x): return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') def main(): mnist = input_data.read_data_sets("MNIST_data", one_hot=True) x = tf.placeholder("float", [None, 784]) y_ = tf.placeholder("float", [None, 10]) # 第一层神经网络 x_image = tf.reshape(x, [-1, 28, 28, 1]) 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) # 池化层 # 第二层神经网络 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) # 池化层 # 全连接层 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) # 防止过拟合 keep_prob = tf.placeholder("float") h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) W_fc2 = weight_variable([1024, 10]) b_fc2 = bias_variable([10]) y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2) cross_entropy = -tf.reduce_sum(y_ * tf.log(y_conv)) train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) # 正确率计算 correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) sess = tf.InteractiveSession() # 交互式初始化 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})) main()
正确率计算结果
多层卷积神经网络模型处理MNIST数据集正确率 多层卷积神经网络模型处理MNIST数据集正确率测试集结果
多层卷积神经网络模型的正确率为99.2%左右。
讨论总结
本次实验学习了两种机器学习的算法。
这两种算法的主要区别在于神经网络的层数,和建立的连接数。
采用多层神经网络能够更深入地表示特征,以及拥有更强的函数模拟能力
更深入地表示特征
神经网络的每一层所学习到的都是对上一层更加抽象的表示。
所以随着神经网络层数的增加,对事物的抽象能力也得到了提升,可以对更多的抽象特征进行分类。
更强的函数模拟能力
神经网络的层数增加,网络的节点和连接也随之增加,使整个网络的参数增多。
神经网络的本质是模拟特征与目标之间的真实函数关系,更多参数引入意味着模拟的函数可以更加复杂,因而有能力进行更深入的拟合。
多层卷积神经网络能够提升模型的正确率
从上文可知,提高模型的正确率的一种简单方法是增加神经网络的层数,但如此一来连接数也会大幅增加,对计算性能的要求较高。
多层卷积神经网络利用图片的局部性,能够在提升正确率的同时,保证连接数不会大幅增长。
卷积神经网络通过局部连接、权值共享和下采样池化的操作达到该目的:
局部连接
由于图片的信息具有局部性,每个神经元不再向全连接神经网络中一样同上一层所有神经元相连,之和与卷积核大小相同的部分相连,减少了连接数。
权值共享
通过卷积核的步长移动实现一组连接共享同一个权重,这样又减少了很多参数。
但我暂时还不清楚这么做的物理意义。
下采样池化
利用图像的局部性原理,对图像进行子抽样,在减少数据处理量的同时保存有用信息。
TODO
- [ ] 交叉熵
- [ ] session交互式初始化
- [ ] ADAM相关理论
- [ ] 参数的调整