在训练模型部分,仅仅使用cpu的话训练速度会慢如蜗牛,建议搭建GPU环境进行训练。
Firstly
首先,确保我们的系统中安装了以下软件包.
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,请在你自己的电脑上尝试运行这一套代码
Secondly
获取并行数据集 我们将使用从JParaCrawl![www.kecl.ntt.co.jp/icl/lirg/jp…]] 日语-英语并行数据集,该数据集被描述为“NTT创建的最大的公开可用的英语-日语并行语料库”。它主要是通过抓取网络并自动对齐平行句子创建的。”
df = pd.read_csv('./zh-ja.bicleaner05.txt', sep='\t', engine='python', header=None)
# 从DataFrame df中提取第三列的数据,即索引为2的列,因为索引从0开始
# 然后将这些数据转换为NumPy数组,最后转换为Python列表
trainen = df[2].values.tolist()#[:10000]
# 从DataFrame df中提取第四列的数据,即索引为3的列
# 然后将这些数据转换为NumPy数组,最后转换为Python列表
trainja = df[3].values.tolist()#[:10000]
# 如果取消注释,将移除英文和日文列表中索引为5972的元素
# trainen.pop(5972)
# trainja.pop(5972)
在导入所有日语和英语对应项之后,我删除了数据集中的最后一个数据,因为它有一个缺失的值。总的来说,trainen和trainja中的句子数量都是5,973,071,然而,出于学习目的,通常建议在一次使用所有数据之前对数据进行采样并确保一切正常工作,以节省时间。 接下来看一下包含在此数据集的句子示例。
print(trainen[500])
# 打印日文训练数据列表中的第501个元素
print(trainja[500])
我们将得到以下输出:
Chinese HS Code Harmonized Code System < HS编码 2905 无环醇及其卤化、磺化、硝化或亚硝化衍生物 HS Code List (Harmonized System Code) for US, UK, EU, China, India, France, Japan, Russia, Germany, Korea, Canada ...
Japanese HS Code Harmonized Code System < HSコード 2905 非環式アルコール並びにそのハロゲン化誘導体、スルホン化誘導体、ニトロ化誘導体及びニトロソ化誘導体 HS Code List (Harmonized System Code) for US, UK, EU, China, India, France, Japan, Russia, Germany, Korea, Canada ...
我们也可以使用不同的并行数据集来跟随本文,只要确保我们可以将数据处理成如上所示的两个字符串列表,其中包含日语和英语句子。
Thirdly
准备标记器,与英语或其他按字母顺序排列的语言不同,日语句子不包含空格来分隔单词。我们可以使用JParaCrawl提供的标记器,它是使用sentencepece为日语和英语创建的,您可以访问JParaCrawl网站下载它们,或者点击这里。
# 创建一个英文分词器对象
# 使用SentencePieceProcessor类加载预训练的英文模型文件
en_tokenizer = spm.SentencePieceProcessor(model_file='spm.en.nopretok.model')
# 创建一个日文分词器对象
# 使用SentencePieceProcessor类加载预训练的日文模型文件
ja_tokenizer = spm.SentencePieceProcessor(model_file='spm.ja.nopretok.model')
'''
编码文本:将句子或文档转换为SentencePiece模型理解的词片段序列。
en_pieces = en_tokenizer.EncodeAsPieces("Your English sentence here.")
ja_pieces = ja_tokenizer.EncodeAsPieces("あなたの日本語の文ここに入ります。")
解码词片段:将编码后的词片段序列转换回原始文本。
en_sentence = en_tokenizer.DecodePieces(en_pieces)
ja_sentence = ja_tokenizer.DecodePieces(ja_pieces)
获取词汇表:访问SentencePiece模型的词汇表。
en_vocab = en_tokenizer.GetVocab()
ja_vocab = ja_tokenizer.GetVocab()
获取模型的ID:将词汇表中的单词转换为模型内部的ID。
en_ids = en_tokenizer.EncodeAsIds(en_sentence)
ja_ids = ja_tokenizer.EncodeAsIds(ja_sentence)
'''
搞定标记器之后,可以测试它们,例如,通过执行下面的代码。
en_tokenizer.encode("All residents aged 20 to 59 years who live in Japan must enroll in public pension system.", out_type='str')
得到以下输出:
['▁All', '▁residents', '▁aged', '▁20', '▁to', '▁59', '▁years', '▁who', '▁live', '▁in', '▁Japan', '▁must', '▁enroll', '▁in', '▁public', '▁pension', '▁system', '.']
#%%
ja_tokenizer.encode("年金 日本に住んでいる20歳~60歳の全ての人は、公的年金制度に加入しなければなりません。", out_type='str')
得到如下输出:
['▁', '年', '金', '▁日本', 'に住んでいる', '20', '歳', '~', '60', '歳の', '全ての', '人は', '、', '公的', '年', '金', '制度', 'に', '加入', 'しなければなりません', '。']
Fourth
建立TorchText词汇对象并将句子转换为Torch张量,使用标记器和原始句子,然后构建从TorchText导入的Vocab对象。根据数据集的大小和计算能力,这个过程可能需要几秒钟或几分钟。不同的标记器也会影响构建词汇所需的时间,我尝试了其他几个日语标记器,但sensenepece似乎工作得很好,对我们来说足够快。
# 定义构建词汇表的函数build_vocab
def build_vocab(sentences, tokenizer):
# 初始化一个Counter对象,用于计数每个词片段出现的次数
counter = Counter()
# 遍历输入的句子列表
for sentence in sentences:
# 使用tokenizer对当前句子进行编码,得到词片段序列
# encode方法将句子分解为词片段,并且out_type=str参数确保输出是字符串格式
# 然后将这些词片段更新到计数器counter中
counter.update(tokenizer.encode(sentence, out_type=str))
# 根据计数器counter创建一个Vocab对象,表示词汇表
# 特殊词片段'<unk>'、'<pad>'、'<bos>'、'<eos>'被添加到词汇表中
# '<unk>'代表未知词,'<pad>'代表填充符,'<bos>'代表句子开始,'<eos>'代表句子结束
return Vocab(counter, specials=['<unk>', '<pad>', '<bos>', '<eos>'])
# 使用build_vocab函数为日文训练数据trainja构建词汇表,使用ja_tokenizer作为分词器
ja_vocab = build_vocab(trainja, ja_tokenizer)
# 使用build_vocab函数为英文训练数据trainen构建词汇表,使用en_tokenizer作为分词器
en_vocab = build_vocab(trainen, en_tokenizer)
在我们有了词汇表对象之后,我们可以使用词汇表和标记器对象来为我们的训练数据构建张量。
def data_process(ja, en):
# 初始化一个空列表,用于存储处理后的数据
data = []
# 使用zip函数将日文和英文句子配对,并遍历这些配对
for (raw_ja, raw_en) in zip(ja, en):
# 使用rstrip方法去除可能存在的换行符\n
# 使用ja_tokenizer对日文句子进行编码,然后将编码后的词片段转换为词汇表索引
# 将索引列表转换为torch长整型张量
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列表中
data.append((ja_tensor_, en_tensor_))
# 返回处理后的数据列表
return data
# 使用data_process函数处理训练数据,并存储结果
train_data = data_process(trainja, trainen)
Fifth
创建要在训练期间迭代的DataLoader对象,在这里,我将BATCH_SIZE设置为16以防止“cuda内存不足”,但这取决于很多因素,例如您的机器内存容量,数据大小等,因此可以根据您的需要随意更改批大小(注意:PyTorch的教程使用Multi30k德语-英语数据集将批大小设置为128)。
# 定义批量大小
BATCH_SIZE = 8
# 从日文词汇表中获取'<pad>'、'<bos>'和'<eos>'标记的索引
PAD_IDX = ja_vocab['<pad>']
BOS_IDX = ja_vocab['<bos>']
EOS_IDX = ja_vocab['<eos>']
# 导入torch库中的DataLoader类和pad_sequence函数
from torch.utils.data import DataLoader
from torch.nn.utils.rnn import pad_sequence
import torch
# 定义生成批次数据的函数generate_batch
def generate_batch(data_batch):
# 初始化存储处理后的日文和英文批次数据的列表
ja_batch, en_batch = [], []
# 遍历数据批次中的每个句子对
for (ja_item, en_item) in data_batch:
# 将'<bos>'和'<eos>'标记添加到日文句子的开始和结束
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))
# 使用pad_sequence函数对日文和英文批次数据进行填充,使它们具有相同的长度
# padding_value设置为PAD_IDX,即'<pad>'标记的索引
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_data作为数据源,批量大小设置为BATCH_SIZE,打乱数据顺序
# collate_fn设置为generate_batch,用于将多个数据样本组合成一个批次
train_iter = DataLoader(train_data, batch_size=BATCH_SIZE,
shuffle=True, collate_fn=generate_batch)
Sequence-to-sequence Transformer
接下来的代码和文本解释(以斜体书写)来自原始的PyTorch教程[pytorch.org/tutorials/b… 除了BATCH_SIZE和单词de_vocab被更改为ja_vocab之外,我没有做任何更改。 Transformer是在“Attention is all you need”论文中提出的用于解决机器翻译任务的Seq2Seq模型。变压器模型由编码器和解码器块组成,每个块包含固定数量的层。 编码器通过一系列多头注意和前馈网络层对输入序列进行传播处理。编码器的输出称为 存储器,与目标张量一起馈送到解码器。编码器和解码器以端到端方式使用强迫学习技术进行训练。
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)
# 创建源语言和目标语言的词嵌入层
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))
# 通过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):
# 定义编码函数,对源语言序列进行编码
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)
# 创建一个表示位置的张量,从0到maxlen-1
pos = torch.arange(0, maxlen).reshape(maxlen, 1)
# 初始化位置编码矩阵,大小为(maxlen, emb_size)
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)
# 添加Dropout层
self.dropout = nn.Dropout(dropout)
# 注册位置编码为一个缓冲区,这样就不会在反向传播中计算梯度
self.register_buffer('pos_embedding', pos_embedding)
def forward(self, token_embedding: torch.Tensor):
# 将位置编码添加到词嵌入张量,并应用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):
# 获取词嵌入,并进行缩放以稳定训练过程
return self.embedding(tokens.long()) * math.sqrt(self.emb_size)
我们创建一个后续单词掩码来阻止目标单词关注它的后续单词。我们还创建遮罩,用于屏蔽源和目标填充令牌。
def generate_square_subsequent_mask(sz):
# 创建一个上三角矩阵,用于后续步骤的掩码
# torch.triu函数创建一个上三角矩阵,torch.ones填充1
mask = (torch.triu(torch.ones((sz, sz), device=device)) == 1).transpose(0, 1)
# 将上三角矩阵转换为float类型,然后使用masked_fill修改值为负无穷和0
# masked_fill的第一个条件将0变为负无穷(用于在softmax中将概率置零)
# 第二个条件将1变为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_seq_len = src.shape[0]
tgt_seq_len = tgt.shape[0]
# 为目标语言创建后续步骤掩码
tgt_mask = generate_square_subsequent_mask(tgt_seq_len)
# 源语言掩码通常不需要后续步骤掩码,所以初始化为全0布尔矩阵
src_mask = torch.zeros((src_seq_len, src_seq_len), device=device).type(torch.bool)
# 创建源语言和目标语言的填充掩码
# 通过比较是否等于填充索引(PAD_IDX)来生成布尔矩阵
src_padding_mask = (src == PAD_IDX).transpose(0, 1)
tgt_padding_mask = (tgt == PAD_IDX).transpose(0, 1)
return src_mask, tgt_mask, src_padding_mask, tgt_padding_mask
按照以下配置可以训练但是效果应该是不行的。如果想要看到训练的效果请使用你自己的带GPU的电脑运行这一套代码。
当你使用自己的GPU的时候,NUM_ENCODER_LAYERS 和 NUM_DECODER_LAYERS 设置为3或者更高,NHEAD设置8,EMB_SIZE设置为512。
# 定义源语言和目标语言的词汇表大小
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
# 创建 Seq2SeqTransformer 模型实例
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)
# 将模型移动到指定的设备(CPU或GPU)
transformer = transformer.to(device)
# 定义损失函数,使用CrossEntropyLoss,并忽略填充索引
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):
# 将模型设置为训练模式
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(val_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)):
# 记录 epoch 开始时间
start_time = time.time()
# 执行训练并获取平均损失
train_loss = train_epoch(transformer, train_iter, optimizer)
# 记录 epoch 结束时间
end_time = time.time()
# 打印当前 epoch 编号、平均训练损失和所需时间
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):
# 将编码的记忆数据移动到设备
memory = memory.to(device)
# 创建解码时的掩码,防止未来信息流入
tgt_mask = generate_square_subsequent_mask(ys.size(0)).to(device).type(torch.bool)
# 解码当前步骤的输出
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):
# 将模型设置为评估模式,这会关闭Dropout等只在训练时使用的层
model.eval()
# 使用分词器对源语言文本进行编码,生成词片段列表
# 并在列表的开始和结束分别添加BOS_IDX和EOS_IDX标记
tokens = [BOS_IDX] + [src_vocab.stoi[tok] for tok in src_tokenizer.encode(src, out_type=str)] + [EOS_IDX]
# 计算编码后词片段的数量
num_tokens = len(tokens)
# 将词片段列表转换为一个形状为(num_tokens, 1)的PyTorch长整型张量
src = torch.LongTensor(tokens).reshape(num_tokens, 1)
# 创建一个形状为(num_tokens, num_tokens)的源掩码
# 掩码用于在模型处理时忽略非对角线元素(即忽略序列中的填充元素)
src_mask = torch.zeros(num_tokens, num_tokens).type(torch.bool)
# 调用greedy_decode函数进行翻译,生成目标语言的词汇索引序列
# max_len参数设置为源语言序列长度加5,以确保有足够的空间生成完整的翻译
# start_symbol设置为BOS_IDX,表示解码从BOS标记开始
tgt_tokens = greedy_decode(model, src, src_mask, max_len=num_tokens + 5, start_symbol=BOS_IDX).flatten()
# 将目标语言的词汇索引序列转换为词汇字符串序列
# 并替换掉序列中的<bos>和<eos>标记,以得到最终的翻译文本
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 ▁ 代 码 ▁85 15 ▁ 焊 接 设 备 ( 包 括 电 气 加 热 ) 。 '
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 ...'
Finally
保存Vocab对象和训练好的模型 最后,在训练完成后,我们将首先使用Pickle保存Vocab对象(en_vocab和ja_vocab)。以上翻译结果来自有道神经网络翻译(YNMT)· 通用场景
import pickle # 导入Python的pickle模块,用于序列化和反序列化对象
# 打开一个文件用于存储英文词汇表数据
file = open('en_vocab.pkl', 'wb')
# 使用pickle.dump将英文词汇表对象en_vocab序列化并写入文件
pickle.dump(en_vocab, file)
# 关闭文件,释放资源
file.close()
# 打开另一个文件用于存储日文词汇表数据
file = open('ja_vocab.pkl', 'wb')
# 使用pickle.dump将日文词汇表对象ja_vocab序列化并写入文件
pickle.dump(ja_vocab, file)
# 关闭文件,释放资源
file.close()
最后,我们还可以使用PyTorch保存和加载函数保存模型以供以后使用。通常,有两种保存模型的方法,这取决于我们以后想要使用它们的目的。第一个仅用于推理,我们可以稍后加载模型并使用它从日语翻译成英语。
# save model for inference
torch.save(transformer.state_dict(), 'inference_model')
第二个也是用于推理的,但当我们稍后想要加载模型并想要恢复训练时也是如此。
# 保存模型和检查点,以便之后可以恢复训练
# 使用PyTorch的torch.save函数来序列化并保存模型和优化器的状态
torch.save({
'epoch': NUM_EPOCHS, # 保存当前训练的epoch编号
'model_state_dict': transformer.state_dict(), # 保存模型的所有参数
'optimizer_state_dict': optimizer.state_dict(), # 保存优化器的状态
'loss': train_loss, # 保存当前epoch的训练损失
}, 'model_checkpoint.tar') # 将这些状态保存到名为'model_checkpoint.tar'的文件中