用于自然语言处理的循环神经网络RNN
前一篇:《人工智能模型学习到的知识是怎样的一种存在?》
序言:
在人工智能领域,卷积神经网络(CNN)备受瞩目,但神经网络的种类远不止于此。实际上,不同类型的神经网络各有其独特的应用场景。在接下来的几节中,我将带大家走近循环神经网络(RNN),深入了解其原理及其在处理人类自然语言中的改进与应用。
需要注意的是,目前自然语言处理领域主流的技术框架是基于 Transformer 架构的模型(如ChatGPT)。Transformer 克服了 RNN 在长序列上下文捕捉中的局限性,并以更高的训练效率和性能成为行业标准。我们在前面已经详细介绍过 Transformer 的相关内容。
本节对 RNN 的介绍,旨在帮助大家理解人工智能技术在序列建模领域的历史演进,以及它是如何一步步发展到今天的状态的。
在前面的几章节中,我们了解了如何对文本进行分词和序列化,将句子转换为数字张量,然后将其输入神经网络。接着,在上一章节中,我们进一步学习了嵌入方法,这是一种让具有相似意义的单词聚集在一起以便于计算情感的方式。这种方法效果非常好,正如你在构建讽刺分类器时所看到的那样。然而,这种方法也有一个局限性,那就是句子并不仅仅是单词的集合——通常,单词出现的顺序会决定句子的整体意义。形容词可以增强或改变它们所修饰名词的意义。例如,“蓝色”(blue)这个词从情感角度可能毫无意义,“天空”(sky)这个词亦然,但当你把它们组合成“蓝天”(blue sky)时,就传达出了一种通常是积极的情感。而有些名词也可能限定其他名词,比如“雨云”(rain cloud)、“书桌”(writing desk)、“咖啡杯”(coffee mug)。
为了考虑到像这样的序列信息,我们需要一种额外的方法,那就是将循环性融入到模型架构中。在接下来的几节中,我们将了解实现这一点的不同方法。我们将探讨如何学习序列信息,以及如何利用这些信息来构建一种更能理解文本的模型:循环神经网络(RNN)。
循环的基础
为了理解循环是如何工作的,首先让我们回顾一下本书迄今为止所使用模型的局限性。本质上,创建模型的过程有点像图 7-1 所示。你提供数据和标签,定义一个模型架构,模型学习将数据与标签匹配的规则。这些规则随后变成了一个 API,能够为未来的数据返回预测标签。
图 7-1. 模型创建的高层视图
不过,你可以看到,数据是被整体塞进去的。没有涉及到任何细节,也没有努力去理解数据出现的顺序。这意味着,像“blue”(蓝色)和“sky”(天空)这样的词,在“今天我很沮丧,因为天空是灰色的”(today I am blue, because the sky is gray)和“今天我很开心,有美丽的蓝天”(today I am happy, and there’s a beautiful blue sky)这样的句子中,它们的意义没有任何区别。对于我们来说,这些词的用法区别是显而易见的,但对于一个像这里所示架构的模型来说,真的没有区别。
那么我们该怎么解决这个问题呢?首先,让我们探讨一下循环的本质,从这里你就能了解基本的 RNN 是如何工作的。
考虑一下著名的斐波那契数列。如果你不熟悉它,我在图 7-2中列出了一些数字。
图 7-2. 斐波那契数列的前几个数字
这个数列的理念是,每个数字都是它前面两个数字的和。所以如果我们从 1 和 2 开始,下一个数字就是 1 + 2,等于 3。再下一个是 2 + 3,等于 5,然后是 3 + 5,等于 8,以此类推。
我们可以把这个过程放到一个计算图中,得到图 7-3。
图 7-3. 斐波那契数列的计算图表示
在这里你可以看到,我们将 1 和 2 输入到函数中,得到输出 3。我们把第二个参数(2)带到下一步,并与前一步的输出(3)一起输入函数。这次的输出是 5,然后它与前一步的第二个参数(3)一起输入函数,产生输出 8。这个过程无限继续,每一步的操作都依赖于之前的结果。左上角的 1 可以说是“存活”在整个过程中。它是被输入到第二次运算的 3 的一部分,也是被输入到第三次运算的 5 的一部分,等等。因此,1 的某种本质贯穿于整个序列,虽然它对整体数值的影响逐渐减小。
这类似于循环神经元的架构。你可以在图 7-4中看到循环神经元的典型表示。
图 7-4. 一个循环神经元
在一个时间步长中,一个值 x 被输入到函数 F,因此通常标记为 xt。这会在该时间步产生一个输出 y,通常标记为 yt。它还产生一个值,被前馈到下一步,这由从 F 指向它自己的箭头表示。
如果你看看循环神经元在不同时间步中如何彼此协作,这会更清晰一些,你可以在图 7-5中看到。
图 7-5. 时间步中的循环神经元
在这里,x₀ 被操作得到 y₀ 和一个传递到下一步的值。下一步获取那个值和 x₁,产生 y₁ 和一个传递到下一步的值。再下一步获取那个值和 x₂,产生 y₂ 和一个传递到下一步的值,就这样一直下去。这和我们在斐波那契数列中看到的类似,我总觉得这是记住 RNN 工作原理的一个好记忆方法。
将循环扩展到语言
在上一节中,你看到了一个在多个时间步上运行的循环神经网络如何帮助在序列中保持上下文。实际上,RNN 将在本书后面用于序列建模。但是,当涉及到语言时,使用像图 7-4 和图 7-5 中那样的简单 RNN,可能会错过一些细微之处。就像之前提到的斐波那契数列例子,被传递的上下文量会随着时间的推移而减小。第 1 步神经元的输出在第 2 步的影响很大,在第 3 步更小,在第 4 步更小,依此类推。
所以,如果我们有一句像“今天有一片美丽的蓝色<…>”这样的句子,“蓝色”这个词对下一个词有很大的影响;我们可以猜测它很可能是“天空”。但是,如果上下文来自句子更前面的地方呢?例如,考虑这句话:“我住在爱尔兰,所以在高中我不得不学习如何说和写<…>。”
那个 <…> 是盖尔语,但真正给我们上下文的词是“爱尔兰”,它在句子中更靠前的位置。因此,为了能够识别 <…> 应该是什么,我们需要一种方法来在更长的距离上保持上下文。RNN 的短期记忆需要变得更长,为此,发明了一种对架构的增强,称为长短期记忆(LSTM)。
虽然我不会详细介绍 LSTM 的底层架构是如何工作的,但如图 7-6 所示的高层图解传达了主要观点。要了解更多关于内部操作的内容,可以查看 Christopher Olah 关于该主题的优秀博文。
LSTM 架构通过添加一个“细胞状态”来增强基本的 RNN,这使得上下文不仅可以从一步到下一步保持,还可以在整个步骤序列中保持。记住这些是神经元,以神经元的方式学习,你可以看到这确保了重要的上下文会随着时间被学习。
图 7-6. LSTM 架构的高级视图
LSTM 的一个重要部分是它可以是双向的——时间步既向前又向后迭代,因此可以在两个方向上学习上下文。请参见图 7-7 获取其高级视图。
图 7-7. LSTM 双向架构的高级视图
这样,既进行了从 0 到 number_of_steps 的评估,也进行了从 number_of_steps 到 0 的评估。在每个步骤中,y 的结果是“前向”传递和“后向”传递的聚合。你可以在图 7-8 中看到这一点。
图 7-8. 双向 LSTM
将每个时间步的每个神经元视为 F₀、F₁、F₂ 等。时间步的方向已显示,所以在前向方向上对 F₁ 的计算是 F₁(→),在反向方向上是 (←)F₁。这些值被聚合以给出该时间步的 y 值。此外,细胞状态也是双向的。这对于管理句子中的上下文非常有用。再次考虑这句话:“我住在爱尔兰,所以在高中我不得不学习如何说和写<某物>”,你可以看到 <某物> 被“爱尔兰”这个上下文词限定了。但如果反过来呢:“我住在<这个国家>,所以在高中我不得不学习如何说和写盖尔语”?你可以看到,通过逆向遍历句子,我们可以了解 <这个国家> 应该是什么。因此,使用双向 LSTM 对于理解文本中的情感非常强大(正如你将在第 8 章中看到的,它们对于生成文本也非常强大!)。
当然,LSTM,特别是双向的,有很多复杂的过程,所以训练会很慢。这时候,值得投资一个 GPU,或者至少在可能的情况下使用 Google Colab 的托管 GPU。
总结:
本节我们介绍了循环神经网络(RNN),并探讨了如何通过改进使其更好地应用于人类语言处理。下一节中,我们将深入讨论 RNN 在自然语言处理中的具体应用及实现方法。