介绍
2021 年,我使用 GPT 模型编写了最初的几行代码,那时我意识到文本生成已经达到了拐点。我要求 GPT-3 总结一份很长的文档,并尝试了几次提示。我可以看到结果比以前的模型先进得多,这让我对这项技术感到兴奋,并渴望了解它是如何实现的。现在,后续的 GPT-3.5、ChatGPT 和 GPT-4 模型正在迅速获得广泛采用,该领域的更多人也对它们的工作原理感到好奇。虽然其内部工作细节是专有且复杂的,但所有 GPT 模型都共享一些不难理解的基本思想。
生成语言模型如何工作
让我们首先探讨生成语言模型的工作原理。最基本的想法如下:它们将n 个标记作为输入,并产生一个标记作为输出。
这看起来是一个相当简单的概念,但为了真正理解它,我们需要知道令牌是什么。
令牌是一段文本。在 OpenAI GPT 模型的上下文中,常见单词和短单词通常对应于单个标记,例如下图中的单词“We”。长且不常用的单词通常被分成几个标记。例如,下图中的“拟人化”一词被分解为三个标记。像“ChatGPT”这样的缩写可以用单个标记表示,也可以分为多个标记,具体取决于字母一起出现的常见程度。您可以转到 OpenAI 的Tokenizer 页面,输入文本,然后查看它如何拆分为标记。您可以选择用于文本的“GPT-3”标记化和用于代码的“Codex”标记化。我们将保留默认的“GPT-3”设置。
您还可以使用 OpenAI 的开源tiktoken库使用 Python 代码进行代币化。OpenAI 提供了几种不同的标记器,每个标记器的行为都略有不同。在下面的代码中,我们使用“davinci”(GPT-3 模型)的分词器来匹配您使用 UI 看到的行为。
import tiktoken
# Get the encoding for the davinci GPT3 model, which is the "r50k_base" encoding.
encoding = tiktoken.encoding_for_model("davinci")
text = "We need to stop anthropomorphizing ChatGPT."
print(f"text: {text}")
token_integers = encoding.encode(text)
print(f"total number of tokens: {encoding.n_vocab}")
print(f"token integers: {token_integers}")
token_strings = [encoding.decode_single_token_bytes(token) for token in token_integers]
print(f"token strings: {token_strings}")
print(f"number of tokens in text: {len(token_integers)}")
encoded_decoded_text = encoding.decode(token_integers)
print(f"encoded-decoded text: {encoded_decoded_text}")
text: We need to stop anthropomorphizing ChatGPT.
total number of tokens: 50257
token integers: [1135, 761, 284, 2245, 17911, 25831, 2890, 24101, 38, 11571, 13]
token strings: [b'We', b' need', b' to', b' stop', b' anthrop', b'omorph', b'izing', b' Chat', b'G', b'PT', b'.']
number of tokens in text: 11
encoded-decoded text: We need to stop anthropomorphizing ChatGPT.
您可以在代码的输出中看到,此标记生成器包含 50,257 个不同的标记,并且每个标记在内部映射到一个整数索引。给定一个字符串,我们可以将其拆分为整数标记,然后将这些整数转换为它们对应的字符序列。对字符串进行编码和解码应该始终返回原始字符串。
这让您对 OpenAI 标记器的工作原理有一个很好的直觉,但您可能想知道为什么他们选择这些标记长度。让我们考虑一些其他标记化选项。假设我们尝试最简单的实现,其中每个字母都是一个标记。这使得将文本分解为标记变得很容易,并使不同标记的总数保持较小。然而,我们无法编码与 OpenAI 方法中一样多的信息。如果我们在上面的示例中使用基于字母的标记,则 11 个标记只能编码“We need to”,而 OpenAI 的 11 个标记可以编码整个句子。事实证明,当前的语言模型对它们可以接收的最大令牌数量有限制。因此,我们希望在每个 token 中包含尽可能多的信息。
现在让我们考虑每个单词都是一个标记的场景。与 OpenAI 的方法相比,我们只需要 7 个 token 就可以表示同一个句子,这似乎更高效。按字拆分也很容易实现。然而,语言模型需要有一个完整的可能遇到的标记列表,而这对于整个单词来说是不可行的——不仅因为字典中有太多单词,而且因为很难跟上领域的步伐——特定术语和发明的任何新词。
因此,OpenAI 选择介于这两个极端之间的解决方案也就不足为奇了。其他公司也发布了遵循类似方法的标记器,例如Google 的Sentence Piece 。
现在我们对令牌有了更好的理解,让我们回到原来的图表,看看我们是否可以更好地理解它。生成模型采用n 个标记,这些标记可以是几个单词、几个段落或几页。他们产生一个单一的标记,它可以是一个短单词或单词的一部分。
现在这更有意义了。
但如果您使用过OpenAI 的 ChatGPT,您就会知道它会生成许多令牌,而不仅仅是单个令牌。这是因为这个基本思想应用于扩展窗口模式。你给它n 个令牌,它会产生一个令牌输出,然后它将该输出令牌合并为下一次迭代的输入的一部分,产生一个新的令牌输出,依此类推。此模式不断重复,直到达到停止条件,表明它已完成生成您需要的所有文本。
例如,如果我输入“We need to”作为模型的输入,算法可能会产生如下所示的结果:
在使用 ChatGPT 时,您可能还注意到该模型不是确定性的:如果您两次问完全相同的问题,您可能会得到两个不同的答案。这是因为该模型实际上并没有生成单个预测标记;而是生成了单个预测标记。相反,它返回所有可能标记的概率分布。换句话说,它返回一个向量,其中每个条目表示选择特定标记的概率。然后,模型从该分布中采样以生成输出令牌。
该模型是如何得出该概率分布的?这就是训练阶段的目的。在训练期间,模型会接触大量文本,并且在给定输入标记序列的情况下,调整其权重以预测良好的概率分布。GPT 模型是通过大部分互联网进行训练的,因此它们的预测反映了它们所看到的信息的混合。
您现在对生成模型背后的想法有了很好的理解。请注意,我只是解释了这个想法,但还没有给你一个算法。事实证明,这个想法已经存在了几十年,并且多年来已经使用几种不同的算法来实现。接下来我们将看看其中一些算法。
生成语言模型简史
隐马尔可夫模型 (HMM) 在 20 世纪 70 年代开始流行。它们的内部表示对句子(名词、动词等)的语法结构进行编码,并在预测新单词时使用这些知识。然而,由于它们是马尔可夫过程,因此在生成新令牌时仅考虑最新的令牌。因此,他们实现了“ n 个令牌输入,一个令牌输出”思想的非常简单的版本,其中n = 1。因此,它们不会生成非常复杂的输出。让我们考虑以下示例:
如果我们将“The Quick Brown Fox Jumps Over the”输入到语言模型中,我们会期望它返回“Lazy”。然而,隐马尔可夫模型只会看到最后一个标记“the”,并且信息如此之少,它不太可能给出我们期望的预测。当人们尝试 HMM 时,很明显语言模型需要支持多个输入标记才能生成良好的输出。当人们尝试 HMM 时,很明显语言模型需要支持多个输入标记才能生成良好的输出。
N-gram 在 20 世纪 90 年代变得流行,因为它们通过采用多个标记作为输入来解决 HMM 的主要限制。对于前面的示例,n-gram 模型在预测“lazy”这个词方面可能会做得很好。
n-gram 最简单的实现是具有基于字符的标记的二元语法,它给定单个字符,能够预测序列中的下一个字符。您只需几行代码即可创建其中一个,我鼓励您尝试一下。首先,计算训练文本中不同字符的数量(我们称之为n),并创建一个用零初始化的nxn二维矩阵。通过选择与第一个字符对应的行和与第二个字符对应的列,每对输入字符可用于定位该矩阵中的特定条目。当您解析训练数据时,对于每一对字符,您只需将一个添加到相应的矩阵单元中即可。例如,如果您的训练数据包含单词“car”,您可以向“c”行和“a”列中的单元格添加 1,然后向“a”行和“r”中的单元格添加 1柱子。累积所有训练数据的计数后,通过将每个单元格除以该行的总数,将每一行转换为概率分布。
然后,为了进行预测,您需要给它一个单个字符来开始,例如“c”。您查找与“c”行相对应的概率分布,并对该分布进行采样以生成下一个字符。然后,您将生成的角色重复该过程,直到达到停止条件。高阶 n 元语法遵循相同的基本思想,但它们能够通过使用 n 维张量来查看更长的输入标记序列。
N 元语法很容易实现。然而,由于矩阵的大小随着输入标记数量的增加而呈指数增长,因此它们不能很好地扩展到更大数量的标记。并且仅使用几个输入标记,他们就无法产生良好的结果。需要一种新技术来继续在这一领域取得进展。
在 2000 年代,循环神经网络 (RNN) 变得非常流行,因为它们能够接受比以前的技术更多数量的输入标记。特别是,LSTM 和 GRU(RNN 的类型)得到了广泛应用,并被证明能够产生相当好的结果。
RNN 是一种神经网络,但与传统的前馈神经网络不同,它们的架构可以适应接受任意数量的输入并产生任意数量的输出。例如,如果我们向 RNN 提供输入标记“We”、“need”和“to”,并希望它生成更多标记直到达到完整点,则 RNN 可能具有以下结构:
上述结构中的每个节点都具有相同的权重。您可以将其视为连接到自身并重复执行的单个节点(因此称为“循环”),或者您可以将其视为上图所示的扩展形式。与基本 RNN 相比,LSTM 和 GRU 添加的一项关键功能是存在从一个节点传递到下一个节点的内部存储单元。这使得后面的节点能够记住前面节点的某些方面,这对于做出良好的文本预测至关重要。
然而,RNN 在处理很长的文本序列时存在不稳定问题。模型中的梯度往往呈指数增长(称为“梯度爆炸”)或减小到零(称为“梯度消失”),从而阻止模型继续从训练数据中学习。LSTM 和 GRU 可以缓解梯度消失问题,但不能完全阻止它。因此,尽管理论上它们的架构允许任意长度的输入,但实际上该长度存在限制。文本生成的质量再次受到算法支持的输入标记数量的限制,需要新的突破。
2017年,Google发布了介绍Transformers的论文,我们进入了文本生成的新时代。Transformers 中使用的架构允许输入令牌数量大幅增加,消除了 RNN 中出现的梯度不稳定问题,并且具有高度并行性,这意味着它能够利用 GPU 的强大功能。Transformer 如今已被广泛使用,OpenAI 选择将其用于最新的 GPT 文本生成模型。
Transformer 基于“注意力机制”,该机制允许模型比其他输入更多地关注某些输入,无论它们出现在输入序列中的位置。例如,让我们考虑以下句子:
在这种情况下,当模型预测动词“买”时,它需要匹配动词“去”的过去时。为了做到这一点,它必须非常关注“去”这个令牌。事实上,它可能更关注标记“went”而不是标记“and”,尽管“went”在输入序列中出现得更早。
GPT 模型中的这种选择性注意力行为是由 2017 年论文中的一个新颖想法实现的:使用“屏蔽多头注意力”层。让我们分解这个术语,并深入研究它的每个子术语:
Attention:“注意力”层包含一个权重矩阵,表示输入句子中所有标记位置对之间的关系强度。这些权重是在训练期间学习的。如果一对位置对应的权重很大,那么这些位置上的两个代币相互影响很大。这种机制使 Transfomer 能够比其他标记更加关注某些标记,无论它们出现在句子中的哪个位置。
Masked:如果矩阵仅限于每个标记位置与输入中较早位置之间的关系,则注意力层将被“屏蔽”。这就是 GPT 模型用于文本生成的方法,因为输出标记只能依赖于它之前的标记。
Multi-head:Transformer 使用屏蔽的“多头”注意层,因为它包含多个并行操作的屏蔽注意层。
LSTM 和 GRU 的记忆单元还使后面的 token 能够记住早期 token 的某些方面。然而,如果两个相关的令牌相距很远,梯度问题可能会产生阻碍。Transformer 不存在这个问题,因为每个令牌都与其之前的所有其他令牌有直接连接。
现在您已经了解了 GPT 模型中使用的 Transformer 架构的主要思想,接下来我们来看看目前可用的各种 GPT 模型之间的区别。
不同的GPT模型是如何实现的
截至撰写本文时,OpenAI 最新发布的三个文本生成模型是 GPT-3.5、ChatGPT 和 GPT-4,它们均基于 Transformer 架构。事实上,“GPT”代表“生成式预训练变压器”。
GPT-3.5 是一个被训练为补全式模型的转换器,这意味着如果我们给它一些单词作为输入,它能够生成更多可能在训练数据中跟随它们的单词。
另一方面,ChatGPT 被训练为对话式模型,这意味着当我们像进行对话一样与它进行交流时,它的性能最佳。它基于与 GPT-3.5 相同的变压器基础模型,但它根据对话数据进行了微调。然后使用人类反馈强化学习 (RLHF) 对其进行进一步微调,这是 OpenAI 在其2022 年 InstructGPT 论文中引入的一项技术。在这种技术中,我们给模型两次相同的输入,得到两个不同的输出,然后询问人类排名者它更喜欢哪个输出。然后使用该选择通过微调来改进模型。这项技术使模型的输出与人类期望保持一致,这对于 OpenAI 最新模型的成功至关重要。
另一方面,GPT-4 既可以用于补全,也可以用于对话,并且拥有自己的全新基础模型。该基本模型还使用 RLHF 进行了微调,以更好地符合人类期望。
编写使用 GPT 模型的代码
两者之间的主要区别在于 Azure 提供了以下附加功能:
- 自动化、负责任的 AI 过滤器可减少 API 的不道德使用
- Azure 的安全功能,例如专用网络
- 区域可用性,在与 API 交互时获得最佳性能
如果您正在编写使用这些模型的代码,则需要选择要使用的特定版本。以下是 Azure OpenAI 服务中当前可用版本的快速备忘单:
- GPT-3.5:文本-davinci-002,文本-davinci-003
- ChatGPT:gpt-35-turbo
- GPT-4:gpt-4、gpt-4–32k
两个 GPT-4 版本的主要区别在于它们支持的令牌数量:gpt-4 支持 8,000 个令牌,gpt-4–32k 支持 32,000 个令牌。相比之下,GPT-3.5 模型仅支持 4,000 个代币。
由于 GPT-4 是目前最昂贵的选项,因此最好从其他型号之一开始,仅在需要时进行升级。有关这些模型的更多详细信息,请查看文档。
结论
在本文中,我们介绍了所有生成语言模型的共同基本原理,特别是 OpenAI 最新 GPT 模型的独特之处。
一路上,我们强调了语言模型的核心思想:“ n 个令牌输入,一个令牌输出”。我们探讨了代币是如何分解的,以及为什么要这样分解。我们追溯了语言模型数十年的演变,从早期的隐马尔可夫模型到最近基于 Transformer 的模型。最后,我们描述了 OpenAI 的三个最新的基于 Transformer 的 GPT 模型、每个模型的实现方式以及如何编写使用它们的代码。
到目前为止,您应该已经做好了充分准备,可以就 GPT 模型进行知情对话,并开始在自己的编码项目中使用它们。我计划写更多关于语言模型的解释,所以请关注我,让我知道您希望看到哪些主题!感谢您的阅读!