nlp实战--transformer--机器翻译

transformer--机器翻译

1.数据集和预处理

首先要导入必要的库。

我们使用“zh-ja.bicleaner05.txt”文件作为我们中日翻译的训练数据,共83892条文本

与英语或其他字母语言不同,日语句子中不包含用于分隔单词的空格。我们可以使用JParaCrawl提供的分词器,该分词器是使用SentencePiece为日语和英语创建的(我们用英文的分词器来训练中文,大家可以自行选择更好的中文分词器),可以通过访问JParaCrawl网站来下载它们

我使用在JParaCrawl找的中日数据集,其中83892条文本,同时在网站寻找的中文和日本的分词器模型。

##########文本编码和解码
import sentencepiece as spm
en_tokenizer = spm.SentencePieceProcessor(model_file='spm.en.nopretok.model')#加载中文分词模型文件
ja_tokenizer = spm.SentencePieceProcessor(model_file='spm.ja.nopretok.model')#加载日文分词模型文件
​
# 示例英文文本编码和解码
encoded_sentence = en_tokenizer.encode("All residents aged 20 to 59 years who live in Japan must enroll in public pension system.")
print(encoded_sentence)
decoded_sentence = en_tokenizer.decode(encoded_sentence)
print(decoded_sentence)
​
# 示例日文文本编码和解码
encoded_sentence = ja_tokenizer.encode("年金 日本に住んでいる20歳~60歳の全ての人は、公的年金制度に加入しなければなりません。")
print(encoded_sentence)
decoded_sentence = ja_tokenizer.decode(encoded_sentence)
print(decoded_sentence)
​
# 示例中文文本编码和解码
encoded_sentence = en_tokenizer.encode("在推荐算法刚刚兴起的时候,基本上只要懂一些推荐系统算法的理论,就能够找到一份推荐算法相关的工作。")
print(encoded_sentence)
decoded_sentence = en_tokenizer.decode(encoded_sentence)
print(decoded_sentence)
​
[227, 2980, 8863, 373, 8, 9381, 126, 91, 649, 11, 93, 240, 19228, 11, 419, 14926, 102, 5]
All residents aged 20 to 59 years who live in Japan must enroll in public pension system.
[4, 31, 346, 912, 10050, 222, 1337, 372, 820, 4559, 858, 750, 3, 13118, 31, 346, 2000, 10, 8978, 5461, 5]
年金 日本に住んでいる20歳~60歳の全ての人は、公的年金制度に加入しなければなりません。
[41, 19501, 24796, 28566, 25502, 23937, 27980, 27980, 28344, 24742, 10312, 25133, 25323, 4, 24684, 11447, 19552, 25988, 23456, 30515, 14168, 25341, 24796, 28566, 24495, 26520, 25502, 23937, 10312, 24445, 25724, 4, 24653, 24541, 27528, 26540, 24613, 14168, 26015, 24796, 28566, 25502, 23937, 24647, 25086, 10312, 24351, 19376, 3669]
在推荐算法刚刚兴起的时候,基本上只要懂一些推荐系统算法的理论,就能够找到一份推荐算法相关的工作。
2.构建词汇表和数据预处理

同样在遍历数据集后,根据词出现的词频来建立词汇表,同时对句子添加特殊标记。当我们有了词汇表对象后,接下来我们可以使用这些词汇表和分词器对象来为训练数据构建张量。

#######使用torchtext.vocab---vocab构建源语言和目标语言的词汇表对象。
#词汇表将token映射到对应的索引,并添加特殊标记(如<unk>, <pad>, <bos>, <eos>)以处理未知标记、填充、句子起始和结束等情况。
# 定义一个构建词汇表的函数
def build_vocab(sentences, tokenizer):
    """
    根据给定的句子列表和分词器构建词汇表。
    参数:
    sentences -- 列表,包含多个句子字符串。
    tokenizer -- 一个分词器,能够将句子编码为单词或子词的标识符。
    返回:
    Vocab -- 一个词汇表对象,它包含句子中所有单词的频率信息,以及特殊标记。
    这个函数首先创建一个Counter对象来计算每个单词或子词的出现次数。
    然后,它遍历提供的句子列表,使用分词器对每个句子进行编码,并更新Counter。
    最后,它使用Counter创建一个Vocab对象,这个对象还会添加几个特殊标记,
    例如 '<unk>' 表示未知单词,'<pad>' 表示填充,'<bos>' 表示句子的开始,'<eos>' 表示句子的结束。
    """
    counter = Counter()
    for sentence in sentences:
        # 使用分词器编码句子,并更新计数器
        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)

数据处理函数,将文本里的每一个字符通过分词器模型转换为张量形式,然后拼接在一起得到输出,得到输入数据和标签数据。然后生成批处理数据函数,在这个过程中通过特殊标记符,在每个句子的开始和结尾添加特殊标记,同时使用pad_sequence对批次进行填充,翻译中可能会出现可变长度的句子,使用填充标记符将句子填充到一样的长度。并加载dataloder,batch_size=16,设置打乱和批处理函数。

# 数据处理函数,将文本转换为张量形式
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
# 使用 data_process 函数处理训练数据 (trainja 和 trainen),并将处理后的数据存储在 train_data 中。
train_data = data_process(trainja, trainen)
##使用torch.utils.data.DataLoader创建用于训练和评估的批处理数据。
# 设置批处理大小和填充索引
# 设置批量大小
​
BATCH_SIZE = 16
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))
    # 使用pad_sequence对批次进行填充
    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)
3.transformers算法构建

通过torch.nn库导入必要的函数定义,transformer的Encoder,Decoder,EncoderLayer,DecoderLayer。进行自定义的PositionalEncoding,TokenEmbedding,Seq2SeqTransformer,同时自定义掩码函数generate_square_subsequent_mask和create_mask,以及填充的掩码项

from torch.nn import (TransformerEncoder, TransformerDecoder,TransformerEncoderLayer, TransformerDecoderLayer)

src_mask用于遮盖序列中不需要关注的位置,通常用于处理填充序列。 src_key_padding_mask用于指定哪些位置的值需要被忽略,通常用于处理输入序列中的无效位置

同时设置原词汇表大小和目标词汇表大小,并设置嵌入层是512,注意力机制的头数为8,前馈网络隐藏层也为8,批量大小为16,,设置编码器和解码器的层数为3,同时来创建Seq2SeqTransformer模型实例,将模型的参数进行xavier_uniform_初始化,使用Adam优化器,以及交叉熵损失函数。使用交叉熵损失函数进行评估。

# 设置源语言词汇表大小
src_vocab_size = len(ja_vocab)
# 设置目标语言词汇表大小
tgt_vocab_size = len(en_vocab)
# 设置嵌入层大小
embeding_size = 512
# 设置多头注意力机制的头数
NHEAD = 8
# 设置前馈神经网络隐藏层维度
ffn_hid_dim = 512
# 设置批量大小
BATCH_SIZE = 16
# 设置编码器层数
NUM_encoder_layers = 3
# 设置解码器层数
NUM_decoder_layers = 3
# 创建Seq2SeqTransformer模型实例
transformer = Seq2SeqTransformer(NUM_encoder_layers, NUM_decoder_layers,
                                embeding_size, src_vocab_size, tgt_vocab_size,
                                ffn_hid_dim)
​
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)
optimizer = torch.optim.Adam(transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)

4.网络训练

自定义一个train_epoch函数作为训练函数,经过dataloder输出的src和tgt原文和译文同时生成掩码,经过transformer结构得到[tgt.size(0),batches,tgt_vocab]的输出,经过10epochs的迭代,并返回训练损失值。

# 训练一个epoch的函数
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)
5.测试翻译

测试翻译时,定义一个评估函数,首先利用分词模型将src进行分词得到序列的token,并转化成张量tensor,在翻译时将掩码设置成二维的[len(token),len(token)]矩阵。然后使用贪婪搜索解码函数,对于目标序列的tensor进行逐字翻译。经过编码层后得到memory,在对每个字进行翻译时,生成memory的掩码为[ys.shape[0],memory.shape[0]],tgt的掩码为[ys.shape[0],ys.shape[0]],同时为对角线和下三角为1,上三角为0的掩码矩阵,防止泄露信息,通过generator完成概率归一化输出最终预测结果,选择概率最大的单词作为next_word,并将next_word添加到目标序列中,一直到解码结束。

# 贪婪解码函数
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
    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)##根据序列逐字输出##---[i,1,d_model]
​
        out = out.transpose(0, 1)
        prob = model.generator(out[:, -1])##完成概率归一化输出最终预测结果#---[1,tgt_vocab]
​
        #选择概率最大的单词作为下一个单词
        _, 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)##--[i,1]
        # 如果遇到结束符,则停止生成
        if next_word == EOS_IDX:
            break
    return ys
​
#翻译函数
def translate(model, src, src_vocab, tgt_vocab, src_tokenizer):
    model.eval()
    # 将源文本转换为源序列
    tokens = [BOS_IDX] + [src_vocab.get_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()
    tgt_tokens = tgt_tokens.cpu().detach()
    # 将目标序列转换为文本并返回
    #print(src_vocab.get_itos())
    return " ".join([tgt_vocab.get_itos()[tok.item()] for tok in tgt_tokens]).replace("<bos>", "").replace("<eos>", "")

通过translate进行翻译得到需要的译文

# 进行翻译
translate(transformer, "HSコード 8515 はんだ付け用、ろう付け用又は溶接用の機器(電気式(電気加熱ガス式を含む。)", ja_vocab, en_vocab, ja_tokenizer)
​
# 移除trainch列表中索引为5的元素(删除第6个元素)
trainen.pop(5)
# 移除trainja列表中索引为5的元素(删除第6个元素)
trainja.pop(5)

效果图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值