上一章帮助我们建立了对NLP概念的基础理解,如文本表示和语言建模,以及基于RNN的架构来执行生成任务。在本章中,我们将建立在这些概念的基础上,并介绍一些增强功能,这些功能促成了当前最先进的变换器架构的开发。我们将重点讨论以下内容:
- 注意力机制概述,以及变换器如何改变NLP领域
- 不同NLP任务的变换器配置
- 使用Hugging Face变换器更好地理解类似BERT的模型
- 基于GPT类架构构建文本生成管道的逐步指南
NLP基准
本章中呈现的所有代码片段都可以直接在Google Colab中运行。由于篇幅限制,依赖项的导入语句未包含在内,但读者可以参考GitHub存储库获取完整代码:github.com/PacktPublis…。
让我们从关注注意力机制开始!
注意力机制
一个典型的RNN层(通常来说,可能是LSTM、GRU等)会将一个定义大小的上下文窗口作为输入,并将所有信息编码成一个单一的向量(比如,用于语言建模任务)。这个瓶颈向量需要在解码阶段使用之前,捕捉大量的信息。这导致了许多与各种NLP任务相关的挑战,如语言翻译、问答系统等。
注意力机制是深度学习领域最强大的概念之一,真正改变了游戏规则。注意力机制背后的核心思想是利用RNN的所有中间隐藏状态(正如我们将看到的,这也扩展到其他架构)来决定在解码阶段之前应该专注于哪个状态。更正式地说,注意力机制可以表示为:
给定一个值向量(所有的RNN隐藏状态)和一个查询向量(这可以是解码器的状态,记作 q),注意力机制是一种计算值的加权和的技术(记作 v),并且这个加权和依赖于查询向量。
这个加权和充当了隐藏状态(值向量)中包含信息的选择性摘要,而查询向量决定了该专注于哪些值。注意力机制的根源可以在与神经机器翻译(NMT)架构相关的研究中找到。NMT模型特别在对齐问题上遇到困难,而注意力机制在这方面大大帮助了这一点。例如,将英语句子翻译成法语时,单词之间的对应关系可能不是一对一的。注意力机制不仅限于NMT应用,还广泛应用于其他NLP任务,如文本生成和分类。
这个想法很简单,但我们如何实现并使用它呢?图4.1展示了注意力机制如何工作的示例场景。该图展示了在时间步t时展开的RNN。
参考图4.1,让我们一步步理解注意力是如何计算的:
首先,令RNN编码器的隐藏状态表示为,当前的输出向量表示为
。
然后,我们计算时间步 t的注意力得分,计算公式为:
这一步也称为对齐步骤。
然后,我们将这个得分转化为注意力分布:
使用 softmax 函数帮助我们将得分转换为一个概率分布,使其总和为 1。
最后一步是通过对编码器的隐藏状态进行加权求和,计算出注意力向量,记作 ccc,也称为上下文向量:
一旦我们得到了注意力向量,就可以将其与前一个时间步的解码器状态向量简单地连接起来,并像之前一样继续解码该向量。
迄今为止,许多研究者已经探索了不同的注意力机制变体。需要注意的几个重要点是:
- 上述注意力计算步骤在所有变体中是相同的。
- 区别在于注意力得分(记作 α\alphaα)的计算方式。
- 广泛使用的注意力得分函数包括基于内容的注意力、加性注意力、点积注意力和缩放点积注意力。读者可以进一步探索这些方法,以更好地理解。
自注意力
自注意力由Cheng等人在他们2016年发表的论文《Long Short-Term Memory Networks for Machine Reading》中提出。自注意力的概念建立在注意力机制的一般思想上。自注意力使得模型能够学习当前令牌(字符、单词、句子等)与其上下文窗口之间的相关性。换句话说,它是一种将给定序列的不同位置关联起来以生成同一序列表示的注意力机制。可以将其想象为在给定句子/序列的上下文中转换词嵌入的方式。原始论文中提出的自注意力概念如图4.2所示。
让我们尝试理解图4.2中展示的自注意力输出。每一行/句子表示模型在每个时间步的状态,当前单词用红色突出显示。蓝色表示模型的注意力,注意力的集中程度由蓝色的深浅程度表示。因此,在当前单词的上下文中,每个词都可以在一定程度上贡献给当前词的嵌入。
这一概念构成了我们接下来要讨论的变换器架构的核心构建块之一。
变换器(Transformers)
注意力、上下文嵌入和无递归架构等概念的融合,最终导致了我们现在所称的变换器架构。变换器架构首次在Vaswani等人于2017年发表的开创性论文《Attention is All You Need》中提出。该研究代表了NLP领域的一个完整范式转变;它不仅提出了一个强大的架构,还智能地利用了一些最近发展的概念,帮助其在不同的基准测试中大幅超过了当时的最先进模型。
变换器的核心是一个基于注意力机制的、无递归和无卷积的编码器-解码器架构。它完全依赖注意力机制(因此得名)来学习局部和全局的依赖关系,从而实现大规模并行化,并在各种NLP任务上带来其他性能提升。
总体架构
与NLP领域早期的编码器-解码器架构不同,这项工作提出了一个堆叠的编码器-解码器结构。图4.3展示了一个高级别的变换器配置。
如图4.3所示,该架构使用了多个编码器块堆叠在一起。解码器本身由堆叠的解码块组成,最终的编码器块将信息传递给每个解码块。这使得解码器在生成解码输出时能够关注输入序列。需要注意的是,编码器和解码器块中都不包含递归或卷积层。图4.4(A)概述了编码器块,图4.4(B)概述了解码器块。虚线表示不同层集之间的残差连接。原始论文展示的变换器架构包含了六个相同的编码器块和解码器块。
如图4.4(A)所示,编码器块由计算自注意力的层组成,随后是归一化和前馈层。这些层之间有跳跃连接。解码器块与编码器块几乎相同,唯一的区别是它多了一个子块,包含自注意力和归一化层。这个附加的子块从最后一个编码器块接收输入,确保编码器的注意力传播到解码块中。
解码器块中的第一层略有修改。这个多头自注意力层对未来的时间步/上下文进行了掩码处理。这确保了模型在解码当前令牌时不会关注目标的未来位置(你能想出为什么需要这个限制吗?)。让我们花点时间进一步理解多头自注意力组件。
多头自注意力
在介绍注意力机制时,我们是通过查询向量(解码器状态,记作 q)和值向量(编码器的隐藏状态,记作 v)来讨论的。在变换器中,这一概念有所修改。我们使用编码器状态或输入令牌作为查询向量和值向量(自注意力),并添加一个额外的向量,称为键向量(记作 k)。在这种情况下,键、值和查询向量的维度是相同的。
变换器架构使用缩放点积作为其注意力机制。这个得分函数定义为:
注意力输出首先计算为查询向量(qqq)和键向量(kkk)之间的点积(这些实际上是矩阵,但我们稍后会解释)。点积尝试捕捉查询与编码器状态的相似性,然后通过输入向量维度的平方根进行缩放。引入这个缩放因子是为了确保梯度能够正确传播,因为对于较大的嵌入向量,常常会出现梯度消失问题。softmax操作将得分转换为一个概率分布,使其总和为1。最后一步是计算加权和的编码器状态(这次是值向量 vvv)与softmax输出的乘积。这个整体操作如图4.5所示,供参考。
为了代替每个编码器块使用单个注意力头,模型采用了多个并行的注意力头(如图4.5(右)所示)。作者在论文中提到:“多头注意力使模型能够在不同的位置共同关注来自不同表示子空间的信息。使用单个注意力头时,平均化会抑制这一点。”换句话说,多头注意力使模型能够学习输入中每个单词的不同方面,即一个注意力头可以捕捉与介词的关系,另一个可以关注与动词的互动,依此类推。多头注意力的概念类似于CNN中的多个滤波器,每个滤波器尝试捕捉输入的特定视觉概念。
由于每个注意力头都有自己的一组查询向量(q)、键向量(k)和值向量(v),因此在实际操作中,这些向量被实现为矩阵(分别为Q、K和V),每一行对应一个特定的头。
这里提供了一个非常直观的多头自注意力可视化解释供参考:多头自注意力视频。
你可能会认为,由于多头设置,参数数量会突然膨胀并导致训练过程变慢。为了应对这一点,作者通过首先将较大的输入嵌入映射到较小的维度(大小为64),使用较小维度的向量来减小参数的规模。然后,他们在原始实现中使用了八个头。这使得最终的拼接向量(来自所有注意力头)与使用单个注意力头和更大输入嵌入向量时的维度相同。这个巧妙的技巧帮助模型在相同的空间内捕捉更多的语义,同时不影响整体训练速度。整体变换器架构使用了多个这样的编码器块,每个编码器块都包含多头注意力层。
现在我们了解了注意力——更具体地说,多头自注意力——如何增强变换器架构。正如前面所说,整体架构是无递归的。在这种情况下,它是如何处理文本输入这种序列数据类型的呢?位置编码的概念为我们提供了解决方案。我们将在下一节讨论这个概念。
位置编码
变换器模型不包含任何递归或卷积层,因此,为了确保模型理解输入顺序的重要性,引入了位置嵌入的概念。作者选择使用以下方法来生成位置编码:
其中,pospospos 是输入令牌的位置,iii 是维度,dmodeld_{\text{model}}dmodel 是输入嵌入向量的长度。作者对偶数位置使用正弦函数,对奇数位置使用余弦函数。位置编码向量的维度与输入向量相同,两个向量在输入编码器或解码器块之前会被相加。
这种计算偶数和奇数位置的方式是一个聪明的技巧,能够使模型学习输入的相对位置。图4.6展示了不同输入位置(pospospos)对应维度0、16和32的编码值。
多头自注意力与位置编码的结合帮助变换器网络构建了高度上下文化的输入序列表示。这使得变换器不仅在多个基准测试中超越了最先进的模型,还为一整套基于变换器的模型奠定了基础。在下一节中,我们将简要介绍这一系列的变换器模型。
NLP任务和变换器架构
原始的变换器架构是一个编码器-解码器设置,展示了在翻译和句法分析任务中的最先进性能。作者在工作中总结道,变换器架构不仅可以应用于NLP任务,还可以扩展到其他模态,如音频、视频和图像。正如所预期的那样,这一发现确实实现了,并且推动了深度学习领域朝着多样化的基于变换器的架构发展,这些架构具有不同的规模和能力。
另一个值得一提的重要工作是Howard等人提出的《Universal Language Model Fine-Tuning for Text Classification》或ULMFiT。尽管这篇论文基于递归架构,并且是在原始变换器论文之后发布的,但它展示并普及了预训练语言模型和迁移学习在下游任务中的概念。本质上,这项工作提供了一个三步法来训练NLP模型:在大规模语料库上进行预训练(建立对广泛概念的初步理解),在特定任务数据上进行微调(以适应特定领域的概念),最后,在任务特定的头上进行微调(例如,分类器)。从那时起,这一方法已被广泛应用于各种基于变换器的架构中。
接下来,我们讨论几个关键的架构家族。
仅编码器架构
仅编码器模型专注于变换器架构中的编码器部分。它们主要设计用于涉及理解和表示输入文本的NLP任务,如分类、命名实体识别等(参见第3章中的文本表示部分)。这些模型通常在大数据集上进行预训练,以创建丰富的上下文嵌入,然后在特定任务上进行微调。这个模型家族的关键贡献是在预训练阶段的掩蔽语言建模目标,其中输入的某些令牌被掩蔽,模型训练以预测这些令牌(我们将在后续部分讨论这些)。这个架构家族中的关键工作包括BERT、RoBERTa(或优化版BERT)、DistilBERT(更轻便高效的BERT)、ELECTRA和ALBERT。
仅解码器架构
顾名思义,仅解码器架构专注于变换器模型的解码器部分。尽管它们本质上设计用于自回归文本生成(即学习预测句子中的下一个单词/令牌),但通过附加适当的输出头,它们也可以适应其他任务,如分类或回归。这些模型通常通过预测序列中的下一个令牌以无监督方式进行预训练。虽然编码器-解码器架构和仅解码器架构都可以用于大多数NLP任务(通过小的修改,例如,BERT是双向的,因此不直接适用于文本生成任务),但正是仅解码器架构(特别是类似GPT的模型)位于今天大型语言模型(LLM)生态系统的核心。这一组中的关键工作包括GPT系列模型、Chinchilla等。
编码器-解码器架构
与原始的变换器架构类似,这一组中的模型结合了编码器和解码器组件,使它们适用于广泛的任务,如机器翻译、摘要生成和文本生成。编码器处理输入序列并生成上下文嵌入,解码器则利用这些嵌入生成输出序列。该组中的关键工作包括T5(Text-to-Text Transfer Transformer),它将所有NLP任务框架化为文本到文本的问题,从而简化了模型和训练过程;Transformer-XL,它通过段级递归解决了固定长度限制问题;以及BART,它使用双向编码器(如BERT)和自回归解码器(如GPT),使其在各种NLP任务中非常有效。
过去几年中,NLP领域的发展和进步是非凡的,每一项新工作都在现有工作基础上进行改进和创新。图4.7提供了不同架构风格和相应模型的快照,展示了这些年来的演变。
图4.7来自Yang等人的调查论文《Harnessing the Power of LLMs in Practice》。这项工作提供了对各种架构的良好概述,以及改进和优化模型的技术。
接下来,让我们深入探讨两项在原始变换器架构之后的开创性工作,并通过实际应用它们来获得一些动手经验。
DistilBERT的应用
变换器架构在NLP领域带来了前所未有的性能基准。最初的、最成功的变换器架构之一是BERT模型。BERT(双向编码器表示来自变换器)由Google AI的Devlin等人于2018年提出。
BERT还推动了NLP领域中的迁移学习,通过展示如何微调预训练模型以适应各种任务,提供了最先进的性能。BERT使用了变换器风格的编码器,依赖于不同数量的编码器块,具体数量取决于模型的大小。作者提出了两个模型:BERT-base(12个块)和BERT-large(24个块)。这两个模型的前馈网络比原始变换器架构更大(分别为768和1,024),并且注意力头的数量也更多(分别为12和16)。
与原始变换器实现的另一个主要变化是双向掩蔽语言模型目标。典型的语言模型确保因果性,也就是说,解码过程只关注过去的上下文,而不考虑未来的时间步。BERT的作者调整了这个目标,旨在从两个方向构建上下文(即,预测掩蔽单词以及下一个句子预测)。如图4.8所示。
如图4.8所示,掩蔽语言模型(Masked Language Model)在训练过程中随机遮蔽15%的标记。BERT模型是在一个庞大的语料库上进行训练的,然后通过在GLUE15和其他相关基准任务上的微调来适应不同的任务。
BERT的成功催生了一系列改进的模型,这些模型在嵌入、编码器层等方面进行了一些调整,以提供渐进的性能提升。像RoBERTa、ALBERT、DistilBERT、XLNet等模型都共享核心思想,并在此基础上进行改进。
由于BERT不符合因果关系,它无法用于典型的语言建模任务,如文本生成。
DistilBERT实践
现在,让我们通过Hugging Face的transformers库将一些理论付诸实践。
Hugging Face的transformers包是一个高层次的封装器,使我们能够用几行代码使用这些庞大的NLP模型(甚至计算机视觉等)。它提供了一组清晰且易于使用的接口,用于训练和推理。请注意,transformers支持多个后端,如PyTorch、TensorFlow等,但我们将专注于PyTorch(对于其他后端,可能需要进行一些小的调整)。此外,如果你希望开发自己的新型transformer架构,建议利用PyTorch/TensorFlow/JAX等底层框架。
我们将关注三个不同的NLP任务,了解预训练模型如何比大多数过去的NLP模型做得更好,但与经过微调的模型相比,似乎还有一些不足。我们将覆盖掩蔽语言建模、文本分类和问答任务。
下载预训练检查点
首先,我们开始为每个任务下载所需的检查点。对于每个任务,我们将探索预训练DistilBERT模型与其任务特定微调版本的性能。以下代码片段定义了下载目标并准备了管道对象:
import transformers
from transformers import pipeline
# 定义一些配置/常量
DISTILBET_BASE_UNCASED_CHECKPOINT = "distilbert/distilbert-base-uncased"
DISTILBET_QA_CHECKPOINT = "distilbert/distilbert-base-uncased-distilled-squad"
DISTILBET_CLASSIFICATION_CHECKPOINT = "distilbert/distilbert-base-uncased-finetuned-sst-2-english"
掩蔽语言建模任务
我们的第一个NLP任务是BERT类模型的基本目标(即掩蔽语言建模任务)。与通常的NLP任务(如分类)相比,BERT最初引入的独特目标是预测被掩蔽的标记。该目标要求我们准备一个数据集,其中我们会掩蔽一定比例的输入标记,并训练模型学习预测这些标记。这个目标被证明在帮助模型学习语言的细微差别方面非常有效。
在这个任务中,我们将测试预训练模型在这个目标上的表现。模型输出包括预测的标记、预测标记/单词的编码索引以及表示模型置信度的分数。以下代码片段准备了管道对象,并在一个示例句子上生成输出:
mlm_pipeline = pipeline(
'fill-mask',
model=DISTILBET_BASE_UNCASED_CHECKPOINT,
device=DEVICE_ID
)
mlm_pipeline("Earth is a [MASK] in our solar system")
输出:
[{'score': 0.4104354977607727, 'token': 4774, 'token_str': 'planet', 'sequence': 'earth is a planet in our solar system'}, {'score': 0.05731089040637016, 'token': 5871, 'token_str': 'satellite', 'sequence': 'earth is a satellite in our solar system'}, {'score': 0.03048967570066452, 'token': 4920, 'token_str': 'hole', 'sequence': 'earth is a hole in our solar system'}, {'score': 0.02207728661596775, 'token': 2732, 'token_str': 'star', 'sequence': 'earth is a star in our solar system'}, {'score': 0.019248900935053825, 'token': 4231, 'token_str': 'moon', 'sequence': 'earth is a moon in our solar system'}]
模型似乎在填充掩蔽部分时做得相当不错,将“planet”作为首选。其他预测尽管在事实上是错误的,但它们仍与天体相关,这本身已经很了不起了。接下来,我们将为情感分析设置管道对象。在这种情况下,我们不仅使用预训练版DistilBERT,还使用在情感分类数据集上微调的版本。以下代码片段为我们设置相关内容,并使用两种模型返回情感分类结果:
# ft表示微调版
classification_ft_pipeline = pipeline(
'sentiment-analysis',
model=DISTILBET_CLASSIFICATION_CHECKPOINT,
device=DEVICE_ID
)
# pt表示预训练版
classification_pt_pipeline = pipeline(
'sentiment-analysis',
model=DISTILBET_BASE_UNCASED_CHECKPOINT,
device=DEVICE_ID
)
SAMPLE_SA_INPUT = "What a messy place! I am never coming here again!"
pretrained_sa_results = classification_pt_pipeline(SAMPLE_SA_INPUT)
finetuned_sa_results = classification_ft_pipeline(SAMPLE_SA_INPUT)
# 微调模型的预测
print(f"Predictions from Fine-Tuned Model={finetuned_sa_results}")
# 预训练模型的预测
print(f"Predictions from Pretrained Model={pretrained_sa_results}")
输出:
Predictions from Fine-Tuned Model=[{'label': 'NEGATIVE', 'score': 0.9995848536491394}]
Predictions from Pretrained Model=[{'label': 'LABEL_1', 'score': 0.5113149285316467}]
如我们所见,微调模型在分配正确标签时非常自信,而预训练模型几乎没有完成任务。接下来是问答任务。这个任务是一个有趣且相当复杂的NLP任务。在此任务中,模型接收包括上下文和问题的输入,并通过从上下文中选择文本来预测答案。该任务的训练设置稍微复杂一些,以下是概述:
训练输入是上下文、问题和答案的三元组。
该输入将被转化为[CLS]question[SEP]context[SEP]或[CLS]context[SEP]question[SEP]的形式,其中答案作为标签。 [CLS]和[SEP]是特殊标记,其中[CLS]用于表示任务(在这里我们使用它来表示分类本身),[SEP]用于分隔两个输入(问题和上下文)。
模型被训练来预测每个输入的对应答案的开始和结束索引。
如惯例,以下代码片段准备了管道对象和上下文及问题的输入。我们将使用一个经过SQuAD22(或斯坦福问答数据集)微调的DistilBERT版本,该数据集不一定包含我们将要测试的上下文/问题的信息:
qa_ft_pipeline = pipeline(
'question-answering',
model=DISTILBET_QA_CHECKPOINT,
device=DEVICE_ID
)
qa_pt_pipeline = pipeline(
'question-answering',
model=DISTILBET_BASE_UNCASED_CHECKPOINT,
device=DEVICE_ID
)
context = """The key contribution from … DistilBERT (lighter and more efficient BERT), ELECTRA and ALBERT… will learn to answer questions based on the context provided."""
question = "What are the key works in this set of models?"
ft_qa_result = qa_ft_pipeline(
question=question,
context=context
)
pt_qa_result = qa_pt_pipeline(
question=question,
context=context
)
print(f"Question:{question}")
print("-" * 55)
print(f"Response from Fine-Tuned Model:\n{ft_qa_result}")
print()
print(f"Response from Pretrained Model:\n{pt_qa_result}")
输出:
Question:What are the key works in this set of models?
-------------------------------------------------------
Response from Fine-Tuned Model:
{'score': 0.01078921090811491, 'start': 294, 'end': 326, 'answer': 'BERT, RoBERTa (or optimized BERT'}
Response from Pretrained Model:
{'score': 0.0001530353765701875, 'start': 329, 'end': 339, 'answer': 'DistilBERT'}
如我们所见,两个模型都做得相当不错,微调模型提供了更好的答案(尽管两个答案都不完整)。在特定领域的数据集上进行微调有助于实现所需的改进。
这结束了我们关于理解仅编码器架构在三种不同NLP任务中的快速实践指南。接下来,我们将回到生成任务,并深入研究解码器-only GPT系列模型。
使用GPT进行文本生成
OpenAI因其引人注目的工作而备受关注,例如GPT-8、GPT-29和GPT-3(还有instructGPT、GPT-3.5、GPT-4,以及引发广泛关注的ChatGPT,但这些与本文稍有不同,后续章节会涉及)。在这一节中,我们将简要讨论从GPT到GPT-3的架构。然后,我们将使用预训练的GPT-2版本进行文本生成任务。
生成式再训练:GPT
这个系列的第一个模型被称为GPT,或生成预训练(Generative Pretraining)。它于2018年发布,和BERT差不多在同一时间发布。论文提出了一种与任务无关的架构,基于transformers和无监督学习的思想。GPT模型在多个基准测试中表现出色,例如GLUE和SST-2,尽管它的性能被稍后发布的BERT超越了。
GPT本质上是一个基于transformer解码器的语言模型。由于语言模型可以通过无监督的方式进行训练,模型的作者利用这一无监督的训练方法在一个非常大的语料库上进行训练,然后再根据特定任务进行微调。作者使用了BookCorpus数据集,该数据集包含超过7000本独特的、未出版的书籍,涵盖不同的类别。这个数据集使得模型能够学习长距离的信息,因为它包含了长时间连续的文本。这被认为比早期的1B Word Benchmark数据集更好,后者由于句子被打乱而无法提供长距离的信息。整体的GPT设置如图4.9所示。
如图4.9(左)所示,GPT模型与原始的transformer-decoder相似。作者使用了12个解码器块(与原始transformer中的6个相比),每个解码器块具有768维的状态和12个自注意力头。由于该模型使用了遮蔽自注意力,它保持了语言模型的因果性,因此也可以用于文本生成。在图4.9(右)展示的其他任务中,基本上使用了相同的预训练语言模型,仅对输入进行了最小的任务特定预处理,并添加了任务特定的层和目标。
GPT-2
GPT被一个更强大的模型取代,称为GPT-2。Radford等人在2019年发布了GPT-2模型,作为他们的研究《语言模型是无监督的多任务学习者》的一部分。GPT-2的最大变体是一个庞大的(按2019年标准)1.5亿参数的基于transformer的模型,在多个NLP任务上表现出色。该工作的最引人注目的方面是,作者展示了一个无监督训练的模型(即语言建模)如何在少量示例的情况下取得最先进的性能。这一点尤为重要,因为与GPT甚至BERT相比,GPT-2不需要在特定任务上进行微调。
与GPT类似,GPT-2的“秘密武器”是它的数据集。作者通过抓取Reddit上的4500万个外链,准备了一个庞大的40GB数据集。他们进行了基于启发式的清理、去重以及移除维基百科文章,最终得到了大约800万篇文档。这个数据集被称为WebText数据集。
GPT-2的整体架构与GPT相同,唯一的变化是一些细微的调整,例如在每个子块开始时放置层归一化,以及在最终的自注意力块后添加额外的层归一化。模型的四个变体分别使用了12、24、36和48层。词汇表也扩展到包含50,000个单词,且上下文窗口扩展到1,024个token(相比于GPT的512个token)。
GPT-2作为语言模型表现得如此优秀,以至于作者最初决定不将预训练模型公开,以免造成潜在的负面影响(参见gpt-news的相关参考)。他们最终还是发布了该模型,理由是迄今为止没有发现恶意使用案例。接下来,我们将利用transformers包来构建我们自己的文本生成管道,基于GPT-2,并看看我们的模型效果如何。
实践:使用GPT-2
与前几章中使用各种复杂架构生成虚假内容的主题一致,我们使用GPT-2生成一些虚假的新闻标题。百万标题数据集包含超过一百万条来自ABC新闻澳大利亚的新闻标题,这些标题是17年间收集的。
从高层次来看,这一虚假新闻生成任务与我们在本章初期进行的语言建模任务相同。由于我们使用了transformers包,关于训练数据集创建、标记化和最终训练模型的步骤已经通过高层次API进行了抽象。
第一步,和往常一样,是读取手头的数据集并将其转换为所需的格式。我们不需要自己准备词到整数的映射,transformers库中的Tokenizer类会为我们处理这一点。以下代码片段准备了数据集和所需的对象:
import pandas as pd
from sklearn.model_selection import train_test_split
from transformers import AutoTokenizer
from transformers import TextDataset, DataCollatorForLanguageModeling
# 获取数据集
news = pd.read_csv('abcnews-date-text.csv')
X_train, X_test = train_test_split(news.headline_text.tolist(), test_size=0.33, random_state=42)
# 写入训练数据集
with open('train_dataset.txt', 'w') as f:
for line in X_train:
f.write(line)
f.write("\n")
# 写入测试数据集
with open('test_dataset.txt', 'w') as f:
for line in X_test:
f.write(line)
f.write("\n")
# 准备Tokenizer对象
tokenizer = AutoTokenizer.from_pretrained("gpt2", pad_token='<pad>')
train_path = 'train_dataset.txt'
test_path = 'test_dataset.txt'
# 准备数据集的工具方法
def load_dataset(train_path, test_path, tokenizer):
train_dataset = TextDataset(
tokenizer=tokenizer,
file_path=train_path,
block_size=4
)
test_dataset = TextDataset(
tokenizer=tokenizer,
file_path=test_path,
block_size=4
)
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer, mlm=False,
)
return train_dataset, test_dataset, data_collator
train_dataset, test_dataset, data_collator = load_dataset(
train_path, test_path, tokenizer
)
在上面的代码中,我们使用sklearn将数据集分割为训练集和测试集,然后通过TextDataset类将它们转换为可用格式。train_dataset和test_dataset对象是简单的生成器对象,将被Trainer类用于微调我们的模型。以下代码片段准备了训练模型的设置:
from transformers import Trainer, TrainingArguments, AutoModelWithLMHead
model = AutoModelWithLMHead.from_pretrained("gpt2")
training_args = TrainingArguments(
output_dir="./headliner", # 输出目录
overwrite_output_dir=True, # 覆盖输出目录内容
num_train_epochs=1, # 训练的轮次
per_device_train_batch_size=4, # 每个设备的训练批次大小
per_device_eval_batch_size=2, # 每个设备的评估批次大小
eval_steps=400, # 两次评估之间的更新步数
save_steps=800, # 每隔多少步保存一次模型
warmup_steps=500, # 学习率调度器的预热步数
)
trainer = Trainer(
model=model,
args=training_args,
data_collator=data_collator,
train_dataset=train_dataset,
eval_dataset=test_dataset,
prediction_loss_only=True,
)
我们使用AutoModelWithLMHead类作为GPT-2的高级封装,目标是训练一个语言模型。Trainer类根据设置的参数执行训练步骤,使用TrainingArguments类。下一步是调用train函数并开始微调。以下代码片段展示了GPT-2的训练步骤:
trainer.train()
# 训练输出
{'loss': 6.99887060546875, 'learning_rate': 5e-05, 'epoch': 0.0010584004182798454, 'total_flos': 5973110784000, 'step': 500}
{'loss': 6.54750146484375, 'learning_rate': 4.994702390916932e-05, 'epoch': 0.0021168008365596907, 'total_flos': 11946221568000, 'step': 1000}
{'loss': 6.5059072265625, 'learning_rate': 4.989404781833863e-05, 'epoch': 0.003175201254839536, 'total_flos': 17919332352000, 'step': 1500}
由于GPT-2是一个庞大的模型,对其进行微调可能需要几个小时,尤其是在非常快的GPU上。为了本练习的目的,我们让它训练了几个小时,同时保存了中间的检查点。以下代码片段展示了管道对象和一个辅助函数get_headline,我们需要用这个函数生成标题:
from transformers import pipeline
headliner = pipeline('text-generation',
model='./headliner',
tokenizer='gpt2',
config={'max_length': 8})
# 辅助方法
def get_headline(headliner_pipeline, seed_text="News"):
return headliner_pipeline(seed_text)[0]['generated_text'].split('\n')[0]
现在,让我们生成一些虚假的新闻标题,看看我们的GPT-2模型效果如何。图4.10展示了使用我们模型生成的一些虚假新闻标题。
生成的输出展示了GPT-2及基于transformer架构的潜力。你应该将其与我们在本章初期训练的基于LSTM的变体进行对比。这里展示的模型能够捕捉到与新闻标题相关的一些细微差别。例如,它生成了简洁明了的句子,捕捉到了像“袋鼠”、“土著”甚至“墨尔本”这样的词,这些词在澳大利亚语境中非常相关,而我们的训练数据集正是来自这一领域。所有这些都在仅仅经过几轮训练后就被模型捕捉到了。可能性是无限的。
GPT-3
GPT-2展示了模型容量(参数规模)和更大数据集如何带来令人印象深刻的成果。布朗等人于2020年5月发布了名为《语言模型是少样本学习者》的论文,该论文介绍了一个庞大的1750亿参数的GPT-3模型。
GPT-3比任何以前的语言模型大了几个数量级(是GPT-2的10倍),并将transformer架构推向极限。在这项工作中,作者展示了该模型的八个不同变体,范围从一个1.25亿参数、12层的“GPT-3 small”模型到一个1750亿参数、96层的GPT-3模型。
模型架构与GPT-2相同,但有一个主要的变化(除了嵌入大小、注意力头和层数的增加)。主要的变化是使用交替的密集和局部带状稀疏注意力模式在transformer块中。这种稀疏注意力技术与Sparse Transformers中提出的技术类似(参见《Generating Long Sequences with Sparse Transformers》,Child等人)。这篇论文的作者发现,模型在计算注意力时采用了非常稀疏的方式。GPT-2类模型通过在一部分token上计算注意力分数(例如,使用更大的步幅或跳过每隔n个token的token)而不是对每一对token计算注意力,从而利用了这种稀疏模式。这有助于减少计算量(进而减少内存使用并节省时间),并允许模型处理更长的上下文窗口作为输入。
与早期的GPT模型类似,作者必须为这第三次迭代准备一个更大的数据集。他们准备了一个3000亿token的数据集,基于现有的数据集,如Common Crawl(经过筛选以获得更好的内容)、WebText2(一个用于GPT-2的大版本WebText)、Books1和Books2以及维基百科数据集。他们按照数据集的质量按比例抽样。
尽管语言模型的性能和容量随着时间的推移得到了改善,但最先进的模型仍然需要进行任务特定的微调。三种评估模式可以总结如下:
- 零样本:仅给出任务的自然语言描述(即,不显示任何正确输出的示例),模型预测答案。
- 单样本:除了任务的描述外,模型还会显示一个示例。
- 少样本:除了任务的描述外,模型还会显示几个示例。
在这三种情况下,都不会执行梯度更新(因为我们仅评估,而不是训练模型)。图4.11展示了每种评估模式的示例设置,任务是将英文文本翻译成西班牙语。
如图所示,在零样本模式下,模型仅呈现任务描述和翻译提示。类似地,在单样本和少样本模式下,模型分别首先显示一个或几个示例,然后呈现实际的翻译提示。作者观察到,GPT-3在零样本和单样本设置下取得了有希望的结果。在少样本设置下,该模型大多具有竞争力,并且对于某些任务,甚至超越了当前的最先进技术。
除了常见的NLP任务外,GPT-3似乎在需要快速适应或即时推理的任务上展示了一些非凡的能力。作者观察到,GPT-3能够在任务中表现得相当出色,如解码单词、执行三位数的算术运算,甚至在只看到一次定义后,能够在句子中使用新词。作者还观察到,在少样本设置下,GPT-3生成的新闻文章足够好,以至于在与人工生成的文章区分时,给人类评估者带来了困难。
GPT-3额外技能/能力的提升可以归因于多个因素。它接触了大量多样化的数据集,使其能够构建非常强大的分布式词汇、短语、概念等表示,从而有效地进行泛化。模型庞大的规模进一步使它能够内化在多个数据集上看到的规则和模式,这些数据集融合了多种获得的能力,如摘要、解码单词等。
该模型的规模非常庞大,以至于需要专用的高性能集群来训练,如论文中所述。作者讨论了训练这个庞大模型所需的计算和能源。GPT-3及更高版本并未公开提供,但可以通过OpenAI API进行微调和进一步训练。有关更多内容将在接下来的章节中介绍。
总结
在本章中,我们介绍了一些主导最近NLP模型的核心理念,如注意力机制、上下文嵌入和自注意力。我们利用这些基础知识学习了transformer架构及其内部组件。接着,我们概述了不同的基于transformer的架构家族,并简要讨论了BERT及其架构系列。我们涵盖了三种不同的NLP任务,并探讨了预训练模型与微调模型之间的性能差异。在本章的下一部分,我们讨论了来自OpenAI的仅解码器transformer语言模型。我们讨论了GPT和GPT-2的架构和数据集相关的选择,并利用Hugging Face的transformer包开发了基于GPT-2的文本生成管道。最后,我们以简短的GPT-3讨论结束了本章。我们讨论了开发如此庞大的模型背后的各种动机及其众多能力,这些能力超越了传统的基准测试。
在下一章中,我们将继续基于这些概念,深入探讨大型语言模型(LLMs)领域。
参考文献
- Cheng, Jianpeng, Li Dong, and Mirella Lapata. 2016. “Long Short-Term Memory-Networks for Machine Reading.” arXiv. arxiv.org/pdf/1601.06….
- Vaswani, Ashish, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, Lukasz Kaiser, and Illia Polosukhin. 2023. “Attention Is All You Need.” arXiv. arxiv.org/abs/1706.03….
- Howard, Jeremy, and Sebastian Ruder. 2018. “Universal Language Model Fine-Tuning for Text Classification.” arXiv. arxiv.org/abs/1801.06….
- Devlin, Jacob, Ming-Wei Chang, Kenton Lee, and Kristina Toutanova. 2019. “BERT: Pre-Training of Deep Bidirectional Transformers for Language Understanding.” arXiv. arxiv.org/abs/1810.04….
- Liu, Yinhan, Myle Ott, Naman Goyal, Jingfei Du, Mandar Joshi, Danqi Chen, Omer Levy, Mike Lewis, Luke Zettlemoyer, and Veselin Stoyanov. 2019. “RoBERTa: A Robustly Optimized BERT Pretraining Approach.” arXiv. arxiv.org/abs/1907.11….
- Lan, Zhenzhong, Mingda Chen, Sebastian Goodman, Kevin Gimpel, Piyush Sharma, and Radu Soricut. 2020. “ALBERT: A Lite BERT for Self-Supervised Learning of Language Representations.” arXiv. arxiv.org/abs/1909.11….
- Sanh, Victor, Lysandre Debut, Julien Chaumond, and Thomas Wolf. 2020. “DistilBERT, a Distilled Version of BERT: Smaller, Faster, Cheaper and Lighter.” arXiv. arxiv.org/abs/1910.01….
- Radford, Alec, and Karthik Narasimhan. 2018. “Improving Language Understanding by Generative Pre-Training.” Semantic Scholar. www.semanticscholar.org/paper/Impro….
- Radford, Alec, Jeffrey Wu, Rewon Child, David Luan, Dario Amodei, and Ilya Sutskever. 2019. “Language Models Are Unsupervised Multitask Learners.” OpenAI. cdn.openai.com/better-lang….
- Hoffmann, Jordan, Sebastian Borgeaud, Arthur Mensch, Elena Buchatskaya, Trevor Cai, Eliza Rutherford, Diego de Las Casas, et al. 2022. “Training Compute-Optimal Large Language Models.” arXiv. arxiv.org/abs/2203.15….
- Raffel, Colin, Noam Shazeer, Adam Roberts, Katherine Lee, Sharan Narang, Michael Matena, Yanqi Zhou, Wei Li, and Peter J. Liu. 2023. “Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer.” arXiv. arxiv.org/abs/1910.10….
- Dai, Zihang, Zhilin Yang, Yiming Yang, Jaime Carbonell, Quoc V. Le, and Ruslan Salakhutdinov. 2019. “Transformer-XL: Attentive Language Models Beyond a Fixed-Length Context.” arXiv. arxiv.org/abs/1901.02….
- Lewis, Mike, Yinhan Liu, Naman Goyal, Marjan Ghazvininejad, Abdelrahman Mohamed, Omer Levy, Ves Stoyanov, and Luke Zettlemoyer. 2019. “BART: Denoising Sequence-to-Sequence Pre-Training for Natural Language Generation, Translation, and Comprehension.” arXiv. arxiv.org/abs/1910.13….
- Yang, Jingfeng, Hongye Jin, Ruixiang Tang, Xiaotian Han, Qizhang Feng, Haoming Jiang, Bing Yin, and Xia Hu. 2023. “Harnessing the Power of LLMs in Practice: A Survey on ChatGPT and Beyond.” arXiv. arxiv.org/abs/2304.13….
- GLUE Benchmark. n.d. gluebenchmark.com/.
- Socher, Richard, Alex Perelygin, Jean Wu, Jason Chuang, Christopher D. Manning, Andrew Ng, and Christopher Potts. 2013. “Recursive Deep Models for Semantic Compositionality over a Sentiment Treebank.” ACL Anthology. aclanthology.org/D13-1170/.
- Zhu, Yukun, Ryan Kiros, Richard Zemel, Ruslan Salakhutdinov, Raquel Urtasun, Antonio Torralba, and Sanja Fidler. 2015. “Aligning Books and Movies: Towards Story-Like Visual Explanations by Watching Movies and Reading Books.” arXiv. arxiv.org/abs/1506.06….
- Chelba, Ciprian, Tomas Mikolov, Mike Schuster, Qi Ge, Thorsten Brants, Phillipp Koehn, and Tony Robinson. 2014. “One Billion Word Benchmark for Measuring Progress in Statistical Language Modeling.” arXiv. arxiv.org/abs/1312.30….
- GPT-News. 2019. “Better Language Models and Their Implications.” OpenAI Blog. openai.com/blog/better….
- Million Headlines Dataset: dataverse.harvard.edu/dataset.xht….
- Child, Rewon, Scott Gray, Alec Radford, and Ilya Sutskever. 2019. “Generating Long Sequences with Sparse Transformers.” arXiv. arxiv.org/abs/1904.10….
- Stanford SQuAD. n.d. rajpurkar.github.io/SQuAD-explo….
- OpenAI. n.d. “Key Concepts to Understand When Working with the OpenAI API.” platform.openai.com/docs/introd….
- Manning, Christopher. “Natural Language Processing with Deep Learning.” Slide 27. 2024. web.stanford.edu/class/cs224….
- Clark, Kevin, Minh-Thang Luong, Quoc V. Le, and Christopher D. Manning. 2020. “ELECTRA: Pre-Training Text Encoders as Discriminators Rather Than Generators.” arXiv. arxiv.org/abs/2003.10….