【NLP】第16章使用RNN和注意力的自然语言处理
大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流?
?个人主页-Sonhhxg_柒的博客_CSDN博客
?欢迎各位→点赞? + 收藏 + 留言??
?系列专栏 - 机器学习【ML】?自然语言处理【NLP】? 深度学习【DL】
?
?说明?本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。
如果你对这个系列感兴趣的话,可以关注订阅哟?
?什么时候Alan Turing 在 1950 年设想了他著名的图灵测试1,他的目标是评估机器匹配人类智能的能力。他本可以测试很多东西,比如识别图片中的猫、下棋、作曲或逃离迷宫的能力,但有趣的是,他选择了语言任务。更具体地说,他设计了一个聊天机器人,能够愚弄其对话者认为它是人类。2这个测试确实有它的弱点:一组硬编码的规则可以欺骗毫无戒心或天真的人(例如,机器可以给出模糊的预定义答案以响应某些关键字;它可以假装它是在开玩笑或喝醉,以通过它最奇怪的答案;或者它可以通过自己的问题来回答困难的问题),并且人类智能的许多方面被完全忽略(例如,解释面部表情等非语言交流的能力,或学习手动任务的能力)。但该测试确实突出了一个事实,即掌握语言可以说是智人最大的认知能力。我们可以建造一台可以读写自然语言的机器吗?
?自然语言任务的一种常见方法是使用递归神经网络。因此,我们将继续探索 RNN(在第 15 章中介绍),从字符 RNN开始,经过训练以预测句子中的下一个字符。这将允许我们生成一些原始文本,在这个过程中,我们将看到如何在一个很长的序列上构建一个 TensorFlow 数据集。我们将首先使用无状态 RNN(它在每次迭代中学习文本的随机部分,而没有关于文本其余部分的任何信息),然后我们将构建一个有状态 RNN(它在训练迭代之间保留隐藏状态并继续阅读它离开的地方,允许它学习更长的模式)。接下来,我们将构建一个 RNN 来执行情感分析(例如,阅读电影评论并提取评价者对电影的感觉),这次将句子视为单词序列,而不是字符。然后我们将展示如何使用 RNN 构建能够执行神经机器翻译 (NMT) 的编码器-解码器架构。为此,我们将使用 TensorFlow Addons 项目提供的 seq2seq API。
在本章的第二部分,我们将看注意力机制。顾名思义,这些是神经网络组件,它们学习选择模型的其余部分在每个时间步应关注的输入部分。首先,我们将了解如何使用注意力提高基于 RNN 的编码器-解码器架构的性能,然后我们将完全放弃 RNN,并研究一种非常成功的仅注意力架构,称为Transformer。最后,我们将看看 NLP 在 2018 年和 2019 年的一些最重要的进步,包括非常强大的语言模型,例如 GPT-2 和 BERT,它们都基于 Transformer。
让我们从一个简单有趣的模型开始,它可以像莎士比亚一样写作(嗯,有点)。
在Andrej Karpathy 在2015 年的一篇名为循环神经网络的不合理有效性的博文中展示了如何训练 RNN 来预测句子中的下一个字符。然后可以使用这个Char-RNN生成新颖的文本,一次一个字符。以下是 Char-RNN 模型在接受莎士比亚所有作品的训练后生成的一小部分文本:
熊猫人:
唉,我想他会来的,那一天
当小小伤痕会变成永不吃饱的时候,
谁只是他死亡的锁链和臣民,
我不应该睡觉。
不完全是杰作,但令人印象深刻的是,该模型能够通过学习预测句子中的下一个字符来学习单词、语法、正确的标点符号等等。让我们从创建数据集开始,逐步了解如何构建 Char-RNN。
首先,让我们下载莎士比亚的所有作品,使用 Keras 的便捷功能并从 Andrej Karpathy 的Char-RNN 项目下载数据:
接下来,我们必须将每个字符编码为一个整数。一种选择是创建一个自定义预处理层,就像我们在第 13 章中所做的那样。但是在这种情况下,使用 Keras 的类会更简单一些。首先,我们需要为文本拟合一个分词器:它会找到文本中使用的所有字符,并将每个字符映射到不同的字符 ID,从 1 到不同字符的数量(它不是从 0 开始的,所以我们可以使用该值进行屏蔽,我们将在本章后面看到):
我们设置为获取字符级编码而不是默认的字级编码。请注意,此标记器默认将文本转换为小写(但如果您不希望这样做,您可以设置)。现在分词器可以将一个句子(或一个句子列表)编码为一个字符 ID 列表并返回,它告诉我们有多少不同的字符以及文本中的字符总数:
让我们对全文进行编码,这样每个字符都由它的 ID 表示(我们减去 1 得到从 0 到 38 的 ID,而不是从 1 到 39):
在继续之前,我们需要将数据集拆分为训练集、验证集和测试集。我们不能只打乱文本中的所有字符,那么如何拆分顺序数据集呢?
它避免训练集、验证集和测试集之间的任何重叠非常重要。例如,我们可以将前 90% 的文本用于训练集,然后将接下来的 5% 用于验证集,最后 5% 用于测试集。在这些集合之间留一个间隙以避免段落重叠在两个集合之间的风险也是一个好主意。
在处理时间序列时,您通常会按时间划分:例如,您可能将 2000 年到 2012 年作为训练集,将 2013 年到 2015 年作为验证集,将 2016 年到 2018 年作为测试集放。但是,在某些情况下,您可能能够沿其他维度进行拆分,这将为您提供更长的训练时间。例如,如果您有 10,000 家公司从 2000 年到 2018 年财务状况的数据,您可以将这些数据拆分到不同的公司。但是,这些公司中的许多很可能是高度相关的(例如,整个经济部门可能共同上升或下降),如果您在训练集和测试集中有相关公司,那么您的测试集将没有那么有用,因为它对泛化误差的度量将是乐观的。
因此,跨时间拆分通常更安全——但这隐含地假设 RNN 过去(在训练集中)可以学习的模式在未来仍然存在。换句话说,我们假设时间序列是平稳的(至少在广义上)。3对于许多时间序列,这个假设是合理的(例如,化学反应应该没问题,因为化学定律不会每天都在变化),但对于许多其他的则不是(例如,众所周知,金融市场不是静止的,因为模式随着时间的推移而消失)一旦交易者发现它们并开始利用它们)。为确保时间序列确实足够平稳,您可以绘制模型在验证集上随时间变化的误差:如果模型在验证集的第一部分上的性能比在最后一部分上好得多,那么时间序列可能不会足够静止,你最好在更短的时间跨度内训练模型。
简而言之,将时间序列拆分为训练集、验证集和测试集并不是一项简单的任务,如何完成将很大程度上取决于手头的任务。
现在回到莎士比亚!让我们将前 90% 的文本用于训练集(保留其余文本用于验证集和测试集),并创建一个将从该集中一个一个地返回每个字符:
这训练集现在由超过一百万个字符的单个序列组成,因此我们不能直接在其上训练神经网络:RNN 将等效于具有超过一百万层的深度网络,我们将有一个 (很长)实例来训练它。相反,我们将使用数据集的方法将这个长字符序列转换为许多较小的文本窗口。数据集中的每个实例都是整个文本的一个相当短的子字符串,RNN 将仅在这些子字符串的长度上展开。这个被称为随时间截断的反向传播。让我们调用该方法来创建短文本窗口的数据集:
小费
您可以尝试调整:在较短的输入序列上训练 RNN 更容易,但当然 RNN 将无法学习比 更长的任何模式,因此不要将其设置得太小。
默认情况下,该方法创建不重叠的窗口,但为了获得我们使用的最大可能训练集,第一个窗口包含字符 0 到 100,第二个窗口包含字符 1 到 101,依此类推。为确保所有窗口的长度正好为 101 个字符(这将允许我们创建批次而无需进行任何填充),我们设置(否则最后 100 个窗口将包含 100 个字符、99 个字符,依此类推直到 1 个字符) .
该方法创建一个包含窗口的数据集,每个窗口也表示为一个数据集。它的嵌套数据集,类似于列表列表。当您想通过调用其数据集方法来转换每个窗口时,这很有用(例如,打乱它们或批处理它们)。但是,我们不能直接使用嵌套数据集进行训练,因为我们的模型将张量作为输入,而不是数据集。因此,我们必须调用该方法:它转换嵌套数据集到一个平面数据集(一个不包含数据集的数据集)。例如,假设 {1, 2, 3} 表示包含张量序列 1、2 和 3 的数据集。如果将嵌套数据集 {{1, 2}, {3, 4, 5, 6}} 展平,你得到了平面数据集 {1, 2, 3, 4, 5, 6}。此外,该方法将函数作为参数,它允许您在展平之前转换嵌套数据集中的每个数据集。例如,如果您将函数传递给,那么它会将嵌套数据集 {{1, 2}, {3, 4, 5, 6}} 转换为平面数据集 {[1, 2], [3, 4] , [5, 6]}:它是一个大小为 2 的张量数据集。考虑到这一点,我们准备展平我们的数据集:
请注意,我们调用了每个窗口:由于所有窗口都具有该长度,因此我们将为每个窗口获得一个张量。现在数据集包含每个 101 个字符的连续窗口。由于梯度下降在训练集中的实例独立且同分布时效果最好(参见第 4 章),我们需要对这些窗口进行打乱。然后我们可以批处理窗口并将输入(前 100 个字符)与目标(最后一个字符)分开:
图 16-1总结了到目前为止讨论的数据集准备步骤(显示长度为 11 而不是 101 的窗口,以及批量大小为 3 而不是 32)。
图 16-1。准备洗牌窗口的数据集
正如第 13 章所讨论的,分类输入特征通常应该被编码,通常作为 one-hot 向量或嵌入。在这里,我们将使用 one-hot 向量对每个字符进行编码,因为不同的字符很少(只有 39 个):
最后,我们只需要添加预取:
而已!准备数据集是最困难的部分。现在让我们创建模型。
至根据前 100 个字符预测下一个字符,我们可以使用具有 2 层的 RNN,每层 128 个单元,输入 (?) 和隐藏状态 ( )都有 20% 的 dropout?。如果需要,我们可以稍后调整这些超参数。输出层是一个时间分布层,就像我们在第 15 章中看到的那样。这次这一层必须有 39 个单元 (?),因为文本中有 39 个不同的字符,我们希望输出每个可能字符的概率(在每个时间步)。每个时间步的输出概率总和应为 1,因此我们将 softmax 激活函数应用于层的输出。然后我们可以编译这个模型,使用损失和Adam优化器。最后,我们准备好将模型训练几个 epoch(这可能需要几个小时,具体取决于您的硬件):
现在我们有一个模型可以预测莎士比亚所写文本中的下一个字符。为了给它提供一些文本,我们首先需要像之前一样对其进行预处理,所以让我们为此创建一个小函数:
现在让我们使用该模型来预测某些文本中的下一个字母:
成功!模型猜对了。现在让我们使用这个模型来生成新的文本。
至使用 Char-RNN 模型生成新文本,我们可以给它一些文本,让模型预测最有可能的下一个字母,将其添加到文本的末尾,然后将扩展文本提供给模型来猜测下一个字母,等等。但在实践中,这通常会导致相同的词被一遍又一遍地重复。相反,我们可以使用 TensorFlow 的函数随机选择下一个字符,概率等于估计的概率。这将产生更加多样化和有趣的文本。给定类对数概率 (logits),该函数对随机类索引进行采样。为了更好地控制生成文本的多样性,我们可以将 logits 划分为一个称为温度的数字,我们可以根据需要对其进行调整:接近 0 的温度将有利于高概率字符,而非常高的温度将使所有字符具有相等的概率。以下函数使用这种方法来选择下一个要添加到输入文本的字符:
接下来,我们可以编写一个小函数,重复调用以获取下一个字符并将其附加到给定的文本中:
我们现在准备生成一些文本!让我们尝试不同的temperatures:
显然,我们的莎士比亚模型在接近 1 的temperatures下效果最好。要生成更有说服力的文本,您可以尝试使用更多层和每层更多的神经元,训练更长时间,并添加一些正则化(例如,您可以在层中设置) .?此外,该模型目前无法学习超过100 个字符的模式。你可以试着把这个窗口变大,但这也会使训练更难,甚至 LSTM 和 GRU 单元也无法处理很长的序列。或者,您可以使用有状态的 RNN。
直到现在,我们只使用了无状态的 RNN:在每次训练迭代中,模型从一个充满零的隐藏状态开始,然后在每个时间步更新这个状态,在最后一个时间步之后,它把它扔掉,因为它不是不再需要了。如果我们告诉 RNN 在处理一个训练批次后保留这个最终状态并将其用作下一个训练批次的初始状态会怎样?通过这种方式,模型可以学习长期模式,尽管只通过短序列进行反向传播。这称为有状态的 RNN。让我们看看如何构建一个。
首先,请注意,有状态 RNN 仅在批次中的每个输入序列恰好开始于前一批中相应序列停止的位置时才有意义。因此,构建有状态 RNN 需要做的第一件事是使用顺序且不重叠的输入序列(而不是我们用来训练无状态 RNN 的混洗和重叠序列)。因此,在创建 时,我们必须在调用方法时使用(而不是)。而且,我们显然不能调用该方法。不幸的是,在为有状态 RNN 准备数据集时,批处理比为无状态 RNN 准备数据集要困难得多。确实,如果我们打电话给,那么 32 个连续的窗口将被放在同一个批次中,并且下一个批次将不会继续它离开的每个窗口。第一批将包含窗口 1 到 32,第二批将包含窗口 33 到 64,因此如果您考虑,例如,每个批次的第一个窗口(即窗口 1 和 33),您可以看到它们不是连续的.?这个问题最简单的解决方案是只使用包含单个窗口的批处理:
图 16-2总结了最初的步骤。
图 16-2。为有状态的 RNN 准备连续序列片段的数据集
批处理更难,但并非不可能。例如,我们可以将莎士比亚的文本分成 32 段等长的文本,为每个文本创建一个包含连续输入序列的数据集,最后用于创建适当的连续批次,其中批次中的第n个输入序列正好从第n个输入序列在前一批中结束(完整代码请参见笔记本)。
现在让我们创建有状态的 RNN。首先,我们需要在创建每个循环层时进行设置。其次,有状态的 RNN 需要知道批量大小(因为它会为批量中的每个输入序列保留一个状态),因此我们必须在第一层设置参数。请注意,我们可以不指定第二维,因为输入可以具有任意长度:
在每个 epoch 结束时,我们需要在回到文本开头之前重置状态。为此,我们可以使用一个小回调:
现在我们可以编译和拟合模型(对于更多的 epoch,因为每个 epoch 比以前短得多,并且每批只有一个实例):
小费
训练此模型后,只能使用它来预测与训练期间使用的大小相同的批次。为避免此限制,请创建一个相同的无状态模型,并将有状态模型的权重复制到此模型。
现在我们已经构建了字符级模型,是时候查看单词级模型并解决常见的自然语言处理任务:情感分析。在此过程中,我们将学习如何使用掩码处理可变长度的序列。
如果MNIST 是计算机视觉的hello world,那么 IMDb 评论数据集就是自然语言处理的hello world:它包含从著名的Internet Movie中提取的 50,000 条英语电影评论(25,000 条用于训练,25,000 条用于测试)数据库,以及每个评论的简单二进制目标,指示它是负面 (0) 还是正面 (1)。就像 MNIST 一样,IMDb 评论数据集之所以受欢迎是有充分理由的:它足够简单,可以在合理的时间内在笔记本电脑上处理,但又足够具有挑战性,既有趣又有益。Keras 提供了一个简单的函数来加载它:
影评在哪里?好吧,正如您所看到的,数据集已经为您进行了预处理:包含一个评论列表,每个评论都表示为一个 NumPy 整数数组,其中每个整数代表一个单词。去掉所有的标点符号,然后将单词转换为小写,用空格分割,最后按频率索引(所以低整数对应频繁的单词)。整数 0、1 和 2 是特殊的:它们代表填充标记,?序列开始(SSS)标记和未知词,分别。如果你想可视化评论,你可以像这样解码它:
在实际项目中,您必须自己预处理文本。您可以使用我们之前使用的相同类来执行此操作,但这次是设置(这是默认设置)。在对单词进行编码时,它会过滤掉很多字符,包括大多数标点符号、换行符和制表符(但您可以通过设置参数来更改)。最重要的是,它使用空格来识别单词边界。这对于英语和许多其他在单词之间使用空格的脚本(书面语言)来说是可以的,但并非所有脚本都以这种方式使用空格。中文不使用单词之间的空格,越南语甚至在单词之间使用空格,而德语等语言经常将多个单词连接在一起,没有空格。即使在英语中,空格也不总是标记文本的最佳方式:想想旧金山或ILoveDeepLearning。
幸运的是,还有更好的选择!Taku Kudo的2018 年论文4介绍了一种无监督学习技术,以独立于语言的方式在子词级别对文本进行标记和去标记,将空格视为其他字符。使用这种方法,即使你的模型遇到一个它以前从未见过的词,它仍然可以合理地猜出它的含义。例如,它可能在训练时从未见过smart这个词,但它可能学会了smart这个词,而且它还知道了后缀est的意思是最,所以它可以推断出smart的含义。 。?Google 的SentencePiece项目提供了一个开源实现,在Taku Kudo 和 John Richardson的论文5中进行了描述。
Rico Sennrich 等人在较早的论文6中提出了另一种选择。探索了其他创建子词的方法编码(例如,使用字节对编码)。最后但同样重要的是,TensorFlow 团队发布了TF.Text库2019 年 6 月,实施各种标记化策略,包括WordPiece?7(字节对编码的一种变体)。
如果您想将模型部署到移动设备或 Web 浏览器,并且不想每次都编写不同的预处理函数,那么您将希望仅使用 TensorFlow 操作来处理预处理,因此可以包含它在模型本身。让我们看看如何。首先,让我们使用 TensorFlow 数据集(在第 13 章中介绍)以文本(字节字符串)的形式加载原始 IMDb 评论:
接下来,让我们编写预处理函数:
它首先截断评论,只保留每个评论的前 300 个字符:这将加快训练速度,并且不会对性能产生太大影响,因为您通常可以在第一句话或第二句话中判断评论是否正面。然后它使用正则表达式将标签替换为空格,并将除字母和引号之外的任何字符替换为空格。例如,文本将变为.?最后,该函数通过空格分割评论,返回一个不规则张量,并将这个不规则张量转换为密集张量,用填充标记填充所有评论,使它们都具有相同的长度。
接下来,我们需要构建词汇表。这需要遍历整个训练集一次,应用我们的函数,并使用 a来计算每个单词的出现次数:
让我们看看三个最常见的词:
伟大的!不过,我们可能不需要我们的模型知道字典中的所有单词来获得良好的性能,所以让我们截断词汇表,只保留 10,000 个最常用的单词:
现在我们需要添加一个预处理步骤,用它的 ID(即它在词汇表中的索引)替换每个单词。就像我们在第 13 章中所做的那样,我们将为此创建一个查找表,使用 1,000 个词汇表外 (oov) 桶:
然后我们可以使用这个表来查找几个单词的 ID:
请注意,在表中找到了单词this、movie和was,因此它们的 ID 低于 10,000,而未找到单词faaaaaantastic,因此映射到其中一个 oov 存储桶, ID 大于或等于 10,000。
小费
TF Transform(在第 13 章中介绍)提供了一些有用的函数来处理这些词汇。例如,查看函数:它将遍历数据集以查找所有不同的单词并构建词汇表,它将生成使用该词汇表对每个单词进行编码所需的 TF 操作。
现在我们准备创建最终的训练集。我们对评论进行批处理,然后使用该函数将它们转换为简短的单词序列,然后使用一个使用我们刚刚构建的表的简单函数对这些单词进行编码,最后预取下一批:
最后我们可以创建模型并训练它:
第一层是一个层,它将单词 ID 转换为嵌入(在第 13 章中介绍)。嵌入矩阵需要每个单词 ID (?) 一行,每个嵌入维度一列(此示例使用 128 个维度,但这是您可以调整的超参数)。模型的输入将是形状 [批量大小、时间步长] 的2D 张量,而层的输出将是形状 [批量大小、时间步长、嵌入大小] 的 3D 张量。
模型的其余部分相当简单:它由两层组成,第二层仅返回最后一个时间步的输出。输出层只是一个使用 sigmoid 激活函数来输出评论表达对电影的正面情绪的估计概率的单个神经元。然后我们非常简单地编译模型,并将其拟合到我们之前准备的数据集上,持续几个时期。
作为事实上,模型需要学习应该忽略填充标记。但我们已经知道了!为什么我们不告诉模型忽略填充标记,以便它可以专注于实际重要的数据?这实际上很简单:只需在创建图层时添加。这意味着所有下游层都将忽略填充令牌(其 ID 为 0)?8 。就这样!
它的工作方式是图层创建一个等于(其中) 的掩码张量:它是一个与输入具有相同形状的布尔张量,并且它等于单词 ID 为 0 或其他任何地方的任何位置。然后,只要保留时间维度,该掩码张量就会由模型自动传播到所有后续层。所以在这个例子中,两个层都会自动接收这个掩码,但是由于第二层没有返回序列(它只返回最后一个时间步的输出),所以掩码不会被传输到层。每个层可能以不同的方式处理掩码,但通常它们只是忽略掩码的时间步长(即,掩码所在的时间步长))。例如,当循环层遇到掩码时间步时,它只是简单地复制前一个时间步的输出。如果掩码一直传播到输出(在输出序列的模型中,在本例中不是这种情况),那么它也将应用于损失,因此掩码时间步不会对损失有贡献(他们的损失将是0)。
警告
和层具有基于 Nvidia 的 cuDNN 库的 GPU 优化实现。但是,此实现不支持屏蔽。如果您的模型使用掩码,那么这些层将回退到(慢得多)默认实现。请注意,优化的实现还要求您使用几个超参数的默认值:、、、、和。
所有接收掩码的图层都必须支持掩码(否则将引发异常)。这包括所有循环层,以及该层和其他一些层。任何支持遮罩的图层都必须具有等于 的属性。如果你想实现你自己的支持屏蔽的自定义层,你应该给方法添加一个参数(并且显然让方法以某种方式使用屏蔽)。此外,您应该在构造函数中设置。如果您的图层不以图层开头,则可以使用该图层:它将掩码设置为,这意味着最后一个维度全为零的时间步将在后续图层中被屏蔽(同样,只要维度存在)。
使用遮罩层和自动遮罩传播最适合简单模型。它并不总是适用于更复杂的模型,例如当您需要将层与循环层混合时。在这种情况下,您需要使用功能 API 或子类 API 显式计算掩码并将其传递给适当的层。例如,以下模型与之前的模型相同,不同之处在于它是使用功能 API 构建的并手动处理屏蔽:
经过几个 epoch 的训练,这个模型会变得非常擅长判断评论是否正面。如果您使用回调,您可以在学习时可视化 TensorBoard 中的嵌入:令人着迷的是,awesome和amazing之类的词逐渐聚集在嵌入空间的一侧,而awful和terrible之类的词则聚集在一起另一方面。有些词并不像你想象的那么积极(至少在这个模型中),比如好这个词,大概是因为许多负面评论都包含不好这个词。令人印象深刻的是,该模型能够仅根据 25,000 条电影评论学习有用的词嵌入。想象一下,如果我们有数十亿条评论要训练,嵌入会有多好!不幸的是我们没有,但也许我们可以重用在其他一些大型文本语料库(例如,维基百科文章)上训练的词嵌入,即使它不是由电影评论组成的?毕竟,无论您用它来谈论电影还是其他任何事情,惊人一词通常具有相同的含义。此外,即使嵌入是在另一项任务上训练的,也许嵌入对于情感分析也很有用:因为像真棒和惊人这样的词具有相似的含义,即使对于其他任务(例如,预测句子中的下一个词)。如果所有正面词和所有负面词都形成聚类,那么这将有助于情感分析。因此,与其使用这么多参数来学习词嵌入,不如看看我们是否不能只重用预训练的嵌入。由于像awesome和amazing这样的词具有相似的含义,即使对于其他任务(例如,预测句子中的下一个词),它们也可能会聚集在嵌入空间中。如果所有正面词和所有负面词都形成聚类,那么这将有助于情感分析。因此,与其使用这么多参数来学习词嵌入,不如看看我们是否不能只重用预训练的嵌入。由于像awesome和amazing这样的词具有相似的含义,即使对于其他任务(例如,预测句子中的下一个词),它们也可能会聚集在嵌入空间中。如果所有正面词和所有负面词都形成聚类,那么这将有助于情感分析。因此,与其使用这么多参数来学习词嵌入,不如看看我们是否不能只重用预训练的嵌入。
这TensorFlow Hub 项目使您可以轻松地在您自己的模型中重用预训练的模型组件。这些模型组件称为模块。只需浏览TF Hub 存储库,找到您需要的存储库,然后将代码示例复制到您的项目中,该模块将连同其预训练的权重一起被自动下载,并包含在您的模型中。简单的!
例如,让我们在情绪分析模型中使用句子嵌入模块,版本 1:
该层从给定的 URL 下载模块。这个特别模块是一个句子编码器:它将字符串作为输入,并将每个字符串编码为单个向量(在本例中为 50 维向量)。在内部,它解析字符串(在空格上拆分单词)并使用在一个巨大的语料库上预训练的嵌入矩阵嵌入每个单词:谷歌新闻 7B 语料库(70 亿字长!)。然后它计算所有词嵌入的平均值,结果就是句子嵌入。9然后我们可以添加两个简单的层来创建一个好的情绪分析模型。默认情况下,a是不可训练的,但您可以在创建它时进行设置以更改它,以便您可以针对您的任务对其进行微调。
警告
并非所有 TF Hub 模块都支持 TensorFlow 2,因此请确保选择支持的模块。
接下来,我们可以只加载 IMDb 评论数据集——无需对其进行预处理(批处理和预取除外)——并直接训练模型:
请注意,TF Hub 模块 URL 的最后一部分指定我们需要模型的版本 1。这种版本控制确保如果发布了新的模块版本,它不会破坏我们的模型。方便的是,如果您只是在 Web 浏览器中输入此 URL,您将获得此模块的文档。默认情况下,TF Hub 会将下载的文件缓存到本地系统的临时目录中。您可能更愿意将它们下载到更永久的目录中,以避免在每次系统清理后重新下载它们。为此,请将环境变量设置为您选择的目录(例如,)。
到目前为止,我们已经研究了时间序列、使用 Char-RNN 的文本生成,以及使用词级 RNN 模型的情感分析,训练我们自己的词嵌入或重用预训练的嵌入。现在让我们看看另一个重要的 NLP 任务:神经机器翻译(NMT),首先使用纯 Encoder-Decoder 模型,然后使用注意力机制对其进行改进,最后看看非凡的 Transformer 架构。
让我们看看一个简单的神经机器翻译模型10,它将英语句子翻译成法语(见图 16-3)。
简而言之,英语句子被馈送到编码器,解码器输出法语翻译。请注意,法语翻译也用作解码器的输入,但向后移动了一步。换句话说,解码器的输入是它应该在上一步输出的单词(不管它实际输出什么)。对于第一个单词,它被赋予序列开始 (SOS) 标记。解码器应以序列结束(EOS)令牌。
请注意,英文句子在输入到编码器之前会被反转。例如,我喝牛奶反转为牛奶喝我。这确保了英文句子的开头将最后馈送到编码器,这很有用,因为这通常是解码器需要翻译的第一件事。
每个单词最初由其 ID 表示(例如,单词milk的 288)。接下来,一个层返回词嵌入。这些词嵌入实际上是馈送到编码器和解码器的。
图 16-3。一个简单的机器翻译模型
在每一步,解码器为输出词汇表中的每个单词(即法语)输出一个分数,然后softmax 层将这些分数转化为概率。例如,在第一步,单词Je可能有 20% 的概率,Tu可能有 1% 的概率,以此类推。输出概率最高的单词。这与常规分类任务非常相似,因此您可以使用损失来训练模型,就像我们在 Char-RNN 模型中所做的那样。
请注意,在推理时(训练后),您不会将目标句子提供给解码器。相反,只需向解码器提供它在上一步输出的单词,如图 16-4所示(这将需要一个图中未显示的嵌入查找)。
图 16-4。在推理时将前一个输出词作为输入提供
好的,现在你有了大局。不过,如果你实现这个模型,还有一些细节需要处理:
到目前为止,我们假设所有输入序列(到编码器和解码器)都具有恒定长度。但显然句子长度不同。由于正则张量具有固定的形状,它们只能包含相同长度的句子。如前所述,您可以使用掩码来处理此问题。但是,如果句子的长度非常不同,您不能像我们在情感分析中那样只裁剪它们(因为我们想要完整的翻译,而不是裁剪的翻译)。相反,将句子分组到相似长度的桶中(例如,一个桶用于 1 到 6 个单词的句子,另一个用于 7 到 12 个单词的句子,等等),对较短的序列使用填充以确保所有句子在桶中具有相同的长度(查看此功能)。例如,我喝牛奶变成
我们希望忽略 EOS 代币之后的任何输出,因此这些代币不应造成损失(它们必须被屏蔽掉)。例如,如果模型输出Je bois du lait
当输出词汇量很大时(这里就是这种情况),为每个可能的单词输出一个概率会非常慢。如果目标词汇表包含 50,000 个法语单词,那么解码器将输出 50,000 维向量,然后在如此大的向量上计算 softmax 函数将是非常计算密集的。为避免这种情况,一种解决方案是仅查看模型输出的 logits 以查找正确的单词和不正确单词的随机样本,然后仅基于这些 logits 计算损失的近似值。这个?Sébastien Jean 等人在 2015 年引入了采样 softmax技术。.?11在 TensorFlow 中,您可以在训练期间使用该函数,并在推理时使用普通的 softmax 函数(采样的 softmax 不能在推理时使用,因为它需要知道目标)。
这TensorFlow Addons 项目包含许多序列到序列工具,可让您轻松构建生产就绪的编码器-解码器。例如,以下代码创建了一个基本的编码器-解码器模型,类似于图 16-3 所示的模型:
代码大部分是不言自明的,但有几点需要注意。首先,我们在创建层时进行设置,以便我们可以得到它的最终隐藏状态并将其传递给解码器。由于我们使用的是 LSTM 单元,它实际上返回两个隐藏状态(短期和长期)。这是 TensorFlow Addons 中可用的几个采样器之一:它们的作用是在每一步告诉解码器它应该假装以前的输出是什么。在推理过程中,这应该是实际输出的令牌的嵌入。训练时,应该是之前目标token的embedding:这就是我们使用.?在实践中,从上一个时间步的目标嵌入开始训练并逐渐过渡到使用上一步输出的实际令牌的嵌入通常是一个好主意。Samy Bengio 等人在2015 年的一篇论文12中介绍了这个想法。将在目标或实际输出之间随机选择,您可能会在训练期间逐渐改变。
一个每个时间步长,一个常规的循环层在生成其输出之前只查看过去和现在的输入。换句话说,它是因果的,这意味着它无法展望未来。这种类型的 RNN 在预测时间序列时很有意义,但对于许多 NLP 任务,例如神经机器翻译,通常最好在编码给定单词之前先查看下一个单词。例如,考虑短语英国女王、红心女王和蜂王:要正确编码女王这个词,你需要向前看。为了实现这一点,在相同的输入上运行两个循环层,一个从左到右读取单词,另一个从右到左读取它们。然后在每个时间步简单地组合它们的输出,通常是通过连接它们。这个称为双向循环层(见图 16-5)。
要在 Keras 中实现双向循环层,请将循环层包装在层中。例如,以下代码创建一个双向层:
笔记
该层将创建该层的克隆(但方向相反),它将同时运行并连接它们的输出。因此,尽管该层有 10 个单位,但该层将在每个时间步输出 20 个值。
图 16-5。双向循环层
认为你训练了一个 Encoder-Decoder 模型,并用它来翻译法语句子Comment vas-tu??到英语。您希望它会输出正确的翻译(你好吗?),但不幸的是它会输出你好吗??查看训练集,您会注意到许多句子,例如Comment vas-tu jouer??翻译为你将如何玩??所以模型在看到Comment vas后输出How will并不荒谬。不幸的是,在这种情况下,这是一个错误,模型无法返回并修复它,因此它试图尽其所能完成句子。通过在每一步都贪婪地输出最有可能的单词,它最终得到了一个次优的翻译。我们如何让模型有机会返回并修复它之前犯下的错误?最常见的解决方案之一是波束搜索:它跟踪k最有希望的句子的简短列表(例如,前三个),并且在每个解码器步骤中,它尝试将它们扩展一个单词,只保留k最有可能的句子。参数k被称为光束宽度。
例如,假设您使用模型翻译句子Comment vas-tu??使用波束宽度为 3 的波束搜索。在第一个解码器步骤中,模型将输出每个可能单词的估计概率。假设前三个词是How(75% 的估计概率)、What(3%)和You(1%)。到目前为止,这是我们的短名单。接下来,我们创建模型的三个副本,并使用它们来查找每个句子的下一个单词。每个模型将输出词汇表中每个单词的一个估计概率。第一个模型将尝试在句子How中找到下一个单词,也许它会输出单词will的概率为 36%,单词are的概率为 32%,单词do的概率为 16% , 等等。笔记鉴于句子以如何开头,这些实际上是条件概率。第二个模型将尝试完成句子What;它可能会为单词are输出 50% 的条件概率,依此类推。假设词汇表有 10,000 个单词,每个模型将输出 10,000 个概率。
接下来,我们计算这些模型考虑的 30,000 个两词句子中的每一个的概率 (3 × 10,000)。我们通过将每个单词的估计条件概率乘以它完成的句子的估计概率来做到这一点。例如,句子How的估计概率是 75%,而单词will(假设第一个词是How)的估计条件概率是 36%,所以句子将如何是 75% × 36% = 27%。在计算了所有 30,000 个两词句子的概率后,我们只保留前 3 个。也许它们都以How一词开头:How will(27%)、How are(24%)和怎么做(12%)。眼下,How will这句话胜出,但How are并没有被淘汰。
然后我们重复相同的过程:我们使用三个模型来预测这三个句子中的每一个中的下一个单词,并计算我们考虑的所有 30,000 个三词句子的概率。现在,前三名可能是你好吗(10%)、你好吗(8%)和你好吗(2%)。在下一步,我们可能会得到你好吗(7%)、你好吗
您可以使用 TensorFlow Addons 相当轻松地实现波束搜索:
我们首先创建一个,它包装了所有解码器克隆(在本例中为 10 个克隆)。然后我们为每个解码器克隆创建一个编码器最终状态的副本,并将这些状态连同开始和结束标记一起传递给解码器。
有了这一切,您可以获得相当短的句子的良好翻译(特别是如果您使用预训练的词嵌入)。不幸的是,这个模型在翻译长句子方面真的很糟糕。再一次,问题来自于 RNN 有限的短期记忆。注意力机制是解决这个问题的改变游戏规则的创新。
考虑图 16-3中从单词milk到其翻译lait的路径:很长!这意味着这个词的表示(连同所有其他词)在实际使用之前需要经过许多步骤。我们不能让这条路更短吗?
这是Dzmitry Bahdanau 等人2014 年开创性论文13中的核心思想。他们引入了一种技术,允许解码器在每个时间步专注于适当的词(由编码器编码)。例如,在解码器需要输出单词lait的时间步,它会将注意力集中在单词milk上。这意味着从输入词到翻译的路径现在要短得多,因此 RNN 的短期记忆限制影响要小得多。注意力机制彻底改变了神经机器翻译(以及一般的 NLP),从而显着改进了现有技术,尤其是对于长句子(超过 30 个单词)。14
图 16-6显示了这个模型的架构(稍微简化,我们将看到)。在左侧,您有编码器和解码器。我们现在不只是将编码器的最终隐藏状态发送给解码器(尽管图中没有显示,但仍然完成),我们现在将其所有输出发送给解码器。在每个时间步,解码器的存储单元计算所有这些编码器输出的加权和:这决定了它在这一步将关注哪些单词。权重α?(?t?,?i?)是第i个编码器输出在第t个解码器时间步的权重。例如,如果权重α?(3,2)比权重α?(3,0)和α?(3,1)大得多,那么解码器将比其他两个词更关注第 2 个词(牛奶),至少在这个时间步.?解码器的其余部分就像之前一样工作:在每个时间步,存储单元接收我们刚刚讨论过的输入,加上前一个时间步的隐藏状态,最后(尽管图中没有表示)它接收目标来自上一个时间步的单词(或在推理时,来自上一个时间步的输出)。
图 16-6。使用带有注意模型的编码器-解码器网络的神经机器翻译
但是这些α?(?t?,?i?)权重是从哪里来的呢?它实际上非常简单:它们是由一种称为对齐模型(或注意力层)的小型神经网络生成的,它与编码器-解码器模型的其余部分联合训练。这种对齐模型在图 16-6的右侧进行了说明。它从具有单个神经元的时间分布层15开始,该神经元接收所有编码器输出作为输入,并与解码器的先前隐藏状态(例如, h?(2))连接。该层为每个编码器输出(例如,e(3, 2)?):这个分数衡量每个输出与解码器先前隐藏状态的对齐程度。最后,所有分数都经过一个 softmax 层以获得每个编码器输出的最终权重(例如,α?(3,2))。给定解码器时间步长的所有权重加起来为 1(因为 softmax 层不是时间分布的)。这种特殊的注意力机制被称为Bahdanau attention(以论文的第一作者命名)。由于它将编码器输出与解码器先前的隐藏状态连接起来,因此有时称为连接注意(或附加注意)。
笔记
如果输入句子有n 个单词长,并且假设输出句子大约一样长,那么这个模型将需要计算大约n?2 个权重。幸运的是,这种二次计算复杂度仍然易于处理,因为即使是长句子也没有数千个单词。
不久之后,Minh-Thang Luong 等人在2015 年的一篇论文16中提出了另一种常见的注意力机制。因为注意力机制的目标是测量编码器的一个输出与解码器先前的隐藏状态之间的相似性,所以作者建议简单地计算这两个向量的点积(见第 4 章),因为这通常是一个相当好的相似性度量,现代硬件可以更快地计算它。为此,两个向量必须具有相同的维度。这个被称为Luong attention(同样,在论文的第一作者之后),有时也称为乘法注意力。点积给出一个分数,所有分数(在给定的解码器时间步长)通过一个 softmax 层给出最终的权重,就像在 Bahdanau attention 中一样。他们提出的另一个简化是在当前时间步而不是在前一个时间步使用解码器的隐藏状态(即,h?(?t?)而不是h?(?t?–1)),然后使用注意力机制的输出(著名的h~(t)) 直接计算解码器的预测(而不是使用它来计算解码器的当前隐藏状态)。他们还提出了一种点积机制的变体,其中编码器输出在计算点积之前首先经过线性变换(即,没有偏置项的时间分布层)。这称为通用点积方法。他们将两种点积方法与级联注意机制(添加一个重新缩放的参数向量v?)进行了比较,他们观察到点积变体的性能优于级联注意。出于这个原因,现在很少使用连接注意力。这三种注意力机制的方程式总结在方程式 16-1中。
公式 16-1。注意力机制
?以下是使用 TensorFlow Addons 将 Luong 注意力添加到编码器-解码器模型的方法:
我们简单地将解码器单元包装在一个 中,并提供所需的注意力机制(本例中的 Luong 注意力)。
注意力机制现在被用于各种目的。他们在 NMT 之外的第一个应用是使用视觉注意生成图像说明:17卷积神经网络首先处理图像并输出一些特征图,然后配备注意机制的解码器 RNN 生成说明,一次一个词。在每个解码器时间步(每个单词),解码器使用注意力模型来关注图像的正确部分。例如,在图 16-7中,模型生成了标题A woman is throwing a frisbee in a park,你可以看到解码器在输出这个词时将注意力集中在输入图像的哪一部分飞盘:显然,它的大部分注意力都集中在飞盘上。
图 16-7。视觉注意:输入图像(左)和模型在产生单词frisbee之前的焦点(右)18
可解释性
一注意力机制的额外好处是,它们更容易理解导致模型产生输出的原因。这称为可解释性。当模型出现错误时,它可能特别有用:例如,如果将狗在雪地中行走的图像标记为狼在雪地中行走,那么您可以返回并检查模型何时关注什么它输出单词wolf。你可能会发现它不仅关注了狗,还关注了雪,这暗示了一个可能的解释:也许模型学习区分狗和狼的方式是通过检查周围是否有很多雪。然后,您可以通过使用更多没有雪的狼和有雪的狗的图像训练模型来解决此问题。这个例子来自一篇很棒的2016 年论文19?Marco Tulio Ribeiro 等人。它使用不同的可解释性方法:围绕分类器的预测在本地学习可解释的模型。
在某些应用程序中,可解释性不仅仅是调试模型的工具;这可能是一项法律要求(想想一个决定是否应该向您提供贷款的系统)。
注意机制非常强大,您实际上可以仅使用注意机制来构建最先进的模型。
在在2017 年的一篇开创性论文中,20谷歌研究人员团队提出注意力就是你所需要的一切。他们设法创建了一个名为Transformer的架构,它在不使用任何循环或卷积层的情况下显着改进了 NMT 的最新技术,仅使用了21 个注意力机制(加上嵌入层、密集层、归一化层和其他一些零碎的东西) )。作为一个额外的好处,这种架构的训练速度也快得多,并且更容易并行化,因此他们设法以以前最先进模型的一小部分时间和成本来训练它。
Transformer 架构如图 16-8 所示。
图 16-8。Transformer 架构22
让我们看一下这个图:
左边的部分是编码器。就像之前一样,它将一批表示为单词 ID 序列的句子作为输入(输入形状为 [?batch size?,?max input sentence length?]),并将每个单词编码为 512 维表示(因此编码器的输出形状是[批量大小,最大输入句子长度,512])。请注意,编码器的顶部堆叠了N次(在论文中,N= 6)。
右手部分是解码器。在训练期间,它将目标句子作为输入(也表示为单词 ID 序列),向右移动一个时间步长(即,在开头插入一个序列开始标记)。它还接收编码器的输出(即来自左侧的箭头)。请注意,解码器的顶部也堆叠了N次,并且编码器堆栈的最终输出在这N层中的每一层都被馈送到解码器。就像之前一样,解码器在每个时间步为每个可能的下一个单词输出一个概率(它的输出形状是[批量大小,最大输出句子长度,词汇长度])。
在推理过程中,无法为解码器提供目标,因此我们向其提供先前输出的单词(从序列开始标记开始)。所以模型需要被重复调用,每轮预测一个单词(在下一轮被馈送到解码器,直到输出序列结束标记)。
仔细观察,你会发现你已经熟悉了大多数组件:有两个嵌入层,5×?N跳过连接,每个都后面跟着一个层归一化层,2×?N?前馈模块,它们由两个密集层(第一个使用 ReLU 激活函数,第二个没有激活函数),最后输出层是使用 softmax 激活函数的密集层。所有这些层都是时间分布的,因此每个单词都独立于所有其他单词进行处理。但是我们如何一次只看一个单词来翻译一个句子呢?好吧,这就是新组件的用武之地:
这编码器的Multi-Head Attention层对同一个句子中每个单词与其他单词的关系进行编码,更加关注最相关的单词。例如,他们欢迎英国女王这句话中女王一词的这一层的输出将取决于句子中的所有词,但它可能会更关注联合这个词和王国,而不是他们或欢迎。这个注意机制称为self-attention(句子正在关注自身)。我们将很快讨论它是如何工作的。这解码器的Masked Multi-Head Attention层做同样的事情,但每个词只允许关注位于它之前的词。最后,解码器的上层 Multi-Head Attention 层是解码器关注输入句子中单词的地方。例如,解码器在即将输出这个词的翻译时,很可能会密切关注输入句子中的Queen这个词。
位置编码是简单的密集向量(很像词嵌入),表示一个词在句子中的位置。第n个位置编码被添加到每个句子中第n个词的词嵌入中。这使模型可以访问每个单词的位置,这是因为 Multi-Head Attention 层不考虑单词的顺序或位置;他们只看他们的关系。由于所有其他层都是时间分布的,因此它们无法知道每个单词的位置(相对或绝对)。显然,相对和绝对的单词位置很重要,所以我们需要以某种方式将这些信息提供给 Transformer,而位置编码是一个很好的方法。
让我们仔细看看 Transformer 架构的这两个新组件,从位置编码开始。
位置编码是一个密集向量,它对句子中单词的位置进行编码:第i个位置编码只是简单地添加到句子中第i个单词的词嵌入中。这些位置编码可以通过模型学习,但在论文中作者更喜欢使用固定位置编码,使用不同频率的正弦和余弦函数定义。位置编码矩阵P(也称为PE)在公式 16-2中定义并表示在图 16-9的顶部(转置),其中P?p?,?i是第i个位于句子中第 p个位置的单词的编码分量。
公式 16-2。正弦/余弦位置编码
PEp,i=sin(p/10000i/d)if?i?is evencos(p/10000(i-1)/d)if?i?is odd
图 16-9。正弦/余弦位置编码矩阵(转置,顶部),重点关注i的两个值(底部)
这个解决方案提供了与学习位置编码相同的性能,但它可以扩展到任意长的句子,这就是它受到青睐的原因。在将位置编码添加到词嵌入之后,模型的其余部分可以访问句子中每个词的绝对位置,因为每个位置都有唯一的位置编码(例如,位于句子中的第 22 个位置由图 16-9左下角的垂直虚线表示,可以看出它是该位置唯一的)。此外,振荡函数(正弦和余弦)的选择使模型也可以学习相对位置。例如,相隔 38 个单词的单词(例如,在位置p= 22 和p = 60) 在编码维度i= 100 和i= 101?中始终具有相同的位置编码值,如图 16-9 所示。这就解释了为什么我们需要每个频率的正弦和余弦:如果我们只使用正弦(i= 100 处的蓝色波),模型将无法区分位置p= 22 和p= 35(标记为穿过)。
TensorFlow中没有层,但很容易创建一个。出于效率原因,我们在构造函数中预先计算了位置编码矩阵(因此我们需要知道最大句子长度,以及每个单词表示的维数,)。然后该方法将此编码矩阵裁剪为输入的大小,并将其添加到输入中。由于我们在创建位置编码矩阵时添加了大小为 1 的额外第一维,因此广播规则将确保将矩阵添加到输入中的每个句子中:
然后我们可以创建 Transformer 的第一层:
现在让我们深入了解 Transformer 模型的核心:Multi-Head Attention 层。
至了解 Multi-Head Attention 层的工作原理,首先要了解Scaled Dot-Product Attention层,它是基于它的。假设编码器分析了输入句子他们下棋,它设法理解单词他们是主语,单词played是动词,因此它将这些信息编码到这些单词的表示中。现在假设解码器已经翻译了主语,它认为接下来应该翻译动词。为此,它需要从输入句子中获取动词。这类似于字典查找:就好像编码器创建了一个字典 {subject: They, verb: played, ...} 并且解码器想要查找对应于键的值动词。?但是,该模型没有离散的标记来表示键(如主题或动词);它具有这些概念的矢量化表示(它在训练期间学习),询问) 不会完全匹配字典中的任何键。解决方案是计算查询和字典中每个键之间的相似度度量,然后使用 softmax 函数将这些相似度分数转换为加起来为 1 的权重。如果表示动词的键是迄今为止最相似的对于查询,那么该键的权重将接近 1。然后模型可以计算相应值的加权和,因此如果动词键的权重接近 1,那么加权和将非常接近到played这个词的表示。简而言之,您可以将整个过程视为可区分的字典查找。Transformer 使用的相似性度量只是点积,就像 Luong attention 中一样。事实上,这个方程和 Luong attention 的方程是一样的,除了一个比例因子。方程 16-3,矢量化形式。
公式 16-3。缩放点积注意力
在这个等式中:
Q是一个矩阵,每个查询包含一行。它的形状是[?n个查询,d个键],其中n个查询是查询的数量,d个键是每个查询和每个键的维数。
K是一个矩阵,每个键包含一行。它的形状是 [?n?keys?,?d?keys?],其中n?keys是键和值的数量。
V是一个矩阵,每个值包含一行。它的形状是 [?n?keys?,?d?values?],其中d?values是每个值的维数。
Q?K的形状是 [?n?queries?,?n?keys?]:它包含每个 query/key 对的相似度分数。softmax函数的输出具有相同的形状,但所有行总和为1。最终输出的形状为[?n个查询,d个值]:每个查询有一行,其中每一行代表查询结果(a值的加权和)。
缩放因子缩小相似度分数以避免使 softmax 函数饱和,这会导致微小的梯度。
在计算 softmax 之前,可以通过在相应的相似性分数上添加一个非常大的负值来屏蔽一些键/值对。这在 Masked Multi-Head Attention 层中很有用。
在编码器中,这个等式适用于批处理中的每个输入句子,其中Q、K和V都等于输入句子中的单词列表(因此句子中的每个单词将与同一句子中的每个单词进行比较句子,包括它自己)。类似地,在解码器的 masked attention 层中,方程将应用于批处理中的每个目标句子,其中Q、K和V都等于目标句子中的单词列表,但这次使用掩码来防止任何单词与位于它之后的单词进行比较(在推理时,解码器只能访问它已经输出的单词,而不是未来话,所以在训练期间我们必须屏蔽未来的输出标记)。在解码器的上层注意力层中,键K和值V只是编码器生成的单词编码列表,查询Q是解码器生成的单词编码列表。
该层实现了 Scaled Dot-Product Attention,有效地将公式 16-3应用于批量中的多个句子。它的输入就像Q,K和V,除了有一个额外的批次维度(第一个维度)。
小费
在 TensorFlow 中,如果和是具有两个以上维度的张量(例如,形状分别为 [2, 3, 4, 5] 和 [2, 3, 5, 6]),那么会将这些张量视为 2 × 3 数组,其中每个单元格包含一个矩阵,它将与相应的矩阵相乘: 中的第i行第j列的矩阵将乘以 中的第i行第j列的矩阵。由于 4 × 5 矩阵与 5 × 6 矩阵的乘积是 4 × 6 矩阵,因此将返回形状为 [2, 3, 4, 6] 的数组。
如果我们忽略跳过连接、层归一化层、前馈块,以及这是 Scaled Dot-Product Attention,而不是 Multi-Head Attention 的事实,那么 Transformer 模型的其余部分可以这样实现:
该参数创建了一个额外的参数,让层学习如何正确缩小相似度分数。这与 Transformer 模型有点不同,后者总是将相似度分数缩小相同的因子(dkeys)。创建第二个注意层时的参数确保每个输出标记只关注先前的输出标记,而不是未来的输出标记。
现在是时候看看最后一块拼图了:什么是 Multi-Head Attention 层?其架构如图 16-10所示。
图 16-10。Multi-Head Attention 层架构23
如您所见,它只是一堆 Scaled Dot-Product Attention 层,每个层前面都有值、键和查询的线性变换(即时间分布没有激活函数的层)。所有的输出都被简单地连接起来,它们经过最终的线性变换(同样是时间分布的)。但为什么?这种架构背后的直觉是什么?好吧,考虑一下我们之前讨论过的下棋这个词(在他们下棋这句话中)。编码器足够聪明,可以编码它是动词的事实。但是由于位置编码,单词表示还包括它在文本中的位置,并且它可能包括许多其他对其翻译有用的特征,例如它是过去时的事实。简而言之,单词表示编码了单词的许多不同特征。如果我们只使用单个 Scaled Dot-Product Attention 层,我们将只能一次查询所有这些特征。这就是为什么多头注意力层对值、键和查询应用多个不同的线性变换的原因:这允许模型将单词表示的许多不同投影应用到不同的子空间中,每个子空间都专注于单词特征的一个子集。也许其中一个线性层会将单词表示投影到一个子空间中,其中只剩下单词是动词的信息,另一个线性层将仅提取它是过去时的事实,依此类推。然后 Scaled Dot-Product Attention 层执行查找阶段,最后我们将所有结果连接起来并将它们投影回原始空间。这允许模型将单词表示的许多不同投影应用到不同的子空间中,每个子空间都专注于单词特征的一个子集。也许其中一个线性层会将单词表示投影到一个子空间中,其中只剩下单词是动词的信息,另一个线性层将仅提取它是过去时的事实,依此类推。然后 Scaled Dot-Product Attention 层执行查找阶段,最后我们将所有结果连接起来并将它们投影回原始空间。这允许模型将单词表示的许多不同投影应用到不同的子空间中,每个子空间都专注于单词特征的一个子集。也许其中一个线性层会将单词表示投影到一个子空间中,其中只剩下单词是动词的信息,另一个线性层将仅提取它是过去时的事实,依此类推。然后 Scaled Dot-Product Attention 层执行查找阶段,最后我们将所有结果连接起来并将它们投影回原始空间。另一个线性层将仅提取它是过去时的事实,依此类推。然后 Scaled Dot-Product Attention 层执行查找阶段,最后我们将所有结果连接起来并将它们投影回原始空间。另一个线性层将仅提取它是过去时的事实,依此类推。然后 Scaled Dot-Product Attention 层执行查找阶段,最后我们将所有结果连接起来并将它们投影回原始空间。
在撰写本文时,没有可用于 TensorFlow 2 的类或类。但是,您可以查看 TensorFlow 的关于构建用于语言理解的 Transformer 模型的精彩教程。此外,TF Hub 团队目前正在将几个基于 Transformer 的模块移植到 TensorFlow 2,它们应该很快就会推出。同时,我希望我已经证明了自己实现一个 Transformer 并不难,这当然是一个很好的练习!
这2018 年被称为NLP 的 ImageNet 时刻:进展令人震惊,越来越大的 LSTM 和基于 Transformer 的架构在庞大的数据集上进行了训练。我强烈建议您查看以下所有发表于 2018 年的论文:
Matthew Peters的ELMo 论文24介绍了语言模型中的嵌入(ELMo):这些是从深度双向语言模型的内部状态中学习的上下文化词嵌入。例如,queen这个词在Queen of the United Kingdom和queen bee中的嵌入是不同的。
Jeremy Howard 和 Sebastian Ruder的ULMFiT 论文25证明了无监督预训练对 NLP 任务的有效性:作者使用自监督学习(即从数据中自动生成标签)在一个巨大的文本语料库上训练了一个 LSTM 语言模型,然后他们在各种任务上对其进行了微调。他们的模型在六个文本分类任务上大大优于现有技术(在大多数情况下将错误率降低了 18-24%)。此外,他们表明,通过仅在 100 个标记示例上微调预训练模型,他们可以获得与在 10,000 个示例上从头开始训练的模型相同的性能。
Alec Radford 和其他 OpenAI 研究人员的GPT 论文26也证明了无监督预训练的有效性,但这次使用的是类似 Transformer 的架构。作者在大型数据集上预训练了一个大型但相当简单的架构,该架构由 12 个 Transformer 模块(仅使用 Masked Multi-Head Attention 层)组成,再次使用自监督学习进行训练。然后他们在各种语言任务上对其进行微调,对每项任务只使用较小的调整。这任务非常多样化:它们包括文本分类、蕴涵(句子 A 是否蕴涵句子 B)、27相似度(例如,今天天气好与天气晴朗非常相似)和问答(给定几段文本给出一些上下文,模型必须回答一些选择题)。仅仅几个月后,也就是 2019 年 2 月,Alec Radford、Jeffrey Wu 和其他 OpenAI 研究人员发表了GPT-2 论文,28提出了一个非常相似的架构,但更大(超过 15 亿个参数!),他们表明它可以在许多任务上取得良好的性能而无需任何微调。这个被称为零样本学习(ZSL)。http://github.com/openai/gpt-2上提供了 GPT-2 模型的较小版本(仅1.17 亿个参数)及其预训练的权重。
Jacob Devlin 和其他 Google 研究人员的BERT 论文29还展示了在大型语料库上进行自我监督预训练的有效性,使用了与 GPT 类似的架构,但没有屏蔽多头注意力层(如 Transformer 的编码器中)。这意味着模型自然是双向的;因此 BERT 中的 B(来自 Transformers 的双向编码器表示)。最重要的是,作者提出了两个预训练任务来解释模型的大部分优势:
掩码语言模型 (MLM)
每个句子中的单词有 15% 的概率被掩盖,并且训练模型来预测被掩盖的单词。例如,如果原始句子是她在生日聚会上玩得很开心,那么模型可能会被赋予句子她
下一句预测(NSP)
该模型被训练来预测两个句子是否连续。例如,它应该预测狗睡觉和它大声打鼾是连续的句子,而狗睡觉和地球绕太阳运行不是连续的。这是一项具有挑战性的任务,当模型在问答或蕴涵等任务上进行微调时,它会显着提高模型的性能。
如您所见,2018 年和 2019 年的主要创新是更好的子词标记化,从 LSTM 转向 Transformers,并使用自监督学习预训练通用语言模型,然后通过很少的架构更改(或根本没有更改)对它们进行微调)。事情进展得很快;没有人能说明年哪种架构会占上风。今天,显然是变形金刚,但明天可能是 CNN(例如,查看Maha Elbayad 等人在2018 年发表的论文30 ,研究人员在该论文中使用掩码 2D 卷积层进行序列到序列任务)。或者它甚至可能是 RNN,如果它们出人意料地卷土重来(例如,查看2018 年的论文31李帅等人。这表明,通过在给定的 RNN 层中使神经元相互独立,可以训练更深的 RNN,从而能够学习更长的序列)。
在下一章中,我们将讨论如何使用自动编码器以无监督的方式学习深度表示,我们将使用生成对抗网络(GAN)来生成图像等等!
使用有状态 RNN 与无状态 RNN 的优缺点是什么?
为什么人们使用编码器-解码器 RNN 而不是普通的序列到序列 RNN 进行自动翻译?
你如何处理可变长度的输入序列?那么可变长度的输出序列呢?
什么是束搜索,为什么要使用它?你可以使用什么工具来实现它?
什么是注意力机制?它有什么帮助?
Transformer 架构中最重要的层是什么?它的目的是什么?
什么时候需要使用采样的 softmax?
嵌入式 Reber 语法是Hochreiter 和 Schmidhuber 在他们关于 LSTM 的论文中使用。它们是生成诸如BPBTSXXVPSEPE之类的字符串的人工语法。查看 Jenny Orr对该主题的精彩介绍。选择一种特定的嵌入式 Reber 语法(例如 Jenny Orr 页面上表示的那个),然后训练一个 RNN 来识别字符串是否符合该语法。您首先需要编写一个能够生成训练批次的函数,其中包含大约 50% 符合语法的字符串,以及 50% 不符合语法的字符串。
训练可以将日期字符串从一种格式转换为另一种格式的编码器-解码器模型(例如,从2019 年 4 月 22 日到2019-04-22)。
阅读 TensorFlow 的注意力神经机器翻译教程。
使用最近的一种语言模型(例如,GPT)来生成更有说服力的莎士比亚文本。
附录 A中提供了这些练习的解决方案。
1Alan Turing,计算机与智能,Mind?49 (1950): 433–460。
2当然,聊天机器人这个词出现的时间要晚得多。图灵将他的测试称为模仿游戏:机器 A 和人类 B 通过短信与人类审讯者 C 聊天;审讯者提出问题以确定哪一台是机器(A 或 B)。机器如果能愚弄审讯者,则通过测试,而人类 B 必须尝试帮助审讯者。
3根据定义,平稳时间序列的均值、方差和自相关(即,由给定间隔分隔的时间序列中的值之间的相关性)不会随时间变化。这是相当严格的;例如,它排除了具有趋势或周期性模式的时间序列。RNN 更宽容,因为它们可以学习趋势和周期性模式。
4Taku Kudo,子词正则化:使用多个候选子词改进神经网络翻译模型,arXiv 预印本 arXiv:1804.10959 (2018)。
5Taku Kudo 和 John Richardson,SentencePiece:用于神经文本处理的简单且与语言无关的子词标记器和去标记器,arXiv 预印本 arXiv:1808.06226(2018 年)。
6Rico Sennrich 等人,带有子词单元的稀有词的神经机器翻译?,计算语言学协会第 54 届年会论文集1(2016 年):1715-1725。
7Yonghui Wu 等人,Google 的神经机器翻译系统:弥合人类和机器翻译之间的差距,arXiv 预印本 arXiv:1609.08144 (2016)。
8它们的 ID 为 0 只是因为它们是数据集中出现频率最高的词。确保填充标记始终编码为 0 可能是一个好主意,即使它们不是最频繁的。
9准确地说,句子嵌入等于平均词嵌入乘以句子中词数的平方根。这弥补了n 个向量的平均值随着n的增长而变短的事实。
10Ilya Sutskever 等人,Sequence to Sequence Learning with Neural Networks,arXiv 预印本 arXiv:1409.3215 (2014)。
11Sébastien Jean 等人,On Using Very Large Target Vocabulary for Neural Machine Translation?,计算语言学协会第 53 届年会和亚洲自然语言处理联合会第 7 届自然语言处理国际联合会议论文集1 (2015 年):1-10。
12Samy Bengio 等人,使用循环神经网络进行序列预测的调度采样,arXiv 预印本 arXiv:1506.03099 (2015)。
13Dzmitry Bahdanau 等人,Neural Machine Translation by Jointly Learning to Align and Translate,arXiv 预印本 arXiv:1409.0473 (2014)。
14NMT中最常用的指标是双语评估研究 (BLEU) 分数,它将模型产生的每个翻译与人类产生的几个好的翻译进行比较:它计算出现在任何目标翻译,并调整分数以考虑目标翻译中生成的n-?gram 的频率。
15回想一下,时间分布层等效于您在每个时间步独立应用的常规层(只是快得多)。
16Minh-Thang Luong 等人,基于注意力的神经机器翻译的有效方法?,2015 年自然语言处理经验方法会议论文集(2015 年):1412-1421。
17Kelvin Xu 等人,展示、参加和讲述:具有视觉注意力的神经图像字幕生成?,第 32 届机器学习国际会议论文集(2015 年):2048-2057。
18这是论文中图 3 的一部分。经作者善意授权转载。
19Marco Tulio Ribeiro 等人,我为什么要相信你?:解释任何分类器的预测?,第 22 届 ACM SIGKDD 知识发现和数据挖掘国际会议论文集(2016 年):1135-1144。
20Ashish Vaswani 等人,Attention is All You Need?,第 31 届神经信息处理系统国际会议论文集(2017 年):6000-6010。
21由于 Transformer 使用时间分布层,您可以说它使用内核大小为 1 的一维卷积层。
22这是论文中的图 1,经作者授权转载。
23这是论文中图 2 的右侧部分,经作者授权转载。
24Matthew Peters 等人,Deep Contextualized Word Representations?,计算语言学协会北美分会 2018 年会议论文集:人类语言技术1(2018):2227–2237。
25Jeremy Howard 和 Sebastian Ruder,文本分类的通用语言模型微调?,计算语言学协会第 56 届年会论文集1(2018):328–339。
26Alec Radford 等人,通过生成式预训练提高语言理解(2018 年)。
27例如,句子Jane 在她朋友的生日聚会上玩得很开心包含Jane 很享受聚会,但它与Everyone hated the聚会相矛盾,与地球是平的无关。
28Alec Radford 等人,语言模型是无监督的多任务学习者(2019 年)。
29Jacob Devlin 等人,BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding?,计算语言学协会北美分会 2018 年会议论文集:人类语言技术1(2019 年)。
30Maha Elbayad 等人,Pervasive Attention: 2D Convolutional Neural Networks for Sequence-to-Sequence Prediction,arXiv 预印本 arXiv:1808.03867 (2018)。
31Shuai Li 等人,独立循环神经网络 (IndRNN):构建更长更深的 RNN?,IEEE 计算机视觉和模式识别会议论文集(2018):5457-5466。