@JackMeGo
2018-02-26T09:42:06.000000Z
字数 6064
阅读 2204
神经网络
梯度下降
反向传递
在神经网络的训练过程中,梯度下降是最快达到误差极小值点的方式,而每个节点权重的梯度是通过反向传递进行计算的。本文主要包含三个方面:
- 简单介绍神经网络的构成,并通过前向传递计算网络损失
- 证明梯度下降法的有效性
- 对后向传递的公式进行推导
下图是一个简单的计算节点,输入乘以权重系数并加上一个偏置量,然后对输出使用激活函数进行规范化处理作为输出。激活函数有很多种(sigmod,tanh等),选择哪种激活函数需要根据实验效果确定,这里我们选择的激活函数是sigmod函数,处理后的结果在(0,1)区间。
图一:神经元结构
其中权重和偏置量分别什么作用?影响的是激活函数的斜率,可以使图像增长的平缓或者陡峭,影响输出的对称中心,可以使图像左移或右移。
=0,改变
=1,改变
有了上面单个节点的概念,我们来看在一个由许多神经元组成的分层神经网络中如何通过前向传递和反向传递,实现对整个神经网络的训练和优化。
下面是一个包含了三个层级的神经网络,分别是输入层(L1),中间层(隐藏层,L2)和输出层(L3)。
我们使用表示第层的第个节点到第层的第个节点的权重值,这跟斯坦福教程中的表示方法一致。例如表示第1层中第一个节点到第2层第2个节点的权重。这种表示有点儿怪,但是当加入偏置量时就很容易表示。我们用表示第层的所有节点对第层的第个节点的偏置量。这里由于偏置量对层所有节点都是一样的,所以我们只需要层节点的下标,省略掉了层节点的下标,这也是为什么我们要把层节点的下标放在前面,否则我们就要添加一个空白占位符表示被省略掉的层下标。这里和都会在神经网络训练过程中被不停的更新。同时,我们使用表示第l层第i个节点的输出,它是对输入使用权重和偏置量计算后,再作用以激活函数后的结果。
有了上面的基础,接下来就可以进行前向传递和反向传递的计算了。
前向传递的过程很简单直接,从第2层第一个节点开始,逐层逐节点从前往后计算就是了。
其中是输入,是第3层的输出,是激活函数,本文我们用的是sigmod函数。
代码表示如下:
def simple_looped_nn_calc(n_layers, x, w, b):
for l in range(n_layers-1):
#Setup the input array which the weights will be multiplied by for each layer
#If it's the first layer, the input array will be the x input vector
#If it's not the first layer, the input to the next layer will be the
#output of the previous layer
if l == 0:
node_in = x
else:
node_in = h
#Setup the output array for the nodes in layer l + 1
h = np.zeros((w[l].shape[0],))
#loop through the rows of the weight array
for i in range(w[l].shape[0]):
#setup the sum inside the activation function
f_sum = 0
#loop through the columns of the weight array
for j in range(w[l].shape[1]):
f_sum += w[l][i][j] * node_in[j]
#add the bias
f_sum += b[l][i]
#finally use the activation function to calculate the
#i-th output i.e. h1, h2, h3
h[i] = f(f_sum)
return h
当然,这段代码只是为了展示计算的过程,实际上通过循环的方式计算每一个节点是很耗时的,有更快的计算方法可以数倍的节省时间,那就是矩阵计算。
为了使用矩阵计算,我们对上面的公式进行整理,引入新的符号代表第l层第i个节点在进入激活函数之前的结果。
这样,前向传递的公式可以表示为:
进一步泛化:
这就是前向传递的矩阵表示,简洁明了,可以通过矩阵乘法进行计算,从而避开了循环计算带来的开销。
代码如下:
def matrix_feed_forward_calc(n_layers, x, w, b):
for l in range(n_layers-1):
if l == 0:
node_in = x
else:
node_in = h
z = w[l].dot(node_in) + b[l]
h = f(z)
return h
经过一次完整的前向传递过程,我们得到了一个输出结果,拿这个结果和真实的结果比较,我们可以计算出误差,我们的目的是让这个误差随着训练次数增大变得越来越小,所以这就成了一个根据误差函数(损失函数),求极小值的问题。
损失函数通常有两种,损失函数和损失函数。损失函数就是真实值和预测值差的绝对值,这里我们使用损失函数,它在机器学习中更常用,损失函数就是平方和误差,也就是真实值和预测值差值的平方,前面添加一个系数1/2是为了在后向传递过程中求偏微分时系数相抵消,定义如下:
这是对一个训练样本计算损失的函数,对所有训练样本计算损失函数只要累加求均值即可,公式如下:
有了损失函数,接下来要考虑的问题是如何调整神经网络每个节点的权重值和偏置项,来使得损失函数在训练样本上降到最低。这就用到了梯度下降和后项传递方法。
考虑下面一张三维的图像,其中的每条等高线就相当于损失函数是某个值的一系列点,如何在位置最快到达中间的极小值点。答案是每一步都沿着图中红色的箭头向量走一小步。红色的向量是如何计算的,它就是在当前点,分别计算损失函数对和的偏导数(也就是改点的梯度)构成的,沿着这个方向可以最快的到达极小值点。
为什么沿着梯度的方向可以最快的到达极小值点?
先给出一组概念:
概念 | 物理意义 |
---|---|
导数 | 函数在该点的瞬时变化率(数值) |
偏导数 | 函数在坐标轴方向上的变化率(数值) |
方向导数 | 函数在某点沿某个特定方向的变化率(数值) |
梯度 | 函数在该点沿所有方向变化率最大的那个方向(向量) |
为了解答上面的问题,需要证明方向导数和梯度向量方向一致的时候,高度下降的最快。证明过程如下:
方向导数定义:
梯度定义:
理解这个证明过程的关键,在于需要深刻理解偏导数计算过程中极限的概念,沿某个方向的偏导数,是沿这个方向移动及其微小的距离,然后计算高度的差值跟这个距离的比例,作为改点的偏导数。这个微小的距离在极限情况下逼近于0,此时曲线变成了直线,曲面变成了平面。
所以,现在我们知道,沿着梯度的方向,可以最快的到达极小值点。具体的方法,就是先求得损失函数对每个变量的偏导数,然后将每个变量沿着偏导数方向移动一小段距离。
实际情况下,我们是无法得知损失函数的具体表达式的,类似于之类的,所以,在实际操作中,我们采用了一种叫后向传递的方法来衡量每个节点的W和b参数对预测值和真实值之间误差的影响。
考虑下面的三层神经网络,我们定义:
为编号为i的节点的加权输入
为隐藏层编号为i的节点的输出
为输出层第i个节点的输出
它们的计算如下:
损失函数定义如下:
如果能计算出损失函数对每个权重的偏导数,就能通过梯度下降算法更新权重:
接下来推导如何计算损失函数对每个权重的偏导数。
根据链式法则,对每个权重的偏导数计算如下:
接下来计算,这里对输出层和隐藏层需要分开计算:
对于输出层来说,通过直接作用在上(不会通过之外的节点)而影响网络误差:
第一项:
第二项:
将第一项和第二项结果带入:
我们定义为网络误差对这个节点输入的偏导数的相反数,称它为一个节点的误差项(定义很奇怪,但可以简化反向传递过程的表示):
带入上面的公式,则有:
带入梯度下降公式:
接下来计算隐藏层的,跟输出层不同,隐藏层节点的会通过传递到当前层的下一层所有节点最终影响到网络误差,所以需要累加下一层所有节点的误差项:
带入,有:
总结上面的推导结果,则有:
输出层:
隐藏层:
更新权重:
这四个公式(公式4可以仿照公式3进行推导)就是反向传递过程中梯度更新使用的四个最终公式。其中,是节点到节点的权重,是一个成为学习速率的常数,是节点的误差项,是节点传递给节点的输入。
参考:
http://adventuresinmachinelearning.com/neural-networks-tutorial/
http://liuchengxu.org/blog-cn/posts/dive-into-gradient-decent/
https://www.zybuluo.com/hanbingtao/note/476663