目录
4.定义参数、实例化transformer模型并定义训练、评估函数
Part1:写在最前面
1.训练平台的选择
这个需要用到GPU来运行,关于GPU的平台:
AutoDL算力云AutoDL算力云 | 弹性、好用、省钱。租GPU就上AutoDL(需要充钱租GPU,自己配置实验环境,本人充了10元,搭了一下午+一晚上的环境,没搞好)
KaggleKaggle: Your Machine Learning and Data Science Community(不需要花钱,也不需要自己配置实验环境,但是每周只有30小时的GPU使用时间)
关于kaggle平台的注册在本人写的另一篇博客中:Kaggle的使用分享-CSDN博客
如果电脑上面有运行内存大于8GB的GPU,可以在自己电脑上实现,关于如何配置在本人写的另一篇博客中:安装Pytorch-CSDN博客
2.使用Kaggle时需要注意
由于Kaggle平台不需要自己配置环境,可以认为是带GPU的希冀平台,但我们这次内容的实现对包的版本有严格的要求,很容易出现由于版本不符而代码出现错误的情况!!!(但是代码本身是没有任何错误的!)
一定要确保torchtext==0.6.0 sentencepiece==0.2.0
在导入必要的库之前,先执行命令
!pip install torchtext==0.6.0
(sentencepiece也是使用这样的命令)
然后再导入需要用到的库
然后执行命令
#import torchtext
torchtext.__version__
这是为了确保当前的kernal中使用的torchtext就是0.6.0 !!!(一定要查看,本人亲身经历,降了好几次的版本,0.6.0和0.4.0都下载了,后面的代码还是由于版本问题报错,导致浪费了很多时间)
如果执行命令发现torchtext的版本不是0.6.0,那么重新启动内核运行
再次执行上面的步骤,然后检查当前kernal使用的torchtext版本
如果torchtext版本不是0.6.0的话,会出现很多错误,出现的错误如下:
(1)"大小写"
解决办法:导入库时“from torchtext.vocab import Vocab”改为“from torchtext.vocab import vocab”,return Vocab(counter, specials=['<unk>', '<pad>', '<bos>', '<eos>'])改为return vocab(counter, specials=['<unk>', '<pad>', '<bos>', '<eos>'])
但是这个代码如果torchtext==0.6.0时是没有问题的,这仅仅是由于版本带来的问题!
(2)调用translate函数时:AttributeError: 'Vocab' object has no attribute 'stoi'
这个错误的解决方法,就是将torchtext的版本设为0.6.0
所以综合(1)(2)来看,事先将torchtext==0.6.0是最佳选择,也是必要选择!!!
除了上面两个错误之外,还会出现一个错误,不过不重要,无非就是索引号和对应的文字
直接将out_type='str'删去即可,这样输出的是索引号
如果要查看对应的文字(字段),可以使用下面的代码
input_ids = en_tokenizer.encode("All residents aged 20 to 59 years who live in Japan must enroll in public pension system.")
print(input_ids)
decoded_pieces = en_tokenizer.DecodeIds(input_ids)
print(decoded_pieces.split())
Part2 具体实现
一、数据预处理
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')
# print(torch.cuda.get_device_name(0)) ## 如果你有GPU,请在你自己的电脑上尝试运行这一套代码
device
2.获取并行数据集
数据集链接:https://pan.baidu.com/s/13A8GmB_IluGSXBnOqH4uIg?pwd=hrnp 提取码: hrnp
import pandas as pd
# 使用 Pandas 读取文本文件,分隔符为制表符 \t,使用 Python 引擎解析
df = pd.read_csv('zh-ja.bicleaner05.txt', sep='\\t', engine='python', header=None)
# 获取第2列(索引为2)作为中文训练数据,转换为列表
trainen = df[2].values.tolist() # 如果需要只获取部分数据,可以使用切片如 trainen[:10000]
# 获取第3列(索引为3)作为日文训练数据,转换为列表
trainja = df[3].values.tolist() # 如果需要只获取部分数据,可以使用切片如 trainja[:10000]
# 如果你需要移除特定索引(如索引为5972)的数据,可以使用 pop 方法
# trainen.pop(5972)
# trainja.pop(5972)
注:代码中的en<英文>理解为zh<中文>,因为我们的目的是日译中,而非日译英
#打印出 trainen 和 trainja 中索引为为500 的数据
print(trainen[500])
print(trainja[500])
![](https://img-blog.csdnimg.cn/direct/b3596da0d85d449da3d2d202c602b981.png)
3.准备分词器
链接: https://pan.baidu.com/s/1L-b9jiCzplj58k70MNUVuQ?pwd=k2e3 提取码: k2e3
与英语或其他字母语言不同,日语句子中不包含用于分隔单词的空格。我们可以使用JParaCrawl提供的分词器,该分词器是使用SentencePiece为日语和英语创建的,可以通过访问JParaCrawl网站来下载它们
(这里我们使用英文的分词器训练中文,大家可以选择其他的中文分词器)
#使用 SentencePiece 库来加载预训练的分词模型(模型文件名为 spm.en.nopretok.model 和 spm.ja.nopretok.model),
#并将它们分别赋值给 en_tokenizer 和 ja_tokenizer 变量
en_tokenizer = spm.SentencePieceProcessor(model_file='spm.en.nopretok.model')
ja_tokenizer = spm.SentencePieceProcessor(model_file='spm.ja.nopretok.model')
加载分词器模型之后,可以通过以下代码来测试它们
en_tokenizer.encode("所有的居民 aged 20 to 59 years who live in Japan must enroll in public pension system.", out_type='str')
ja_tokenizer.encode("年金 日本に住んでいる20歳~60歳の全ての人は、公的年金制度に加入しなければなりません。", out_type='str')
4.构建词汇表和张量
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_tokenizer)构建日语词汇表
ja_vocab = build_vocab(trainja, ja_tokenizer)
# 使用英语分词器(en_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
# 将训练数据 trainja 和 trainen 转换为张量形式
train_data = data_process(trainja, trainen)
5.创建用于训练时迭代的 DataLoader 对象
将 BATCH_SIZE 设置为 16,以防止“cuda out of memory”错误。但是实际的 BATCH_SIZE 可能会受到诸如计算机内存容量、数据大小等因素的影响,因此根据实际需求自由调整批处理大小
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
# 创建 DataLoader 对象,用于批处理训练数据
train_iter = DataLoader(train_data, batch_size=BATCH_SIZE,
shuffle=True, collate_fn=generate_batch)
二、transformer模型的实现
Transformer是一种Seq2Seq模型,用于解决机器翻译任务。Transformer模型包含一个编码器和一个解码器模块,每个模块都包含固定数量的层。
编码器通过一系列的多头注意力和前馈网络层处理输入序列。编码器的输出被称为“记忆”,与目标张量一起被传递给解码器。编码器和解码器使用教师强制技术进行端到端训练。
1.构建模型
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)
# 创建一个Transformer编码器,它包含多个编码器层
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)
# 创建一个Transformer解码器,它包含多个解码器层
self.transformer_decoder = TransformerDecoder(decoder_layer, num_layers=num_decoder_layers)
# 创建一个线性层作为生成器,用于最终输出预测的目标词汇
self.generator = nn.Linear(emb_size, tgt_vocab_size)
# 创建源语言和目标语言的token嵌入层
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 (Tensor): 输入源序列张量,形状为 (src_len, batch_size).
trg (Tensor): 输入目标序列张量,形状为 (trg_len, batch_size).
src_mask (Tensor): 源序列的填充遮挡张量,形状为 (src_len, src_len).
tgt_mask (Tensor): 目标序列的解码器遮挡张量,形状为 (trg_len, trg_len).
src_padding_mask (Tensor): 源序列的填充遮挡张量,形状为 (batch_size, src_len).
tgt_padding_mask (Tensor): 目标序列的填充遮挡张量,形状为 (batch_size, trg_len).
memory_key_padding_mask (Tensor): 编码器记忆的填充遮挡张量,形状为 (batch_size, src_len).
返回:
Tensor: 经过生成器线性层生成的输出张量,形状为 (trg_len, batch_size, tgt_vocab_size).
"""
# 对源语言序列进行嵌入和位置编码
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):
"""
编码器函数,用于对源语言序列进行编码。
参数:
src (Tensor): 输入源序列张量,形状为 (src_len, batch_size).
src_mask (Tensor): 源序列的填充遮挡张量,形状为 (src_len, src_len).
返回:
Tensor: 经过编码器处理后的记忆张量,形状为 (src_len, batch_size, emb_size).
"""
# 对源语言序列进行嵌入和位置编码,然后通过Transformer编码器处理
return self.transformer_encoder(self.positional_encoding(
self.src_tok_emb(src)), src_mask)
def decode(self, tgt: Tensor, memory: Tensor, tgt_mask: Tensor):
"""
解码器函数,用于对目标语言序列进行解码。
参数:
tgt (Tensor): 输入目标序列张量,形状为 (trg_len, batch_size).
memory (Tensor): 编码器记忆张量,形状为 (src_len, batch_size, emb_size).
tgt_mask (Tensor): 目标序列的解码器遮挡张量,形状为 (trg_len, trg_len).
返回:
Tensor: 经过解码器处理后的输出张量,形状为 (trg_len, batch_size, emb_size).
"""
# 对目标语言序列进行嵌入和位置编码,然后通过Transformer解码器处理
return self.transformer_decoder(self.positional_encoding(
self.tgt_tok_emb(tgt)), memory,
tgt_mask)
2.位置编码和词嵌入
文本标记通过使用标记嵌入来表示。为了引入词序的概念,位置编码被添加到标记嵌入中。
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.register_buffer('pos_embedding', pos_embedding)
# 定义dropout层
self.dropout = nn.Dropout(dropout)
def forward(self, token_embedding: torch.Tensor):
"""
前向传播函数
参数:
token_embedding (torch.Tensor): 形状为 (seq_len, batch_size, emb_size) 的输入张量
返回:
torch.Tensor: 添加了位置编码的张量,形状与输入张量相同 (seq_len, batch_size, emb_size)
"""
# 将位置编码与输入的token_embedding相加,并应用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: torch.Tensor):
"""
前向传播函数
参数:
tokens (torch.Tensor): 形状为 (seq_len, batch_size) 的输入张量,包含了token的索引
返回:
torch.Tensor: 形状为 (seq_len, batch_size, emb_size) 的词嵌入张量
"""
# 获取词嵌入,并按照论文中的建议对词嵌入进行缩放
return self.embedding(tokens.long()) * math.sqrt(self.emb_size)
3.生成掩码
创建后续词掩码,以阻止目标词汇关注其后续词汇。创建掩码,用于掩盖源和目标填充标记。
def generate_square_subsequent_mask(sz):
"""
生成一个下三角矩阵,用于屏蔽未来信息。
参数:
sz (int): 方阵的大小
返回:
torch.Tensor: 下三角掩码张量,形状为 (sz, sz),数据类型为 float
"""
# 生成全为1的方阵
mask = (torch.triu(torch.ones((sz, sz), device=device)) == 1).transpose(0, 1)
# 将掩码转换为浮点型,并将为0的位置用负无穷大替换,为1的位置用0.0替换
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
return mask
def create_mask(src, tgt):
"""
创建用于掩盖源和目标填充标记的掩码。
参数:
src (torch.Tensor): 形状为 (src_seq_len, batch_size) 的源张量
tgt (torch.Tensor): 形状为 (tgt_seq_len, batch_size) 的目标张量
返回:
torch.Tensor: 源掩码,形状为 (src_seq_len, src_seq_len),数据类型为 bool
torch.Tensor: 目标掩码,形状为 (tgt_seq_len, tgt_seq_len),数据类型为 float
torch.Tensor: 源填充标记掩码,形状为 (batch_size, src_seq_len),数据类型为 bool
torch.Tensor: 目标填充标记掩码,形状为 (batch_size, tgt_seq_len),数据类型为 bool
"""
src_seq_len = src.shape[0] # 获取源序列长度
tgt_seq_len = tgt.shape[0] # 获取目标序列长度
# 生成目标序列的掩码,防止目标单词关注其后续单词
tgt_mask = generate_square_subsequent_mask(tgt_seq_len)
# 创建一个全零张量作为源序列的掩码,形状为 (src_seq_len, src_seq_len)
src_mask = torch.zeros((src_seq_len, src_seq_len), device=device).type(torch.bool)
# 生成源序列的填充标记掩码,将填充标记的位置设为True
src_padding_mask = (src == PAD_IDX).transpose(0, 1)
# 生成目标序列的填充标记掩码,将填充标记的位置设为True
tgt_padding_mask = (tgt == PAD_IDX).transpose(0, 1)
return src_mask, tgt_mask, src_padding_mask, tgt_padding_mask
4.定义参数、实例化transformer模型并定义训练、评估函数
SRC_VOCAB_SIZE = len(ja_vocab) # 源语言词汇表大小
TGT_VOCAB_SIZE = len(en_vocab) # 目标语言词汇表大小
EMB_SIZE = 512 # 词嵌入维度
NHEAD = 8 # 头数(Transformer中的注意力头数)
FFN_HID_DIM = 512 # 前馈神经网络隐藏层维度
BATCH_SIZE = 16 # 批量大小
NUM_ENCODER_LAYERS = 3 # 编码器层数
NUM_DECODER_LAYERS = 3 # 解码器层数
NUM_EPOCHS = 16 # 训练轮数
# 初始化Seq2SeqTransformer模型
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) # 将模型移动到设备上
# 定义交叉熵损失函数,忽略填充标记的损失计算
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):
"""
训练模型一个epoch。
参数:
model (Seq2SeqTransformer): 要训练的Seq2SeqTransformer模型
train_iter (torch.utils.data.DataLoader): 训练数据迭代器
optimizer (torch.optim.Adam): 优化器
返回:
float: epoch的平均损失
"""
model.train() # 设置模型为训练模式
losses = 0
for idx, (src, tgt) in enumerate(train_iter):
src = src.to(device) # 将源语言数据移动到设备上
tgt = tgt.to(device) # 将目标语言数据移动到设备上
tgt_input = tgt[:-1, :] # 去除tgt最后一个标记,作为解码器的输入
# 创建源掩码、目标掩码和填充标记掩码
src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)
# 前向传播获取预测结果logits
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:, :] # 去除tgt第一个标记,作为损失计算的目标值
# 计算损失
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 (Seq2SeqTransformer): 要评估的Seq2SeqTransformer模型
val_iter (torch.utils.data.DataLoader): 验证数据迭代器
返回:
float: 验证集的平均损失
"""
model.eval() # 设置模型为评估模式
losses = 0
for idx, (src, tgt) in enumerate(val_iter):
src = src.to(device) # 将源语言数据移动到设备上
tgt = tgt.to(device) # 将目标语言数据移动到设备上
tgt_input = tgt[:-1, :] # 去除tgt最后一个标记,作为解码器的输入
# 创建源掩码、目标掩码和填充标记掩码
src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)
# 前向传播获取预测结果logits
logits = model(src, tgt_input, src_mask, tgt_mask,
src_padding_mask, tgt_padding_mask, src_padding_mask)
tgt_out = tgt[1:, :] # 去除tgt第一个标记,作为损失计算的目标值
# 计算损失
loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))
losses += loss.item() # 累加损失值
return losses / len(val_iter) # 返回平均损失
三、训练模型
在准备好必要的类和函数之后,开始训练模型
# 循环训练每个epoch
for epoch in tqdm.tqdm(range(1, NUM_EPOCHS + 1)):
start_time = time.time() # 记录当前epoch开始时间
# 训练一个epoch,并计算训练损失
train_loss = train_epoch(transformer, train_iter, optimizer)
end_time = time.time() # 记录当前epoch结束时间
# 打印当前epoch的训练损失和训练时间
print(f"Epoch: {epoch}, Train loss: {train_loss:.3f}, Epoch time = {(end_time - start_time):.3f}s")
四、使用训练后的模型翻译日语
我们需要创建一些函数来翻译一条新的句子,包括以下步骤:获取日语句子、进行分词、转换为张量、推理(inference),然后将结果解码回一个中文句子
def greedy_decode(model, src, src_mask, max_len, start_symbol):
"""
使用贪婪解码生成目标序列。
Args:
- model: Transformer模型
- src: 源语言张量序列
- src_mask: 源语言的掩码,用于遮盖填充部分
- max_len: 生成序列的最大长度
- start_symbol: 目标语言序列的起始符号索引
Returns:
- ys: 生成的目标语言序列张量
"""
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() # 转换为Python标量
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: Transformer模型
- src: 源语言句子(字符串)
- src_vocab: 源语言词汇表
- tgt_vocab: 目标语言词汇表
- src_tokenizer: 源语言的分词器
Returns:
- 翻译后的目标语言句子(字符串)
"""
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()
# 将生成的目标语言序列张量转换为字符串并去除特殊标记
translated_sentence = " ".join([tgt_vocab.itos[tok] for tok in tgt_tokens]).replace("<bos>", "").replace("<eos>", "")
return translated_sentence
调用 translate 函数进行翻译并传递所需的参数
#翻译
translate(transformer, "この報を受けたNHKの取材チームがすぐに調査を開始し、魚の研究者である国立科学博物館の松浦啓一氏が研究を行った。)", ja_vocab, en_vocab, ja_tokenizer)
trainen.pop(5) # 从 trainen 列表中移除索引为 5 的元素
trainja.pop(5) # 从 trainja 列表中移除索引为 5 的元素
五、保存Vocab对象和训练的模型
在训练完成后,我们首先使用Pickle保存Vocab对象(envocab和javocab)。
import pickle
# 打开一个文件,用于存储数据
file = open('en_vocab.pkl', 'wb')
# 使用pickle将英语词汇表(en_vocab)存储到文件中
pickle.dump(en_vocab, file)
# 关闭文件
file.close()
# 打开另一个文件,用于存储数据
file = open('ja_vocab.pkl', 'wb')
# 使用pickle将日语词汇表(ja_vocab)存储到文件中
pickle.dump(ja_vocab, file)
# 关闭文件
file.close()
使用PyTorch的保存和加载功能来保存模型以供以后使用。通常,根据我们稍后想要使用模型的方式,有两种保存模型的方法。第一种是仅用于推理,我们可以稍后加载模型并用它来从日语翻译成中文
# 保存用于推理(预测)的模型
torch.save(transformer.state_dict(), 'inference_model')
第二种方法也是用于推理(预测),但同时也适用于稍后加载模型并恢复训练的情况。
# 保存模型和检查点,以便稍后恢复训练
torch.save({
'epoch': NUM_EPOCHS, # 保存当前训练的轮数(epoch)
'model_state_dict': transformer.state_dict(), # 保存模型的状态字典,包括所有的参数
'optimizer_state_dict': optimizer.state_dict(), # 保存优化器的状态字典,包括所有的优化器参数
'loss': train_loss, # 保存当前训练的损失值
}, 'model_checkpoint.tar')