基于transformer实现机器翻译

目录

1、什么是transformer?

Transformer的基本结构

编码器(Encoder)

解码器(Decoder)

注意力机制

优点

应用

2、模型构建

2.1导入必要的库

2.2获取并行数据集

2.3准备分词器

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

2.5创建用于训练迭代的DataLoader对象

2.6 Sequence-to-sequence Transformer

2.7开始训练

2.8测试

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


1、什么是transformer?

Transformer是一种用于自然语言处理(NLP)任务的深度学习模型架构,由Vaswani等人在2017年提出。它通过完全依赖于注意力机制而非传统的循环神经网络(RNN)或卷积神经网络(CNN),在多个NLP任务上取得了显著的成功。

Transformer的基本结构

Transformer模型的核心由编码器(Encoder)和解码器(Decoder)两部分组成,每个部分由多个相同的层堆叠而成。

编码器(Encoder)

编码器由若干个相同的层(通常是6层)堆叠而成,每一层包括以下两个子层:

  1. 多头自注意力机制(Multi-Head Self-Attention Mechanism)
    • 这种机制允许模型在对每个词进行编码时同时关注序列中的不同位置,捕捉全局信息。
  2. 前馈神经网络(Feed-Forward Neural Network, FFN)
    • 一个简单的全连接网络,应用于每个位置的表示向量。

每个子层都包含残差连接(Residual Connection)和层归一化(Layer Normalization),以确保训练过程的稳定。

解码器(Decoder)

解码器也由若干个相同的层(通常是6层)堆叠而成,每一层包括以下三个子层:

  1. 掩码多头自注意力机制(Masked Multi-Head Self-Attention Mechanism)
    • 防止解码阶段的信息泄露,只允许模型关注之前生成的词。
  2. 编码器-解码器注意力(Encoder-Decoder Attention)
    • 允许解码器在生成每个词时关注编码器输出的所有位置,从而捕捉源序列的信息。
  3. 前馈神经网络(Feed-Forward Neural Network, FFN)
    • 同编码器中的前馈神经网络。

同样,每个子层也包含残差连接和层归一化。

注意力机制

Transformer模型的核心创新是注意力机制,特别是“自注意力”(Self-Attention)和“多头注意力”(Multi-Head Attention)。注意力机制的关键思想是为每个词计算与其他所有词的关联度,使得模型能够灵活地聚集和处理输入序列中的信息。

优点

  • 并行处理:与RNN不同,Transformer不依赖于序列顺序,可以更高效地并行处理输入数据。
  • 长距离依赖:通过自注意力机制,Transformer可以捕捉远距离词汇之间的关系。
  • 可扩展性:Transformer模型易于扩展,适用于大量数据和更复杂的任务。

应用

自从提出以来,Transformer已经成为许多NLP任务的基石,包括但不限于:

  • 机器翻译(如Google Translate)
  • 文本生成(如GPT系列)
  • 文本摘要
  • 情感分析
  • 问答系统(如BERT)

2、模型构建

2.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')

2.2获取并行数据集

# 读取数据文件,并指定分隔符为制表符
df = pd.read_csv('./zh-ja/zh-ja.bicleaner05.txt', sep='\\t', engine='python', header=None)
 
# 提取中文和日文文本列
trainen = df[2].values.tolist()  # 中文文本列表
trainja = df[3].values.tolist()  # 日文文本列表
# trainen.pop(5972)

2.3准备分词器

en_tokenizer = spm.SentencePieceProcessor(model_file='enja_spm_models/spm.en.nopretok.model')
ja_tokenizer = spm.SentencePieceProcessor(model_file='enja_spm_models/spm.ja.nopretok.model')

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

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>'])
 
# 示例数据 trainja 和 trainen 分别为日语和英语句子列表
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)

2.5创建用于训练迭代的DataLoader对象

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))
        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)

2.6 Sequence-to-sequence 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__()
        
        # Transformer 编码器层
        encoder_layer = TransformerEncoderLayer(d_model=emb_size, nhead=NHEAD,
                                                dim_feedforward=dim_feedforward)
        self.transformer_encoder = TransformerEncoder(encoder_layer, num_layers=num_encoder_layers)
        
        # Transformer 解码器层
        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))
        
        # Transformer 编码器处理源语言序列
        memory = self.transformer_encoder(src_emb, src_mask, src_padding_mask)
        
        # Transformer 解码器处理目标语言序列
        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):
    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)
        self.register_buffer('pos_embedding', pos_embedding)
 
    def forward(self, token_embedding: Tensor):
        # 返回加上位置编码后的结果,并应用 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):
        super(TokenEmbedding, self).__init__()
        self.embedding = nn.Embedding(vocab_size, emb_size)
        self.emb_size = emb_size
    
    def forward(self, tokens: Tensor):
        # 返回词嵌入乘以 sqrt(词嵌入维度) 的结果
        return self.embedding(tokens.long()) * math.sqrt(self.emb_size)

def generate_square_subsequent_mask(sz):
    """
    生成一个用于 Transformer 解码器的目标序列遮蔽,确保在每个时间步只能看到之前的信息。
    Args:
    - sz (int): 序列的长度
    Returns:
    - mask (Tensor): 形状为 (sz, sz) 的上三角遮蔽矩阵,对角线及以下元素为-∞,其余元素为0
    """
    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):
    """
    创建用于 Transformer 模型的遮蔽张量。
    Args:
    - src (Tensor): 源序列张量
    - tgt (Tensor): 目标序列张量
    Returns:
    - src_mask (Tensor): 源序列的填充遮蔽张量,形状为 (src_seq_len, src_seq_len)
    - tgt_mask (Tensor): 目标序列的上三角遮蔽张量,形状为 (tgt_seq_len, tgt_seq_len)
    - src_padding_mask (Tensor): 源序列的填充遮蔽张量,形状为 (src_seq_len, batch_size)
    - tgt_padding_mask (Tensor): 目标序列的填充遮蔽张量,形状为 (tgt_seq_len, batch_size)
    """
    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

SRC_VOCAB_SIZE = len(ja_vocab)  # 源语言词汇表大小
TGT_VOCAB_SIZE = len(en_vocab)  # 目标语言词汇表大小
EMB_SIZE = 512  # 词嵌入维度
NHEAD = 8  # 注意力头的数量
FFN_HID_DIM = 512  # FeedForward 层隐藏层维度
BATCH_SIZE = 16  # 批量大小
NUM_ENCODER_LAYERS = 3  # 编码器层数
NUM_DECODER_LAYERS = 3  # 解码器层数
NUM_EPOCHS = 16  # 训练轮数
 
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)
 
transformer = transformer.to(device)  # 将模型移动到GPU上(如果可用)
 
loss_fn = torch.nn.CrossEntropyLoss(ignore_index=PAD_IDX)  # 定义损失函数,忽略填充索引
 
optimizer = torch.optim.Adam(
    transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9
)
 
def train_epoch(model, train_iter, optimizer):
    """
    训练模型的一个epoch。
    Args:
    - model (Seq2SeqTransformer): Transformer 模型
    - train_iter (DataLoader): 训练数据迭代器
    - optimizer (torch.optim.Adam): 优化器
    Returns:
    - float: 平均损失值
    """
    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):
    """
    评估模型在验证集上的表现。
    Args:
    - model (Seq2SeqTransformer): Transformer 模型
    - val_iter (DataLoader): 验证数据迭代器
    Returns:
    - float: 平均验证损失值
    """
    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)

2.7开始训练

for epoch in tqdm.tqdm(range(1, NUM_EPOCHS+1)):
  start_time = time.time()  # 记录每个epoch的开始时间
  train_loss = train_epoch(transformer, train_iter, optimizer)  # 执行一个epoch的训练并计算损失
  end_time = time.time()  # 记录每个epoch的结束时间
  print((f"Epoch: {epoch}, Train loss: {train_loss:.3f}, "
          f"Epoch time = {(end_time - start_time):.3f}s"))  # 打印当前epoch的训练损失和耗时

2.8测试

def greedy_decode(model, src, src_mask, max_len, start_symbol):
    """
    使用贪婪解码方法生成目标语言序列。
    Args:
    - model (Seq2SeqTransformer): Transformer模型对象。
    - src (Tensor): 源语言输入序列张量,形状为(seq_len, batch_size)。
    - src_mask (Tensor): 源语言输入序列的mask张量,形状为(seq_len, seq_len)。
    - max_len (int): 生成序列的最大长度。
    - start_symbol (int): 目标语言序列的起始符号索引。
    Returns:
    - ys (Tensor): 生成的目标语言序列张量,形状为(seq_len, 1)。
    """
    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):
    """
    将源语言文本翻译为目标语言文本。
    Args:
    - model (Seq2SeqTransformer): Transformer模型对象。
    - src (str): 源语言文本字符串。
    - src_vocab (Vocab): 源语言词汇表对象。
    - tgt_vocab (Vocab): 目标语言词汇表对象。
    - src_tokenizer (Tokenizer): 源语言文本分词器。
    Returns:
    - translation (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>", "")

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

import pickle
 
# 打开一个文件,用于存储数据
file = open('en_vocab.pkl', 'wb')
 
# 将词汇表对象 en_vocab 序列化并写入文件
pickle.dump(en_vocab, file)
 
# 关闭文件
file.close()
 
# 打开一个文件,用于存储数据
file = open('ja_vocab.pkl', 'wb')
 
# 将词汇表对象 ja_vocab 序列化并写入文件
pickle.dump(ja_vocab, file)
 
# 关闭文件
file.close()

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

# 使用torch.save()保存以下内容到'model_checkpoint.tar'文件中:
torch.save({
  'epoch': NUM_EPOCHS,
  'model_state_dict': transformer.state_dict(),
  'optimizer_state_dict': optimizer.state_dict(),
  'loss': train_loss,
  }, 'model_checkpoint.tar')

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值