tf2.0学习(四)——反向传播算法

2021-05-24  本文已影响0人  雪糕遇上夏天

我们接着之前的内容
tf2.0学习(一)——基础知识
tf2.0学习(二)——进阶知识
tf2.0学习(三)——神经网络

反向传播

开始后边的新内容:反向传播算法(BP算法)
误差的反向传播,是神经网络中最核心的算法之一。虽然各深度学习框架都为我们提供了自动求导,自动更新参数的功能,模型设计者几乎不需要深入了解BP算法也能设计出复杂网络。但是BP算法作为神经网络的核心算法,深入理解其工作原理十分重要。

4.1 导数与梯度

导数的定义为,自变量x产生一个微小的扰动\bigtriangleup x之后,函数输出值的增量\bigtriangleup y与自变量增量\bigtriangleup x趋近于0时的极限a,如果存在a即为x_0处的导数:
a = lim_{\bigtriangleup x->0} \frac{\bigtriangleup y}{\bigtriangleup x} = lim_{\bigtriangleup x->0} \frac{f(x + \bigtriangleup x) - f(x)}{\bigtriangleup x}
导数记作f^{'}(x)
从几何角度看,一元函数在某处的导数,就是函数的切线在此处的斜率,即函数值沿着x方向的变化率。偏导数是导数在多元函数上的推广。
在神经网络中x一般表示为输入,网络的自变量是网络参数集\theta = (w_1, b_1, w_2, b_2, ... w_n, b_n )
利用梯度下降算法优化神经网络时,需要求出输出函数对所有单数的偏导数。
损失函数对所有参数的偏导数可以记作如下向量:
\triangledown_{\theta} L_{loss} = \frac{L_{loss}}{\partial \theta} = (\frac{\partial L_{loss}}{\partial \theta_1}, \frac{\partial L_{loss}}{\partial \theta_2}, ...\frac{\partial L_{loss}}{\partial \theta_n})
此时梯度下降的参数更新公式如下:
\theta_{i+1} = \theta_i - \eta \triangledown_{\theta} L_{loss}
(\frac{\partial L_{loss}}{\partial \theta_1}, \frac{\partial L_{loss}}{\partial \theta_2}, ...\frac{\partial L_{loss}}{\partial \theta_n})这个由所有偏导数组成的向量叫做函数的梯度,它所表征的是函数值上升最快的方向,梯度的反方向则是函数值下降最快的方向。
梯度下降算法并不能保证在所有情况下都能找到全局最优解,这主要是由于目标函数可能非凸导致的。
神经网络的模型表达,往往需要数亿级别的参数。深度学习的框架帮我们实现的最主要任务之一就是反向传播算法和梯度下降算法。

4.2 导数的常见性质

4.2.1 基本函数的导数

4.2.2 常用导数的性质

4.3 激活函数的导数

4.3.1 Sigmoid函数导数

\sigma(x) = \frac{1}{1+e^{-x}}
\frac{d\sigma(x)}{dx} = \frac{d}{dx}(\frac{1}{1+e^{-x}})
= \frac{e^{-x}}{(1+e^{-x})^2}
=\frac{1+e^{-x}-1}{(1+e^{-x})^2}
=\frac{1+e^{-x}}{(1+e^{-x})^2} - \frac{1}{(1+e^{-x})^2}
=\sigma(x) - \sigma(x)^2
=\sigma(1-\sigma)
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函数导数

ReLU(x) = max(0, x)
\frac{d}{dx}(ReLU(x)) = \left\{ \begin{array}{**lr**} 1 \quad(x>=0), & \\ 0 \quad(x<=0) \end{array} \right.
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 函数导数

LeakyReLU(x) = max(0, x)
\frac{d}{dx}(LeakyReLU(x)) = \left\{ \begin{array}{**lr**} 1 \quad(x>=0), \\ p \quad(x<=0) \end{array} \right.
python代码实现如下:

def derivative(x): # LeakyReLU函数的导数
    d = np.ones_like(x, copy=True) # 用于保存梯度的张量 
    d[x < 0] = p # 元素为负的导数为p
    return d

4.3.4 Tanh函数导数

tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}
= 2 sigmoid(2x) - 1

\frac{d}{dx}tanh(x) = \frac{(e^x + e^{-x})(e^x + e^{-x}) - (e^x - e^{-x})(e^x - e^{-x})}{(e^x + e^{-x})^2}
=1-tanh(x)
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 均方误差函数梯度

均方误差的损失函数如下:
\ell = \frac{1}{2}\sum_{i=0}^{n}(y_i - o_i)^2
他的偏导数\frac{\partial \ell}{\partial o_k}可以表示为:
\frac{\partial \ell}{\partial o_k} = \frac{1}{2} \sum_{i=0}^n -2(y_i - o_i) \frac{\partial o_i}{\partial o_k}
当且仅当 k=i时,\frac{\partial o_i}{\partial o_k} = 1,也就是说偏导数\frac{\partial o_i}{\partial o_k}只与第k号节点相关,与其他节点都不相关,值为0。
\frac{\partial \ell}{\partial o_k} = -(y_i - o_i)

4.4.2 交叉熵函数梯度

在计算交叉熵损失函数的时候,一般将Softmax函数与交叉熵函数统一实现。所以我们先推到Softmax函数的梯度,再推到交叉熵函数的梯度。
首先看Softmax函数:
p_i = \frac{e^{x_i}}{\sum_{k=1}^{K} e^{x_k}}
回顾前边提到的,函数相除的导数:
f(x) = \frac{g(x)}{h(x)}
f^{'}(x) = \frac{g^{'}(x)h(x) - g(x)h^{'}(x)}{h^2(x)}
那么,在softmax函数中,g(x) = e^{x_i}, h(x) = \sum_{k=1}^{K} e^{x_k}
当j = i时:
\frac{\partial p_i}{\partial x_j} = \frac{\partial}{\partial x_j} \frac{e^{x_i}}{\sum_{k=1}^{K} e^{x_k}} =\frac{e^{x_i}\sum_{k=1}^{K} e^{x_k} - e^{x_i} e^{x_j}}{(\sum_{k=1}^{K} e^{x_k})^2}
=\frac{e^{x_i}(\sum_{k=1}^{K} e^{x_k} - e^{x_i})}{(\sum_{k=1}^{K} e^{x_k})^2}
= p_i(1-p_i)
当 j 不等于 i 时:
\frac{\partial p_i}{\partial x_j} = \frac{\partial}{\partial x_i} \frac{e^{x_i}}{\sum_{k=1}^{K} e^{x_k}} = \frac{0 - e^{x_i}e^{x_j}}{(\sum_{k=1}^{K} e^{x_k}) ^ 2}
= \frac{-e^{x_i}}{\sum_{k=1}^{K} e^{x_k}} \frac{e^{x_j}}{\sum_{k=1}^{K} e^{x_k}}
=-p_ip_j
综上所述,softmax函数的偏导数如下:
\frac{\partial p_i}{\partial x_j} = \left\{ \begin{array}{**lr**} p_i(1-p_i) \quad(i = j), \\ -p_ip_j \quad(i != j) \end{array} \right.
交叉熵损失函数的表达式如下:
L = - \sum_{j} y_jlog(p_j)
则:
\frac{\partial L}{\partial x_i} = -\sum_jy_j\frac{\partial log(p_j)}{\partial x_i}
= -\sum_j y_j \frac{\partial log(p_j)}{\partial p_j} \frac{\partial p_j}{\partial x_i}
=-\sum_j y_j \frac{1}{p_j} \frac{\partial p_j}{\partial x_i}
其中\frac{\partial p_j}{\partial x_i}即为我们前天推到的softmax函数的偏导数。
将前边结果带入上边公式中:
\frac{\partial L}{\partial x_i} = -y_i (1-p_i) + \sum_{j \neq i} y_j p_i
= -y_i + y_i p_i + \sum_{j \neq i} y_i p_i
= -y_i + p_i

4.5 全连接层梯度

接下来将以全连接网络,softmax激活函数,softmax/MSE损失函数,介绍神经网络的梯度传播规律。

4.5.1 单神经网络梯度

神经元模型
如上图所示,即为一个单神经元的神经网络模型,其中上标(1)表示神经网络的第一层(只有一层)。该模型共有J个输入节点,w分别对应每个输入节点的权重。
L = \frac{1}{2}(o_1 - t)^2
\frac{\partial L}{\partial w_{j1}} = \frac{\partial L}{\partial o_1} \frac{\partial o_1}{\partial z_{1}} \frac{\partial z_1}{\partial w_{j1}}
=(o_1 - t) \frac{\partial o_1}{\partial z_{1}} \frac{\partial z_1}{\partial w_{j1}}
=(o_1 - t)(\sigma(z_1)(1-\sigma(z_1))) \frac{\partial z_1}{\partial w_{j1}}
=(o_1 - t)(\sigma(z_1)(1-\sigma(z_1))) x_j
从上边公式可以看出,误差对权值w的导数,只与真实值、预测值和当前输入有关。

4.5.2 全连接层梯度

全连接层模型
把单个神经元的模型推广到多个神经元,就构成了如上所示全连接层的模型。多输出的全连接层和单神经元层的区别是,全连接层会有多个输出o_1, o_2, ... o_k,对应过个真实标签t_1, t_2, ... t_k。loss function可以表示如下:
L = \frac{1}{2} \sum_{i=0}^{K} (o_k - t_k)^2
现在求\frac{\partial L}{\partial w_{jk}},由于它只与o_k相关,此时i=k,因此:
\frac{\partial L}{\partial w_{jk}} = (o_k - t_k) \frac{\partial o_k}{\partial z_k} \frac{\partial z_k}{\partial w_{jk}}
其中sigmoid的导函数为 \sigma (1-\sigma),带入其中:
= (o_k - t_k) o_k(1-o_k) \frac{\partial z_k}{\partial w_{jk}},其中\frac{\partial z_k}{\partial w_{jk}}只与x_j相关,所以 = x_j。最终可得:
= (o_k - t_k)o_k(1-o_k)x_j
\delta_k = (o_k - t_k)o_k(1-o_k),可得\frac{\partial L}{\partial w_{jk}} = \delta_kx_j
接下来要尝试推到导数第二层的梯度传播方式,我们首先介绍一下导数传播的一个核心法则:链式法则。

4.6 链式法则

链式法则是不显示推到神经网络的数学表达式的前提下,逐层推到梯度的核心公式。复合函数y = f(u), u = g(x), \frac{\partial y}{\partial x} = \frac{\partial f}{\partial u} \frac{\partial u}{\partial x}
在多元函数的情况下,z = f(x, y), x = g(t), y = h(t):
\frac{\partial z}{\partial t} = \frac{\partial z}{\partial x} \frac{\partial x}{\partial t} + \frac{\partial z}{\partial y} \frac{\partial y}{\partial t}
在神经网络中,往往会有不同的网络层,每层都有各自的输入节点,输出节点。

各层梯度传播示意图
现要求出\frac{\partial L}{\partial w_{ij}}
\frac{\partial L}{\partial w_{ij}} = \frac{\partial L}{\partial o_k}\frac{\partial o_k}{\partial o_j} \frac{\partial o_j}{\partial w_{ij}}
在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个节点。

反向传播算法
\frac{\partial L}{\partial w_{ij}} = \frac{\partial \frac{1}{2} \sum_{k=0}^{K}(o_k - t_k)^2}{\partial w_{ij}}
= \sum_{k=0}^{K}(o_k - t_k) \frac{\partial o_k}{\partial w_{ij}}
= \sum_{k=0}^{K}(o_k - t_k) \frac{\partial o_k}{\partial z_k} \frac{\partial z_k}{\partial w_{ij}}
=\sum_{k=0}^{K}(o_k - t_k)o_k(1-o_k) \frac{\partial z_k}{\partial w_{ij}}
= \sum_{k=0}^{K}(o_k - t_k) o_k (1-o_k)\frac{\partial z_k}{\partial o_j}\frac{\partial o_j}{\partial w_{ij}}
其中\frac{\partial z_k}{\partial o_j} = w_{jk}
= \sum_{k=0}^{K}(o_k - t_k)o_k(1-o_k)w_{jk}\frac{\partial o_j}{\partial w_{ij}}
由于\frac{\partial o_j}{\partial w_{ij}}与k无关:
=\frac{\partial o_j}{\partial w_{ij}} \sum_{k=0}^{K} (o_k - t_k)o_k(1-o_k)w_{jk}
可以将\frac{\partial o_j}{\partial w_{ij}} 进一步拆分成o_j(1-o_j)\frac{\partial z_j}{\partial w_{ij}}:
=o_j(1-o_j)\frac{\partial z_j}{\partial w_{ij}} \sum_{k=0}^{K}(o_k - t_k)o_k(1-o_k)w_{jk}
\frac{\partial z_j}{\partial w_{ij}} = o_i:
=o_j(1-o_j)o_i \sum_{k=0}^{K}(o_k - t_k)o_k(1-o_k)w_{jk}
现令(o_k-t_k)o_k(1-o_k) = \delta_k:
= o_j(1-o_j)o_i \sum_{k=0}^{K}\delta_kw_{jk}
\delta_j = o_j(1-o_j)\sum_{k=0}^{K}\delta_kw_{jk}:
= \delta_jo_i
可以看出,通过定义\delta,可以使梯度表达式变得更加清晰简洁。
下面进行一下总结:
输出层:
\frac{\partial L}{\partial w_{jk}} = \delta_ko_j
\delta_k = o_k(1-o_k)(o_k - t_k)
倒数第二层:
\frac{\partial L}{\partial w_{ij}} = \delta_jo_i
\delta_j = o_j(1-o_j)\sum_{k=0}^{K}\delta_kw_{jk}
倒数第三层:
\frac{\partial L}{\partial w_{ni}} = \delta_io_n
\delta_i = o_n(1-o_n)\sum_{j=0}^{J}\delta_jw_{nj}
按照规律需要保存每个层每个节点的\delta即可计算出最终的偏导数,再利用梯度下降法优化网络参数即可。
至此反向传播算法介绍完毕。
上一篇 下一篇

猜你喜欢

热点阅读