切换语言为:繁体
生成语言模型(GLMs)和大型语言模型(LLMs)

生成语言模型(GLMs)和大型语言模型(LLMs)

  • 爱糖宝
  • 2024-08-16
  • 2066
  • 0
  • 0

在本章中,您将学习生成语言模型(GLMs)和大型语言模型(LLMs)。接下来,您将学习如何在自己的文本上预训练任何语言模型,例如生成预训练变换器2(GPT-2),并将其用于自然语言生成(NLG)等任务。您将了解文本到文本转换器(T5)模型的基本知识,进行T5的实际多任务学习实验,并在自己的机器翻译(MT)数据上训练多语言T5(mT5)模型。在完成本章后,您将对GLMs及其在文本到文本应用中的各种用例(如摘要生成、释义、多任务学习、零样本学习和机器翻译)有一个概述。

本章将涵盖以下主题:

  • 使用GLMs

  • 使用文本到文本模型

  • 自回归语言模型训练

  • GLM训练

  • 使用自回归模型进行NLG

技术要求

以下库/软件包是成功完成本章所必需的:

  • Anaconda

  • transformers 4.0.0

  • pytorch 1.0.2

  • tensorflow 2.4.0

  • SimpleT5 0.1.4

  • datasets 2.10.0

  • tokenizers

生成语言模型简介

在本章中,您将了解生成语言模型(GLMs)和大型语言模型(LLMs)。与基于变换器编码器部分的自编码语言模型(如BERT)主要适用于分类问题不同,生成模型则是编码器-解码器模型或仅解码器模型。GLMs最初是为语言生成任务(如机器翻译或文本摘要)而设计的,但我们也可以看到GLMs在其他成功的应用场景中。

自编码(AE)模型特别适用于分类任务,如文本分类和情感分析,以及基于标记的任务,如命名实体识别(NER)或词性标注(POS)。在这些任务中,AE模型通过将句子开头的特殊CLS标记或任何位置的单个标记映射到预定义的类标签来进行分类。另一方面,虽然GLMs广泛用于语言生成任务,但它们最近也成功地应用于分类任务。

GLMs的预训练使用两个目标中的一个:要么是因果语言建模目标,使模型能够预测序列中的下一个单词(例如,GPT-3或PaLM),要么是去噪目标,使模型从损坏的句子中恢复原始句子(例如T5和BART)。前者完全依赖于变换器的解码器部分,也称为自回归(AR)模型;后者则使用变换器的编码器-解码器部分,也称为文本到文本或序列到序列模型。

这些模型通常用于机器翻译、对话系统和文本摘要等任务。一个有趣的特点是,生成的文本可以用于分类任务,这些任务通常由仅编码器模型解决。然而,反过来则不成立,我们不能使用仅编码器模型进行生成。例如,使用BERT翻译文本几乎是不可能的。

控制生成模型的文本输出对于防止生成有害文本和模型幻觉问题至关重要。通过控制输出,我们可以轻松地将GLMs用于分类任务。关键技巧是将任务表达为指令。以下是一些示例:

任务 输入模板 可能的文本输出
情感分析 输入文本 + “情感是什么?” “它是积极的”


“它是消极的”
文本分类 输入文本 + “这篇文本关于什么?” “它是关于经济的”


“它是关于健康的”
文本回归 输入文本 + “你能在1到5的范围内给句子质量评分吗?” “它是3.0”


“它是1.0”
命名实体识别 “... 约翰 … 巴黎 …” + 提示 “巴黎是一个城市实体”


“约翰是一个人实体”

表4.1 - 任务提示

在输入文本中添加称为提示的文本,以便模型朝向所需的目标进行调整。

得益于LLMs的生成能力和提示(我们将在后续章节中详细讨论提示),生成模型将任何任务视为文本到文本的问题,这导致了成功的解决方案。这种方法提供了非常容易应用多任务学习的机会,因为我们可以在同一批次中处理各种任务。我们可以将许多任务映射到一个单一的结构,即文本到文本。目前流行的主要语言模型如GPT-3、PaLM和ChatGPT都利用了这种生成特性。我们将在第10章中进一步讨论LLMs。

使用GLMs和基于指令的多任务学习的一个显著例子是FLAN框架。该框架能够同时建模1800个不同的指令微调任务,而无需更改模型的架构。通过将模型扩展到5400亿参数的语言模型(PaLM)以及将任务规模从62个扩展到1800个,即使在训练过程中未曾接触到的任务(称为零样本学习),模型也能取得成功。得益于允许这种扩展的生成模型,每天遇到令人兴奋的应用并不令人惊讶。

还需要注意的是,随着时间的推移,我们今天看到的提示工程将逐渐消失。我们已经看到许多研究文章旨在找到最有效的提示。然而,随着指令微调的进展,我们离拥有越来越自然的指令越来越近。ChatGPT是这些进展的绝佳例子。ChatGPT(GPT-3.5和davinci-003模型)的指令能力表明,更好地理解指令并更好地遵循指令可以带来许多新功能。您可能已经看到许多人使用这些微调模型创建了诸如对话问答系统等出色的工具。

但是(在本书编写时),仍然存在一些问题。LLMs仍然受到幻觉的困扰,许多研究正在进行以解决这个问题。有时候,即使您提供了良好的上下文并提出了相关问题(开放书籍问答或OBQA),您也可能会收到一个幻觉回答。使用这些���具非常重要,但了解它们的问题更为重要。

使用GLMs

变换器架构最初旨在对文本到文本任务(如机器翻译和摘要生成)有效,但它已被用于各种自然语言处理(NLP)问题,从标记分类到共指消解。后续的工作开始更加创造性地分别使用架构的左侧和右侧。除了下一单词预测之外,还利用了一些去噪目标,以完全恢复来自损坏或截断输入的原始输入。

虽然像T5这样的文本到文本模型使用了编码器和解码器部分,而仅解码器模型如GPT仅使用解码器部分。仅解码器自回归模型阻止模型在前向方向上访问当前单词右侧的单词(或在后向方向上访问当前单词左侧的单词),这称为单向性。

GPT及其后续模型(GPT-2、GPT-3、InstructGPT(即GPT-3.5)和ChatGPT)、Transformer-XL和XLNet是文献中一些流行的仅解码器自回归模型。尽管XLNet基于自回归,但它通过基于排列的语言目标以双向方式利用了单词的上下文。现在,我们开始介绍这些模型,并展示如何通过各种实验训练这些模型。让我们首先看看GPT。

GPT模型系列

自回归模型由多个变换器解码器块组成。每个块包含一个带掩码的多头自注意力层以及一个点对点前馈层。最终变换器块的激活输入SoftMax函数,生成整个词汇表上的单词概率分布,以预测下一个单词。值得注意的例子大多来自OpenAI的GPT模型系列。

在原始的GPT论文《通过生成预训练改进语言理解》(2018)中,作者解决了传统基于机器学习的NLP管道所面临的几个瓶颈。例如,首先,传统管道需要大量的任务特定数据和任务特定架构。其次,很难在对预训练模型的架构进行最小更改的情况下应用任务感知的输入变换。OpenAI团队设计的原始GPT及其后续模型(GPT-2和GPT-3)集中于这些问题及其解决方案,以减轻这些瓶颈。原始GPT研究的主要贡献是,预训练模型不仅在单一任务中取得了令人满意的结果,还在多任务中取得了成功,这被称为多任务学习。在自监督预训练后,模型只需用相对少量的任务特定数据对下游任务(或多个任务)进行微调,这被称为监督微调(SFT)。这种两阶段方案在其他变换器模型中也被广泛使用,自监督预训练之后是监督微调。

GPT模型向我们展示了GLMs如何通过依赖于多任务学习来解决零样本任务泛化问题。为了使多任务学习可行,GPT架构保持尽可能通用:仅输入以任务特定的方式转换,而整个架构保持完全相同。这种方法将文本输入转换为适合任务的格式,以便预训练模型能够理解任务。图4.1左侧(来自原始论文)说明了在原始GPT工作中使用的变换器架构和训练目标。右侧展示了如何为多个任务进行微调的输入转换。

简单来说,对于单序列任务如文本分类,输入按原样传递通过网络,线性层使用最后的激活来做出决策。对于句子对任务如文本蕴涵,由两序列组成的输入用分隔符标记,如图4.1中的第二个示例。在这两种情况下,架构看到的是经过预训练模型处理的均匀标记序列。用于这种转换的分隔符帮助预训练模型知道在文本蕴涵的情况下哪个部分是前提,哪个部分是假设。得益于输入转换,我们不必在任务间架构上做出实质性变化。

您可以在这里看到输入转换的示例:

生成语言模型(GLMs)和大型语言模型(LLMs)

GPT及其后继模型主要专注于实现一种特定的架构设计,以消除微调阶段的需求。这一理念基于模型可以在预训练阶段学习大量语言知识,从而在微调阶段几乎不需要额外工作。在推理过程中,模型会接收文本以及一些任务示例作为输入。在某些情况下,如零样本学习,甚至不会提供任何示例。

原始GPT的后继模型

流行的ChatGPT,如其名称所示,基于OpenAI团队开发的GPT。在此过程中,团队还设计了几个其他模型,包括GPT-2、GPT-3、InstructGPT和ChatGPT。

GPT-2(参见论文《语言模型是无监督多任务学习者》,2019年),作为原始GPT的后继模型,称为WebText,是一个比GPT更大的模型,且训练数据比原始模型多得多。GPT-2在八个任务中的七个任务上在零样本设置下取得了更好的结果,该设置中没有应用微调,但在一些其他任务上表现有限。GPT-2的理念是语言模型主要需要自监督的预训练。尽管该模型的理念非常有价值,但性能并不是特别好。然而,它在一些情况下表现得非常出色。研究人员追随这种方法,因为他们意识到零样本/少样本学习的理念可以通过扩展训练过程的某些方面来应用。他们发现一个任务无关的模型可以在一个庞大而多样的数据集上进行训练。

由于他们发现,通过扩展模型规模、数据集规模和多任务的任务规模,语言模型的零样本和少样本能力有了显著提高,OpenAI团队训练了GPT-3模型(参见论文《语言模型是少样本学习者》,2020年),其参数数量为1750亿,比GPT-2大了100倍。这项研究显示,扩展模型规模可以显著提高任务无关和少样本学习的性能。

GPT-2和GPT-3的架构类似,主要的区别通常在于模型规模、多样的任务以及数据集的数量/质量。由于数据集中的数据量巨大且训练的参数数量庞大,GPT-3在许多下游任务中在零样本、单样本和少样本(如K=32样本)设置下取得了更好的结果,而无需任何基于梯度的微调。团队展示了,随着参数规模和示例数量的增加,模型性能在许多任务中(包括翻译、问答和掩码标记任务)得到了提升。

GLMs的主要问题之一是生成有害信息的潜在风险。为了应对有关有毒语言和虚假信息的担忧,团队推出了一种称为InstructGPT的新方法,作为GPT模型系列的另一个扩展。为了提高模型的安全性和实用性,团队采用了一种名为“人类反馈强化学习”(RLHF)的技术来优化GPT-3。与GPT-3相比,InstructGPT模型在理解和执行用自然语言表达的指令方面表现出更好的能力。此外,它在生成虚假或有害信息的可能性方面也有所降低。

OpenAI团队为InstructGPT开发了一个三步流程。第一步是对模型进行微调,然后创建奖励模型(RM)。第三步是对监督微调模型进行进一步的优化,使用强化学习。InstructGPT的一个优势是其与人类意图的强一致性。这是通过使用强化学习框架实现的,使其能够从人类反馈中学习。据说流行的ChatGPT基于InstructGPT,但我们还没有看到关于ChatGPT训练过程的任何文档。

Transformer-XL

另一个重要的生成模型是Transformer-XL。模型的作者指出,Transformer模型在初始设计中由于缺乏递归和上下文碎片化而受到固定长度上下文的困扰,尽管它们能够学习长期依赖关系。大多数Transformer将文档分解为固定长度(通常为512)的片段,其中跨片段的信息流是不可能的。因此,语言模型无法捕捉超出这一固定长度限制的长期依赖关系。此外,分段过程构建片段时并未考虑句子边界。一个片段可能荒谬地由一个句子的后半部分和其后续句子的前半部分组成,从而导致语言模型在预测下一个标记时可能会遗漏必要的上下文信息。这个问题在文献中被称为上下文碎片化问题。

为了解决这些问题,Transformer-XL的作者(参见论文《Transformer-XL:超越固定长度上下文的注意力语言模型》,2019年)提出了一种新型Transformer架构,该架构包括一个片段级递归机制和一种新的位置编码方案。这种方法激发了许多后续模型的设计。模型可以处理的最大依赖长度受限于层数和片段长度。

XLNet

掩码语言建模(MLM)在仅编码器Transformer架构的预训练阶段中占据主导地位。然而,它曾受到批评,因为掩码标记在预训练阶段存在,但在微调阶段缺失,这导致了预训练和微调之间的差异。由于这种缺失,模型可能无法利用在预训练阶段学到的所有信息。XLNet(参见论文《XLNet:通用自回归预训练用于语言理解》,2019年)用排列语言建模(PLM)替代了MLM,PLM是对输入标记的随机排列,以克服这一瓶颈。排列语言建模使每个标记位置能够利用来自所有位置的上下文信息,从而捕捉双向上下文。目标函数仅排列因式分解顺序,并定义标记预测的顺序,但不改变序列的自然位置。简而言之,模型在排列后选择一些标记作为目标,然后尝试基于剩余标记和目标的自然位置预测这些目标。这使得可以以双向方式使用自回归(AR)模型。

XLNet充分利用了AE和AR模型。它实际上是一个通用的AR模型。除了目标函数外,XLNet还由两个重要机制组成:将Transformer-XL的片段级递归机制集成到其框架中,并包括精心设计的双流注意力机制,用于目标感知表示。

我们将在下一节中讨论那些同时使用Transformer两个部分的模型。

使用文本到文本模型

Transformer的左侧编码器和右侧解码器部分通过交叉注意力连接,这帮助每个解码器层关注最终的编码器层,这意味着它利用了在编码器层积累的编码信息。这自然地推动模型生成与原始输入紧密相关的输出。以下是一些流行的文本到文本模型,它们保留了Transformer的编码器和解码器部分:

  • T5: 统一的文本到文本Transformer,探索迁移学习的极限

  • BART: 双向和自回归Transformer

  • PEGASUS: 用于抽象总结的提取间隙句子预训练序列到序列模型

让我们开始理解和使用T5。

使用T5进行多任务学习

大多数自然语言处理(NLP)架构,从Word2Vec到Transformer,通过使用上下文(邻近)词来预测被掩码的单词来学习嵌入和其他参数。我们将NLP问题视为单词预测问题。虽然一些文本到文本模型纯粹依赖于下一个单词预测,但像T5和BART这样的模型利用了去噪目标,最大化模型在重构随机损坏文本方面的能力。也就是说,模型被训练来预测输入中缺失或损坏的标记。T5(参见论文《探索迁移学习的极限:统一的文本到文本Transformer》,2019年)强调了这一点,作者基于此开发了自己的目标函数。但T5的主要贡献是通过将任务重新表述为指令,从而实现了多任务学习的能力。

T5提出了一个统一的框架,以相同的结构解决许多任务。T5的核心思想是将所有NLP任务重新表述为文本到文本(Seq2Seq)问题,其中输入和输出都是标记列表。Transformer已经被发现对于应用相同的模型到各种NLP任务非常有用,如GPT-3所示,从问答到文本总结,这使我们能够进行多任务训练。

以下图表受到原始论文的启发,展示了T5如何在统一框架内解决许多(图中四个)不同的NLP任务——机器翻译(MT)、语言可接受性、语义相似性和总结:

生成语言模型(GLMs)和大型语言模型(LLMs)

T5模型大致遵循了最初的编码器-解码器Transformer模型。它在层归一化、去噪目标函数和位置嵌入方案上进行了修改。T5使用相对位置嵌入,而不是使用正弦位置嵌入或学习的位置嵌入,这在Transformer架构中变得越来越普遍。

T5将任务转换为文本到文本格式。模型的输入由任务前缀和附加的输入文本组成。我们将标注的文本数据集转换为 {'inputs': '....', 'targets': ...'} 格式,其中在输入中插入任务目的。然后,我们用标注数据训练模型,使其学习如何完成任务。正如前面的图表所示,对于英语-德语翻译任务,输入 "translate English to German: That is good." 将产生 "das ist gut."。同样,任何带有 "summarize:" 前缀的输入都会被模型总结。

需要注意的是,T5模型并不是为了任务无关的目的设计的。在下一节中,我们将讨论如何将其修改为任务无关的,这称为T0。

使用T5进行多任务训练

现在,让我们简单了解一下如何使用SimpleT5库在T5架构中应用多任务训练。为了全面理解和解决多任务学习问题,我们将从一个概念验证(PoC)开始。我们的PoC将集中在确定多任务学习的想法是否可以变为现实。当然,探索所有潜在影响是重要的。首先,模型选择、数据集和任务多样性将尽可能保持简单。我们将使用两个不同的数据集:用于情感分析的glue-sst2数据集和来自Opus项目的opus_en-de(英语-德语)机器翻译数据集。

NLP库

在本书中,我们使用各种来源和库,以便您体验不同的基础设施。通过这种方式,我们将学习大量开源软件。我们将它们传播给社区。例如,我们现在将使用SimpleT5库进行多任务训练。在下一节中,我们将使用simpletransformer库来训练T5进行机器翻译。这两个库都是基于Hugging Face的Transformers库构建的。

首先,让我们安装必要的库:

pip install simpleT5 datasets

为了更快地训练,我们现在下载T5模型的小型检查点。但请记住,我们需要更大的模型来提高实际生产中的性能。这对于少样本问题尤为重要。

from simplet5 import SimpleT5
model = SimpleT5()
model.from_pretrained("t5", "t5-small")

现在,我们将使用datasets库加载数据集并为建模做好准备。由于SimpleT5需要一个pandas DataFrame,并且列名必须是[source_text, target_text],我们在pandas DataFrame中重命名了列。我们仅取前1000个句子进行快速原型设计。一旦成功训练模型,您可以使用整个数据集来提高准确性!

import pandas as pd
from datasets import load_dataset

sst2_df = pd.DataFrame(
    load_dataset("glue", "sst2", split="train[:1000]"))
sst2_df = sst2_df[["sentence", "label"]]
sst2_df.columns = ["source_text", "target_text"]

我们将使用提示来重新表述任务,以便它们适用于文本到文本格式。我们在源句子中添加指令 "What is the sentiment? Good or Bad?"。同样,我们将目标文本的 "Good" 和 "Bad" 设置为1和0,如下所示:

prompt_sentiment = ". What is the sentiment ? " + "Good or Bad ?"
sst2_df["source_text"] = sst2_df.source_text.apply(lambda x: x + prompt_sentiment)
sst2_df["target_text"] = sst2_df.target_text.apply(lambda x: "Good" if x == 1 else "Bad")

我们将在MT数据集中进行类似的安排,以适应文本到文本模板。首先,我们从opus en-de数据集中加载1000个实例。注意,我们将源和目标分别设置为英语和德语,以应用英语到德语的翻译:

opus = load_dataset("opus100", "de-en", split="train[:1000]")
opus_df = pd.DataFrame(opus)
opus_df["source_text"] = opus_df.apply(lambda x: x.translation["en"], axis=1)
opus_df["target_text"] = opus_df.apply(lambda x: x.translation["de"], axis=1)
opus_df = opus_df[["source_text", "target_text"]]

我们在句子的末尾添加的提示是 "Translate English to German",如下所示:

prompt_tr = ". Translate English to German"
opus_df["source_text"] = opus_df.source_text.apply(lambda x: x + prompt_tr)

我们的两个任务已经准备好进行训练。我们将它们合并并打乱:

merge = opus_df.append(sst2_df).sample(frac=1.0)
merge.head(5)

生成语言模型(GLMs)和大型语言模型(LLMs)

最后,我们将两个任务合并为一个seq2seq格式的数据包。我们选择1800个实例用于训练,200个用于测试:

train_df = merge[:1800]
eval_df = merge[1800:]

借助SimpleT5框架,训练过程非常简单。我们将源文本的最大token长度设置为512,这比目标文本的长度128要长:

model.train(
    train_df=train_df,
    eval_df=eval_df,
    source_max_token_len=512,
    target_max_token_len=128,
    batch_size=8,
    max_epochs=3,
    use_gpu=True,
    precision=32
)

训练成功完成后,我们可以通过推断体验多任务学习的应用。现在,我们将使用相同的句子 "The cats are fun!" 进行情感分析和机器翻译,使用适当的提示。

我们先从情感分析开始,只需在句子中添加情感提示:

import torch
model.device = torch.device("cpu")
a_sentence = "The cats are fun!"
model.predict(a_sentence + prompt_sentiment)

输出:['Good']

输出结果为 "Good",这是正确的标签。但此时重要的是模型理解了任务是什么。T5检查点(T5-small、T5-base或T5-large)已经经过不同任务的预训练。因此,它们有可能误解任务。现在我们可以说,我们通过提示正确引导了模型。我们不再关心模型是否分类准确,因此我们不对模型性能进行评估。

现在,将指令更改为英德翻译,通过在句子中添加prompt_tr并将提示传递给模型:

model.predict(a_sentence + prompt_tr)

输出:['Die Katzen sind Spaß!']

哇!效果很好。在这两种情况下,模型都理解了该做什么。尽管我们使用了少量实例,但模型的翻译能力非常好,因为预训练的T5检查点以前已经在这种翻译任务上训练过。我们在这里通过微调使用了迁移学习!不过,我们需要采取一些措施来提高模型性能:

  1. 使用整个数据集,而不仅仅是1000个样本。

  2. 增加任务多样性。可以添加如NER(命名实体识别)、问答和摘要等任务。

  3. 为每个任务使用多个数据集

  4. 增加指令多样性。使用不同提示的相同数据集。

  5. 选择更大的模型。我们使用了T5-small作为初始模型。根据您的硬件选择更大的模型,如T5-base或T5-large。

  6. 模型训练可能需要大量时间。关注参数高效的方法和框架。

在这一部分,我们通过将所有NLP问题整合为单一格式构建了一个简单的多任务学习模型。现在,让我们讨论什么是T0。

零-shot文本泛化与T0

T5框架在其训练过的任务中取得了成功。这一成功可以归因于其多任务建模方案。换句话说,在相同架构下解决不同任务对每个任务都有积极影响,因为知识在任务之间循环。然而,T5框架存在一个不足之处,即它不是任务无关的,这意味着它并未设计用于解决那些未包含在训练中的任务。

已知,扩大模型的参数规模、数据集大小和任务多样性可以在各种困难任务以及未见任务上取得语言理解能力的突破。在这一动机下,T0团队开发了一个能够成功解决零-shot泛化的任务无关模型。这个名为T0的模型基于T5架构,考虑了模型的扩展和数据的多样性。

这里展示了一个简化的T0训练和推断图:

生成语言模型(GLMs)和大型语言模型(LLMs)

如图所示,模型首先在多样化的持有任务混合数据上进行训练(图上部分)。随后,它在零-shot泛化评估中测试对未见过的持出任务的能力(图下部分)。共有62个数据集,分为12个任务,T0模型在任务间的零-shot任务泛化问题上进行测试,而不是在数据集间。

另一种基于去噪的Seq2Seq模型——BART

与XLNet类似,BART模型(参见论文《BART: Denoising Sequence-to-Sequence Pre-training for Natural Language Generation, Translation, and Comprehension》,2019)利用了自编码(AE)和自回归(AR)模型的方案。它采用了标准的Seq2Seq变换器架构,进行了小的修改。然而,它具有不同于其他AR模型的训练目标。它使用了多种噪声方法来破坏文档,而不是预测下一个词。该模型对领域的主要贡献在于允许我们应用多种创意的破坏方案,如下图所示:

生成语言模型(GLMs)和大型语言模型(LLMs)

我们将详细查看每种方案,如下所示:

  • Token Masking(标记掩码) : 随机用 [MASK] 符号遮盖标记,类似于 BERT 模型。

  • Token Deletion(标记删除) : 随机从文档中删除标记,模型需要判断哪些位置被删除了。

  • Text Infilling(文本填充) : 参照 SpanBERT,随机采样一些文本片段,并用一个 [MASK] 标记替代这些片段。此外,还包括 [MASK] 标记插入。

  • Sentence Permutation(句子排列) : 将输入中的句子分段并以随机顺序打乱。

  • Document Rotation(文档旋转) : 旋转文档,使其从图中的随机选择的标记 C 开始。目标是找到文档的起始位置。

BART 模型可以通过多种方式进行微调,以适应下游应用,就像 BERT 一样。

对于序列分类任务,输入通过编码器和解码器,解码器的最终隐藏状态被认为是学到的表示。然后,可以使用简单的线性分类器进行预测。同样,对于标记分类任务,将整个文档传入编码器和解码器,解码器的最后状态就是每个标记的表示。基于这些表示,我们可以进行如命名实体识别(NER)和词性标注(POS)的标记分类任务。

对于序列生成,BART 模型的解码器块可以直接微调用于序列生成任务,如抽象问答(QA)或总结。BART 的作者使用了两个标准的总结数据集进行训练:CNN/DailyMail 和 XSum。作者还展示了可以使用编码器部分(处理源语言)和解码器部分(生成目标语言的单词)作为一个单一的预训练解码器进行机器翻译(MT)。他们用一个新的随机初始化的编码器替代了原编码器的嵌入层,以学习源语言的单词。然后,模型以端到端的方式训练,这种方式训练新的编码器将外语单词映射到 BART 可以去噪的输入,从而生成目标语言。新的编码器可以使用一个与原 BART 模型不同的词汇表,包括外语。

在 Hugging Face 平台上,我们可以通过以下代码访问原始的预训练 BART 模型:

AutoModel.from_pretrained('facebook/bart-large')

当我们调用变换器库的标准总结管道时,如下代码所示,将加载一个蒸馏的预训练 BART 模型。此调用隐式地加载了 sshleifer/distilbart-cnn-12-6 模型和相应的分词器,如下所示:

summarizer = pipeline("summarization")

以下代码明确加载相同的模型和相应的分词器。代码示例接受待总结的文本并输出结果:

from transformers import (
    BartTokenizer, BartForConditionalGeneration, BartConfig)
from transformers import pipeline

model = BartForConditionalGeneration.from_pretrained('sshleifer/distilbart-cnn-12-6')
tokenizer = BartTokenizer.from_pretrained('sshleifer/distilbart-cnn-12-6')
nlp = pipeline("summarization", model=model, tokenizer=tokenizer)

text = '''
We order two different types of jewelry from this
company the other jewelry we order is perfect.
However with this jewelry I have a few things I
don't like. The little Stone comes out of these
and customers are complaining and bringing them
back and we are having to put new jewelry in their
holes. You cannot sterilize these in an autoclave
...[truncated]'''

q = nlp(text)
import pprint
pp = pprint.PrettyPrinter(indent=0, width=100)
pp.pprint(q[0]['summary_text'])

输出结果为:

('The little Stone comes out of these little stones and customers are complaining and bringing '
 'them back and we are having to put new jewelry in their holes. You cannot sterilize these in an '
 'autoclave because it heats up too much and the glue does not hold up so the second group of '
 'these that we used I did not sterilize them that way and the stones still came out.')

在下一节中,我们将讨论如何训练基础模型。

GLM 训练

在本节中,您将了解如何训练自己的语言模型。我们将从 GPT-2 开始,并深入探讨使用 transformers 库进行训练的不同功能。

您可以使用任何语料库来训练自己的 GPT-2,但在这个示例中,我们使用了简·奥斯汀的《艾玛》,这是一本浪漫小说。为了生成更通用的语言,建议使用更大的语料库。

在开始之前,需要注意的是,我们使用了 TensorFlow 的原生训练功能,展示了所有 Hugging Face 模型可以直接在 TensorFlow 或 PyTorch 上进行训练。如果您愿意,可以按照以下步骤进行操作:

您可以使用以下命令下载《艾玛》的原始文本:

wget https://raw.githubusercontent.com/teropa/nlp/master/resources/corpora/gutenberg/austen-emma.txt

接下来,需要为 GPT-2 训练 BytePairEncoding(BPE)分词器,语料库为您打算训练 GPT-2 的数据集。以下代码将从 tokenizers 库中导入 BPE 分词器:

from tokenizers.models import BPE
from tokenizers import Tokenizer
from tokenizers.decoders import ByteLevel as ByteLevelDecoder
from tokenizers.normalizers import Sequence, Lowercase
from tokenizers.pre_tokenizers import ByteLevel
from tokenizers.trainers import BpeTrainer

在这个示例中,我们打算训练一个更高级的分词器,增加了更多功能,如 Lowercase 归一化。要创建一个分词器对象,您可以使用以下代码:

tokenizer = Tokenizer(BPE())
tokenizer.normalizer = Sequence([Lowercase()])
tokenizer.pre_tokenizer = ByteLevel()
tokenizer.decoder = ByteLevelDecoder()

第一行创建了一个来自 BPE 分词器类的分词器。归一化部分添加了 Lowercase,pre_tokenizer 属性设置为 ByteLevel,以确保输入为字节。decoder 属性也必须设置为 ByteLevelDecoder,以便正确解码。

接下来,将使用最大词汇量 50000 和 ByteLevel 的初始字母表训练分词器,如下所示:

trainer = BpeTrainer(vocab_size=50000,
    inital_alphabet=ByteLevel.alphabet(),
    special_tokens=[
        "<s>",
        "<pad>",
        "</s>",
        "<unk>",
        "<mask>"
        ])
tokenizer.train(["austen-emma.txt"], trainer)

还需要添加特殊标记。为保存分词器,您需要创建一个目录,如下所示:

!mkdir tokenizer_gpt

可以通过以下命令保存分词器:

tokenizer.save("tokenizer_gpt/tokenizer.json")

分词器保存完毕后,就可以使用保存的分词器对语料库进行预处理,为 GPT-2 训练做准备。但首先,重要的导入不能忘记。进行导入的代码如下所示:

from transformers import (
    GPT2TokenizerFast, GPT2Config, TFGPT2LMHeadModel)

分词器可以通过 GPT2TokenizerFast 加载,如下所示:

tokenizer_gpt = GPT2TokenizerFast.from_pretrained(
    "tokenizer_gpt")

还需要添加特殊标记及其标记,如下所示:

tokenizer_gpt.add_special_tokens({
    "eos_token": "</s>",
    "bos_token": "<s>",
    "unk_token": "<unk>",
    "pad_token": "<pad>",
    "mask_token": "<mask>"
})

您还可以运行以下代码来双重检查一切是否正确:

tokenizer_gpt.eos_token_id 
# >> 2

这段代码将输出句子结束(EOS)标记的标识符(ID),当前分词器的 EOS 标记 ID 为 2。

您也可以通过执行以下代码来测试句子:

tokenizer_gpt.encode("<s> this is </s>")
# >> [0, 265, 157, 56, 2]

对于这个输出,0 是句子的开始,265、157 和 56 与句子本身相关,EOS 标记为 2,即 。

这些设置必须用于创建配置对象。以下代码将创建一个配置对象和 GPT-2 模型的 TensorFlow 版本:

config = GPT2Config(
    vocab_size=tokenizer_gpt.vocab_size,
    bos_token_id=tokenizer_gpt.bos_token_id,
    eos_token_id=tokenizer_gpt.eos_token_id
)
model = TFGPT2LMHeadModel(config)

运行配置对象后,您可以看到字典格式的配置,如下所示:

config
# >> GPT2Config { "activation_function": "gelu_new", "attn_pdrop": 0.1, "bos_token_id": 0, "embd_pdrop": 0.1, "eos_token_id": 2, "gradient_checkpointing": false, "initializer_range": 0.02, "layer_norm_epsilon": 1e-05, "model_type": "gpt2", "n_ctx": 1024, "n_embd": 768, "n_head": 12, "n_inner": null, "n_layer": 12, "n_positions": 1024, "resid_pdrop": 0.1, "summary_activation": null, "summary_first_dropout": 0.1, "summary_proj_to_labels": true, "summary_type": "cls_index", "summary_use_proj": true, "transformers_version": "4.3.2", "use_cache": true, "vocab_size": 11750}

如您所见,其他设置没有更改,值得注意的是 vocab_size 设置为 11750。原因是我们将最大词汇量设置为 50000,但语料库中的单词较少,其 Byte-Pair Encoding (BPE) 分词器创建了 11750 个单词。

现在,您可以准备您的语料库以进行预训练,如下所示:

with open("austen-emma.txt", "r", encoding='utf-8') as f:
    content = f.readlines()

内容现在将包括原始文件中的所有文本,但需要从每行中删除 '\n' 并删除少于 10 个字符的行,如下所示:

content_p = []
for c in content:
    if len(c) > 10:
        content_p.append(c.strip())
content_p = " ".join(content_p) + tokenizer_gpt.eos_token

删除短行将确保模型在长序列上进行训练,以便能够生成更长的序列。在前面的代码片段结束时,content_p 包含了带有 eos_token 的连接原始文件。但您也可以遵循不同的策略,例如,通过在每行末尾添加  来分隔每一行,这将帮助模型识别句子的结束。不过,我们打算使其适用于更长的序列而不遇到 EOS。代码如下所示:

tokenized_content = tokenizer_gpt.encode(content_p)

上述代码片段中的 GPT 分词器将对整个文本进行分词,并将其转换为一个长的令牌 ID 序列。

现在,是时候创建训练样本了,如下所示:

sample_len = 100
examples = []
for i in range(0, len(tokenized_content)):
    examples.append(
        tokenized_content[i:i + sample_len])

前面的代码将创建大小为 100 的样本,每个样本从给定文本的某一部分开始,到 100 个令牌后结束:

train_data = []
labels = []
for example in examples:
    train_data.append(example[:-1])
    labels.append(example[1:])

train_data 中,将有一个从开始到第 99 个令牌的大小为 99 的序列,而标签将有从 1 到 100 的令牌序列。

为了加快训练速度,需要将数据放入 TensorFlow 数据集形式,如下所示:

import tensorflow as tf
buffer = 500
batch_size = 16
dataset = tf.data.Dataset.from_tensor_slices(
    (train_data, labels))
dataset = dataset.shuffle(buffer).batch(batch_size,
    drop_remainder=True)

buffer 是用于数据洗牌的缓冲区大小,batch_size 是训练的批量大小。drop_remainder 用于丢弃余数,如果它少于 16。

现在,您可以指定优化器、损失和度量属性,如下所示:

optimizer = tf.keras.optimizers.Adam(
       learning_rate=3e-5,
       epsilon=1e-08, clipnorm=1.0)
loss = tf.keras.losses\
    .SparseCategoricalCrossentropy(from_logits=True)
metric = tf.keras.metrics\
      .SparseCategoricalAccuracy('accuracy')
model.compile(optimizer=optimizer,
       loss=[loss, *[None] * model.config.n_layer],
       metrics=[metric])

模型已编译并准备好进行训练,您可以指定希望的训练轮数,如下所示:

epochs = 10
model.fit(dataset, epochs=epochs)

您将看到类似于以下内容的输出:

生成语言模型(GLMs)和大型语言模型(LLMs)

到目前为止,您已经了解了如何训练自己的模型进行自然语言生成(NLG)。在下一节中,我们将描述如何利用这些 NLG 模型进行语言生成。

使用 AR 模型进行 NLG

在前一节中,您学习了如何在自己的语料库上训练 AR 模型。结果,您训练了一个自定义版本的 GPT-2。但问题是:我该如何使用它?为了解答这个问题,我们可以按如下方式进行操作:

让我们开始使用您刚刚训练的模型生成句子,如下所示:

def generate(start, model):
    input_token_ids = tokenizer_gpt.encode(start, return_tensors='tf')
    output = model.generate(
        input_token_ids,
        max_length=500,
        num_beams=5,
        temperature=0.7,
        no_repeat_ngram_size=2,
        num_return_sequences=1
    )
    return tokenizer_gpt.decode(output[0])

在上述代码片段中定义的 generate 函数接受一个起始字符串,并生成跟随该字符串的序列。您可以根据需要调整参数,例如将 max_length 设置为更小的序列长度,或者将 num_return_sequences 调整为生成不同的序列。

让我们尝试用一个空字符串来生成句子,如下所示:

generate(" ", model)

我们将得到如下输出:

生成语言模型(GLMs)和大型语言模型(LLMs)

从前面的输出可以看到,即使文本的语义可能不太令人满意,但在许多情况下,语法几乎是正确的。现在,让我们尝试不同的起始文本,将 max_length 设置为较小的值,如 30,如下所示:

generate("weston was very good")
>> 'weston was very good; but it, that he was a great must be a mile from them, and a miss taylor in the house;'

正如您可能记得的那样,"weston" 是小说中的一个角色。

为了保存模型,您可以使用以下代码将其保存,以便在发布或不同应用程序中重复使用:

model.save_pretrained("my_gpt-2/")

为了确保模型正确保存,您可以尝试加载它,如下所示:

model_reloaded = TFGPT2LMHeadModel.from_pretrained("my_gpt-2/")

保存了两个文件——一个配置文件和一个模型文件(model.h5),这是 TensorFlow 版本的文件。我们可以在以下截图中看到这两个文件:

生成语言模型(GLMs)和大型语言模型(LLMs)

Hugging Face 还规定了必须使用的标准文件名——这些标准文件名可以通过以下导入方式获取:

from transformers import (WEIGHTS_NAME, CONFIG_NAME, TF2_WEIGHTS_NAME)

然而,当使用 save_pretrained 函数时,不需要指定文件名,只需指定目录即可。

Hugging Face 还有 AutoModelAutoTokenizer 类,如您在之前的章节中看到的。您也可以使用这些功能来保存模型,但在此之前仍需要进行一些手动配置。首先,需要以正确的格式保存 tokenizer 以供 AutoTokenizer 使用。您可以通过以下方式使用 save_pretrained

tokenizer_gpt.save_pretrained("tokenizer_gpt_auto/")

这将生成以下输出:

生成语言模型(GLMs)和大型语言模型(LLMs)

文件列表会显示在您指定的目录中,但 tokenizer_config 文件必须手动更改才能使用。首先,您需要将其重命名为 config.json,其次,您需要在 JSON 格式中添加一个属性,指明 model_type 属性为 gpt2,如以下所示:

{
  "model_type": "gpt2",
  ...
}

现在,一切都准备好了,您可以使用以下两行代码来加载模型和 tokenizer:

model = AutoModel.from_pretrained("my_gpt-2/", from_tf=True)
tokenizer = AutoTokenizer.from_pretrained("tokenizer_gpt_auto")

请务必设置 from_tfTrue,因为您的模型是以 TensorFlow 格式保存的。

做得很好。您现在已经学会了如何使用 TensorFlow 和 transformers 预训练和保存自己的文本生成模型。您还学会了如何训练自己的 AR 模型。

总结

在本章中,我们叙述涵盖了 GLMs(自回归模型和序列到序列模型)的各种方面,从预训练到微调。我们通过训练 GLMs 并在机器翻译和多任务训练等任务上进行微调,探讨了这些模型的最佳特性。我们探索了 T5、GPT-3 和 T0 等更复杂模型的基础知识,并使用这些模型执行机器翻译。我们使用不同的 Python 库训练了基于 T5 的多任务学习原型。我们在自己的语料库上训练了 GPT-2 并使用它生成文本。我们学会了如何保存它并使用 AutoModel 加载它。我们还深入了解了如何使用 tokenizers 库训练和使用 BPE。

0条评论

您的电子邮件等信息不会被公开,以下所有项均必填

OK! You can skip this field.