人类掌握了序列转导,将一个表示转移到另一个对象。我们可以轻松地想象一个序列的心理表示。如果有人说“我的花园里的花很美”,我们可以轻松地想象一个有花的花园。我们可以看到花园的图像,尽管我们可能从未见过那个花园。我们甚至可以想象到鸟儿的叫声和花香。
机器必须从头开始学习数值表示的转导。循环或卷积方法已经取得了一些有趣的结果,但尚未达到显著的BLEU翻译评估分数。翻译需要将语言A的表示转换为语言B。
Transformer模型的自注意力创新增强了机器智能的分析能力。在尝试将其翻译成语言B之前,要充分表示语言A中的序列。自注意力提升了机器获取更好BLEU分数所需的智能水平。
2017年,具有开创意义的“Attention Is All You Need Transformer”在英语-德语和英语-法语翻译中获得了最佳结果。自那以后,其他Transformer模型改进了分数。
到目前为止,在本书的这一部分,我们已经涵盖了Transformer的基本方面:Transformer的架构,从头开始训练RoBERTa模型,微调BERT,评估微调后的BERT,以及探索一些Transformer示例的下游任务。
在本章中,我们将通过三个附加主题来介绍机器翻译。首先,我们将定义什么是机器翻译。然后,我们将对Workshop on Machine Translation(WMT)数据集进行预处理。最后,我们将看到如何实现机器翻译。
本章涵盖以下主题:
- 定义机器翻译
- 人类的转导和翻译
- 机器的转导和翻译
- 预处理WMT数据集
- 使用BLEU评估机器翻译
- 几何评估
- Chencherry平滑
- 介绍Google Translate的API
- 使用Trax初始化英语-德语问题
我们的第一步将是定义机器翻译。
定义机器翻译
Vaswani等人(2017)在设计Transformer时解决了NLP中最困难的问题之一。机器翻译的人类基准似乎超出了我们人机智能设计师的能力。然而,这并没有阻止Vaswani等人(2017)发布Transformer的架构并实现了最先进的BLEU结果。
在本节中,我们将定义机器翻译。机器翻译是通过机器的转录和输出来复制人工翻译的过程:
图6.1中的一般思想是让机器通过以下几个步骤完成任务:
- 选择要翻译的句子
- 通过数亿个参数学习单词之间的关系
- 学习单词如何引用彼此的多种方式
- 利用机器转录将学到的参数应用于新的序列
- 为单词或序列选择候选翻译
这个过程始终以源语言A的待翻译句子开始,最终输出一个语言B中的翻译句子。中间计算涉及到转录。
人类的转录和翻译
例如,欧洲议会的人类翻译员不会逐字逐句地翻译一句话。逐字逐句的翻译通常没有意义,因为它们缺乏适当的语法结构,无法产生正确的翻译,因为忽略了每个单词的上下文。
人类的转录是将语言A中的一句话转化为该句意义的认知表示。欧洲议会的口译员(口头翻译)或翻译员(书面翻译)将只会将这种转录转化为语言B中的一句解释。
我们将翻译员或翻译在语言B中进行的翻译称为参考句。
在图6.1中描述的机器翻译过程中,您将注意到几个参考。
在现实生活中,人类翻译员通常不会多次将句子A翻译为句子B,而只会翻译一次。然而,现实生活中可能有多个翻译员翻译句子A。例如,您可以找到蒙田的《散文》的多个法语到英语的翻译版本。如果您从原始的法语版本中提取一个句子A,那么您将找到数个句子B的不同版本,分别标记为参考1到参考n。
如果有一天您去欧洲议会,您可能会注意到口译员只能翻译有限的时间,例如两小时。然后另一位口译员接管。没有两名口译员有相同的风格,就像作家有不同的风格一样。源语言中的句子A可能会被同一人在一天中重复多次,但会被翻译为多个参考句子B版本:
reference=reference1,reference2,…referencenreference ={reference 1, reference 2,…reference n}
机器必须找到一种像人类翻译员一样思考的方法。
机器的转换和翻译
原始Transformer架构的转换过程使用编码器堆栈、解码器堆栈和所有模型的参数来表示一个参考序列。我们将该输出序列称为参考。
为什么不直接说“输出预测”呢?问题在于,没有单一的输出预测。Transformer和人类一样,会产生一个我们可以参考的结果,但如果我们以不同的方式训练它或使用不同的Transformer模型,结果可能会发生变化!
我们立刻意识到,人类转换的人类基准,即语言序列的表示,是一个相当大的挑战。然而,已经取得了很多进展。
对机器翻译的评估证明了自然语言处理的进步。要确定一个解决方案是否比另一个更好,每个自然语言处理的挑战者、实验室或组织都必须参考相同的数据集,才能进行有效的比较。
现在让我们来探索一个WMT数据集。
处理WMT数据集的预处理工作
Vaswani等人(2017)介绍了Transformer在WMT 2014英德翻译任务和WMT 2014英法翻译任务上的成就。Transformer取得了最先进的BLEU分数。BLEU将在本章的“使用BLEU评估机器翻译”部分进行描述。
2014年的WMT包括多个欧洲语言数据集。其中一个数据集包含了来自Europarl语料库第7版的数据。我们将使用来自欧洲议会会议平行语料库1996-2011的法英数据集(www.statmt.org/europarl/v7…
一旦您下载了文件并进行了提取,我们将对两个平行文件进行预处理:
- europarl-v7.fr-en.en
- europarl-v7.fr-en.fr
我们将加载、清理和缩小语料库的大小。
让我们开始预处理。
预处理原始数据
在本节中,我们将预处理 europarl-v7.fr-en.en 和 europarl-v7.fr-en.fr 文件。
打开 read.py 文件,该文件位于本章的 GitHub 目录中。确保这两个 europarl 文件与 read.py 文件位于相同的目录中。
该程序开始使用标准的 Python 函数和 pickle 来保存序列化的输出文件:
import pickle
from pickle import dump
接下来,我们定义加载文件到内存的函数:
# load doc into memory
def load_doc(filename):
# open the file as read only
file = open(filename, mode='rt', encoding='utf-8')
# read all text
text = file.read()
# close the file
file.close()
return text
然后,加载的文档被分割成句子:
# split a loaded document into sentences
def to_sentences(doc):
return doc.strip().split('n')
获取最短和最长长度:
# shortest and longest sentence lengths
def sentence_lengths(sentences):
lengths = [len(s.split()) for s in sentences]
return min(lengths), max(lengths)
导入的句子行必须经过清理,以避免训练无用和嘈杂的标记。这些行在被标准化后,会根据空白分词,并转换为小写。从每个标记中删除标点符号,去除不可打印字符,然后排除包含数字的标记。清理后的行被存储为字符串。
程序运行清理函数并返回清理后的字符串:
# clean lines
import re
import string
import unicodedata
def clean_lines(lines):
cleaned = list()
# prepare regex for char filtering
re_print = re.compile('[^%s]' % re.escape(string.printable))
# prepare translation table for removing punctuation
table = str.maketrans('', '', string.punctuation)
for line in lines:
# normalize unicode characters
line = unicodedata.normalize('NFD', line).encode('ascii', 'ignore')
line = line.decode('UTF-8')
# tokenize on white space
line = line.split()
# convert to lower case
line = [word.lower() for word in line]
# remove punctuation from each token
line = [word.translate(table) for word in line]
# remove non-printable chars form each token
line = [re_print.sub('', w) for w in line]
# remove tokens with numbers in them
line = [word for word in line if word.isalpha()]
# store as string
cleaned.append(' '.join(line))
return cleaned
我们已经定义了我们将调用来准备数据集的关键函数。首先加载和清理英文数据:
# load English data
filename = 'europarl-v7.fr-en.en'
doc = load_doc(filename)
sentences = to_sentences(doc)
minlen, maxlen = sentence_lengths(sentences)
print('English data: sentences=%d, min=%d, max=%d' % (len(sentences), minlen, maxlen))
cleanf=clean_lines(sentences)
数据集现在已经清理干净,并以名为English.pkl的序列化文件进行了保存:
filename = 'English.pkl'
outfile = open(filename,'wb')
pickle.dump(cleanf,outfile)
outfile.close()
print(filename," saved")
输出显示了关键统计信息,并确认已保存English.pkl:
English data: sentences=2007723, min=0, max=668
English.pkl saved
现在,我们将使用相同的过程处理法语数据,并将其保存到名为French.pkl的序列化文件中:
# load French data
filename = 'europarl-v7.fr-en.fr'
doc = load_doc(filename)
sentences = to_sentences(doc)
minlen, maxlen = sentence_lengths(sentences)
print('French data: sentences=%d, min=%d, max=%d' % (len(sentences), minlen, maxlen))
cleanf=clean_lines(sentences)
filename = 'French.pkl'
outfile = open(filename,'wb')
pickle.dump(cleanf,outfile)
outfile.close()
print(filename," saved")
输出显示了法语数据集的关键统计信息,并确认了French.pkl已保存。
French data: sentences=2007723, min=0, max=693
French.pkl saved
主要的预处理工作已完成,但我们仍需要确保数据集不包含嘈杂和混淆的标记。
完成数据集的最终预处理
现在,打开与read.py在同一目录中的read_clean.py。我们的流程现在定义了一个函数,该函数将加载在前一节中进行了清理的数据集,并在预处理完成后保存它们:
from pickle import load
from pickle import dump
from collections import Counter
# load a clean dataset
def load_clean_sentences(filename):
return load(open(filename, 'rb'))
# save a list of clean sentences to file
def save_clean_sentences(sentences, filename):
dump(sentences, open(filename, 'wb'))
print('Saved: %s' % filename)
我们现在定义一个函数,将创建一个词汇计数器。知道一个词在我们要解析的序列中被使用了多少次是很重要的。例如,如果一个词只在包含两百万行的数据集中使用了一次,我们将浪费宝贵的GPU资源来学习它。让我们定义这个计数器:
# create a frequency table for all words
def to_vocab(lines):
vocab = Counter()
for line in lines:
tokens = line.split()
vocab.update(tokens)
return vocab
词汇计数器将检测频率低于 min_occurrence 的单词:
# remove all words with a frequency below a threshold
def trim_vocab(vocab, min_occurrence):
tokens = [k for k,c in vocab.items() if c >= min_occurrence]
return set(tokens)
在这种情况下,min_occurrence=5,频率低于或等于这一阈值的单词已被删除,以避免浪费训练模型分析它们的时间。
现在我们必须处理词汇表外的单词(Out-Of-Vocabulary,OOV)。OOV 单词可以是拼写错误的单词、缩写或任何不适合标准词汇表表示的单词。我们可以使用自动拼写检查,但这不会解决所有问题。在这个例子中,我们将简单地用 “unk”(未知)标记替换 OOV 单词:
# mark all OOV with "unk" for all lines
def update_dataset(lines, vocab):
new_lines = list()
for line in lines:
new_tokens = list()
for token in line.split():
if token in vocab:
new_tokens.append(token)
else:
new_tokens.append('unk')
new_line = ' '.join(new_tokens)
new_lines.append(new_line)
return new_lines
我们现在将运行英语数据集的函数,保存输出,然后显示 20 行:
# load English dataset
filename = 'English.pkl'
lines = load_clean_sentences(filename)
# calculate vocabulary
vocab = to_vocab(lines)
print('English Vocabulary: %d' % len(vocab))
# reduce vocabulary
vocab = trim_vocab(vocab, 5)
print('New English Vocabulary: %d' % len(vocab))
# mark out of vocabulary words
lines = update_dataset(lines, vocab)
# save updated dataset
filename = 'english_vocab.pkl'
save_clean_sentences(lines, filename)
# spot check
for i in range(20):
print("line",i,":",lines[i])
输出函数首先显示所获得的词汇量压缩:
English Vocabulary: 105357
New English Vocabulary: 41746
Saved: english_vocab.pkl
预处理后的数据集已保存。接下来,输出函数将显示20行,如以下摘录所示:
line 0 : resumption of the session
line 1 : i declare resumed the session of the european parliament adjourned on friday december and i would like once again to wish you a happy new year in the hope that you enjoyed a pleasant festive period
line 2 : although, as you will have seen, the dreaded millennium bug failed to materialise still the people in a number of countries suffered a series of natural disasters that truly were dreadful
line 3 : you have requested a debate on this subject in the course of the next few days during this partsession
现在让我们运行法语数据集的函数,保存输出,然后显示20行:
# load French dataset
filename = 'French.pkl'
lines = load_clean_sentences(filename)
# calculate vocabulary
vocab = to_vocab(lines)
print('French Vocabulary: %d' % len(vocab))
# reduce vocabulary
vocab = trim_vocab(vocab, 5)
print('New French Vocabulary: %d' % len(vocab))
# mark out of vocabulary words
lines = update_dataset(lines, vocab)
# save updated dataset
filename = 'french_vocab.pkl'
save_clean_sentences(lines, filename)
# spot check
for i in range(20):
print("line",i,":",lines[i])
输出函数首先显示了获得的词汇压缩:
French Vocabulary: 141642
New French Vocabulary: 58800
Saved: french_vocab.pkl
预处理后的数据集已保存。输出函数然后显示了20行,如下所示:
line 0 : reprise de la session
line 1 : je declare reprise la session du parlement europeen qui avait ete interrompue le vendredi decembre dernier et je vous renouvelle tous mes vux en esperant que vous avez passe de bonnes vacances
line 2 : comme vous avez pu le constater le grand bogue de lan ne sest pas produit en revanche les citoyens dun certain nombre de nos pays ont ete victimes de catastrophes naturelles qui ont vraiment ete terribles
line 3 : vous avez souhaite un debat a ce sujet dans les prochains jours au cours de cette periode de session
这一部分展示了在训练之前必须处理原始数据。现在,数据集已准备好,可以输入到 Transformer 中进行训练。
法语数据集的每一行都是待翻译的句子。英语数据集的每一行都是机器翻译模型的参考。机器翻译模型必须生成一个与参考相匹配的英文候选翻译。
BLEU 提供了一种评估机器翻译模型生成的候选翻译的方法。
使用BLEU评估机器翻译
Papineni等人(2002年)提出了一种评估人工翻译的有效方法。人工基准很难定义。但是,他们意识到,如果我们逐字逐句地比较人工翻译和机器翻译,就可以获得有效的结果。
Papineni等人(2002年)将他们的方法命名为双语评估研究分数(BLEU)。
在本节中,我们将使用自然语言工具包(NLTK)来实现BLEU:
我们将从几何评估开始。
几何评估
打开BLEU.py,它在本书的GitHub存储库章节目录中。
程序导入nltk模块:
from nltk.translate.bleu_score import sentence_bleu
from nltk.translate.bleu_score import SmoothingFunction
然后,它模拟了由机器翻译模型产生的候选翻译与数据集中的实际翻译参考之间的比较。请记住,一句话可能已经多次重复,并由不同的翻译人员以不同的方式进行翻译,这使得寻找有效的评估策略变得具有挑战性。
该程序可以评估一个或多个参考翻译:
#Example 1
reference = [['the', 'cat', 'likes', 'milk'], ['cat', 'likes' 'milk']]
candidate = ['the', 'cat', 'likes', 'milk']
score = sentence_bleu(reference, candidate)
print('Example 1', score)
#Example 2
reference = [['the', 'cat', 'likes', 'milk']]
candidate = ['the', 'cat', 'likes', 'milk']
score = sentence_bleu(reference, candidate)
print('Example 2', score)
两个示例的分数都是1:
Example 1 1.0
Example 2 1.0
候选项 C、参考 R 和候选项 C 中找到的正确标记数量(N)的简单评估 P 可以表示为一个几何函数: