本节介绍的机器翻译系统和本章前面7.2节中的实例功能完全一样,只不过本项目是使用PyTorch实现的。本项目也是使用基于注意力机制的神经网络训练一个序列到序列 (seq2seq) 模型,功能是将西班牙语翻译为英语。
7.3.1 准备数据集
实例7-2:PyTorch使用注意力机制实现翻译系统(源码路径:daima\7\fanyi.py)
本实例的实现文件是fanyi.py,在开始之前需要先准备数据集。本实例使用的数据是成千上万的英语到法语翻译对的集合,可以从https://tatoeba.org/eng/downloads下载需要的数据。幸运的是,有热心网友做了一些额外的工作,将巨大的语言数据包对拆分为单独的文本文件,请大家去https://www.manythings.org/anki/下载英文对法文的数据。因为文件太大,无法包含在仓库中,所以请先下载并保存到“data/eng-fra.txt”中,该文件的内容是制表符分隔的翻译对列表:
I am cold. J'ai froid.
7.3.2 数据预处理
(1)编码转换
将一种语言中的每个单词表示为一个单向向量,或零个大向量(除单个单向索引外)(在单词的索引处)。 与某种语言中可能存在的数十个字符相比,单词更多很多,因此编码向量要大得多。 但是,我们将作弊并整理数据以使每种语言仅使用几千个单词,如图8-4所示。
图8-4 作弊并整理数据
我们需要为每个单词设置一个唯一的索引,以便以后用作网络的输入和目标。 为了跟踪所有这些内容,将使用一个名为Lang的帮助程序类,该类具有单词→索引(word2index)和索引→单词(index2word)字典,以及每个要使用的单词word2count的计数,以便以后替换稀有词。首先编写类Lang,用于管理语言相关的字典和计数。构建一个语言对象,用于存储语言的相关信息,包括单词到索引的映射、单词的计数和索引到单词的映射。通过 addSentence 方法可以将句子中的单词添加到语言对象中,以便后续使用。这样的语言对象常用于自然语言处理任务中的数据预处理和特征表示。对应的实现代码如下所示:
class Lang:
def __init__(self, name):
self.name = name
self.word2index = {}
self.word2count = {}
self.index2word = {0: "SOS", 1: "EOS"}
self.n_words = 2 # Count SOS and EOS
def addSentence(self, sentence):
for word in sentence.split(' '):
self.addWord(word)
def addWord(self, word):
if word not in self.word2index:
self.word2index[word] = self.n_words
self.word2count[word] = 1
self.index2word[self.n_words] = word
self.n_words += 1
else:
self.word2count[word] += 1
(2)编码处理
为了简化起见,将文件中的Unicode 字符转换为 ASCII,将所有内容都转换为小写,并修剪大多数标点符号。将文本数据进行预处理,使其符合特定的格式要求。常见的预处理操作包括转换为小写、去除非字母字符、标点符号处理等,以便后续的文本分析和建模任务。这些预处理函数常用于自然语言处理领域中的文本数据清洗和特征提取过程。对应的实现代码如下所示:
def readLangs(lang1, lang2, reverse=False):
print("Reading lines...")
lines = open('data/%s-%s.txt' % (lang1, lang2), encoding='utf-8').read().strip().split('\n')
pairs = [[normalizeString(s) for s in l.split('\t')] for l in lines]
if reverse:
pairs = [list(reversed(p)) for p in pairs]
input_lang = Lang(lang2)
output_lang = Lang(lang1)
else:
input_lang = Lang(lang1)
output_lang = Lang(lang2)
return input_lang, output_lang, pairs
上述代码定义了两个函数:unicodeToAscii()和 normalizeString(),用于文本数据的预处理。下面是代码的简单解释:
- unicodeToAscii(s):该函数将 Unicode 字符串转换为 ASCII 字符串。它使用 unicodedata.normalize 函数将字符串中的 Unicode 字符标准化为分解形式(NFD),然后通过列表推导式遍历字符串中的每个字符 c,并筛选出满足条件 unicodedata.category(c) != 'Mn' 的字符(即不属于 Mark, Nonspacing 类别的字符)。最后,使用 join 方法将字符列表拼接成字符串并返回。
- normalizeString(s):该函数对字符串进行规范化处理,包括转换为小写、去除首尾空格,并移除非字母字符。
(3)文件拆分
读取数据文件,将文件拆分为几行,然后将几行拆分为两对。这些文件都是英语→其他语言的,因此,如果我们要从其他语言→英语进行翻译,需要添加reverse标志来反转对。编写函数 readLangs(lang1, lang2, reverse=False),用于读取并处理文本数据,并将其分割为一对一对的语言句子对。每一对句子都经过了规范化处理,以便后续的文本处理和分析任务。如果指定了 reverse=True,还会反转语言对的顺序。最后,返回两种语言的语言对象和句子对列表。这个函数在机器翻译等序列到序列任务中常用于数据准备阶段。对应的实现代码如下所示:
def readLangs(lang1, lang2, reverse=False):
print("Reading lines...")
lines = open('data/%s-%s.txt' % (lang1, lang2), encoding='utf-8').read().strip().split('\n')
pairs = [[normalizeString(s) for s in l.split('\t')] for l in lines]
if reverse:
pairs = [list(reversed(p)) for p in pairs]
input_lang = Lang(lang2)
output_lang = Lang(lang1)
else:
input_lang = Lang(lang1)
output_lang = Lang(lang2)
return input_lang, output_lang, pairs
(4)数据裁剪
由于本实例使用的数据文件中的句子有很多,并且我们想快速训练一些东西,因此将数据集修剪为仅相对简短的句子。在这里,设置最大长度为 10 个字(包括结尾的标点符号),过滤翻译成“我是”或“他是”等形式的句子(考虑到前面已替换掉撇号的情况)。对应的实现代码如下所示:
MAX_LENGTH = 10
eng_prefixes = (
"i am ", "i m ",
"he is", "he s ",
"she is", "she s ",
"you are", "you re ",
"we are", "we re ",
"they are", "they re "
)
def filterPair(p):
return len(p[0].split(' ')) < MAX_LENGTH and \
len(p[1].split(' ')) < MAX_LENGTH and \
p[1].startswith(eng_prefixes)
def filterPairs(pairs):
return [pair for pair in pairs if filterPair(pair)]
(5)准备数据
准备数据的完整过程是首先读取文本文件并拆分为行,将行拆分为偶对;然后规范文本,按长度和内容过滤;然后成对建立句子中的单词列表,读取文本文件并拆分为行,将行拆分为偶对;然后规范文本,按长度和内容过滤;最后成对建立句子中的单词列表。对应的实现代码如下所示:
def prepareData(lang1, lang2, reverse=False):
input_lang, output_lang, pairs = readLangs(lang1, lang2, reverse)
print("Read %s sentence pairs" % len(pairs))
pairs = filterPairs(pairs)
print("Trimmed to %s sentence pairs" % len(pairs))
print("Counting words...")
for pair in pairs:
input_lang.addSentence(pair[0])
output_lang.addSentence(pair[1])
print("Counted words:")
print(input_lang.name, input_lang.n_words)
print(output_lang.name, output_lang.n_words)
return input_lang, output_lang, pairs
input_lang, output_lang, pairs = prepareData('eng', 'fra', True)
print(random.choice(pairs))
执行后会输出:
Reading lines...
Read 135842 sentence pairs
Trimmed to 10599 sentence pairs
Counting words...
Counted words:
fra 4345
eng 2803
['il a l habitude des ordinateurs .', 'he is familiar with computers .']