更多:TensorFlow…
一、前期回顾:
上一篇,向大家介绍了 TensorFlow 的安装和基础用法。这篇文章将用 TensorFlow 实现一个手写数字识别功能,来展示 TensorFlow 如何用神经网络实现对图片的识别。Google 也为入门者提供了一个这样的例子,也就是 TensorFlow 里的“ hello world ”,这个例子的名字叫“ MNIST ”。
二、简介
官方已经有了相应的文档,这里不是简单的翻译,而是以更通俗的语言来解释,为了让没有基础的同学也能看得懂,特别用简单的表述解释了一些专业名词,希望能有更多的人能接触到深度学习这个领域。
如果想看中文版本:MNIST 机器学习入门,可以点这里进行查看。
我们如果只简单重复这个例子,就没什么意思了。正好“手写识别”在 Kaggle 上也有竞赛,我们使用 Kaggle 的数据进行识别和测试,这样和 Google 官方的例子虽然差不多,但又有不同。
手写图片识别的实现,分为三步:
- 1,数据的准备
- 2,模型的设计
- 3,代码实现
三、数据的准备
Kaggle 里包含了42000份训练数据和28000份测试数据(和谷歌准备的 MNIST 数据,在数量上有所不同)。训练和测试数据的下载地址可以百度也可以点这里。下载下来是两个CVS文件。

Kaggle 的数据都是表格形式的,和 MNIST 给的图片不一样。但实际上只是对图片的信息进行了处理,把一个28 * 28的图片信息,变成了28 * 28=784的一行数据。
为了便于理解,我们先来看 MNIST 的图片信息:
它每份的图片都是被规范处理过的,是一张被放在中间部位的灰度图。

MNIST的图片集
类似这样的,每一个图片均为28 * 28像素,我们可以将其理解为一个二维数组的结构:

MNIST 的图片解释
28 * 28 = 784,也就是说,这个二维数组可以转为一个784个数字组成的一维数组。
扁平化会丢失图片的二维结构信息,好的图形结构算法都会利用二维结构信息,但是为了简化过程便于理解,这里先使用这种一维结构来进行分析。
这样,上面的训练数据和测试数据,都可以分别转化为[42000,769]和[28000,768]的数组。
为什么训练数据会多一列呢?因为有一列存的是这个图片的结果。好我们继续来看图片:

Kaggle 训练集的截图
这个图片上我们可以看出来,第一列是存的结果,后面784列存的是图片的像素信息,到这里,数据就准备好了。 下面我们进行模型的设计。
四、模型的设计
不想看理论的可以跳过这一步,直接进入代码环节。
这个模型,组成是这样的:
- 使用一个最简单的单层的神经网络进行学习
- 用 SoftMax 来做为激活函数
- 用交叉熵来做损失函数
- 用梯度下降来做优化方式
这里有几个新的名词,神经网络、激活函数、SoftMax、损失函数、交叉熵、梯度下降,我们挨个解释一下。
神经网络:由很多个神经元组成,每个神经元接收很多个输入:[X1,X2….Xn],加权相加然后加上偏移量后,看是不是超过了某个阀值,超过了发出1,没超过发出0。

单个神经元
由很多个神经元互相连接,形成了神经网络。

神经网络
更详细的描述,可以查看知乎文章: 如何简单形象又有趣地讲解神经网络是什么?
激活函数:每个神经元,在通过一系列计算后,得到了一个数值,怎么来判断应该输出什么呢?激活函数就是解决这个问题,你把值给我,我来判断怎么输出。所以一个神经网络,激活函数是非常重要的。
想要成为激活函数,你得有两把刷子啊。这两把刷子是:一是你得处处可微,可微分才能求导,求极值。二是要非线性的,因为线性模型的表达能力不够。
线性的模型是这样的:

非线性的模型是这样的:

目前主流的几个激活函数是:sigmoid,tanh,ReLU。
sigmoid:采用 S 形函数,取值范围[0,1] tanh:双切正切函数,取值范围[-1,1] ReLU:简单而粗暴,大于0的留下,否则一律为0。
SoftMax:我们知道 max(A,B),是指 A 和 B 里哪个大就取哪个值,但我们有时候希望比较小的那个也有一定概率取到,怎么办呢?我们就按照两个值的大小,计算出概率,按照这个概率来取 A 或者 B。比如A=9,B=1,那取 A 的概率是90%,取B的概率是10%。
这个看起来比max(A,B)这样粗暴的方式柔和一些,所以叫SoftMax(名字解释纯属个人瞎掰,大家能理解概念就好)。
损失函数:损失函数是模型对数据拟合程度的反映,拟合得越好损失应该越小,拟合越差损失应该越大,然后我们根据损失函数的结果对模型进行调整。
交叉熵:这个概念要解释的简单,那就不准确,如果要准确,那可能一千字都打不住。这里说一个简单但不一定准确的解释吧。
比如,你想把乾坤大挪移练到第七层大圆满,你现在是第五层,那你还差两层,这个两层就是你和大圆满之间的距离。交叉熵通俗的讲就是现在的训练程度和圆满之间的距离,我们希望距离越小越好,所以交叉熵可以作为一个损失函数,来衡量和目标之间的距离。
梯度下降:这个概念可以这样理解,我们要解决的问题是一座山,答案在山底,我们从山顶到山底的过程就是解决问题的过程。
在山顶,想找到最快的下山的路。这个时候,我们的做法是什么呢?在每次选择道路的时候,选最陡的那条路。梯度是改变率或者斜度的另一个称呼,用数学的语言解释是导数。对于求损失函数最小值这样的问题,朝着梯度下降的方向走,就能找到最优值了。

梯度下降
生僻的名词解释完了,咱们进入编程环节。
五、代码实现
1,载入数据,并对数据进行处理
在写代码的过程中,数据的预处理是最大的一块工作,做一个项目,60%以上的代码在做数据预处理。 这个项目的预处理,分为5步:
- 把输入和结果分开
- 对输入进行处理:把一维的输入变成28*28的矩阵
- 对结果进行处理:把结果进行 One-Hot 编码
- 把训练数据划分训练集和验证集
- 对训练集进行分批
import numpy as np import tensorflow as tf import pandas as pd 1 加载数据集,把对输入和结果进行分开 train = pd.read_csv("train.csv") images = train.iloc[:,1:].values labels_flat = train.iloc[:,0].values.ravel() 2 对输入进行处理 images = images.astype(np.float) images = np.multiply(images, 1.0 / 255.0) print('输入数据的数量: (%g, %g)' % images.shape) image_size = images.shape[1] print ('输入数据的维度=> {0}'.format(image_size)) image_width = image_height = np.ceil(np.sqrt(image_size)).astype(np.uint8) print ('图片的长 => {0}\n图片的高 => {1}'.format(image_width,image_height)) x = tf.placeholder('float', shape=[None, image_size]) 3 对结果进行处理 labels_count = np.unique(labels_flat).shape[0] print('结果的种类 => {0}'.format(labels_count)) # 进行One-hot编码 def dense_to_one_hot(labels_dense, num_classes): num_labels = labels_dense.shape[0] index_offset = np.arange(num_labels) * num_classes labels_one_hot = np.zeros((num_labels, num_classes)) labels_one_hot.flat[index_offset + labels_dense.ravel()] = 1 return labels_one_hot labels = dense_to_one_hot(labels_flat, labels_count) labels = labels.astype(np.uint8) print('结果的数量:({0[0]},{0[1]})'.format(labels.shape)) y = tf.placeholder('float', shape=[None, labels_count]) 4 把输入数据划分训练集和验证集 # 把40000个数据作为训练集,2000个数据作为验证集 VALIDATION_SIZE = 2000 validation_images = images[:VALIDATION_SIZE] validation_labels = labels[:VALIDATION_SIZE] train_images = images[VALIDATION_SIZE:] train_labels = labels[VALIDATION_SIZE:] 5 对训练集进行分批 batch_size = 100 n_batch = int(len(train_images)/batch_size)

处理完毕后,打印的结果是:
数据预处理好了,如果不需要显示结果,可以把Print语句都去掉,不影响建模。
2,建立神经网络,设置损失函数,设置梯度下降的优化参数
这里只是最简单的一个实现,下篇文章我们会继续对网络进行优化
6 创建一个简单的神经网络用来对图片进行识别 weights = tf.Variable(tf.zeros([784,10])) biases = tf.Variable(tf.zeros([10])) result = tf.matmul(x,weights)+biases prediction = tf.nn.softmax(result) 7 创建损失函数,以交叉熵的平均值为衡量 loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels = y, logits =prediction )) 8 用梯度下降法优化参数 train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
3,初始化变量,设置好准确度的计算方法,在 Session 中运行
9 初始化变量 init = tf.global_variables_initializer() 10 计算准确度 correct_prediction = tf.equal(tf.argmax(y,1),tf.argmax(prediction,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32)) with tf.Session() as sess: #初始化 sess.run(init) #循环50轮 for epoch in range(50): for batch in range(n_batch): #按照分片取出数据 batch_x = train_images[batch*batch_size:(batch+1)*batch_size] batch_y = train_labels[batch*batch_size:(batch+1)*batch_size] #进行训练 sess.run(train_step,feed_dict = {x:batch_x,y:batch_y}) #每一轮计算一次准确度 accuracy_n = sess.run(accuracy,feed_dict={ x: validation_images, y: validation_labels}) print ("第" + str(epoch+1)+"轮,准确度为:"+str(accuracy_n))
最后我们得到运行完50轮后的结果:

运行结果
我们这个网络识别的准确度是92%左右,这个成绩比较差,然而这只是我们最简单的模型而已,后面我会和大家对模型持续进行优化。