深度学习(一)- 全连接神经网络
1. 神经元模型
在神经网络中,最基本的单元为神经元。在生物的角度上来看,神经元互相连接,在神经元处于“兴奋“状态时,会向其相连的神经元传递化学物质。其中处于”兴奋“的条件为:神经元的电位达到某个阈值。
类似的,在神经网络模型中,一个基本的神经元模型为:
Fig. 1. 周志华. 机器学习. 2016
可以看到,一个神经元接受多个输入,并产生一个输出。其中 x 为输入(例如一列 one-hot 编码后的向量),w为输入权重(即神经元的参数),y为输出。
在其他文档中,我们可能会看到以下神经元结构,例如:
Fig. 2. 郑泽宇 顾思宇. TensorFlow 实战Google深度学习框架. 2017
此处与上图的区别在于:多了一个常量 b(偏置项),并且神经元并没有阈值。这是由于此处将神经元的阈值固定为 -1,将 b 作为输入考虑,以简化问题。(上图暂未提到激活函数,会在后续提及)在下文中提到的神经元均以 Fig. 2. 为准。
2. 前向传播
多个神经元之间相互连接,即构成了神经网络,例如:
Fig. 3. 郑泽宇 顾思宇. TensorFlow 实战Google深度学习框架. 2017
常规神经网络由三部分组成:输入层,(一个或多个)隐藏层,输出层。
前向传播是指:由左到右,依次计算出每层每个节点的值,直至计算出y的值。
以上图为例(此处先省略偏置项 b),设:
输入为:X = [x1, x2]
输入层参数为:
隐藏层参数为:
则隐藏层输出为:
(若加上偏置项,则为:
输出层为: A x W(2) = y
(若加上偏置项,则为 A x W(2) + b(2) = y)
3. 激活函数
以上例子并未将激活函数考虑入内,可以明显看出,由 x 计算出 y 的公式为一个线性模型。
若是展开 A x W2 式,则最终结果类似为:
也就是说,最终的结果仍是一个线性函数,并且添加层数并不能解决此问题。线性模型能解决的问题有限,若是一个分类问题,可以由一条线或是一个高位平面划分解决,则可以使用线性模型。而若是此问题并不是一个线性可分问题,则无法直接通过线性模型解决,例如:
此分类问题的 decision boundary(分类边界)为一个(类)圆形方程,非线性方程,所以无法解决此类问题。
为了将神经元的输出做去线性化,便引入了激活函数,常用激活函数有:ReLU、sigmoid以tanh。具体如下所示:
Fig. 4. 郑泽宇 顾思宇. TensorFlow 实战Google深度学习框架. 2017
在加入激活函数后,前向传播的公式可写为(f 为激活函数):
隐藏层输出为:
输出层为:
4. 损失函数
在机器学习中,损失函数用于衡量模型输出值与目标值之间的损失(差距)。例如:在Fig.3. 的神经网络中,对于一个训练样本,假设模型输出为 y,训练样本的目标值为 y’,则此单个样本的损失值为 |y’ - y|。对于多个样本,损失值为:
为了方便求导以及计算,一般使用均方误差,则此时损失值为:
由于 x 已知,所以其中 y’ 可以表示为自变量为 w(神经网络参数)的输出,所以 loss 可以表示为自变量为 w 函数。此时机器学习训练的目标为:找出使得 loss 值最小的参数 w。此过程即为机器学习的训练过程。
以上提到的均方误差损失函数主要用于监督学习中的回归问题。在机器学习中的监督学习中,主要有两大类:分类与回归。上面介绍了回归问题中的经典损失函数,下面介绍分类问题中经典损失函数。
在分类问题中,以MINST 手写识别问题为例,目标类别一共有 11 个类别,为 0 – 10。所以在神经网络的输出层用,一般会对应有 11个节点,这是 11 个节点组成一个 11 维向量。若是需要识别的目标值为 5,则神经网络的输出越接近 [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],表示预测概率越准。其中,衡量两个向量相似度的方法为:交叉熵(cross entropy)。
交叉熵衡量的是两个概率分布之间的距离,作为损失函数广泛用于多分类问题中。给定两个概率分布 p 和 q,通过 q 来表示 p 的交叉熵为:
上文提到,交叉熵用于衡量两个概率分布之间的距离,但是神经网络的输出却不一定是概率分布。所以我们需要将神经网络的输出层处理为一个概率分布,这里用到的方法是 Softmax。
假设神经网络的输出为 y1 , y2 ,…, yn,在经过 Softmax 处理后,输出为一个概率分布:
再次回到交叉熵的公式,此公式并不是一个对称的(也就是说 H(p,q) 并不等于 H(q,p)),它刻画的是:用概率分布 q来表示概率分布 p 的难度。我们在计算出预测分布后,需要了解的是如何用预测分布来刻画实际分布。所以神经网络的输出(经过Softmax)被用作q,实际概率分布为p。
交叉熵衡量了两个概率分布之间的距离,所以此值越小,表示预测分布与实际分布越接近。
5. 梯度下降
在神经网络中,最常用的优化方法为反向传播算法。神经网络的训练过程是:求解参数 w 的值,使得损失函数的损失值尽可能的小(一般很难求解最小)。所以神经网络训练优化的是参数 w 的值。
反向传播算法基于的是梯度下降法,梯度下降法的过程简单的说,就是:不断迭代更新参数,使得损失值不断向最小值方向移动。此处的梯度表示的是函数的梯度(斜率、一阶导数),给定一个参数值,使得此参数值不断沿着梯度(斜率)的反方向移动,以使参数值不断趋近于最小值。
下面以二元二次方程为例:令 y = f(x) = x2,使用梯度下降的方式求解 x,使得y不断趋近于最小值。
1. 1. 由 f(x) = x2 ,我们可以求得 f(x) 的一阶导数为: f(x)’ = 2x
2. 2. 对 x 的初始值取随机值(如正整数2),则在 x=2 时,它的梯度为 4,梯度的反方向为 -4
3. 3. 指定在移动的过程中步长参数为 0.1(也就是梯度下降的学习速率),则第一次移动的值为 -0.4,移动后的 x 值为 1.6,y值为 2.56
4. 4. 不断重复步骤3,直到移动到足够小的值,或是达到指定训练轮数
此过程图示如下:
Fig. 5. Andrew Ng. Machine Learning (Couresra)
一般在机器学习的训练中,会有多个参数值,如 w1, w2, w3 … wn。此时在使用梯度下降优化时,首先随机为它们产生初始值,然后在一轮学习中,更新每个参数值。如更新 w1 时,先固定除它之外的所有参数,然后更新 w1。继而在更新 w2 时,仍使用w1 更新前的值,并固定其他参数,然后更新w2 。更新完所有 w 后,为一轮学习(更新)结束,继而进入下一轮学习(更新)。
在机器学习中,一般使用梯度下降算法对损失函数进行优化,求解损失函数的参数 w,使得损失值最小。由损失函数的公式(这里以均方误差损失函数为例)可知,在计算时需要将训练集中所有样本考虑在内。这样就会导致大量计算,每一轮迭代中都要计算整个样本量的损失值,使得计算时间大大增加。所以之后又提出了随机梯度下降方法。
随机梯度下降是指:不使用全部的训练集,而只是使用一条训练数据,对参数进行更新。但是此方法的问题是,一条训练数据不足以代表整个数据集。所以最终的方法使用的是:使用一个batch(如50条,100条等)的训练数据对参数进行更新。这样兼顾了训练速度以及准确性。
6. 反向传播
反向传播是神经网络中最常用的优化算法,其原理基于梯度下降,用于优化神经网络中参数。简单的说,就是由输出层开始,使用梯度下降的方式,逐步向前更新每层参数。此方向与前向传播方向相反,所以称为反向传播。这里我们以一个最简单的神经网络来说明反向传播的过程:
此处 x 为单节点输入层,a 为单节点隐藏层,y 为单节点输出层,则:
以均方误差为损失函数,则有:
以上函数中,给定 x,以及初始化随机值 w、u,可求得 a 与 y,并应用梯度下降法更新参数 u 的值。将以上损失函数继续展开得:
可以进一步得到有关自变量 w 的损失函数,然后再使用梯度下降对参数 w 进行更新。若是有多层,则逐层往以此方式更新参数。以上是一个简单的反向传播思想,在实际应用中,除了有多层,多节点外,还涉及激活函数。更详细的反向传播推理、求导过程(链式求导),请参考《机器学习》(周志华,2016)神经网络章节。
7. 全连接神经网络实现
在有了以上知识基础后,下面我们用Keras 搭建一个神经网络,应用于 MINST(手写数字数据集)。Keras 是 python 的一个库,提供了用于搭建神经网络模型的高层 API。在Keras 之下,用于训练的框架可以基于 TensorFlow,CNTK 或Theano。下面我们使用 Keras 搭建一个全连接神经网络。
首先获取MINST数据集:
训练图片集中,每一条为 28 × 28 的矩阵,取值范围为 0-255,一共60000 条数据。这里我们需要将 28 × 28 矩阵转换为一维向量,以输入模型神经元:
这里除以 255 是为了将数据做归一化,将数据范围调整到(0, 1)范围内,以防止数值之间差值太大,影响训练结果。
再看一下训练图片集中的标注信息:
接下来搭建模型:
这里我们使用了全连接神经网络(即 Dense()),其中输入层为 28 × 28个神经元(对应训练数据每条向量维度),隐藏层为 512 个神经元,输出层为 10 个神经元(对应每条输出的向量维度)。
所以输入层与隐藏层之间的参数个数为:28×28 ×512 + 512(这里+512 表示的是偏置项)= 401920。这里隐藏层的激活函数为上文提到的 sigmoid,输出层的激活函数为上文提到的 softmax。
优化方法为 rmsprop(optimizer=’rmsprop’,也是一种基于梯度的优化方法,类似随机梯度下降),损失函数为 categorical_crossentropy(上文提到对于多分类任务使用的损失函数),metrics为accuracy(即以准确度衡量模型训练结果)。
搭建完模型后,开始对模型进行训练:
这里epochs 表示训练 20 个轮次,batch_size 表示每次以 128 条为一个 batch(前文在随机梯度下降有提到),validation_split 表示使用训练集的多少比率数据用作验证集。
训练结束后,我们在测试集上进行拟合:
可以看到在测试集上的loss 为 0.123,准确度为 96.55%。
在history.history 里会记录每轮训练后的损失值以及正确率,使用 matplotlib 画出训练集与验证集上每轮的损失值与正确率:
从上图可以看出,训练集在开始训练后的很早几轮后即开始出现了过拟合的现象,导致模型泛化性能相对不足。对此,可以尝试增加L1、L2 正则惩罚项、使用dropout、减少网络大小(即可训练的参数个数)、获取更多训练数据等,以降低过拟合(这里其实前几轮训练后即开始出现过拟合,所以可以在4轮左右即可停止训练)。具体有关过拟合与欠拟合之后再详述。
References:
1. 郑泽宇 顾思宇. TensorFlow 实战Google深度学习框架. 2017
2. 周志华. 机器学习. 2016
3. Python 深度学习. 弗朗索瓦 肖莱. 张亮译. 2018