我们接着之前的内容
tf2.0学习(一)——基础知识
tf2.0学习(二)——进阶知识
tf2.0学习(三)——神经网络
开始后边的新内容:反向传播算法(BP算法)。
误差的反向传播,是神经网络中最核心的算法之一。虽然各深度学习框架都为我们提供了自动求导,自动更新参数的功能,模型设计者几乎不需要深入了解BP算法也能设计出复杂网络。但是BP算法作为神经网络的核心算法,深入理解其工作原理十分重要。
4.1 导数与梯度
导数的定义为,自变量x产生一个微小的扰动之后,函数输出值的增量与自变量增量趋近于0时的极限a,如果存在a即为处的导数:
导数记作。
从几何角度看,一元函数在某处的导数,就是函数的切线在此处的斜率,即函数值沿着x方向的变化率。偏导数是导数在多元函数上的推广。
在神经网络中x一般表示为输入,网络的自变量是网络参数集。
利用梯度下降算法优化神经网络时,需要求出输出函数对所有单数的偏导数。
损失函数对所有参数的偏导数可以记作如下向量:
此时梯度下降的参数更新公式如下:
这个由所有偏导数组成的向量叫做函数的梯度,它所表征的是函数值上升最快的方向,梯度的反方向则是函数值下降最快的方向。
梯度下降算法并不能保证在所有情况下都能找到全局最优解,这主要是由于目标函数可能非凸导致的。
神经网络的模型表达,往往需要数亿级别的参数。深度学习的框架帮我们实现的最主要任务之一就是反向传播算法和梯度下降算法。
4.2 导数的常见性质
4.2.1 基本函数的导数
- 常数函数 c,导数为0
- 线性函数 y=ax+b, 导数为a
- 幂函数 ,导数为
- 指数函数 ,导数为
- 对数函数 ,导数为
4.2.2 常用导数的性质
- 函数相加
- 函数相乘
- 函数相除
- 复合函数
4.3 激活函数的导数
4.3.1 Sigmoid函数导数
python代码实现如下:
import numpy as np
def sigmoid(x):
return 1 / (1+np.exp(-x))
def derivative(x):
return sigmoid(x) * (1-sigmoid(x))
4.3.2 ReLU函数导数
python代码实现如下:
def derivative(x): # ReLU函数的导数
d = np.array(x, copy=True) # 用于保存梯度的张量
d[x < 0] = 0 # 元素为负的导数为0
d[x >= 0] = 1 # 元素为正的导数为1
return d
4.3.3 LeakyReLU 函数导数
python代码实现如下:
def derivative(x): # LeakyReLU函数的导数
d = np.ones_like(x, copy=True) # 用于保存梯度的张量
d[x < 0] = p # 元素为负的导数为p
return d
4.3.4 Tanh函数导数
python实现如下:
def sigmoid(x): # sigmoid函数实现
return 1 / (1 + np.exp(-x))
def tanh(x): # tanh函数实现
return 2*sigmoid(2*x) - 1
def derivative(x): # tanh导数实现
return 1-tanh(x)**2
4.4 损失函数的梯度
这里主要介绍平方误差损失函数和交叉熵损失函数的梯度。
4.4.1 均方误差函数梯度
均方误差的损失函数如下:
他的偏导数可以表示为:
当且仅当 k=i时,,也就是说偏导数只与第k号节点相关,与其他节点都不相关,值为0。
4.4.2 交叉熵函数梯度
在计算交叉熵损失函数的时候,一般将Softmax函数与交叉熵函数统一实现。所以我们先推到Softmax函数的梯度,再推到交叉熵函数的梯度。
首先看Softmax函数:
回顾前边提到的,函数相除的导数:
那么,在softmax函数中,。
当j = i时:
当 j 不等于 i 时:
综上所述,softmax函数的偏导数如下:
交叉熵损失函数的表达式如下:
则:
其中即为我们前天推到的softmax函数的偏导数。
将前边结果带入上边公式中:
4.5 全连接层梯度
接下来将以全连接网络,softmax激活函数,softmax/MSE损失函数,介绍神经网络的梯度传播规律。
4.5.1 单神经网络梯度
如上图所示,即为一个单神经元的神经网络模型,其中上标(1)表示神经网络的第一层(只有一层)。该模型共有J个输入节点,w分别对应每个输入节点的权重。
从上边公式可以看出,误差对权值w的导数,只与真实值、预测值和当前输入有关。
4.5.2 全连接层梯度
把单个神经元的模型推广到多个神经元,就构成了如上所示全连接层的模型。多输出的全连接层和单神经元层的区别是,全连接层会有多个输出,对应过个真实标签。loss function可以表示如下:
现在求,由于它只与相关,此时i=k,因此:
其中sigmoid的导函数为 ,带入其中:
,其中只与x_j相关,所以 = 。最终可得:
另 ,可得。
接下来要尝试推到导数第二层的梯度传播方式,我们首先介绍一下导数传播的一个核心法则:链式法则。
4.6 链式法则
链式法则是不显示推到神经网络的数学表达式的前提下,逐层推到梯度的核心公式。复合函数。
在多元函数的情况下,:
在神经网络中,往往会有不同的网络层,每层都有各自的输入节点,输出节点。
现要求出:
在TensorFlow中可以轻松的实现神经网络的链式求导。
x = tf.random.uniform((3, 4), dtype = tf.float32)
w1 = tf.random.uniform((4, 5), dtype= tf.float32)
b1 = tf.random.uniform([5], dtype=tf.float32)
w2 = tf.random.uniform((5, 2), dtype=tf.float32)
b2 = tf.random.uniform([2], dtype=tf.float32)
with tf.GradientTape() as tape:
tape.watch([w1, b1, w2, b2])
y1 = x @ w1 + b1
y2 = y1 @ w2 + b2
tape.gradient(y2, [y1, w2, w1])
[<tf.Tensor: shape=(3, 5), dtype=float32, numpy=
array([[0.6315979, 1.1942216, 1.7914456, 1.1851715, 0.7441447],
[0.6315979, 1.1942216, 1.7914456, 1.1851715, 0.7441447],
[0.6315979, 1.1942216, 1.7914456, 1.1851715, 0.7441447]],
dtype=float32)>, <tf.Tensor: shape=(5, 2), dtype=float32, numpy=
array([[7.344633 , 7.344633 ],
[8.424996 , 8.424996 ],
[4.8777943, 4.8777943],
[7.8957777, 7.8957777],
[5.315347 , 5.315347 ]], dtype=float32)>, <tf.Tensor: shape=(4, 5), dtype=float32, numpy=
array([[1.4673182 , 2.7743967 , 4.161858 , 2.7533717 , 1.728785 ],
[0.75748134, 1.4322412 , 2.1484978 , 1.4213874 , 0.8924598 ],
[0.97064626, 1.8352923 , 2.7531126 , 1.8213841 , 1.1436093 ],
[1.2653594 , 2.3925343 , 3.589028 , 2.374403 , 1.4908385 ]],
dtype=float32)>]
tape可以分别求出y2对y1,w2,w1的导数。
4.7 反向传播算法
考虑如下神经网络,输出层有K个节点,倒数第二层有J个节点,倒数第三层有I个基点,输入层有N个节点。
其中
由于无关:
可以将 进一步拆分成:
:
现令:
令:
可以看出,通过定义,可以使梯度表达式变得更加清晰简洁。
下面进行一下总结:
输出层:
倒数第二层:
倒数第三层:
按照规律需要保存每个层每个节点的即可计算出最终的偏导数,再利用梯度下降法优化网络参数即可。
至此反向传播算法介绍完毕。