更多深度文章,请关注:https://yq.aliyun.com/cloud
一入侯门“深”似海,深度学习深几许(深度学习入门系列之一)人工“碳”索意犹尽,智能“硅”来未可知(深度学习入门系列之二)神经网络不胜语,M-P模型似可寻(深度学习入门系列之三)“机器学习”三重门,“中庸之道”趋若人(深度学习入门系列之四)Hello World感知机,懂你我心才安息 (深度学习入门系列之五)损失函数减肥用,神经网络调权重(深度学习入门系列之六)山重水复疑无路,最快下降问梯度(深度学习入门系列之七)
在神经网络(甚至深度学习)参数训练中,BP(Back Propagation)算法非常重要,它都占据举足轻重的地位。在提及BP算法时,我们常将它与杰弗里•辛顿(Geoffrey Hinton)的名字联系在一起。但实际上,辛顿还真不是第一个提出BP算法的人,就像爱迪生不是第一个发明电灯的人一样。但人们记住的,永远都是那个让电灯“飞入平常百姓家”的功勋人物爱迪生,而不是它的第一发明人美国人亨利·戈培尔(Henry Goebel)。如果说辛顿就是BP算法的“爱迪生”,那谁是BP算法的“戈培尔”呢?他就是保罗·沃伯斯(Paul Werbos)。1974年,沃伯斯在哈佛大学博士毕业。在他的博士论文里,首次提出了通过误差的反向传播来训练人工神经网络[1]。事实上,这位沃伯斯不光是BP算法的开创者,他还是循环神经网络(Recurrent Neural Network,RNN)的早期开拓者之一。在后期的系列入门文章中,我们还会详细介绍RNN,这里暂且不表。说到BP算法,我们通常强调的是反向传播,但其实呢,它是一个典型的双向算法。更确切来说,它的工作流程是分两大步走:(1)正向传播输入信号,输出分类信息(对于有监督学习而言,基本上都可归属于分类算法);(2)反向传播误差信息,调整全网权值(通过微调网络参数,让下一轮的输出更加准确)。下面我们分别举例,说明这两个流程。为了简化问题描述,我们使用如图8-1所示的最朴素三层神经网络。在这个网络中,假设输入层的信号向量是[-1, 1],输出层的目标向量为[1, 0],“学习率”η为0.1,权值是随机给的,这里为了演示方便,分别给予或“1”或“-1”的值。下面我们就详细看看BP算法是如何运作的?
图8-1 简易的三层神经网络正向传播信息,简单说来,就是把信号通过激活函数的加工,一层一层的向前“蔓延”,直到抵达输出层。在这里,假设神经元内部的激活函数为Sigmod(\( f(x) = frac{1}{{1 + {e^{ - x}}}}\))。之所以选用这个激活函数,主要因为它的求导形式非常简洁而优美:
$$ f'(x) = f(x)(1 - f(x))\tag{8.1} $$
事实上,类似于感知机,每一个神经元的功能都可细分两大部分:(1)汇集各路链接带来的加权信息;(2)加权信息在激活函数的“加工”下,神经元给出相应的输出,如图8-2所示。
图8-2 单个神经元的两部分功能于是,在正向传播过程中,对于\(f_1(e)\)神经元的更新如图8-3所示,其计算过程如下所示:
$$ \begin{array}{l} {f_1}(e) = {f_1}({w_{11}}{x_1} + {w_{21}}{x_2}) \\\\ {\rm{ }} = {f_1}(( - 1) \times 1 + 1 \times ( - 1)) \\\\ {\rm{ }} = {f_1}( - 2) \\\\ {\rm{ }} = \frac{1}{{1 + {e^{ - ( - 2)}}}} \\\\ {\rm{ }} = 0.12 \\\\ \end{array} $$
图8-3 神经元信息前向更新神经元1的f1(e) 接着,在同一层更新\(f_2(e)\)的值,过程和计算步骤类似于\(f_1(e)\),如图8-4所示:$$ \begin{array}{l} {f_2}(e) = {f_2}({w_{12}}{x_1} + {w_{22}}{x_2}) \\\\ {\rm{ }} = {f_2}(1 \times 1 + 1 \times ( - 1)) \\\\ {\rm{ }} = {f_2}(0) \\\\ {\rm{ }} = \frac{1}{{1 + {e^0}}} \\\\ {\rm{ }} = 0.5\\\\ \end{array} $$
图8-4 神经元信息前向更新神经元2的f2(e)接下来,信息正向传播到下一层(即输出层),更新神经元3的\(f_3(e)\)(即输出\(y_1\)的值),如图8-5所示。
$$ \begin{array}{l} {y_1} = {f_3}(e) = {f_3}({w_{13}}{f_1} + {w_{23}}{f_2}) \\\\ {\rm{ }} = {f_3}(1 \times 0.12 + 1 \times 0.5) \\\\ {\rm{ }} = {f_3}(0.62) \\\\ {\rm{ }} = \frac{1}{{1 + {e^{ - 0.62}}}} \\\\ {\rm{ }} = 0.65 \\\\ \end{array} $$
图8-5 神经元信息前向更新神经元3的f3(e) 然后,类似地,计算同在输出层求神经元\(f_4(e)\)(即输出\(y_2\))的值,如图8-6所示。$$ \begin{array}{l} {y_2} = {f_4}(e) = {f_4}({w_{14}}{f_1} + {w_{24}}{f_2}) \\\\ {\rm{ }} = {f_4}(( - 1) \times 0.12 + 1 \times 0.5) \\\\ {\rm{ }} = {f_4}(0.38)\\\\ {\rm{ }} = \frac{1}{{1 + {e^{ - 0.38}}}} \\\\ {\rm{ }} = 0.59 \\\\ \end{array} $$
图8-6神经元信息前向更新f4(e) 到此,在第一轮信号前向传播中,实际输出向量已计算得到\(y' = {[0.65,0.59]^T}\),但我们预期输出的向量(即教师信号)是\(y = {[1,0]^T}\),这二者之间是存在“误差”的。于是,重点来了,下面我们就用“误差”信息反向传播,来逐层调整网络参数。为了提高权值更新效率,这里就要用到下文即将提到的“反向模式微分法则(chain rule)”。(砰!砰!砰!敲黑板!请注意:如下部分是BP算法最为精妙之处,值得细细品味!)在前面信号正向传播的示例中,为了方便读者理解,我们把所有的权值都暂时给予了确定之值。而实际上,这些值都是可以调整的,也就是说其实它们都是变量,除掉图8-1中的所有确定的权值,把其视为变量,得就到更为一般化的神经网络示意图8-7。
图8-7 带权重变量的神经网络这里为了简化理解,我们暂时假设神经元没有激活函数(或称激活函数为\(y=x\)),于是对于隐含层神经元,它的输出可分别表示为:
$$ {f_1} = {x_1}{w_{11}} + {x_2}{w_{21}}\\\\ {f_2} = {x_1}{w_{12}} + {x_2}{w_{22}} $$
然后,对于输出层神经元有:
$$ \begin{array}{l} {f_3} = {f_1}{w_{13}} + {f_2}{w_{23}} \\\\ = ({x_1}{w_{11}} + {x_2}{w_{21}}){w_{13}} + ({x_1}{w_{12}} + {x_2}{w_{22}}){w_{23}} \\\\ = {x_1}{w_{11}}{w_{13}} + {x_2}{w_{21}}{w_{13}} + {x_1}{w_{12}}{w_{23}} + {x_2}{w_{22}}{w_{23}} \\\\ \end{array} $$
$$ \begin{array}{l} {f_4} = {f_1}{w_{14}} + {f_2}{w_{24}} \\\\ = ({x_1}{w_{11}} + {x_2}{w_{21}}){w_{14}} + ({x_1}{w_{12}} + {x_2}{w_{22}}){w_{24}} \\\\ = {x_1}{w_{11}}{w_{14}} + {x_2}{w_{21}}{w_{14}} + {x_1}{w_{12}}{w_{24}} + {x_2}{w_{22}}{w_{24}} \\\\ \end{array} $$
于是,损失函数\(L\)可表示为公式(8.2):
$$ L({w_{11}},{w_{12}},...,{w_{ij}},...,{w_{mn}}) \\\\ = \frac{1}{2}{({y_i} - {f_i}({w_{11}},{w_{12}},...,{w_{ij}},...,{w_{mn}}))^2}\tag{8.2} $$
这里\(Y\)为预期输出值向量(由\(y_1,y_2,...,y_i,...\)等元素构成),实际输出向量为\({f_i}({w_{11}},{w_{12}},...,{w_{ij}},...,{w_{mn}})\)。对于有监督学习而言,在特定训练集合下,输入元素\(x_i\)和预期输出\(y_i\)都可视为常量。由此可以看到,损失函数\(L\),在本质上,就是一个单纯与权值\(w_{ij}\)相关的函数(即使把原本的激活函数作用加上去,除了使得损失函数的形式表现得更加复杂外,并不影响这个结论)。于是,损失函数\(L\)梯度向量可表示为公式(8.3):
$$ \begin{array}{l} \nabla L = (\frac{{\partial L}}{{\partial {w_{11}}}},\frac{{\partial L}}{{\partial {w_{12}}}},...,\frac{{\partial L}}{{\partial {w_{mn}}}}) \\\\ {\rm{ }} = \frac{{\partial L}}{{\partial {w_{11}}}}{e_{11}} + \frac{{\partial L}}{{\partial {w_{12}}}}{e_{12}} + ... + \frac{{\partial L}}{{\partial {w_{mn}}}}{e_{mn}} \\\\ \end{array}\tag{8.3} $$
其中,这里的\(e_{ij}\)是正交单位向量。为了求出这个梯度,需要求出损失函数\(L\)对每一个权值\(w_{ij}\)的偏导数。BP算法之所以经典,部分原因在于,它是求解这类“层层累进”的复合函数偏导数的利器。为啥这么说呢?下面,我们就列举一个更为简化但不失一般性的例子来说明这个观点(以下案例参考了Chris Olah的博客文章[3])。假设有如下一个三层但仅仅包括\(a\)、\(b\)、\(c\)、\(d\)和\(e\)等5个神经元的网络,在传递过程中,\(c\)、\(d\)和\(e\)神经元对输入信号做了简单的加工,如图8-8所示。
图8-8 简易的神经网络假设变量\(a\)影响\(c\),此时我们想弄清楚\(a\)是如何影响\(c\)的。于是我们考虑这么一个问题,如果\(a\)变化一点点,那么\(c\)是如何变化的呢?我们把这种影响关系定义为变量\(c\)相对于变量\(a\)的偏导数,记做\(frac{{partial c}}{{partial a}}\)。利用高等数学的知识,我们很容易求得,对于直接相连的神经元(如\(a\)对\(c\),或\(b\)对\(d\)),可利用“加法规则”或“乘法规则”直接求出。例如,利用加法规则,\(frac{{partial c}}{{partial a}}\)可表示为:
$$ \frac{{\partial c}}{{\partial a}} = \frac{{\partial (a + b)}}{{\partial a}} = \frac{{\partial a}}{{\partial a}} + \frac{{\partial b}}{{\partial a}} = 1 $$
而对于表达式为乘法的求偏导规则为:
$$ \frac{\partial }{{\partial u}}uv = u\frac{{\partial v}}{{\partial u}} + v\frac{{\partial u}}{{\partial u}} = v $$
那么,对于间接相连的神经元,比如\(a\)对\(e\),如果我们也想知道\(a\)变化一点点时\(e\)变化多少,怎么办呢?也就是说,偏导数\(frac{{partial e}}{{partial a}}\)该如何求呢?这时,我们就需要用到链式法则了。这里假设\(a=2\),\(b=1\)。如果\(a\)的变化速率是1,那么\(c\)的变化速率也是1(因为\(frac{{partial c}}{{partial a}}=1\))。类似地,如果\(c\)的变化速率为1,那么\(e\)的变化速率为2(因为\(frac{{partial e}}{{partial c}}=d=2\))。所以相对于\(a\)变化1,\(e\)的变化为\(1 times 2 = 2\)。这个过程就是我们常说的“链式法则”,其更为形式化的表达为(如图8-9所示):
$$ \frac{{\partial e}}{{\partial a}} = \frac{{\partial e}}{{\partial c}} \cdot \frac{{\partial c}}{{\partial a}} = d \times 1 = 2 \times 1 = 2 $$
图8-9 链式法则示意图\(a\)对\(e\)的“影响”属于单路径的影响,还比较容易求得,但这并不是我们关注的重点。因为在神经网络中,神经元对神经元的连接,阡陌纵横,其影响也是通过多条路径“交织”在一起的。在图8-9中,我们研究一下\(b\)对\(e\)的影响,就能比较好理解这一工作机理。显然,\(b\)对\(e\)的影响,也可表达为一个偏导关系:
$$ \begin{array}{l} \frac{{\partial e}}{{\partial b}} = \frac{{\partial cd}}{{\partial b}} = d\frac{{\partial c}}{{\partial b}} + c\frac{{\partial d}}{{\partial b}} \\\\ = d \times 1 + c \times 1 \\\\ = 2 \times 1 + 3 \times 1 \\\\ = 5 \\\\ \end{array} $$
从图8-9可以看出,\(b\)对\(e\)影响,其实是“兵分两路”:(1)\(b\)通过影响\(c\),然后通过\(c\)影响\(e\);(2)\(b\)通过影响\(d\),然后通过\(d\)影响\(e\)。这就是多维变量(这里“多”仅为2)链式法则的“路径加和”原则。这个原则,看起来简单明了,但其实蕴藏着巨大代价。因为当网络结构庞大时,这样的“路径加和”原则,很容易产生组合爆炸问题。例如,在如图8-10所示的有向图中,如果\(X\)到\(Y\)有三条路径(即\(X\)分别以\(alpha\)、\(beta\)和\(chi \)的比率影响\(Y\)),\(Y\)到\(Z\)也有三条路径(\(Y\)分别以\(delta \)、\(varepsilon\)和\(xi \)的比率影响\(Z\))。
图8-10 路径加和规则演示于是,很容易根据路径加和原则得到\(X\)对\(Z\)的偏导数:
$$ \begin{array}{l} \frac{{\partial Z}}{{\partial X}} = (\alpha \delta + \alpha \varepsilon + \alpha \xi ) + \\\\ {\rm{ (}}\beta \delta + \beta \varepsilon + \beta \xi ) + \\\\ {\rm{ (}}\chi \delta + \chi \varepsilon + \chi \xi ) \\\\ \end{array}\tag{8.4} $$
上面用到的求偏导数方法,可称之为“前向模式微分(forward-mode differentiation)”,如图8-11-(a)所示。当网络结构简单时,即使\(X\)到\(Z\)的每一个路径都被“临幸(遍历)”一遍,总共才有 \(3 times 3 = 9\) 条路径,但一旦网络结构的复杂度上去了,这种“前向模式微分”,就会让求偏导数的次数和神经元个数的平方成正比。这个计算量,就很可能是成为机器“难以承受的计算之重”。有道是“东方不亮西方亮”。为了避免这种海量求导模式,数学家们另辟蹊径,提出了一种称之为“反向模式微分(reverse-mode differentiation)”。取代公式(8.4)的那种简易的表达方式,我们用公式(8.5)的表达方式来求\(X\)对\(Z\)的偏导:
$$ \frac{{\partial Z}}{{\partial X}} = (\alpha + \beta + \xi )(\delta + \varepsilon + \xi )\tag{8.5} $$
或许你会不屑一顾,这又是在搞什么鬼?把公式(8.4)恒等变换为公式(8.5)又有什么用呢?
图8-11 前向与反向微分方法对比你可别急,这背后大有玄机,且听我慢慢道来。前文提到的前向模式微分方法,其实就是我们在高数课堂上学习的求导方式。在这种求导模式中,强调的是某一个输入(比如\(X\))对某一个节点(如神经元)的影响。因此,在求导过程中,偏导数的分子部分,总是根据不同的节点总是不断变化,而分母则锁定为偏导变量“\(\partial X\)”,保持定不变(见图8-11-(a))。相比而言,反向模式微分方法则有很大不同。首先在求导方向上,它是从输出端(output)到输入端进行逐层求导。其次,在求导方法上,它不再是对每一条“路径”加权相乘然后求和,而是针对节点采纳“合并同类路径”和“分阶段求解”的策略。拿8-11-(b)的例子来说,先求\(Y\)节点对\(Z\)节点的"总影响"(反向第一层):
$$ \frac{{\partial Z}}{{\partial Y}} = \delta + \varepsilon + \xi $$
然后,再求节点\(X\)对节点\(Z\)的总影响(反向第二层):
$$ \frac{{\partial {\rm{Z}}}}{{\partial X}} = \frac{{\partial {\rm{Z}}}}{{\partial Y}}\frac{{\partial Y}}{{\partial X}} = (\delta + \varepsilon + \xi )(\alpha + \beta + \chi ) $$
特别需要注意的是,\( frac{{partial {rm{Z}}}}{{partial Y}} \)已经在第一层求导得到。在第二层仅仅需要求得\( frac{{partial {rm{Y}}}}{{partial X}} \),而\( frac{{partial {rm{Y}}}}{{partial X}} \) 容易求得\( (alpha + beta + chi ) \),然后二者相乘即可得到所求。这样一来,就大大减轻了第二层的求导负担。在求导形式上,偏导数的分子部分(节点)不变,而分母部分总是随着节点不同而变化,即\([frac{{partial Z}}{partial }]\)。这样说来说去,好像还是不太明白!下面我们还是用图8-9所示的原始例子,对比二者的求导过程,一遍走下来,有了感性认识,你就能明白其中的差异。为了进一步方便读者理解,我们将图8-9重新绘制为图8-12的样子。
图8-12 前向模式微分方法以求变量\(b\)偏导数的流程为例,我们先用前向模式微分法,来说明这种方法的求导过程。根据加法规则,对于求偏导值\(frac{{partial e}}{{partial b}}\)的步骤可以分两步走:(1)求得所有输入(包括\(a\)和\(b\))到终点\(e\)的每条路径上的偏导值乘积;(2)对所有条路径的导数值进行加和操作。从8-12所示的图中,对于两个输入\(a\)和\(b\),它们共有3条路径抵达终点\(e\)(分别计为①、②和③)。对于第①条路径而言,输入\(a\)对\(e\)的影响为:
$$ \frac{{\partial e}}{{\partial b}} = 0 \times 1 \times 1 \times 2 = 0 $$
对于第②条路径而言,输入\(b\)对\(e\)的影响为:
$$ \frac{{\partial e}}{{\partial b}} = 1 \times 1 \times 1 \times 2 = 2 $$
对于第③条路径而言,输入\(b\)对\(e\)的影响为:
$$ \frac{{\partial e}}{{\partial b}} = 1 \times 1 \times 1 \times 3 = 3 $$
所以在整体上,输入\(a\)和\(b\)从三条路径上对e施加的“总影响”为:0+2+3=5.或许读者已经注意到了,有些路径已经被冗余遍历了,比如在图8-12所示中,\(a to c to e\)(第①条路)和\(b to c to e\)(第②条路)就都走了路径\(c to e\)。这些局部性的冗余也就罢了,更为“恶劣”的是,对于求\(frac{{partial e}}{{partial a}}\),上述三条路径,它们同样还得“一个都不能少”地跑一遍,如果求得的变量多了,这到底得有多少冗余啊!
可能你会疑问,对于\(frac{{partial e}}{{partial b}}\)(\(frac{{partial e}}{{partial a}}\)),第①(③)条路径明明可以不走的嘛?这种明智,是对人的观察而言的,而且是对于简单网络而言的。因为,如果网络及其复杂,人们可能就没有这么“慧眼识珠”,识别其中的路径冗余。此外,对于计算机而言,有些时候,局部操作的“优化”,相对于整体操作的“规范化”,顶多算得上“奇淫巧技”,其优势可谓是“荡然无存”。有过大规模并行编程经验的读者,可能对这个观点会有更深的认知。
然而,同样是利用链式法则,反向模式微分方法就能非常机智地避开了这种冗余(下面即将讲到的BP算法,正是由于这么干,才有其优势的)。在这种方法中,它能做到对每一条路径只“临幸”一次,这是何等的节俭!下面我们来看看它是如何工作的。
图8-13反向模式微分方法相比于“前向模式微分法”是以输入(如图8-13-(a)所示的\(a\)和\(b\))为锚点,正向遍历每一条可能的路径。反向模式微分法是以节点(或说神经元,如图8-13-(a)所示的\(e\)、\(c\)和\(d\))为锚点,逐层分阶段求导,然后汇集每一个节点的外部路径(合并同类路径)。如图8-13-(b)所示,在反向求导的第1层,对于节点\(c\)有:
$$ \frac{{\partial e}}{{\partial c}} = \frac{{\partial (c \times d)}}{{\partial c}} = d = 2 $$
类似的,对于节点\(d\)有:
$$ \frac{{\partial e}}{{\partial d}} = \frac{{\partial (c \times d)}}{{\partial d}} = c = 3 $$
在阶段性的求解完毕第一层的导数之后,下面开始求解第二层神经元变量的偏导。如图8-13-(c)所示,在反向第2层,对于节点\(a\)有如图8-14-(Ⅰ)所示的求导模式。
图8-14反向模式微分方法的推演特别需要注意的是,8-14-(Ⅰ)所示的表达式“\(frac{{partial e}}{{partial c}}frac{{partial c}}{{partial a}}\)”中左部“\(frac{{partial e}}{{partial c}}\)”,已经在第1层求解过了,并“存储”在神经元\(c\)中。此时,采用“拿来主义”,拿来就能用!这就是反向模式微分的精华所在!
类似地,在反向求导第2层,对于节点\(b\),由于它汇集“两路兵马”的影响,所以需要合并同类路径,有如图8-14-(Ⅱ)所示结果。这样一来,如图8-13反向模式微分方法,每个路径仅仅遍历一次,就可以求得所有输出(如\(e\)节点)对输入(如\(a\)或\(b\)节点)的偏导,干净利落,没有任何冗余!在第七章中,我们曾提到,“BP算法把网络权值纠错的运算量,从原来的与神经元数目的平方成正比,下降到只和神经元数目本身成正比。”其功劳,正是得益于这个反向模式微分方法节省的计算冗余
有了前面“链式求导”的知识铺垫,下面我们就来细细讲解误差反向传播的过程。鉴于我们的系列文章是写给初学者(实践者)看的,下面我们尽量省略了其中较为复杂的推导公式,对该部分感兴趣的读者可参阅卡内基梅隆大学Tom Mitchell教授的经典著作《机器学习》[3](对公式不感冒的读者,第一遍阅读可以直接跳过公式,直达图文解释部分)。
误差反向传播通过梯度下降算法,迭代处理训练集合中的样例,一次处理一个样例。对于样例\(d\),如果它的预期输出(即教师信号)和实际输出有“误差”,BP算法抓住这个误差信号\(L_d\),以“梯度递减”的模式修改权值。也就是说,对于每个训练样例\(d\),权值\({w_{ji}}\)的校正幅度为\(Delta {w_{ji}}\)(需要说明的是,\({w_{ji}}\)和\({w_{ij}}\)其实都是同一个权值,\({w_{ji}}\)表示的是神经元\(j\)的第\(i\)个输入相关的权值,这里之所以把下标“\(j\)”置于“\(i\)”之前,仅仅表示这是一个反向更新过程而已):
$$ \Delta {w_{ji}} = - \eta \frac{{\partial {L_d}}}{{\partial {w_{ji}}}}\tag{8.6} $$
在这里,\(L_d\)表示的是训练集合中样例\(d\)的误差,分解到输出层的所有输出向量,\(L_d\)可表示为:
$$ {L_d}(w) = \frac{1}{2}\sum\limits_{j \in outputs} {({y_j} - y_{j}^{'}} {)^2}\tag{8.7} $$
其中:\(y_j\)表示的是第\(j\)个神经单元的预期输出值。\(y^{'}_{j}\)表示的\(j\)个神经单元的实际输出值。\(outputs\)的范围是网络最后一层的神经元集合。下面我们推导出\(frac{{partial {L_d}}}{{partial {w_{ji}}}}\)的一个表达式,以便在公式(8.7)中使用梯度下降规则。首先,我们注意到,权值\(w_{ji}\)仅仅能通过\(net_j\)影响其他相连的神经元。因此利用链式法则有:
$$ \begin{array}{l} \frac{{\partial {L_d}}}{{\partial {w_{ji}}}} = \frac{{\partial {L_d}}}{{\partial ne{t_j}}}\frac{{\partial ne{t_j}}}{{\partial {w_{ji}}}} \\ {\rm{ }} = \frac{{\partial {L_d}}}{{\partial ne{t_j}}}{x_{ji}} \\ \end{array}\tag{8.8} $$
在这里,\(ne{t_j} = sumnolimits_i {{w_{ji}}{x_{ji}}} \),也就是神经元\(j\)输入的加权和。\(x_{ji}\)表示的神经\(j\)的第\(i\)个输入。需要注意的是,这里的\(x_{ji}\)是个统称,实际上,在反向传播过程中,在经历输出层、隐含层和输入层时,它的标记可能有所不同。由于在输出层和隐含层的神经元对“纠偏”工作,承担的“责任”是不同的,至少是形式不同,所以需要我们分别给出推导。(1)在输出层,对第\(i\)个神经元而言,省略部分推导过程,公式(8.8)的左侧第一项为:
$$ \frac{{\partial {L_d}}}{{\partial ne{t_j}}} = ({y_j} - y_{j}^{'})y_{j}^{'}(1 - y_{j}^{'})\tag{8.9} $$
为了方便表达,我们用该神经元的纠偏“责任(responsibility)”\(delta _j^{(1)}\)描述这个偏导,即:
$$ \delta _{j}^{(1)} = \frac{{\partial {L_d}}}{{\partial ne{t_j}}} \tag{8.10} $$
这里\(delta _{j}^{(1)}\)的上标“(1)”,表示的是第1类(即输出层)神经元的责任。如果上标为“(2)”,则表示第2类(即隐含层)神经元的责任,见下面的描述。(2)对隐含层神经元\(j\)的梯度法则(省略了部分推导过程),有:
其中:\(f_j\)表示神经单元\(j\)的计算输出。\( net_j \)表示的是神经单元\(j\)的加权之和。\( Downstream(j) \)表示的是在网络中神经单元\(j\)的直接下游单元集合。隐含层神经元的纠差职责,是通过计算前一步输出神经元的“责任”来实现的。这里说的每层神经元“责任”,或者更为确切来说是“纠偏责任”,其实就是在8.2.2节讲到的“分阶段求解”策略。在明确了各个神经元“纠偏”的职责之后,下面就可以依据类似于感知机学习,通过如下加法法则更新权值:对于输出层神经元有:
$$ w_{ji}^{(1)} = w_{ji}^{(1)} + \eta \Delta w_{ji}^{(1)} \\\\ = w_{ji}^{(1)} + \eta \delta _i^{(1)}{h_j} \tag{8.12} $$
对于隐含层神经元有:
在这里,\(eta in (0,1)\)表示学习率。在实际操作过程中,为了防止错过极值,\(eta\)通常取小于0.1的值。\(h_j\)为神经元j的输出。\(x_{jk}\)表示的是神经单元\(j\)的第\(k\)个输入。上面的公式依然比较抽象,难以理解。下面我们还是以前面的神经网络拓扑结构为例,用实际运算过程给予详细说明,以期望给与读者感性认识。从上面的描述可以得知,针对输出层的神经元3,它的输出值\(y_{1}^{'}\)为0.65,而期望输出值\(y_1\)为1,二者存在“误差”:\({ e_1} = {y_1} - y_{1}^{'} = 1 - 0.65 = 0.35 \)。在这里,我们把每个神经元根据误差调参的“责任”记为\(delta \),那么,根据公式(8.9)和(8.10),神经元3的“责任”可表示为:
图8-15 误差反向传播计算神经元3的“责任”从上面分析可知,我们很容易计算出\( delta _{3}^{(1)} \)的值:
于是,可以反向更新\(w_{31}^{(1)}\)的权值为:
在这里,(具体推导见公式8.8-8.10),\(eta \)为学习率,此处取值为0.1。\(f_1\)为神经元1的输出(即\(y_{1}^{'}\))。类似地,我们可以反向更新\(w_{_{32}}^{(1)}\))的权值:
$$ \begin{array}{l} w_{32}^{(1)} = w_{32}^{(1)} + \eta \Delta w_{32}^{(1)} \\\\ {\rm{ }} = w_{32}^{(1)} + \eta \delta _3^{(1)}{f_2} \\\\ {\rm{ }} = 1 + 0.1 \times 0.0796 \times 0.5 \\\\ {\rm{ }} = 1.00398 \\\\ \end{array} $$
同样的操作,我们可以计算出\(delta _4^{(1)}\)的值,如图8-16所示。
图8-16 误差反向传播计算神经元4的责任从而可以反向更新\(w_{41}^{(1)}\)的权值:
$$ \begin{array}{l} w_{41}^{(1)} = w_{41}^{(1)} + \eta \Delta w_{41}^{(1)} \\\\ {\rm{ }} = w_{41}^{(1)} + \eta \delta _4^{(1)}{f_1} \\\\ {\rm{ }} = - 1 + 0.1 \times ( - 0.1427) \times 0.12 \\\\ {\rm{ }} = - 1.0017 \\\\ \end{array} $$
类似地,我们可以反向更新\(w_{42}^{(1)}\)的权值:
$$ \begin{array}{l} w_{42}^{(1)} = w_{42}^{(1)} + \eta \Delta w_{42}^{(1)} \\\\ {\rm{ }} = w_{42}^{(1)} + \eta \delta _4^{(1)}{f_2} \\\\ {\rm{ }} = 1 + 0.1 \times ( - 0.1427) \times 0.5 \\\\ {\rm{ }} = 0.9929 \\\\ \end{array} $$
在反向更新完毕输出层的权值后,下面。我们开始反向更新隐含层的网络权值,示意图如图8-17所示。
图8-17 误差反向传播计算神经元1的责任如果我们把反向传播误差的“职责(即\({delta _j}\))”,也看做一种特殊信息的话,那么在隐藏层的每个神经元都会有一个加权和影响,记为\({Delta _j}\),实际上,这里的\({Delta _j}\),就是公式(8.11)的加权求和\(Downstream(j)\)(其实也就是8.2.2所提及的“合并同类路径”)。对于隐含层神经1,则有:
有了这个权值影响,我们就可以很容易计算出神经元1承担的“责任”\(delta _{_1}^{(2)}\):
$$ \begin{array}{l} \delta _{_1}^{(2)} = {f_1}(1 - {f_1}){\Delta _1} \\\\ = 0.12 \times (1 - 0.12) \times 0.2223 \\\\ = - 0.0235 \\\\ \end{array} $$
在计算出计算神经元1承担的“责任”之后,我们就可以更新与神经元1相连的两个输入变量权值:
类似的流程(示意图如图8-18所示),可以求得神经元2的累计加权影响有:
图8-18误差反向传播计算神经元2的责任于是,计算神经元2承担的“责任”\(delta _2^{(2)}\):
$$ \begin{array}{l} \delta _{_2}^{(2)} = {f_2}(1 - {f_2}){\Delta _2} \\\\ = 0.5 \times (1 - 0.5) \times ( - 0.0631) \\\\ = 0.0158 \\\\ \end{array} $$
同样,计算出计算神经元2承担的“责任”之后,我们就可以更新与神经元2相连的两个输入变量权值:
从上面的推导过程,我们可以看到,经过一轮的误差逆传播,神经网络的权值前后确有不同。但由于学习率(即步长)较小(为0.1),所以前后的权值变化并不大(括号内的数值为原始权值),如图8-19所示。
图8-19 一轮BP算法之后前后的权值对比如此一来,整个网络的权值就全部得以更新。接下来,网络就可接受下一个训练样例,接着往下训练了,直到输出层的误差小于预设的容忍度。BP算法,在很多场所的确非常有用。例如,1989年,Yann Lecun(又一位当下的深度学习大牛)等人就用BP算法,在手写邮政编码识别上有着非常成功的应用[5],训练好的系统,手写数字错误率只有5%。Lecun借此还申请了美国专利,开了公司,发了一笔小财。在发表BP算法之后的30年,2006年,辛顿等人在发表的有关“深度信念网”的经典论文中指出[8],深度信念网(DBN)的组成元件就是受限玻尔兹曼机 (Restricted Boltzmann Machines, RBM)。而DBN的构建其实分两步走的:(1)单独“无监督”地训练每一层RBM网络,以确保特征向量在映射到不同特征空间时,能够尽可能多地保留特征信息;(2)在DBN的最后一层,设置BP网络,用以接收RBM的输出特征向量作为它的输入特征向量,然后“有监督”地训练实体关系分类器,对网络权值实施微调(Fine-Tune)。现在你看到了,BP算法的影响力,一直渗透到“深度学习”骨子里!这就是为什么在讲深度学习时,我们绕不过BP算法的原因。
在本章中,我们详细解释了反向传播(BP)算法,通过学习我们知道,BP算法其实并不仅仅是个反向算法,而是一个双向算法。也就是说,它其实是分两大步走:(1)正向传播信号,输出分类信息;(2)反向传播误差,调整网络权值。如果没有达到预期目的,重走回头路(1)和(2)。BP算法很成功。但我们也要看到BP算法的不足,比如说会存在“梯度扩散(Gradient Diffusion)”现象,其根源在于对于非凸函数,梯度一旦消失,就没有指导意义,导致它可能限于局部最优。而且“梯度扩散”现象会随着网络层数增加而愈发严重,也就是说,随着梯度的逐层消减,导致它对调整网络权值的调整效益,作用越来越小,故此BP算法多用于浅层网络结构(通常小于等于3),这就限制了BP算法的数据表征能力,从而也就限制了BP的性能上限。再比如说,虽然相比于原生态的BP算法,虽然它降低了网络参数的训练量,但其网络参数的训练代价还是不小,耗时非常“可观”。就拿LeCun的识别手写邮编的案例说事,其训练耗时就达3天之久。再后来,与LeCun同在一个贝尔实验室的同事Vladimir Vapnik(弗拉基米尔·万普尼克),提出并发扬光大了支持向量机 (Support Vector Machine) 算法。SVM作为一种分类算法,对于线性分类,自然不在话下。在数据样本线性不可分时,它使用了所谓“核机制(kernel trick)”,将线性不可分的样本,映射到高维特征空间 (high-dimensional feature space),从而使其线性可分。自上世纪九十年代初开始,SVM逐渐大显神威,在图像和语音识别等领域,获得了广泛而成功的应用。在手写邮政编码的识别问题上,LeCun利用BP算法,把错误率整到5%左右,而SVM在1998年就把错误率降到低至0.8%。这远超越同期的传统神经网络算法。就这样,万普尼克又把神经网络研究送到了一个新的低潮! 在下一章中,我们将来聊聊“卷积神经网络”,这可是深度学习发展过程中的一个重要里程碑,希望你能关注。
通过本章的学习,请你思考如下问题:(1)正是由于神经网络具有强大的表示能力,“成也萧何,败也萧何”,BP算法常常遭遇“过拟合(overfitting)”,它可能会把噪音当做有效信号,你知道有什么策略来避免过拟合吗?(2)利用梯度递减策略,BP算法常停止于局部最优解,而非全局最优解,这好比“只因身在此山中”,却不知“人外有人,山外有山”,你知道有什么方法可以改善这一状况吗?(3)在BP算法流程中,我们看到的是反反复复的权值调整,而杰弗里•辛顿在文献[4]中提到的特征表示(representation),体现在什么地方?
[1] Paul J. Werbos. Beyond Regression: New Tools for Prediction and Analysis in the Behavioral Sciences. PhD thesis, Harvard University, 1974[2] Christopher Olah. Calculus on Computational Graphs: Backpropagation [3]Tom Mitchell著.曾华军等译. 机器学习. 机器工业出版社. 2007.4[4] Williams D, Hinton G. Learning representations by back-propagating errors[J]. Nature, 1986, 323(6088): 533-538.[5] LeCun Y, Boser B, Denker J S, et al. Backpropagation applied to handwritten zip code recognition[J]. Neural computation, 1989, 1(4): 541-551.[6]周志华. 机器学习. 清华大学出版社. 2016.1[7] Mirosav Kubat著. 王勇等译. 机器学习导论. 机械工业出版社. 2016.11[8] Hinton G E, Osindero S, Teh Y W. A fast learning algorithm for deep belief nets[J]. Neural computation, 2006, 18(7): 1527-1554.
文章作者:张玉宏,著有《品味大数据》一书。节选自《深度学习之美》(2018年7月电子工业出版社出版)审校:我是主题曲哥哥。
---一入侯门“深”似海,深度学习深几许(深度学习入门系列之一)人工“碳”索意犹尽,智能“硅”来未可知(深度学习入门系列之二)神经网络不胜语, M-P模型似可寻(深度学习入门系列之三)“机器学习”三重门,“中庸之道”趋若人(深度学习入门系列之四)Hello World感知机,懂你我心才安息(深度学习入门系列之五)损失函数减肥用,神经网络调权重(深度学习入门系列之六)山重水复疑无路,最快下降问梯度(深度学习入门系列之七)BP算法双向传,链式求导最缠绵(深度学习入门系列之八)全面连接困何处,卷积网络见解深(深度学习入门系列之九)卷地风来忽吹散,积得飘零美如画(深度学习入门系列之十) 局部连接来减参,权值共享肩并肩(深度学习入门系列之十一)
##(未完待续) 相关资源:七夕情人节表白HTML源码(两款)