基于Transformer实现机器翻译

一.什么是Transformer实现机器翻译

  1. Transformer模型由Vaswani等人在2017年提出,是一种基于注意力机制的序列到序列(Seq2Seq)模型,广泛应用于自然语言处理任务,特别是机器翻译。
  2. 完全基于注意力机制:Transformer模型不依赖于传统的循环神经网络(RNN)或卷积神经网络(CNN),而是完全依赖于自注意力(Self-Attention)机制来捕捉输入数据之间的全局依赖关系。
  3. 编码器-解码器结构:Transformer遵循编码器-解码器结构,其中编码器用于理解输入数据(源语言),解码器负责生成输出数据(目标语言)。
  4. 高效并行计算:由于Transformer不依赖于序列顺序处理,因此可以高效地利用现代计算硬件进行并行计算,显著提高训练速度。
  5. 数据预处理:包括读取和预处理数据集,如分词、构建词汇表、将句子转换为张量等。
  6. 构建模型:使用PyTorch等深度学习框架构建基于Transformer的机器翻译模型,包括编码器、解码器、注意力机制等组件。
  7. 训练模型:使用准备好的数据集对模型进行训练,通过反向传播算法优化模型参数。
  8. 预测和评估:使用训练好的模型进行预测,并评估翻译结果的准确性、流畅性等指标。
  9. 高效性:由于Transformer可以并行处理数据,因此在大规模数据集上训练速度更快。
  10. 准确性:Transformer能够捕捉全局上下文信息,因此在处理长句子和复杂结构时表现更好。
  11. 灵活性:Transformer模型可以应用于不同的语言对和不同的翻译任务,具有较强的泛化能力。

二.实验内容 

import math# 导入math模块,提供数学函数和常量
import torchtext# 导入torchtext模块,用于文本处理和数据加载 
import torch# 导入PyTorch库,用于深度学习
import torch.nn as nn# 导入PyTorch的神经网络模块 
from torch import Tensor# 导入PyTorch的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)) 

df = pd.read_csv('./zh-ja/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)

print(trainen[500])
print(trainja[500])

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

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

RuntimeError                              Traceback (most recent call last)
Cell In[9], line 1
----> 1 en_tokenizer.encode("All residents aged 20 to 59 years who live in Japan must enroll in public pension system.", out_type='str')

File /usr/local/anaconda3/lib/python3.11/site-packages/sentencepiece/__init__.py:561, in SentencePieceProcessor.Encode(self, input, out_type, add_bos, add_eos, reverse, emit_unk_piece, enable_sampling, nbest_size, alpha, num_threads)
    557 if out_type == 'immutable_proto':
    558   return self._EncodeAsImmutableProto(input, enable_sampling, nbest_size,
    559                                       alpha, add_bos, add_eos, reverse, emit_unk_piece)
--> 561 raise RuntimeError('unknown out_type={}'.format(out_type))
    562 return None

RuntimeError: unknown out_type=str

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

RuntimeError                              Traceback (most recent call last)
Cell In[10], line 1
----> 1 ja_tokenizer.encode("年金 日本に住んでいる20歳~60歳の全ての人は、公的年金制度に加入しなければなりません。", out_type='str')

File /usr/local/anaconda3/lib/python3.11/site-packages/sentencepiece/__init__.py:561, in SentencePieceProcessor.Encode(self, input, out_type, add_bos, add_eos, reverse, emit_unk_piece, enable_sampling, nbest_size, alpha, num_threads)
    557 if out_type == 'immutable_proto':
    558   return self._EncodeAsImmutableProto(input, enable_sampling, nbest_size,
    559                                       alpha, add_bos, add_eos, reverse, emit_unk_piece)
--> 561 raise RuntimeError('unknown out_type={}'.format(out_type))
    562 return None

RuntimeError: unknown out_type=str

def build_vocab(sentences, tokenizer):
  counter = 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) #使用定义好的build_vocab函数和日语的分词器ja_tokenizer,根据日语训练数据trainja来构建日语词汇表  
en_vocab = build_vocab(trainen, en_tokenizer)

def data_process(ja, en):
  data = []# 初始化一个空列表,用于存储处理后的数据 
  for (raw_ja, raw_en) in zip(ja, en):# 使用zip函数将日语和英语句子列表并行遍历
    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)
# 使用训练数据调用data_process函数,并将结果存储在train_data变量中

BATCH_SIZE = 8
PAD_IDX = ja_vocab['<pad>']
BOS_IDX = ja_vocab['<bos>']
EOS_IDX = ja_vocab['<eos>']
def generate_batch(data_batch):# 定义generate_batch函数,用于生成一个批次的数据
  ja_batch, en_batch = [], []# 初始化日语和英语批次的列表  
  for (ja_item, en_item) in data_batch:# 遍历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)

Transformer 是一种在“Attention is All You Need”论文中提出的 Seq2Seq 模型,用于解决机器翻译任务。Transformer 模型由一个编码器(Encoder)和一个解码器(Decoder)组成,它们各自包含固定数量的层。

编码器通过一系列的多头注意力(Multi-head Attention)和前馈网络(Feed Forward Network)层来处理输入序列。编码器的输出,通常被称为“记忆”(memory),与目标张量一起被送入解码器。编码器和解码器使用“教师强制”(teacher forcing)技术以端到端的方式进行训练。

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):# 定义位置编码模块
    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): # 将位置嵌入与token嵌入相加,并通过dropout层  
        return self.dropout(token_embedding +
                            self.pos_embedding[:token_embedding.size(0),:])

class TokenEmbedding(nn.Module):# 定义token嵌入模块
    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):
        return self.embedding(tokens.long()) * math.sqrt(self.emb_size)

我们创建一个后续词掩码(mask),以阻止目标词关注其后续的词。同时,我们也创建掩码来屏蔽源和目标中的填充标记(padding tokens)。

def generate_square_subsequent_mask(sz):# 生成一个上三角矩阵作为后续位置掩码,用于Transformer解码器中的自注意力机制 
    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的掩码  
  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)# 创建源序列的padding掩码,标识源序列中padding的位置
  tgt_padding_mask = (tgt == PAD_IDX).transpose(0, 1)# 创建目标序列的padding掩码,标识目标序列中padding的位置
  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
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)

for p in transformer.parameters():
    if p.dim() > 1:
        nn.init.xavier_uniform_(p)

transformer = transformer.to(device)

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

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

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):# 进行最大长度-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]
    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>", "")# 将目标序列的token索引转换为对应的单词,并连接成字符串 

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

最后,在训练完成后,我们将首先使用 Pickle 保存词汇表对象(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()

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

三.总结

基于Transformer实现机器翻译的应用与影响

一、应用

  1. 机器翻译
    • Transformer模型在机器翻译任务中取得了显著的成功,它通过自注意力机制能够建模更长距离的依赖关系,提高了翻译质量和流畅性。
    • 该模型已成为机器翻译领域的主流方法,并在许多翻译任务中达到了人类水平的性能。
    • 无论是英语到中文、日语到英文等跨语言翻译,Transformer都表现出了强大的能力。
  2. 文本生成与摘要
    • Transformer模型也广泛用于文本生成和摘要任务,能够生成连贯的文本,例如在自动写作、聊天机器人等场景中。
    • 通过学习大量文本数据,模型可以捕获语言模式和规律,并据此生成新的文本。
  3. 智能客服和聊天机器人
    • 在自然语言处理方面,Transformer模型为构建智能客服和聊天机器人系统提供了强大的支持。
    • 通过与用户的交互,模型可以理解用户的意图并生成相应的回复,从而提高用户体验。

二、影响

  1. 性能提升
    • 与传统的基于短语的机器翻译方法相比,Transformer模型通过自注意力机制能够建模更长距离的依赖关系,显著提高了翻译质量和流畅性。
    • 在多个翻译任务上,Transformer模型都取得了最佳效果。
  2. 推动NLP领域发展
    • Transformer模型的出现对自然语言处理领域产生了巨大影响,推动了该领域的快速发展。
    • 基于Transformer的预训练模型(如BERT、GPT等)可以在大规模语料库上进行预训练,学习到丰富的语言知识和语义表示,为各种下游NLP任务提供更好的初始化和特征表示。
  3. 改变翻译行业
    • 随着Transformer模型在机器翻译任务中的广泛应用,传统的翻译行业也受到了冲击。
    • 虽然机器翻译不能完全替代人工翻译,但它已经能够处理大量的简单翻译任务,提高了翻译效率并降低了成本。
  4. 促进多模态学习
    • 最近的研究将Transformer应用于处理多模态数据,如结合文本和图像的任务。
    • 这为未来的多模态学习和跨媒体分析提供了新的思路和方法。
  • 19
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值