真实世界的自然语言处理(三)(2)

本文涉及的产品
NLP 自学习平台,3个模型定制额度 1个月
NLP自然语言处理_高级版,每接口累计50万次
NLP自然语言处理_基础版,每接口每天50万次
简介: 真实世界的自然语言处理(三)

真实世界的自然语言处理(三)(1)https://developer.aliyun.com/article/1519800

9.4.2 XLNet

2019 年提出的 XLNet 是 BERT 的重要后继者,通常被引用为当今最强大的 PLM 之一。XLNet 解决了 BERT 训练中的两个主要问题:训练-测试偏差和掩码的独立性。第一个问题与 BERT 如何使用掩码语言模型(MLM)目标进行预训练有关。在训练时,BERT 被训练以便能够准确预测掩码标记,而在预测时,它只看到输入句子,其中不包含任何掩码。这意味着 BERT 在训练和测试之间暴露给的信息存在差异,从而产生了训练-测试偏差问题。

第二个问题与 BERT 如何对掩码标记进行预测有关。如果输入中有多个[MASK]标记,BERT 会同时对它们进行预测。乍一看,这种方法似乎没有任何问题——例如,如果输入是“The Statue of [MASK] in New [MASK]”,模型不会有困难地回答“Liberty”和“York”。如果输入是“The Statue of [MASK] in Washington, [MASK]”,大多数人(也可能是语言模型)会预测“Lincoln”和“DC”。但是,如果输入是以下内容:

[MASK]雕像位于[MASK][MASK]中

然后没有信息偏向于你的预测。BERT 不会从这个例子的训练中学习到“华盛顿特区的自由女神像”或“纽约的林肯像”这样的事实,因为这些预测都是并行进行的。这是一个很好的例子,表明你不能简单地对标记进行独立的预测,然后将它们组合起来创建一个有意义的句子。

注意 这个问题与自然语言的多模态性有关,这意味着联合概率分布中存在多种模式,并且独立做出的最佳决策的组合并不一定导致全局最佳决策。多模态性是自然语言生成中的一个重大挑战。

为了解决这个问题,你可以将预测顺序改为顺序预测,而不是并行预测。事实上,这正是典型语言模型所做的——逐个从左到右生成标记。然而,在这里,我们有一个插入了屏蔽标记的句子,并且预测不仅依赖于左边的标记(例如,前面示例中的“雕像的”),还依赖于右边的标记(“in”)。XLNet 通过以随机顺序生成缺失的标记来解决这个问题,如图 9.11 所示。例如,您可以选择首先生成“New”,这为下一个单词“York”和“Liberty”提供了强有力的线索,依此类推。请注意,预测仍然基于先前生成的所有标记。如果模型选择首先生成“Washington”,那么模型将继续生成“DC”和“Lincoln”,而不会混淆这两个标记。


图 9.11 XLNet 以任意顺序生成标记。

XLNet 已经在 Transformers 库中实现,您只需更改几行代码即可使用该模型。

9.4.3 RoBERTa

RoBERTa(来自“robustly optimized BERT”)是另一个在研究和工业中常用的重要 PLM。RoBERTa 重新审视并修改了 BERT 的许多训练决策,使其达到甚至超过后 BERT PLMs 的性能,包括我们之前介绍的 XLNet。截至本文撰写时(2020 年中期),我个人的印象是,RoBERTa 在 BERT 之后是被引用最多的第二个 PLM,并且在许多英文下游 NLP 任务中表现出稳健的性能。

RoBERTa 在 BERT 的基础上进行了几项改进,但最重要(也是最直接)的是其训练数据量。RoBERTa 的开发者收集了五个不同大小和领域的英文语料库,总计超过 160 GB 的文本(而训练 BERT 仅使用了 16 GB)。仅仅通过使用更多的数据进行训练,RoBERTa 在微调后的下游任务中超越了一些其他强大的 PLMs,包括 XLNet。第二个修改涉及我们在第 9.2.3 节中提到的下一句预测(NSP)目标,在该目标中,BERT 被预先训练以分类第二个句子是否是跟随语料库中第一个句子的“真实”句子。RoBERTa 的开发者发现,通过移除 NSP(仅使用 MLM 目标进行训练),下游任务的性能保持大致相同或略有提高。除此之外,他们还重新审视了批量大小以及 MLM 的屏蔽方式。综合起来,这个新的预训练语言模型在诸如问答和阅读理解等下游任务中取得了最先进的结果。

因为 RoBERTa 使用与 BERT 相同的架构,并且两者都在 Transformers 中实现,所以如果您的应用程序已经使用 BERT,那么切换到 RoBERTa 将非常容易。

注意与 BERT 与 RoBERTa 类似,跨语言语言模型 XLM(在第 8.4.4 节中介绍)有其“优化鲁棒性”的同类称为 XLM-R(缩写为 XML-RoBERTa)。⁸ XLM-R 对 100 种语言进行了预训练,并在许多跨语言 NLP 任务中表现出竞争力。

9.4.4 DistilBERT

尽管诸如 BERT 和 RoBERTa 等预训练模型功能强大,但它们在计算上是昂贵的,不仅用于预训练,而且用于调整和进行预测。例如,BERT-base(常规大小的 BERT)和 BERT-large(较大的对应物)分别具有 1.1 亿和 3.4 亿个参数,几乎每个输入都必须通过这个巨大的网络进行预测。如果您要对基于 BERT 的模型(例如我们在第 9.3 节中构建的模型)进行微调和预测,那么您几乎肯定需要一个 GPU,这并不总是可用的,这取决于您的计算环境。例如,如果您想在手机上运行一些实时文本分析,BERT 将不是一个很好的选择(它甚至可能无法适应内存)。

为了降低现代大型神经网络的计算需求,通常使用知识蒸馏(或简称蒸馏)。这是一种机器学习技术,其中给定一个大型预训练模型(称为教师模型),会训练一个较小的模型(称为学生模型)来模仿较大模型的行为。有关更多详细信息,请参见图 9.12。学生模型使用掩码语言模型(MLM)损失(与 BERT 相同),以及教师和学生之间的交叉熵损失。这将推动学生模型产生与教师尽可能相似的预测标记的概率分布。


图 9.12 知识蒸馏结合了交叉熵和掩码 LM 目标。

Hugging Face 的研究人员开发了 BERT 的精简版本称为DistilBERT,⁹它的大小缩小了 40%,速度提高了 60%,同时与 BERT 相比重新训练了 97%的任务性能。您只需将传递给 AutoModel.from_pretrained()的模型名称(例如,bert-base-cased)替换为精炼版本(例如,distilbert-base-cased),同时保持其余代码不变即可使用 DistilBERT。

9.4.5 ALBERT

另一个解决了 BERT 计算复杂性问题的预训练语言模型是 ALBERT,¹⁰简称“轻量 BERT”。与采用知识蒸馏不同,ALBERT 对其模型和训练过程进行了一些修改。

ALBERT 对其模型进行的一个设计变化是它如何处理词嵌入。在大多数深度 NLP 模型中,词嵌入由一个大的查找表表示和存储,该表包含每个词的一个词嵌入向量。这种管理嵌入的方式通常适用于较小的模型,如 RNN 和 CNN。然而,对于基于 Transformer 的模型,如 BERT,输入的维度(即长度)需要与隐藏状态的维度匹配,通常为 768 维。这意味着模型需要维护一个大小为 V 乘以 768 的大查找表,其中 V 是唯一词汇项的数量。因为在许多 NLP 模型中 V 也很大(例如,30,000),所以产生的查找表变得巨大,并且占用了大量的内存和计算。

ALBERT 通过将词嵌入查找分解为两个阶段来解决这个问题,如图 9.13 所示。第一阶段类似于从映射表中检索词嵌入,只是词嵌入向量的输出维度较小(比如,128 维)。在下一个阶段,使用线性层扩展这些较短的向量,使它们与模型的所需输入维度相匹配(比如,768)。这类似于我们如何使用 Skip-gram 模型扩展词嵌入(第 3.4 节)。由于这种分解,ALBERT 只需要存储两个较小的查找表(V × 128,加上 128 × 768),而不是一个大的查找表(V × 768)。


图 9.13 ALBERT(右)将词嵌入分解为两个较小的投影。

ALBERT 实施的另一个设计变化是 Transformer 层之间的参数共享。Transformer 模型使用一系列自注意力层来转换输入向量。这些层将输入转换的方式通常各不相同——第一层可能以一种方式转换输入(例如,捕获基本短语),而第二层可能以另一种方式进行转换(例如,捕获一些句法信息)。然而,这意味着模型需要针对每一层保留所有必要的参数(用于键、查询和值的投影),这是昂贵的,并且占用了大量内存。相反,ALBERT 的所有层都共享相同的参数集,这意味着模型重复应用相同的转换到输入上。这些参数被调整为这样一种方式,即尽管它们相同,一系列转换对于预测目标是有效的。

最后,ALBERT 使用一种称为 句子顺序预测(SOP)的训练目标进行预训练,而不是 BERT 采用的下一个句子预测(NSP)。正如前面提到的,RoBERTa 的开发人员和其他一些人发现 NSP 目标基本无用,并决定将其排除。ALBERT 用句子顺序预测 (SOP) 取代了 NSP,在这个任务中,模型被要求预测两个连续文本段落的顺序。例如:¹¹

  • (A) 她和她的男朋友决定去散步。 (B) 走了一英里后,发生了一些事情。
  • © 然而,周边区域的一位老师帮助了我站起来。(D) 起初,没有人愿意帮我站起来。

在第一个示例中,你可以知道 A 发生在 B 之前。在第二个示例中,顺序被颠倒,D 应该在 C 之前。这对人类来说很容易,但对机器来说是一个困难的任务——自然语言处理模型需要学会忽略表面主题信号(例如,“去散步”,“步行超过一英里”,“帮我站起来”),并专注于话语级连贯性。使用这种目标进行训练使得模型更加强大且可用于更深入的自然语言理解任务。

因此,ALBERT 能够通过更少的参数扩大其训练并超越 BERT-large。与 DistilBERT 一样,ALBERT 的模型架构与 BERT 几乎完全相同,您只需在调用 AutoModel.from_pretrained() 时提供模型名称即可使用它 (例如,albert-base-v1)。

9.5 案例研究 2:BERT 自然语言推理

在本章的最后一部分,我们将构建自然语言推理的 NLP 模型,这是一个预测句子之间逻辑关系的任务。我们将使用 AllenNLP 构建模型,同时演示如何将 BERT(或任何其他基于 Transformer 的预训练模型)集成到你的管道中。

9.5.1 什么是自然语言推理?

自然语言推理(简称 NLI)是确定一对句子之间逻辑关系的任务。具体而言,给定一个句子(称为前提)和另一个句子(称为假设),你需要确定假设是否从前提中逻辑推演出来。在以下示例中,这更容易理解。¹²

前提 假设 标签
一名男子查看一个身穿东亚某国制服的人物。 男子正在睡觉。 矛盾
一名年长和一名年轻男子微笑着。 两名男子对在地板上玩耍的猫笑着。 中性
进行多人踢足球的比赛。 一些男人正在运动。 蕴涵

在第一个例子中,假设(“这个人正在睡觉”)显然与前提(“一个人正在检查…”)相矛盾,因为一个人不能在睡觉时检查某事。在第二个例子中,你无法确定假设是否与前提矛盾或被前提蕴含(特别是“笑猫”的部分),这使得关系是“中性”的。在第三个例子中,你可以从前提中逻辑推断出假设——换句话说,假设被前提蕴含。

正如你猜到的那样,即使对于人类来说,NLI 也可能是棘手的。这项任务不仅需要词汇知识(例如,“人”的复数是“人们”,足球是一种运动),还需要一些“常识”(例如,你不能在睡觉时检查)。NLI 是最典型的自然语言理解(NLU)任务之一。你如何构建一个 NLP 模型来解决这个任务?

幸运的是,NLI 是 NLP 中一个经过深入研究的领域。NLI 最流行的数据集是标准自然语言推理(SNLI)语料库(nlp.stanford.edu/projects/snli/),已被大量用作 NLP 研究的基准。接下来,我们将使用 AllenNLP 构建一个神经 NLI 模型,并学习如何为这个特定任务使用 BERT。

在继续之前,请确保你已经安装了 AllenNLP(我们使用的是版本 2.5.0)和 AllenNLP 模型的模块。你可以通过运行以下代码来安装它们:

pip install allennlp==2.5.0
pip install allennlp-models==2.5.0

这也将 Transformers 库安装为一个依赖项。

9.5.2 使用 BERT 进行句对分类

在我们开始构建模型之前,请注意,NLI 任务的每个输入都由两部分组成:前提和假设。本书涵盖的大多数 NLP 任务仅有一个部分——通常是模型的输入的一个部分——通常是单个句子。我们如何构建一个可以对句子对进行预测的模型?

我们有多种方法来处理 NLP 模型的多部分输入。我们可以使用编码器对每个句子进行编码,并对结果应用一些数学操作(例如,串联、减法),以得到一对句子的嵌入(顺便说一句,这是孪生网络的基本思想¹³)。研究人员还提出了更复杂的具有注意力的神经网络模型,例如 BiDAF¹⁴。

然而,从本质上讲,没有什么阻止 BERT 接受多个句子。因为 Transformer 接受任何令牌序列,你可以简单地将两个句子串联起来并将它们输入模型。如果你担心模型混淆了两个句子,你可以用一个特殊的令牌,[SEP],将它们分开。你还可以为每个句子添加不同的值作为模型的额外信号。BERT 使用这两种技术对模型进行了少量修改,以解决句对分类任务,如 NLI。

流水线的其余部分与其他分类任务类似。特殊令牌[CLS]被附加到每个句子对,从中提取输入的最终嵌入。最后,您可以使用分类头将嵌入转换为一组与类相对应的值(称为logits)。这在图 9.14 中有所说明。


图 9.14 使用 BERT 对一对句子进行馈送和分类

在实践中,连接和插入特殊令牌都是由 SnliReader 处理的,这是专门用于处理 SNLI 数据集的 AllenNLP 数据集读取器。您可以初始化数据集并观察它如何将数据转换为 AllenNLP 实例,代码如下:

from allennlp.data.tokenizers import PretrainedTransformerTokenizer
from allennlp_models.pair_classification.dataset_readers import SnliReader
BERT_MODEL = 'bert-base-cased'
tokenizer = PretrainedTransformerTokenizer(model_name=BERT_MODEL, add_special_tokens=False)
reader = SnliReader(tokenizer=tokenizer)
dataset_url = 'https://realworldnlpbook.s3.amazonaws.com/data/snli/snli_1.0_dev.jsonl'
for instance in reader.read():
    print(instance)

数据集读取器从斯坦福 NLI 语料库中获取一个 JSONL(JSON 行)文件,并将其转换为一系列 AllenNLP 实例。我们指定了一个我在线上(S3)放置的数据集文件的 URL。请注意,在初始化分词器时,您需要指定 add_special_tokens=False。这听起来有点奇怪——难道我们不是应该在这里添加特殊令牌吗?这是必需的,因为数据集读取器(SnliReader)而不是分词器会处理特殊令牌。如果您仅使用 Transformer 库(而不是 AllenNLP),则不需要此选项。

前面的代码片段生成了以下生成的实例的转储:

Instance with fields:
         tokens: TextField of length 29 with text:
                [[CLS], Two, women, are, em, ##bracing, while, holding, to, go, packages,
 ., [SEP], The, sisters, are, hugging, goodbye, while, holding, to, go, 
 packages, after, just, eating, lunch, ., [SEP]]
                and TokenIndexers : {'tokens': 'SingleIdTokenIndexer'}
         label: LabelField with label: neutral in namespace: 'labels'.'
Instance with fields:
         tokens: TextField of length 20 with text:
                [[CLS], Two, women, are, em, ##bracing, while, holding, to, go, packages,
 ., [SEP], Two, woman, are, holding, packages, ., [SEP]]
                and TokenIndexers : {'tokens': 'SingleIdTokenIndexer'}
         label: LabelField with label: entailment in namespace: 'labels'.'
Instance with fields:
         tokens: TextField of length 23 with text:
                [[CLS], Two, women, are, em, ##bracing, while, holding, to, go, packages,
 ., [SEP], The, men, are, fighting, outside, a, del, ##i, ., [SEP]]
                and TokenIndexers : {'tokens': 'SingleIdTokenIndexer'}
         label: LabelField with label: contradiction in namespace: 'labels'.'
...

请注意,每个句子都经过了标记化,并且句子被连接并由[SEP]特殊令牌分隔。每个实例还有一个包含金标签的标签字段。

注意:您可能已经注意到令牌化结果中出现了一些奇怪的字符,例如##bracing 和##i。这些是字节对编码(BPE)的结果,这是一种将单词分割为所谓的子词单元的标记化算法。我们将在第十章中详细介绍 BPE。

9.5.3 使用 AllenNLP 与 Transformers

现在我们准备使用 AllenNLP 构建我们的模型。好消息是,由于 AllenNLP 的内置模块,您不需要编写任何 Python 代码来构建 NLI 模型——您只需要编写一个 Jsonnet 配置文件(就像我们在第四章中所做的那样)。AllenNLP 还无缝集成了 Hugging Face 的 Transformer 库,因此即使您想要将基于 Transformer 的模型(如 BERT)集成到现有模型中,通常也只需要进行很少的更改。

当将 BERT 集成到您的模型和流水线中时,您需要对以下四个组件进行更改:

  • Tokenizer—就像您在之前的 9.3 节中所做的那样,您需要使用与您正在使用的预训练模型相匹配的分词器。
  • Token indexer—Token indexer 将令牌转换为整数索引。由于预训练模型带有其自己预定义的词汇表,因此很重要您使用匹配的令牌索引器。
  • Token embedder—Token embedder 将令牌转换为嵌入。这是 BERT 的主要计算发生的地方。
  • Seq2Vec 编码器—BERT 的原始输出是一系列嵌入。您需要一个 Seq2Vec 编码器将其转换为单个嵌入向量。

如果这听起来令人生畏,不要担心—在大多数情况下,你只需要记住使用所需模型的名称来初始化正确的模块。接下来我会引导你完成这些步骤。

首先,让我们定义我们用于读取和转换 SNLI 数据集的数据集。我们之前已经用 Python 代码做过这个了,但在这里我们将在 Jsonnet 中编写相应的初始化。首先,让我们使用以下代码定义我们将在整个流水线中使用的模型名称。Jsonnet 相对于普通 JSON 的一个很酷的功能是你可以定义和使用变量:

local bert_model = "bert-base-cased";

配置文件中初始化数据集的第一部分看起来像以下内容:

"dataset_reader": {
    "type": "snli",
    "tokenizer": {
        "type": "pretrained_transformer",
        "model_name": bert_model,
        "add_special_tokens": false
    },
    "token_indexers": {
        "bert": {
            "type": "pretrained_transformer",
            "model_name": bert_model,
        }
    }
},

在顶层,这是初始化一个由类型 snli 指定的数据集读取器,它是我们之前尝试过的 SnliReader。数据集读取器需要两个参数—tokenizer 和 token_indexers。对于 tokenizer,我们使用一个 PretrainedTransformerTokenizer(类型:pretrained_transformer)并提供一个模型名称。同样,这是我们之前在 Python 代码中初始化和使用的分词器。请注意 Python 代码和 Jsonnet 配置文件之间的良好对应关系。大多数 AllenNLP 模块都设计得非常好,使得这两者之间有着很好的对应关系,如下表所示。

Python 代码 Jsonnet 配置
tokenizer = PretrainedTransformerTokenizer(model_name=BERT_MODEL,add_special_tokens=False) “tokenizer”: {“type”: “pretrained_transformer”,“model_name”: bert_model,“add_special_tokens”: false}

初始化令牌索引器部分可能看起来有点混乱。它正在使用模型名称初始化一个 PretrainedTransformerIndexer(类型:pretrained_transformer)。索引器将把索引结果存储到名为 bert 的部分(对应于令牌索引器的键)。幸运的是,这段代码是一个样板,从一个模型到另一个模型几乎没有变化,很可能当你在一个新的基于 Transformer 的模型上工作时,你可以简单地复制并粘贴这一部分。

至于训练/验证数据,我们可以使用本书的 S3 存储库中的数据,如下所示:

"train_data_path": "https://realworldnlpbook.s3.amazonaws.com/data/snli/snli_1.0_train.jsonl",
"validation_data_path": "https://realworldnlpbook.s3.amazonaws.com/data/snli/snli_1.0_dev.jsonl",

现在我们准备开始定义我们的模型:

"model": {
    "type": "basic_classifier",
    "text_field_embedder": {
        "token_embedders": {
            "bert": {
                "type": "pretrained_transformer",
                "model_name": bert_model
            }
        }
    },
    "seq2vec_encoder": {
        "type": "bert_pooler",
        "pretrained_model": bert_model
    }
},

在顶层,此部分定义了一个 BasicClassifier 模型(类型:basic_classifier)。它是一个通用的文本分类模型,它嵌入输入,使用 Seq2Vec 编码器对其进行编码,并使用分类头进行分类(带有 softmax 层)。您可以将您选择的嵌入器和编码器作为模型的子组件“插入”。例如,您可以通过单词嵌入嵌入标记,并使用 RNN 对序列进行编码(这是我们在第四章中所做的)。或者,您可以使用 CNN 对序列进行编码,就像我们在第七章中所做的那样。这就是 AllenNLP 设计的优点所在——通用模型仅指定了什么(例如,一个 TextFieldEmbedder 和一个 Seq2VecEncoder),但不是确切的如何(例如,单词嵌入、RNN、BERT)。您可以使用任何嵌入/编码输入的子模块,只要这些子模块符合指定的接口(即,它们是所需类的子类)。

在这个案例研究中,我们将首先使用 BERT 对输入序列进行嵌入。这是通过一个特殊的标记嵌入器,PretrainedTransformerEmbedder(类型:pretrained_transformer)实现的,它接受 Transformer 分词器的结果,经过预训练的 BERT 模型,并产生嵌入的输入。您需要将此嵌入器作为 token_embedders 参数的 bert 键的值传递(您之前为 token_indexers 指定的那个)。

然而,从 BERT 中得到的原始输出是一系列嵌入。因为我们感兴趣的是对给定的句子对进行分类,我们需要提取整个序列的嵌入,这可以通过提取与 CLS 特殊标记对应的嵌入来完成。AllenNLP 实现了一种称为 BertPooler(类型:bert_pooler)的 Seq2VecEncoder 类型,它正是这样做的。

在嵌入和编码输入之后,基本分类器模型处理剩下的事情——嵌入经过一个线性层,将它们转换为一组 logits,并且整个网络使用交叉熵损失进行训练,就像其他分类模型一样。整个配置文件如下所示。

列表 9.4 使用 BERT 训练 NLI 模型的配置文件

local bert_model = "bert-base-cased";
{
    "dataset_reader": {
        "type": "snli",
        "tokenizer": {
            "type": "pretrained_transformer",
            "model_name": bert_model,
            "add_special_tokens": false
        },
        "token_indexers": {
            "bert": {
                "type": "pretrained_transformer",
                "model_name": bert_model,
            }
        }
    },
    "train_data_path": "https://realworldnlpbook.s3.amazonaws.com/data/snli/snli_1.0_train.jsonl",
    "validation_data_path": "https://realworldnlpbook.s3.amazonaws.com/data/snli/snli_1.0_dev.jsonl",
    "model": {
        "type": "basic_classifier",
        "text_field_embedder": {
            "token_embedders": {
                "bert": {
                    "type": "pretrained_transformer",
                    "model_name": bert_model
                }
            }
        },
        "seq2vec_encoder": {
            "type": "bert_pooler",
            "pretrained_model": bert_model,
        }
    },
    "data_loader": {
        "batch_sampler": {
            "type": "bucket",
            "sorting_keys": ["tokens"],
            "padding_noise": 0.1,
            "batch_size" : 32
        }
    },
    "trainer": {
        "optimizer": {
            "type": "huggingface_adamw",
            "lr": 5.0e-6
        },
        "validation_metric": "+accuracy",
        "num_epochs": 30,
        "patience": 10,
        "cuda_device": 0
    }
}

如果您不熟悉数据加载器和训练器部分正在发生的事情也没关系。我们将在第十章讨论这些主题(批处理、填充、优化、超参数调整)。在将此配置文件保存在 examples/nli/snli_transformers.jsonnet 后,您可以通过运行以下代码开始训练过程:

allennlp train examples/nli/snli_transformers.jsonnet --serialization-dir models/snli

这将运行一段时间(即使在诸如 Nvidia V100 这样的快速 GPU 上也是如此),并在 stdout 上产生大量的日志消息。以下是我在四个时期后得到的日志消息的片段:

...
allennlp.training.trainer - Epoch 4/29
allennlp.training.trainer - Worker 0 memory usage MB: 6644.208
allennlp.training.trainer - GPU 0 memory usage MB: 8708
allennlp.training.trainer - Training
allennlp.training.trainer - Validating
allennlp.training.tensorboard_writer -                        Training |  Validation
allennlp.training.tensorboard_writer - accuracy           |     0.933  |     0.908
allennlp.training.tensorboard_writer - gpu_0_memory_MB    |  8708.000  |       N/A
allennlp.training.tensorboard_writer - loss               |     0.190  |     0.293
allennlp.training.tensorboard_writer - reg_loss           |     0.000  |     0.000
allennlp.training.tensorboard_writer - worker_0_memory_MB |  6644.208  |       N/A
allennlp.training.checkpointer - Best validation performance so far. Copying weights to 'models/snli/best.th'.
allennlp.training.trainer - Epoch duration: 0:21:39.687226
allennlp.training.trainer - Estimated training time remaining: 9:04:56
...

注意验证准确率(0.908)。考虑到这是一个三类分类,随机基线只会是 0.3。相比之下,当我用基于 LSTM 的 RNN 替换 BERT 时,我得到的最佳验证准确率约为~0.68。我们需要更仔细地进行实验,以公平地比较不同模型之间的差异,但这个结果似乎表明 BERT 是解决自然语言理解问题的强大模型。

摘要

  • 转移学习是一个机器学习概念,其中一个模型学习了一个任务,然后通过在它们之间转移知识来应用到另一个任务上。这是许多现代、强大、预训练模型的基本概念。
  • BERT 是一个使用掩码语言建模和下一句预测目标进行预训练的 Transformer 编码器,以产生上下文化的嵌入,一系列考虑上下文的词嵌入。
  • ELMo、XLNet、RoBERTa、DistilBERT 和 ALBERT 是现代深度自然语言处理中常用的其他流行的预训练模型。
  • 你可以直接使用 Hugging Face 的 Transformers 库构建基于 BERT 的 NLP 应用,也可以使用无缝集成 Transformers 库的 AllenNLP。

^(1.)Ramponi 和 Plank,“NLP 中的神经无监督领域自适应——一项调查”,(2020)。arxiv.org/abs/2006.00632

^(2.)Jacob Devlin,Ming-Wei Chang,Kenton Lee 和 Kristina Toutanova,“BERT:用于语言理解的深度双向 Transformer 预训练”,(2018)。arxiv.org/abs/1810.04805

^(3.)Bryan McCann,James Bradbury,Caiming Xiong 和 Richard Socher,“翻译中学到的:上下文化的词向量”,2017 年 NIPS 会议。

^(4.)Peters 等人,“深度上下文化的词表示”,(2018)。arxiv.org/abs/1802.05365

^(5.)点击这里查看有关如何使用 ELMo 与 AllenNLP 的详细文档:allennlp.org/elmo

^(6.)请参阅huggingface.co/transformers/model_doc/xlnet.html以获取文档。

^(7.)Liu 等人,“RoBERTa:一个稳健优化的 BERT 预训练方法,”(2019)。arxiv.org/abs/1907.11692

^(8.)Conneau 等人,“规模化的无监督跨语言表示学习”,(2019)。arxiv.org/abs/1911.02116

^(9.)Sanh 等人,“DistilBERT,BERT 的精简版:更小、更快、更便宜、更轻”,(2019)。arxiv.org/abs/1910.01108

^(10.)Lan 等人,“ALBERT:一种用于自监督学习语言表示的轻量级 BERT”,(2020)。arxiv.org/abs/1909.11942

^(11.)这些示例来自 ROCStories:cs.rochester.edu/nlp/rocstories/

^(12.)这些示例来自于nlpprogress.com/english/natural_language_inference.html.

^(13.)Reimers 和 Gurevych,“使用 Siamese BERT-Network 的句子嵌入:Sentence-BERT”,(2019)。arxiv.org/abs/1908.10084.

^(14.)Seo 等人,“面向机器理解的双向注意力流”,(2018)。arxiv.org/abs/1611.01603.

第三部分:投入生产

在第 1 和第二部分,我们学到了许多关于现代 NLP 中“建模”部分的知识,包括词嵌入、RNN、CNN 和 Transformer。然而,你仍然需要学习如何有效地训练、提供、部署和解释这些模型,以构建健壮和实用的 NLP 应用程序。

第十章涉及在开发 NLP 应用程序时触及到的重要机器学习技术和最佳实践,包括批处理和填充、正则化和超参数优化。

最后,如果第 1 到 10 章是关于构建 NLP 模型,第十一章则涵盖了发生在 NLP 模型外部 的一切。该章节涵盖了如何部署、提供、解释和解读 NLP 模型。

第十章:开发自然语言处理应用的十大最佳实践

本章内容包括

  • 通过对令牌进行排序、填充和掩码使神经网络推断更有效率
  • 应用基于字符和 BPE 的分词技术将文本分割成令牌
  • 通过正则化避免过拟合
  • 通过使用上采样、下采样和损失加权处理不平衡数据集
  • 优化超参数

到目前为止,我们已经涵盖了很多内容,包括 RNN、CNN 和 Transformer 等深度神经网络模型,以及 AllenNLP 和 Hugging Face Transformers 等现代 NLP 框架。然而,我们对训练和推断的细节关注不多。例如,如何高效地训练和进行预测?如何避免模型过拟合?如何优化超参数?这些因素可能会对模型的最终性能和泛化能力产生巨大影响。本章涵盖了您需要考虑的这些重要主题,以构建在实际中表现良好的稳健准确的 NLP 应用程序。

10.1 实例批处理

在第二章中,我们简要提到了批处理,这是一种机器学习技术,其中实例被分组在一起形成批次,并发送到处理器(CPU 或更常见的 GPU)。在训练大型神经网络时,批处理几乎总是必要的——它对于高效稳定的训练至关重要。在本节中,我们将深入探讨与批处理相关的一些技术和考虑因素。

10.1.1 填充

训练大型神经网络需要进行许多线性代数运算,如矩阵加法和乘法,这涉及同时对许多许多数字执行基本数学运算。这就是为什么它需要专门的硬件,如 GPU,设计用于高度并行化执行此类操作的处理器。数据被发送到 GPU 作为张量,它们只是数字的高维数组,以及一些指示,说明它需要执行什么类型的数学运算。结果被发送回作为另一个张量。

在第二章中,我们将 GPU 比作海外高度专业化和优化的工厂,用于大量生产相同类型的产品。由于在通信和运输产品方面存在相当大的开销,因此如果您通过批量运输所有所需材料来进行小量订单以制造大量产品,而不是按需运输材料,则效率更高。

材料和产品通常在标准化的容器中来回运输。如果你曾经自己装过搬家货舱或观察别人装过,你可能知道有很多需要考虑的因素来确保安全可靠的运输。你需要紧紧地把家具和箱子放在一起,以免在过渡过程中移位。你需要用毯子裹着它们,并用绳子固定它们,以防止损坏。你需要把重的东西放在底部,以免把轻的东西压坏,等等。

机器学习中的批次类似于现实世界中用于运输物品的容器。就像运输集装箱都是相同的尺寸和矩形形状一样,机器学习中的批次只是装有相同类型数字的矩形张量。如果你想要将不同形状的多个实例在单个批次中“运送”到 GPU,你需要将它们打包,使打包的数字形成一个矩形张量。

在自然语言处理中,我们经常处理长度不同的文本序列。因为批次必须是矩形的,所以我们需要进行填充(即在每个序列末尾加上特殊标记< PAD >),以便张量的每一行具有相同的长度。你需要足够多的填充标记,以使序列的长度相同,这意味着你需要填充短的序列,直到它们与同一批次中最长的序列一样长。示例见图 10.1。


图 10.2 嵌入序列的填充和分批创建了三维的矩形张量。

看起来越来越像真正的容器了!

真实世界的自然语言处理(三)(3)https://developer.aliyun.com/article/1519805

相关文章
|
4月前
|
机器学习/深度学习 自然语言处理 PyTorch
真实世界的自然语言处理(二)(1)
真实世界的自然语言处理(二)
58 1
|
4月前
|
机器学习/深度学习 自然语言处理 算法
真实世界的自然语言处理(三)(1)
真实世界的自然语言处理(三)
36 3
|
4月前
|
机器学习/深度学习 自然语言处理 数据可视化
真实世界的自然语言处理(一)(4)
真实世界的自然语言处理(一)
24 2
|
4月前
|
机器学习/深度学习 自然语言处理 算法
真实世界的自然语言处理(二)(4)
真实世界的自然语言处理(二)
39 1
|
4月前
|
机器学习/深度学习 JSON 自然语言处理
真实世界的自然语言处理(一)(5)
真实世界的自然语言处理(一)
44 1
|
4月前
|
机器学习/深度学习 人工智能 自然语言处理
真实世界的自然语言处理(一)(3)
真实世界的自然语言处理(一)
29 1
|
4月前
|
机器学习/深度学习 自然语言处理 监控
真实世界的自然语言处理(一)(2)
真实世界的自然语言处理(一)
41 1
|
4月前
|
机器学习/深度学习 自然语言处理 算法
真实世界的自然语言处理(二)(5)
真实世界的自然语言处理(二)
36 0
|
4月前
|
机器学习/深度学习 自然语言处理 算法
真实世界的自然语言处理(二)(3)
真实世界的自然语言处理(二)
31 0
|
4月前
|
机器学习/深度学习 自然语言处理 算法
真实世界的自然语言处理(二)(2)
真实世界的自然语言处理(二)
66 0

热门文章

最新文章