赞
踩
目录
4.定义参数、实例化transformer模型并定义训练、评估函数
这个需要用到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博客
由于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())
- #导入需要的库
- 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
数据集链接: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://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')
- 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)
将 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是一种Seq2Seq模型,用于解决机器翻译任务。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)
- # 创建一个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)
文本标记通过使用标记嵌入来表示。为了引入词序的概念,位置编码被添加到标记嵌入中。
- 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)
创建后续词掩码,以阻止目标词汇关注其后续词汇。创建掩码,用于掩盖源和目标填充标记。
- 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
- 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 的元素
在训练完成后,我们首先使用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')
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。