最近开始入门学习TensorFlow,选择了机器学习最经典的MNIST,实现手写数字识别作为入门项目。采用了softmax回归和CNN两种方式。项目在google colab上完成。
加载MNIST数据集
TensorFlow提供了一份python源代码用于自动下载和安装这个数据集。使用tensorflow.examples.tutorials.mnist 里自带的input_data.py。
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("drive/AI/MNIST_data/", one_hot=True)
通过read_data_sets的方法,MNIST的数据分为3个部分:55000份训练数据(mnist.train)、10000份测试数据(mnist.test)和5000张的验证数据(mnist.validation)。每个部分包含image和label两个部分。图片的尺寸是28*28,每个标签含有10个类别。
查看加载数据集里的内容,每个数据集的大小,选取训练集的一张图片,将其显示出来。
import matplotlib.pyplot as plt
print(mnist.train.images.shape)
print(mnist.train.labels.shape)
print(mnist.test.images.shape)
print(mnist.test.labels.shape)
print(mnist.validation.images.shape)
print(mnist.validation.labels.shape)
#加载训练集第666张图片
image = mnist.train.images[666,:]
#将图像数据还原成28*28的分辨率
image = image.reshape(28,28)
#打印对应的标签
print(mnist.train.labels[666])
plt.figure()
plt.imshow(image)
plt.show()
Softmax回归实现MNIST手写识别模型
按照TensorFlow入门实现Softmax回归。
对于softmax回归模型可以用下面的图解释,对于输入的xs加权求和,再分别加上一个偏置量,最后再输入到softmax函数中:
如果把它写成一个等式,我们可以得到:
我们也可以用向量表示这个计算过程:用矩阵乘法和向量相加。这有助于提高计算效率。(也是一种更有效的思考方式)
更进一步,可以写成更加紧凑的方式:
y = s o f t m a x ( W x + b ) y= softmax(Wx+b) y=softmax(Wx+b)
损失函数loss
选用“交叉熵”(cross-entropy)。交叉熵产生于信息论里面的信息压缩编码技术,但是它后来演变成为从博弈论到机器学习等其他领域里的重要技术手段。
H ( y ) = − ∑ y ′ l o g y H(y)=-\sum y'logy H(y)=−∑y′logy
y ′ y' y′是真实值, y y y是预测值。通过cross_entropy = -tf.reduce_sum(y_real*tf.log(y))来实现。
具体实现如下。采用tensorboard实现流动图的绘制以及loss和accuracy训练图的绘制。
with tf.name_scope('parameters'):
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
with tf.name_scope('data'):
x = tf.placeholder("float", [None, 784])
y_real = tf.placeholder("float", [None,10])
with tf.name_scope('prediction'):
y = tf.nn.softmax(tf.matmul(x,W) + b)
with tf.name_scope('loss'):
cross_entropy = -tf.reduce_sum(y_real*tf.log(y))
with tf.name_scope('train'):
#用梯度下降算法(gradient descent algorithm)以0.01的学习速率最小化交叉熵。
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
with tf.name_scope('init'):
init = tf.global_variables_initializer()
#精度计算
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_real,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
writer = tf.summary.FileWriter("drive/AI/logs/", sess.graph)
#tensorboard记录损失和精度变化
loss_summary = tf.summary.scalar('loss', cross_entropy)
acc_summary = tf.summary.scalar('accuracy', accuracy)
merged = tf.summary.merge([loss_summary, acc_summary])
with tf.Session() as sess:
sess.run(init)
for i in range(200):
batch_xs, batch_ys = mnist.train.next_batch(100)
if i % 20 == 0:
train_accuracy = accuracy.eval({x: batch_xs,y_real:batch_ys})
test_accuracy = accuracy.eval({x: mnist.test.images, y_real: mnist.test.labels})
print("第%d次训练精度%g,测试集精度%g"%(i,train_accuracy,test_accuracy))
train_step.run(feed_dict={x: batch_xs, y_real: batch_ys})
rs=sess.run(merged,feed_dict={x: batch_xs, y_real: batch_ys})
writer.add_summary(rs, i)
#100个批处理数据点的小批量随机梯度下降训练
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_real,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print("测试集精度%g"%sess.run(accuracy, feed_dict={x: mnist.test.images, y_real: mnist.test.labels}))
运行结果
数据流图
loss和accuracy变化曲线
实现中的一些问题
- 用with tf.name_scope命名一些模块,可以在查看数据流图时更加精简
- x = tf.placeholder(“float”, [None, 784])
x不是一个特定的值,而是一个占位符placeholder,我们在TensorFlow运行计算时输入这个值。我们希望能够输入任意数量的MNIST图像,每一张图展平成784维的向量。我们用2维的浮点数张量来表示这些图,这个张量的形状是[None,784 ]。(这里的None表示此张量的第一个维度可以是任何长度的。) - 关于batch_size的选择。batch_xs, batch_ys = mnist.train.next_batch(100)。设置太小收敛需要的迭代次数大大增加,设置过大内存或显存容量无法支撑。可以查看下面这篇博客。
- 关于colab使用tensorboard的方法。使用ngrok将流量隧道传输到localhost。ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。ngrok 可捕获和分析所有通道上的流量,便于后期分析和重放。具体实现方法参见下面的链接。
https://stackoverflow.com/questions/47818822/can-i-use-tensorboard-with-google-colab
采用简单CNN训练MNIST
可以看到,采用softmax训练的话最终的精度在92%左右,这个结果并不是很理想。下面采用简单CNN再次实现。
CNN的结构,采用经典的LeNet-5的结构,激活函数换成了ReLu函数。
conv1 : 32个55的卷积添加same padding,22的max pooling
conv2 : 64个55的卷积添加same padding,22的max pooling
FC1 : 7764的输入,1024的输出
FC2 : softmax 1024输入,10输出
tf.reset_default_graph()
with tf.name_scope('data'):
x = tf.placeholder("float", [None, 784])
y_real = tf.placeholder("float", [None,10])
with tf.name_scope('x_image'):
x_image = tf.reshape(x, [-1,28,28,1])
#参数概要
def variable_summaries(var):
with tf.name_scope('summaries'):
mean = tf.reduce_mean(var) #平均值
tf.summary.scalar('mean', mean)
with tf.name_scope('stddev'):
stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
tf.summary.scalar('stddev', stddev) #标准差
tf.summary.scalar('max', tf.reduce_max(var)) #最大值
tf.summary.scalar('min', tf.reduce_min(var)) #最小值
tf.summary.histogram('histogram', var) #直方图
#初始化权重,采用正则化随机初始,加入少量的噪声来打破对称性以及避免0梯度
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(initial)
#初始化偏置b为常量0.1
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')
with tf.name_scope('conv_1'):
#输入28*28*1,输出14*14*32
W_conv1 = weight_variable([5,5,1,32])
#variable_summaries(W_conv1)可以通过tensorboard查看参数变化的情况
variable_summaries(W_conv1)
b_conv1 = bias_variable([32])
variable_summaries(b_conv1)
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
with tf.name_scope('conv_2'):
#输入14*14*32,输出7*7*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)
with tf.name_scope('FC1'):
#将经过两层卷积得到的(7,7,64)的结果扁平化到一维空间
#输入1*(7*7*64=3136),输出1*1024
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64], name='h_pool2_flat')
W_fc1 = weight_variable([7*7*64,1024])
b_fc1 = bias_variable([1024])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
with tf.name_scope('dropout'):
#占位符keep_prob,实现训练时开启dropout,测试时关闭dropout(keep_prob=1)
keep_prob = tf.placeholder(tf.float32, name='keep_prob')
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
with tf.name_scope('FC2'):
#FC1得到的结果经过softmax得到1*10的输出
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)
with tf.name_scope('loss'):
cross_entropy = -tf.reduce_sum(y_real*tf.log(y_conv))
with tf.name_scope('train'):
#采用Adam优化的梯度下降法来训练模型
train_step = tf.train.AdamOptimizer(0.0001).minimize(cross_entropy)
init = tf.global_variables_initializer()
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_real,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
writer = tf.summary.FileWriter("drive/AI/logs/", sess.graph)
#tensorboard记录损失和精度变化
loss_summary = tf.summary.scalar('CNN_loss', cross_entropy)
acc_summary = tf.summary.scalar('CNN_accuracy', accuracy)
merged = tf.summary.merge_all()
with tf.Session() as sess:
sess.run(init)
for i in range(2000):
batch_xs, batch_ys = mnist.train.next_batch(150)
if i % 100 == 0:
train_accuracy = accuracy.eval({x: batch_xs, y_real:batch_ys, keep_prob : 0.7})
test_accuracy = accuracy.eval({x: mnist.test.images, y_real: mnist.test.labels, keep_prob : 1})
print("第%d次训练精度%g,测试集精度%g"%(i,train_accuracy,test_accuracy))
train_step.run(feed_dict={x : batch_xs, y_real : batch_ys, keep_prob : 0.7})
rs=sess.run(merged,feed_dict={x : batch_xs, y_real: batch_ys, keep_prob : 0.7})
writer.add_summary(rs, i)
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_real,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print("最终模型测试集精度%g"%sess.run(accuracy, feed_dict={x: mnist.test.images, y_real: mnist.test.labels, keep_prob : 1}))
实验结果
经过2000次的训练,测试集上的精度达到了98.5%,效果还是不错的。
tensorboard中的记录如下
实现时的一些问题及解决
- colab中实现时报图模块错误,查找问题原因,是因为实现的时候没有将数据流图重置,导致模块错误发生。解决方案:添加
tf.reset_default_graph()
总结
通过MNIST数据集实现手写数字识别,完成TensorFlow的入门应用。实现了softmax回归模型和简单CNN模型,此外还实现了TensorBoard最基本的一些功能。