Python计算机视觉_实现手写体识别

文章目录

  • 一、minist数据集的特点
  • 二、思路
  • 三、原理介绍
  • 四、实现过程
    • 1、代码:
  • 五、分析总结

一、minist数据集的特点

minist数据集可以在 http://yann.lecun.com/exdb/mnist/ 获取,它包含以下四个部分:
Python计算机视觉_实现手写体识别_第1张图片
t10k-images-idx3-ubyte:包含10000个样本
t10k-labels-idx1-ubyte:包含10000个标签
train-images-idx3-ubyte:包含60000个样本
train-labels-idx1-ubyte:包含60000个标签

MNIST 数据集来自美国国家标准与技术研究所,National Institute of Standards and Technology(NIST)。 训练集 (training set) 由来自 250 个不同人手写的数字构成,其中 50% 是高中学生,50% 来自人口普查局 (the Census Bureau) 的工作人员. 测试集(test set)也是同样比例的手写数字数据。
如图为minist数据集的范本:
Python计算机视觉_实现手写体识别_第2张图片

二、思路

把图片当成一枚枚像素来看,下图为手写体数字1的图片,它在计算机中的存储其实是一个二维矩阵,每个元素都是0~1之间的数字,0代表白色,1代表黑色,小数代表某种程度的灰色。
Python计算机视觉_实现手写体识别_第3张图片
对于minist数据集中的图片来说,我们只要把它当成长度为784的向量就可以了(忽略它的二维结构,28*28=784)。我们的任务就是让这个向量经过一个函数后输出一个类别。就是下边这个函数,称为softmax分类器。
Python计算机视觉_实现手写体识别_第4张图片
这个式子里的图片向量的长度只有3,用x表示。乘上一个系数矩阵W,再加上一个列向量b,然后输入softmax函数,输出就是分类结果y。w是一个权重矩阵,w的每一行与整个图片像素相乘的结果是一个分数score,分数越高表示图片越接近改行代表的类别。因此,Wx + b的结果其实是一个列向量,每一行代表图片属于该类的评分。通常分类的结果并非评分,而是概率,表示有多大的概率属于此类别。因此,softmax函数的作用就是把评分转换成概率,并使总的概率为1.

三、原理介绍

卷积神经网络(Convolutional Neural Networks/CNNS/ConvNets)与普通神经网络非常相似,他们都由可学习的权重和偏置常量(biases)的神经元组成。每个神经元都接收一些输入,并做一些点积计算,输出是每个分类的分数,普通神经网络里的一些计算技巧到这里依旧适用。
卷积神经网络利用输入是图片的特点,把神经元设计成三个维度:width,height,depth(注意这个depth不是神经网络的深度,而是用来描述神经元的)。比如输入的图片大小是32323(rgb),那么输入神经元也就具有32323的维度。
一个卷积神经网络由很多层组成,它们的输入是三维的,输出也是三维的,有的层有参数,有的层不需要参数。
卷积神经网络通常包含以下几种层:
数据输入层:
该层要做的处理主要是对原始图像数据进行预处理,其中包括:
• 去均值:把输入数据各个维度都中心化为0,如下图所示,其目的就是把样本的中心拉回到坐标系原点上。
• 归一化:幅度归一化到同样的范围,如下所示,即减少各维度数据取值范围的差异而带来的干扰,比如,我们有两个维度的特征A和B,A范围是0到10,而B范围是0到10000,如果直接使用这两个特征是有问题的,好的做法就是归一化,即A和B的数据都变为0到1的范围。
• PCA/白化:用PCA降维;白化是对数据各个特征轴上的幅度归一化
卷积层

卷积神经网路中每层卷积层由若干卷积单元组成,每个卷积单元的参数都是通过反向传播算法优化得到的。卷积运算的目的是提取输入的不同特征,第一层卷积层可能只能提取一些低级的特征如边缘、线条和角等层级,更多层的网络能从低级特征中迭代提取更复杂的特征。

下面的动态图形象地展示了卷积层的计算过程:
Python计算机视觉_实现手写体识别_第5张图片
线性整流层(Rectified Linear Units layer, ReLU layer),这一层神经的活性化函数(Activation function)使用线性整流(Rectified Linear Units, ReLU)f(x)=max(0,x)

把卷积层输出结果做非线性映射。
Python计算机视觉_实现手写体识别_第6张图片
池化层(Pooling layer),通常在卷积层之后会得到维度很大的特征,将特征切成几个区域,取其最大值或平均值,得到新的、维度较小的特征。

池化层的具体作用。

1.特征不变性,也就是我们在图像处理中经常提到的特征的尺度不变性,池化操作就是图像的resize,平时一张狗的图像被缩小了一倍我们还能认出这是一张狗的照片,这说明这张图像中仍保留着狗最重要的特征,我们一看就能判断图像中画的是一只狗,图像压缩时去掉的信息只是一些无关紧要的信息,而留下的信息则是具有尺度不变性的特征,是最能表达图像的特征。

2.特征降维,我们知道一幅图像含有的信息是很大的,特征也很多,但是有些信息对于我们做图像任务时没有太多用途或者有重复,我们可以把这类冗余信息去除,把最重要的特征抽取出来,这也是池化操作的一大作用。

3.在一定程度上防止过拟合,更方便优化。
Python计算机视觉_实现手写体识别_第7张图片
池化层用的方法有Max pooling 和 average pooling,而实际用的较多的是Max pooling。
全连接层( Fully-Connected layer), 把所有局部特征结合变成全局特征,用来计算最后每一类的得分。

四、实现过程

1、代码:

trainMinistFromPackage.py(训练数据)

import tensorflow as tf
import numpy as np # 习惯加上这句,但这边没有用到
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
mnist = input_data.read_data_sets('MNIST_data/', one_hot=True)

sess = tf.InteractiveSession()

# 1、权重初始化,偏置初始化
# 为了创建这个模型 ,我们需要创建大量的权重和偏置项
# 为了不在建立模型的时候反复操作,定义两个函数用于初始化
def weight_variable(shape):
    initial = tf.truncated_normal(shape,stddev=0.1)#正太分布的标准差设为0.1
    return tf.Variable(initial)
def bias_variable(shape):
    initial = tf.constant(0.1,shape=shape)
    return tf.Variable(initial)


# 2、卷积层和池化层也是接下来要重复使用的,因此也为它们定义创建函数
# tf.nn.conv2d是Tensorflow中的二维卷积函数,参数x是输入,w是卷积的参数
# strides代表卷积模块移动的步长,都是1代表会不遗漏地划过图片的每一个点,padding代表边界的处理方式
# padding = 'SAME',表示padding后卷积的图与原图尺寸一致,激活函数relu()
# tf.nn.max_pool是Tensorflow中的最大池化函数,这里使用2 * 2 的最大池化,即将2 * 2 的像素降为1 * 1的像素
# 最大池化会保留原像素块中灰度值最高的那一个像素,即保留最显著的特征,因为希望整体缩小图片尺寸
# ksize:池化窗口的大小,取一个四维向量,一般是[1,height,width,1]
# 因为我们不想再batch和channel上做池化,一般也是[1,stride,stride,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')


# 3、参数
# 这里的x,y_并不是特定的值,它们只是一个占位符,可以在TensorFlow运行某一计算时根据该占位符输入具体的值
# 输入图片x是一个2维的浮点数张量,这里分配给它的shape为[None, 784],784是一张展平的MNIST图片的维度
# None 表示其值的大小不定,在这里作为第1个维度值,用以指代batch的大小,means x 的数量不定
# 输出类别y_也是一个2维张量,其中每一行为一个10维的one_hot向量,用于代表某一MNIST图片的类别
x = tf.placeholder(tf.float32, [None, 784], name="x-input")
y_ = tf.placeholder(tf.float32, [None, 10]) # 10列


# 4、第一层卷积,它由一个卷积接一个max pooling完成
# 张量形状[5,5,1,32]代表卷积核尺寸为5 * 5,1个颜色通道,32个通道数目
w_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32]) # 每个输出通道都有一个对应的偏置量
# 我们把x变成一个4d 向量其第2、第3维对应图片的宽、高,最后一维代表图片的颜色通道数(灰度图的通道数为1,如果是RGB彩色图,则为3)
x_image = tf.reshape(x, [-1, 28, 28, 1])
# 因为只有一个颜色通道,故最终尺寸为[-1,28,28,1],前面的-1代表样本数量不固定,最后的1代表颜色通道数量
h_conv1 = tf.nn.relu(conv2d(x_image, w_conv1) + b_conv1) # 使用conv2d函数进行卷积操作,非线性处理
h_pool1 = max_pool_2x2(h_conv1)                          # 对卷积的输出结果进行池化操作


# 5、第二个和第一个一样,是为了构建一个更深的网络,把几个类似的堆叠起来
# 第二层中,每个5 * 5 的卷积核会得到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)

# 6、密集连接层
# 图片尺寸减小到7 * 7,加入一个有1024个神经元的全连接层,
# 把池化层输出的张量reshape(此函数可以重新调整矩阵的行、列、维数)成一些向量,加上偏置,然后对其使用Relu激活函数
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)

# 7、使用dropout,防止过度拟合
# dropout是在神经网络里面使用的方法,以此来防止过拟合
# 用一个placeholder来代表一个神经元的输出
# tf.nn.dropout操作除了可以屏蔽神经元的输出外,
# 还会自动处理神经元输出值的scale,所以用dropout的时候可以不用考虑scale
keep_prob = tf.placeholder(tf.float32, name="keep_prob")# placeholder是占位符
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)


# 8、输出层,最后添加一个softmax层
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, name="y-pred")


# 9、训练和评估模型
# 损失函数是目标类别和预测类别之间的交叉熵
# 参数keep_prob控制dropout比例,然后每100次迭代输出一次日志
cross_entropy = tf.reduce_sum(-tf.reduce_sum(y_ * tf.log(y_conv),reduction_indices=[1]))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
# 预测结果与真实值的一致性,这里产生的是一个bool型的向量
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
# 将bool型转换成float型,然后求平均值,即正确的比例
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 初始化所有变量,在2017年3月2号以后,用 tf.global_variables_initializer()替代tf.initialize_all_variables()
sess.run(tf.initialize_all_variables())

# 保存最后一个模型
saver = tf.train.Saver(max_to_keep=1)

for i in range(1000):
    batch = mnist.train.next_batch(64)
    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 %f " % accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))

# 保存模型于文件夹
saver.save(sess,"save/model")

运行结果:
Python计算机视觉_实现手写体识别_第8张图片
demp.py(生成界面,测试检测效果)

import tensorflow as tf
import numpy as np
import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk
from tkinter import filedialog
import time


def creat_windows():
    win = tk.Tk() # 创建窗口
    sw = win.winfo_screenwidth()
    sh = win.winfo_screenheight()
    ww, wh = 400, 450
    x, y = (sw-ww)/2, (sh-wh)/2
    win.geometry("%dx%d+%d+%d"%(ww, wh, x, y-40)) # 居中放置窗口

    win.title('手写体识别') # 窗口命名

    bg1_open = Image.open("timg.jpg").resize((300, 300))
    bg1 = ImageTk.PhotoImage(bg1_open)
    canvas = tk.Label(win, image=bg1)
    canvas.pack()


    var = tk.StringVar() # 创建变量文字
    var.set('')
    tk.Label(win, textvariable=var, bg='#C1FFC1', font=('宋体', 21), width=20, height=2).pack()

    tk.Button(win, text='选择图片', width=20, height=2, bg='#FF8C00', command=lambda:main(var, canvas), font=('圆体', 10)).pack()
    
    win.mainloop()

def main(var, canvas):
    file_path = filedialog.askopenfilename()
    bg1_open = Image.open(file_path).resize((28, 28))
    pic = np.array(bg1_open).reshape(784,)
    bg1_resize = bg1_open.resize((300, 300))
    bg1 = ImageTk.PhotoImage(bg1_resize)
    canvas.configure(image=bg1)
    canvas.image = bg1

    init = tf.global_variables_initializer()

    with tf.Session() as sess:
            sess.run(init)
            saver = tf.train.import_meta_graph('save/model.meta')  # 载入模型结构
            saver.restore(sess, 'save/model')  # 载入模型参数
            graph = tf.get_default_graph()       # 加载计算图
            x = graph.get_tensor_by_name("x-input:0")  # 从模型中读取占位符变量
            keep_prob = graph.get_tensor_by_name("keep_prob:0")
            y_conv = graph.get_tensor_by_name("y-pred:0")  # 关键的一句  从模型中读取占位符变量
            prediction = tf.argmax(y_conv, 1)
            predint = prediction.eval(feed_dict={x: [pic], keep_prob: 1.0}, session=sess)  # feed_dict输入数据给placeholder占位符
            answer = str(predint[0])
    var.set("预测的结果是:" + answer)

if __name__ == "__main__":
    creat_windows()

运行结果:
Python计算机视觉_实现手写体识别_第9张图片
实验中每次batch的数量为100,batch表示一次迭代训练数据量的大小;batchsize越小,一个batch中的随机性越大,越不易收敛。然而batchsize越小,速度越快,权值更新越频繁;且具有随机性,对于非凸损失函数来讲,更便于寻找全局最优。从这个角度看,收敛更快,更容易达到全局最优。batchsize越大,越能够表征全体数据的特征,其确定的梯度下降方向越准确,(因此收敛越快),且迭代次数少,总体速度更快。
因为实验结果的手写体识别正确率为:
在这里插入图片描述
这里就不在演示手写体测试正确的例子了,我们来看一下,识别失败的例子吧。
(1)下图是‘1’,却识别成了‘8’
Python计算机视觉_实现手写体识别_第10张图片
Python计算机视觉_实现手写体识别_第11张图片
(2)下图是‘2’,却识别成了‘7’
Python计算机视觉_实现手写体识别_第12张图片
(3)下图是‘2’却识别成了‘3’Python计算机视觉_实现手写体识别_第13张图片
(4)下图是‘4’却识别成了‘9’
Python计算机视觉_实现手写体识别_第14张图片
(5)下图是‘5’,却识别成了‘8’
Python计算机视觉_实现手写体识别_第15张图片
(7)下图是‘6’,却识别成了‘8’
Python计算机视觉_实现手写体识别_第16张图片

五、分析总结

(1)通过上面识别错误的例子可以看出,该方法对手写体的规范性有着较高的要求,不规范或者‘歪瓜裂枣’的数字往往很容易识别出错。
(2)实验中每次batch的数量为100,batch表示一次迭代训练数据量的大小;batchsize越小,一个batch中的随机性越大,越不易收敛,batchsize越大,越能够表征全体数据的特征,其确定的梯度下降方向越准确,(因此收敛越快),且迭代次数少,总体速度更快。

你可能感兴趣的:(Python计算机视觉_实现手写体识别)