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

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

1. 引言

在现代社会中,随着全球化的不断深入,跨语言交流变得越来越重要。无论是国际商务、学术研究还是文化交流,机器翻译技术都扮演着不可或缺的角色。传统的机器翻译方法,如基于规则的翻译和统计机器翻译,虽然在一定程度上解决了语言障碍问题,但它们的局限性也日益显现。近年来,深度学习的兴起为机器翻译带来了新的突破,其中最具代表性的便是Transformer模型。
在这里插入图片描述

Transformer模型是Google在2017年的"Attention is All You Need"这篇论文中首次提出的,这也是第一次在翻译模型中全面使用注意力机制而不再使用循环神经网络(RNN)的尝试。此后,我们可以看到众多基于Transformer的改进模型,比如受到广泛使用的BERT, GPT以及T5等。它凭借其强大的自注意力机制和并行计算能力,迅速在自然语言处理(NLP)领域中崭露头角,尤其在机器翻译任务中表现出色。与传统的循环神经网络(RNN)和卷积神经网络(CNN)相比,Transformer不仅在处理长距离依赖关系上更为高效,而且在训练速度和翻译质量上也具有显著优势。

在本篇博客中,我们将探讨Transformer模型的核心原理,展示如何使用Transformer实现一个日语到中文的机器翻译系统,并通过具体的代码示例和实验结果,帮助读者理解并应用这一强大的模型。

2. 原理

2.1 自注意力机制(Self-attention Mechanism)

自注意力机制允许模型在处理一个序列时,考虑到序列中每个元素与其他所有元素的关系。这种机制通过计算序列中每个元素与其他元素的关联度(或称为权重),帮助模型更好地理解序列中的上下文信息,从而更准确地处理序列数据。在自注意力机制中,模型会计算序列中每个元素与其他所有元素的关联度,这些权重反映了元素之间的相互关系。
Transformer通过自注意力机制来捕获文本序列内部的依赖关系。它使得模型能够关注到序列中的每一个位置,并根据实际需要决定各个位置的重要性。如此一来,它就能捕获到那些超出固定距离范围的长距离依赖关系,而且并不增加计算成本。
自注意力机制

2.2 编码器和解码器(Encoder & Decoder)

Transformer是编码器-解码器结构。编码器由N个完全相同的层堆叠而成,每一层都有两个子层:自注意力机制和前馈神经网络。解码器也有N个完全相同的层,但是它比编码器多一个子层,用于处理编码器的输出。
在这里插入图片描述

2.2.1 编码器

编码器是一个循环神经网络,通常使用LSTM或GRU,负责将一个不定长的输入序列变换成一个定长的语义向量。
在这里插入图片描述

2.2.2 解码器

解码器也是一个循环神经网络,负责根据语义向量生成指定的序列,这个过程称为解码,解码的过程有两种不同的
结构,其不同点在于语义向量是否应用于每一时间步的输出。
在这里插入图片描述
在这里插入图片描述

2.3 序列到序列seq2seq模型(sequence-to-sequence,seq2seq)

所谓序列到序列模型,就是一种能够根据给定的序列,通过特定的方法生成另一个序列的方法。
序列到序列模型,简单来说就是一个翻译模型,把一个语言序列翻译成另一种语言序列,即将一个序列作为输入映
射为另外一个输出序列。
总体来说,序列到序列任务往往具有以下两个特点:
• 输入、输出长度不确定。例如要构建一个聊天机器人,我们说的话和它的回复长度都是不固定的;
• 输入输出元素之间具有顺序关系。不同的顺序,得到的结果应该是不同的,例如“黄蓉的女儿是谁”和“谁的女
儿是黄蓉”,这两个短语的意思是不同的。

在这里插入图片描述

3. 导入所需的包

首先,确保我们系统中安装了以下包,如果发现有缺失的包,请确保安装。

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)) ## 如果你有GPU,请在你自己的电脑上尝试运行这一套代码
*NVIDIA GeForce RTX 4090*
device
*device(type='cuda')*

4. 获取并行数据集

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

# 读取文件
df = pd.read_csv('zh-ja.bicleaner05.txt', sep='\\t', engine='python', header=None)
trainen = df[2].values.tolist()#[:10000]
trainja = df[3].values.tolist()#[:10000]
# trainen.pop(5972)
# trainja.pop(5972)

在导入所有的日语及其英文对之后,我删除了数据集中的最后一个数据,因为它有缺失值。总共,trainen和trainja中的句子数为5,973,071条,然而,为了学习目的,通常建议对数据进行抽样,并确保一切正常运行,然后再一次性使用所有数据,以节省时间。

以下是数据集中包含的句子示例。

# 打印第500个句子对
print(trainen[500])
print(trainja[500])
*Chinese HS Code Harmonized Code System < HS编码 2905 无环醇及其卤化、磺化、硝化或亚硝化衍生物 HS Code List (Harmonized System Code) for US, UK, EU, China, India, France, Japan, Russia, Germany, Korea, Canada ...
Japanese HS Code Harmonized Code System < HSコード 2905 非環式アルコール並びにそのハロゲン化誘導体、スルホン化誘導体、ニトロ化誘導体及びニトロソ化誘導体 HS Code List (Harmonized System Code) for US, UK, EU, China, India, France, Japan, Russia, Germany, Korea, Canada ...*

我们还可以使用不同的平行数据集来跟随本文,只需确保我们能将数据处理成上述所示的包含日语和英语句子的两个字符串列表。

5. 准备分词器

与英语或其他字母语言不同,日语句子中不包含空格以分隔单词。我们可以使用JParaCrawl提供的基于SentencePiece创建的分词器,用于日语和英语,可以访问JParaCrawl网站下载它们,或者点击这里

# 加载预训练的 SentencePiece 模型,用于英文和日文的分词
en_tokenizer = spm.SentencePieceProcessor(model_file='spm.en.nopretok.model')
ja_tokenizer = spm.SentencePieceProcessor(model_file='spm.ja.nopretok.model')

分词器加载完成后,可以通过执行以下代码来测试它们。

en_tokenizer.encode("All residents aged 20 to 59 years who live in Japan must enroll in public pension system.", out_type='str')

[‘▁All’,
‘▁residents’,
‘▁aged’,
‘▁20’,
‘▁to’,
‘▁59’,
‘▁years’,
‘▁who’,
‘▁live’,
‘▁in’,
‘▁Japan’,
‘▁must’,
‘▁enroll’,
‘▁in’,
‘▁public’,
‘▁pension’,
‘▁system’,
‘.’]

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

[‘▁’,
‘年’,
‘金’,
‘▁日本’,
‘に住んでいる’,
‘20’,
‘歳’,
‘~’,
‘60’,
‘歳の’,
‘全ての’,
‘人は’,
‘、’,
‘公的’,
‘年’,
‘金’,
‘制度’,
‘に’,
‘加入’,
‘しなければなりません’,
‘。’]

6. 构建TorchText的词汇对象并将句子转换为Torch张量

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

def build_vocab(sentences, tokenizer):  # 构建词汇表函数,参数为句子和分词器
  counter = Counter()  # 创建计数器
  for sentence in sentences:
    counter.update(tokenizer.encode(sentence, out_type=str))  # 使用分词器对句子进行编码,并更新计数器
  return Vocab(counter, specials=['<unk>', '<pad>', '<bos>', '<eos>'])
# 为日文和英文句子构建词汇表
ja_vocab = build_vocab(trainja, ja_tokenizer)
en_vocab = build_vocab(trainen, en_tokenizer)

获得词汇对象后,我们可以使用词汇表和分词器对象为训练数据构建张量。

def data_process(ja, en):  # 数据处理函数,将句子转换为张量
  data = []
  for (raw_ja, raw_en) in zip(ja, en):  
    ja_tensor_ = torch.tensor([ja_vocab[token] for token in ja_tokenizer.encode(raw_ja.rstrip("\n"), out_type=str)],
                            dtype=torch.long)  # 将日文句子编码为张量
    en_tensor_ = torch.tensor([en_vocab[token] for token in en_tokenizer.encode(raw_en.rstrip("\n"), out_type=str)],
                            dtype=torch.long)  # 将英文句子编码为张量
    data.append((ja_tensor_, en_tensor_))  # 将处理后的张量添加到数据列表中
  return data
train_data = data_process(trainja, trainen)

7. 创建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:
    # 在每个句子前后添加起始和结束标记,并将其连接成一个张量
    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
train_iter = DataLoader(train_data, batch_size=BATCH_SIZE,
                        shuffle=True, collate_fn=generate_batch)

8. 序列到序列Transformer

接下来的几行代码和文本解释来自原始的PyTorch教程[https://pytorch.org/tutorials/beginner/translation_transformer.html]。除了BATCH_SIZE和词汇表de_vocab被更改为ja_vocab之外,我没有进行任何更改。

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):
        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):
        # 将源输入转换为嵌入并添加位置编码,然后通过编码器处理
        return self.transformer_encoder(self.positional_encoding(
                            self.src_tok_emb(src)), src_mask)

    def decode(self, tgt: Tensor, memory: Tensor, tgt_mask: Tensor):
        # 将目标输入转换为嵌入并添加位置编码,然后通过解码器处理
        return self.transformer_decoder(self.positional_encoding(
                          self.tgt_tok_emb(tgt)), memory,
                          tgt_mask)

文本标记通过使用标记嵌入来表示。位置编码被添加到标记嵌入中,以引入单词顺序的概念。

class PositionalEncoding(nn.Module): 
    # 初始化函数,传入嵌入维度,dropout率,最大长度
    def __init__(self, emb_size: int, 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)  # 在最后一个维度上增加一维

        self.dropout = nn.Dropout(dropout)  # 定义dropout层
        self.register_buffer('pos_embedding', pos_embedding)  # 将位置编码矩阵注册为缓冲区

    def forward(self, token_embedding: Tensor):  # 前向传播函数
        return self.dropout(token_embedding +  
                            self.pos_embedding[:token_embedding.size(0),:])  # 将位置编码加到token嵌入上

class TokenEmbedding(nn.Module): 
    # 初始化函数,传入词汇表大小和嵌入维度
    def __init__(self, vocab_size: int, emb_size):  
        super(TokenEmbedding, self).__init__()  # 调用父类的初始化方法
        self.embedding = nn.Embedding(vocab_size, emb_size)  # 定义嵌入层
        self.emb_size = emb_size  # 保存嵌入维度
     # 前向传播函数

## 翻译 private_upload\default_user\2024-06-26-13-48-35\jp_cn_translation_zs.md.part-2.md

```python
def generate_square_subsequent_mask(sz): 
    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):  
  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  

定义模型参数并实例化模型。这里根据自己使用机器的计算能力配置训练。

当您使用自己的GPU时,NUM_ENCODER_LAYERS和NUM_DECODER_LAYERS设置为3或更高,NHEAD设置为8,EMB_SIZE设置为512。

SRC_VOCAB_SIZE = len(ja_vocab)  # 源语言词汇表的大小
TGT_VOCAB_SIZE = len(en_vocab)  # 目标语言词汇表的大小
EMB_SIZE = 512  # 嵌入维度
NHEAD = 8  # 多头注意力机制的头数
FFN_HID_DIM = 512  # 前馈神经网络的隐藏层维度
BATCH_SIZE = 512  # 批处理大小
NUM_ENCODER_LAYERS = 3  # 编码器层数
NUM_DECODER_LAYERS = 3  # 解码器层数
NUM_EPOCHS = 16  # 训练的轮数

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

for p in transformer.parameters():  
    if p.dim() > 1:  
        nn.init.xavier_uniform_(p)  # 使用Xavier均匀分布进行初始化

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
)

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, :]  # 去掉目标序列的最后一个元素,作为输入

      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:,:]  # 目标输出为目标序列去掉第一个元素
      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(valid_iter)):  # 遍历验证数据集
    src = src.to(device)  
    tgt = tgt.to(device)  

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

    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:,:]  # 目标输出为目标序列去掉第一个元素
    loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))  # 计算损失
    losses += loss.item()  # 累加损失
  return losses / len(val_iter) 
*/opt/conda/lib/python3.11/site-packages/torch/nn/modules/transformer.py:282: UserWarning: enable_nested_tensor is True, but self.use_nested_tensor is False because encoder_layer.self_attn.batch_first was not True(use batch_first for better inference performance)
  warnings.warn(f"enable_nested_tensor is True, but self.use_nested_tensor is False because {why_not_sparsity_fast_path}")*

9. 开始训练

最后,在准备好必要的类和函数之后,我们准备训练我们的模型。毫无疑问,完成训练所需的时间可能会因计算能力、参数和数据集大小等因素而大大变化。

当我使用JParaCrawl的完整句子列表(每种语言约590万句)训练模型时,使用单个NVIDIA GeForce RTX 4090 GPU大约需要1小时多。

以下是代码:

for epoch in tqdm.tqdm(range(1, NUM_EPOCHS+1)):
  start_time = time.time()
  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"))
  6%|▋         | 1/16 [04:04<1:01:05, 244.39s/it]

Epoch: 1, Train loss: 4.548, Epoch time = 244.387s


 12%|█▎        | 2/16 [08:11<57:27, 246.26s/it]  

Epoch: 2, Train loss: 3.623, Epoch time = 247.566s


 19%|█▉        | 3/16 [12:17<53:17, 245.94s/it]

Epoch: 3, Train loss: 3.331, Epoch time = 245.559s


 25%|██▌       | 4/16 [16:20<48:55, 244.65s/it]

Epoch: 4, Train loss: 3.147, Epoch time = 242.674s


 31%|███▏      | 5/16 [20:25<44:54, 244.96s/it]

Epoch: 5, Train loss: 3.016, Epoch time = 245.522s


 38%|███▊      | 6/16 [24:31<40:50, 245.08s/it]

Epoch: 6, Train loss: 2.910, Epoch time = 245.310s


 44%|████▍     | 7/16 [28:37<36:49, 245.48s/it]

Epoch: 7, Train loss: 2.832, Epoch time = 246.292s


 50%|█████     | 8/16 [32:44<32:47, 245.93s/it

10. 尝试使用训练模型翻译一条日语句子

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

for epoch in tqdm.tqdm(range(1, NUM_EPOCHS+1)): 
  start_time = time.time()  # 记录开始时间
  train_loss = train_epoch(transformer, train_iter, optimizer)  # 训练一个周期并计算训练损失
  end_time = time.time()  # 记录结束时间
  print((f"Epoch: {epoch}, 训练损失: {train_loss:.3f}, "  # 打印训练损失和每个周期所用时间
          f"每轮时间 = {(end_time - start_time):.3f}秒"))

def greedy_decode(model, src, src_mask, max_len, start_symbol): 
    src = src.to(device)  
    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)  
        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
    return ys  # 返回生成的目标序列

# 翻译函数
def translate(model, src, src_vocab, tgt_vocab, src_tokenizer):  
    model.eval()  # 将模型设置为评估模式
    tokens = [BOS_IDX] + [src_vocab.stoi[tok] for tok in src_tokenizer.encode(src, out_type=str)]+ [EOS_IDX]  # 生成源序列tokens
    num_tokens = len(tokens)  # 获取tokens长度
    src = (torch.LongTensor(tokens).reshape(num_tokens, 1))  # 将tokens转换为张量并重塑形状
    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 函数,并传递所需的参数。

translate(transformer, "HSコード 8515 はんだ付け用、ろう付け用又は溶接用の機器(電気式(電気加熱ガス式を含む。)", ja_vocab, en_vocab, ja_tokenizer)

‘_H S 用 于 85 15 焊 、焊 接 设 备 、焊 接 电 气 式 ( 包 括 电 气 加 热 ) 。’

trainen.pop(5)

‘Chinese HS Code Harmonized Code System < HS编码 8515 : 电气(包括电热气体)、激光、其他光、光子束、超声波、电子束、磁脉冲或等离子弧焊接机器及装置,不论是否 HS Code List (Harmonized System Code) for US, UK, EU, China, India, France, Japan, Russia, Germany, Korea, Canada …’

trainja.pop(5)

‘Japanese HS Code Harmonized Code System < HSコード 8515 はんだ付け用、ろう付け用又は溶接用の機器(電気式(電気加熱ガス式を含む。)、レーザーその他の光子ビーム式、超音波式、電子ビーム式、 HS Code List (Harmonized System Code) for US, UK, EU, China, India, France, Japan, Russia, Germany, Korea, Canada …’

11. 保存词汇表对象和训练好的模型

最后,在训练完成后,我们将首先使用Pickle保存Vocab对象(en_vocab 和 ja_vocab)。

import pickle  
# 打开一个文件,用于存储数据
file = open('en_vocab.pkl', 'wb')
# 将信息写入文件
pickle.dump(en_vocab, file)
file.close()  # 关闭文件
file = open('ja_vocab.pkl', 'wb')  
pickle.dump(ja_vocab, file)  # 将信息写入文件
file.close()  

最后,我们还可以使用PyTorch的保存和加载功能保存模型以便以后使用。通常,有两种方式保存模型,具体取决于以后我们想要如何使用它们。第一种方式是仅用于推理,以后我们可以加载模型并用它来从日语翻译成英语。

# 保存用于推理的模型
torch.save(transformer.state_dict(), 'inference_model')

第二种方式也是用于推理,但还用于以后加载模型,并在想要恢复训练时使用。

# 保存模型和用于以后恢复训练的检查点
torch.save({
  'epoch': NUM_EPOCHS,
  'model_state_dict': transformer.state_dict(),
  'optimizer_state_dict': optimizer.state_dict(),
  'loss': train_loss,
  }, 'model_checkpoint.tar')

12. 结论

  • 成功实现了一个基于Transformer架构的日中机器翻译模型。
  • 通过预处理、模型训练和评估步骤,模型能够将日语句子翻译成中文。
  • 训练好的模型和词汇表对象已保存,方便后续的使用和进一步的模型改进。
  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于Transformer实现机器翻译是一种先进的方法。在Pytorch,可以使用nn.Transformer实现英文到文的机器翻译任务\[1\]。如果想要深入了解nn.Transformer的使用,可以参考一篇博文《Pytorch nn.Transformer的使用详解与Transformer的黑盒讲解》\[1\]。在这篇博文,作者建议先学习CopyTask任务,然后再学习机器翻译任务,这样会更容易理解。 此外,谷歌翻译也在逐步将转换器编码器引入其翻译算法\[2\]。他们提供了一个即用型翻译界面,可以在谷歌翻译网站上使用\[2\]。另外,瓦斯瓦尼等人在2017年的研究发现,Transformer在WMT 2014英德翻译任务和WMT 2014英法翻译任务上取得了最先进的BLEU分数\[3\]。BLEU是一种用于评估机器翻译质量的指标,具体的评估方法可以在《Evaluating machine translation with BLEU》部分找到\[3\]。 综上所述,基于Transformer机器翻译方法在实践取得了很好的效果,并且在Pytorch有相应的实现。同时,谷歌翻译也在逐步引入转换器编码器,并且Transformer机器翻译任务取得了最先进的结果。 #### 引用[.reference_title] - *1* [Pytorch入门实战(5):基于nn.Transformer实现机器翻译(英译汉)](https://blog.csdn.net/zhaohongfei_358/article/details/126175328)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [【NLP】第6章 使用 Transformer 进行机器翻译](https://blog.csdn.net/sikh_0529/article/details/127037111)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值