【新智元导读】谷歌大脑团队的Chris Olah & Shan Carter 整理了 2016 年递归神经网络(RNN)的发展,总结了神经图灵机、注意力界面、自适应计算时间和神经编程器这四个使用注意力对常规 RNN 进行增强的模型,并使用动态图生动阐释了相关概念。他们认为,这四大模型会对接下来 RNN 发展产生重大影响。新智元提供本文中文翻译,可访问原始页面查看更多。图片均来自原文。Github 代码:https://github.com/distillpub/post--augmented-rnns
(图文/Chris Olah & Shan Carter,Google Brain)递归神经网络(RNN)是深度学习的常用方法之一,RNN 让神经网络能够处理文本、音频和视频等数据序列。RNN 能提炼出对一串序列的高级理解,对序列进行注释甚至从零开始生成新的序列!
一个单元都能多次重复使用。
基本的 RNN 设计会涉及复杂的长序列,但有一种特殊的 RNN——“长短时记忆”(LSTM)网络——能应对这一问题。LSTM 模型十分强大,在翻译、语音识别和图像描述等任务上取得了显著的结果。因此,RNN 在过去几年也得到了广泛的使用。
由此,我们也见到了很多增强 RNN 使其具有额外功能的尝试。其中,以下 4 种方向尤其让人激动:
单独看,这些技巧都足以用作 RNN 的扩展,但更惊人的是,它们还可以结合起来适用于更广的范畴。不仅如此,它们都依赖于同样的底层技巧——注意力。
我们猜测,这些“增强 RNN”将会在未来扩展深度学习能力方面发挥重大的作用。
神经图灵机(NTM)
神经图灵机(Graves, et al., 2014)将 RNN 和一个外部存储库结合起来。鉴于向量是神经网络的自然语言,记忆也就成了一组组的向量:
但读写又是怎么发生的呢?难点就在于我们希望区分每一次的读写。具体说,我们希望能区分发生在不同位置的读写,这样我们才能知道哪里发生了信息的读写。这一点不太好弄,因为存储地址从本质上看似乎是离散的。对此,NTM 采取了一个非常巧妙的解决方案:每一步都在所有的地方进行读写,只是程度不一样。
举例来说,让我们看看读取信息的情况。RNN 没有明确单独的一个位置,而是给出“注意力分布”,描述我们是如何在不同的存储位置分散我们注意的那些信息量的。因此,读取信息操作的结果也就成了权重的总合。
同样的,我们一次性在所有的位置都以不同的程度读取信息。这里,也会有一个注意力分布,描述我们是如何在每一处读取信息的。形式如下:
但 NTM 是如何决定将注意力聚焦到某个位置呢?它们实际上采用了两种不同方法的结合:基于内容的注意力和基于位置的注意力。基于内容的注意力让 NTM 查找整合存储,并将注意力集中在它们寻找的位置上面,而基于位置的注意力让 NTM 可以循环。
这种读写的能力让 NTM 能够运行很多此前神经网络不能运行的简单算法。例如,NTM 能够学会在存储中储存一段长序列,然后循环,不断重复。在 NTM 这样做的过程中,我们可以查看它们都是在哪里进行读写,从而更好地理解 NTM 的行为。
NTM 还能学会模仿排序表,甚至学会排序(虽然方法有些作弊!)但另一方面,NTM 还是不能完成加法、乘法等简单的任务。
从最初提出 NTM 的论文以来,又有好些篇论文在这个方向进行了令人激动的探索。神经 GPU(Kaiser & Sutskever, 2015)克服了 NTM 无法加、乘的问题。Zaremba & Sutskever 在 2016 年写的一篇论文,训练 NTM 使用强化学习,在原来差分读写的基础上有所提高。Kurach 等人 2015 年提出的神经随机存取机(Neural Random Access Machine)则是基于指针。还有些论文探讨了差分数据结构,比如堆栈和序列(Grefenstette et al. 2015; Joulin & Mikolov, 2015)。记忆网络(Weston et al., 2014; Kumar et al., 2015)也是解决类似问题的一种不同的手段。
客观而言,这些模型能执行的任务,比如学会如何计数,实际上并没有那么困难。传统的程序合成轻而易举就能实现。但是,神经网络还能做到其他很多事情,而像神经图灵机(NTM)这样的模型突破了神经网络的一个很大的局限。
代码(访问原文页面获取)
这些模型部署有很多开源代码。神经图灵机的开源代码实现有 Taehoon Kim 的 TensorFlow、Shawn Tan 的 Theano、Fumin 的 Go、Kai Sheng Tai 的 Torch,以及 Snip 的 Lasagne。神经 GPU 相关的开源代码已经纳入 TensorFlow 的模型库当中。记忆网络的开源代码实现包括 Facebook 的 Torch/Matlab、YerevaNN 的 Theano,以及 Taehoon Kim 的 TensorFlow。
注意力界面(Attention Interface)
当我翻译一句话的时候,我会特别注意我当前正在翻译的词。当我听译音频的时候,我会仔细听我手头正在写的片段。如果你让我形容我坐在一个怎样的房间里,我会一遍环顾周围的物体一边向你描述。
通过使用注意力,神经网络也能进行同样的行为,只关注提供给它们的信息中的一部分。例如,一个 RNN 能注意另一个 RNN 的输出。每一步,这个 RNN 都关注另一个 RNN 的不同位置。
我们希望注意力是有区别的(differentiable),这样我们就能学习集中到哪里。要做到这一点,我们使用和神经图灵机一样的技巧:关注每一个地方,但程度各不相同。
注意力分布通常由基于内容的注意力生成。负责监控的 RNN 会生成一个 query,描述它想要重点关注什么。Query 跟每一项的点积会生成一个值,这个值会被输入 SOFTMAX 创建注意力分布。
在 RNN 之间使用注意力的一大应用是翻译(Bahdanau, et al., 2014)。传统的序列到序列模型必须将整个输入全部转换为单一的一个向量后,再反向扩展。注意力让处理输入的 RNN 能够传递信息,再单词产生关联后再集中注意力对其进行处理。
RNN 之间的这类注意力有很多应用。在 Chan 等人 2015 年的论文中,它被用于语音识别,一个 RNN 处理音频输入,另一个 RNN 则过滤信息,只注意相关的部分生成录音。
其他的应用还包括文本分析,例如在 Vinyals 等人 2014 年写的论文中,就让模型扫一遍它生成的分析树。还有 Vinyals 和 Le 2015 年写的论文,让模型在生成回应时,关注之前的一部分对话,通过这种方式建立对话模型。
注意力还能用于卷积神经网络和 RNN 之间的界面。这使得 RNN 在图像处理的过程中每一步都关注不同的位置。这类注意力有一个很受欢迎的应用,那就是图像描述。首先,一个卷积网络处理图像,当这个卷积网络生成描述的一个个单词时,RNN 关注卷积网络对图像中关联部分的解释。我们可以用图像将这一过程展示出来:
更广泛地说,当你希望与一个有重复性输出的神经网络进行交互时,都可以使用注意力界面。
注意力界面是一个十分强大且适用范围广的技术,近来得到越来越多的使用。
自适应计算时间
标准的 RNN 每一步的计算量相同,这似乎不是很直观。当然了,当事情更难一些的时候,思考应该更复杂一些,对吧?这也限制了 RNN 对一组长度为 n 的序列进行 O(n) 的操作。
Graves 2016 年提出的自适应计算时间(Adaptive Computation Time),是让 RNN 每一步进行不同计算量的一种方法。其实原理很简单:让 RNN 在每一个时间段內进行多次计算步骤即可。
为了让网络学会有多少步,我们希望步骤的数目是可导的。我们通过让需要进行的步骤的数量进行注意力分布实现这一点。输出是每一步输出加权的和。
上一幅图中省略了一些细节。下面这幅图是进行 3 个计算步骤的完整图示。
看上去有些复杂,我们接下来就一步一步地讲解。从较高的层面看,我们仍然在运行 RNN,并且输出每个状态的加权和。
每一步的权重都由一个“缓冲神经元”(halting neuron)决定。这个缓冲神经元是一个 sigmoid 神经元,它会查看 RNN 的状态,给出一个缓冲权重,我们可以将其视为在那一步停止的几率。
我们拥有的缓冲权重总和为 1,沿着这条线追踪这个值,当它 < ε 时,我们就停下来。
当我们停止的时候,有可能还剩下一些缓冲值,这个时候该怎么做呢?这些值会给到接下来的步骤,但这些步骤我们并不会计算。因此,将剩下的这部分值全部归到最后一步。
训练自适应计算时间模型时,需要为代价函数加上一个“思考代价”(ponder cost)。思考代价会根据模型花费的计算量对模型进行惩罚会。思考代价的值越大,模型用于提升性能的时间牺牲就会越小。换句话说,模型花费的计算时间就越少,但计算结果的精度也会相应下降。
自适应计算时间是一个很新的概念,但我们相信,自适应计算时间和其他类似的概念将具有很重要的意义。
代码
目前自适应计算时间的开源代码实现只有 Mark Neumann 的 TensorFlow。
神经编程器(Neural Programmer)
神经网络能够执行很多任务,但一些最简单的事情,比如算术这种十分初等的计算,却无法搞定。找到一种将神经网络和常规编程结合起来,发挥两者长处方法,就显得十分必要。
Neelakantan 等人 2015 年提出的神经编程器就是这样的一种方法。神经编程器能够创建程序解决问题。实际上,神经编程器能够在没有正确编程样本的情况下生成这些程序。换句话说,神经编程器通过发现如何生成程序完成某些任务。
生成的程序是一系列操作。每一步操作都将上一步操作的输出作为输入。因此,一个命令可能是“将操作 2 的上一步输出和操作 1 的上一步输出相加”。
每一步操作都会生成一个程序,这都由一个控制器 RNN (controller RNN)负责。每一步,控制器 RNN 输出一个描述下一步操作的概率分布。例如,我们可能很明确第一步想要做的是加法,但第二步是做乘法还是除法就很犹豫,以此类推……
最终各个操作的分布可以进行估值。我们将注意力放到全部操作上,然后取得输出的均值,根据每步操作的几率加权。
只要我们通过操作确定衍生值(derivative),程序的输出相对于概率就是可导的。我们可以确定一个 loss,然后训练神经网络生成给出正确答案的程序。通过这种方法,神经编程器就学会了在没有好的程序样本的前提下,生成程序。需要监督的只是程序生成的答案而已。
神经编程器还有其他的一些版本。神经编程器并非唯一让神经网络编程的方法。Reed & de Freitas 在 2015 年提出的 Neural Programmer-Interpreter 也能完成一些有趣的任务,不过需要对正确的程序形式进行监督。
总的来说,神经编程器在传统编程和神经网络之间架起了桥梁,而这一点是十分重要的。神经编程器可能不是终极解决办法,但从中能够学到很多重要的事情。
代码
目前神经编程器似乎还没有开源代码实现。不过,有 Ken Morishita 在 Keras 的 Neural Programmer-Interpreter。
综述
参考文献
编译来源:Chris Olah & Shan Carter, “Attention and Augmented Recurrent Neural Networks”, Distill, 2016. distill.pub/2016/augmented-rnns/