实现日中机器翻译模型---结合Transformer和PyTorch

一、总体介绍

实现日中机器翻译模型,结合Transformer和PyTorch,下面是一般关键步骤和技术:

1. 数据准备
首先,需要准备大量的日语和中文平行语料作为训练数据。这些数据应该是句子级别的对应,即每个日语句子对应一个对应的中文翻译句子。可以使用公开可用的语料库,如TED演讲语料库、开放翻译数据等。

2. 构建Transformer模型
Transformer是一种强大的深度学习模型,特别适合处理序列到序列的任务,如机器翻译。在PyTorch中,可以使用`torch.nn.Transformer`模块来构建Transformer模型。它包含了多个注意力头(attention heads)和位置编码,能够有效地捕捉长距离依赖关系。

3. 数据预处理
在将数据输入到Transformer之前,需要对数据进行预处理:
- 使用分词器(tokenizer)将日语和中文文本转换为模型可接受的输入表示。可以选择使用分词库如`nltk`、`jieba`等来进行分词。
- 将每个句子转换为对应的索引序列,并添加起始(<sos>)和结束(<eos>)标记,以便模型了解句子的起始和终止。

4. 构建数据加载器(DataLoader)
使用PyTorch的`DataLoader`来加载和批处理预处理后的数据。这是训练过程中的一个关键步骤,能够有效地管理和处理大规模数据集。

5. 定义Transformer模型
在PyTorch中定义一个自定义的Transformer模型,包括编码器(Encoder)和解码器(Decoder),以及注意力机制(Attention)和前向传播逻辑。

 6. 损失函数和优化器
选择适当的损失函数,如交叉熵损失(CrossEntropyLoss),并选择优化器,如Adam优化器,来调整模型参数以最小化损失函数。

7. 训练模型
利用准备好的训练数据和定义好的模型,通过多轮训练来优化模型参数。在每轮训练中,通过前向传播计算损失,然后反向传播更新模型参数。

8. 评估和推理
在训练过程中,定期使用验证集评估模型的性能。训练完成后,可以使用测试集来评估模型的泛化能力和翻译质量。

9. 部署和应用
完成训练和评估后,可以将模型部署为一个服务或应用程序,接受用户输入的日语文本并输出对应的中文翻译。

不过要确保在整个过程中保持数据的质量和一致性,特别是在数据清洗和预处理阶段。调整模型超参数(如学习率、批量大小等)以优化模型的性能。可以尝试使用预训练的Transformer模型(如BERT、GPT等)进行迁移学习,以提升模型的表现。

二、具体描述

1、导入必要库

import math
import torchtext
import torch
import torch.nn as nn
from torch import Tensor
from torch.nn.utils.rnn import pad_sequence
from torch.utils.data import DataLoader
from collections import Counter
from torchtext.vocab import Vocab
from torch.nn import TransformerEncoder, TransformerDecoder, TransformerEncoderLayer, TransformerDecoderLayer
import io
import time
import pandas as pd
import numpy as np
import pickle
import tqdm
import sentencepiece as spm
torch.manual_seed(0)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# print(torch.cuda.get_device_name(0)) 

2、获取并行数据集

在本教程中,我们将使用从JParaCrawl下载的日英并行数据集![http://www.kecl.ntt.co.jp/icl/lirg/jparacrawl]该语料库被描述为“由NTT创建的最大的公开可用的英语-日语平行语料库”。它是通过大量抓取网络并自动对齐平行句子而创建的。”

在导入了所有的日语和英语数据后,我删除了数据集中的最后一个数据,因为它缺少一个值。总的来说,trainen和trainja中的句子数量为5,973,071,但是,出于学习目的,通常建议在一次使用所有数据之前对数据进行采样,并确保一切按预期运行,以节省时间。

# 使用加载的英文SentencePiece模型对句子进行分词,输入相应句子,参数 out_type='str' 指定输出的分词结果为字符串格式
en_tokenizer.encode("All residents aged 20 to 59 years who live in Japan must enroll in public pension system.", out_type='str')

3、准备记号化器

与英语或其他字母语言不同,日语句子不包含空格来分隔单词。我们可以使用JParaCrawl提供的标记器,它是使用SentencePiece为日语和英语创建的,您可以访问JParaCrawl网站下载它们,或者单击此处。

# 加载英文的SentencePiece模型,用于将英文句子进行分词
en_tokenizer = spm.SentencePieceProcessor(model_file='enja_spm_models/spm.en.nopretok.model')
# 加载日文的SentencePiece模型,用于将日文句子进行分词
ja_tokenizer = spm.SentencePieceProcessor(model_file='enja_spm_models/spm.ja.nopretok.model')

加载完标记器后,您可以测试它们,例如,通过执行下面的代码。

# 使用加载的英文SentencePiece模型对句子进行分词,输入相应句子,参数 out_type='str' 指定输出的分词结果为字符串格式
en_tokenizer.encode("All residents aged 20 to 59 years who live in Japan must enroll in public pension system.", out_type='str')

ja_tokenizer.encode("年金 日本に住んでいる20歳~60歳の全ての人は、公的年金制度に加入しなければなりません。", out_type='str')

4、构建TorchText Vocab对象并将句子转换为Torch tensors

使用标记器和原始句子,我们然后构建从TorchText导入的Vocab对象。这个过程可能需要几秒或几分钟,具体取决于数据集的大小和计算能力。不同的分词器也会影响构建vocab所需的时间,我尝试了几种其他的日语分词器,但SentencePiece对我来说似乎工作得很好,也足够快。

# 定义一个函数,用于根据给定的句子列表和分词器构建词汇表
def build_vocab(sentences, tokenizer):
    counter = Counter()  # 创建一个Counter对象来统计词频
    for sentence in sentences:
        # 使用分词器对句子进行分词,并更新Counter对象
        counter.update(tokenizer.encode(sentence, out_type='str'))
    # 创建并返回一个Vocab对象,包含特殊符号<unk>(未知词)、<pad>(填充符)、<bos>(句子开始)和<eos>(句子结束)
    return Vocab(counter, specials=['<unk>', '<pad>', '<bos>', '<eos>'])

# 使用训练集中的日文句子列表和日文分词器构建日文词汇表
ja_vocab = build_vocab(trainja, ja_tokenizer)

# 使用训练集中的英文句子列表和英文分词器构建英文词汇表
en_vocab = build_vocab(trainen, en_tokenizer)

有了词汇对象之后,我们就可以使用vocab和tokenizer对象来为我们的训练数据构建张量。

def data_process(ja, en):
    # 初始化一个空列表用于存储处理后的数据
    data = []
    
    # 使用 zip 函数将日语和英语句子配对,并逐对进行处理
    for (raw_ja, raw_en) in zip(ja, en):
        # 对每个日语句子进行分词,去除末尾换行符后将其编码为字符串 token 列表
        ja_tokens = ja_tokenizer.encode(raw_ja.rstrip("\n"), out_type=str)
        
        # 将每个 token 转换为其在词汇表中的索引,并生成张量
        ja_tensor_ = torch.tensor([ja_vocab[token] for token in ja_tokens], dtype=torch.long)
        
        # 对每个英语句子进行相同的处理
        en_tokens = en_tokenizer.encode(raw_en.rstrip("\n"), out_type=str)
        en_tensor_ = torch.tensor([en_vocab[token] for token in en_tokens], dtype=torch.long)
        
        # 将处理后的日语和英语张量作为元组添加到数据列表中
        data.append((ja_tensor_, en_tensor_))
    
    # 返回处理后的数据列表
    return data

# 将训练数据中的日语和英语句子进行处理,得到训练数据列表
train_data = data_process(trainja, trainen)

5、创建要在训练期间迭代的DataLoader对象

在这里,我将BATCH_SIZE设置为16,以防止“cuda内存不足”,但这取决于各种因素,如您的机器内存容量、数据大小等。,所以可以根据需要随意更改批量大小(注意:PyTorch的教程使用Multi30k德语-英语数据集将批量大小设置为128。)

# 定义批处理大小
BATCH_SIZE = 8

# 获取特殊标记在词汇表中的索引
PAD_IDX = ja_vocab['<pad>']  # 填充符号的索引
BOS_IDX = ja_vocab['<bos>']  # 句子开始符号的索引
EOS_IDX = ja_vocab['<eos>']  # 句子结束符号的索引

def generate_batch(data_batch):
    #生成批处理数据
   
    ja_batch, en_batch = [], []  # 初始化存储批处理数据的列表
    
    # 遍历数据批次中的每一对日语和英语张量
    for (ja_item, en_item) in data_batch:
        # 在每个日语张量前后分别添加 <bos> 和 <eos> 标记
        ja_batch.append(torch.cat([torch.tensor([BOS_IDX]), ja_item, torch.tensor([EOS_IDX])], dim=0))
        # 在每个英语张量前后分别添加 <bos> 和 <eos> 标记
        en_batch.append(torch.cat([torch.tensor([BOS_IDX]), en_item, torch.tensor([EOS_IDX])], dim=0))
    
    # 使用 pad_sequence 函数对批处理数据进行填充,使它们具有相同的长度
    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)

6、序列间变压器

接下来的几个代码和文本解释(用斜体书写)摘自原始PyTorch教程[https://py torch . org/tutorials/初学者/translation_transformer.html]。除了BATCH_SIZE和单词de _ vocab(改为ja_vocab)之外,我没有做任何更改。

Transformer是一个Seq2Seq模型,在“注意力是你所需要的”论文中介绍,用于解决机器翻译任务。转换器模型由编码器和解码器模块组成,每个模块包含固定数量的层。

编码器通过一系列多头注意力和前馈网络层传播输入序列来处理输入序列。来自编码器的输出被称为存储器,与目标张量一起被馈送到解码器。使用教师强制技术以端到端的方式训练编码器和解码器。

# 导入 Transformer 模型相关的模块
from torch.nn import (TransformerEncoder, TransformerDecoder,
                      TransformerEncoderLayer, TransformerDecoderLayer)


        """
        :param num_encoder_layers: 编码器层的数量
        :param num_decoder_layers: 解码器层的数量
        :param emb_size: 嵌入层的维度
        :param src_vocab_size: 源语言词汇表大小
        :param tgt_vocab_size: 目标语言词汇表大小
        :param dim_feedforward: 前馈网络的维度
        :param dropout: Dropout 概率
        """
    
#初始化 Seq2Seq Transformer 模型
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):
        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):
        
        # 对源语言输入进行嵌入和位置编码
        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):
        """
        编码函数,将源语言序列转换为编码器输出。
        :param src: 源语言输入张量 (batch_size, src_sequence_length)
        :param src_mask: 源语言序列的掩码
        :return: 编码器输出张量
        """
        return self.transformer_encoder(self.positional_encoding(
                            self.src_tok_emb(src)), src_mask)

    def decode(self, tgt: Tensor, memory: Tensor, tgt_mask: Tensor):
         """
        解码函数,将目标语言序列和编码器输出结合,生成解码器输出。
        :param tgt: 目标语言输入张量 (batch_size, tgt_sequence_length)
        :param memory: 编码器输出张量
        :param tgt_mask: 目标语言序列的掩码
        :return: 解码器输出张量
        """
        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):
        """
        初始化位置编码层。

        :param emb_size: 嵌入维度大小
        :param dropout: Dropout 概率
        :param maxlen: 最大序列长度,默认值为 5000
        """
        super(PositionalEncoding, self).__init__()

        # 计算位置编码公式中的分母部分
        den = torch.exp(-torch.arange(0, emb_size, 2) * math.log(10000) / emb_size)
        
        # 创建一个形状为 (maxlen, 1) 的位置索引张量
        pos = torch.arange(0, maxlen).reshape(maxlen, 1)
        
        # 初始化位置编码张量,形状为 (maxlen, emb_size)
        pos_embedding = torch.zeros((maxlen, emb_size))
        
        # 按照公式计算位置编码的奇数和偶数位置
        pos_embedding[:, 0::2] = torch.sin(pos * den)  # 每隔一列计算一次 sin
        pos_embedding[:, 1::2] = torch.cos(pos * den)  # 每隔一列计算一次 cos
        
        # 在最后一维增加一个维度,以便与输入的嵌入相加时广播运算
        pos_embedding = pos_embedding.unsqueeze(-2)

        # 定义 Dropout 层
        self.dropout = nn.Dropout(dropout)
        
        # 注册位置编码张量为模型的 buffer,使其在模型保存和加载时被包含在内
        self.register_buffer('pos_embedding', pos_embedding)

    def forward(self, token_embedding: Tensor):
        """
        前向传播函数,将位置编码添加到输入的嵌入上,并应用 Dropout。

        :param token_embedding: 输入的嵌入张量,形状为 (seq_len, batch_size, emb_size)
        :return: 添加了位置编码并应用了 Dropout 的嵌入张量
        """
        # 将位置编码与输入的嵌入相加,并应用 Dropout
        return self.dropout(token_embedding +
                            self.pos_embedding[:token_embedding.size(0), :])

class TokenEmbedding(nn.Module):
    def __init__(self, vocab_size: int, emb_size):
        """
        初始化词嵌入层。

        :param vocab_size: 词汇表大小
        :param emb_size: 嵌入维度大小
        """
        super(TokenEmbedding, self).__init__()
        
        # 定义嵌入层
        self.embedding = nn.Embedding(vocab_size, emb_size)
        
        # 保存嵌入维度大小
        self.emb_size = emb_size

    def forward(self, tokens: Tensor):
        """
        前向传播函数,将输入的 tokens 转换为嵌入表示,并缩放嵌入向量。

        :param tokens: 输入的 tokens 张量,形状为 (batch_size, seq_len)
        :return: 缩放后的嵌入张量,形状为 (batch_size, seq_len, emb_size)
        """
        # 获取嵌入表示并进行缩放,缩放因子为嵌入维度的平方根
        return self.embedding(tokens.long()) * math.sqrt(self.emb_size)

我们创建一个后续单词掩码来阻止目标单词关注它的后续单词。我们还创建了屏蔽,用于屏蔽源和目标填充标记

def generate_square_subsequent_mask(sz):
    """
    生成一个大小为 (sz, sz) 的上三角矩阵掩码,用于 Transformer解码器中的自注意力机制。
    
    :param sz: 掩码的尺寸
    :return: 一个 (sz, sz) 的张量,表示上三角掩码矩阵
    """
    # 创建一个上三角矩阵,其中包含 1 的位置为可见部分,0 的位置为不可见部分
    mask = (torch.triu(torch.ones((sz, sz), device=device)) == 1).transpose(0, 1)
    # 将掩码转换为浮点数,并且将 0 的位置填充为负无穷大,将 1 的位置填充为 0.0
    mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
    return mask

def create_mask(src, tgt):
    """
    为源序列和目标序列创建掩码,用于 Transformer 模型。
    
    :param src: 源序列张量,形状为 (src_seq_len, batch_size)
    :param tgt: 目标序列张量,形状为 (tgt_seq_len, batch_size)
    :return: 四个张量,分别是源序列掩码、目标序列掩码、源序列填充掩码和目标序列填充掩码
    """
    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

定义模型参数并实例化模型。这里我们服务器实在是计算能力有限,按照以下配置可以训练但是效果应该是不行的。如果想要看到训练的效果请使用你自己的带国家政治保卫局。参见OGPU的电脑运行这一套代码。

当你使用自己的国家政治保卫局。参见OGPU的时候,数量_编码器_层数和数量_解码器_层数设置为3或者更高,NHEAD设置8、EMB _大小设置为512。

# 定义源语言和目标语言的词汇表大小
SRC_VOCAB_SIZE = len(ja_vocab)  # 源语言(例如日语)词汇表的大小
TGT_VOCAB_SIZE = len(en_vocab)  # 目标语言(例如英语)词汇表的大小

# 设置嵌入向量的维度
EMB_SIZE = 512  # 嵌入层的维度

# 设置多头注意力机制中的头的数量
NHEAD = 8  # 多头注意力机制的头的数量

# 设置前馈神经网络隐藏层的维度
FFN_HID_DIM = 512  # 前馈神经网络隐藏层的维度

# 设置训练的批处理大小
BATCH_SIZE = 16  # 每个批次的数据量

# 设置编码器和解码器的层数
NUM_ENCODER_LAYERS = 3  # 编码器的层数
NUM_DECODER_LAYERS = 3  # 解码器的层数

# 设置训练的轮数
NUM_EPOCHS = 16  # 训练的轮数

# 初始化Seq2SeqTransformer模型
transformer = Seq2SeqTransformer(NUM_ENCODER_LAYERS, NUM_DECODER_LAYERS, EMB_SIZE, 
                                 SRC_VOCAB_SIZE, TGT_VOCAB_SIZE, FFN_HID_DIM)

# 对模型参数进行Xavier初始化
for p in transformer.parameters():
    if p.dim() > 1:
        nn.init.xavier_uniform_(p)

# 将模型移动到指定设备(如GPU)上
transformer = transformer.to(device)

# 定义损失函数,使用交叉熵损失,并忽略填充索引
loss_fn = torch.nn.CrossEntropyLoss(ignore_index=PAD_IDX)

# 定义优化器,使用Adam优化器
optimizer = torch.optim.Adam(
    transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9
)

# 定义训练一个epoch的函数
def train_epoch(model, train_iter, optimizer):
    model.train()  # 设置模型为训练模式
    losses = 0  # 初始化损失
    for idx, (src, tgt) in enumerate(train_iter):
        src = src.to(device)  # 将源数据移动到设备上
        tgt = tgt.to(device)  # 将目标数据移动到设备上

        tgt_input = tgt[:-1, :]  # 去掉目标序列的最后一个token作为输入

        # 创建掩码
        src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)

        # 前向传播
        logits = model(src, tgt_input, src_mask, tgt_mask,
                       src_padding_mask, tgt_padding_mask, src_padding_mask)

        optimizer.zero_grad()  # 清空梯度

        tgt_out = tgt[1:, :]  # 去掉目标序列的第一个token作为输出
        loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))  # 计算损失
        loss.backward()  # 反向传播计算梯度

        optimizer.step()  # 更新参数
        losses += loss.item()  # 累加损失
    return losses / len(train_iter)  # 返回平均损失

# 定义评估模型的函数
def evaluate(model, val_iter):
    model.eval()  # 设置模型为评估模式
    losses = 0  # 初始化损失
    for idx, (src, tgt) in enumerate(val_iter):
        src = src.to(device)  # 将源数据移动到设备上
        tgt = tgt.to(device)  # 将目标数据移动到设备上

        tgt_input = tgt[:-1, :]  # 去掉目标序列的最后一个token作为输入

        # 创建掩码
        src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)

        # 前向传播
        logits = model(src, tgt_input, src_mask, tgt_mask,
                       src_padding_mask, tgt_padding_mask, src_padding_mask)
                       
        tgt_out = tgt[1:, :]  # 去掉目标序列的第一个token作为输出
        loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))  # 计算损失
        losses += loss.item()  # 累加损失
    return losses / len(val_iter)  # 返回平均损失

7、开始训练

最后,在准备好必要的类和函数之后,我们准备好训练我们的模型。这是不言而喻的,但完成训练所需的时间可能会因计算能力、参数和数据集大小等诸多因素而有很大差异。

# 进行多个训练周期
for epoch in tqdm.tqdm(range(1, NUM_EPOCHS + 1)):
    # 记录当前时间,作为该训练周期的开始时间
    start_time = time.time()
    
    # 调用train_epoch函数进行一个训练周期,并计算训练损失
    # 参数transformer是模型,train_iter是训练数据集的迭代器,optimizer是优化器
    train_loss = train_epoch(transformer, train_iter, optimizer)
    
    # 记录当前时间,作为该训练周期的结束时间
    end_time = time.time()
    
    # 打印当前训练周期的编号、训练损失和训练周期所花费的时间
    print((f"Epoch: {epoch}, Train loss: {train_loss:.3f}, "
           f"Epoch time = {(end_time - start_time):.3f}s"))

8、尝试使用训练好的模型翻译一个日语句子

首先,我们创建翻译一个新句子的函数,包括获取日语句子、标记、转换为张量、推理,然后将结果解码为句子等步骤,但这次是用英语。
 

def greedy_decode(model, src, src_mask, max_len, start_symbol):
    """
    使用贪婪解码算法生成源序列的翻译

    model (torch.nn.Module): 用于翻译的Transformer模型。
    src (torch.Tensor): 形状为(seq_len, 1)的源序列张量。
    src_mask (torch.Tensor): 形状为(seq_len, seq_len)的源掩码张量。
    max_len (int): 输出序列的最大长度。
    start_symbol (int): 目标词汇表中起始符号的索引。
    ys (torch.Tensor): 形状为(output_seq_len,)的张量,包含解码后的目标序列。
    """

    src = src.to(device)  # 将源张量移动到设备上(GPU或CPU)
    src_mask = src_mask.to(device)  # 将源掩码张量移动到设备上
    memory = model.encode(src, src_mask)  # 编码源序列
    ys = torch.ones(1, 1).fill_(start_symbol).type(torch.long).to(device)  # 以起始符号初始化输出序列

    for i in range(max_len-1):
        memory = memory.to(device)  # 将记忆张量移动到设备上
        memory_mask = torch.zeros(ys.shape[0], memory.shape[0]).to(device).type(torch.bool)  # 创建记忆掩码
        tgt_mask = (generate_square_subsequent_mask(ys.size(0))
                    .type(torch.bool)).to(device)  # 创建目标掩码以进行自回归解码
        out = model.decode(ys, memory, tgt_mask)  # 使用Transformer模型解码
        out = out.transpose(0, 1)  # 转置输出以进行进一步处理
        prob = model.generator(out[:, -1])  # 生成词汇表上的概率分布
        _, next_word = torch.max(prob, dim=1)  # 获取概率最大的词的索引
        next_word = next_word.item()  # 获取下一个词的整数索引
        ys = torch.cat([ys,
                        torch.ones(1, 1).type_as(src.data).fill_(next_word)], dim=0)  # 将下一个词追加到输出序列
        if next_word == EOS_IDX:  # 检查是否生成了序列结束标记
            break  # 如果生成了EOS标记,则停止解码

    return ys  # 返回解码后的目标序列张量

def translate(model, src, src_vocab, tgt_vocab, src_tokenizer):
    """
    使用提供的模型和词汇表将源句子翻译成目标句子。

    model (torch.nn.Module): 用于翻译的Transformer模型。
    src (str): 要翻译的源句子。
    src_vocab (torchtext.vocab.Vocab): 源语言词汇表。
    tgt_vocab (torchtext.vocab.Vocab): 目标语言词汇表。
    src_tokenizer (SentencePieceProcessor): 源语言的分词器。
    str: 目标语言中的翻译句子。
    """

    model.eval()  # 设置模型为评估模式
    tokens = [BOS_IDX] + [src_vocab.stoi[tok] for tok in src_tokenizer.encode(src, out_type=str)] + [EOS_IDX]  # 对源句子进行分词
    num_tokens = len(tokens)  # 源序列中的标记数
    src = (torch.LongTensor(tokens).reshape(num_tokens, 1))  # 将标记转换为张量并重塑
    src_mask = (torch.zeros(num_tokens, num_tokens)).type(torch.bool)  # 创建源掩码
    tgt_tokens = greedy_decode(model, src, src_mask, max_len=num_tokens + 5, start_symbol=BOS_IDX).flatten()  # 生成目标标记
    return " ".join([tgt_vocab.itos[tok] for tok in tgt_tokens]).replace("<bos>", "").replace("<eos>", "")# 将目标标记转换为字符串并返回
translate(transformer, "HSコード 8515 はんだ付け用、ろう付け用又は溶接用の機器(電気式(電気加熱ガス式を含む。)", ja_vocab, en_vocab, ja_tokenizer)
trainen.pop(5)

9、保存Vocab对象和训练好的模型

训练结束后,我们将首先使用Pickle保存Vocab对象(en_vocab和ja_vocab)。

import pickle
# open a file, where you want to store the data
file = open('en_vocab.pkl', 'wb')
# dump information to that file
pickle.dump(en_vocab, file)
file.close()
file = open('ja_vocab.pkl', 'wb')
pickle.dump(ja_vocab, file)
file.close()

我们还可以使用PyTorch保存和加载函数保存模型以备后用。一般来说,有两种保存模型的方法,这取决于我们以后想用它们做什么。第一个只是为了推断,我们可以稍后加载模型,并用它来从日语翻译成英语。

# save model for inference
torch.save(transformer.state_dict(), 'inference_model')

第二个也是为了推理,但也是为了我们以后想加载模型,想恢复训练的时候。

# save model + checkpoint to resume training later
torch.save({
  'epoch': NUM_EPOCHS,
  'model_state_dict': transformer.state_dict(),
  'optimizer_state_dict': optimizer.state_dict(),
  'loss': train_loss,
  }, 'model_checkpoint.tar')

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值