PDF 物理页索引 260
文件中的第 261 页 · 印刷页码:245
此外还有计算可行性方面的困难,神经网络每增加一个隐层,都会导致神经元连接数量的快速增长。通过第 4 章的学习我们知道,神经网络最后的输出值是由所有流经它的连接路径汇集,然后把连接路径上权值的加权和放到激活函数中运算得出的。每一条路径上的神经元权值都会对结果产生难以估量的影响,各神经元权值不能孤立地训练,但是如果每一组训练数据都牵涉全部可能的路径组合的话,这里面涉及的计算量就太大了,不要说当时,就算是今时今日的计算机硬件也难以直接通过暴力运算来解决。
多层神经网络难以训练,除了上面列出的两个从问题本身出发所遇到的困难外,还有另外一层外部原因:从某种意义上说,困难也是由当时人工智能冰河期的寒冷的学术气氛所带来的。无论是明斯基撰写《感知机》抨击多层感知机无法训练的时候,还是罗森布拉特带着问题和遗憾去世的时候,抑或是辛顿最后重新发现了可行的多层神经网络训练方法的时候,都没有人注意到,其实早已经有人提出过一种训练方法(后来被辛顿命名为“误差反向传播”算法),该方法可以很好地解决多层神经网络的训练问题。
1969 年,斯坦福大学电子工程系教授阿瑟· 布 莱 森(Arthur Bryson,1925—)在自动化控制领域以“多级动态系统优化方法”的名字提出了与现在的误差反向传播算法思路相同的方法。到了 1974 年,哈佛大学的一位博士生保罗·韦尔博斯(Paul Werbos,1947—)再次发现了这个训练算法,他甚至意识到了此算法的价值,在论文中明确指出了该算法可以应用于多层神经网络的训练,但是这个隐藏在一篇普通博士毕业论文中的重要成果没有得到应有的重视,后来连韦尔博斯自己也没有再继续这方面的任何研究了。在多年之后的一
第 7 章 深度学习时代
245
○一 对这部分仍存有疑问的读者,请参考第 6 章的相关内容。
PDF 物理页索引 261
文件中的第 262 页 · 印刷页码:246
次采访中,韦尔博斯这样说道:
“当时我认为,这种研究思路对解决感知机问题是有意义的,但是在人工智能寒冬中,这个圈子已经失去解决那些问题的信念了。”
自此以后,这个算法还在不断被人重复发现,例如戴维·帕克(DavidParker,1956—)与杨立昆曾在 1985 年的麻省理工学院的学报上发表过这个算法。但最终到 1986 年,辛顿与心理学家大卫·鲁梅尔哈特(David Rumelhart,1942—2011)在《自然》杂志上发表了论文《通过误差反向传播算法的学习表示》(“Learning Representations by Back-propagating Errors”)<sup>○一</sup>,此算法才真正引起了学术界的关注。在《自然》这种顶级科学杂志上,辛顿清晰地论证了误差反向传播算法是切实可操作的训练多层神经网络的方法,才得以彻底扭转了明斯基《感知机》一书带来的负面影响,终于令学术界普遍认可多层神经网络也是可以有效训练的,神经网络并不是没有前途的“炼金术”。直到此时,神经网络的“生存危机”才算是初步化解,误差反向传播算法对神经网络来说可谓期盼已久、能称作雪中送炭的重要成果,神经网络也由此踏出了它的漫长复兴之路的第一步。
7.4.2 从感知机到神经网络
从分类关系上讲,毫无疑问多层感知机属于神经网络中的一种,与今天广泛应用、可被误差反向传播算法训练的经典神经网络有许多相同的地方,也有一些差异,主要体现在以下几个方面。
第 一, 多 层 感 知 机 一 般 是 以“全 连 接 前 馈 神 经 网 络”(Fully ConnectFeedforward Neural Network)的形式出现的。全连接前馈神经网络结构如图 7-2 所示,其名字中的“前馈”(Feedforward)是指把若干个单层神经网络级联在一起,前一层的输出作为后一层的输入,构成一种最基础的多层神经网络。神经网络加上“前馈”这个定语的目的在于特别强调这样的网络是单向的、无反馈的,也就是说,位置靠后的神经元不会把输出反向连接到前面层次上的神经元作为输入。而“全连接”(Fully Connect)就很好理解了,它是指网络中的任意一个神经元与其上、下层的每一个神经元都有连接。“全连接”带来的主要好处是对网络的输入顺序没有要求,打乱输入顺序也不会对输出结果产生影响。
246
第三部分 第三波高潮
○一 这里还应当包括辛顿同年发表的另一篇更为深入的文章:《通过误差传播算法的机器学习内部表示》(“ Learning Internal Representations by Error Propagation ”)。
PDF 物理页索引 262
文件中的第 263 页 · 印刷页码:247
误差反向传播算法适用的神经网络范围要更大,它不仅可以用来训练多层感知机,也可以用来训练不是全连接的、不是前向的神经网络,例如循环神经网络就是具备反馈连接的,同样可以使用误差反向传播算法。
第二,多层感知机和现在大多数经典的神经网络所采用的激活函数不同,或者说,多层感知机中没有非线性激活函数的概念。为了能说清楚这一点,笔者先简单解释一下激活函数是什么。
从第 4 章我们知道了神经元的数据流动过程,简而言之就是把所有输入项先加权求和,然后放在网络输入阈值函数中进行比较判断,大于阈值则激活输出,否则就置零忽略输入信号。如果没有激活函数参与,中间的全部过程都将是线性操作,多层感知机最终的输出值只会由多个简单线性函数组合得到。所谓“线性函数”是指多元一次多项式,它在多维空间中的图像就是一个超平面。虽然线性方程是很容易求解的,但是它的表达能力有限,从数据中学习复杂函数映射所需的神经元将会更多。这个结论我们不必做严格推导,读者只需稍微想象一下就能理解:在一个二维平面中,不使用任何曲线,仅通过数量足够的直线段也是可以在任意精度要求上拟合任意图形的,只是要求的精度越高,需要的直线段数量相应就会越多。在更高维的空间上,使用超平面来拟合曲面形状也是同样的道理。
第 7 章 深度学习时代
247
PDF 物理页索引 263
文件中的第 264 页 · 印刷页码:248
“激活函数”(Activation Function,也常被译为“激励函数”)的作用就是给神经网络加入非线性的表达能力,它改进了阈值函数只能单纯比较大小来判断是否激发神经元信号的简单逻辑,允许根据实际需要,对输入的加权和进行不受限制的数学处理。图 7-3 列举了一些常见的激活函数,根据阈值函数的特性,我们可以认为多层感知机没有激活函数概念,也可以将它看成是采用了线性函数“UnitStep”(阶跃)作为激活函数的一种特殊神经网络。
今天在各种问题上被广泛应用的神经网络,基本都不再继续采用 UnitStep函数了,除了线性函数表达能力受限外,还有一个主要原因是 UnitStep 函数并不是一种可微函数<sup>○一</sup>,稍后我们会介绍,误差反向传播算法的理论基础是微积分里经典的“链式法则”,它被用来根据后面的网络输出的结果计算前面一层隐层的导数。由于链式法则本身决定了被训练网络的激活函数必须具备可微性,因此,即使原本用到 UnitStep 函数的场景,也一般会使用可微的 Sigmoid 函数来代替不可微的 UnitStep 函数。图 7-4 展示了这两种函数的图像,从中可以轻易地看出两个函数的异同之处。
248
第三部分 第三波高潮
○一 图 片 来 源:https://stats.stackexchange.com/questions/263768/can-a-perceptron-with-sigmoidactivation-function-perform-nonlinear-classificati/263816。
PDF 物理页索引 264
文件中的第 265 页 · 印刷页码:249
除了非线性和可微性之外,激活函数一般还要具备单调性,这是因为如果激活函数是单调的,那么使用这个激活函数的单层神经网络就能够保证等价于某个凸函数,我们前面介绍机器学习训练过程时曾经提到,凸函数是相对易于优化的。当然,现在非凸优化已成为神经网络的一个前沿热点,但是在相同性能下,尽量避免非凸函数仍然是值得推荐的。
相对于现在深度学习中卷积网络、循环网络、长短期记忆网络等各种类型的神经网络模型,全连接前馈网络是一种较为简单的多层神经网络。然而,即使是这种简单的神经网络,即使仅使用一个隐层,也已经具有相当强大的表达力了。数学上已有定理证明,即使只有一个隐层的前馈神经网络(其中用作证明的例子就是多层感知机),也同样具备了模拟任何函数图像的强大表现能力<sup>○二</sup>。误差反向传播算法最初提出的目标,就是面向这种多层前馈神经网络,给出一种计算量在可接受范围内的训练机制。辛顿在 1986 年的论文中,将这种可被误差反向传播算法训练的前馈全连接神经网络称为“反向传播神经网络”(即“ Back Propagation 神经网络”或者“ BP 神经网络”)。今天误差反向传播算法可应用的范围已经拓展至大量其他形式的网络模型上,但这个命名沿用了下来,以指代前馈全连接神经网络。
第 7 章 深度学习时代
249
○一 在微积分中,可微函数是指那些在定义域中所有点都存在导数的函数。可微函数的图像在定义域内的每一点上必存在非垂直切线。因此,可微函数的图像是相对光滑的,没有间断点、尖点或任何有垂直切线的点。
○二 稍后介绍深度学习还会提及这是一个纯粹“理论上”的结论,要表达同样的函数,减少网络的层次可能会使所需的神经元数量呈指数级增加,因此,目前主流的方法是采用层次更多(更深)的模型来实现。
PDF 物理页索引 265
文件中的第 266 页 · 印刷页码:250
7.4.3 误差反向传播算法
用一节篇幅去介绍一个算法细节,无论是从本书的主旨还是一贯的行文风格来说,都显得有些格格不入。但误差反向传播算法在神经网络领域实在是举足轻重,而且极有讨论价值:一方面,在今天的神经网络研究学习资料里,它是无可争议的“必修课程”,只要是涉及神经网络的教科书,它都必定占有一席之地;另一方面,随着深度学习的兴起,误差反向传播算法现在又处在风口浪尖上,以它的发明人辛顿为首的一批学者,正在考虑“另起炉灶”,彻底抛弃误差反向传播算法。只有在本节讲清楚这个算法的内容,后面才能说明白它在深度学习时代的不足之处,为什么它的发明人现在要抛弃它。基于这些原因,笔者不打算只讲原理和作用,而是准备从它所解决的问题出发,把它的思路和细节都详细介绍一下。
这部分不可避免地要涉及一些微积分的基础知识,如果读者对这部分不关注,可以略过其中的公式和推导过程,仅从文字上领略这个算法的思路和来龙去脉。但假如读者是这个方向的开发或者研究人员的话,那这个算法的推导过程恐怕必须死磕,绕不过去。
假如我们换一种理解方式来看待神经网络,把网络的每一层都使用一个函数符号来表示的话,那不难发现,多层神经网络就等价于多个函数的逐层嵌套。关于求嵌套函数导数的方法,计算机领域中已经有了成熟的解决方案:数学和计算机代数中的自动微分(有时称作“演算式微分”)就是一种有着固定迭代步骤,可以借由计算机程序运行的嵌套函数的导数计算方法。在思路上,误差反向传播算法其实是自动微分技术在反向积累模式的特例。
如图7-5所示,我们先考虑最简单的情况,忽略其他神经元的影响,每层只关注一个神经元,数据从输入层的神经元x到隐层的神经元u之间的变换用函数$g ( x ) = u$ 来表示,从隐层的神经元 u 到输出层的神经元y 的变换用 $f ( u ) = y$ 来表示。
250
第三部分 第三波高潮
PDF 物理页索引 266
文件中的第 267 页 · 印刷页码:251
那么,对于这三个神经元而言,它们之间的变换关系就是函数嵌套关系,微积分中称这种嵌套函数为“复合函数”,记作 $( f \circ g ) ( x )$ ,如以下式子所示:
$$
y = f ( u ) , \ u = \ g ( x ) , \ \not \equiv \ y = \ f ( g ( x ) ) \ \not \equiv \not \equiv \not \equiv \not \langle \not { F } ( f \circ g ) ( x )
$$
对于这种复合函数的求导,通常使用微积分中非常经典而且重要的“链式法则”(Chaining Rule)来处理,这条法则的含义是:“由两个函数组成的复合函数,其导数等于内部函数代入外部函数值之导数乘以内部函数的导数。”具体应用到上述讨论的场景中,经过 $f ( )$ 和 $g ( \ u )$ 两层函数变换后,x 对最终输出y施加的影响,可以通过导数 $\cdot \frac { \partial y } { \partial x }$ 体现出来。根据链式法则, $\frac { \partial y } { \partial x }$ 的计算方法为
$$
1 \sharp \vec { \chi } \ : ; \ : \varkappa : \ : y = f ( u ) , u = g ( x )
$$
$$
\mathbb { H } \mathbb { 3 } \textrm { : } \frac { \partial \boldsymbol { y } } { \partial \boldsymbol { x } } = \frac { \partial \boldsymbol { y } } { \partial \boldsymbol { u } } \cdot \frac { \partial \boldsymbol { u } } { \partial \boldsymbol { x } }
$$
基于这个场景的计算过程是最简单的,只涉及三个神经元,而更贴近实际的场景是在全连接前馈神经网络里,输出层的任意一个神经元y 都会受到其前一个隐层所有神经元的共同影响,而每一个隐层神经元 $u _ { i }$ 也都受到所有输入层神经元 $x _ { i }$ 的输入值的共同影响,那对于单独任一个输入值 x 求导数,其计算过程如下所示:
假设: $y = f ( u _ { 1 } , u _ { 2 } , \cdots , u _ { k } ) , ~ u _ { i } = { g } _ { i } ( x ) , ~ i = ~ 1 , \cdots , k$
$$
\mathbb { H } \mathbb { 3 } \ ; \ \underline { { \partial } } \underline { { y } } = \ \sum _ { i = 1 } ^ { k } \frac { \partial { y } } { \partial { u } _ { i } } \ \bullet \ \frac { \partial { u } _ { i } } { \partial { x } }
$$
第 7 章 深度学习时代
251
PDF 物理页索引 267
文件中的第 268 页 · 印刷页码:252
接下来我们再把隐含在g()、f() 函数中的激活函数单独提取出来。提取激活函数以后,对每一个神经元的处理过程就能表述得更具体。每一个神经元的功能都可划分为两个部分:
● 汇集各路输入传递过来的加权信息,得出一个加权和。
● 使用激活函数对加权和进行“加工”后,神经元给出相应的输出。
这样,每个神经元的输出都是将所有输入的加权和(以 s 表示)作为参数,传给激活函数(以 σ表示)进行计算得出的结果,上述过程可使用如下式子表示:
$$
\begin{array} { r l } { s _ { j } } & { { } \sum w _ { i j } x _ { i } } \\ { x _ { j } } & { { } \sigma ( s _ { j } ) } \end{array}
$$
至此已经得出了推导误差反向传播算法所需的全部前置知识,现在我们就从神经网络本身的运算过程转到误差反向传播算法的操作步骤上。虽然这个算法的名字中只包含了“反向”二字,但是其操作过程是包含了正向和反向两个阶段的,具体步骤如下:
● 准备阶段:随机初始化网络中的所有权值 w,最简单的做法是初始化为符合高斯分布的一组较小但不为零的随机数。
● 第一阶段:正向传播阶段,将训练集的数据输入神经网络,经过神经元的权值和激活函数处理,神经网络会获得一个输出值,这个值作为网络对输入值的响应。
● 第二阶段:反向传播阶段,由于权值最初都是随机产生的,神经网络对输入值的响应与训练集中标示的标签值势必会存在误差,该误差使用损失函数 E 来表示。由于这个误差值是由随机权值导致的,我们的目标就是要求解出每一层、每一个神经元的权值(从神经元 i 到神经元j 的连接权值以 $w _ { i j }$ 表示)对误差到底产生了多少影响。“一个变量对结果产生的影响程度”就是数学上导数的定义。只有求得导数之后,我们才能使用该导数逐步修正权值的大小,也即梯度下降的优化方法。所以第二阶段的核心就是求 $\frac { \partial E } { \partial w _ { i j } }$ ,如何求导是此算法的重点,稍后会详细解释。
252
第三部分 第三波高潮
PDF 物理页索引 268
文件中的第 269 页 · 印刷页码:253
由于整个求解过程使用传统正向模式求导的话,会面临很大的重复计算量,而这个算法的精髓是从输出层的误差开始,反过来逐层把误差传播至每一个隐层上,直到输入层为止,每一层都依赖后面已经计算好的信息去完成求导,因此称作“反向传播”。
● 第三阶段:权值更新,将每一个权值的梯度乘以一个比例值(称作训练因子,以 α 表示,作用是控制学习效率,承担了类似于梯度下降算法中步长幅度的角色)并取反,然后加到原来的权重上以得到新的权值。
● 重复:重复第一至第三阶段,直至获得的误差足够小,达到期望后跳出循环,完成训练过程。
这个算法的操作步骤从整体来说并没有神来之笔,其实根本就是梯度下降算法!最初人们认为神经网络是不可能训练的,并不是想不出这样的操作步骤,而是受限于操作步骤中第二阶段计算网络的每一个权值的导数 $\cdot \frac { \partial E } { \partial w _ { i j } }$ ,这个过程需耗费巨量的计算资源。而误差反向传播的最精妙之处就在于给出了可快速完成求导的“反向模式”,这才是误差反向传播算法改进的精髓所在。为了能更好地解释这个“反向模式”,笔者先使用计算图计算导数的过程来讲解一下传统正向模式是如何操作的,以及正常求导需要花费多少计算量。
我们以计算图求算子 a 和 b 对算式“ $\begin{array} { r l } { e } & { { } \ ( a + b ) \times ( b + 1 ) ^ { * } } \end{array}$ 的偏导数为例来讲解,这个算式的复合关系使用计算图展开后如图 7-6 所示<sup>○一</sup>。
第 7 章 深度学习时代
253
○一 本例的部分文字和图片来源于“ Calculus on Computational Graphs: Backpropagation ”:http://colah.github.io/posts/2015-08-Backprop/。
PDF 物理页索引 269
文件中的第 270 页 · 印刷页码:254
图 7-6 中引入了两个中间变量 c 和 $d ,$ ,要求 $\cdot \frac { \partial e } { \partial a }$ 和 $\frac { \partial e } { \partial b }$ ,假设输入的样例数据为 $a = 2 , \ b = 1$ ,传统正向模式的做法是根据偏导数的定义,先求出各个层级各个节点的偏导关系,如图 7-7 所示。
根据求导的链式法则,我们可以很容易知道 a 和 b 是如何影响最终结果 e的,即它们的偏导数计算过程如下:
$$
\frac { \partial e } { \partial a } = \frac { \partial e } { \partial c } \cdot \frac { \partial c } { \partial a }
$$
以及
$$
\frac { \partial e } { \partial b } = \frac { \partial e } { \partial c } \cdot \frac { \partial c } { \partial b } + \frac { \partial e } { \partial d } \cdot \frac { \partial d } { \partial b }
$$
对比计算图的结构和这两个式子,不难发现规律:计算 $\cdot \frac { \partial e } { \partial a }$ 的值,等价于计算图中从 a 到 $e$ 的路径( $( \ a \ \to c \ \to e )$ 上经过的偏导值的乘积,而计算 $\frac { \partial e } { \partial b }$ 的值就等价于计算图中从 $b$ 到 $e$ 的两条不同路径经过的偏导值乘积之和 $\ : \ " \ : b c \ :$ $e ^ { \prime \prime } + \mathrm { \Large ~ ~ } b d e ^ { \prime \prime } ,$ )。这一点并非巧合,通过计算图做自动微分其实就是对计算图路径上的偏导值求和,这是计算机求导的算法原理。
一般性地描述这个过程:对顶层节点 $p$ 和底层节点 $q$ ,要求导数 $\cdot { \frac { \partial p } { \partial q } }$ ,便需找到从节点 $q$ 到节点 $p$ 的所有路径,并且对每条路径求得该路径上的所有偏导数,把它们相乘,再将所有路径的乘积累加起来,就能得到 $\frac { \partial p } { \partial q }$ 的值。这种导数计算方法应用在许多问题中的效果都不错,但应用在神经网络上就有一个明显的缺陷:运算冗余,计算图中大量的节点被重复访问了。比如在我们的例子里,计算图的两条路径“ $\mathbf { \Phi } ^ { \cdot } a \mathbf { \Phi } \xrightarrow { } c \mathbf { \Phi } \xrightarrow { \cdot > } e ^ { \mathbf { \Phi } ^ { \prime \prime } }$ 和 $^ { } b c e ^ { \ ' }$ 都经过了公共的路径 $^ { 6 6 } c > e ^ { \prime \prime }$ 。对于例子中的计算图而言这点冗余算不了什么,但对于权值数量动辄百万乃至上亿的神经网络来说,这样的重复路径的排列组合会出现数百亿、数千亿条,所面临的计算量是完全不能承受的。
254
第三部分 第三波高潮