简易实现可通过网页进行手写数字输入,并使用基于MNIST数据训练的神经网络进行输入预测。先上图
软件版本:
Python:3.6
Tensorflow:1.9
代码地址:https://github.com/chfenix/learnTensorflow/tree/master/top/chfenix/tensorflow/mnist
首先实现使用MNIST进行神经网络训练,创建文件nn_train.py
无法自动下载的可以通过官网下载:http://yann.lecun.com/exdb/mnist/
下载后的文件无需解压,直接放到代码所在目录下的MNIST_data目录中
在文件中导入MNIST数据
from tensorflow.examples.tutorials.mnist import input_data
# 读取mnist数据
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
MNIST数据每张图片为28*28像素的灰度图 ,即每张图片有784个数据点
mnist.train 为训练集,共55000条
mnist.test 为测试集,共10000条
可通过如下代码查看MNIST数据中具体的矩阵数值以及图像(文件:test_mnist2img.py)
im = mnist.train.images[index]
print("----------------------------------------")
print("label: %s" % mnist.train.labels[index])
for i in range(28):
print(np.hsplit(im, 28)[i])
print("----------------------------------------")
im = im.reshape(-1,28)
pylab.imshow(im)
pylab.show()
考虑采用包含一个隐层的神经网络进行训练,由于每张图片有784个像素点组成,所以输入层节点数为784。预测结果为0-9,共10种可能,所以输出节点数位10。(训练集所提供的结果labels数据也是长度为10的数组)
首先声明占位符,用于之后接收训练或测试集的输入数据。由于简易页面输入无灰度值,所以此处对MNIST数据进行了四舍五入(tf.round)。此处也可以不做处理,训练后结果差异不大
with tf.name_scope("input"):
# 输入数据,n*784矩阵
inputData = tf.round(tf.placeholder(tf.float32, shape=(None, 784), name="inputData"))
# 输入数据的结果(标签)n*10矩阵
inputLabel = tf.placeholder(tf.float32, shape=(None, 10), name="inputLabel")
定义创建网络层方法,参数为输入数据,上层节点数,本层节点数,层名称(便于在Tensorboard查看),激活函数(默认使用relu)
# 创建神经网络层 @param:输入数据,上层节点数,本层节点数,层名称,激活函数
def create_nn_layer(input_tensor, input_dim, output_dim, layer_name, act=tf.nn.relu):
# 设置命名空间
with tf.name_scope(layer_name):
# 初始化权重值
with tf.name_scope("weights"):
weights = init_weights([input_dim, output_dim])
variable_summaries(weights) # 记录权重信息
# 初始化偏置量
with tf.name_scope("biases"):
biases = init_bias([output_dim])
variable_summaries(biases) # 记录偏置量信息
# 执行Wx+b的线性计算,并且用直方图记录下来
with tf.name_scope("linear_compute"):
preactivate = tf.matmul(input_tensor, weights) + biases
tf.summary.histogram("linear", preactivate)
# 将线性输出经过激励函数,并将输出也用直方图记录下来
activations = act(preactivate, name="activation")
tf.summary.histogram("activations", activations)
# 返回激励层的最终输出
return activations
创建隐层,输入数据即MNIST的原始数据,隐层节点数通过常量定义,便于修改观察运行效果
# 创建隐层
layerHidden1 = create_nn_layer(inputData, 784, HIDDEN_LAYER_POTIN_NUM, "layerHidden1")
通过常量定义,可以选择使用或者不是用dropout层,dropout层作用为按照比例禁用一部分节点,防止网络过拟合(在实际测试过程中,此模型dropout对其影响不大)
# 创建dropout层
with tf.name_scope('dropout'):
keep_prob = tf.placeholder(tf.float32, name="keep_prob")
tf.summary.scalar('dropout_keep_probability', keep_prob)
layerDropout = tf.nn.dropout(layerHidden1, keep_prob)
layerToOut = layerHidden1
# 判断是否使用dropout层
if BOL_DROPOUT:
layerToOut = layerDropout
创建输出层,由于之后会使用tf的内置softmax交叉熵方法,此处只对结果层的输入做原样输出(tf.identity)
# 创建输出层,tf.identity为恒等操作
layerOutput = create_nn_layer(layerToOut, HIDDEN_LAYER_POTIN_NUM, 10, "layerOutput", act=tf.identity)
至此已经将网络结构定义完毕,整体网络分为输入层(MNIST原始数据,784节点),隐层layerHidden1,Dropout层(如果有),输出层(10节点)。接下来定义损失函数
# 定义损失函数
with tf.name_scope("loss"):
# 计算交叉熵损失(每个样本都会有一个损失)
diff = tf.nn.softmax_cross_entropy_with_logits_v2(labels=inputLabel, logits=layerOutput)
with tf.name_scope("total"):
# 计算所有样本交叉熵损失的均值
cross_entropy = tf.reduce_mean(diff)
有了损失函数以后定义训练模型,使用梯度下降方式
# 定义训练函数
with tf.name_scope("train"):
train_op = tf.train.GradientDescentOptimizer(TRAIN_LEARN_RATE).minimize(cross_entropy)
定义预测准确率
# 定义预测准确率
with tf.name_scope("accuracy"):
# 预测op,layerOutput为n*10矩阵,argmax为获取矩阵中某个纬度的最大值所在索引,也就是预测出的具体数字,第二个参数1代表每行计算最大值,0代表按列计算
with tf.name_scope("correct_prediction"):
# 分别将预测和真实的标签中取出最大值的索引,弱相同则返回1(true),不同则返回0(false)
correct_prediction = tf.equal(tf.argmax(layerOutput, 1), tf.argmax(inputLabel, 1))
with tf.name_scope("accuracy"):
# 求均值即为准确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32), name="rate")
至此已经将训练所需的内容定义完毕,可以使用tensorflow运行定义好的模型
sess = tf.InteractiveSession()
# 运行初始化所有变量
tf.global_variables_initializer().run()
# 输入数据(True:训练,False:测试)
def feed_dict(train):
if train:
# 训练
image_data, result_label = mnist.train.next_batch(TRAIN_BATCH_NUM)
dropout = DROPOUT_RATE
else:
# 测试
image_data, result_label = mnist.test.images, mnist.test.labels
dropout = 1.0
return {inputData: image_data, inputLabel: result_label, keep_prob: dropout}
# 进行迭代训练
total_start_time = time.clock()
for i in range(TRAIN_STEP):
start_time = time.clock()
if i % 10 == 0: # 记录测试集的summary与accuracy
summary, acc = sess.run([merged, accuracy], feed_dict=feed_dict(False))
test_writer.add_summary(summary, i)
end_time = time.clock()
print("迭代[%s] 准确率:%.2f%% 耗时:%.0fms" % (i, acc * 100, (end_time - start_time) * 1000))
else: # 记录训练集的summary
if i % 100 == 99: # Record execution stats
run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
run_metadata = tf.RunMetadata()
summary, _ = sess.run([merged, train_op],
feed_dict=feed_dict(True),
options=run_options,
run_metadata=run_metadata)
train_writer.add_run_metadata(run_metadata, "step%03d" % i)
train_writer.add_summary(summary, i)
# print("Adding run metadata for", i)
else: # Record a summary
summary, _ = sess.run([merged, train_op], feed_dict=feed_dict(True))
train_writer.add_summary(summary, i)
total_end_time = time.clock()
# 最终准确率
end_acc = sess.run(accuracy, feed_dict=feed_dict(False))
print("迭代次数[%s] 测试集准确率[%.2f%%] 学习率[%s] 隐层节点数[%s] Dropout[%s:%s] 总耗时[%.3fs]"
% (i + 1, end_acc * 100, TRAIN_LEARN_RATE, HIDDEN_LAYER_POTIN_NUM, BOL_DROPOUT, DROPOUT_RATE,(total_end_time - total_start_time)))
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
train_writer.close()
test_writer.close()
没10次迭代进行一次测试,并输入测试准确率。没100次迭代记录训练情况,用于Tensorboard显示
在训练结束后将参数及最终测试准确率,耗时输出,便于观察参数对模型变化的影响
最后将训练好的模型进行保存,用于后续的网页输入预测
if BOL_SAVE_CHECKPOINT:
saver = tf.train.Saver()
save_path = saver.save(sess, "./model/mnist")
在代码所在目录执行 tensorboard --logdir=./eventlog ,并在浏览器访问执行后显示的url,可以查看模型运行情况
按照MNIST数据形式,将页面输入定义为28*28的网格结构,通过js保存canvas数据并提交到后台,数据为784长度的数组,选中位置为1,其他位置为0。转换为28*28矩阵后数据示例如下:
由于此部分不是重点,不贴代码了,有兴趣的可以直接查看源代码(ocr.htm,ocr.js,server.py)
当通过server.py采集到网页输入内容后,将输入内容传入nn_predict.py进行输入预测。
首先读取已经训练好的神经网络模型及数据
saver = tf.train.import_meta_graph('./model/mnist.meta')
saver.restore(sess, tf.train.latest_checkpoint('./model'))
graph = tf.get_default_graph()
# 恢复输入占位符
restore_input_data = graph.get_tensor_by_name("input/inputData:0")
if BOL_DROPOUT:
# 恢复dropout占位符
restore_keep_prob = graph.get_tensor_by_name("dropout/keep_prob:0")
# 恢复预测算法
restore_predict = graph.get_tensor_by_name("layerOutput/activation:0")
关于tensor的名称,如果不确定的可以在训练时print tensor对象,日志会显示其名称
使用页面输入的手写数据进行预测。预测时不进行节点的Dropout,所以此处dropout参数为1
# 将界面的图片数据传入预测算法,预测结果
result = sess.run(tf.argmax(restore_predict, 1),
feed_dict={restore_input_data: np.mat(image_data), restore_keep_prob: 1.0})
1.在mnist目录中通过 python -m http.server启动web服务
2.运行server.pay
3.在浏览器访问 http://localhost:8000/ocr.html