当前位置:   article > 正文

NLP——基于Transformer& PyTorch实现机器翻译(日译中)_pytorch transformer 机器翻译

pytorch transformer 机器翻译

目录

一、实验任务

二、Transformer的介绍

2.1 自注意力机制(Self-Attention)

2.2 编码器-解码器结构:

2.3 位置编码(Positional Encoding)

2.4 多头注意力机制(Multi-Head Attention)

2.5 前馈神经网络(Feedforward Neural Network)

2.6 残差连接层归一化(Residual Connections and Layer Normalization)

三、基于Kaggle使用GPU环境

四、实验步骤

5.1 数据集

5.2 实验环境及设备

5.3 代码实现

五、分析总结


一、实验任务

      本实验基于Transformer,使用Pytorch深度学习框架完成日译中任务并保存模型状态字典,因数据量较大,需使用GPU运行,对电脑硬件要求较高。(附带Kaggle平台注册登录并使用GPU方法)

二、Transformer的介绍

        Transformer模型是一种基于注意力机制(attention mechanism)的深度学习模型,专门用于处理序列到序列的任务,例如机器翻译、文本生成等。Transformer 是第一个完全依赖自注意力(self-attention)来计算输入和输出的表示,而不使用序列对齐的递归神经网络或卷积神经网络的转换模型,取代了传统的循环神经网络(RNN)和长短期记忆网络(LSTM)在处理长距离依赖和并行计算能力上的限制,由于其并行计算的特性,Transformer在训练时能够更高效地处理大规模数据。

2.1 自注意力机制(Self-Attention)

       Transformer的核心是自注意力机制,它允许模型在处理输入时同时考虑输入中不同位置的信息,而不像传统的RNN或CNN需要按顺序逐步处理。自注意力机制能够计算输入序列中每个位置的注意力权重,从而更有效地捕捉长距离依赖关系。简单来说,如图2.1.1所示,Self-Attention就是保证每一输出都与所有的输入有关,从而有效地利用上下文信息。

图2.1.1 self-sttention图示

2.2 编码器-解码器结构

       Transformer模型通常由编码器和解码器组成。编码器负责将输入序列转换为隐藏表示,解码器则利用编码器输出的信息生成输出序列。这种结构非常适合sentence to sentence的任务,如机器翻译。

图2.2.1 编码器-解码器模型 

2.3 位置编码(Positional Encoding)

       由于Transformer不像RNN或CNN具有显式的序列顺序信息,位置编码被引入来为输入序列中每个位置添加位置信息。位置编码通常是通过一组特定函数来生成的,以便模型能够理解输入序列中的顺序。

2.4 多头注意力机制(Multi-Head Attention)

       为了提升模型的表达能力,Transformer引入了多头注意力机制。多头注意力使模型能同时关注来自不同表示子空间的不同位置的信息,每个注意力头都学习一个独特的注意力权重矩阵。Multi-Head Attention能够捕捉到更多的特征以及信息,能够解决相关性形式不止一种的问题。

2.5 前馈神经网络(Feedforward Neural Network)

        Transformer模型中每个位置都包含一个前馈神经网络子层。这个子层通常由两层全连接层组成,通过一个ReLU激活函数连接,每个位置的词都单独经过这个完全相同的前馈神经网络。这一结构有助于捕捉输入表示中的非线性特征。

2.6 残差连接层归一化(Residual Connections and Layer Normalization)

        每个编码器的每个子层(Self-Attention 层和 FFN 层)都有一个残差连接(Residual),再执行一个层标准化(Layer normalization)操作,如图2.6.1所示。Residual是将输入加到输出上,作为最后的输出,可以解决深度神经网络的退化问题,同等层数的前提下残差网络也收敛得更快;Layer normalization是数据归一化的一种方式,每个子层输出都通过残差连接与其输入相加,计算均值和方差,即应用层归一化来稳定训练过程。

图2.6.1 编码器结构

三、基于Kaggle使用GPU环境

      前文提到本实验需要配置GPU环境,简单记录一下Kaggle平台的GPU环境配置,Kaggle网页链接如下:

Kaggle: Your Home for Data Scienceicon-default.png?t=N7T8https://www.kaggle.com/第一步先登陆/注册,点击右上角Register

会出现如下界面,选择第二个邮箱注册:

 出现如下注册界面:

 但是还不能注册成功,因为这个界面缺少验证部分,需要在拓展(插件)里下载插件Hoxx VPN Proxy(我使用的是microsaft edge,不同浏览器可能不太一样)

 获取该插件后需要在插件内注册登录一下,然后在下图界面选择一个Free Server连接就好了

 连接成功:

启用Hoxx VPN后,回到注册界面,刷新一下就能够看到人机验证信息了:

注册成功后即可登陆Kaggle平台:

 其中点击Create或Code部分都可以创建一个新notebook,然后就可以开始写代码并运行了。

 另外,因为我们要用的是GPU,还需要进行手机号验证才能获得使用GPU的权限,点击上图右上角的头像,再点击setting即可看到手机验证:

 验证后回到notebook,即代码运行界面,点击上边栏的三个小黑点,再点击Accelerate即可选择GPU,有GPU T4X2和GPU P100两种型号,经过验证,个人认为好像GPU T4X2更快一些;

然后就可以正式开始写代码啦! 

四、实验步骤

5.1 数据集

  本实验使用了从JParaCrawl下载的日英并行数据集[http://www.kecl.ntt.co.jp/icl/lirg/jparacrawl]

图5.1.1 数据集文件

5.2 实验环境及设备

Python

GPU T4X2

5.3 代码实现

5.3.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')
  20. print(torch.cuda.get_device_name(0))

 运行环境输出:

 5.3.2 获取并行数据集 :

  1. # 读取文本文档,使用 '\t' 作为分隔符,因为 '\t' 需要转义,所以使用 '\\t'
  2. df = pd.read_csv('/kaggle/input/newdata/newsdata/zh-ja.bicleaner05.txt', sep='\\t', engine='python', header=None) #读取文本文档
  3. # 提取英语和日语句子到列表中
  4. trainen = df[2].values.tolist()#[:10000]
  5. trainja = df[3].values.tolist()#[:10000]
  6. # 移除特定索引(这里是索引为 5972 的数据点)
  7. trainen.pop(5972)
  8. trainja.pop(5972)

 输出:

'2014年と2017年のサンデータイムズ紙によってイギリス国内で生活に最も適した街と名付けられ、またヨーロッパグリーンキャピタルの賞も受賞しています。'

5.3.3 数据集预处理

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

  1. print(trainen[500])
  2. print(trainja[500])

 数据集中包含的句子示例:

 由于日语句子不包含空格来分隔单词。我们可以使用JParaCrawl提供的标记器,它是使用SentencePiece为日语和英语创建的。

  1. # 初始化 SentencePieceProcessor 对象,加载预训练的模型文件
  2. en_tokenizer = spm.SentencePieceProcessor(model_file='/kaggle/input/newdata/newsdata/spm.en.nopretok.model')
  3. ja_tokenizer = spm.SentencePieceProcessor(model_file='/kaggle/input/newdata/newsdata/spm.ja.nopretok.model')
  4. # 对英语句子进行编码,输出类型为字符串(out_type='str')
  5. en_tokenizer.encode("All residents aged 20 to 59 years who live in Japan must enroll in public pension system.", out_type=str)
  6. # 对日本语句子进行编码,输出类型为字符串(out_type='str')
  7. ja_tokenizer.encode("年金 日本に住んでいる20歳~60歳の全ての人は、公的年金制度に加入しなければなりません。", out_type='str')

输出示例:

构建TorchText Vocab对象并将句子转换为Torch tensors 使用标记器和原始句子,用SentencePiece作分词器效果很好;

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

  1. def build_vocab(sentences, tokenizer):
  2. counter = Counter() # 创建一个空的计数器对象,用于统计词频
  3. for sentence in sentences: # 遍历每个句子
  4. counter.update(tokenizer.encode(sentence, out_type=str)) # 使用指定的tokenizer对句子进行编码,并更新计数器
  5. return Vocab(counter, specials=['<unk>', '<pad>', '<bos>', '<eos>']) # 根据统计的词频构建词汇表,并指定特殊标记
  6. ja_vocab = build_vocab(trainja, ja_tokenizer) # 构建日语的词汇表,trainja为日语训练数据,ja_tokenizer为日语的tokenizer
  7. en_vocab = build_vocab(trainen, en_tokenizer) # 构建英语的词汇表,trainen为英语训练数据,en_tokenizer为英语的tokenizer
  1. # 定义函数:数据处理,将文本数据转换为张量数据
  2. def data_process(ja, en):
  3. data = []
  4. for (raw_ja, raw_en) in zip(ja, en):
  5. # 使用日语tokenizer对日语句子进行编码,并转换为长整型张量
  6. ja_tensor_ = torch.tensor([ja_vocab[token] for token in ja_tokenizer.encode(raw_ja.rstrip("\n"), out_type=str)],
  7. dtype=torch.long)
  8. # 使用英语tokenizer对英语句子进行编码,并转换为长整型张量
  9. en_tensor_ = torch.tensor([en_vocab[token] for token in en_tokenizer.encode(raw_en.rstrip("\n"), out_type=str)],
  10. dtype=torch.long)
  11. data.append((ja_tensor_, en_tensor_)) # 将处理好的数据组成元组并添加到data列表中
  12. return data
  13. #处理训练数据
  14. train_data = data_process(trainja, trainen)

创建要在训练期间迭代的DataLoader对象,将BATCH_SIZE设置为16,以防止“cuda内存不足”。

  1. # 定义常量:批量大小和特殊标记的索引
  2. BATCH_SIZE = 8
  3. PAD_IDX = ja_vocab['<pad>']
  4. BOS_IDX = ja_vocab['<bos>']
  5. EOS_IDX = ja_vocab['<eos>']
  6. # 定义函数:生成批量数据,包括填充和特殊标记
  7. def generate_batch(data_batch):
  8. ja_batch, en_batch = [], []
  9. for (ja_item, en_item) in data_batch:
  10. # 在日语句子前后添加起始和结束标记,并拼接为张量
  11. ja_batch.append(torch.cat([torch.tensor([BOS_IDX]), ja_item, torch.tensor([EOS_IDX])], dim=0))
  12. # 在英语句子前后添加起始和结束标记,并拼接为张量
  13. en_batch.append(torch.cat([torch.tensor([BOS_IDX]), en_item, torch.tensor([EOS_IDX])], dim=0))
  14. # 对批量数据进行填充,使用PAD_IDX作为填充值
  15. ja_batch = pad_sequence(ja_batch, padding_value=PAD_IDX)
  16. en_batch = pad_sequence(en_batch, padding_value=PAD_IDX)
  17. return ja_batch, en_batch
  18. # 创建数据加载器,用于训练模型
  19. train_iter = DataLoader(train_data, batch_size=BATCH_SIZE,
  20. shuffle=True, collate_fn=generate_batch)

5.3.4 Sequence-to-sequence Transformer

       转换器模型由编码器和解码器模块组成,每个模块包含固定数量的层。 编码器通过一系列多头注意力和前馈网络层传播输入序列来处理输入序列。来自编码器的输出被称为存储器,与目标张量一起被馈送到解码器。

  1. from torch.nn import (TransformerEncoder, TransformerDecoder,
  2. TransformerEncoderLayer, TransformerDecoderLayer)
  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. # 定义Transformer编码器层
  9. encoder_layer = TransformerEncoderLayer(d_model=emb_size, nhead=NHEAD,
  10. dim_feedforward=dim_feedforward)
  11. # 创建Transformer编码器,堆叠多个编码器层
  12. self.transformer_encoder = TransformerEncoder(encoder_layer, num_layers=num_encoder_layers)
  13. # 定义Transformer解码器层
  14. decoder_layer = TransformerDecoderLayer(d_model=emb_size, nhead=NHEAD,
  15. dim_feedforward=dim_feedforward)
  16. # 创建Transformer解码器,堆叠多个解码器层
  17. self.transformer_decoder = TransformerDecoder(decoder_layer, num_layers=num_decoder_layers)
  18. #线性层,用于生成最终的目标词汇分布
  19. self.generator = nn.Linear(emb_size, tgt_vocab_size)
  20. # 源语言和目标语言的词嵌入层,可以是任意定义的TokenEmbedding类
  21. self.src_tok_emb = TokenEmbedding(src_vocab_size, emb_size)
  22. self.tgt_tok_emb = TokenEmbedding(tgt_vocab_size, emb_size)
  23. # 位置编码层,用于向词嵌入中加入位置信息
  24. self.positional_encoding = PositionalEncoding(emb_size, dropout=dropout)
  25. def forward(self, src: Tensor, trg: Tensor, src_mask: Tensor,
  26. tgt_mask: Tensor, src_padding_mask: Tensor,
  27. tgt_padding_mask: Tensor, memory_key_padding_mask: Tensor):
  28. # 对源语言和目标语言的词嵌入加入位置编码
  29. src_emb = self.positional_encoding(self.src_tok_emb(src))
  30. tgt_emb = self.positional_encoding(self.tgt_tok_emb(trg))
  31. # 使用Transformer编码器处理源语言编码
  32. memory = self.transformer_encoder(src_emb, src_mask, src_padding_mask)
  33. # 使用Transformer解码器生成目标语言的输出
  34. outs = self.transformer_decoder(tgt_emb, memory, tgt_mask, None,
  35. tgt_padding_mask, memory_key_padding_mask)
  36. # 经过线性层生成最终的目标语言词汇分布
  37. return self.generator(outs)
  38. def encode(self, src: Tensor, src_mask: Tensor):
  39. # 编码函数,用于Transformer模型的编码器部分
  40. # src: 输入源数据张量
  41. # src_mask: 输入源数据的掩码张量
  42. # 返回经过位置编码后的编码器输出
  43. return self.transformer_encoder(self.positional_encoding(
  44. self.src_tok_emb(src)), src_mask)
  45. def decode(self, tgt: Tensor, memory: Tensor, tgt_mask: Tensor):
  46. # 解码函数,用于Transformer模型的解码器部分
  47. # tgt: 目标数据张量
  48. # memory: 编码器的输出,用于解码器的注意力机制
  49. # tgt_mask: 目标数据的掩码张量
  50. # 返回经过位置编码后的解码器输出
  51. return self.transformer_decoder(self.positional_encoding(
  52. self.tgt_tok_emb(tgt)), memory,
  53. tgt_mask)

5.3.5 Positional encoding

  1. class PositionalEncoding(nn.Module):
  2. def __init__(self, emb_size: int, dropout, maxlen: int = 5000):
  3. super(PositionalEncoding, self).__init__()
  4. # 计算位置编码
  5. den = torch.exp(- torch.arange(0, emb_size, 2) * math.log(10000) / emb_size)
  6. # 填充位置编码张量
  7. pos = torch.arange(0, maxlen).reshape(maxlen, 1)
  8. pos_embedding = torch.zeros((maxlen, emb_size))
  9. # 填充位置编码张量
  10. pos_embedding[:, 0::2] = torch.sin(pos * den)
  11. pos_embedding[:, 1::2] = torch.cos(pos * den)
  12. pos_embedding = pos_embedding.unsqueeze(-2)# 增加一维作为批处理维度
  13. # 使用丢弃法
  14. self.dropout = nn.Dropout(dropout)
  15. self.register_buffer('pos_embedding', pos_embedding)# 注册为模型的缓冲区,使其在保存和加载模型时保持不变
  16. def forward(self, token_embedding: Tensor):
  17. # 前向传播函数
  18. # token_embedding: 输入的token嵌入张量
  19. # 返回添加了位置编码并且经过dropout的嵌入张量
  20. # 叠加位置编码并应用丢弃法
  21. return self.dropout(token_embedding +
  22. self.pos_embedding[:token_embedding.size(0),:])
  23. class TokenEmbedding(nn.Module):
  24. def __init__(self, vocab_size: int, emb_size):
  25. super(TokenEmbedding, self).__init__()
  26. # 嵌入层定义
  27. self.embedding = nn.Embedding(vocab_size, emb_size)
  28. self.emb_size = emb_size
  29. def forward(self, tokens: Tensor):
  30. # 前向传播函数
  31. # tokens: 输入的token张量
  32. # 返回token的嵌入张量,乘以嵌入维度的平方根
  33. # 计算嵌入并按照论文推荐的缩放因子调整
  34. return self.embedding(tokens.long()) * math.sqrt(self.emb_size)
  1. def generate_square_subsequent_mask(sz):
  2. # 生成一个上三角矩阵,用于遮掩后续位置的信息
  3. mask = (torch.triu(torch.ones((sz, sz), device=device)) == 1).transpose(0, 1)
  4. # 将 mask 转换为浮点类型,并且用 -inf 和 0 来填充 mask 的值
  5. mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
  6. return mask
  7. def create_mask(src, tgt):
  8. # 创建用于源和目标序列的遮掩张量
  9. src_seq_len = src.shape[0] # 获取源序列的长度
  10. tgt_seq_len = tgt.shape[0] # 获取目标序列的长度
  11. tgt_mask = generate_square_subsequent_mask(tgt_seq_len) # 生成目标序列的遮掩张量
  12. src_mask = torch.zeros((src_seq_len, src_seq_len), device=device).type(torch.bool) # 创建与源序列相关的遮掩张量,这里初始化为全零
  13. src_padding_mask = (src == PAD_IDX).transpose(0, 1)# 创建用于遮掩源序列中填充位置的布尔型张量
  14. tgt_padding_mask = (tgt == PAD_IDX).transpose(0, 1)# 创建用于遮掩目标序列中填充位置的布尔型张量
  15. return src_mask, tgt_mask, src_padding_mask, tgt_padding_mask #返回所有的遮掩张量

  5.3.6 训练模型

指定所需参数并进行训练:

  1. # 定义模型参数和超参数
  2. SRC_VOCAB_SIZE = len(ja_vocab) # 源语言词汇表大小
  3. TGT_VOCAB_SIZE = len(en_vocab) # 目标语言词汇表大小
  4. EMB_SIZE = 512 # 词嵌入维度
  5. NHEAD = 8 # 注意力头数
  6. FFN_HID_DIM = 512 # FeedForward层隐藏层维度
  7. BATCH_SIZE = 16 # 批量大小
  8. NUM_ENCODER_LAYERS = 3 # 编码器层数
  9. NUM_DECODER_LAYERS = 3 # 解码器层数
  10. NUM_EPOCHS = 16 # 训练的轮数
  11. # 初始化Transformer模型
  12. transformer = Seq2SeqTransformer(NUM_ENCODER_LAYERS, NUM_DECODER_LAYERS,
  13. EMB_SIZE, SRC_VOCAB_SIZE, TGT_VOCAB_SIZE,
  14. FFN_HID_DIM)
  15. # 对模型参数进行Xavier初始化
  16. for p in transformer.parameters():
  17. if p.dim() > 1:
  18. nn.init.xavier_uniform_(p)
  19. # 将模型移动到指定的设备(如GPU)
  20. transformer = transformer.to(device)
  21. # 定义损失函数(交叉熵损失),忽略填充标记的损失计算
  22. loss_fn = torch.nn.CrossEntropyLoss(ignore_index=PAD_IDX)
  23. # 定义优化器(Adam优化器)
  24. optimizer = torch.optim.Adam(
  25. transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9
  26. )
  27. # 定义训练函数
  28. def train_epoch(model, train_iter, optimizer):
  29. model.train()
  30. losses = 0
  31. for idx, (src, tgt) in enumerate(train_iter):
  32. src = src.to(device)
  33. tgt = tgt.to(device)
  34. tgt_input = tgt[:-1, :] # 获取目标序列的输入部分(去掉最后一个词)
  35. # 创建遮掩张量
  36. src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)
  37. # 前向传播计算logits
  38. logits = model(src, tgt_input, src_mask, tgt_mask, src_padding_mask, tgt_padding_mask, src_padding_mask)
  39. # 梯度清零
  40. optimizer.zero_grad()
  41. tgt_out = tgt[1:, :] # 获取目标序列的输出部分(去掉第一个词)
  42. # 计算损失
  43. loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))
  44. # 反向传播
  45. loss.backward()
  46. # 更新参数
  47. optimizer.step()
  48. losses += loss.item() # 累计损失值
  49. return losses / len(train_iter) # 返回平均损失
  50. def evaluate(model, val_iter):
  51. """
  52. 评估模型在验证集上的性能。
  53. Args:
  54. - model: 训练好的Seq2SeqTransformer模型
  55. - val_iter: 验证集数据迭代器
  56. Returns:
  57. - 平均损失值
  58. """
  59. model.eval() # 设置模型为评估模式
  60. losses = 0
  61. for idx, (src, tgt) in (enumerate(valid_iter)):
  62. src = src.to(device) # 将源语言数据移到设备(如GPU)
  63. tgt = tgt.to(device) # 将目标语言数据移到设备(如GPU)
  64. tgt_input = tgt[:-1, :] # 获取目标序列的输入部分(去掉最后一个词)
  65. # 创建遮掩张量
  66. src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)
  67. # 前向传播计算logits
  68. logits = model(src, tgt_input, src_mask, tgt_mask,src_padding_mask, tgt_padding_mask, src_padding_mask)
  69. tgt_out = tgt[1:,:] # 获取目标序列的输出部分(去掉第一个词)
  70. # 计算损失
  71. loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))
  72. losses += loss.item()# 累计损失值
  73. return losses / len(val_iter)# 返回平均损失
  1. for epoch in tqdm.tqdm(range(1, NUM_EPOCHS+1)):
  2. start_time = time.time()# 记录当前epoch的开始时间
  3. # 在训练集上训练一个epoch,并返回训练损失
  4. train_loss = train_epoch(transformer, train_iter, optimizer)
  5. end_time = time.time() # 记录当前epoch的结束时间
  6. # 打印当前epoch的训练损失和训练时长
  7. print((f"Epoch: {epoch}, Train loss: {train_loss:.3f}, "
  8. f"Epoch time = {(end_time - start_time):.3f}s"))

共需运行十轮,在该平台的GPU上,我每轮训练需要630秒左右:

 由运行结果可以看出来,每轮训练之后Train loss在逐步减少,这说明训练出来的效果越来越好了。

5.3.7 模型测试

使用训练好的模型翻译一个日语句子,先创建翻译一个新句子的函数,包括获取日语句子、标记、转换为张量、推理,然后将结果解码为句子等步骤。

  1. def greedy_decode(model, src, src_mask, max_len, start_symbol):
  2. src = src.to(device) # 将输入源序列移动到指定的计算设备(如GPU)
  3. src_mask = src_mask.to(device) # 将输入源序列的掩码也移动到指定的计算设备
  4. memory = model.encode(src, src_mask) # 对输入源序列进行编码,生成记忆(memory)
  5. ys = torch.ones(1, 1).fill_(start_symbol).type(torch.long).to(device) # 初始化目标序列,以起始符号开始
  6. # 循环生成目标序列直到达到最大长度或者生成结束符号
  7. for i in range(max_len - 1):
  8. memory = memory.to(device) # 将记忆也移动到指定的计算设备
  9. memory_mask = torch.zeros(ys.shape[0], memory.shape[0]).to(device).type(torch.bool) # 创建记忆掩码
  10. tgt_mask = (generate_square_subsequent_mask(ys.size(0)).type(torch.bool)).to(device) # 生成目标序列的掩码
  11. out = model.decode(ys, memory, tgt_mask) # 解码器生成下一个词的概率分布
  12. out = out.transpose(0, 1) # 调整输出的形状
  13. prob = model.generator(out[:, -1]) # 通过生成器获取最后一个词的概率分布
  14. _, next_word = torch.max(prob, dim=1) # 获取概率最高的词作为下一个词
  15. next_word = next_word.item() # 将下一个词转换为标量值
  16. ys = torch.cat([ys,
  17. torch.ones(1, 1).type_as(src.data).fill_(next_word)], dim=0) # 将预测的下一个词追加到目标序列中
  18. if next_word == EOS_IDX: # 如果预测的下一个词是结束符号,则停止生成
  19. break
  20. return ys # 返回生成的目标序列
  21. def translate(model, src, src_vocab, tgt_vocab, src_tokenizer):
  22. model.eval() # 将模型设置为评估模式,这会禁用一些具有随机性质的操作,如Dropout
  23. tokens = [BOS_IDX] + [src_vocab.stoi[tok] for tok in src_tokenizer.encode(src, out_type=str)] + [EOS_IDX] # 将输入源文本转换为标记序列
  24. num_tokens = len(tokens) # 获取标记序列的长度
  25. src = (torch.LongTensor(tokens).reshape(num_tokens, 1)) # 将标记序列转换为PyTorch张量,并调整形状以适应模型输入
  26. src_mask = (torch.zeros(num_tokens, num_tokens)).type(torch.bool) # 创建输入源序列的掩码
  27. # 调用贪婪解码函数生成目标序列的标记
  28. tgt_tokens = greedy_decode(model, src, src_mask, max_len=num_tokens + 5, start_symbol=BOS_IDX).flatten()
  29. # 将生成的目标序列标记转换为文本并进行后处理(去除特殊标记)
  30. 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)

翻译结果:

' ▁H S 还 是 一 部 标 准 的 多 用 途 商 品 , 广 泛 用 于 焊 接 用 于 焊 接 的 设 备 , 包 括 电 气 加 热 加'
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 ...'

5.3.8 保存模型 

      保存Vocab对象和训练好的模型,在训练结束后,使用Pickle保存Vocab对象(en_vocab和ja_vocab),并保存模型的状态字典,以便后续推断使用:

  1. import pickle
  2. # open a file, where you want to store the data
  3. file = open('en_vocab.pkl', 'wb')
  4. # 使用pickle将英语词汇表(en_vocab)存储到文件中
  5. pickle.dump(en_vocab, file)
  6. file.close()
  7. # 使用pickle将日语词汇表(ja_vocab)存储到文件中
  8. file = open('ja_vocab.pkl', 'wb')
  9. pickle.dump(ja_vocab, file)
  10. file.close()
  11. # 保存模型的状态字典,以便后续推断使用
  12. torch.save(transformer.state_dict(), 'inference_model')
  13. # 保存模型、优化器状态字典、损失值和当前训练的轮次
  14. torch.save({
  15. 'epoch': NUM_EPOCHS,
  16. 'model_state_dict': transformer.state_dict(),
  17. 'optimizer_state_dict': optimizer.state_dict(),
  18. 'loss': train_loss,
  19. }, 'model_checkpoint.tar')

 保存结果:

五、分析总结

  • 模型改进与调优

    • 未来可以尝试改进Transformer模型的特定部分,如注意力机制或者模型结构的变种,以进一步提升其在日译中任务中的表现。
  • 应用场景扩展

    • Transformer模型不仅局限于机器翻译,还可用于文本摘要、对话生成等多个NLP任务。探索其在不同应用场景下的适用性和性能表现是未来的研究方向之一。
  • 硬件资源管理

    • 随着深度学习模型规模的增大,对硬件资源的需求也在增加。优化和管理好硬件资源,尤其是GPU的使用,将是未来研究和实践中需要重视的问题。

        综上所述,本实验通过Transformer模型在日译中任务上的应用,展示了其强大的表达能力和优越的性能。通过利用PyTorch和GPU加速,不仅验证了模型的有效性,还探索了在云端平台如Kaggle上进行深度学习实验的方法与优势,为之后深度学习上的进一步探索打下基础。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号