当前位置:   article > 正文

基于transformer实现机器翻译_tranformer 机器翻译数据集

tranformer 机器翻译数据集

目录

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导入必要的库

  1. import math
  2. import torchtext
  3. import torch
  4. import torch.nn as nn
  5. from torch import Tensor
  6. from torch.nn.utils.rnn import pad_sequence
  7. from torch.utils.data import DataLoader
  8. from collections import Counter
  9. from torchtext.vocab import Vocab
  10. from torch.nn import TransformerEncoder, TransformerDecoder, TransformerEncoderLayer, TransformerDecoderLayer
  11. import io
  12. import time
  13. import pandas as pd
  14. import numpy as np
  15. import pickle
  16. import tqdm
  17. import sentencepiece as spm
  18. torch.manual_seed(0)
  19. device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

2.2获取并行数据集

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

2.3准备分词器

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

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

  1. def build_vocab(sentences, tokenizer):
  2. counter = Counter()
  3. for sentence in sentences:
  4. counter.update(tokenizer.encode(sentence, out_type=str))
  5. return Vocab(counter, specials=['<unk>', '<pad>', '<bos>', '<eos>'])
  6. # 示例数据 trainja 和 trainen 分别为日语和英语句子列表
  7. ja_vocab = build_vocab(trainja, ja_tokenizer) # 构建日语词汇表
  8. en_vocab = build_vocab(trainen, en_tokenizer) # 构建英语词汇表
  9. def data_process(ja, en):
  10.     data = []
  11.     for (raw_ja, raw_en) in zip(ja, en):
  12.         # 将日语句子转换为张量
  13.         ja_tensor = torch.tensor([ja_vocab[token] for token in ja_tokenizer.encode(raw_ja.rstrip("\n"), out_type=str)],
  14.                                  dtype=torch.long)
  15.         # 将英语句子转换为张量
  16.         en_tensor = torch.tensor([en_vocab[token] for token in en_tokenizer.encode(raw_en.rstrip("\n"), out_type=str)],
  17.                                  dtype=torch.long)
  18.         # 将日语和英语张量数据对加入到数据列表中
  19.         data.append((ja_tensor, en_tensor))
  20.     return data
  21.  
  22. # 将训练数据处理为张量数据集
  23. train_data = data_process(trainja, trainen)

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

  1. BATCH_SIZE = 8
  2. PAD_IDX = ja_vocab['<pad>']
  3. BOS_IDX = ja_vocab['<bos>']
  4. EOS_IDX = ja_vocab['<eos>']
  5.  
  6. def generate_batch(data_batch):
  7.     ja_batch, en_batch = [], []
  8.     for (ja_item, en_item) in data_batch:
  9.         # 添加起始标记 <bos> 和结束标记 <eos>,并拼接成张量序列
  10.         ja_batch.append(torch.cat([torch.tensor([BOS_IDX]), ja_item, torch.tensor([EOS_IDX])], dim=0))
  11.         en_batch.append(torch.cat([torch.tensor([BOS_IDX]), en_item, torch.tensor([EOS_IDX])], dim=0))
  12.     
  13.     # 对日语和英语批量数据进行填充,使它们的长度保持一致
  14.     ja_batch = pad_sequence(ja_batch, padding_value=PAD_IDX)
  15.     en_batch = pad_sequence(en_batch, padding_value=PAD_IDX)
  16.     
  17.     return ja_batch, en_batch
  18.  
  19. # 创建训练数据迭代器,每次返回一个批量的数据
  20. train_iter = DataLoader(train_data, batch_size=BATCH_SIZE,
  21.                         shuffle=True, collate_fn=generate_batch)

2.6 Sequence-to-sequence Transformer

  1. from torch.nn import TransformerEncoder, TransformerDecoder, TransformerEncoderLayer, TransformerDecoderLayer
  2.  
  3. class Seq2SeqTransformer(nn.Module):
  4.     def __init__(self, num_encoder_layers: int, num_decoder_layers: int,
  5.                  emb_size: int, src_vocab_size: int, tgt_vocab_size: int,
  6.                  dim_feedforward:int = 512, dropout:float = 0.1):
  7.         super(Seq2SeqTransformer, self).__init__()
  8.         
  9.         # Transformer 编码器层
  10.         encoder_layer = TransformerEncoderLayer(d_model=emb_size, nhead=NHEAD,
  11.                                                 dim_feedforward=dim_feedforward)
  12.         self.transformer_encoder = TransformerEncoder(encoder_layer, num_layers=num_encoder_layers)
  13.         
  14.         # Transformer 解码器层
  15.         decoder_layer = TransformerDecoderLayer(d_model=emb_size, nhead=NHEAD,
  16.                                                 dim_feedforward=dim_feedforward)
  17.         self.transformer_decoder = TransformerDecoder(decoder_layer, num_layers=num_decoder_layers)
  18.  
  19.         # 线性层作为生成器,将解码器输出映射到目标词汇表的维度
  20.         self.generator = nn.Linear(emb_size, tgt_vocab_size)
  21.         
  22.         # 源语言和目标语言的词嵌入层
  23.         self.src_tok_emb = TokenEmbedding(src_vocab_size, emb_size)
  24.         self.tgt_tok_emb = TokenEmbedding(tgt_vocab_size, emb_size)
  25.         
  26.         # 位置编码层
  27.         self.positional_encoding = PositionalEncoding(emb_size, dropout=dropout)
  28.  
  29.     def forward(self, src: Tensor, trg: Tensor, src_mask: Tensor,
  30.                 tgt_mask: Tensor, src_padding_mask: Tensor,
  31.                 tgt_padding_mask: Tensor, memory_key_padding_mask: Tensor):
  32.         # 对源语言和目标语言的输入进行位置编码
  33.         src_emb = self.positional_encoding(self.src_tok_emb(src))
  34.         tgt_emb = self.positional_encoding(self.tgt_tok_emb(trg))
  35.         
  36.         # Transformer 编码器处理源语言序列
  37.         memory = self.transformer_encoder(src_emb, src_mask, src_padding_mask)
  38.         
  39.         # Transformer 解码器处理目标语言序列
  40.         outs = self.transformer_decoder(tgt_emb, memory, tgt_mask, None,
  41.                                         tgt_padding_mask, memory_key_padding_mask)
  42.         
  43.         # 通过生成器映射到目标词汇表的维度
  44.         return self.generator(outs)
  45.  
  46.     def encode(self, src: Tensor, src_mask: Tensor):
  47.         # 编码器的编码过程,返回编码器的输出(编码器最后一层的输出)
  48.         return self.transformer_encoder(self.positional_encoding(
  49.                             self.src_tok_emb(src)), src_mask)
  50.  
  51.     def decode(self, tgt: Tensor, memory: Tensor, tgt_mask: Tensor):
  52.         # 解码器的解码过程,返回解码器的输出(解码器最后一层的输出)
  53.         return self.transformer_decoder(self.positional_encoding(
  54.                           self.tgt_tok_emb(tgt)), memory,
  55.                           tgt_mask)
  56. class PositionalEncoding(nn.Module):
  57.     def __init__(self, emb_size: int, dropout, maxlen: int = 5000):
  58.         super(PositionalEncoding, self).__init__()
  59.         
  60.         # 计算位置编码
  61.         den = torch.exp(- torch.arange(0, emb_size, 2) * math.log(10000) / emb_size)
  62.         pos = torch.arange(0, maxlen).reshape(maxlen, 1)
  63.         pos_embedding = torch.zeros((maxlen, emb_size))
  64.         pos_embedding[:, 0::2] = torch.sin(pos * den)
  65.         pos_embedding[:, 1::2] = torch.cos(pos * den)
  66.         pos_embedding = pos_embedding.unsqueeze(-2)
  67.  
  68.         self.dropout = nn.Dropout(dropout)
  69.         self.register_buffer('pos_embedding', pos_embedding)
  70.  
  71.     def forward(self, token_embedding: Tensor):
  72.         # 返回加上位置编码后的结果,并应用 dropout
  73.         return self.dropout(token_embedding +
  74.                             self.pos_embedding[:token_embedding.size(0), :])
  75.  
  76. class TokenEmbedding(nn.Module):
  77.     def __init__(self, vocab_size: int, emb_size):
  78.         super(TokenEmbedding, self).__init__()
  79.         self.embedding = nn.Embedding(vocab_size, emb_size)
  80.         self.emb_size = emb_size
  81.     
  82.     def forward(self, tokens: Tensor):
  83.         # 返回词嵌入乘以 sqrt(词嵌入维度) 的结果
  84.         return self.embedding(tokens.long()) * math.sqrt(self.emb_size)
  85. def generate_square_subsequent_mask(sz):
  86.     """
  87.     生成一个用于 Transformer 解码器的目标序列遮蔽,确保在每个时间步只能看到之前的信息。
  88.     Args:
  89.     - sz (int): 序列的长度
  90.     Returns:
  91.     - mask (Tensor): 形状为 (sz, sz) 的上三角遮蔽矩阵,对角线及以下元素为-∞,其余元素为0
  92.     """
  93.     mask = (torch.triu(torch.ones((sz, sz), device=device)) == 1).transpose(0, 1)
  94.     mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
  95.     return mask
  96.  
  97. def create_mask(src, tgt):
  98.     """
  99.     创建用于 Transformer 模型的遮蔽张量。
  100.     Args:
  101.     - src (Tensor): 源序列张量
  102.     - tgt (Tensor): 目标序列张量
  103.     Returns:
  104.     - src_mask (Tensor): 源序列的填充遮蔽张量,形状为 (src_seq_len, src_seq_len)
  105.     - tgt_mask (Tensor): 目标序列的上三角遮蔽张量,形状为 (tgt_seq_len, tgt_seq_len)
  106.     - src_padding_mask (Tensor): 源序列的填充遮蔽张量,形状为 (src_seq_len, batch_size)
  107.     - tgt_padding_mask (Tensor): 目标序列的填充遮蔽张量,形状为 (tgt_seq_len, batch_size)
  108.     """
  109.     src_seq_len = src.shape[0]
  110.     tgt_seq_len = tgt.shape[0]
  111.  
  112.     # 生成目标序列的遮蔽张量
  113.     tgt_mask = generate_square_subsequent_mask(tgt_seq_len)
  114.     
  115.     # 源序列的遮蔽张量为全零矩阵,不遮蔽任何内容
  116.     src_mask = torch.zeros((src_seq_len, src_seq_len), device=device).type(torch.bool)
  117.  
  118.     # 生成源序列和目标序列的填充遮蔽张量
  119.     src_padding_mask = (src == PAD_IDX).transpose(0, 1)
  120.     tgt_padding_mask = (tgt == PAD_IDX).transpose(0, 1)
  121.  
  122.     return src_mask, tgt_mask, src_padding_mask, tgt_padding_mask
  123. SRC_VOCAB_SIZE = len(ja_vocab)  # 源语言词汇表大小
  124. TGT_VOCAB_SIZE = len(en_vocab)  # 目标语言词汇表大小
  125. EMB_SIZE = 512  # 词嵌入维度
  126. NHEAD = 8  # 注意力头的数量
  127. FFN_HID_DIM = 512  # FeedForward 层隐藏层维度
  128. BATCH_SIZE = 16  # 批量大小
  129. NUM_ENCODER_LAYERS = 3  # 编码器层数
  130. NUM_DECODER_LAYERS = 3  # 解码器层数
  131. NUM_EPOCHS = 16  # 训练轮数
  132.  
  133. transformer = Seq2SeqTransformer(NUM_ENCODER_LAYERS, NUM_DECODER_LAYERS,
  134.                                  EMB_SIZE, SRC_VOCAB_SIZE, TGT_VOCAB_SIZE,
  135.                                  FFN_HID_DIM)
  136.  
  137. # 使用 Xavier 初始化网络参数
  138. for p in transformer.parameters():
  139.     if p.dim() > 1:
  140.         nn.init.xavier_uniform_(p)
  141.  
  142. transformer = transformer.to(device)  # 将模型移动到GPU上(如果可用)
  143.  
  144. loss_fn = torch.nn.CrossEntropyLoss(ignore_index=PAD_IDX)  # 定义损失函数,忽略填充索引
  145.  
  146. optimizer = torch.optim.Adam(
  147.     transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9
  148. )
  149.  
  150. def train_epoch(model, train_iter, optimizer):
  151.     """
  152.     训练模型的一个epoch。
  153.     Args:
  154.     - model (Seq2SeqTransformer): Transformer 模型
  155.     - train_iter (DataLoader): 训练数据迭代器
  156.     - optimizer (torch.optim.Adam): 优化器
  157.     Returns:
  158.     - float: 平均损失值
  159.     """
  160.     model.train()
  161.     losses = 0
  162.     for idx, (src, tgt) in enumerate(train_iter):
  163.         src = src.to(device)
  164.         tgt = tgt.to(device)
  165.  
  166.         tgt_input = tgt[:-1, :]
  167.  
  168.         src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)
  169.  
  170.         logits = model(src, tgt_input, src_mask, tgt_mask,
  171.                       src_padding_mask, tgt_padding_mask, src_padding_mask)
  172.  
  173.         optimizer.zero_grad()
  174.  
  175.         tgt_out = tgt[1:,:]
  176.         loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))
  177.         loss.backward()
  178.  
  179.         optimizer.step()
  180.         losses += loss.item()
  181.     return losses / len(train_iter)
  182.  
  183.  
  184. def evaluate(model, val_iter):
  185.     """
  186.     评估模型在验证集上的表现。
  187.     Args:
  188.     - model (Seq2SeqTransformer): Transformer 模型
  189.     - val_iter (DataLoader): 验证数据迭代器
  190.     Returns:
  191.     - float: 平均验证损失值
  192.     """
  193.     model.eval()
  194.     losses = 0
  195.     for idx, (src, tgt) in (enumerate(valid_iter)):
  196.         src = src.to(device)
  197.         tgt = tgt.to(device)
  198.  
  199.         tgt_input = tgt[:-1, :]
  200.  
  201.         src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)
  202.  
  203.         logits = model(src, tgt_input, src_mask, tgt_mask,
  204.                       src_padding_mask, tgt_padding_mask, src_padding_mask)
  205.         tgt_out = tgt[1:,:]
  206.         loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))
  207.         losses += loss.item()
  208.     return losses / len(val_iter)

2.7开始训练

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

2.8测试

  1. def greedy_decode(model, src, src_mask, max_len, start_symbol):
  2.     """
  3.     使用贪婪解码方法生成目标语言序列。
  4.     Args:
  5.     - model (Seq2SeqTransformer): Transformer模型对象。
  6.     - src (Tensor): 源语言输入序列张量,形状为(seq_len, batch_size)。
  7.     - src_mask (Tensor): 源语言输入序列的mask张量,形状为(seq_len, seq_len)。
  8.     - max_len (int): 生成序列的最大长度。
  9.     - start_symbol (int): 目标语言序列的起始符号索引。
  10.     Returns:
  11.     - ys (Tensor): 生成的目标语言序列张量,形状为(seq_len, 1)。
  12.     """
  13.     src = src.to(device)
  14.     src_mask = src_mask.to(device)
  15.     memory = model.encode(src, src_mask)
  16.     ys = torch.ones(1, 1).fill_(start_symbol).type(torch.long).to(device)
  17.     
  18.     for i in range(max_len-1):
  19.         memory = memory.to(device)
  20.         memory_mask = torch.zeros(ys.shape[0], memory.shape[0]).to(device).type(torch.bool)
  21.         tgt_mask = (generate_square_subsequent_mask(ys.size(0))
  22.                                     .type(torch.bool)).to(device)
  23.         out = model.decode(ys, memory, tgt_mask)
  24.         out = out.transpose(0, 1)
  25.         prob = model.generator(out[:, -1])
  26.         _, next_word = torch.max(prob, dim=1)
  27.         next_word = next_word.item()
  28.         ys = torch.cat([ys, torch.ones(1, 1).type_as(src.data).fill_(next_word)], dim=0)
  29.         
  30.         if next_word == EOS_IDX:
  31.             break
  32.     
  33.     return ys
  34.  
  35. def translate(model, src, src_vocab, tgt_vocab, src_tokenizer):
  36.     """
  37.     将源语言文本翻译为目标语言文本。
  38.     Args:
  39.     - model (Seq2SeqTransformer): Transformer模型对象。
  40.     - src (str): 源语言文本字符串。
  41.     - src_vocab (Vocab): 源语言词汇表对象。
  42.     - tgt_vocab (Vocab): 目标语言词汇表对象。
  43.     - src_tokenizer (Tokenizer): 源语言文本分词器。
  44.     Returns:
  45.     - translation (str): 翻译后的目标语言文本字符串。
  46.     """
  47.     model.eval()
  48.     tokens = [BOS_IDX] + [src_vocab.stoi[tok] for tok in src_tokenizer.encode(src, out_type=str)] + [EOS_IDX]
  49.     num_tokens = len(tokens)
  50.     src = torch.LongTensor(tokens).reshape(num_tokens, 1)
  51.     src_mask = torch.zeros(num_tokens, num_tokens).type(torch.bool)
  52.     tgt_tokens = greedy_decode(model, src, src_mask, max_len=num_tokens + 5, start_symbol=BOS_IDX).flatten()
  53.     return " ".join([tgt_vocab.itos[tok] for tok in tgt_tokens]).replace("<bos>", "").replace("<eos>", "")

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

  1. import pickle
  2.  
  3. # 打开一个文件,用于存储数据
  4. file = open('en_vocab.pkl', 'wb')
  5.  
  6. # 将词汇表对象 en_vocab 序列化并写入文件
  7. pickle.dump(en_vocab, file)
  8.  
  9. # 关闭文件
  10. file.close()
  11.  
  12. # 打开一个文件,用于存储数据
  13. file = open('ja_vocab.pkl', 'wb')
  14.  
  15. # 将词汇表对象 ja_vocab 序列化并写入文件
  16. pickle.dump(ja_vocab, file)
  17.  
  18. # 关闭文件
  19. file.close()
  20. # save model for inference
  21. torch.save(transformer.state_dict(), 'inference_model')
  22. # 使用torch.save()保存以下内容到'model_checkpoint.tar'文件中:
  23. torch.save({
  24.   'epoch': NUM_EPOCHS,
  25.   'model_state_dict': transformer.state_dict(),
  26.   'optimizer_state_dict': optimizer.state_dict(),
  27.   'loss': train_loss,
  28.   }, 'model_checkpoint.tar')

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/972705
推荐阅读
相关标签
  

闽ICP备14008679号