BERT
BERT:从 Decoder 到 Encoder
OpenAI Transformer 为我们提供了一个基于 Transformer 的可以微调的预训练网络。但是在把 LSTM 换成 Transformer 的过程中,有些东西丢失了。ELMo 的语言模型是双向的,但 OpenAI Transformer 只训练了一个前向的语言模型。我们是否可以构建一个基于 Transformer 的语言模型,它既向前看,又向后看(用技术术语来说 - 融合上文和下文的信息)。
Masked Language Model(MLM 语言模型)
那么如何才能像 LSTM 那样,融合上文和下文的双向信息呢?
一种直观的想法是使用 Transformer 的 Encoder。但是 Encoder 的 Self Attention 层,每个 token 会把大部分注意力集中到自己身上,那么这样将容易预测到每个 token,模型学不到有用的信息。BERT 提出使用 mask,把需要预测的词屏蔽掉。 BERT mask
BERT 在语言建模任务中,巧妙地屏蔽了输入中 15% 的单词,并让模型预测这些屏蔽位置的单词。
找到合适的任务来训练一个 Transformer 的 Encoder 是一个复杂的问题,BERT 通过使用早期文献中的 “masked language model” 概念(在这里被称为完形填空)来解决这个问题。
除了屏蔽输入中 15% 的单词外, BERT 还混合使用了其他的一些技巧,来改进模型的微调方式。例如,有时它会随机地用一个词替换另一个词,然后让模型预测这个位置原来的实际单词。
两个句子的任务
如果你回顾 OpenAI Transformer 在处理不同任务时所做的输入变换,你会注意到有些任务需要模型对两个句子的信息做一些处理(例如,判断它们是不是同一句话的不同解释。将一个维基百科条目作为输入,再将一个相关的问题作为另一个输入,模型判断是否可以回答这个问题)。
为了让 BERT 更好地处理多个句子之间的关系,预训练过程还包括一个额外的任务:给出两个句子(A 和 B),判断 B 是否是 A 后面的相邻句子。
BERT 预训练的第 2 个任务是两个句子的分类任务。在上图中,tokenization 这一步被简化了,因为 BERT 实际上使用了 WordPieces 作为 token,而不是使用单词本身。在 WordPiece 中,有些词会被拆分成更小的部分。
BERT 在不同任务上的应用
BERT 的论文展示了 BERT 在多种任务上的应用。
将 BERT 用于特征提取
使用 BERT 并不是只有微调这一种方法。就像 ELMo 一样,你可以使用预训练的 BERT 来创建语境化的词嵌入。然后你可以把这些词嵌入用到你现有的模型中。论文里也提到,这种方法在命名实体识别任务中的效果,接近于微调 BERT 模型的效果 那么哪种向量最适合作为上下文词嵌入?我认为这取决于任务。论文里验证了 6 种选择(与微调后的 96.4 分的模型相比):
GPT
GPT-2 是使用 Transformer 的 Decoder 模块构建的。另一方面,BERT 是使用 Transformer 的 Encoder 模块构建的。我们将在下一节中研究这种差异。但它们之间的一个重要差异是,GPT-2 和传统的语言模型一样,一次输出一个 token。例如,让一个训练好的 GPT-2 背诵机器人第一定律:
这些模型的实际工作方式是,在产生每个 token 之后,将这个 token 添加到输入的序列中,形成一个新序列。然后这个新序列成为模型在下一个时间步的输入。这是一种叫“自回归(auto-regression)”的思想。这种做法可以使得 RNN 非常有效。 GPT-2,和后来的一些模型如 TransformerXL 和 XLNet,本质上都是自回归的模型。但 BERT 不是自回归模型。这是一种权衡。去掉了自回归后,BERT 能够整合左右两边的上下文,从而获得更好的结果。XLNet 重新使用了 自回归,同时也找到一种方法能够结合两边的上下文。 GPT-2 能够处理 1024 个 token。每个 token 沿着自己的路径经过所有的 Decoder 模块
运行一个训练好的 GPT-2 模型的最简单的方法是让它自己生成文本(这在技术上称为 生成无条件样本)。或者,我们可以给它一个提示,让它谈论某个主题(即生成交互式条件样本)。在漫无目的情况下,我们可以简单地给它输入初始 token,并让它开始生成单词(训练好的模型使用 <|endoftext|> 作为初始的 token。我们称之为 <s>)。
模型只有一个输入的 token,因此只有一条活跃路径。token 在所有层中依次被处理,然后沿着该路径生成一个向量。这个向量可以根据模型的词汇表计算出一个分数(模型知道所有的 单词,在 GPT-2 中是 5000 个词)。在这个例子中,我们选择了概率最高的 the。但我们可以把事情搞混–你知道如果一直在键盘 app 中选择建议的单词,它有时候会陷入重复的循环中,唯一的出路就是点击第二个或者第三个建议的单词。同样的事情也会发生在这里,GPT-2 有一个 top-k 参数,我们可以使用这个参数,让模型考虑第一个词(top-k =1)之外的其他词。
下一步,我们把第一步的输出添加到我们的输入序列,然后让模型做下一个预测
深入理解 GPT2 的更多细节
输入编码
让我们更深入地了解模型。首先从输入开始。与之前我们讨论的其他 NLP 模型一样,GPT-2 在嵌入矩阵中查找输入的单词的对应的 embedding 向量–这是我们从训练好的模型中得到的组件之一。 每一行都是词的 embedding:这是一个数字列表,可以表示一个词并捕获一些含义。这个列表的大小在不同的 GPT-2 模型中是不同的。最小的模型使用的 embedding 大小是 768
因此在开始时,我们会在嵌入矩阵查找第一个 token <s> 的 embedding。在把这个 embedding 传给模型的第一个模块之前,我们需要融入位置编码,这个位置编码能够指示单词在序列中的顺序。训练好的模型中,有一部分是一个矩阵,这个矩阵包括了 1024 个位置中每个位置的位置编码向量。 在这里,我们讨论了输入单词在传递到第一个 Transformer 模块之前,是如何被处理的。我们还知道,训练好的 GPT-2 包括两个权重矩阵。 把一个单词输入到 Transformer 的第一个模块,意味着寻找这个单词的 embedding,并且添加第一个位置的位置编码向量 在这些层中向上流动
第一个模块现在可以处理 token,首先通过 Self Attention 层,然后通过神经网络层。一旦 Transformer 的第一个模块处理了 token,会得到一个结果向量,这个结果向量会被发送到堆栈的下一个模块处理。每个模块的处理过程都是相同的,不过每个模块都有自己的 Self Attention 和神经网络层 回顾 Self-Attention
语言严重依赖于上下文。例如,看看下面的第二定律:
机器人第二定律
机器人必须服从人给予 它 的命令,当 该命令 与 第一定律 冲突时例外。
我在句子中高亮了 3 个部分,这些部分的词是用于指代其他的词。如果不结合它们所指的上下文,就无法理解或者处理这些词。当一个模型处理这个句子,它必须能够知道:
- 它 指的是机器人
- 该命令 指的是这个定律的前面部分,也就是 人给予 它 的命令
- 第一定律 指的是机器人第一定律
这就是 Self Attention 所做的事。它在处理某个词之前,将模型对这个词的相关词和关联词的理解融合起来(并输入到一个神经网络)。它通过对句子片段中每个词的相关性打分,并将这些词的表示向量加权求和。
举个例子,下图顶部模块中的 Self Attention 层在处理单词 it 的时候关注到a robot 。它传递给神经网络的向量,是 3 个单词和它们各自分数相乘再相加的和。 模型输出
当模型顶部的模块产生输出向量时(这个向量是经过 Self Attention 层和神经网络层得到的),模型会将这个向量乘以嵌入矩阵。 回忆一下,嵌入矩阵中的每一行都对应于模型词汇表中的一个词。这个相乘的结果,被解释为模型词汇表中每个词的分数。 我们可以选择最高分数的 token(top_k=1)。但如果模型可以同时考虑其他词,那么可以得到更好的结果。所以一个更好的策略是把分数作为单词的概率,从整个列表中选择一个单词(这样分数越高的单词,被选中的几率就越高)。一个折中的选择是把 top_k 设置为 40,让模型考虑得分最高的 40 个词 这样,模型就完成了一次迭代,输出一个单词。模型会继续迭代,直到所有的上下文都已经生成(1024 个 token),或者直到输出了表示句子末尾的 token。
GPT2 总结
现在我们基本知道了 GPT-2 是如何工作的。如果你想知道 Self Attention 层里面到底发生了什么,那么文章接下来的额外部分就是为你准备的,我添加这个额外的部分,来使用更多可视化解释 Self Attention,以便更加容易讲解后面的 Transformer 模型(TransformerXL 和 XLNet)。
我想在这里指出文中一些过于简化的说法:
- 我在文中交替使用 token 和 词。但实际上,GPT-2 使用 Byte Pair Encoding 在词汇表中创建 token。这意味着 token 通常是词的一部分。
- 我们展示的例子是在推理模式下运行。这就是为什么它一次只处理一个 token。在训练时,模型将会针对更长的文本序列进行训练,并且同时处理多个 token。同样,在训练时,模型会处理更大的 batch size,而不是推理时使用的大小为 1 的 batch size。
- 为了更加方便地说明原理,我在本文的图片中一般会使用行向量。但有些向量实际上是列向量。在代码实现中,你需要注意这些向量的形式。
- Transformer 使用了大量的层归一化(layer normalization),这一点是很重要的。我们在图解Transformer中已经提及到了一部分这点,但在这篇文章,我们会更加关注 Self Attention。
- 有时我需要更多的框来表示一个向量,例如下面这幅图
|