[译] 第四章 可视化证明神经网络可以计算任何函数

Neil Zhu,简书ID Not_GOD,University AI 创始人 & Chief Scientist,致力于推进世界人工智能化进程。制定并实施 UAI 中长期增长战略和目标,带领团队快速成长为人工智能领域最专业的力量。
作为行业领导者,他和UAI一起在2014年创建了TASA(中国最早的人工智能社团), DL Center(深度学习知识中心全球价值网络),AI growth(行业智库培训)等,为中国的人工智能人才建设输送了大量的血液和养分。此外,他还参与或者举办过各类国际性的人工智能峰会和活动,产生了巨大的影响力,书写了60万字的人工智能精品技术内容,生产翻译了全球第一本深度学习入门书《神经网络与深度学习》,生产的内容被大量的专业垂直公众号和媒体转载与连载。曾经受邀为国内顶尖大学制定人工智能学习规划和教授人工智能前沿课程,均受学生和老师好评。

第四章 可视化证明神经网络可以计算任何函数

神经网络的一个最引人注目的特点就是它实际上可以计算任何的函数。也就是说,假设某个人给你某种复杂而奇特的函数,$$f(x)$$:

不管这个函数是什么样的,总会有一个神经网络能够对任何可能的输入 $$x$$,网络可以得到对应的值 $$f(x)$$(或者某个足够准确的近似),如图:

即使函数有很多输入或者多个输出,这个结果都是成立的,$$f=f(x_1,...,x_m)$$ 。例如,这里有一个输入为 $$m=3$$ 和输出为 $$n=2$$ 的网络:

结果表明神经网络拥有一种普遍性(universality)。不管拿过来什么函数,我们都确信存在一个神经网络可以计算它。

而且,这个普遍性定理甚至在我们限制了神经网络只在输入层和输出层存在一个中间层的情况下成立。所以即使是很简单的网络架构都极其强大。

这个定理在使用神经网络的人群中非常著名。但是它为何正确却不被广泛地理解。现有的大多数的解释都具有很强的技术性。例如,最原始的论文使用了 Hahn-Banach 定理、Riesz 表示定理和一些傅里叶分析证明了这个结果。如果你是数学家,这个证明应该不大难理解,但对于大多数人还是很困难的。这不得不算是一种遗憾,因为这个普遍性背后的原理其实是简单而美妙的。

在本章,我会给一个有关普遍性的简单且基本上可视化的解释。我们会一步步深入背后的思想。你会理解为何神经网络可以计算任何的函数。你会理解到一些关于结论的一些极限。并且你还会理解这些结论如何和深度神经网络关联的。

要跟随本章的内容,你不需要读过本书前面的章节。相反,本章其实可以当成字包含的文章阅读。如果已经对神经网络有了一定的熟悉,你应该能够弄清楚这些解释。然而,我偶尔也会给出一些联系到前面的章节的链接,帮助你填补一些知识结构的空白。

普遍性定理在计算机科学领域中常常会有,太多了以至于我们都忘了这些定理的特别之处。但值得提醒自己的是:计算任意函数的能力真是是太赞了。几乎你可以想象的任何过程都可以看做是函数的计算。考虑给一段音乐用短的音乐片段进行命名这个问题。这其实也能够看做是计算一个函数。或者考虑将中文文本翻译成英文。同样,这又可以看成是计算一个函数。{实际上可以看成是计算很多的函数,因为对于一个文本来说有很多种翻译。}又或者根据一个 mp4 视频文件生成视频的画面的描述和对表演质量的讨论。同样,这些也都可以看成是一种类型的函数计算。普遍性是指,在原理上,神经网络可以做所有这些事情,或者更多。

当然,仅仅因为我们知道存在一个可以将中文翻译成英文的神经网络,这并不意味着我们有了一种构造或者识别出这样的网络的很好的技术。这个极限同样可以应用在布尔电路上的传统的普遍性定理上。但是,如同我们在本书前面看到的那样,神经网络拥有强大的算法来学习函数。学习算法和普遍性的结合是一种有趣的混合。直到现在,本书一直是着重谈学习算法。到了本章,我们来看看普遍性,看看它究竟意味着什么。

两个提醒


在解释为何普遍性定理成立前,我想要说说关于不大形式化的表述“神经网络可以计算任何函数”的两个提醒。

第一点,这句话不是说一个网络可以被用来准确地计算任何函数。而是说,我们可以获得尽可能好的一个近似。通过增加隐藏元的数量,我们可以提升近似的精度。例如,早先我使用了三个隐藏元的网络来计算 $$f(x)$$。使用三个隐藏元仅仅能得到一个低质量的大多数函数近似。通过增加隐藏元的数量(比如说,设置为五个),我们能够明显地得到更好的近似:

并且我们可以继续增加隐藏元的数目。

为了让这个表述更加准确,假设我们给定一个需要按照目标精度 $$\epsilon > 0$$ 的函数 $$f(x)$$。通过使用足够多的隐藏神经元使得神经网络的输出 $$g(x)$$ 对所有的 $$x$$,满足 $$|g(x)-f(x)|<\epsilon$$ 从而实现近似计算。换言之,近似对每个可能的输入都是限制在目标准确度范围内的。

第二点,就是可以按照上面的方式近似的函数类其实是连续函数。如果函数不是连续的,也就是会有突然、极陡的跳跃,那么一般来说无法使用一个神经网络进行近似。这并不意外,因为神经网络计算的就是输入的连续函数。然而,即使那些我们真的想要计算的函数是不连续的,一般来说连续的近似其实也足够的好了。如果这样的话,我们就可以用神经网络来近似了。实践中,这通常不是一个严重的限制。

总结一下,更加准确的关于普遍性定理的表述是包含一个隐藏层的神经网络可以被用来按照任意给定的精度来近似任何连续函数。本章,我们会使用了两个隐藏层的网络来证明这个结果的弱化的版本。在问题中我将简要介绍如何通过一些修改把解释转化成只使用一个隐藏层的网络的证明。

一个输入和一个输出的普遍性


为了理解为何普遍性定理成立,我们先从理解如何构造一个神经网络能偶近似一个只有一个输入和一个输出的函数:

结果表明,这其实是普遍性问题的核心。一旦我们理解了这个特例,那么实际上就会很容易扩展到那些有多个输入输出的函数上。

为了构建关于如何构造一个计算 $$f$$ 的网络的洞察,让我们从一个只包含一个隐藏层的网络开始,隐藏元两个,还有一个只有一个输出神经元的输出层:

为了感受一下网络的组成部分工作的机制,我们聚焦在最顶上的那个隐藏神经元。在下图例子中,点击权重,$$w$$,将鼠标从左往右拉动可以进行权重的增加。你可以立即看到最上面的隐藏元计算的函数变化的情况:

Paste_Image.png
Paste_Image.png
Paste_Image.png
Paste_Image.png
Paste_Image.png
Paste_Image.png

就在本书前面,我们也讲过隐藏元所计算的函数其实是 $$\sigma(wx+b)$$,其中 $$\sigma(z)\equiv 1/(1+e^{_z})$$ 是 sigmoid 函数。到现在为止,我们已经用过这个代数形式若干次了。但是为了证明普遍性,我们会完全忽略代数而通过操作观察图中的形状来获得有关这个函数的认知。这不仅仅能够给我们一种关于发生了什么的更好的感受,同样还会给出可以用在其他激活函数上普遍性的证明。

在开始证明前,试着点击图中上方的偏差,$$b$$,向右拉拽来增加这个值。你会看到在偏差增加时,图向左侧移动了,不过它的形状没有改变。

下一步,点击并向左拉拽来降低偏差。这时可以看到在偏差下降的时候,这个图向右移动了,但是它的形状仍然没有发生变化。

下面,降低权重到 $$2$$ 或者 $$3$$ 附近。你可以看到在降低权重的时候,这个曲线变得宽平了。你可能需要同时改变偏差,这样可以保证曲线在展示的范围内。

最后,增加权重 $$w=100$$。在进行的时候,曲线变得很陡,最后看起来想一个阶梯函数。试着调整偏差使得阶梯出现在 $$x=0.3$$ 处。下面的短视频可以展示你的结果应该长什么样。点击下面的进行播放:

【这里是视频】

我们可以简化分析通过增加权重使得输出是一个阶梯函数的比较好的近似。下面我已经画出了在权重 $$w=999$$ 时顶部的隐藏神经元的输出。注意这个图示静态的,你不能够改变参数。

实际上,借助于阶梯函数会比通常的 sigmoid 函数更加方便讲述。原因就是在输出层我们加入了来自所有隐藏层神经元的贡献。相比较分析在相加在一起的一堆 sigmoid 函数,分析一堆阶梯函数的和比较容易。所以,假设我们的隐藏层神经元都是阶梯函数会让事情变得简化。更加具体地说,我们通过将权重 $$w$$ 固定在一些非常大的值上,然后通过修改偏差来设置阶梯函数的位置。当然,将输出看做是阶梯函数是一种近似,不过这其实是非常好的一个近似,所以目前我将其看做是准确的。后面我们还会回过头讨论这个近似所产生的影响。

$$x$$ 在什么地方让阶梯出现?换言之,阶梯的位置如何依赖于权重和偏差?

为了回答这个问题,试着在图中修改权重和偏差(到 这里 去尝试)。你能不能试着找出这个阶梯出现的位置和权重 $$w$$ 及偏差 $$b$$ 的关系。通过一些尝试,你应该能让自己相信,阶梯出现的位置是和 $$b$$ 成正比例的,而和 $$w$$ 成反比例。

实际上,阶梯出现在 $$s=-b/w$$,你可以在下面的图中看出来:

动态例子,请参考原书例子

现在使用单个参数 $$s$$ 表示阶梯位置,$$s=-b/w$$。这会简化我们描述隐藏层神经元。试着下图中修改 $$s$$ 值,体验新参数的效果:

动态例子,请参考原书例子

需要注意的是,我们隐式地设置了权重 $$w$$ 为一个较大的值——大到可以让阶梯函数成为好的近似。我们可以轻易转换神经元的参数到正常模式,通过选择偏差为 $$b=-ws$$。

到现在位置,我们一直聚焦在顶部的隐藏神经元。现在来看看整个网络。特别地,我们假设隐藏神经元的计算是按照阶梯点 $$s_1$$(顶部)和 $$s_2$$(底部)进行的。然后相应的输出权重 $$w_1$$ 和 $$w_2$$。下图是现在的网络:

动态示例,参考原文

右边展示的其实是隐藏层的带权输出 $$w_1 a_1 + w_2 a_2$$。这里,$$a_1$$ 和 $$a_2$$ 分别是顶部和底部的隐藏神经元的输出。这些输出使用 $$a$$ 来表示因为通常被称作是神经元的激活值(activation)。

注意,实际上整个网络的输出应该是 $$\sigma(w_1a_1+w_2a_2+b)$$, 其中 $$b$$ 是输出神经元的偏差。显然,这和隐藏层的带权输出还是不同的,不过这里我们并没有画出来。我们现在聚焦在带权输出上,后面会考虑整个网络的情况。

试着增加和降低顶部隐藏神经元的 $$s_1$$。感受一下这种变化给带权输出带来的影响。这里理解

推荐阅读更多精彩内容