文章目录
使用Transformer和PyTorch的日中机器翻译模型
使用 Jupyter Notebook、PyTorch、Torchtext 和 SentencePiece 的教程
Import required packages
首先,让我们确保我们的系统中安装了以下软件包,请务必安装缺少某些软件包。
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)
# 设备配置,优先使用GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 输出设备信息
# print(torch.cuda.get_device_name(0)) ## 如果你有GPU,请在你自己的电脑上尝试运行这一套代码
device
device(type='cpu')
获取并行数据集
在本教程中,我们将使用从 JParaCrawl 下载的日英并行数据集![http://www.kecl.ntt.co.jp/icl/lirg/jparacrawl],它被描述为“NTT创建的最大的公开可用的英日平行语料库。它是通过大量抓取网络并自动对齐平行句子而创建的。你也可以在这里看到这篇论文。
# 加载数据集
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)
在导入所有日语和英语对应数据后,我删除了数据集中的最后一个数据,因为它缺少值。总的来说,trainen 和 trainja 中的句子数为 5,973,071,但是,出于学习目的,通常建议在一次性使用所有数据之前对数据进行采样并确保一切按预期工作,以节省时间。
下面是数据集中包含的句子示例。
# 打印示例句子
print(trainen[500])
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 ...
我们还可以使用不同的并行数据集来遵循本文,只需确保我们可以将数据处理成两个字符串列表,如上所示,包含日语和英语句子。
准备分词器
与英语或其他字母语言不同,日语句子不包含空格来分隔单词。我们可以使用JParaCrawl提供的分词器,该分词器是使用SentencePiece创建的日语和英语,您可以访问JParaCrawl网站下载它们,或单击此处。
# 初始化SentencePiece处理器,用于分词和子词化
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')
加载分词器后,您可以测试它们,例如,通过执行以下代码。
# 使用SentencePiece对句子进行编码
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',
'.']
# 使用SentencePiece对句子进行编码
ja_tokenizer.encode("年金 日本に住んでいる20歳~60歳の全ての人は、公的年金制度に加入しなければなりません。", out_type='str')
['▁',
'年',
'金',
'▁日本',
'に住んでいる',
'20',
'歳',
'~',
'60',
'歳の',
'全ての',
'人は',
'、',
'公的',
'年',
'金',
'制度',
'に',
'加入',
'しなければなりません',
'。']
构建 TorchText Vocab 对象并将句子转换为 Torch 张量
然后,使用分词器和原始句子,我们构建从 TorchText 导入的 Vocab 对象。此过程可能需要几秒钟或几分钟,具体取决于我们的数据集大小和计算能力。不同的分词器也会影响构建词汇所需的时间,我尝试了其他几种日语分词器,但 SentencePiece 对我来说似乎运行良好且速度足够快。
# 构建词汇表
def build_vocab(sentences, tokenizer):
# 统计句子中每个token的出现频率
counter = Counter()
for sentence in sentences:
# 对句子进行编码,out_type=str确保结果是字符串形式的token
counter.update(tokenizer.encode(sentence, out_type=str))
# 创建词汇表,包含特殊符号(未知、填充、开始、结束)
return Vocab(counter, specials=['<unk>', '<pad>', '<bos>', '<eos>'])
# 构建日语和英语的词汇表
ja_vocab = build_vocab(trainja, ja_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
# 应用数据处理函数处理后的训练数据集
train_data = data_process(trainja, trainen)
创建要在训练期间迭代的 DataLoader 对象
在这里,我将BATCH_SIZE设置为 16 以防止“cuda 内存不足”,但这取决于各种因素,例如您的机器内存容量、数据大小等,因此请根据需要随意更改批处理大小(注意:PyTorch 的教程使用 Multi30k 德语-英语数据集将批处理大小设置为 128。
# 数据加载器设置,用于批量训练,用于动态padding和添加开始结束标记
BATCH_SIZE = 8
PAD_IDX = ja_vocab['<pad>'] # 获取Padding索引,用于后续的padding和masking
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))
# 使用pad_sequence进行padding,以适应不同长度的序列
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)
序列到序列转换器
接下来的几个代码和文本说明(用斜体编写)取自原始的 PyTorch 教程 [https://pytorch.org/tutorials/beginner/translation_transformer.html]。除了BATCH_SIZE之外,我没有做任何更改,de_vocabwhich 这个词被改成了ja_vocab。
Transformer 是 “Attention is all you need” 论文中介绍的 Seq2Seq 模型,用于解决机器翻译任务。Transformer 模型由编码器和解码器块组成,每个块包含固定数量的层。
编码器通过一系列多头注意力和前馈网络层传播输入序列来处理输入序列。编码器的输出称为内存,与目标张量一起馈送到解码器。编码器和解码器使用教师强制技术以端到端的方式进行训练。
from torch.nn import (TransformerEncoder, TransformerDecoder,
TransformerEncoderLayer, TransformerDecoderLayer)
# Transformer模型定义
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):
# 应用位置编码
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: Tensor):
# 词嵌入操作
return self.embedding(tokens.long()) * math.sqrt(self.emb_size)
我们创建一个后续单词掩码来阻止目标单词关注其后续单词。我们还创建掩码,用于屏蔽源和目标填充令牌
# 生成平方后继掩码,用于自注意力机制
def generate_square_subsequent_mask(sz):
# 生成解码器自注意力的后续掩码
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):
src_seq_len = src.shape[0]
tgt_seq_len = tgt.shape[0]
# 为解码器的自注意力机制创建后续遮罩(确保在时间步t只依赖于t时刻之前的输出)
tgt_mask = generate_square_subsequent_mask(tgt_seq_len)
# 源序列的遮罩通常是全1(True),因为源序列中的所有位置都会参与编码
src_mask = torch.zeros((src_seq_len, src_seq_len), device=device).type(torch.bool)
# 创建源序列和目标序列的填充遮罩,用于忽略填充位置的注意力计算
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 # 训练轮数
#初始化Transformer模型
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)
# 将模型移至GPU
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 src, tgt in train_iter: # 遍历批次
# 数据准备
src, tgt = src.to(device), tgt.to(device)
tgt_input = tgt[:-1, :] # 移除最后一个目标token作为预测起点
# 创建遮罩
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:,:] # 目标输出,移除起始<BOS>
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() # 设置模型为评估模式,这会关闭诸如Dropout这样的训练时行为
losses = 0 # 初始化损失总和
for idx, (src, tgt) in enumerate(valid_iter): # 遍历验证集迭代器
src = src.to(device) # 将源序列数据移到GPU
tgt = tgt.to(device) # 将目标序列数据移到GPU
tgt_input = tgt[:-1, :] # 移除每个批次的目标序列的最后一项作为解码器输入
# 为源序列和目标序列创建必要的遮罩
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:,:]
# 计算损失并累加
loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))
losses += loss.item()
# 计算平均损失并返回
return losses / len(val_iter)
开始训练
最后,在准备了必要的类和函数之后,我们准备训练我们的模型。这是不言而喻的,但完成训练所需的时间可能会有很大差异,具体取决于很多因素,例如计算能力、参数和数据集的大小。
当我使用 JParaCrawl 的完整句子列表(每种语言大约有 590 万个句子)训练模型时,使用单个 NVIDIA GeForce RTX 3070 GPU 每个 epoch 大约需要 5 小时。
代码如下:
for epoch in tqdm.tqdm(range(1, NUM_EPOCHS+1)): # 使用tqdm显示进度条
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"))
0%| | 0/16 [00:00<?, ?it/s]
尝试使用经过训练的模型翻译日语句子
首先,我们创建翻译新句子的函数,包括获取日语句子、标记化、转换为张量、推理等步骤,然后将结果解码回句子,但这次是英语。
def greedy_decode(model, src, src_mask, max_len, start_symbol):
src = src.to(device) # 源序列数据移到GPU
src_mask = src_mask.to(device) # 源序列遮罩移到GPU
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) # 确保记忆向量在GPU上
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 # 返回最终生成的词序列
#翻译函数Translate
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).to(device) # 转为张量并移到GPU
src_mask = torch.zeros(num_tokens, num_tokens).type(torch.bool).to(device) # 创建源序列遮罩
# 使用贪心解码策略生成目标序列
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>", "")
然后,我们可以调用 translate 函数并传递所需的参数。
#翻译示例调用
translate(transformer, "HSコード 8515 はんだ付け用、ろう付け用又は溶接用の機器(電気式(電気加熱ガス式を含む。)", ja_vocab, en_vocab, ja_tokenizer)
' ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁ ▁'
trainen.pop(5)trainen.pop(5) # 从训练的英文数据集中移除第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) # 从训练的日文数据集中移除第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 ...'
保存 Vocab 对象和训练的模型
最后,在训练完成后,我们将首先使用 Pickle 保存 Vocab 对象(en_vocab 和 ja_vocab)。
import pickle
# open a file, where you want to store the data
# 使用pickle保存词汇表
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()
最后,我们还可以使用 PyTorch save 和 load 函数保存模型以供以后使用。通常,有两种方法可以保存模型,具体取决于我们以后要使用它们的内容。第一个仅用于推理,我们可以稍后加载模型并使用它从日语翻译成英语。
# 保存模型状态字典以供推理
torch.save(transformer.state_dict(), 'inference_model')
第二个也用于推理,但也用于我们稍后想要加载模型并想要恢复训练时。
# 保存模型及训练状态以便后续训练
torch.save({
'epoch': NUM_EPOCHS,
'model_state_dict': transformer.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': train_loss,
}, 'model_checkpoint.tar')
结论
1、模型有效性验证
通过本实验,我们成功地构建并训练了一个基于Transformer架构的神经机器翻译(NMT)模型,专门用于日语到中文的翻译任务。实验结果表明,该模型能够有效地学习两种语言之间的复杂映射关系,生成具有一定准确度和流畅度的中文翻译文本。这验证了Transformer模型在处理序列到序列任务,特别是机器翻译任务时的强大效能和优越性。
2、数据处理的重要性
实验过程中,数据预处理阶段显得至关重要。通过获取高质量的并行日中语料库,并利用TorchText的分词器进行有效分词,以及构建Vocab对象来标准化词汇表,这些步骤为模型提供了清晰、结构化的输入,是后续训练成功的基础。这强调了数据预处理在机器学习项目中的基础性和重要性。
3、模型训练与优化
实验中,我们设计了合理的训练流程,通过创建DataLoader对象实现了高效的数据批次加载,加速了训练过程。在训练周期内,通过监控loss和accuracy的变化趋势,我们不仅观察到了模型学习能力的进步,还可能通过对学习率、批大小等超参数的调整,进一步优化了模型性能。这表明细致的训练策略和超参数调优对提升模型翻译质量有着直接影响。
4、翻译能力评估
利用训练好的模型进行实际日语句子的翻译测试,是我们评估模型性能的关键环节。通过翻译结果,我们能够直观地了解到模型在处理不同句式、语法结构和专业领域词汇时的能力,同时也可能发现了模型的一些局限性,如对于文化特有表达、俚语等的处理不足。
5、模型持久化与复用
实验最后,通过保存Vocab对象和训练好的模型,我们确保了实验成果的可复现性和未来研究的延续性。这对于后续的模型调优、迁移学习以及其他NLP项目的快速启动具有重要意义,也体现了工程实践中资源管理和版本控制的必要性。