一文看懂深度学习手写体数字识别(MNIST),小白学习总结
使用ModelArts官方文档里的指导即可完成本文介绍的Mnist手写体识别
https://support.huaweicloud.com/bestpractice-modelarts/modelarts_10_0009.html
先简单说下原理:利用已有的数据集(6W张手写体数字的图片)进行模型训练,训练出模型后,利用模型,针对输入x(图片),计算出每个数字的概率,概率最大的就是识别的结果。
那么如何进行模型训练呢?先从最简单的数学原理开始
我们知道,数学中有线性拟合的概念。意思就是说,根据现有的东西,我画一条线,拟合这些数据,用以预测数据。如下面的例子,房子的大小和价格的关系。现在我有一些房子的大小和价格的部分数据,图上的红叉所示,现在,我需要拟合一条直线,假设拟合的就是图中的蓝线所示。(价格不能为负,只能这样画了)
所以你看,这个不就是数字识别的最简单的同类模型吗?有一堆数据,然后做一个线性拟合,拟合的结果用来预测输入的数字大概率是哪个数字。
上面的例子是最简单的模型,输入只有一个特征,就是x - 房子的大小。所以该例中模型为 y = wx + b (先忽略为0的部分),其中y 是price ,而x是 size of house 。w和b是拟合出来的参数。
但是,现实中,房子的价格往往和多个元素相关。可能是大小,也可能和距离市区的距离相关,还可能周边的教育环境有关(学区房不解释),还可能和周边人的富裕程度有关。总之可能的相关性比较多,这些都是输入特征x。如下图所示。(当然这也是最简单的神经网络,单层神经网络)
根据上图的结论的模型,我们可以知道价格y 由多个特征x决定,我们简单化一下这个模型,就假定这些东西是可以线性相关的、可线性拟合的,简单说就是可以改成三元一次函数y=ax1+bx2+cx3+d。于是,我们假定该模型为 z = w1x1 + w2x2 + w3x3 + b(字母换了一下,意思一样的,深度学习中通用的字母)。其中z为价格,w为各特征的权重,b为偏移量。当前是只有三个特征,看起来还不算糟糕,但想象一下,如果有100个特征,那么表达式将会非常复杂,因此,这里需要引入矩阵。
我们假设x是个3*1的向量(也是一维矩阵),而w是个3*1的矩阵(为什么是3*1,不算1*3,是因为方便程序计算,可以自行探索),这样的话,表达式如下图
注:wT是指矩阵的转置,将3*1矩阵转置为1*3的矩阵,才可以和x进行运算。
展开就是 z = w1x1 + w2x2 + w3x3 + b (如果不理解该式子,建议百度一下最基本的矩阵运算,这里不赘述)
接下来说明一下 损失函数 Loss。还记得我们是怎么线性拟合数据的吗?我们把拟合出的线性表达式所预测的数据y和实际的数据y_real相减并平方,然后把所有的平方加在一起,值最小的那个拟合表达式,就是最好的拟合结果。高中数学的线性拟合,忘光了
我们转化一下这个过程,即有一个损失函数Lost = (y-y_real)^2
然后我们记所有值的Lost的和成本函数为Cost
Cost= Lost(y(1)) + Lost(y(2)) + ... + Lost(y(n))
当Cost最小的时候,就是拟合效果最好的时候。此时的线性表达式,就是我们所期望的最好的线性表达式。
到此最基本的模型搭建的原理就讲完了,总结来看,就是,数据集里有n个特征输入的x,利用这些x去拟合一个线性表达式,然后利用cost成本函数最小时,表示最好的拟合结果。开始拟合的时候W和b都是可以初始化随便取值,然后每次拟合,就可以判断Cost是否达到最小,如果没有,那么就继续调整W和b,直到cost最小,此时拟合完的结果就是一个W和b已知的线性表达式,之后,我们输入x,就能预测输出了!
二分分类Logistics的应用
接下来我们谈一谈二分分类,也是最简单的神经网络,意思是,我有一张图片,我判断它是不是猫,是,则y=1,不是,则y=0。非此即彼
这跟我们最开始的手写体识别有什么关系呢?我们来想象一下。我们可能会怎么做手写体识别。我们输入一个图像X,此时我们可以想象,我们对每个数字做了一个二分分类,它是不是7;它是不是9;它是不是0。只要我们把所有数字都给判断出来,再比较他们的可能性,不就知道最有可能是哪个数字了吗?
好的,回过头来说Logistics分类,我们知道,在之前z的表达式中
z是对应价格,而价格是个没有边界的值,可以从0到10万。但,如果我们要判断一个图像是不是猫,只能是一个概率值,就是这个图片是猫的概率是多少?概率是在(0,1)之间的。因此,我们需要做一个转换,把这个值转化成一个概率值。
我们引入sigmoid函数,并称之为激活函数,如下图所示
这个函数很有意思,它的值域是0,1之间,因此我们可以放心地把我们的z进行运算。
经过转化的值,预测值y-hat就是加个帽子的y,就是一个z的转换表达式,它表示图片是猫的概率(y=1,y是实际值)
这样,我们就找到了预测值y-hat的计算公式。
同时,我们不要忘记,为了得到最好的W和b,我们还需要利用评估用的成本函数cost和每个值的准确率函数Lost
这里,我们使用下式损失函数,这里是有讲究的。lost函数的选择需要根据实际情况来评估并选择。如二分分类中,比较好的lost函数是
代入第i个数据集,即:
则cost函数为,所有lost函数的累加,共有m个数据:
于是整个过程就是,初始化W和b,然后计算,并调整W和b,使得cost函数J的值达到最小
因此引入了梯度下降的概念,上述的表达式J,之所以写成J(w,b)是因为最终的表达式会转化成与w,b相关的式子。
因此可以模拟一个表达式的图形如下,其中Z轴是J,如图可知,我们要做的事,就是在这个图上找到最低点,则表示的就是J(w,b)的最小值
这里不作详细的推导过程,只需要知道是高数中的偏导数和梯度下降法。
......
经过漫长而又复杂的训练过程,我们最终会得到一个关于y-hat(预测值)的表达式,这是一个有已知的参数W和b,而输入是x的表达式。每次输入x,y-hat计算的就是满足条件的概率是多少。如识别是不是猫的过程中,就是计算,是猫的概率是多少。
回到我们的手写体识别,看看具体是怎么实现的
根据前面的基础知识介绍,我们已经大概知道了手写体识别过程中的原理。接下来继续详细看一看。
1、首先是数据的格式化。
它的数据集的每个数据形式是这样子的,每个图片都只记录了灰度值,即0代表全白,255代表全黑。每个图片总共有28*28个像素点。图片非常的小,处理成PNG格式后,大概就这么点大。
我们把这些像素进行提取
则对于每个数据样本x,x = 28 * 28的矩阵,就是一个二维数组,记录了每个像素点的灰度值。当然在数据处理中,我们为了矩阵的高效运算,不妨将其转化成一维向量,即使用x = 784 * 1 或 1 * 784的矩阵。看实际使用过程中怎么定义矩阵的格式。
相应地,对于W,我们知道有多少个特征,就对应了多少个W,因为z=w1x1+w2x2+w3x3+b。因此W的维度是和x保持一致的,因为他们需要进行矩阵运算。
对于b,我们知道,b只是一个偏移值,所以b和y一样,就是一个数。
对于y-hat(预测值),我们知道,我们需要知道的是对于每个数字的概率值,然后取个概率值最高的,即为我们最期望看到的结果。这样的话我们会出现10个y-hat,分别代表了10个数字。
到这里,我们知道了每个数字都有自己的一个y-hat,分别计算是该数字的概率。这样我们总共有10个表达式,对于每个表达式,都要有自己的一套W,和一套b。
发现问题没有,如果我们对每个数字进行y-hat的模型训练,我们就需要计算出十套y-hat、十套W和十套b。那么我们的模型就得训练十遍!(训练模型是个非常费时的操作)
因此我们作一个向量转换,这里我们就有个技巧转换,我们在做数据处理的时候,把实际值做一个转换,变成一个1*10的数组,并把对应数字的位置置为1,其他都是0,举个例子,比如说我们要表示2,其为[0,0,1,0,0,0,0,0,0,0] (注意,是从零开始计算),这样做有一个好处,我们只需要训练一次,最终得到的训练结果是y-hat,也是1*10的数组,代表了每个数字的概率。因此我们随意输入一个x,则y-hat会计算返回一个含十个数字概率的数组。
经过上述的推导,我们统一一下风格,可得到如下的矩阵关系
y-hat = 1 * 10
W = 784 * 10
b = 1 * 10
x = 1 * 784
再加上m个数据,y-hat = m * 10。 x = m * 784
在手写体中,数据集m = 60,000,因此y-hat = 60,000 * 10的矩阵!矩阵计算需要耗费很大的资源,因此,我们需要知道应当尽可能的一步完成计算,减少多次执行。
2、使用TensorFlow进行模型训练(使用开头说的ModelArts指导时则可以跳过这些代码步骤!)
TensorFlow是一个深度学习的框架,站在巨人的肩膀上,能够做的更快。也可以自己处理,那么就需要进行详细的代码编写,需要引用python的numpy包,用来高效计算矩阵。由于自己写的代码对CPU的调度能力不足,算法复杂度高,循环次数多等问题,训练模型时间异常久,能力不足只能靠框架了,使用TensorFlow训练数据,只要30秒就可以完成数据读取以及训练。
直接上代码,稍作解释,mnist是从官方读取数据函数中读取官方数据集的函数。(会附在附件中)只要按照标准操作,就能直接读取完成数据。尝试过手动读数据,处理效果非常慢。官方数据分为四个包,train训练集和test测试集。两个集合都分为两套数据,一个是image,记录了28*28个像素,一个是label集,记录了该图形的正确数字是几。
mnist = inputdata.read_data_sets(=) W = tf.Variable(tf.zeros([])) b = tf.Variable(tf.zeros([])) x = tf.placeholder([]) y = tf.nn.softmax(tf.matmul(xW) + b) y_ = tf.placeholder([]) cross_entropy = -tf.reduce_sum(y_ * tf.log(y)) train_step = tf.train.GradientDescentOptimizer().minimize(cross_entropy)
这里解释一下x中庸的placeholder函数,这个表示占位模式,意思就是我现在不知道这个x有多少行,是多少个数据,我先占着内存,等之后我知道需要输入多少个数据集的时候,我再想办法赋值进去
使用如下的方式进行赋值,把minist_test_images的数据填充到x中;label填充到y中
feed_dict={x: mnist.test.images, y_: mnist.test.labels}
y函数的计算,这里用了另外一个激活函数sotfmax,和sigmoid函数一样,是把数值转化成概率的函数
另外就是,还执行了归一化的功能,把10个y-hat的概率归一化,方便查看哪个预测值的占比最高
cross_entropy就是我们说的cost函数,它统计了所有差异值,当然这里用的lost函数和我们之前的不一致
可以看到此处的lost函数= y_real * log(y-hat)
train_step这是一个声明使用梯度下降法训练模型的表达式,需要两个变量,一个是步长0.01,表示每次调整W,b需要调整多少量。另外一个是cost函数。表示期望要让哪个值达到最小。
完成这些初始操作后,开始初始化引擎,并初始化变量
# 初始化变量,TensorFlow引擎需要的初始化 init = tf.global_variables_initializer() sess = tf.Session() #初始化引擎 sess.run(init) #初始化变量
完成初始化后,我们就可以开始训练了。这里采用的是一个科普训练方法,随机训练。意思就是我利用数据每次取100个数据去训练,总共训练1000次。
# 开始循环模型, 循环1000次梯度下降的过程 for i in range(1000): batch_xs, batch_ys = mnist.train.next_batch(100) # mnist是数据集的所有数据 sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys}) # 每训练100次后评估模型状态 if i % 100 == 0: correct_prediction = tf.equal(tf.argmax(y, 1), tf.arg_max(y_, 1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})) y_test = tf.nn.softmax(tf.matmul(input_array, W) + b)
解释一下其中的几个函数功能。首先是minist.train.next_batch(100)
表示在train集中随机取出100个数据子集。
sess.run表示开启会话,TensorFlow需要使用会话的模式进行操作。
correct_prediction则表示,用预测值y去对比实际值y_,得到一个boolean型结果,如果匹配成功,则为true
而accuracy正确率,是用来评估准确率的,比如预测值y = [True,False,True,True] ,则会被转化为[1,0,1,1]此时取平均值,可以得到 0.75 ,则说明预测值y对于指定的数据样本,准确率为0.75。
最后,我们利用test集合去评估模型,并利用test集合的样本数量测试整体的准确率有多少。
我们输出这个准确率,以此来校验,在当前训练的情况下,训练的准确率情况。
到此,我们利用TensorFlow的第一次最简陋的模型就训练完成了,大概准确率在91%,非常低,网上有很多训练好的,使用ModelArts平台,准确率也会提高很多
最后这个for循环结束,我们的模型就训练完成了,此时W和b就已经被训练出来了。这个时候,我们再输入一个测试集,就可以去查看根据这个模型计算出来的概率数组了。它会计算返回一个1*10的数组,经过排序,我们输出概率最高的那个,就是最有可能的数字!
那么,最后的问题就是,我们怎么输入呢,当然是期望能够有个绘画板,画完以后,能够把数据传到后台,最终给出计算的结果,告诉我们,最有可能的结果
最后总结
整个过程还是神经网络的入门,学习及时作总结,更好的理解神经网络。这里仍然有几个问题需要回答和继续探索。
1、TensorFlow的使用,及框架学习
2、怎么继续提高手写体的识别率?
3、里面涉及到的各种细节操作。如lost函数的选取,激活函数的选取
继续谈几个关于细节操作的理解
1、首先是激活函数的选择
sigmoid、softmax等函数都是比较适用于概率模型的,能把数值转化成(0,1)的概率数值。当然有许多问题不是概率型的,因此这类函数就不一定适用,还有一些其他的函数,如ReLU,修正线性单元函数,ReLU=max(0,z),比较常用的激活函数,还有tanh,sigmod函数向下平移0.5的变型函数,还有
leaky ReLU = max(0.01z,z)。激活函数的选择会影响学习过程中的一些权重变化。里面有很深的学问。
2、神经网络层数的选择
还记得最前面谈的房价模型吗?那个是单层神经网络,意思就是输入直接影响到输出。即y和x之间只有一层的关系,但往往在实际应用中,是多层网络的,所以才叫深度神经网络。
如下图,表示的是一个双层神经网络,在输入层输出层中间还含了一个隐含层。
我们来看看这个隐含层是什么含义。举个例子,房子的大小是影响价格的关键因素吗?是的,但是还有一个事情我们需要考虑,房子的大小与价格之间还隔着一个所处区域的人口密集程度的概念。什么意思呢,就好比城市房价高、农村房子虽然大,但房价不一定高一样。所以我们推测,大小、市区距离和教育资源,其实不是影响价格的关键因素,而是其中隐藏的人口密集度最终影响到了价格。
同样,我们可以举出另外的几个原因,所处城市的科技水平、国家房价政策等等,这样,图中的三个小圈,即隐含层,就有了含义,意思就是,这些才是关键的影响房价的因素,但是我没法直接看出这些因素,我只能靠输入层的数据来推理出这些因素,从而看出其对价格的影响。
理解了这个含义后,我们才知道这个含义就不重要了。我只需要知道,输入层的数据,想知道最终的输出层的结果。至于中间有多少隐含层,我不关心它。他可能是人口密集度,可能人口密集度下还有一层关键因素,即还可能有其它的隐含层。但是没关系,我不需要知道有什么隐含层,只需要知道,能不能训练处一个复杂的模型,而这个模型,最终能够得到高准确率的值。
因此,在这个过程中,我们是不知道原因的,里面复杂的神经网络,我们不知道,我们只能猜测多少层神经网络会比较靠谱,因此这里就产生了巨大的变数,到底是房子的大小影响了房价吗?还是更进一层,其实是人口密集度影响了房价?还是更进一步,是人才聚集度影响了房价?还是单位面积的社会生产力影响了房价?这些问题可以一层一层深入,也意味着,神经网络可以一层一层递进。听说alphaGo的神经网络有100多层。这么一个庞大而又复杂的模型的拟合效果,是高超的。
3、神经网络神经元数量的选择
接着前面的话题,我们知道,可能会有个隐含层,这个隐含层是影响价格的关键因素。那么这些关键因素有多少呢?不知道,但我们可以测试,可以寻找,在实际的问题中,我们可能会去猜想,调研,就像我们会想人口密集度这样的因素,但这样的因素是层出不穷的。我们可以不断地去举,举很多例子。那么是多少个呢,不知道,所以才给了更多的训练空间,多余每个层级的神经网络能给出非常多的神经元数量,组合无数。(有很多研究会去寻找这样更有的方案)
4、损失函数的选择
在前面的例子中,在线性拟合的时候,我们使用了(y-y_real)^2作为损失函数,这个是线性拟合较合理的选择。后来在图片是不是猫的例子中,我们用了另外一个损失函数
再最后的手写体识别中,我们又用了另外一个损失函数。因此损失函数也有多种选择,它会在不同的场景表现出不一样的效果。而损失函数的选择,直接影响到成本函数Cost的计算,我们知道,最终计算其实就是利用了cost函数取最小值,来表示什么时候是训练的最好的模型,因此损失函数的选择尤为重要,它直接代表了怎么去评判模型的好坏。当然我们有个朴素的认识是没问题的,就是尽量让我们训练的模型,更可能地去贴合训练的真实数据。但往往这个过程是不可能完全拟合的,只能有意识的选择合适的评估方法。
5、W和b初始值的选择
我们知道,我们这个训练的过程,就是反复地迭代以找到最合适的W和b的过程,最开始我们在思考问题的时候,对W/b是带着一种“随意的态度”,仿佛是不论什么时候开始,反正最终都能找到一个最合理的J,来形成结果。但问题是,W和b如果完全从零开始,它可能导致所有的z=0 从而 y = 0,从而训练的模型是无效的。尤其在多层神经网络中,会有减弱的效果,训练到最后,就变成一个某个隐含层变成是0输入的层次,从而使得最终结果又变成一个空模型。因此初始值的选择也是尤为关键的事情。可以采用一些随机的方法,去不断训练,在保持其他值不变的情况下,随机初始W和b的值,也许在多次试验中,能得到一个较好的模型也说不定。
6、步长的选择
步长好像是根本不知道前文哪里出现过的概念,事实上,我们在TensorFlow的训练函数中,使用到这个步长。还记得我们的训练函数的两个传值吗?一个是步长 =0.01,另外一个是cost函数的传入,而训练函数的目的是,是在每次调整0.01步长下调整W和b,使得cost函数达到最小值。这个步长会影响结果吗?会。它会影响两个层面,第一是训练的速度,如果取得过小,将会导致训练时间过长,无法做更多的训练。第二是对最佳模型的影响。我们想象一下,我们现在是在一条直线上寻找最优解。假设此时它的位置是x=0.0085,我们使用0.01的步长调整,0.01,0.02,0.03,...0.08,0.09,发现了没有,它直接跳过了最好的0.085,只能在其周边进行摆渡。因此在成本函数J中也是如此,我们无法评估到底取的步长,会不会错过一个较好的模型,刚好才最优模型附近的变化值较大,一跳,就差异巨大。因此,步长的选择,又是一个关键的参数命题。
7、训练方法的选择
我们知道,我们前面使用的训练方法是随机方法,就是随机取100个值,然后训练1000次。信仰人品达到最好的模型,但同时我们发现,如果多次执行训练函数,它的模型结果是不一致的。有的时候准确率在92%,有的时候却只有85%。而在我们广义上的训练,应该是把每个数据都完整的走一遍,但这样做,又可能受数据排列的影响而影响到训练的结果。因此这里训练方法的选择,也是一个大命题。
总之,由于这里存在的变量太多,可能产生的结果也层出不穷,因此,怎么样去得到一个好的训练模型,仍然是需要探索的事情。只能多多参考、多多学习!
- 点赞
- 收藏
- 关注作者
评论(0)