现代循环神经网络5-机器翻译与数据集

一、概述

机器翻译(Machine Translation)指的是将一种语言的文本序列自动转换为另一种语言的文本序列。它不仅是自然语言处理中的一个重要应用,而且还是检验语言模型能力的经典基准。

在历史上,早期的机器翻译方法依赖统计学(称为统计机器翻译 ),例如著名的Brown等人提出的方法;而近年来,随着神经网络技术的发展,端到端的神经机器翻译(Neural Machine Translation)成为主流。

在神经机器翻译中,我们的任务可以看作是一个 序列转换(sequence transduction) 问题:即将输入序列(源语言)映射成输出序列(目标语言)。这与单一语言的语言模型任务不同,因为这里每个样本是一个语言对。

二、下载与预处理数据集

在本例中,我们使用 Tatoeba 项目的"英-法"双语句子对数据集。数据集中,每一行由制表符分隔,第一部分为英文文本,第二部分为翻译后的法语文本。需要注意的是,每个文本序列既可以是一个单独的句子,也可以是包含多个句子的段落。

2.1 数据下载

下面的代码展示了如何下载数据集并读取原始文本内容:

python 复制代码
DATA_HUB['fra-eng'] = (DATA_URL + 'fra-eng.zip', '94646ad1522d915e7b0f9296181140edcf86a4f5')


def read_data_nmt():
    """载入"英语-法语"数据集"""
    data_dir = download_extract('fra-eng')
    with open(os.path.join(data_dir, 'fra.txt'), 'r', encoding='utf-8') as f:
        return f.read()


raw_text = read_data_nmt()
print(raw_text[:75])

输出:

text 复制代码
Go.	Va !
Hi.	Salut !
Run!	Cours !
Run!	Courez !
Who?	Qui ?
Wow!	Ça alors !

2.2 原始数据预处理

下载后,我们需要对原始文本数据进行预处理:

  • 用空格替换不间断空格;
  • 将大写字母转换为小写;
  • 在单词与标点符号之间插入空格,使得后续分词更为准确。

预处理代码示例如下:

python 复制代码
def preprocess_nmt(text: str):
    """预处理"英语-法语"数据集"""

    def no_space(char, prev_char):
        return char in set(',.!?') and prev_char != ' '

    # 使用空格替换不间断空格
    # 使用小写字母替换大写字母
    text = text.replace('\u202f', ' ').replace('\xa0', ' ').lower()
    # 在单词和标点符号之间插入空格
    out = [' ' + char if i > 0 and no_space(char, text[i - 1]) else char
           for i, char in enumerate(text)]
    return ''.join(out)


text = preprocess_nmt(raw_text)
print(text[:80])
  • text.replace('\u202f', ' ').replace('\xa0', ' ')是将文本中出现的特殊空格字符替换为普通的空格。具体来说:

    • \u202f 是窄不换行空格(narrow no-break space),
    • \xa0 是不换行空格(non-breaking space)。

通过替换为普通的空格,可以使文本在后续的处理(例如分词、标点符号处理等)时更加统一,避免因空格类型不同而引发的意外问题。

输出:

text 复制代码
go .	va !
hi .	salut !
run !	cours !
run !	courez !
who ?	qui ?
wow !	ça alors !

三、词元化处理

在机器翻译中,我们更倾向于使用单词级词元化而非字符级词元化。这意味着我们把句子切分成单词或标点符号,而不是单个字符。下面的函数对预处理后的文本数据进行词元化,将数据分为源语言(英文)和目标语言(法语)两部分。

python 复制代码
def tokenize_nmt(text, num_examples=None):
    """词元化"英语-法语"数据数据集"""
    source, target = [], []
    for i, line in enumerate(text.split('\n')):
        if num_examples and i > num_examples:
            break
        parts = line.split('\t')
        if len(parts) == 2:
            source.append(parts[0].split(' '))
            target.append(parts[1].split(' '))
    return source, target


source, target = tokenize_nmt(text)
pprint((source[:6], target[:6]))

输出:

text 复制代码
([['go', '.'],
  ['hi', '.'],
  ['run', '!'],
  ['run', '!'],
  ['who', '?'],
  ['wow', '!']],
 [['va', '!'],
  ['salut', '!'],
  ['cours', '!'],
  ['courez', '!'],
  ['qui', '?'],
  ['ça', 'alors', '!']])

让我们绘制每个文本序列所包含的词元数量的直方图。在这个简单的"英-法"数据集中,大多数文本序列的词元数量少于20个。

python 复制代码
def show_list_len_pair_hist(legend, xlabel, ylabel, xlist, ylist):
    """绘制列表长度对的直方图"""
    set_figsize((6.18, 3.82))
    _, _, patches = plt.hist([[len(l) for l in xlist], [len(l) for l in ylist]])
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    for patch in patches[1].patches:
        patch.set_hatch('/')
    plt.legend(legend)
    plt.show()


show_list_len_pair_hist(['source', 'target'], '# tokens per sequence', 'count', source, target)
  • plt.hist() 的返回值:counts, bin_edges, patches = plt.hist(data)
    • counts:每个区间(bin)内的计数,即直方图的高度。
    • bin_edges:直方图的边界值列表,定义了区间的划分。
    • patches:一个 BarContainer 对象,包含所有的矩形条(Rectangle 对象)。
  • patch.set_hatch('/'):表示对 第二个直方图 (即 patches[1])的所有矩形条应用 set_hatch('/'),这会给柱状块加上 斜线填充模式,用于区分不同的数据。

四、构建词表

由于机器翻译数据集由语言对构成,我们需要分别为源语言和目标语言构建词表。
注意: 单词级词元化生成的词表会比字符级词元化大得多。为此,我们将出现次数少于2次的词元统一视为"<unk>"(未知词元),同时还添加了以下特殊词元:

  • <pad>:用于填充,使每个序列长度相同;
  • <bos>:表示序列开始(Beginning Of Sentence);
  • <eos>:表示序列结束(End Of Sentence)。

构建词表示例代码如下:

python 复制代码
src_vocab = d2l.Vocab(source, min_freq=2, reserved_tokens=['<pad>', '<bos>', '<eos>'])
print(len(src_vocab))  # 输出:10012

五、加载数据集与批量处理

在机器翻译任务中,每个样本是一个包含源语言和目标语言的文本序列对,而且每个序列的长度通常不一致。为提高计算效率,我们需要将同一小批量内的所有序列调整为相同长度,这通常通过截断填充实现。

5.1 截断与填充

假设我们设定固定长度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> n u m _ s t e p s num\_steps </math>num_steps,对于每个序列:

  • 若序列长度 <math xmlns="http://www.w3.org/1998/Math/MathML"> ∣ l ∣ > n u m _ s t e p s |l| > num\_steps </math>∣l∣>num_steps,则截取前 <math xmlns="http://www.w3.org/1998/Math/MathML"> n u m _ s t e p s num\_steps </math>num_steps 个词元;
  • 若序列长度 <math xmlns="http://www.w3.org/1998/Math/MathML"> ∣ l ∣ ≤ n u m _ s t e p s |l| \leq num\_steps </math>∣l∣≤num_steps,则在序列末尾添加足够的 <pad> 词元直至达到 <math xmlns="http://www.w3.org/1998/Math/MathML"> n u m _ s t e p s num\_steps </math>num_steps。

这一过程可以用下面的数学公式描述:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> truncate_pad ( l , n u m _ s t e p s , p ) = { l [ 0 : n u m _ s t e p s ] if ∣ l ∣ > n u m _ s t e p s , l + [ p ] × ( n u m _ s t e p s − ∣ l ∣ ) if ∣ l ∣ ≤ n u m _ s t e p s , \text{truncate\_pad}(l, num\_steps, p) = \begin{cases} l[0:num\_steps] & \text{if } |l| > num\_steps, \\\\ l + [p] \times (num\_steps - |l|) & \text{if } |l| \leq num\_steps, \end{cases} </math>truncate_pad(l,num_steps,p)=⎩ ⎨ ⎧l[0:num_steps]l+[p]×(num_steps−∣l∣)if ∣l∣>num_steps,if ∣l∣≤num_steps,

代码实现如下:

python 复制代码
def truncate_pad(line, num_steps, padding_token):
    """截断或填充文本序列"""
    if len(line) > num_steps:
        return line[:num_steps]
    return line + [padding_token] * (num_steps - len(line))
python 复制代码
# 示例:对第一个英文序列进行截断或填充
print(d2l.truncate_pad(src_vocab[source[0]], 10, src_vocab['<pad>']))

输出:

text 复制代码
[47, 4, 1, 1, 1, 1, 1, 1, 1, 1]

5.2 构造小批量数据

为了方便模型训练,我们还需要构建一个函数,将处理好的文本序列转换成小批量数据。这里的操作包括:

  • 将每个序列转换为词元索引表示;
  • 在每个序列末尾添加 <eos> 词元;
  • 使用 truncate_pad 函数统一每个序列的长度;
  • 计算每个序列的有效长度,即不包含 <pad> 的词元数目。

有效长度可以用以下公式表示:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> valid_len = ∑ i = 1 n 1 ( l i ≠ < pad > ) \text{valid\len} = \sum{i=1}^{n} \mathbf{1}(l_i \neq \text{< pad >}) </math>valid_len=i=1∑n1(li=< pad >)

对应代码如下:

python 复制代码
def build_array_nmt(lines, vocab, num_steps):
    """将机器翻译的文本序列转换成小批量"""
    lines = [vocab[line] for line in lines]
    lines = [l + [vocab['<eos>']] for l in lines]
    array = torch.tensor([truncate_pad(
        l, num_steps, vocab['<pad>']) for l in lines])
    valid_len = (array != vocab['<pad>']).type(torch.int32).sum(dim=1)
    return array, valid_len    

接下来,我们定义一个函数来加载数据并返回数据迭代器,同时输出源语言和目标语言的词表:

python 复制代码
def load_data_nmt(batch_size, num_steps, num_examples=600):
    """返回翻译数据集的迭代器和词表"""
    text = preprocess_nmt(read_data_nmt())
    source, target = tokenize_nmt(text, num_examples)
    src_vocab = Vocab(source, min_freq=2,
                      reserved_tokens=['<pad>', '<bos>', '<eos>'])
    tgt_vocab = Vocab(target, min_freq=2,
                      reserved_tokens=['<pad>', '<bos>', '<eos>'])
    src_array, src_valid_len = build_array_nmt(source, src_vocab, num_steps)
    tgt_array, tgt_valid_len = build_array_nmt(target, tgt_vocab, num_steps)
    data_arrays = (src_array, src_valid_len, tgt_array, tgt_valid_len)
    data_iter = load_array(data_arrays, batch_size)
    return data_iter, src_vocab, tgt_vocab

读取一个小批量数据示例

python 复制代码
train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size=2, num_steps=8)
for X, X_valid_len, Y, Y_valid_len in train_iter:
    print('X:', X.type(torch.int32))
    print('X的有效长度:', X_valid_len)
    print('Y:', Y.type(torch.int32))
    print('Y的有效长度:', Y_valid_len)
    break

输出示例可能为:

css 复制代码
X: tensor([[  7, 101,   4,   3,   1,   1,   1,   1],
        [  7, 151,   4,   3,   1,   1,   1,   1]], dtype=torch.int32)
X的有效长度: tensor([4, 4])
Y: tensor([[  6,   7, 158,   4,   3,   1,   1,   1],
        [  6,   7,  32,  29, 108,   4,   3,   1]], dtype=torch.int32)
Y的有效长度: tensor([5, 7])

六、训练模型与小结

在完成上述数据预处理、词元化、词表构建以及批量数据加载之后,就可以利用这些数据来训练神经机器翻译模型。在训练过程中,模型会一个词一个词地生成输出序列,当生成到 <eos> 时,就认为输出完成。

  • 机器翻译 是将一种语言的文本自动转换为另一种语言的关键任务,其核心在于序列到序列的转换。
  • 数据集 通常由源语言和目标语言的文本对组成,预处理步骤包括替换空格、大小写转换及标点处理。
  • 词元化 采用单词级分词方法,并通过示例代码将文本分为两部分。
  • 词表构建 时对低频词(出现次数少于2次)用 <unk> 统一处理,并加入 <pad><bos><eos> 等特殊标记。
  • 截断与填充 技巧使得各个序列统一为固定长度 <math xmlns="http://www.w3.org/1998/Math/MathML"> n u m _ s t e p s num\_steps </math>num_steps,便于小批量训练。

通过这些步骤,我们不仅可以清晰地理解机器翻译的基本原理,也为后续模型训练打下坚实基础。希望这篇博客能帮助你快速入门神经机器翻译相关技术!

相关推荐
BIT_Legend3 小时前
Torch 模型 model => .onnx => .trt 及利用 TensorTR 在 C++ 下的模型部署教程
c++·人工智能·python·深度学习
枫子有风4 小时前
深度学习实验
人工智能·深度学习
(initial)4 小时前
高效微调算法 (Parameter-Efficient Fine-tuning, PEFT) 详解
人工智能·深度学习·机器学习
weixin_307779134 小时前
PyTorch调试与错误定位技术
开发语言·人工智能·pytorch·python·深度学习
Averill_5 小时前
【论文阅读】多模态——CLIPasso
深度学习·机器学习·计算机视觉
江木1236 小时前
NAFNet:Simple Baselines for Image Restoration
论文阅读·图像处理·深度学习
网络安全(king)6 小时前
基于java社交网络安全的知识图谱的构建与实现
开发语言·网络·深度学习·安全·web安全·php
Ronin-Lotus7 小时前
深度学习篇---Opencv中的机器学习和深度学习
python·深度学习·opencv·机器学习
ylfhpy7 小时前
Manus 演示案例:自动完成小说编写并生成最终 PDF 文档
人工智能·深度学习·机器学习·自然语言处理·manus