目录
一、机器翻译与数据集
1.1 背景介绍
机器翻译是序列到序列学习课题中的一个典型案例,所以接下来的学习会以机器翻译这一案例展开。基于神经网络的翻译方法通常被称为神经机器翻译(neural machine translation),后续编写代码时将使用nmt这一缩写来代替名词神经机器翻译。
在学习序列到序列模型的过程中,将使用到一个新的机器翻译数据集,数据集下载链接如下:http://d2l-data.s3-accelerate.amazonaws.com/fra-eng.zip。
1.2 代码实现
nmtDataLoader.py:
导入依赖包:
import os
import torch
from preprocessing import Vocab
用于读取翻译数据集的函数:
def read_data_nmt(path):
with open(os.path.join(path), 'r', encoding='utf-8') as f:
return f.read()
翻译文本预处理函数:
def preprocess_nmt(text):
# 判断标点符号(,.!?)前面有没有空格,如果没有返回True
def no_space(char, prev_char):
return char in set(',.!?') and prev_char != ' '
# \u202f 是一个Unicode字符,代表窄非断行空格(Narrow No-Break Space)
# \xa0 是另一个表示空格的字符,在Unicode中代表不间断空格(Non-Breaking Space)
text = text.replace('\u202f', ' ').replace('\xa0', ' ').lower()
# 遍历text中的每一个字符,如果该字符属于标点符号(,.!?)且前一个字符不是空格,
# 那么在这个标点前面添加一个空格,方便后续把标点符号分割成一个单独的token
out = [' ' + char if i > 0 and no_space(char, text[i - 1]) else char for i, char in enumerate(text)]
return ''.join(out)
从翻译数据集中提取num_examples个token:
def tokenize_nmt(text, num_examples=None):
source, target = [], []
# 遍历翻译数据集中的每一行
for i, line in enumerate(text.split('\n')):
# 判断已提取的token数量是否达到上限
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
为了保证每个文本序列具有相同的长度,定义一个用于截断或填充文本序列的函数:
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))
将机器翻译的文本序列转换成小批量:
def build_array_nmt(lines, vocab, num_steps):
# token to index,实际上是调用了Vocab的__getitem__成员函数
# 如:[['go', '.'], ['go', '.'], ['go', '.']] -> [[47, 4], [47, 4], [47, 4]]
lines = [vocab[l] for l in lines]
# 将特定的“<eos>”词元添加到序列末尾
# 如:[[47, 4], [47, 4], [47, 4]] -> [[47, 4, 3], [47, 4, 3], [47, 4, 3]]
lines = [l + [vocab['<eos>']] for l in lines]
array = torch.tensor([truncate_pad(l, num_steps, vocab['<pad>']) for l in lines])
# 统计每个序列除去填充词元后的有效长度
# array != vocab['<pad>']操作会生成一个新的张量,其形状与array相同,如果array的某个元素满足不等式条件,则新张量在该位置上的值为True,否则为False
# .type(torch.int32)操作将上一步得到的布尔张量转换为torch.int32类型的张量,即False变为0,True变为1
# .sum(1)操作沿着张量的第二个维度进行求和
valid_len = (array != vocab['<pad>']).type(torch.int32).sum(1)
return array, valid_len
将上面实现的功能封装在一个函数中,方便外部模块调用。该函数的主要功能是返回翻译数据迭代器和词表:
def load_data_nmt(path, batch_size, num_steps, num_examples=600):
text = preprocess_nmt(read_data_nmt(path))
# source:[['go', '.'], ['hi', '.'], ['run', '!'], ...]
# target:[['va', '!'], ['salut', '!'], ['cours', '!'], ...]
source, target = tokenize_nmt(text, num_examples)
# min_freq=2表示出现频率小于2的token会被忽略
# <pad>:在小批量时用于将序列填充到相同长度;<bos>:begin of sentence,开始词元;<eos>:end of sentence,结束词元
src_vocab = Vocab(source, min_freq=2, reserved_tokens=['<pad>', '<bos>', '<eos>'])
tgt_vocab = Vocab(target, min_freq=2, reserved_tokens=['<pad>', '<bos>', '<eos>'])
# 经过处理后,原序列和目标序列中的子序列的长度都为num_steps
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)
# is_train=True表示打乱文本序列的次序,每次返回batch_size个子序列
data_iter = load_array(data_arrays, batch_size, is_train=True)
return data_iter, src_vocab, tgt_vocab
主函数测试:
if __name__=='__main__':
train_iter, src_vocab, tgt_vocab = load_data_nmt('fra-eng/fra.txt', 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)
pri