论文:Introduction to Gradient Descent and Backpropagation Algorithm
文章目录
- 0 前言
- 1 链式法则
- 定义
- 作用
- 2 单神经元的正向传播forward propagation
- 定义
- z 激活函数
- 3 损失函数
- 定义
- 4 损失函数对权重张量的偏导数
- 定义
- z对w求偏导
- l对z求偏导
- 5 多个神经元的正向传播和 损失函数对权重张量的偏导数
- z对w求偏导
- l对z求偏导
- 6 反向传播 backward propagation
- 定义
- 示例:单输出的梯度
- 示例:多输出的感知机以及梯度
- 示例:
- 1加法节点的反向传播
- 2 乘法节点的反向传播
- 小结
- 示例:复杂的函数的正向传播和反向传播
- 1 Sigmoid函数
- 2 Softmax-with-Loss 层
- 总结
- 参考
- 附录
0 前言
专用名词:
- 梯度下降
- 深度学习的大致步骤( 构建神经网络—> 数据拟合 --> 选出最佳模型)
- 选出最佳模型的方式,其实就是利用梯度下降算法,选出损失函数最小的那个。
- 在深度学习当中,由于存在输入层,隐藏层,输出层,因此计算也会更加繁杂。
- 正向传播,可以知道神经元的输出。
- 目标函数,也叫做损失函数。计算正向传播的结果和真值结构的差异的公式。
- 利用反向传播,逐层求出目标函数对各神经元权值的偏导数,构成目标函数对权值张量的梯度,梯度是为了对权值的优化提供依据。
- 权值优化了之后,再转为正向传播,当输出的结果达到设定的标准时,算法结束。
1 链式法则
定义
链式法则公式:
我们需要了解的就是链式法则:
通过扰动的思路,更容易理解链式法则,从扰动的角度来思考它会更直观。 ds 扰动 s 也会扰动 z,仍然可以推导得到链式法则
作用
链式法则,通过使用链式法则就可以把最后一层的误差一层一层的输出到中间层的权值中去,从而得到中间层的梯度信息,然后就可以很好的更新权值,得到最优化的效果。
对于一个简单的线性层来说不需要链式法则,但是对于对于实际的神经网络来说,展开公式是非常复杂的,不能直接一次到位,因此使用链式法则可以使得我们的求导过程非常简单。
2 单神经元的正向传播forward propagation
定义
用逻辑回归的神经元为例,
z
=
w
1
x
1
+
w
2
x
2
+
b
z = w_1x_1+w_2x_2 + b
z=w1x1+w2x2+b。线性方程z会被代入到Sigmoid函数当中
一步步从左向右计算,最后得出结果,这就是称为正向传播(forward propagation)。
z 激活函数
Sigmoid函数的导数
3 损失函数
定义
正向传播的结果与真值的差异,用损失函数记为l,进行计算,例如,最小二乘法的损失函数。
假设实际值是y,损失函数是各个输出层的损失函数的累加,所以,整个神经网络的最小二乘损失函数就是:
4 损失函数对权重张量的偏导数
为了更新权重w,因此损失函数要对各个w求偏导,于是:
那么,这当中,最关键的部分,其实就是求sigma’(z),对于分类问题
定义
深度学习模型的优化目的是:修正权值w。让损失函数 l 对 w 求偏导,根据链式准则:
其中,
z对w求偏导
∂
z
∂
w
\frac{\partial z}{\partial w}
∂w∂z计算如下,是z对w求偏导:
又知道
z
=
w
1
x
1
+
w
2
x
2
+
b
z = w_1x_1+w_2x_2 + b
z=w1x1+w2x2+b
因此x1,x2其实就是最开始的输入值,因此可以当做是已知的。
l对z求偏导
而l对z求导,这个部分其实很复杂,用链式法则求解。
那么这个部分该怎么求呢?如下所示:
∂
l
∂
z
=
∂
l
∂
a
∂
a
∂
z
=
∂
l
∂
a
σ
′
(
z
)
(公式
1
)
\frac{\partial l}{\partial z} =\frac{\partial l}{\partial a} \frac{\partial a}{\partial z}=\frac{\partial l}{\partial a} \sigma '(z)(公式1)
∂z∂l=∂a∂l∂z∂a=∂a∂lσ′(z)(公式1)
上游传来的导数乘上局部导数,得出传给下游的导数
5 多个神经元的正向传播和 损失函数对权重张量的偏导数
如果存在多个神经元的传播,如下图:
损失函数对权重张量的偏导数,
z对w求偏导
∂
z
∂
w
\frac{\partial z}{\partial w}
∂w∂z计算如下,是z对w求偏导:
l对z求偏导
∂ l ∂ z \frac{\partial l}{\partial z} ∂z∂l计算如下,
∂ l ∂ z = [ w 3 ∂ l ∂ z ′ + w 4 ∂ l ∂ z ′ ′ ] σ ′ ( z ) (公式 2 ) \frac{\partial l}{\partial z} = [w_{3}\frac{\partial l}{\partial z'} + w_{4}\frac{\partial l}{\partial z''}]\sigma '(z)(公式2) ∂z∂l=[w3∂z′∂l+w4∂z′′∂l]σ′(z)(公式2)
即:先求l对 z ′ z' z′的导,以及l对 z ′ ′ z'' z′′的导,然后分别乘以w3和w4,在结合sigma’(z)。由于在正向传播的时候,z是已知,sigomid函数已知,因此sigma’(z)我们也是已知的。
同学们注意: z ′ z' z′和 z ′ ′ z'' z′′是经过线性方程的结果,不是导数。本文中的导数都是用的 ∂ z {\partial z} ∂z
不过,又产生新的问题了。dl/dz的求解,明显依赖于dl/dz’,以及dl/dz’’。那么这两项又是如何求的呢?答案是:根据输出值去求。任何一组数据,在经过神经网络运算之后,都会产生一个输出值,我们记为y。如下所示:
这个时候,我们就要分情况讨论了。
第一种情况:y1与y2在都在输出层
这个时候,比较简单,根据下面这个公式,直接套用即可:
∂
l
∂
z
′
=
∂
l
∂
y
1
∂
y
1
∂
z
′
\frac{\partial l}{\partial z'} = \frac{\partial l}{\partial y_{1}}\frac{\partial y_{1}}{\partial z'}
∂z′∂l=∂y1∂l∂z′∂y1
∂
l
∂
z
′
′
=
∂
l
∂
y
2
∂
y
2
∂
z
′
′
\frac{\partial l}{\partial z''} =\frac{\partial l}{\partial y_{2}} \frac{\partial y_{2}}{\partial z''}
∂z′′∂l=∂y2∂l∂z′′∂y2
第二种情况:y1,y2存在不在输出层的情况
这个时候,那就一直算到输出层,然后再从输出层往回传播即可。
6 反向传播 backward propagation
定义
计算图的反向传播(backward propagation)也就是BP算法,计算的方式:沿着与正方向相反的方向,上游传来的导数乘上局部导数,得出传给下游的导数。
这样通过链式法则,就能够完成反向传播了。
例如公式2
∂
l
∂
z
=
[
w
3
∂
l
∂
z
′
+
w
4
∂
l
∂
z
′
′
]
σ
′
(
z
)
\frac{\partial l}{\partial z} = [w_{3}\frac{\partial l}{\partial z'} + w_{4}\frac{\partial l}{\partial z''}]\sigma '(z)
∂z∂l=[w3∂z′∂l+w4∂z′′∂l]σ′(z),根据其计算流程,画出来了下面这个图,上游传来的导数乘上局部导数,得出传给下游的导数。
示例:单输出的梯度
所谓的单输出,就是指,只输出一个值,如下图所示。我们假设这个单输出的网络如下所示:其中符号sum就是我们一直说的z,它经过sigma函数后,输出的结果是O,真实值是t,则他们之间的损失值就是E。
则,最小二乘法的损失函数E对某一个权值w求导的结果就是:
因此,这个求导的结果,是可以直接根据神经网络的输出结果算出来的。
总结:
方法实现
输入10个特征的x
x = torch.randn(1,10)
# tensor([[ 0.5817, -1.1089, -0.9756, -0.4556, -0.2144, -1.1662, 1.9232, 0.2331,
# -1.2987, -0.4950]])
w = torch.randn(1,10,requires_grad = True)
# tensor([[-1.0490, -1.7553, 0.1665, -0.0458, -0.8664, -0.3328, -0.1398, 1.2416,
# 1.3097, -0.4996]], requires_grad=True)
o = torch.sigmoid(x@w.t())
# tensor([[0.5831]], grad_fn=<SigmoidBackward>)
loss = F.mse_loss(torch.ones(1,1),o)
# tensor(0.1738, grad_fn=<MseLossBackward>)
loss.backward()
w.grad
# tensor([[-0.1179, 0.2248, 0.1977, 0.0923, 0.0435, 0.2364, -0.3898, -0.0472,
# 0.2632, 0.1003]])
这样就得到了每一个 w w w的梯度,随后可以根据 w ′ = w − 0.001 ∗ ∇ w 来更新参数了。
示例:多输出的感知机以及梯度
其实多输出的感知机已经不能叫做感知机了,因为感知机的定义就是一个单一输出的。而多输出的感知机相当于标准全连接层的输出层。
最小二乘法的损失函数E对某一个权值w求导的结果就是
from torch.nn import functional as F
x = torch.randn(1,10)
w = torch.randn(2,10,requires_grad = True)
o = torch.sigmoid(x@w.t())
o.shape # torch.Size([1, 2])
loss = F.mse_loss(torch.ones(1,2),o) # tensor(0.1849, grad_fn=<MseLossBackward>)
loss.backward()
w.grad
# tensor([[ 0.1048, 0.0295, 0.0433, 0.0624, 0.0105, -0.0463, 0.0789, -0.0975,
# -0.0250, -0.0553],
# [ 0.1950, 0.0549, 0.0805, 0.1162, 0.0196, -0.0862, 0.1467, -0.1815,
# -0.0464, -0.1029]])
示例:
下面介绍一些节点的反向传播的方法
1加法节点的反向传播
首先来考虑加法节点的反向传播。这里以z = x + y为对象,观察它的反向传播。z = x + y的导数可由下式(解析性地)计算出来。
那么其反向传播就如下:
加法节点的反向传播将上游(反向传播右边为上游)的值原封不动地输出到下游
例子:
2 乘法节点的反向传播
接下来,我们看一下乘法节点的反向传播。这里我们考虑z = xy。这个
式子的导数用式(5.6)表示。
乘法的反向传播会将上游的值乘以正向传播时的输入信号的“翻转值”后传递给下游。翻转值表示一种翻转关系,如下图,正向传播时信号是x的话,反向传播时则是y;正向传播时信号是y的话,反向传播时则是x。
例子:
注意:
因为乘法的反向传播会乘以输入信号的翻转值,所以各自可按1.3 × 5 = 6.5、1.3 × 10 = 13计算。另外,加法的反向传播只是将上游的值传给下游,并不需要正向传播的输入信号。但是,乘法的反向传播需要正向传播时的输入信号值。因此,实现乘法节点的反向传播时,要保存正向传播的输入信号。
小结
其实还会有很多复杂的节点例如log节点,exp节点,/节点等等,其实会计算各种归根到底就是学会如何求这些函数的导数罢了。因此列举加法节点和乘法节点就是为了抛砖引玉,使得我们更形象的理解反向传播。
示例:复杂的函数的正向传播和反向传播
接下来让我看一些比较复杂的函数的正向传播和反向传播的过程。
1 Sigmoid函数
Sigmoid函数计算图的正向传播过程:
接下来进行反向传播:
为了方便表示,我们令
步骤1
“/”节点表示 ,它的导数可以解析性地表示为下式:
步骤2
“+”节点将上游的值原封不动地传给下游。计算图如下所示。
步骤3
“exp”节点表示y = exp(x),它的导数由下式表示。
步骤4
“×”节点将正向传播时的值翻转后做乘法运算。因此,这里要乘以−1。
至此,反向传播已经完成。
可以整理一下:
那么我们可以定义一个Sigmoid节点,其反向传播表示如下:
2 Softmax-with-Loss 层
下面来实现Softmax层。考虑到这里也包含作为损失函数的交叉熵误差(cross entropy error),所以称为“Softmax-with-Loss层”(Softmax函数和交叉熵误差)。
其正向传播如下:
为了方便表示,我们进行一下变量替换如下:
值得注意的是:
因为计算图是局部计算,图中的求导是将前面两个输入节点的看成不同变量的。虽然二者可能相关,但是还是要看成不同的变量,因此需要使用图的乘法规则来进行反向传播的计算。
接下来让我们一步步看看反向传播如何进行。
首先,交叉熵误差的反向传播:
“x”节点上游传过来的导数为1
“+”节点上游传过来的导数为-1
那列“x”节点上游传过来的导数都为-1
那列log节点上有传过来的导数分别为-t1、-t2、-t3
这里遇到“x”节点。
“×”节点将正向传播的值翻转后相乘。这个过程中会进行下面的计算。
即传给“/”的导数如下:
“+”节点原封不动地传递上游的值。
“×”节点将值翻转后相乘。
因为上面令
所以
最后exp节点的反向传播如下:
因为exp(x)的导数就是exp(x),求导还是他本身
所以第一个exp节点最后反向传播的结果如下(正向分流,反向合流):
因为开始令
所以最后就是
然后剩下的计算都是一样的了。
最终反向传播结果如下:
至此,Softmax-with-Loss 层反向传播的推导完成。
虽然一眼看上去感觉这个计算图十分复杂,但是一步步走过去每一步都是蛮简单的,这就是计算图的局部计算的优势了。
然后现在指定一个学习率lr(0.001等),假设我们第一个输入是a1经过反向传播得到的误差为y1-t1,那么更新a1的公式如下:
a1 *= a1 - lr * (y1-t1)
这就是BP算法思想的核心,即先正向传播,计算出误差(损失值,通常会用一个损失函数来衡量预测值与真实值的差距,例如交叉熵函数、均方误差函数等等),然后将误差反向传播,得出每个参数应当做多少的修改以更接近真实值,让误差变小,从而使模型进行训练。
深度学习模型的训练就是依靠误差反向传播调整参数进行的。
总结
正向传播与反向传播其实是同时使用的。
首先,你需要正向传播,来计算z对w的偏导,进而求出sigmoid’(z)是多少。然后,根据输出层输出的数据进行反向传播,计算出l对z的偏导是多少,最后,代入到公式0当中,即可求出l对w的偏导是多少。注意,这个偏导,其实反应的就是梯度。然后我们利用梯度下降等方法,对这个w不断进行迭代(也就是权值优化的过程),使得损失函数越来越小,整体模型也越来越接近于真实值。
参考
https://blog.csdn.net/johnny_love_1968/article/details/117598649
https://blog.csdn.net/johnny_love_1968/article/details/117849939
https://atcold.github.io/pytorch-Deep-Learning/en/week02/02-1/
https://blog.csdn.net/vincent_duan/article/details/113974739
https://blog.csdn.net/qq_52785473/article/details/127133611
附录
softmax 函数求导