本次实验在kaggle平台进行
下载torchtext 0.6.0
导入所需的包和语料库,使用从JParaCrawl下载的日语-英语平行数据集[http://www.kecl.ntt.co.jp/icl/lirg/jparacrawl]。JParaCrawl被描述为“由NTT创建的最大公开可用的英日平行语料库。它主要通过网络抓取和自动对齐平行句子来创建。”
准备分词器
与英语或其他字母语言不同,日语句子中没有空格来分隔单词。我们使用由JParaCrawl提供的分词器,它们使用了SentencePiece来处理日语和英语。
测试案例:
构建词汇表和张量
创建DataLoader对象以在训练过程中迭代。
在这里,将BATCH_SIZE设置为16,以避免“cuda内存溢出”,但这取决于诸如机器内存容量、数据大小等各种因素,因此可以根据需求自由更改批量大小
BATCH_SIZE = 8 # 定义批量大小
PAD_IDX = ja_vocab['<pad>'] # 获取填充符索引
BOS_IDX = ja_vocab['<bos>'] # 获取序列开始符索引
EOS_IDX = ja_vocab['<eos>'] # 获取序列结束符索引
def generate_batch(data_batch):
"""生成一个小批量数据函数
Args:
data_batch (list): 包含日语和英语句子对的数据批量
Returns:
tuple: 包含填充后的日语和英语句子批量
"""
ja_batch, en_batch = [], []
for (ja_item, en_item) in data_batch:
# 在日语句子的开始和结束处添加起始和结束符
ja_batch.append(torch.cat([torch.tensor([BOS_IDX]), ja_item, torch.tensor([EOS_IDX])], dim=0))
# 在英语句子的开始和结束处添加起始和结束符
en_batch.append(torch.cat([torch.tensor([BOS_IDX]), en_item, torch.tensor([EOS_IDX])], dim=0))
# 对填充后的日语和英语句子批量进行填充
ja_batch = pad_sequence(ja_batch, padding_value=PAD_IDX)
en_batch = pad_sequence(en_batch, padding_value=PAD_IDX)
return ja_batch, en_batch
# 使用DataLoader加载训练数据,并指定批量大小、是否打乱数据、以及使用的数据生成函数
train_iter = DataLoader(train_data, batch_size=BATCH_SIZE,
shuffle=True, collate_fn=generate_batch)
Transformer是一种Seq2Seq模型,最早在《Attention is all you need》论文中提出,用于解决机器翻译任务。Transformer模型由编码器和解码器模块组成,每个模块包含固定数量的层。
编码器通过一系列的多头注意力和前馈网络层处理输入序列。编码器的输出被称为记忆,与目标张量一起输入解码器。编码器和解码器通过教师强制技术以端到端的方式进行训练。
from torch.nn import (TransformerEncoder, TransformerDecoder,
TransformerEncoderLayer, TransformerDecoderLayer)
class Seq2SeqTransformer(nn.Module):
def __init__(self, num_encoder_layers: int, num_decoder_layers: int,
emb_size: int, src_vocab_size: int, tgt_vocab_size: int,
dim_feedforward:int = 512, dropout:float = 0.1):
"""
初始化Seq2SeqTransformer模型。
Args:
num_encoder_layers (int): 编码器的层数
num_decoder_layers (int): 解码器的层数
emb_size (int): 词嵌入的维度大小
src_vocab_size (int): 源语言词汇表的大小
tgt_vocab_size (int): 目标语言词汇表的大小
dim_feedforward (int): 前馈神经网络的隐藏层大小,默认为512
dropout (float): dropout比率,默认为0.1
"""
super(Seq2SeqTransformer, self).__init__()
# 定义编码器和解码器层
encoder_layer = TransformerEncoderLayer(d_model=emb_size, nhead=NHEAD,
dim_feedforward=dim_feedforward)
self.transformer_encoder = TransformerEncoder(encoder_layer, num_layers=num_encoder_layers)
decoder_layer = TransformerDecoderLayer(d_model=emb_size, nhead=NHEAD,
dim_feedforward=dim_feedforward)
self.transformer_decoder = TransformerDecoder(decoder_layer, num_layers=num_decoder_layers)
# 定义生成器和词嵌入层
self.generator = nn.Linear(emb_size, tgt_vocab_size)
self.src_tok_emb = TokenEmbedding(src_vocab_size, emb_size)
self.tgt_tok_emb = TokenEmbedding(tgt_vocab_size, emb_size)
# 定义位置编码层
self.positional_encoding = PositionalEncoding(emb_size, dropout=dropout)
def forward(self, src: Tensor, trg: Tensor, src_mask: Tensor,
tgt_mask: Tensor, src_padding_mask: Tensor,
tgt_padding_mask: Tensor, memory_key_padding_mask: Tensor):
"""
模型的前向传播。
Args:
src (Tensor): 输入序列张量
trg (Tensor): 目标序列张量
src_mask (Tensor): 输入序列的mask张量
tgt_mask (Tensor): 目标序列的mask张量
src_padding_mask (Tensor): 输入序列的填充mask张量
tgt_padding_mask (Tensor): 目标序列的填充mask张量
memory_key_padding_mask (Tensor): 编码器输出的填充mask张量
Returns:
Tensor: 模型输出张量
"""
src_emb = self.positional_encoding(self.src_tok_emb(src))
tgt_emb = self.positional_encoding(self.tgt_tok_emb(trg))
memory = self.transformer_encoder(src_emb, src_mask, src_padding_mask)
outs = self.transformer_decoder(tgt_emb, memory, tgt_mask, None,
tgt_padding_mask, memory_key_padding_mask)
return self.generator(outs)
def encode(self, src: Tensor, src_mask: Tensor):
"""
编码器的前向传播。
Args:
src (Tensor): 输入序列张量
src_mask (Tensor): 输入序列的mask张量
Returns:
Tensor: 编码器输出的张量
"""
return self.transformer_encoder(self.positional_encoding(
self.src_tok_emb(src)), src_mask)
def decode(self, tgt: Tensor, memory: Tensor, tgt_mask: Tensor):
"""
解码器的前向传播。
Args:
tgt (Tensor): 目标序列张量
memory (Tensor): 编码器输出的张量
tgt_mask (Tensor): 目标序列的mask张量
Returns:
Tensor: 解码器输出的张量
"""
return self.transformer_decoder(self.positional_encoding(
self.tgt_tok_emb(tgt)), memory,
tgt_mask)
编写位置编码函数,文本标记通过使用标记嵌入来表示。为了引入单词顺序的概念,会向标记嵌入中添加位置编码
class PositionalEncoding(nn.Module):
def __init__(self, emb_size: int, dropout, maxlen: int = 5000):
"""
初始化位置编码层。
Args:
emb_size (int): 词嵌入的维度大小
dropout (float): dropout比率
maxlen (int): 最大序列长度,默认为5000
"""
super(PositionalEncoding, self).__init__()
# 计算位置编码矩阵
den = torch.exp(- torch.arange(0, emb_size, 2) * math.log(10000) / emb_size)
pos = torch.arange(0, maxlen).reshape(maxlen, 1)
pos_embedding = torch.zeros((maxlen, emb_size))
pos_embedding[:, 0::2] = torch.sin(pos * den)
pos_embedding[:, 1::2] = torch.cos(pos * den)
pos_embedding = pos_embedding.unsqueeze(-2)
# 定义dropout层和位置编码张量
self.dropout = nn.Dropout(dropout)
self.register_buffer('pos_embedding', pos_embedding)
def forward(self, token_embedding: Tensor):
"""
前向传播函数。
Args:
token_embedding (Tensor): 输入的词嵌入张量
Returns:
Tensor: 加上位置编码后的张量
"""
return self.dropout(token_embedding + self.pos_embedding[:token_embedding.size(0), :])
class TokenEmbedding(nn.Module):
def __init__(self, vocab_size: int, emb_size):
"""
初始化词嵌入层。
Args:
vocab_size (int): 词汇表大小
emb_size (int): 词嵌入的维度大小
"""
super(TokenEmbedding, self).__init__()
# 定义词嵌入层
self.embedding = nn.Embedding(vocab_size, emb_size)
self.emb_size = emb_size
def forward(self, tokens: Tensor):
"""
前向传播函数。
Args:
tokens (Tensor): 输入的词索引张量
Returns:
Tensor: 词嵌入张量乘以math.sqrt(self.emb_size)后的结果
"""
return self.embedding(tokens.long()) * math.sqrt(self.emb_size)
创建一个后续词掩码,以阻止目标词关注其后续的词。同时,我们也创建掩码来屏蔽源语言和目标语言中的填充标记。
def generate_square_subsequent_mask(sz):
"""
生成Transformer解码器用的序列位置mask。
Args:
sz (int): 序列长度
Returns:
Tensor: 方形上三角形mask张量
"""
mask = (torch.triu(torch.ones((sz, sz), device=device)) == 1).transpose(0, 1)
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
return mask
def create_mask(src, tgt):
"""
创建输入序列和目标序列的mask。
Args:
src (Tensor): 输入序列张量
tgt (Tensor): 目标序列张量
Returns:
tuple: 包含输入序列和目标序列mask及填充mask的元组
"""
src_seq_len = src.shape[0]
tgt_seq_len = tgt.shape[0]
tgt_mask = generate_square_subsequent_mask(tgt_seq_len)
src_mask = torch.zeros((src_seq_len, src_seq_len), device=device).type(torch.bool)
src_padding_mask = (src == PAD_IDX).transpose(0, 1)
tgt_padding_mask = (tgt == PAD_IDX).transpose(0, 1)
return src_mask, tgt_mask, src_padding_mask, tgt_padding_mask
最终,在准备好必要的类和函数之后,我们准备开始训练我们的模型。毫无疑问,完成训练所需的时间会因计算能力、参数和数据集大小等因素而大不相同。
训练过程:
结果:
模型保存:
总结:
本次实验旨在建立基于Transformer架构的中日机器翻译系统。项目流程包括数据预处理、模型构建与训练,以及最终的性能评估。
在数据准备阶段,我们精细处理了中日双语语料,包括分词和构建词汇表,以便将文本转换为模型可理解的张量形式。
在模型训练过程中,我们基于PyTorch框架实现了Transformer模型,并通过一系列实验调整了超参数。这些调整包括增加模型深度、提升多头注意力机制的复杂性,以及增强词嵌入的维度,从而显著提升了翻译性能。
评估结果显示,该中日机器翻译模型在处理复杂和长句子时表现出色。然而,在处理简单句子时偶尔出现误差。这可能是因为训练数据集中简单句子的样本较少,导致模型在这方面的学习能力有所限制。