真实世界的自然语言处理(二)(5)

本文涉及的产品
NLP自然语言处理_高级版,每接口累计50万次
文档翻译,文档翻译 1千页
NLP自然语言处理_基础版,每接口每天50万次
简介: 真实世界的自然语言处理(二)

真实世界的自然语言处理(二)(4)https://developer.aliyun.com/article/1519772

8.4 基于 Transformer 的语言模型

在第 5.5 节中,我们介绍了语言模型,这是一种给文本赋予概率的统计模型。通过将文本分解为令牌序列,语言模型可以估计给定文本的“概率”。在第 5.6 节中,我们演示了通过利用这一特性,语言模型也可以用于“凭空”生成新的文本!

Transformer 是一个强大的模型,在 Seq2Seq 任务(如机器翻译)中取得了令人印象深刻的结果,尽管它的架构也可以用于语言建模和生成。在本节中,我们将学习如何使用 Transformer 进行语言建模和生成真实文本。

8.4.1 Transformer 作为语言模型

在第 5.6 节中,我们建立了基于字符 LSTM-RNN 的语言生成模型。简而言之,给定一个前缀(到目前为止生成的部分句子),模型使用基于 LSTM 的 RNN(一个带有循环的神经网络)来生成可能的下一个令牌的概率分布,如图 8.12 所示。


图 8.12 使用 RNN 生成文本

我们早些时候指出,通过将 Transformer 解码器视为黑匣子,您可以使用与我们之前介绍的 RNN 相同的一组解码算法(贪婪、束搜索等)。对于语言生成也是如此——通过将神经网络视为在给定前缀的情况下产生某种分数的黑匣子,您可以使用相同的逻辑生成文本,而不管底层模型如何。图 8.13 显示了类似 Transformer 的架构如何用于语言生成。除了一些细微差别(如缺乏交叉注意力)之外,结构几乎与 Transformer 解码器相同。


图 8.13 使用 Transformer 进行语言生成

以下片段显示了使用 Transformer 模型生成文本的类似 Python 的伪代码。在这里,model() 是主要的函数,模型计算发生在这里——它接受标记,将它们转换为嵌入,添加位置编码,并将它们传递到所有的 Transformer 层,将最终的隐藏状态返回给调用者。调用者然后将它们通过线性层传递,将它们转换为 logits,然后通过 softmax 转换为概率分布:

def generate():
    token = <START>
    tokens = [<START>]
    while token != <END>:
        hidden = model(tokens)
        probs = softmax(linear(hidden))
        token = sample(probs)
        tokens.append(token)
    return tokens

实际上,Seq2Seq 模型的解码和语言模型的语言生成是非常相似的任务,输出序列是逐标记生成的,将自身反馈给网络,就像前面的代码片段所示。唯一的区别在于,前者有某种形式的输入(源句子),而后者没有(模型自我反馈)。这两个任务也分别称为无条件生成有条件生成。图 8.14 描绘了这三个组件(网络、任务和解码)以及它们如何结合起来解决特定问题。


图 8.14 语言生成和 Seq2Seq 任务的三个组件

在本节的其余部分,我们将尝试使用一些基于 Transformer 的语言模型,并使用它们生成自然语言文本。我们将使用由 Hugging Face 开发的 transformers 库(huggingface.co/transformers/),这个库在过去几年已经成为了 NLP 研究人员和工程师使用 Transformer 模型的标准库。它提供了一些最先进的模型实现,包括 GPT-2(本节)和 BERT(下一章),以及预训练模型参数,您可以立即加载和使用。它还提供了一个简单、一致的接口,通过这个接口您可以与强大的 NLP 模型进行交互。

8.4.2 Transformer-XL

在许多情况下,您希望加载并使用由第三方提供的预训练模型(通常是模型的开发者),而不是从头开始训练它们。最近的 Transformer 模型相当复杂(通常具有数亿个参数),并且使用大量的数据集进行训练(数十吉字节的文本)。这将需要只有大型机构和科技巨头才能承受得起的 GPU 资源。甚至有些模型在训练时需要数天的时间,即使有十几个 GPU!好消息是,这些庞大的 Transformer 模型的实现和预训练模型参数通常由它们的创建者公开提供,以便任何人都可以将它们集成到他们的 NLP 应用程序中。

在这一部分中,我们首先将介绍 Transformer-XL,这是由 Google Brain 的研究人员开发的 Transformer 的一个变种。由于原始的 Transformer 模型中没有固有的“循环”,不像 RNNs,所以原始的 Transformer 不擅长处理超长的上下文。在用 Transformer 训练语言模型时,你首先将长文本分割成较短的块,比如 512 个单词,并将它们分别馈送到模型中。这意味着模型无法捕获超过 512 个单词的依赖关系。Transformer-XL⁴通过对原始 Transformer 模型进行一些改进来解决这个问题(“XL”表示额外长)。尽管这些改变的细节超出了本书的范围,在简单地说,该模型重复使用前一个段落的隐藏状态,有效地创建了一个在不同文本段之间传递信息的循环。它还改进了我们之前提到的位置编码方案,使得模型更容易处理更长的文本。

您只需在命令行中运行 pip install transformers 即可安装 transformers 库。您将与主要抽象进行交互的是分词器和模型。分词器将原始字符串拆分为一系列标记,而模型定义了架构并实现了主要逻辑。模型和预训练权重通常取决于特定的标记化方案,因此您需要确保您使用的分词器与模型兼容。

初始化一个分词器和一个模型,并使用一些指定的预训练权重的最简单方法是使用 AutoTokenizer 和 AutoModelWithLMHead 类,并调用它们的 from_pretrained()方法如下所示:

import torch
from transformers import AutoModelWithLMHead, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('transfo-xl-wt103')
model = AutoModelWithLMHead.from_pretrained('transfo-xl-wt103')

from_pre-trained()函数的参数是模型/预训练权重的名称。这是一个在名为 wt103(WikiText103)的数据集上训练的 Transformer-XL 模型。

您可能想知道 AutoModelWithLMHead 中的“LMHead”部分是什么意思。LM(语言模型)头是添加到神经网络中的特定层,它将其隐藏状态转换为一组分数,这些分数确定要生成的下一个标记。然后,这些分数(也称为 logits)被馈送到 softmax 层以获得可能的下一个标记的概率分布(图 8.15)。我们希望一个带有 LM 头的模型,因为我们有兴趣通过将 Transformer 作为语言模型来生成文本。但是,根据任务的不同,您可能还想要一个没有 LM 头的 Transformer 模型,并且只想使用其隐藏状态。这将是我们在下一章中要做的事情。


图 8.15 使用 Transformer 的语言模型头

下一步是初始化前缀,用于让语言模型生成故事的其余部分。可以使用 tokenizer.encode() 方法将字符串转换为标记 ID 列表,然后将其转换为张量。我们还将初始化变量 past,用于缓存内部状态并加速推理过程,如下所示:

generated = tokenizer.encode("On our way to the beach")
context = torch.tensor([generated])
past = None

现在,您已准备好生成文本的其余部分了。请注意,下面的代码与我们之前显示的伪代码相似。思路很简单:从模型获取输出,使用输出随机采样一个标记,并将其输入模型。反复这个过程。

for i in range(100):
    output = model(context, mems=past)
    token = sample_token(output.prediction_scores)
    generated.append(token.item())
    context = token.view(1, -1)
    past = output.mems

需要进行一些清理工作,以使张量的形状与模型兼容,我们暂时可以忽略此步骤。此处的 sample_token() 方法将模型的输出转换为概率分布,并从中随机采样一个标记。我没有显示该方法的完整代码,但您可以查看 Google Colab 笔记本(realworldnlpbook.com/ch8.html#xformer-nb)了解更多细节。此外,虽然我们在此处从零编写了生成算法,但如果您需要更全面的生成方式(如波束搜索),请查看该库开发者的官方示例脚本:mng.bz/wQ6q

在生成完成后,您可以通过调用 tokenizer.decode() 将标记 ID 转换为原始字符串,如下所示:

print(tokenizer.decode(generated))

运行后我得到了以下“故事”:

On our way to the beach, she finds, she finds the men who are in the group to be " in the group ". This has led to the perception that the " group " in the group is " a group of people in the group with whom we share a deep friendship, and which is a common cause to the contrary. " <eos> <eos> = = Background = = <eos> <eos> The origins of the concept of " group " were in early colonial years with the English Civil War. The term was coined by English abolitionist John

这不是一个坏的开始。我喜欢这个故事试图通过坚持“群体”概念来保持一致性的方式。然而,由于该模型仅训练于维基百科文本,其生成的结果并不真实,看起来有点过于正式。

8.4.3 GPT-2

GPT-2(代表生成预训练)是由 OpenAI 开发的迄今为止最著名的语言模型。你可能听说过关于一种语言模型生成如此真实无缝的自然语言文本,以至于你无法分辨其与人类写作的文本。从技术上讲,GPT-2 只是一个庞大的 Transformer 模型,就像我们之前介绍的那个一样。主要区别在于其规模(最大模型有 48 层!)以及该模型是通过从网络上收集到的大量自然语言文本进行训练的。OpenAI 团队公开发布了实现和预训练权重,因此我们可以轻松尝试这个模型。

初始化标记器和 GPT-2 模型,方法与 Transformer-XL 相同,如下所示:

tokenizer = AutoTokenizer.from_pretrained('gpt2-large')
model = AutoModelWithLMHead.from_pretrained('gpt2-large')

然后使用以下代码片段生成文本:

generated = tokenizer.encode("On our way to the beach")
context = torch.tensor([generated])
past = None
for i in range(100):
    output = model(context, past_key_values=past)
    token = sample_token(output.logits)
    generated.append(token.item())
    context = token.unsqueeze(0)
    past = output.past_key_values
print(tokenizer.decode(generated))

你可能已经注意到这段代码与 Transformer-XL 的代码几乎没有变化。在许多情况下,当切换不同的模型时,您不需要进行任何修改。这就是为什么 transformers 库如此强大的原因 - 您可以尝试并集成各种最先进的基于 Transformer 的模型到您的应用程序中,只需使用一个简单且一致的界面。正如我们将在下一章中看到的那样,这个库还集成到 AllenNLP 中,这使得使用最先进的模型构建强大的自然语言处理应用程序变得容易。

当我尝试这个代码时,GPT-2 生成了以下精美的段落:

On our way to the beach, there was a small island that we visited for the first time. The island was called 'A' and it is a place that was used by the French military during the Napoleonic wars and it is located in the south-central area of the island.
A is an island of only a few hundred meters wide and has no other features to distinguish its nature. On the island there were numerous small beaches on which we could walk. The beach of 'A' was located in the...

注意它和自然的阅读感。此外,GPT-2 模型擅长保持一致性-您可以看到“A”这个岛的名字在整个段落中始终使用。就我所知,世界上没有一个真正名为 A 的岛屿,这意味着这是模型简单地编造的。这是一个伟大的成就,模型记住了它刚刚创造的名字,并成功围绕它写了一个故事!

下面是 GPT-2 根据提示生成的另一段话:'Real World Natural Language Processing’是这本书的名字:

'Real World Natural Language Processing' is the name of the book. It has all the tools you need to write and program natural language processing programs on your computer. It is an ideal introductory resource for anyone wanting to learn more about natural language processing. You can buy it as a paperback (US$12), as a PDF (US$15) or as an e-book (US$9.99).
The author's blog has more information and reviews.
The free 'Real World Natural Language Processing' ebook has all the necessary tools to get started with natural language processing. It includes a number of exercises to help you get your feet wet with writing and programming your own natural language processing programs, and it includes a few example programs. The book's author, Michael Karp has also written an online course about Natural Language Processing.
'Real World Natural Language Processing: Practical Applications' is a free e-book that explains how to use natural language processing to solve problems of everyday life (such as writing an email, creating and

到 2019 年 2 月,当 GPT-2 发布时,我几乎刚开始写这本书,所以我怀疑 GPT-2 对此一无所知。对于一个没有关于这本书的任何先验知识的语言模型来说,这是一项惊人的工作,尽管我必须指出它价格和作者的错误。

8.4.4 XLM

最后,作为一个有趣的例子,我们将尝试多语言语言生成。XLM(跨语言语言模型)是由 Facebook AI Research 的研究人员提出的基于 Transformer 的跨语言语言模型,可以生成和编码多种语言的文本。通过学习如何编码多语言文本,模型可以用于不同语言之间的迁移学习。我们将在第九章介绍迁移学习。

您可以通过以下方式初始化分词器和模型,并使用预训练权重进行初始化:

tokenizer = AutoTokenizer.from_pretrained('xlm-clm-enfr-1024')
model = AutoModelWithLMHead.from_pretrained('xlm-clm-enfr-1024')

在这里,我们加载一个使用英语和法语(enfr)进行训练的 XLM 模型(xlm),使用因果语言模型(CLM)目标(clm)进行训练。CLM 只是以更高级的方式描述我们在本章中所做的内容-根据前缀预测下一个标记。XLM 通常用于对多语言文本进行编码,用于一些下游任务,如文本分类和机器翻译,但我们只是将其用作生成文本的语言模型。有关使用 XLM 生成多语言文本的代码片段,请参见清单 8.2。您可以再次重用大部分之前的代码片段,尽管您还需要指定您正在使用的语言(请注意 lang = 0 行)。此外,在这里,我们通过仅提供 BOS 标记(其索引为零)从头开始生成文本。

清单 8.2 生成多语言文本与 XLM

generated = [0] # start with just <s>
context = torch.tensor([generated])
lang = 0 # English
for i in range(100):
    langs = torch.zeros_like(context).fill_(lang)
    output = model(context, langs=langs)
    token = sample_token(output)
    generated.append(token.item())
    context = torch.tensor([generated])
print(tokenizer.decode(generated))

运行这个代码后,我得到了以下结果:

<s>and its ability to make decisions on the basis of its own. " </s>The government has taken no decisions on that matter, " Mr Hockey said. </s>A lot of the information is very sensitive. </s>The new research and information on the Australian economy, which is what we're going to get from people, and the information that we are going to be looking at, we're going to be able to provide and we 'll take it forward. </s>I'm not trying to make sure we're not

然后,让我们将语言更改为 1(表示法语),并再次运行相同的代码片段,这将给出下一段文本:

<s></s>En revanche, les prix des maisons individuelles n' ont guère augmenté ( - 0,1 % ). </s>En mars dernier, le taux de la taxe foncière, en légère augmentation à la hausse par rapport à février 2008\. </s>" Je n' ai jamais eu une augmentation " précise ". </s>" Je me suis toujours dit que ce n' était pas parce que c' était une blague. </s>En effet, j' étais un gars de la rue " </s>Les jeunes sont des gens qui avaient beaucoup d' humour... "

尽管这种生成质量不如我们之前实验的 GPT-2 那么好,但是看到一种单一模型可以同时生成英语和法语的文本非常令人耳目一新。如今,构建基于 Transformer 的多语言 NLP 模型以解决多种语言的 NLP 问题和任务越来越普遍。这也得益于 Transformer 对语言复杂性建模的强大能力。

8.5 案例研究:拼写检查器

在本章的最后一节中,我们将使用 Transformer 构建一个实用的 NLP 应用——拼写检查器。在现代世界中,拼写检查器无处不在。你的 Web 浏览器可能装备有一个拼写检查器,它会在拼写错误的单词下划线提示你。许多字处理器和编辑器也默认运行拼写检查器。一些应用程序(包括 Google Docs 和 Microsoft Word)甚至指出简单的语法错误。你是否想知道它们是如何工作的?我们将学习如何将其作为 NLP 问题进行规划、准备数据集、训练和改进模型。

8.5.1 拼写纠正作为机器翻译

拼写检查器接收这样一个文本:“tisimptant too spll chck ths dcment”,检测任何拼写和语法错误,并修复所有错误:“It’s important to spell-check this document.” 如何使用自然语言处理技术解决这个任务?这些系统如何实现?

最简单的方法是将输入文本分词为单词,并检查每个单词是否在字典中。如果不在,你可以查找距离最近的有效单词并替换它。可以使用一些度量(如编辑距离)来计算距离。重复这个过程,直到没有需要更正的单词。这种逐个单词修正的算法被许多拼写检查器广泛使用,因为它很简单。

然而,这种类型的拼写检查器有几个问题。首先,就像示例中的第一个单词“tisimptant”一样,您如何知道句子的哪一部分实际上是一个单词?我副本中默认的微软 Word 拼写检查器指出它是“disputant”的拼写错误,尽管对于任何英语使用者来说,它实际上是两个(或更多)单词的拼写错误是显而易见的。用户还可能拼写标点符号(包括空格),这使得一切都变得复杂。其次,仅仅因为某个单词在词典中存在,并不意味着它就没有错误。例如,示例中的第二个单词“too”是“to”的拼写错误,但两者都是任何英语词典中都有的有效单词。您如何判断前者在这种情况下是错误的呢?第三,所有这些决定都是在没有上下文的情况下做出的。我尝试过的一个拼写检查器在这个例子中显示“thus”是替换“ths”的候选词之一。然而,从这个上下文(名词之前)来看,“this”是一个更合适的候选词是显而易见的,尽管“this”和“thus”都与“ths”相隔一个编辑距离,这意味着根据编辑距离来看,它们都是同样有效的选项。

通过添加一些启发式规则,您可以解决其中一些问题。例如,“too”更有可能是动词之前“to”的拼写错误,“this”更有可能出现在名词之前而不是“thus”。但这种方法显然不具备可扩展性。还记得第 1.1.2 节中可怜的初级开发者吗?语言广阔而充满异常。您不能仅仅通过编写这些简单单词的规则来处理语言的全部复杂性。即使您能够为这些简单单词编写规则,您又如何知道“tisimptant”实际上是两个单词呢?您会尝试在每个可能的位置拆分这个单词,看拆分后的单词是否与现有单词相似吗?如果输入的是一种没有空格的语言,比如中文和日语,会怎么样呢?

此时,您可能意识到这种“拆分和修复”的方法行不通。一般来说,在设计自然语言处理应用程序时,您应该从以下三个方面考虑:

  • 任务—正在解决什么任务?是分类、序列标注还是序列到序列问题?
  • 模型—您将使用什么模型?是前馈网络、循环神经网络还是 Transformer?
  • 数据集—您从哪里获取数据集来训练和验证您的模型?

根据我的经验,如今绝大多数自然语言处理应用程序都可以通过结合这些方面来解决。拼写检查器呢?因为它们以一段文本作为输入,并生成修复后的字符串,如果我们将其作为一个 Seq2Seq 任务使用 Transformer 模型来解决将会最直接。换句话说,我们将建立一个机器翻译系统,将带有拼写/语法错误的嘈杂输入转换为干净、无误的输出,如图 8.16 所示。您可以将这两个方面看作是两种不同的“语言”(或英语的“方言”)。


图 8.16 将拼写检查器训练为将“嘈杂”的句子翻译成“干净”的句子的 MT 系统

此时,您可能会想知道我们从哪里获取数据集。这通常是解决现实世界自然语言处理问题中最重要(也是最困难)的部分。幸运的是,我们可以使用公共数据集来完成这项任务。让我们深入研究并开始构建一个拼写检查器。

8.5.2 训练拼写检查器

我们将使用 GitHub Typo Corpus(github.com/mhagiwara/github-typo-corpus)作为训练拼写检查器的数据集。这个数据集是由我和我的合作者创建的,其中包含数十万个从 GitHub 自动收集的“打字错误”编辑。这是迄今为止最大的拼写错误及其校正数据集,这使得它成为训练拼写检查器的理想选择。

在准备数据集和训练模型之前,我们需要做出一个决定,那就是选择模型操作的原子语言单位。许多自然语言处理模型使用令牌作为最小单位(即,RNN/Transformer 被馈送一个令牌序列),但越来越多的自然语言处理模型使用单词或句子片段作为基本单位(第 10.4 节)。对于拼写校正,我们应该使用什么作为最小的单位?与许多其他自然语言处理模型一样,起初使用单词作为输入听起来像是一个很好的“默认”选择。然而,正如我们之前所看到的,令牌的概念并不适用于拼写校正——用户可能会弄乱标点符号,如果您正在处理令牌,这会使一切过于复杂。更重要的是,因为自然语言处理模型需要操作一个固定的词汇表,所以拼写校正器的词汇表需要包含训练期间遇到的每个单词的每个拼写错误。这将使得训练和维护这样一个自然语言处理模型变得不必要昂贵。

由于这些原因,我们将使用字符作为拼写检查器的基本单位,就像在第 5.6 节中一样。使用字符有几个优点——它可以保持词汇表的大小相当小(通常对于具有小字母表集的语言,如英语,不到一百个)。即使是充满打字错误的嘈杂数据集,您也不必担心膨胀您的词汇表,因为打字错误只是字符的不同排列。您还可以将标点符号(甚至空白符)视为词汇表中的字符之一。这使得预处理步骤非常简单,因为您不需要任何语言工具包(如标记器)来执行此操作。

注意:使用字符并非没有缺点。其中一个主要问题是使用它们会增加序列的长度,因为你需要将所有内容分解为字符。这使得模型变得庞大且训练速度变慢。

首先,让我们为训练拼写检查器准备数据集。构建拼写检查器所需的所有必要数据和代码都包含在此代码库中:github.com/mhagiwara/xfspell。经过分词和拆分的数据集位于 data/gtc 目录下(如 train.tok.fr、train.tok.en、dev.tok.fr、dev.tok.en)。后缀 en 和 fr 是机器翻译中常用的约定,其中“fr”表示“外语”,“en”表示英语,因为许多机器翻译研究项目最初是由希望将某种外语翻译为英语的人发起的。这里,我们将“fr”和“en”仅仅解释为“拼写纠错前的嘈杂文本”和“拼写纠错后的纠正文本”。

图 8.17 显示了根据 GitHub Typo Corpus 创建的拼写纠错数据集的摘录。请注意,文本被分割成单个字符,甚至包括空格(由“_”替换)。所有不在通用字母表(大写字母、小写字母、数字和一些常见标点符号)内的字符都被替换为“#”。您可以看到数据集包含各种纠正,包括简单的拼写错误(pubilc->public 在第 670 行,HYML->HTML 在第 672 行),更复杂的错误(mxnet 一词替换成 mxnet is not 在第 681 行,22th->22nd 在第 682 行),甚至不带任何更正的行(第 676 行)。这看起来是训练拼写检查器的一个好资源。


图 8.17 拼写纠错的训练数据

训练拼写检查器(或任何其他 Seq2Seq 模型)的第一步是对数据集进行预处理。因为数据集已经分割和格式化,你只需要运行 fairseq-preprocess 将数据集转换为二进制格式,操作如下:

fairseq-preprocess --source-lang fr --target-lang en \
    --trainpref data/gtc/train.tok \
    --validpref data/gtc/dev.tok \
    --destdir bin/gtc

然后,您可以使用以下代码立即开始训练模型。

列表 8.3 训练拼写检查器

fairseq-train \
    bin/gtc \
    --fp16 \
 --arch transformer \
 --encoder-layers 6 --decoder-layers 6 \
 --encoder-embed-dim 1024 --decoder-embed-dim 1024 \
 --encoder-ffn-embed-dim 4096 --decoder-ffn-embed-dim 4096 \
 --encoder-attention-heads 16 --decoder-attention-heads 16 \
    --share-decoder-input-output-embed \
    --optimizer adam --adam-betas '(0.9, 0.997)' --adam-eps 1e-09 --clip-norm 25.0 \
    --lr 1e-4 --lr-scheduler inverse_sqrt --warmup-updates 16000 \
    --dropout 0.1 --attention-dropout 0.1 --activation-dropout 0.1 \
    --weight-decay 0.00025 \
    --criterion label_smoothed_cross_entropy --label-smoothing 0.2 \
 --max-tokens 4096 \
    --save-dir models/gtc01 \
    --max-epoch 40

您不需要担心这里的大多数超参数——这组参数对我来说效果还不错,尽管可能还有其他参数组合效果更好。但是,您可能想注意一些与模型大小相关的参数,即:

  • 层数(-[encoder|decoder]-layers)
  • 自注意力的嵌入维度(-[encoder|decoder]-embed-dim)
  • 前馈层的嵌入维度(-[encoder/decoder]-ffn-embed-dim)
  • 注意力头数(-[encoder|decoder]-attention-heads)

这些参数决定了模型的容量。一般来说,这些参数越大,模型的容量就越大,尽管作为结果,模型也需要更多的数据、时间和 GPU 资源来进行训练。另一个重要的参数是—max-token,它指定加载到单个批次中的标记数。如果在 GPU 上遇到内存不足错误,请尝试调整此参数。

训练完成后,您可以运行以下命令使用训练好的模型进行预测:

echo "tisimptant too spll chck ths dcment." \
    | python src/tokenize.py \
    | fairseq-interactive bin/gtc \
    --path models/gtc01/checkpoint_best.pt \
    --source-lang fr --target-lang en --beam 10 \
    | python src/format_fairseq_output.py

因为 fairseq-interactive 界面也可以从标准输入接收源文本,所以我们直接使用 echo 命令提供文本。Python 脚本 src/format_fairseq_output.py,顾名思义,格式化来自 fairseq-interactive 的输出,并显示预测的目标文本。当我运行这个脚本时,我得到了以下结果:

tisimplement too spll chck ths dcment.

这相当令人失望。拼写检查器学会了如何将“imptant”修正为“implement”,尽管它未能纠正任何其他单词。我怀疑有几个原因。使用的训练数据,GitHub Typo Corpus,严重偏向于软件相关的语言和纠正,这可能导致了错误的更正(imptant -> implement)。此外,训练数据可能对于 Transformer 来说太小了。我们如何改进模型,使其能够更准确地纠正拼写错误呢?

8.5.3 改进拼写检查器

正如我们之前讨论的,拼写检查器不如预期工作的一个主要原因可能是因为模型在训练过程中没有暴露给更多种类、更大数量的拼写错误。但据我所知,没有这样的大型数据集公开可用于训练一个通用领域的拼写检查器。我们如何获取更多的数据来训练一个更好的拼写检查器呢?

这就是我们需要有创造性的地方。一个想法是从干净的文本中人工生成带有噪音的文本。如果你想一想,这是非常困难的(尤其对于一个机器学习模型)来纠正拼写错误,但很容易“破坏”干净的文本,以模拟人们如何打字错误,即使对于计算机也是如此。例如,我们可以从一些干净的文本(例如,几乎无限的从网页抓取的文本)中随机替换一些字母。如果你将以这种方式创建的人工生成的带噪音的文本与原始的干净文本配对,这将有效地创建一个新的、更大的数据集,你可以在其上训练一个更好的拼写检查器!

我们需要解决的剩下的问题是如何“破坏”干净的文本以生成看起来像人类所做的真实拼写错误。你可以编写一个 Python 脚本,例如,随机替换、删除和/或交换字母,虽然不能保证以这种方式生成的拼写错误与人类所做的拼写错误相似,也不能保证生成的人工数据集能为 Transformer 模型提供有用的见解。我们如何建模这样一个事实,例如,人们更有可能在“too”的地方输入“to”,而不是“two”呢?

这又开始听起来熟悉了。我们可以使用数据来模拟打字错误!但是如何做呢?这就是我们需要再次发挥创造力的地方——如果你“翻转”我们用来训练拼写检查器的原始数据集的方向,你可以观察到人们是如何打字错误的。如果你把干净的文本视为源语言,把嘈杂的文本视为目标语言,并为该方向训练一个 Seq2Seq 模型,那么你实际上是在训练一个“拼写损坏器”—一个将看起来很真实的拼写错误插入干净文本的 Seq2Seq 模型。请参见图 8.18 进行说明。


图 8.18 使用回译生成人工噪声数据

在机器学习文献中,使用原始训练数据的“反向”来从目标语言中的真实语料库中人工生成大量源语言数据的技术被称为 回译。这是一种提高机器翻译系统质量的流行技术。正如我们接下来将展示的,它也可以有效地提高拼写检查器的质量。

通过交换源语言和目标语言,您可以轻松训练一个拼写损坏器。您可以在运行 fairseq-preprocess 时将“en”(干净文本)作为源语言提供,将“fr”(嘈杂文本)作为目标语言,如下所示:

fairseq-preprocess --source-lang en --target-lang fr \
    --trainpref data/gtc/train.tok \
    --validpref data/gtc/dev.tok \
    --destdir bin/gtc-en2fr

我们不再详细介绍训练过程——你可以使用几乎相同的 fairseq-train 命令启动训练。只是不要忘记为 —save-dir 指定一个不同的目录。在训练结束后,您可以检查拼写损坏器是否确实能按预期损坏输入文本:

$ echo 'The quick brown fox jumps over the lazy dog.' | python src/tokenize.py \ 
    | fairseq-interactive \
    bin/gtc-en2fr \
    --path models/gtc-en2fr/checkpoint_best.pt \
    --source-lang en --target-lang fr \
 --beam 1 --sampling --sampling-topk 10 \
    | python src/format_fairseq_output.py
The quink brown fox jumps ove-rthe lazy dog.

注意我之前添加的额外选项,以粗体显示。这意味着 fairseq-interactive 命令使用采样(从概率最大的前 10 个标记中采样)而不是束搜索。当损坏干净文本时,通常最好使用采样而不是束搜索。简而言之,采样根据 softmax 层后的概率分布随机选择下一个标记,而束搜索则试图找到最大化输出序列分数的“最佳路径”。虽然束搜索在翻译某些文本时可以找到更好的解决方案,但在通过回译增加数据时,我们希望得到更嘈杂、更多样化的输出。过去的研究⁶也表明,采样(而不是束搜索)对通过回译增加数据效果更好。

从这里开始,一切皆有可能。你可以收集尽可能多的干净文本,使用刚刚训练的损坏程序生成嘈杂文本,并增加训练数据的大小。并不能保证人工错误看起来像人类所做的真实错误一样,但这并不重要,因为 1)源(嘈杂)侧仅用于编码,2)目标(干净)侧数据始终是由人类编写的“真实”数据,从中 Transformer 可以学习如何生成真实文本。你收集的文本数据越多,模型对无错误的真实文本的信心就越大。

我不会详细介绍我为增加数据量所采取的每一步,但这里是我所做的事情以及你也可以做的事情的总结。从公开可用的数据集(如 Tatoeba 和维基百科的转储)中收集尽可能多的干净和多样化的文本数据是一个方法。我最喜欢的方法是使用 OpenWebTextCorpus(skylion007.github.io/OpenWebTextCorpus/),这是一个开源项目,用于复制最初用于 GPT-2 训练的数据集。它由从 Reddit 的所有外部链接爬取的大量(40 GB)高质量网页文本组成。因为整个数据集的预处理和运行损坏程序可能需要几天甚至几周的时间,你可以取一个子集(比如说,1/1000),然后将其添加到数据集中。我取了数据集的 1/100 子集,对其进行了预处理,并运行了损坏程序,以获得嘈杂干净的平行数据集。这 1/100 子集单独就添加了五百多万对(相比之下,原始训练集仅包含约 240k 对)。你可以下载预训练权重并尝试存储库中的拼写检查器,而不是从头开始训练。

训练花了几天时间,甚至在多个 GPU 上,但当完成时,结果非常令人鼓舞。它不仅可以准确地修复拼写错误,如下所示

$ echo "tisimptant too spll chck ths dcment." \
    | python src/tokenize.py \
    | fairseq-interactive \
    bin/gtc-bt512-owt1k-upper \
    --path models/bt05/checkpoint_best.pt \
    --source-lang fr --target-lang en --beam 10 \
    | python src/format_fairseq_output.py
    It's important to spell check this document.

而且拼写检查器似乎也在某种程度上理解英语的语法,如下所示:

$ echo "The book wer about NLP." |
    | python src/tokenize.py \
    | fairseq-interactive \
   ...
The book was about NLP.
$ echo "The books wer about NLP." |
    | python src/tokenize.py \
    | fairseq-interactive \
   ...
The books were about NLP.

这个例子本身可能不能证明模型真正理解语法(即根据主语的数量使用正确的动词)。它可能只是学习了一些连续单词之间的关联,这可以通过任何统计 NLP 模型(如 n-gram 语言模型)实现。然而,即使在你让句子更加复杂之后,拼写检查器也显示出了惊人的弹性,如下一个代码片段所示:

$ echo "The book Tom and Jerry put on the yellow desk yesterday wer about NLP." |
    | python src/tokenize.py \
    | fairseq-interactive \
   ...
The book Tom and Jerry put on the yellow desk yesterday was about NLP.
$ echo "The books Tom and Jerry put on the yellow desk yesterday wer about NLP." |
    | python src/tokenize.py \
    | fairseq-interactive \
   ...
The books Tom and Jerry put on the yellow desk yesterday were about NLP.

从这些例子中,可以清楚地看出模型学会了如何忽略不相关的名词短语(例如“Tom and Jerry”和“yellow desk”),并专注于决定动词形式(“was”与“were”)的名词(“book(s)”)。我们更有信心它理解了基本的句子结构。我们所做的一切只是收集了大量干净的文本,并在其上训练了 Transformer 模型,结合了原始的训练数据和损坏器。希望通过这些实验,你能感受到 Transformer 模型的强大之处!

摘要

  • 注意力机制是神经网络中的一种机制,它专注于输入的特定部分,并计算其上下文相关的摘要。它类似于“软”版本的键-值存储。
  • 可以将编码器-解码器注意力机制添加到 Seq2Seq 模型中,以提高其翻译质量。
  • 自注意力是一种注意力机制,通过总结自身来产生输入的摘要。
  • Transformer 模型反复应用自注意力机制,逐渐转换输入。
  • 可以使用 Transformer 和一种称为回译的技术来构建高质量的拼写检查器。

^(1.)Vaswani 等人,“注意力机制就是一切”,(2017)。arxiv.org/abs/1706.03762

^(2.)Bahdanau 等人,“通过共同学习对齐和翻译进行神经机器翻译”,(2014)。arxiv.org/abs/1409.0473

^(3.)Bahdanau 等人,“通过共同学习对齐和翻译进行神经机器翻译”,(2014)。arxiv.org/abs/1409.0473

^(4.)Dai 等人,“Transformer-XL:超越固定长度上下文的注意力语言模型”,(2019)。arxiv.org/abs/1901.02860

^(5.)Lample 和 Conneau,“跨语言语言模型预训练”,(2019)。arxiv.org/abs/1901 .07291

^(6.)Edunov 等人,“大规模理解回译”,(2018)。arxiv.org/abs/1808.09381

相关文章
|
4月前
|
机器学习/深度学习 自然语言处理 PyTorch
真实世界的自然语言处理(二)(1)
真实世界的自然语言处理(二)
52 1
|
4月前
|
机器学习/深度学习 自然语言处理 异构计算
真实世界的自然语言处理(三)(2)
真实世界的自然语言处理(三)
28 3
|
4月前
|
机器学习/深度学习 自然语言处理 算法
真实世界的自然语言处理(三)(1)
真实世界的自然语言处理(三)
34 3
|
4月前
|
机器学习/深度学习 自然语言处理 数据可视化
真实世界的自然语言处理(一)(4)
真实世界的自然语言处理(一)
23 2
|
4月前
|
机器学习/深度学习 自然语言处理 算法
真实世界的自然语言处理(二)(4)
真实世界的自然语言处理(二)
37 1
|
4月前
|
机器学习/深度学习 JSON 自然语言处理
真实世界的自然语言处理(一)(5)
真实世界的自然语言处理(一)
40 1
|
4月前
|
机器学习/深度学习 人工智能 自然语言处理
真实世界的自然语言处理(一)(3)
真实世界的自然语言处理(一)
23 1
|
4月前
|
机器学习/深度学习 自然语言处理 监控
真实世界的自然语言处理(一)(2)
真实世界的自然语言处理(一)
38 1
|
4月前
|
机器学习/深度学习 自然语言处理 算法
真实世界的自然语言处理(二)(3)
真实世界的自然语言处理(二)
28 0
|
4月前
|
机器学习/深度学习 自然语言处理 算法
真实世界的自然语言处理(二)(2)
真实世界的自然语言处理(二)
62 0

热门文章

最新文章

下一篇
DDNS