最近一直在学习李宏毅老师的机器学习视频教程, 学到和神经网络那一块知识的时候, 我觉得单纯的学习理论知识过于枯燥, 就想着自己动手实现一些简单的 Demo, 毕竟实践是检验真理的唯一标准!!! 但是网上很多的与 tensorflow 或者神经网络相关的 Demo 教程都只是在验证官方程序的过程, 而如何把这些程序变成自己可以真正利用的程序这一块的资料就比较少, 就好比被 "玩烂的"MNIST 数据集(ML 界的 "hello world"), 网上是有很多手写数字识别的教程, 但那些利用的都是官方提供的数据集, 这样就算验证成功了带来的满足感还是远远不够! 废话不多说, 接下来就让我来介绍一下如何使用 Tensorflow 和 MNIST 识别自己写的数字(比如下图这个我写的数字 5~~)
本文也参考了某些大神博客的内容. 希望能帮助和我一样刚刚起步的同学, 大家多多指教.
相应的代码和官方以及自己的数据集: https://github.com/tgpcai/digit_recognition
目录:
(1)MNIST 数据集简介
(2)利用 MNIST 数据集训练模型
(3)自己手写数字, 并用 matlab 进行预处理
(4)将图片输入网络进行识别
(5)实践过程遇到的坑与总结
(1)MNIST 数据集简介
既然我们要构建自己的数据集, 那我们就必须要了解官方提供的数据集的格式, 大小等一些特征. MNIST 是一个巨大的手写数字数据集, 被广泛应用于机器学习识别领域. MNIST 有 60000 张训练集数据和 10000 张测试集数据, 每一个训练元素都是 28*28 像素的手写数字图片, 而且都是黑白色构成(这里的黑色是一个 0-1 的浮点数, 黑色越深表示数值越靠近 1). 在网上搜索一下 MNIST, 你可以发现图片长这样:
上图就是 4 张 MNIST 图片. 这些图片并不是传统意义上的 PNG 或者 jpg 格式的图片, 因为 PNG 或者 jpg 的图片格式, 会带有很多干扰信息, 所以我们在创建自己的数据集的时候就必须进行预处理.
划重点: 28*28 像素, 灰度图
(2)利用 MNIST 数据集训练模型, 并保存模型
该 Demo 使用的模型主要是 CNN 卷积神经网络, 该模型广泛应用于图片识别, 自然语言处理等方向. 有关 CNN 卷积神经网络的知识在我的其他博客中有详细介绍, 欢迎大家一起交流! 上代码:
- import tensorflow as tf
- from tensorflow.examples.tutorials.mnist import input_data
- #定义初始化权重的函数
- def weight_variavles(shape):
- w = tf.Variable(tf.truncated_normal(shape, stddev=0.1))
- return w
- #定义一个初始化偏置的函数
- def bias_variavles(shape):
- b = tf.Variable(tf.constant(0.1, shape=shape))
- return b
- def model():
- #1. 建立数据的占位符 x [None, 784] y_true [None, 10]
- with tf.variable_scope("date"):
- x = tf.placeholder(tf.float32, [None, 784])
- y_true = tf.placeholder(tf.float32, [None, 10])
- #2. 卷积层 1 卷积: 5*5*1,32 个 filter,strides= 1 - 激活 - 池化
- with tf.variable_scope("conv1"):
- #随机初始化权重
- w_conv1 = weight_variavles([5, 5, 1, 32])
- b_conv1 = bias_variavles([32])
- #对 x 进行形状的改变[None, 784] ----- [None,28,28,1]
- x_reshape = tf.reshape(x,[-1, 28, 28, 1]) #不能填 None, 不知道就填 - 1
- # [None,28, 28, 1] -------- [None, 28, 28, 32]
- x_relu1 = tf.nn.relu(tf.nn.conv2d(x_reshape, w_conv1, strides=[1, 1, 1, 1], padding = "SAME") + b_conv1)
- #池化 2*2, 步长为 2,[None, 28,28, 32]--------[None,14, 14, 32]
- x_pool1 = tf.nn.max_pool(x_relu1, ksize=[1, 2, 2, 1],strides = [1,2,2,1],padding = "SAME")
- #3. 卷积层 2 卷积: 5*5*32,64 个 filter,strides= 1 - 激活 - 池化
- with tf.variable_scope("conv2"):
- #随机初始化权重和偏置
- w_conv2 = weight_variavles([5, 5, 32, 64])
- b_conv2 = bias_variavles([64])
- #卷积, 激活, 池化
- #[None,14, 14, 32]----------[NOne, 14, 14, 64]
- x_relu2 = tf.nn.relu(tf.nn.conv2d(x_pool1, w_conv2,strides=[1, 1, 1, 1], padding = "SAME") + b_conv2)
- #池化 2*2, 步长为 2 [None, 14,14,64]--------[None,7, 7, 64]
- x_pool2 = tf.nn.max_pool(x_relu2, ksize=[1, 2, 2, 1],strides = [1,2,2,1],padding = "SAME")
- #4. 全连接层 [None,7, 7, 64] --------- [None, 7*7*64] * [7*7*64, 10]+[10] = [none, 10]
- with tf.variable_scope("fc"):
- #随机初始化权重和偏置:
- w_fc = weight_variavles([7 * 7 * 64, 1024])
- b_fc = bias_variavles([1024])
- #修改形状 [none, 7, 7, 64] ----------[None, 7*7*64]
- x_fc_reshape = tf.reshape(x_pool2,[-1,7 * 7 * 64])
- h_fc1 = tf.nn.relu(tf.matmul(x_fc_reshape, w_fc) + b_fc)
- # 在输出之前加入 dropout 以减少过拟合
- keep_prob = tf.placeholder("float")
- h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
- w_fc1 = weight_variavles([1024, 10])
- b_fc1 = bias_variavles([10])
- #进行矩阵运算得出每个样本的 10 个结果[NONE, 10], 输出
- y_predict = tf.nn.softmax(tf.matmul(h_fc1_drop, w_fc1) + b_fc1)
- return x, y_true, y_predict,keep_prob
- def conv_fc():
- #获取数据, MNIST_data 是楼主用来存放官方的数据集, 如果你要这样表示的话, 那 MNIST_data 这个文件夹应该和这个 python 文件在同一目录
- mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
- #定义模型, 得出输出
- x,y_true,y_predict,keep_prob = model()
- #进行交叉熵损失计算
- #3. 计算交叉熵损失
- with tf.variable_scope("soft_cross"):
- #求平均交叉熵损失, tf.reduce_mean 对列表求平均值
- loss = -tf.reduce_sum(y_true*tf.log(y_predict))
- #4. 梯度下降求出最小损失, 注意在深度学习中, 或者网络层次比较复杂的情况下, 学习率通常不能太高
- with tf.variable_scope("optimizer"):
- train_op = tf.train.AdamOptimizer(1e-4).minimize(loss)
- #5. 计算准确率
- with tf.variable_scope("acc"):
- equal_list = tf.equal(tf.argmax(y_true, 1), tf.argmax(y_predict, 1))
- #equal_list None 个样本 类型为列表 1 为预测正确, 0 为预测错误[1, 0, 1, 0......]
- accuray = tf.reduce_mean(tf.cast(equal_list, tf.float32))
- init_op = tf.global_variables_initializer()
- saver = tf.train.Saver()
- #开启会话运行
- with tf.Session() as sess:
- sess.run(init_op)
- for i in range(3000):
- mnist_x, mnist_y = mnist.train.next_batch(50)
- if i%100 == 0:
- # 评估模型准确度, 此阶段不使用 Dropout
- train_accuracy = accuray.eval(feed_dict={x:mnist_x, y_true: mnist_y, keep_prob: 1.0})
- print("step %d, training accuracy %g"%(i, train_accuracy))
- # 训练模型, 此阶段使用 50% 的 Dropout
- train_op.run(feed_dict={x:mnist_x, y_true: mnist_y, keep_prob: 0.5})
- # 将模型保存在你自己想保存的位置
- saver.save(sess, "D:/Dict/model/fc_model.ckpt")
- return None
- if __name__ == "__main__":
- conv_fc()
然后在你保存模型的目录下会产生 4 个文件
.data 文件是用来记录权重, 偏置等参数信息;.meta 是用来记录 tensorflow 图的结构. 以下是我的电脑的结果图:
我只运行了 6000 次, 按照 tensorflow 官方文档, 运行 9000 次左右可以达到 0.992 左右的正确率
(3)自己手写数字, 并用 matlab 进行预处理
首先让我们看一下预处理的结果:
->->
具体过程也就是分为 3 个步骤: 缩小它的大小为 28*28 像素, 并转变为灰度图, 最后进行二值化处理. 具体 matlab 的代码如下:
clear all; close all; clc;
% 改图片像素为 28*28
I=imread('5.jpg'); % 你自己手写的数字的图片
- J=imresize(I,[28,28]);
- imshow(I);
- figure;
- imshow(J);
imwrite(J,'new5.jpp');% 生成 28*28 手写数字图片
接下来进行灰度与二值化处理
- clear all;close all;clc;
- % Read an input image
- A = imread('new5.jpg');
- % Convert the image to single-channel grayscale image
- A_gray = rgb2gray(A);
- figure,imhist(A_gray),title('hist of A_grey');
- % Convert image to double i.e., [0,1]
- A_gray = im2double(A_gray);
- % Generate threhold value using Otsu's algorithm
- otsu_level = graythresh(A_gray);
- % Threshold image using Otsu's threshold and manually defined
- % threshold values
- B_otsu_thresh = im2bw(A_gray, otsu_level);
- B_thresh_50 = im2bw(A_gray, 50/255);
- B_thresh_100 = im2bw(A_gray, 100/255);
- B_thresh_150 = im2bw(A_gray, 150/255);
- B_thresh_200 = im2bw(A_gray, 200/255);
- % Display original and thresholded binary images side-by-side
- figure, subplot(2, 3, 1), imshow(A_gray), title('Original image');
- subplot(2, 3, 2), imshow(B_otsu_thresh), title('Binary image using Otsu threshold value');
- subplot(2, 3, 3), imshow(B_thresh_50), title('Binary image using threshold value = 50');
- subplot(2, 3, 4), imshow(B_thresh_100), title('Binary image using threshold value = 100');
- subplot(2, 3, 5), imshow(B_thresh_150), title('Binary image using threshold value = 150');
- subplot(2, 3, 6), imshow(B_thresh_200), title('Binary image using threshold value = 200');
imwrite(B_otsu_thresh,'newnew5.jpg');% 填写你希望最终生成的数据集的名字和路径
到此就完成了对自己手写图片的预处理过程!
预处理的方法有很多, 在这我在介绍一种利用 OPENCV 进行预处理:
- import cv2
- global img
- global point1, point2
- def on_mouse(event, x, y, flags, param):
- global img, point1, point2
- img2 = img.copy()
- if event == cv2.EVENT_LBUTTONDOWN: #左键点击
- point1 = (x,y)
- cv2.circle(img2, point1, 10, (0,255,0), 5)
- cv2.imshow('image', img2)
- elif event == cv2.EVENT_MOUSEMOVE and (flags & cv2.EVENT_FLAG_LBUTTON): #按住左键拖曳
- cv2.rectangle(img2, point1, (x,y), (255,0,0), 5) # 图像, 矩形顶点, 相对顶点, 颜色, 粗细
- cv2.imshow('image', img2)
- elif event == cv2.EVENT_LBUTTONUP: #左键释放
- point2 = (x,y)
- cv2.rectangle(img2, point1, point2, (0,0,255), 5)
- cv2.imshow('image', img2)
- min_x = min(point1[0], point2[0])
- min_y = min(point1[1], point2[1])
- width = abs(point1[0] - point2[0])
- height = abs(point1[1] -point2[1])
- cut_img = img[min_y:min_y+height, min_x:min_x+width]
- resize_img = cv2.resize(cut_img, (28,28)) # 调整图像尺寸为 28*28
- ret, thresh_img = cv2.threshold(resize_img,127,255,cv2.THRESH_BINARY) # 二值化
- cv2.imshow('result', thresh_img)
- cv2.imwrite('new5.jpg', thresh_img) # 预处理后图像保存位置
- def main():
- global img
- img = cv2.imread('5.jpg') # 手写数字图像所在位置
- img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换图像为单通道(灰度图)
- cv2.namedWindow('image')
- cv2.setMouseCallback('image', on_mouse) # 调用回调函数
- cv2.imshow('image', img)
- cv2.waitKey(0)
- if __name__ == '__main__':
- main()
以上两种方法都可以, 甚至还有大神利用 PS 自己生成数据集, 感兴趣的同学可以自己去搜索一下~
(4)将图片输入网络进行识别
完成图像预处理后, 即可将图片输入到网络中进行识别
- from PIL import Image, ImageFilter
- import tensorflow as tf
- import matplotlib.pyplot as plt
- def imageprepare():
- im = Image.open('C:/Users/tgp/Desktop/newnew5.jpg')
- plt.imshow(im)
- data = list(im.getdata())
- result = [(255-x)*1.0/255.0 for x in data]
- return result
- #定义初始化权重的函数
- def weight_variavles(shape):
- w = tf.Variable(tf.truncated_normal(shape, stddev=0.1))
- return w
- #定义一个初始化偏置的函数
- def bias_variavles(shape):
- b = tf.Variable(tf.constant(0.0, shape=shape))
- return b
- def model():
- tf.reset_default_graph()
- #1. 建立数据的占位符 x [None, 784] y_true [None, 10]
- with tf.variable_scope("date"):
- x = tf.placeholder(tf.float32, [None, 784])
- #y_true = tf.placeholder(tf.float32, [None, 10])
- #2. 卷积层 1 卷积: 5*5*1,32 个 filter,strides= 1 - 激活 - 池化
- with tf.variable_scope("conv1"):
- #随机初始化权重
- w_conv1 = weight_variavles([5, 5, 1, 32])
- b_conv1 = bias_variavles([32])
- #对 x 进行形状的改变[None, 784] ----- [None,28,28,1]
- x_reshape = tf.reshape(x,[-1, 28, 28, 1]) #不能填 None, 不知道就填 - 1
- # [None,28, 28, 1] -------- [None, 28, 28, 32]
- x_relu1 = tf.nn.relu(tf.nn.conv2d(x_reshape, w_conv1, strides=[1, 1, 1, 1], padding = "SAME") + b_conv1)
- #池化 2*2, 步长为 2,[None, 28,28, 32]--------[None,14, 14, 32]
- x_pool1 = tf.nn.max_pool(x_relu1, ksize=[1, 2, 2, 1],strides = [1,2,2,1],padding = "SAME")
- #3. 卷积层 2 卷积: 5*5*32,64 个 filter,strides= 1 - 激活 - 池化
- with tf.variable_scope("conv2"):
- #随机初始化权重和偏置
- w_conv2 = weight_variavles([5, 5, 32, 64])
- b_conv2 = bias_variavles([64])
- #卷积, 激活, 池化
- #[None,14, 14, 32]----------[NOne, 14, 14, 64]
- x_relu2 = tf.nn.relu(tf.nn.conv2d(x_pool1, w_conv2,strides=[1, 1, 1, 1], padding = "SAME") + b_conv2)
- #池化 2*2, 步长为 2 [None, 14,14,64]--------[None,7, 7, 64]
- x_pool2 = tf.nn.max_pool(x_relu2, ksize=[1, 2, 2, 1],strides = [1,2,2,1],padding = "SAME")
- #4. 全连接层 [None,7, 7, 64] --------- [None, 7*7*64] * [7*7*64, 10]+[10] = [none, 10]
- with tf.variable_scope("fc"):
- #随机初始化权重和偏置:
- w_fc = weight_variavles([7 * 7 * 64, 1024])
- b_fc = bias_variavles([1024])
- #修改形状 [none, 7, 7, 64] ----------[None, 7*7*64]
- x_fc_reshape = tf.reshape(x_pool2,[-1,7 * 7 * 64])
- h_fc1 = tf.nn.relu(tf.matmul(x_fc_reshape, w_fc) + b_fc)
- keep_prob = tf.placeholder("float")
- h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
- w_fc1 = weight_variavles([1024, 10])
- b_fc1 = bias_variavles([10])
- #进行矩阵运算得出每个样本的 10 个结果[NONE, 10]
- #y_predict = tf.matmul(h_fc1_drop, w_fc1) + b_fc1
- y_predict = tf.nn.softmax(tf.matmul(h_fc1_drop, w_fc1) + b_fc1)
- return x, y_predict,keep_prob
- def conv_fc():
- #获取数据
- result = imageprepare()
- #定义模型, 得出输出
- x,y_predict,keep_prob = model()
- init_op = tf.global_variables_initializer()
- saver = tf.train.Saver()
- #开启会话运行
- #tf.reset_default_graph()
- with tf.Session() as sess:
- sess.run(init_op)
- print(result)
- saver.restore(sess, "D:/Dict/model/fc_model.ckpt")
- prediction = tf.argmax(y_predict,1)
- predint = prediction.eval(feed_dict={x: [result],keep_prob: 1.0}, session=sess)
- print(predint)
- print("recognize result: %d" %predint[0])
- return None
- if __name__ == "__main__":
- conv_fc()
运行结果如下:
(5)实践过程遇到的坑与总结
刚开始写训练模型的代码的时候, 我认为不需要防止过拟合这个处理过程, 所以在我的模型里面没有防止过拟合这一操作, 直接导致的结果是: 在训练模型的时候效果非常不错, 但是当真正拿自己手写数字去识别的时候, 经常把'4'和'9'搞错. 随便我在输出层和全连接层中间添加了一些代码用于防止过拟合, 这样训练出的模型表现结果尚佳! 由此可见, 在训练模型的时候防止过拟合的操作还是非常有必要的.
有关随机初始化权重和偏置的函数的选择: 利用 tf.truncated_normal()这个函数随机初始化权重训练出的模型的表现效果比利用 tf.random_nomal()这个函数训练出的模型表现的更好, 上网查询了一下, 发现这两个函数有一下的区别: tf.truncated_normal 的输出如字面意思是截断的, 而截断的标准是 2 倍的 stddev. 使用 tf.truncated_normal 的输出是不可能出现 [-2,2] 以外的点的, 而如果 shape 够大的话, tf.random_normal 却会产生 2.2 或者 2.4 之类的输出. 也就是说使用 tf.random_normal 产生的初始权重的值比 tf.truncated_normal 产生的大, 这对于神经网络而言是致命的, 因为这样非常容易产生梯度消失的问题.
在随机初始化权重和偏置的时候, 方差不能设置的过大, 若方差过大, 则在训练的时候准确率一直维持在很低的位置, 容易产生梯度消失的问题.
保存模型尽量以. ckpt 结果, 反正楼主一开始没有以. ckpt 结尾, 带来了很多麻烦, 然后加上这个后缀, 啥问题都消失了~(可能是玄学, 不加可能也行的通, 但是加了一定不会错~~)
以上就是本次实践的全部过程, 欢迎大家交流讨论.
来源: https://www.cnblogs.com/XDU-Lakers/p/10526748.html