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

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

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

5.6 使用 RNN 生成文本

我们看到语言模型为自然语言句子给出了概率。但更有趣的部分是,你可以使用语言模型从头开始生成自然语言句子!在本章的最后一节,我们将构建一个语言模型。你可以使用训练好的模型来评估和生成英语句子。你可以在 Google Colab 笔记本上找到此子节的整个脚本(realworldnlpbook.com/ch5.html#lm-nb)。

5.6.1 将字符馈送到 RNN

在本节的前半部分,我们将构建一个英语语言模型,并使用通用英语语料库对其进行训练。在我们开始之前,我们注意到,本章中构建的 RNN 语言模型是基于 字符 而不是基于单词或令牌的。到目前为止,我们所见过的所有 RNN 模型都是基于单词的,这意味着 RNN 的输入始终是单词序列。另一方面,在本节中,我们将使用的 RNN 接受字符序列作为输入。

理论上,RNNs 可以处理任何序列,无论是令牌、字符还是完全不同的东西(例如,语音识别的波形),只要它们可以转换为向量。在构建语言模型时,我们通常将字符作为输入,甚至包括空白和标点,将它们视为长度为 1 的单词。模型的其余部分完全相同——首先将单个字符嵌入(转换为向量),然后将其馈送到 RNN 中,然后训练 RNN,以便它能最好地预测可能出现的下一个字符的分布。

在决定是否应将单词或字符馈送到 RNN 时需要考虑一些因素。使用字符肯定会使 RNN 的效率降低,这意味着它需要更多的计算才能“理解”相同的概念。例如,基于单词的 RNN 可以在一个时间步接收到单词“dog”并更新其内部状态,而基于字符的 RNN 必须等到接收到三个元素 dog,以及可能的“_”(空格符)才能做到。基于字符的 RNN 需要“学会”这个由这三个字符组成的序列表示了某个特殊意义(“dog”这个概念)。

另一方面,通过向 RNN 馈送字符,您可以避开许多处理标记的问题。其中一个问题与处理词汇表外(OOV)的单词有关。当训练基于单词的 RNN 时,通常会固定整个词汇表的集合,通常通过枚举在训练集中出现的所有单词来实现。但是,每当在测试集中遇到一个 OOV 单词时,它就不知道如何处理它。通常情况下,它会给所有 OOV 单词分配一个特殊的标记 并以相同的方式处理它们,这并不理想。相反,基于字符的 RNN 仍然可以处理单个字符,因此它可能能够通过观察训练集中的“dog”所学到的规则,推断出“doggy”是什么意思,即使它从未见过确切的单词“doggy”。

5.6.2 使用语言模型评估文本

让我们开始构建一个基于字符的语言模型。第一步是读取一个纯文本数据集文件并生成用于训练模型的实例。我将展示如何在不使用数据集读取器的情况下构建实例以进行演示。假设您有一个 Python 字符串对象 text,您想将其转换为用于训练语言模型的实例。首先,您需要使用 CharacterTokenizer 将其分段为字符,如下所示:

from allennlp.data.tokenizers import CharacterTokenizer
tokenizer = CharacterTokenizer()
tokens = tokenizer.tokenize(text)

需要注意的是,这里的 tokens 是一个 Token 对象的列表。每个 Token 对象包含一个字符,而不是一个单词。然后,按照下面显示的方式在列表的开头和末尾插入 和 符号:

from allennlp.common.util import START_SYMBOL, END_SYMBOL
tokens.insert(0, Token(START_SYMBOL))
tokens.append(Token(END_SYMBOL))

在 NLP 中,在每个句子的开头和结尾插入这些特殊符号是一种常见做法。使用这些符号,模型可以区分句子中一个标记在中间出现与一个标记在开头或结尾出现的情况。例如,句点很可能出现在句子的末尾(“. ”)而不是开头(“ .”),语言模型可以给出两个非常不同的概率,而不使用这些符号是不可能做到的。

最后,您可以通过指定单独的文本字段来构建一个实例。请注意,语言模型的“输出”与输入完全相同,只是偏移了一个标记,如下所示:

from allennlp.data.fields import TextField
from allennlp.data.instance import Instance
input_field = TextField(tokens[:-1], token_indexers)
output_field = TextField(tokens[1:], token_indexers)
instance = Instance({'input_tokens': input_field,
                     'output_tokens': output_field})

这里的 token_indexers 指定了如何将各个标记映射到 ID。我们仍然使用迄今为止使用的 SingleIdTokenIndexer,如下所示:

from allennlp.data.token_indexers import TokenIndexer 
token_indexers = {'tokens': SingleIdTokenIndexer()}

图 5.13 显示了从该过程创建的实例。


图 5.13 用于训练语言模型的实例

训练流程的其余部分以及模型与本章前面提到的顺序标记模型非常相似。有关更多详细信息,请参见 Colab 笔记本。如下面的代码片段所示,在模型完全训练后,你可以从新的文本中构建实例、将它们转化为实例,并计算损失,该损失基本上衡量了模型在预测下一个字符方面的成功程度:

predict('The trip to the beach was ruined by bad weather.', model)
{'loss': 1.3882852}
predict('The trip to the beach was ruined by bad dogs.', model)
{'loss': 1.5099115}
predict('by weather was trip my bad beach the ruined to.', model)
{'loss': 1.8084583}

这里的损失是预测字符与期望字符之间的交叉熵损失。出现较多“不符合预期”的字符,损失值就会越高,因此你可以使用这些值来衡量输入作为英文文本的自然程度。正如预期的那样,自然句子(如第一个句子)得分低于非自然句子(如最后一个句子)。

注意,如果你计算交叉熵的 2 的幂,那么这个值就被称为困惑度。对于给定的固定自然语言文本,困惑度会降低,因为语言模型在预测下一个字符方面表现更好,所以它通常用于评估文献中的语言模型的质量。

5.6.3 使用语言模型生成文本

(完全训练好的) 语言模型最有趣的方面在于,它们可以根据给定的一些上下文来预测可能出现的下一个字符。具体而言,它们可以给出可能的下一个字符的概率分布,然后根据该分布选择确定下一个字符。例如,如果模型生成了“t”和“h”,并且 LM 是基于通用英文文本训练的,它可能会对字母“e”分配较高的概率,生成常见的英文单词,包括 thetheythem 等。如果你从 标记开始这个过程,并一直进行下去直到达到句子的结尾(即生成 ),你就可以从头开始生成一句英文句子。顺便说一句,这也是为什么像 和 这样的标记很有用——你需要将某些内容输入 RNN 以开始生成,并且你还需要知道句子何时结束。

让我们在下面的类似 Python 代码的伪代码中看一下这个过程:

def generate():
    state = init_state()
    token = <START>
    tokens = [<START>]
    while token != <END>:
        state = update(state, token)
        probs = softmax(linear(state))
        token = sample(probs)
        tokens.append(token)
    return tokens

这个循环看起来与更新 RNNs 的循环非常相似,但有一个关键区别:在这里,我们不接收任何输入,而是生成字符并将它们作为输入。换句话说,RNN 的操作对象是 RNN 自己迄今为止生成的字符序列。这种在其自身生成的过去序列上操作的模型称为 自回归模型。有关此过程的示例,请参见图 5.14。


图 5.14 使用 RNN 生成文本

在上一个代码段中,init_state()和 update()函数是初始化和更新 RNN 隐藏状态的函数,正如我们之前所见。 在生成文本时,我们假设模型及其参数已经训练好了大量的自然语言文本。softmax()函数是在给定向量上运行 Softmax 的函数,而 linear()是扩展/缩小向量大小的线性层。sample()函数根据给定的概率分布返回一个字符。例如,如果分布是“a”:0.6,“b”:0.3,“c”:0.1,则会在 60%的时间内选择“a”,30%的时间选择“b”,10%的时间选择“c”。这确保生成的字符串每次都不同,同时每个字符串看起来都像是英语句子。

注意,您可以使用 PyTorch 的 torch.multinomial()从概率分布中进行抽样。

如果使用 Tatoeba 中的英语句子进行训练,并按照这个算法生成句子,系统将会创建类似于以下举出的例子:

You can say that you don't know it, and why decided of yourself.
Pike of your value is to talk of hubies.
The meeting despoit from a police?
That's a problem, but us?
The sky as going to send nire into better.
We'll be look of the best ever studented.
There's you seen anything every's redusention day.
How a fail is to go there.
It sad not distaples with money.
What you see him go as famous to eat!

这不是个坏开端!如果你看看这些句子,有很多词语和短语看起来是合理的英语句子(“You can say that”、“That’s a problem”、“to go there”、“see him go”等)。即使系统生成了奇怪的单词(“despoit”、“studented”、“redusention”、“distaples”),它们看起来几乎像真正的英语单词,因为它们基本上遵循英语的形态和音韵规则。这意味着语言模型成功地学习了英语的基本语言要素,如字母排列(拼写)、词形变化(形态学)以及基本句子结构(语法)。

然而,如果你将句子作为一个整体来看,很少有句子是有意义的(例如,你看到他去当名人吃饭)。这意味着我们训练的语言模型在建模句子的语义一致性方面存在缺陷。这可能是因为我们的模型不够强大(我们的 LSTM-RNN 需要将句子的所有内容压缩成一个 256 维的向量),或者训练数据集太小(只有 10,000 个句子),或者两者兼而有之。但是你可以轻易想象,如果我们不断增加模型的容量以及训练集的大小,该模型在生成逼真的自然语言文本方面将变得非常出色。2019 年 2 月,OpenAI 宣布开发了一个基于 Transformer 模型的巨型语言模型(我们将在第八章介绍),该模型在 40GB 的互联网文本上进行了训练。该模型显示,它可以在给定提示的情况下生成逼真的文本,展现了几乎完美的语法和长期的主题一致性。事实上,该模型非常出色,以至于 OpenAI 决定不发布他们训练的大型模型,因为他们担心技术可能被用于恶意目的。但是重要的是要记住,无论输出看起来多么智能,他们的模型都是基于我们在本章中的示例玩具模型的相同原理——只是尝试预测下一个字符!

总结

  • 序列标记模型会给输入中的每个词都打上一个标签,这可以通过递归神经网络(Recurrent Neural Networks, RNNs)来实现。
  • 词性标注(Part-of-speech tagging)和命名实体识别(Named Entity Recognition, NER)是序列标记任务的两个实例。
  • 多层 RNN 将多个 RNN 层堆叠在一起,而双向 RNN 结合了前向和后向 RNN 来编码整个句子。
  • 语言模型为自然语言文本分配概率,这是通过预测下一个词来实现的。
  • 你可以使用一个经过训练的语言模型来评估一个自然语言句子的“自然程度”,甚至是从零开始生成看起来逼真的文本。

第二部分:高级模型

过去几年,自然语言处理领域取得了迅猛的进步。具体来说,Transformer 和预训练语言模型(如 BERT)的出现彻底改变了该领域的格局以及从业者构建自然语言处理应用的方式。本书的这部分内容将帮助你跟上这些最新进展。

第六章介绍了序列到序列模型,这是一类重要的模型,它将使你能够构建更复杂的应用,比如机器翻译系统和聊天机器人。第七章讨论了另一种流行的神经网络架构,卷积神经网络(CNNs)。

第八章和第九章可以说是本书最重要和最令人兴奋的章节。它们分别涵盖了 Transformer 和迁移学习方法(如 BERT)。我们将演示如何利用这些技术构建高质量的机器翻译和拼写检查器等高级自然语言处理应用。

当你完成阅读这一部分时,你会自信地感觉到,通过你目前所学,你现在能够解决各种各样的自然语言处理任务。

第六章:序列到序列模型

本章包括

  • 使用 Fairseq 构建机器翻译系统
  • 使用 Seq2Seq 模型将一句话转换成另一句话
  • 使用束搜索解码器生成更好的输出
  • 评估机器翻译系统的质量
  • 使用 Seq2Seq 模型构建对话系统(聊天机器人)

在本章中,我们将讨论序列到序列(Seq2Seq)模型,这些模型是一些最重要的复杂自然语言处理模型,被用于广泛的应用场景,包括机器翻译。Seq2Seq 模型及其变种已经在许多实际应用中作为基本构建块使用,包括谷歌翻译和语音识别。我们将使用一个强大的框架来构建一个简单的神经机器翻译系统,以了解这些模型的工作原理以及如何使用贪婪和束搜索算法生成输出。在本章的结尾,我们将构建一个聊天机器人——一个可以与之对话的自然语言处理应用。我们还将讨论简单 Seq2Seq 模型的挑战和局限性。

6.1 介绍序列到序列模型

在前一章中,我们讨论了两种强大的自然语言处理模型,即序列标记和语言模型。回顾一下,序列标记模型接收一些单元的序列(例如,单词)并为每个单元分配一个标签(例如,词性标注)。而语言模型接收一些单元的序列(例如,单词),并估计给定序列在模型训练的领域中出现的概率。你还可以使用语言模型从零开始生成看起来真实的文本。请参阅图 6.1 以了解这两种模型的概况。


图 6.1 序列标记和语言模型

虽然这两种模型对于许多自然语言处理任务都非常有用,但对于某些任务,你可能希望兼顾这两者——让你的模型接收一些输入(例如,一句句子)并产生另一个东西(例如,另一句句子)作为响应。例如,如果你希望将用一种语言写的文本翻译成另一种语言,你需要让模型接收一个句子并产生另一个句子。你能用序列标记模型实现吗?不能,因为它们只能产生与输入句子中标记数量相同数量的输出标签。这显然对于翻译来说太过有限——一种语言中的表达(比如法语中的“Enchanté”)在另一种语言中可以有任意多或少的单词(比如英语中的“Nice to meet you”)。你能用语言模型实现吗?还是不能。虽然你可以使用语言模型生成看起来真实的文本,但你几乎无法控制它们生成的文本。事实上,语言模型不接受任何输入。

但是如果你仔细看图 6.1,你可能会注意到一些东西。左侧模型(序列标记模型)以句子作为输入,并生成某种形式的表示,而右侧模型则生成一个看起来像自然语言文本的长度可变的句子。我们已经有了构建我们想要的东西所需的组件,即一个接受句子并将其转换为另一个句子的模型。唯一缺失的部分是一种连接这两者的方法,以便我们可以控制语言模型生成什么。

实际上,当左侧模型完成处理输入句子时,循环神经网络已经生成了其抽象表示,该表示被编码在循环神经网络的隐藏状态中。如果你能简单地将这两者连接起来,使得句子表示从左到右传递,并且语言模型可以根据这个表示生成另一个句子,那么似乎你可以实现最初想要做的事情!

序列到序列模型,简称Seq2Seq模型,是基于这一见解构建的。Seq2Seq 模型由两个子组件组成,即编码器和解码器。见图 6.2 进行说明。编码器接受一系列单位(例如,一个句子)并将其转换为某种内部表示。另一方面,解码器从内部表示生成一系列单位(例如,一个句子)。总的来说,Seq2Seq 模型接受一个序列并生成另一个序列。与语言模型一样,生成过程在解码器产生一个特殊标记时停止,这使得 Seq2Seq 模型可以生成比输入序列更长或更短的输出。


图 6.2 序列到序列模型

有许多 Seq2Seq 模型的变体存在,这取决于你用于编码器的架构,你用于解码器的架构以及两者之间信息流动的方式。本章涵盖了最基本类型的 Seq2Seq 模型——简单地通过句子表示连接两个循环神经网络。我们将在第八章中讨论更高级的变体。

机器翻译是 Seq2Seq 模型的第一个,也是迄今为止最流行的应用。然而,Seq2Seq 架构是一个通用模型,适用于许多自然语言处理任务。在其中一项任务中,摘要生成,一个自然语言处理系统接受长文本(例如新闻文章)并生成其摘要(例如新闻标题)。Seq2Seq 模型可以用来将较长的文本“翻译”成较短的文本。另一个任务是对话系统,或者聊天机器人。如果你将用户的话语视为输入,系统的回应视为输出,对话系统的工作就是将前者“翻译”成后者。在本章后面,我们将讨论一个案例研究,在这个案例中,我们实际上使用了 Seq2Seq 模型构建了一个聊天机器人。另一个(有些令人惊讶的)应用是解析——如果你将输入文本视为一种语言,将其语法表示视为另一种语言,你可以使用 Seq2Seq 模型解析自然语言文本。

6.2 机器翻译 101

我们在第 1.2.1 节简要提及了机器翻译。简而言之,机器翻译(MT)系统是将给定文本从一种语言翻译成另一种语言的自然语言处理系统。输入文本所用语言称为源语言,而输出文本所用语言称为目标语言。源语言和目标语言的组合称为语言对

首先,让我们看一些例子,看看是什么样子,以及为什么将外语翻译成英语(或者任何其他你理解的语言)是困难的。在第一个例子中,让我们将一个西班牙句子翻译成英文,即,“Maria no daba una bofetada a la bruja verde.” 翻译成英文对应的是,“Mary did not slap the green witch.” 在说明翻译过程时的一个常见做法是绘制两个句子之间具有相同意思的单词或短语如何映射的图。两个实例之间的语言单位的对应称为对齐。图 6.3 显示了西班牙语和英语句子之间的对齐。


图 6.3 西班牙语和英语之间的翻译和词对齐

一些单词(例如,“Maria” 和 “Mary”,“bruja” 和 “witch”,以及 “verde” 和 “green”)完全一一对应。然而,一些表达(例如,“daba una bofetada” 和 “slap”)在某种程度上有很大不同,以至于你只能在西班牙语和英语之间对齐短语。最后,即使单词之间有一对一的对应关系,单词的排列方式,或者词序,在两种语言之间可能也会有所不同。例如,形容词在西班牙语中在名词之后添加(“la bruja verde”),而在英语中,它们在名词之前(“the green witch”)。在语法和词汇方面,西班牙语和英语在某种程度上是相似的,尤其是与中文和英语相比,尽管这个单一的例子显示了在两种语言之间进行翻译可能是一项具有挑战性的任务。

汉语和英语之间的情况开始变得更加复杂。图 6.4 展示了一句汉语句子(“布什与沙龙举行了会谈。”)和其英文翻译(“Bush held a talk with Shalon.”)之间的对齐。尽管汉语使用了自己的表意文字,但我们在这里使用了罗马化的句子以示简便。


图 6.4 汉语和英语之间的翻译和词对齐

现在你可以在图中看到更多交叉的箭头。与英语不同,汉语介词短语(比如“和沙龙一起”)通常从左边附着在动词上。此外,汉语不明确标记时态,机器翻译系统(以及人工翻译)需要“猜测”英文翻译中应该使用的正确时态。最后,汉译英的机器翻译系统还需要推断每个名词的正确数量(单数或复数),因为汉语名词没有根据数量明确标记(例如,“会谈”只是表示“谈话”,没有明确提及数量)。这是一个很好的例子,说明了翻译的难度取决于语言对。在语言学上不同的语言之间开发机器翻译系统(如中文和英文)通常比在语言学上类似的语言之间(如西班牙语和葡萄牙语)更具挑战性。


图 6.5 日语和英语之间的翻译和词对齐

让我们再看一个例子——从日语翻译成英语,在图 6.5 中有说明。图中所有的箭头都是交叉的,表示这两个句子的词序几乎完全相反。除了日语介词短语(例如“to music”)和关系从句从左边附着,跟汉语一样,宾语(例如例句中的“listening”在“我喜爱听”中)出现在动词之前。换句话说,日语是一种 SOV(主语-宾语-动词)的语言,而到目前为止我们提到的其他语言(英语、西班牙语和汉语)都是 SVO(主语-动词-宾语)的语言。结构上的差异是直接、逐字翻译效果不佳的原因之一。

注 这种语言的词序分类系统(如 SOV 和 SVO)常常用于语言类型学。世界上绝大多数语言都是 SOV(最常见)或 SVO(稍少一些),尽管少数语言遵循其他词序系统,例如阿拉伯语和爱尔兰语使用的 VSO(动词-主语-宾语)。很少一部分语言(不到所有语言的 3%)使用其他类型(VOS、OVS 和 OSV)。

除了前面图示的结构差异之外,许多其他因素也会使机器翻译成为一项困难的任务。其中之一是词汇差异。例如,如果你将日语单词“音楽”翻译成英语“music”,几乎没有歧义。“音楽”几乎总是“music”。然而,如果你将英语单词“brother”翻译成中文,你会面临歧义,因为中文对“哥哥”和“弟弟”使用不同的词语。在更极端的情况下,如果你将“cousin”翻译成中文,你会有八种不同的选择,因为在中国家庭制度中,你需要根据你的表兄弟是母亲的还是父亲的,是女性还是男性,比你大还是小,使用不同的词语。

另一个使机器翻译具有挑战性的因素是省略。你可以在图 6.5 中看到,日语中没有“我”的单词。在诸如中文、日语、西班牙语等许多其他语言中,当主语代词在上下文和/或动词形式中是明确的时候,你可以省略主语代词。这被称为zero pronoun,当从一个省略代词的语言翻译成一个省略频率较低的语言时(例如英语),它可能会成为一个问题。

在乔治敦-IBM 实验期间开发的最早的机器翻译系统之一是在冷战期间将俄语句子翻译成英语的。但它所做的不过是不比用双语词典查找每个单词并用其翻译替换它有多不同。上面展示的三个例子应该足以让你相信,简单地逐词替换太过于限制了。后来的系统包含了更大的词典和语法规则,但这些规则是由语言学家手动编写的,并不足以捕捉语言的复杂性(再次记住第一章中可怜的软件工程师)。

在神经机器翻译(NMT)出现之前,在学术界和工业界主导的机器翻译的主要范式称为统计机器翻译(SMT)。其背后的理念很简单:通过数据学习如何翻译,而不是通过手工制定规则。具体而言,SMT 系统学习如何从包含源语言文本和其在目标语言中的翻译的数据集中进行翻译。这些数据集称为平行语料库(或平行文本双文本)。通过查看两种语言中成对句子的集合,算法寻找一种语言中的单词应如何翻译为另一种语言的模式。由此产生的统计模型称为翻译模型。同时,通过查看一系列目标句子,算法可以学习目标语言中有效句子的外观。听起来耳熟吗?这正是语言模型的全部内容(请参阅前一章)。最终的 SMT 模型结合了这两个模型,并生成一种对输入的合理翻译,并且在目标语言中是一句有效、流畅的句子。

大约在 2015 年,强大的神经机器翻译(NMT)模型的出现颠覆了 SMT 的主导地位。SMT 和 NMT 有两个关键区别。首先,根据定义,NMT 基于神经网络,而神经网络以其准确建模语言的能力而闻名。因此,由 NMT 生成的目标句子往往比由 SMT 生成的句子更流畅和自然。其次,NMT 模型是端到端训练的,正如我在第一章中简要提到的那样。这意味着 NMT 模型由一个单一的神经网络组成,该网络接受输入并直接产生输出,而不是您需要独立训练的子模型和子模块的拼接。因此,与 SMT 模型相比,NMT 模型更容易训练,代码规模更小。

MT 已经在许多不同的行业和我们生活的方方面面得到了应用。将外语文本翻译成您理解的语言以快速抓住其含义的过程称为摘要。如果在摘要后认为文本足够重要,则可能会将其发送到正式的手动翻译中。专业翻译人员也使用 MT 进行工作。通常,源文本首先使用 MT 系统翻译为目标语言,然后由人类翻译人员编辑生成的文本。这种编辑称为后编辑。使用自动化系统(称为计算机辅助翻译或 CAT)可以加速翻译过程并降低成本。

6.3 构建你的第一个翻译器

在本节中,我们将构建一个可工作的 MT 系统。我们不会编写任何 Python 代码来实现,而是会充分利用现有的 MT 框架。许多开源框架使构建 MT 系统变得更加容易,包括 Moses(www.statmt.org/moses/)用于 SMT 和 OpenNMT(opennmt.net/)用于 NMT。在本节中,我们将使用 Fairseq(github.com/pytorch/fairseq),这是 Facebook 开发的一个 NMT 工具包,如今在 NLP 从业者中变得越来越流行。以下几个方面使 Fairseq 成为快速开发 NMT 系统的不错选择:1)它是一个现代化的框架,提供了许多预定义的最先进的 NMT 模型,您可以立即使用;2)它非常可扩展,意味着您可以通过遵循它们的 API 快速实现自己的模型;3)它非常快速,默认支持多 GPU 和分布式训练。由于其强大的模型,您可以在几小时内构建一个质量不错的 NMT 系统。

在开始之前,请在项目目录的根目录中运行pip install fairseq来安装 Fairseq。此外,请在您的 shell 中运行以下命令来下载并展开数据集(如果您使用的是 Ubuntu,则可能需要安装 unzip,可以通过运行sudo apt-get install unzip来安装):²

$ mkdir -p data/mt
$ wget https://realworldnlpbook.s3.amazonaws.com/data/mt/tatoeba.eng_spa.zip
$ unzip tatoeba.eng_spa.zip -d data/mt

我们将使用 Tatoeba 项目中的西班牙语和英语平行句子来训练一个西班牙语到英语的 MT 系统,这是我们在第四章中已经使用过的。该语料库包含大约 20 万个英语句子及其西班牙语翻译。我已经提前格式化了数据集,这样您就可以在不必担心获取数据、标记文本等方面的情况下使用它。数据集已经分为训练、验证和测试子集。

6.3.1 准备数据集

如前所述,MT 系统(包括 SMT 和 NMT)是机器学习模型,因此是根据数据训练的。MT 系统的开发过程看起来与任何其他现代 NLP 系统相似,如图 6.6 所示。首先,对平行语料库的训练部分进行预处理,并用于训练一组 NMT 模型候选者。接下来,使用验证部分来选择所有候选模型中表现最佳的模型。这个过程称为模型选择(请参阅第二章进行复习)。最后,最佳模型将在数据集的测试部分上进行测试,以获得反映模型优劣的评估指标。


图 6.6 构建 NMT 系统的流水线

MT 开发的第一步是对数据集进行预处理。但在进行预处理之前,你需要将数据集转换为易于使用的格式,通常是自然语言处理中的纯文本格式。实践中,用于训练 MT 系统的原始数据以多种不同格式出现,例如,纯文本文件(如果你很幸运的话)、专有软件的 XML 格式、PDF 文件和数据库记录。你的第一项任务是对原始文件进行格式化,使源句子和它们的目标翻译按句子对齐。结果文件通常是一个 TSV 文件,每行都是一个以制表符分隔的句子对,如下所示:

Let's try something.                   Permíteme intentarlo.
Muiriel is 20 now.                     Ahora, Muiriel tiene 20 años.
I just don't know what to say.         No sé qué decir.
You are in my way.                     Estás en mi camino.
Sometimes he can be a strange guy.     A veces él puede ser un chico raro.
...

在翻译对齐后,平行语料被输入到预处理管道中处理。具体的操作因应用程序和语言而异,但以下步骤最为常见:

  1. 过滤
  2. 清理
  3. 分词

在过滤步骤中,将从数据集中移除任何不适合用于训练 MT 系统的句子对。一个句子对是否太长、是否有用等因素影响很大,例如,任何其中一个文本长度过长(例如超过 1000 个单词)的句子对都无用,因为大多数 MT 模型不能建模这样长的句子。此外,任何其中一个句子过长但另一个句子过短的句子对都可能是由于数据处理或对齐错误而引起的噪音。例如,如果一个西班牙语句子有 10 个单词,其英语翻译的长度应该在 5 到 15 个单词之间。最后,如果平行语料库包含除源语言和目标语言之外的任何语言,应该移除这样的句子对。这种情况比你想象的要多得多——许多文档由于引用、解释或代码切换(在一个句子中混合多种语言)而成为多语言文档。语言检测(见第四章)可以帮助检测到这些异常情况。

过滤后,数据集中的句子可以进一步清理。该过程可能包括删除 HTML 标签和任何特殊字符,以及对字符(例如,繁体中文和简体中文)和拼写(例如,美式英语和英式英语)进行归一化。

如果目标语言使用类似于拉丁(a,b,c,…)或西里尔(а,б,в,…)字母表的脚本,区分大小写,您可能需要规范化大小写。通过这样做,您的 MT 系统将“NLP”与“nlp”和“Nlp”分组在一起。通常,这是一件好事,因为通过具有三个不同表示的单一概念,MT 模型必须从数据中学习它们实际上是单一概念。规范化大小写也会减少不同单词的数量,从而使训练和预测更快。但是,这也将“US”和“Us”以及“us”分组在一起,这可能不是一种理想的行为,具体取决于您处理的数据类型和领域。在实践中,这些决策,包括是否规范化大小写,都是通过观察它们对验证数据性能的影响来谨慎做出的。

机器翻译和 NLP 的数据清理

请注意,这里提到的清理技术并不特定于 MT。任何 NLP 应用和任务都可以从经过精心设计的过滤和清理操作的流程中受益。然而,对于 MT 来说,清理训练数据尤为重要,因为翻译的一致性对于构建强大的 MT 模型至关重要。如果您的训练数据在某些情况下使用“NLP”,而在其他情况下使用“nlp”,则模型将难以找到正确翻译该单词的方法,而人类很容易理解这两个单词代表一个概念。

此时,数据集仍然是一堆字符字符串。大多数 MT 系统操作单词,因此您需要对输入进行标记化(第 3.3 节)以识别单词。根据语言,您可能需要运行不同的流程(例如,对于中文和日文,需要进行词段切分)。

您之前下载和展开的 Tatoeba 数据集已经通过了所有这些预处理流程。现在,您已经准备好将数据集交给 Fairseq 了。第一步是告诉 Fairseq 将输入文件转换为二进制格式,以便训练脚本可以轻松读取它们,如下所示:

$ fairseq-preprocess \
      --source-lang es \
      --target-lang en \
      --trainpref data/mt/tatoeba.eng_spa.train.tok \
      --validpref data/mt/tatoeba.eng_spa.valid.tok \
      --testpref data/mt/tatoeba.eng_spa.test.tok \
      --destdir data/mt-bin \
      --thresholdsrc 3 \
      --thresholdtgt 3

当成功时,您应该在终端上看到一条“Wrote preprocessed data to data/mt-bin”的消息。您还应该在 data/mt-bin 目录下找到以下一组文件:

dict.en.txt dict.es.txt  test.es-en.en.bin  test.es-en.en.idx  test.es-en.es.bin  test.es-en.es.idx  train.es-en.en.bin  train.es-en.en.idx  train.es-en.es.bin  train.es-en.es.idx  valid.es-en.en.bin  valid.es-en.en.idx  valid.es-en.es.bin  valid.es-en.es.idx

此预处理步骤的关键功能之一是构建词汇表(在 Fairseq 中称为dictionary),它是从词汇项(通常为单词)到它们的 ID 的映射。注意目录中的两个字典文件 dict.en.txt 和 dict.es.txt。MT 涉及两种语言,因此系统需要维护两个映射,每个语言一个。

6.3.2 训练模型

现在,训练数据已转换为二进制格式,您可以准备好训练 MT 模型了。按下面所示使用包含二进制文件的目录以及几个超参数来调用 fairseq-train 命令:

$ fairseq-train \
    data/mt-bin \
    --arch lstm \
    --share-decoder-input-output-embed \
    --optimizer adam \
    --lr 1.0e-3 \
    --max-tokens 4096 \
    --save-dir data/mt-ckpt

您不必担心理解大多数参数的含义(至少暂时不用)。此时,您只需要知道使用指定目录中存储的数据(data/mt-bin)使用 LSTM 架构(-arch lstm)和一堆其他超参数来训练模型,并将结果保存在 data/mt-ckpt(checkpoint 的缩写)中即可。

运行此命令时,终端会交替显示两种进度条——一个用于训练,另一个用于验证,如下所示:

| epoch 001:  16%|???▏                | 61/389 [00:13<01:23,  3.91it/s, loss=8.347, ppl=325.58, wps=17473, ups=4, wpb=3740.967, bsz=417.180, num_updates=61, lr=0.001, gnorm=2.099, clip=0.000, oom=0.000, wall=17, train_wall=12]
| epoch 001 | valid on 'valid' subset | loss 4.208 | ppl 18.48 | num_updates 389

验证结果对应的行内容很容易区分——它们会说“验证”子集。每个时期,训练过程会轮流进行两个阶段:训练和验证。机器学习中使用的一个概念——一个时期,意味着对整个训练数据的一次遍历。在训练阶段,使用训练数据计算损失,然后以使新的参数集降低损失的方式调整模型参数。在验证阶段,模型参数被固定,使用一个单独的数据集(验证集)来衡量模型在该数据集上的表现。

我在第一章中提到过,验证集用于模型选择,这是从单个培训集中选择最佳的机器学习模型的过程。在这里,通过交替进行训练和验证阶段,我们使用验证集来检查所有中间模型(即第一个时期后的模型,第二个时期后的模型,等等)的性能。换言之,我们使用验证阶段来监视培训的进展情况。

为什么这是个好方法?我们通过在每个时期之后插入验证阶段获得了许多好处,但最重要的好处是避免过度拟合——验证数据之所以重要正是因为这个原因。为了进一步说明这一点,让我们看看在我们的西班牙语到英语机器翻译模型的训练过程中,训练集和验证集的损失如何随着时间变化,如图 6.7 所示。

随着训练的进行,训练损失变得越来越小,并逐渐趋近于零,因为这正是我们告诉优化器要做的:使损失尽可能地降低。检查训练损失是否在一个个时期后稳步下降是一个很好的“健全性检查”,可以验证您的模型和培训流水线是否按预期工作。

另一方面,如果您看一下验证损失,它在前几个时期内会下降,但在一定点之后逐渐回升,形成一个 U 形曲线——这是过度拟合的一个典型迹象。经过几个时期的培训后,您的模型在训练集上表现得非常好,开始失去其对验证集的泛化性。


图 6.7 训练和验证损失

让我们用机器翻译中的一个具体例子来说明当模型过度拟合时实际发生了什么。例如,如果您的训练数据包含了英文句子“It is raining hard”及其西班牙语翻译“Esta lloviendo fuerte”,而其他句子中没有包含“hard”一词,那么过拟合的模型可能会认为“fuerte”是“hard”的唯一可能翻译。一个正确拟合的模型可能会留下一些余地,让其他西班牙语单词出现作为“hard”的翻译,但一个过拟合的机器翻译系统总是会将“hard”翻译为“fuerte”,这是根据训练集“正确”的做法,但显然不是您想要构建健壮的机器翻译系统的理想选择。例如,“She is trying hard”中“hard”的最佳翻译方式并不是“fuerte”。

如果您看到验证损失开始上升,那么继续保持训练过程是没有意义的,因为很有可能您的模型已经在某种程度上过度拟合了数据。在这种情况下的一种常见做法,称为提前停止,是终止训练。具体来说,如果您的验证损失在一定数量的轮次内没有改善,您就停止训练,并使用验证损失最低时的模型。等待训练终止的轮次数称为耐心。在实践中,最关心的指标(例如 BLEU;请参阅第 6.5.2 节)用于提前停止,而不是验证损失。

好了,现在关于训练和验证就说到这里。图 6.7 中的图表表明验证损失在第 8 轮左右最低,所以你可以在大约 10 轮后停止(通过按 Ctrl + C),否则该命令会一直运行下去。Fairseq 将自动将最佳模型参数(根据验证损失)保存到 checkpoint_best.pt 文件中。

警告 如果您只使用 CPU 进行训练,可能需要很长时间。第十一章解释了如何使用 GPU 加速训练。

6.3.3 运行翻译器

模型训练完成后,您可以调用 fairseq-interactive 命令以交互方式在任何输入上运行您的机器翻译模型。您可以通过指定二进制文件位置和模型参数文件来运行该命令,如下所示:

$ fairseq-interactive \
    data/mt-bin \
    --path data/mt-ckpt/checkpoint_best.pt \
    --beam 5 \
    --source-lang es \
    --target-lang en

看到提示“Type the input sentence and press return”后,尝试逐一输入(或复制粘贴)以下西班牙语句子:

¡ Buenos días !
¡ Hola !
¿ Dónde está el baño ?
¿ Hay habitaciones libres ?
¿ Acepta tarjeta de crédito ?
La cuenta , por favor .

请注意这些句子中的标点和空白——Fairseq 假定输入已经进行了分词。您的结果可能会略有不同,这取决于许多因素(深度学习模型的训练通常涉及一些随机性),但您会得到类似以下的结果(我加粗了以示强调):

¡ Buenos días !
S-0     ¡ Buenos días !
H-0     -0.20546913146972656    Good morning !
P-0     -0.3342 -0.3968 -0.0901 -0.0007
¡ Hola !
S-1     ¡ Hola !
H-1     -0.12050756067037582    Hi !
P-1     -0.3437 -0.0119 -0.0059
¿ Dónde está el baño ?
S-2     ¿ Dónde está el baño ?
H-2     -0.24064254760742188    Where &apos;s the restroom ?
P-2     -0.0036 -0.4080 -0.0012 -1.0285 -0.0024 -0.0002
¿ Hay habitaciones libres ?
S-3     ¿ Hay habitaciones libres ?
H-3     -0.25766071677207947    Is there free rooms ?
P-3     -0.8187 -0.0018 -0.5702 -0.1484 -0.0064 -0.0004
¿ Acepta tarjeta de crédito ?
S-4     ¿ Acepta tarjeta de crédito ?
H-4     -0.10596384853124619    Do you accept credit card ?
P-4     -0.1347 -0.0297 -0.3110 -0.1826 -0.0675 -0.0161 -0.0001
La cuenta , por favor .
S-5     La cuenta , por favor .
H-5     -0.4411449432373047     Check , please .
P-5     -1.9730 -0.1928 -0.0071 -0.0328 -0.0001

这里大部分的输出句子都几乎完美,除了第四句(我会翻译成“有免费的房间吗?”)。即使考虑到这些句子都是任何一本旅行西班牙短语书中都可以找到的简单例子,但对于一个在一个小时内构建的系统来说,这并不是一个坏的开始!

6.4 Seq2Seq 模型的工作原理

在本节中,我们将深入探讨构成 Seq2Seq 模型的各个组件,包括编码器和解码器。我们还将涵盖用于解码目标句子的算法——贪婪解码和波束搜索解码。

6.4.1 编码器

正如我们在本章开始看到的,Seq2Seq 模型的编码器与我们在第五章中讨论的顺序标记模型并没有太大的不同。它的主要工作是接受输入序列(通常是一个句子)并将其转换为固定长度的向量表示。你可以使用像图 6.8 中所示的 LSTM-RNN。


图 6.8 Seq2Seq 模型的编码器

与顺序标记模型不同,我们只需要 RNN 的最终隐藏状态,然后将其传递给解码器生成目标句子。你也可以使用多层 RNN 作为编码器,这种情况下句子表示是每一层输出的串联,如图 6.9 所示。


图 6.9 使用多层 RNN 作为编码器

同样地,你可以使用双向(甚至是双向多层)RNN 作为编码器。最终的句子表示是正向层和反向层输出的串联,如图 6.10 所示。


图 6.10 使用双向 RNN 作为编码器

注意 这是一个小细节,但要记得 LSTM 单元产生两种类型的输出:单元状态和隐藏状态(请参阅 4.2.2 节)。在使用 LSTM 编码序列时,我们通常只使用最终隐藏状态,而丢弃单元状态。把单元状态看作是类似于临时循环变量,用于计算最终结果(隐藏状态)。请参见图 6.11 进行说明。


图 6.11 使用 LSTM 单元的编码器

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

相关文章
|
8月前
|
机器学习/深度学习 自然语言处理 PyTorch
真实世界的自然语言处理(二)(1)
真实世界的自然语言处理(二)
81 1
|
8月前
|
机器学习/深度学习 自然语言处理 异构计算
真实世界的自然语言处理(三)(2)
真实世界的自然语言处理(三)
56 3
|
8月前
|
机器学习/深度学习 自然语言处理 算法
真实世界的自然语言处理(三)(1)
真实世界的自然语言处理(三)
50 3
|
8月前
|
机器学习/深度学习 自然语言处理 数据可视化
真实世界的自然语言处理(一)(4)
真实世界的自然语言处理(一)
42 2
|
8月前
|
机器学习/深度学习 自然语言处理 算法
真实世界的自然语言处理(二)(4)
真实世界的自然语言处理(二)
55 1
|
8月前
|
机器学习/深度学习 JSON 自然语言处理
真实世界的自然语言处理(一)(5)
真实世界的自然语言处理(一)
69 1
|
8月前
|
机器学习/深度学习 人工智能 自然语言处理
真实世界的自然语言处理(一)(3)
真实世界的自然语言处理(一)
50 1
|
8月前
|
机器学习/深度学习 自然语言处理 监控
真实世界的自然语言处理(一)(2)
真实世界的自然语言处理(一)
70 1
|
8月前
|
机器学习/深度学习 自然语言处理 算法
真实世界的自然语言处理(二)(5)
真实世界的自然语言处理(二)
49 0
|
8月前
|
机器学习/深度学习 自然语言处理 算法
真实世界的自然语言处理(二)(3)
真实世界的自然语言处理(二)
59 0