实验14.基于Transformer实现机器翻译(日译中)

本次实验在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模型,并通过一系列实验调整了超参数。这些调整包括增加模型深度、提升多头注意力机制的复杂性,以及增强词嵌入的维度,从而显著提升了翻译性能。

评估结果显示,该中日机器翻译模型在处理复杂和长句子时表现出色。然而,在处理简单句子时偶尔出现误差。这可能是因为训练数据集中简单句子的样本较少,导致模型在这方面的学习能力有所限制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值