MNIST For Bejinners and Experts

此为TensorFlow框架官网学习笔记,原文为MNIST For ML Beginners

MNIST For ML Beginners

数据集

首先是下载数据集,如下:

1
2
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

MNIST数据集分为三部分,训练数据(mnist.train)有55000个,测试数据(mnist.test)有10000个,验证数据(mnist.validation)有5000个。关于这个,Ng的视频中讲到过,神经网络与深度学习的书中也提到过,验证集用来交叉验证来优化模型,测试集用于评估模型的表现。

softmax回归

这里输出层使用的是Softmax回归,简单的神经网络如下图所示,当让这里应该有784个特征(矩阵图平铺),输出层应该有10个神经元,这里输出使用独热编码,比如3用[0,0,0,1,0,0,0,0,0,0]表示。


使用占位符创建输入变量,这里的行为batch的大小,使用None表示可以是任意长度,列为图片特征的大小:
1
x = tf.placeholder(tf.float32, [None, 784])

W用正态分布来初始化(Ng的视频中提到为了防止梯度爆炸或者梯度消失,使用方差为1/n的正态分布来初始化),b用0来初始化,这里正规的W的行应该为当前层的神经元个数,列为上一层神经元的个数,这里写反了:

1
2
W = tf.Variable(tf.random_normal([784, 10],stddev=1,seed=1))
b = tf.Variable(tf.zeros([10]))

输出模型:

1
y = tf.nn.softmax(tf.matmul(x, W) + b)

训练

损失函数使用的是交叉熵:

这里用y_表示真实标签集,使用占位符来接收一个输入,

1
y_ = tf.placeholder(tf.float32, [None, 10])

交叉函数如下表示,其中reduction_indices=[1]表示对每一行求和,y_中每一列只有一个值是1,其他都为0,也就是说交叉熵的结果每一列只有一个值不为0,reduce_mean是求均值

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

关于这里用到的函数,给出理解的例子:

1
2
3
4
x = tf.constant([[1., 1.], [2., 2.]])
tf.reduce_mean(x) # 1.5
tf.reduce_mean(x, 0) # [1.5, 1.5]
tf.reduce_mean(x, 1) # [1., 2.]

当然这里计算损失函数应该使用下面的函数,给出的原因是说上面的式子在数值上是不稳定的

1
2
cross_entropy = tf.reduce_mean(
tf.nn.softmax_cross_entropy_with_logits(labels = y_,logits = y))

然后是梯度下降,因为tensorflow知道计算的整个图标,会自动使用反向传播算法:

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

前面使用tf.Session()来创建会话,这里直接如下使用,表示默认会话,省去了注册默认会话的过程,不如第二行初始化,没有将其放在run中运行,而是直接使用run【纯个人理解。。。】

1
2
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()

然后让训练运行1000次,这里每次batch为100:

1
2
3
for _ in range(1000):
batch_xs, batch_ys = mnist.train.next_batch(100)
sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

模型评估

tf.argmax是一个非常有用的函数,它可以在沿某个轴在张量中输入最高的索引值,tf.argmax(y,1)将模型中概率值最大的索引输出,而tf.argmax(y_,1)为正确的标签对应的索引值,我们可以使用tf.equal来检查我们的预测是否符合事实。

1
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))

输出为一个布尔的列表,这里我们需要转换一下,将布尔类型转为浮点数,然后取平均值,如[True, False, True, True]会变成[1,0,1,1],然后变成0.75,表示75%的正确率。

1
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

最后测试数据的准确性:

1
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

只有92%的正确率,当然我们都没有用隐藏层,完整代码见github

Deep MNIST for Experts

下面使用CNN来实现,这里就不介绍CNN的原理了,后面有时间会整理一下

Weight Initialization

这里避免梯度消失,使用ReLU激活函数,为了避免初始化导致神经元死亡可以将偏差设置为一个正数,这里将标准差设置为0.1,应该是为了防止初始化导致下一层Z过大,让该层所有的W之和的方差接近1.

1
2
3
4
5
6
7
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)

Convolution and Pooling

这里提一下,卷积层中每一个节点的输入只是上一层神经网络的一小块,一般为3x3或者5x5,通过卷积层处理过的节点矩阵会变得更深,卷积层使用的过滤器会将上一层的过滤器大小的矩阵进行权重求和,得到的是一个值,深度为k时,得到k个值,这k个值是由于权重矩阵的权重不同而导致的,权重矩阵的深度就是卷积层的深度。池化层神经网络不会改变三维矩阵的深度,但可以缩小矩阵的大小。

1
2
3
4
5
6
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')

strides表示的是不同维度上的步长,第1个和第4要求为1,因为卷积层的步长只对矩阵的长宽有效,padding表示填充的方法,SAME表示添加全0填充,VALID表示不添加全0填充。ksize表示的是过滤器的尺寸,第一个和第四个参数也必须是1,因为过滤器不能跨不同样本或深度。

First Convolutional Layer

第一层卷积层,其中对应的权重矩阵是一个5x5的过滤器,输入通道为1(由上一层决定),深度为32。

1
2
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])

将x的维度变为4维的张量,第一维(batch)为-1表示先按照后面的维度,剩下的就是第一维的,第二维和第三维是图像的长宽,第四维是深度,如果是彩色图,那么深度就应该是3。

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

权重和经过卷积层后进入神经元,这里激活函数使用的是ReLU,从激活函数中出来,进入池化层,池化层步长使用的是2x2,也就是说最后的矩阵从28x28变为14x14。

1
2
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

Second Convolutional Layer

第二层卷积层中过滤器为5x5,深度为64,此时通过卷积层后,矩阵大小由14x14变为7x7。

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)

Densely Connected Layer

这是全连接层,此时输入的矩阵长宽为7x7,深度是64,这里全连接层用1024个神经元,需要将三维矩阵转为一维作为输入:

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,测试的时候关闭dropout:

1
2
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

Readout Layer

最后,在全连接层上使用dropout,这里提一下,前面的h_fc1_drop根据keep_prob的值会随机的将h_fc1的某些结果置0。

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

Train and Evaluate the Model

最后一步训练然后评估,这里不使用梯度下降而使用ADAM,详细可参考Ng的深度学习视频,下面是训练与评估的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
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})#这里评估的时候关闭dropout
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}))

每迭代100次后,就评估一次,最后使用测试数据评估,结果为99.2%,完整代码见github

如果觉得有帮助,给我打赏吧!