tf2.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 基本函数的导数

  • 常数函数 c,导数为0
  • 线性函数 y=ax+b, 导数为a
  • 幂函数 x^a,导数为 ax^{a-1}
  • 指数函数 a^x,导数为a_xln a
  • 对数函数 log_a x,导数为\frac{1}{xlna}

4.2.2 常用导数的性质

  • 函数相加 (f+g)^{'} = f^{'} + g^{'}
  • 函数相乘 (fg)^{'} = f^{'}g + fg^{'}
  • 函数相除 (\frac{f}{g})^{'} = \frac{f^{'}g - fg^{'}}{g^2}
  • 复合函数 f(u), u=g(x), \frac{df(g(x))}{dx} = \frac{df(u)}{du}\frac{dg(x)}{dx} = f^{'}(u) g^{'}(x)

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即可计算出最终的偏导数,再利用梯度下降法优化网络参数即可。
至此反向传播算法介绍完毕。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容