NLP—基于编码器—解码器和注意力机制的机器翻译

目录

一、介绍

二、实验环境

三、实验步骤

3.1 数据集准备

3.2 数据预处理

3.3 含注意力机制的编码器—解码器

3.3.1 编码器函数类

3.3.2  注意力机制

3.3.3 解码器函数类 

3.4 训练模型

3.5 预测不定长序列

3.6 模型评估

四、分析总结

5.1 思考

5.1.1 问题一

 5.1.2 问题二

 5.1.3 问题三

5.2 总结


一、介绍

       机器翻译是指将一段文本从一种语言自动翻译到另一种语言。因为一段文本序列在不同语言中的长度不一定相同,所以我们使用机器翻译为例来介绍编码器—解码器和注意力机制的应用。关于Seq2Seq模型的具体内容在另一篇已经详细解释,本篇是Transformer模型的简单使用示例。

二、实验环境

python

CPU

三、实验步骤

3.1 数据集准备

为了测试代码,使用一个很小的法语—英语数据集进行训练。

数据集示例如下:

3.2 数据预处理

为了能够正确处理句子信息,需要先定义一些特殊符号。其中<pad>(padding)符号用来添加在较短序列后,直到每个序列等长,而<bos>和<eos>符号分别表示序列的开始和结束。

!tar -xf d2lzh_pytorch.tar
import collections# 导入collections库,用于Python中更多的数据结构
import os
import io
import math
import torch
from torch import nn
import torch.nn.functional as F
import torchtext.vocab as Vocab
import torch.utils.data as Data

import sys# 导入sys库,提供对解释器相关的操作
# sys.path.append("..") 
import d2lzh_pytorch as d2l  # 导入d2lzh_pytorch模块,这是一个自定义的PyTorch深度学习库

PAD, BOS, EOS = '<pad>', '<bos>', '<eos>'  # 定义特殊的标记,用于序列处理中的填充、起始和结束标记
os.environ["CUDA_VISIBLE_DEVICES"] = "0"  # 设置环境变量,指定可见的CUDA设备为第0号(如果有GPU的话)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')  # 根据CUDA是否可用选择设备,如果可用选择cuda,否则选择cpu
print(torch.__version__, device)  # 打印PyTorch的版本号和当前使用的设备(cuda或cpu)

PyTorch的版本号和当前使用的设备(cuda或cpu):

1.5.0  cpu 

定义预处理辅助函数,读取文件并对数据进行处理:

# 将一个序列中所有的词记录在all_tokens中以便之后构造词典,然后在该序列后面添加PAD直到序列
# 长度变为max_seq_len,然后将序列保存在all_seqs中
def process_one_seq(seq_tokens, all_tokens, all_seqs, max_seq_len):
    all_tokens.extend(seq_tokens)
    seq_tokens += [EOS] + [PAD] * (max_seq_len - len(seq_tokens) - 1)
    all_seqs.append(seq_tokens)

# 使用所有的词来构造词典。并将所有序列中的词变换为词索引后构造Tensor
def build_data(all_tokens, all_seqs):
    vocab = Vocab.Vocab(collections.Counter(all_tokens),
                        specials=[PAD, BOS, EOS])
    indices = [[vocab.stoi[w] for w in seq] for seq in all_seqs]
    return vocab, torch.tensor(indices)
def read_data(max_seq_len):
    # 初始化存储token和sequence的列表
    in_tokens, out_tokens, in_seqs, out_seqs = [], [], [], []
    # 打开文件并逐行读取数据
    with io.open('fr-en-small.txt') as f:
        lines = f.readlines()
    # 遍历每一行数据
    for line in lines:
        # 拆分输入和输出序列
        in_seq, out_seq = line.rstrip().split('\t')
        # 拆分输入和输出序列中的单词(token)
        in_seq_tokens, out_seq_tokens = in_seq.split(' '), out_seq.split(' ')
        # 如果加上EOS后的长度超过max_seq_len,则忽略此样本
        if max(len(in_seq_tokens), len(out_seq_tokens)) > max_seq_len - 1:
            continue
        # 处理输入序列和输出序列的token列表,将处理结果添加到对应列表中
        process_one_seq(in_seq_tokens, in_tokens, in_seqs, max_seq_len)
        process_one_seq(out_seq_tokens, out_tokens, out_seqs, max_seq_len)
    # 构建输入和输出的词汇表(vocab)和数据集(TensorDataset)
    in_vocab, in_data = build_data(in_tokens, in_seqs)
    out_vocab, out_data = build_data(out_tokens, out_seqs)
    # 返回输入词汇表、输出词汇表和数据集(TensorDataset)
    return in_vocab, out_vocab, Data.TensorDataset(in_data, out_data)

将序列的最大长度设成7,然后查看读取到的第一个样本。该样本分别包含法语词索引序列和英语词索引序列。

max_seq_len = 7
in_vocab, out_vocab, dataset = read_data(max_seq_len)
dataset[0] 

第一个样本: 

3.3 含注意力机制的编码器—解码器

3.3.1 编码器函数类

编码器中,将输入语言的词索引通过词嵌入层得到词的表征,然后输入到一个多层门控循环单元中。PyTorch的`nn.GRU`实例在前向计算后也会分别返回输出和最终时间步的多层隐藏状态。其中的输出指的是最后一层的隐藏层在各个时间步的隐藏状态,并不涉及输出层计算。注意力机制将这些输出作为键项和值项。

class Encoder(nn.Module):
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                 drop_prob=0, **kwargs):
        """
        初始化编码器。
        
        参数:
        - vocab_size:词汇表大小,即输入的词汇量大小。
        - embed_size:嵌入维度大小,用于将输入的词汇索引映射为密集向量。
        - num_hiddens:GRU 隐藏单元的数量。
        - num_layers:GRU 层的数量。
        - drop_prob:GRU 层的 dropout 概率,默认为 0,表示不使用 dropout。
        """
        super(Encoder, self).__init__(**kwargs)
         # 创建词嵌入层
        self.embedding = nn.Embedding(vocab_size, embed_size)
        self.rnn = nn.GRU(embed_size, num_hiddens, num_layers, dropout=drop_prob)

    def forward(self, inputs, state):
        # 输入形状是(批量大小, 时间步数)。将输出互换样本维和时间步维
        embedding = self.embedding(inputs.long()).permute(1, 0, 2) # (seq_len, batch, input_size)
        return self.rnn(embedding, state)# 将嵌入后的序列输入到 GRU 层中

    def begin_state(self):
        """
        初始化编码器的初始状态。
        返回:
        - None:因为 GRU 层的初始状态会自动设置为零张量。
        """
        return Noneclass Encoder(nn.Module):
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                 drop_prob=0, **kwargs):
        """
        初始化编码器。
        
        参数:
        - vocab_size:词汇表大小,即输入的词汇量大小。
        - embed_size:嵌入维度大小,用于将输入的词汇索引映射为密集向量。
        - num_hiddens:GRU 隐藏单元的数量。
        - num_layers:GRU 层的数量。
        - drop_prob:GRU 层的 dropout 概率,默认为 0,表示不使用 dropout。
        """
        super(Encoder, self).__init__(**kwargs)
         # 创建词嵌入层
        self.embedding = nn.Embedding(vocab_size, embed_size)
        self.rnn = nn.GRU(embed_size, num_hiddens, num_layers, dropout=drop_prob)

    def forward(self, inputs, state):
        # 输入形状是(批量大小, 时间步数)。将输出互换样本维和时间步维
        embedding = self.embedding(inputs.long()).permute(1, 0, 2) # (seq_len, batch, input_size)
        return self.rnn(embedding, state)# 将嵌入后的序列输入到 GRU 层中

    def begin_state(self):
        """
        初始化编码器的初始状态。
        返回:
        - None:因为 GRU 层的初始状态会自动设置为零张量。
        """
        return None

创建一个批量大小为4、时间步数为7的小批量序列输入。设门控循环单元的隐藏层个数为2,隐藏单元个数为16。编码器对该输入执行前向计算后返回的输出形状为(时间步数, 批量大小, 隐藏单元个数)。门控循环单元在最终时间步的多层隐藏状态的形状为(隐藏层个数, 批量大小, 隐藏单元个数)。对于门控循环单元来说,state就是一个元素,即隐藏状态;如果使用长短期记忆,state是一个元组,包含两个元素即隐藏状态和记忆细胞。 

# 创建一个编码器对象,指定参数:词汇表大小为10,嵌入维度为8,隐藏单元数量为16,层数为2
encoder = Encoder(vocab_size=10, embed_size=8, num_hiddens=16, num_layers=2)
# 将输入数据和初始状态传递给编码器的 forward 方法,进行前向传播
output, state = encoder(torch.zeros((4, 7)), encoder.begin_state())
output.shape, state.shape # GRU的state是h, 而LSTM的是一个元组(h, c)

3.3.2  注意力机制

将输入连结后通过含单隐藏层的多层感知机变换。其中隐藏层的输入是解码器的隐藏状态与编码器在所有时间步上隐藏状态的一一连结,且使用tanh函数作为激活函数。输出层的输出个数为1。两个`Linear`实例均不使用偏差。其中函数$a$定义里向量$\boldsymbol{v}$的长度是一个超参数,即`attention_size`。

def attention_model(input_size, attention_size):
     # 创建一个序列模型,该模型包含以下几层:
    # 1. 全连接层:输入大小为 input_size,输出大小为 attention_size,无偏置项
    #    这一层用于将输入特征映射到注意力大小的隐藏表示空间
    # 2. Tanh 激活函数:对第一层的输出进行 Tanh 激活,将其范围限制在 (-1, 1)
    # 3. 全连接层:输入大小为 attention_size,输出大小为 1,无偏置项
    model = nn.Sequential(nn.Linear(input_size, attention_size, bias=False),
                          nn.Tanh(),
                          nn.Linear(attention_size, 1, bias=False))
    return model

注意力机制的输入包括查询项、键项和值项。设编码器和解码器的隐藏单元个数相同。这里的查询项为解码器在上一时间步的隐藏状态,形状为(批量大小, 隐藏单元个数);键项和值项均为编码器在所有时间步的隐藏状态,形状为(时间步数, 批量大小, 隐藏单元个数)。注意力机制返回当前时间步的背景变量,形状为(批量大小, 隐藏单元个数)。

def attention_forward(model, enc_states, dec_state):
    """
    enc_states: (时间步数, 批量大小, 隐藏单元个数)
    dec_state: (批量大小, 隐藏单元个数)
    """
    # 将解码器隐藏状态广播到和编码器隐藏状态形状相同后进行连结
    dec_states = dec_state.unsqueeze(dim=0).expand_as(enc_states)
    enc_and_dec_states = torch.cat((enc_states, dec_states), dim=2)
    e = model(enc_and_dec_states)  # 形状为(时间步数, 批量大小, 1)
    alpha = F.softmax(e, dim=0)  # 在时间步维度做softmax运算
    return (alpha * enc_states).sum(dim=0)  # 返回背景变量

时间步数为10,批量大小为4,编码器和解码器的隐藏单元个数均设为8:

seq_len, batch_size, num_hiddens = 10, 4, 8
# 定义注意力模型,输入大小为 2*num_hiddens,注意力大小为 10
model = attention_model(2*num_hiddens, 10) 

# 创建编码器状态张量,形状为 (序列长度 seq_len, 批量大小 batch_size, 隐藏单元数 num_hiddens)
enc_states = torch.zeros((seq_len, batch_size, num_hiddens))

# 创建解码器状态张量,形状为 (批量大小 batch_size, 隐藏单元数 num_hiddens)
dec_state = torch.zeros((batch_size, num_hiddens))

# 对注意力模型进行前向传播,并获取输出的形状
attention_forward(model, enc_states, dec_state).shape

注意力机制返回一个小批量的背景向量,由于每个背景向量的长度等于编码器的隐藏单元个数因此输出的形状为(4, 8)。

3.3.3 解码器函数类 

在解码器的前向计算中,先通过注意力机制计算得到当前时间步的背景向量。由于解码器的输入来自输出语言的词索引,需要将输入通过词嵌入层得到表征,然后和背景向量在特征维连结。将连结后的结果与上一时间步的隐藏状态通过门控循环单元计算出当前时间步的输出与隐藏状态。最后,将输出通过全连接层变换为有关各个输出词的预测,形状为(批量大小, 输出词典大小)。 

class Decoder(nn.Module):
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                 attention_size, drop_prob=0):
        super(Decoder, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embed_size)
        self.attention = attention_model(2*num_hiddens, attention_size)
        # GRU的输入包含attention输出的c和实际输入, 所以尺寸是 num_hiddens+embed_size
        self.rnn = nn.GRU(num_hiddens + embed_size, num_hiddens, 
                          num_layers, dropout=drop_prob)
        self.out = nn.Linear(num_hiddens, vocab_size)

    def forward(self, cur_input, state, enc_states):
        """
        cur_input shape: (batch, )
        state shape: (num_layers, batch, num_hiddens)
        """
        # 使用注意力机制计算背景向量
        c = attention_forward(self.attention, enc_states, state[-1])
        # 将嵌入后的输入和背景向量在特征维连结, (批量大小, num_hiddens+embed_size)
        input_and_c = torch.cat((self.embedding(cur_input), c), dim=1) 
        # 为输入和背景向量的连结增加时间步维,时间步个数为1
        output, state = self.rnn(input_and_c.unsqueeze(0), state)
        # 移除时间步维,输出形状为(批量大小, 输出词典大小)
        output = self.out(output).squeeze(dim=0)
        return output, state

    def begin_state(self, enc_state):
        # 直接将编码器最终时间步的隐藏状态作为解码器的初始隐藏状态
        return enc_state

3.4 训练模型

先实现`batch_loss`函数计算一个小批量的损失

def batch_loss(encoder, decoder, X, Y, loss):
    batch_size = X.shape[0]
    enc_state = encoder.begin_state()
    enc_outputs, enc_state = encoder(X, enc_state)
    # 初始化解码器的隐藏状态
    dec_state = decoder.begin_state(enc_state)
    # 解码器在最初时间步的输入是BOS
    dec_input = torch.tensor([out_vocab.stoi[BOS]] * batch_size)
    # 我们将使用掩码变量mask来忽略掉标签为填充项PAD的损失, 初始全1
    mask, num_not_pad_tokens = torch.ones(batch_size,), 0
    l = torch.tensor([0.0])
    for y in Y.permute(1,0): # Y shape: (batch, seq_len)
        dec_output, dec_state = decoder(dec_input, dec_state, enc_outputs)
        l = l + (mask * loss(dec_output, y)).sum()
        dec_input = y  # 使用强制教学
        num_not_pad_tokens += mask.sum().item()
        # EOS后面全是PAD. 下面一行保证一旦遇到EOS接下来的循环中mask就一直是0
        mask = mask * (y != out_vocab.stoi[EOS]).float()
    return l / num_not_pad_tokens

在训练函数中,我们需要同时迭代编码器和解码器的模型参数。

def train(encoder, decoder, dataset, lr, batch_size, num_epochs):
    # 定义编码器和解码器的优化器,使用Adam优化器,学习率为lr
    enc_optimizer = torch.optim.Adam(encoder.parameters(), lr=lr)
    dec_optimizer = torch.optim.Adam(decoder.parameters(), lr=lr)
    # 定义损失函数为交叉熵损失,reduction='none'表示返回每个样本点的损失
    loss = nn.CrossEntropyLoss(reduction='none')
    # 创建数据迭代器,每次产生一个批量的数据,打乱数据集
    data_iter = Data.DataLoader(dataset, batch_size, shuffle=True)
    # 迭代num_epochs次
    for epoch in range(num_epochs):
        l_sum = 0.0  # 用于累加每个epoch的总损失
        for X, Y in data_iter:
            enc_optimizer.zero_grad()  # 梯度清零,防止累积
            dec_optimizer.zero_grad()
            l = batch_loss(encoder, decoder, X, Y, loss)  # 计算当前批量的损失
            l.backward()  # 反向传播计算梯度
            enc_optimizer.step()  # 更新编码器参数
            dec_optimizer.step()  # 更新解码器参数
            l_sum += l.item()  # 累加当前批量的损失值
        # 每10个epoch打印一次损失值
        if (epoch + 1) % 10 == 0:
            print("epoch %d, loss %.3f" % (epoch + 1, l_sum / len(data_iter)))

创建模型实例,并设置超参数,就可以开始训练模型了:

embed_size, num_hiddens, num_layers = 64, 64, 2
attention_size, drop_prob, lr, batch_size, num_epochs = 10, 0.5, 0.01, 2, 50
encoder = Encoder(len(in_vocab), embed_size, num_hiddens, num_layers,
                  drop_prob)
decoder = Decoder(len(out_vocab), embed_size, num_hiddens, num_layers,
                  attention_size, drop_prob)
train(encoder, decoder, dataset, lr, batch_size, num_epochs)

 训练结果:

3.5 预测不定长序列

使用贪婪搜索生成解码器在每个时间步的输出。定义翻译测试函数:

def translate(encoder, decoder, input_seq, max_seq_len):
    in_tokens = input_seq.split(' ')# 将输入序列分割成单词,并添加特殊标记和填充以匹配模型输入要求
    in_tokens += [EOS] + [PAD] * (max_seq_len - len(in_tokens) - 1)# 添加EOS标记和填充
    enc_input = torch.tensor([[in_vocab.stoi[tk] for tk in in_tokens]]) # batch=1
    # 初始化编码器的初始状态,并进行编码器的前向计算
    enc_state = encoder.begin_state()
    enc_output, enc_state = encoder(enc_input, enc_state)
    # 初始化解码器的输入为起始标记BOS的索引
    dec_input = torch.tensor([out_vocab.stoi[BOS]])
     # 使用编码器的最终状态初始化解码器的初始状态
    dec_state = decoder.begin_state(enc_state)
    output_tokens = [] # 存储输出的token序列
    for _ in range(max_seq_len):
        dec_output, dec_state = decoder(dec_input, dec_state, enc_output)
        # 预测输出的token为输出概率最大的索引对应的token
        pred = dec_output.argmax(dim=1)
        pred_token = out_vocab.itos[int(pred.item())]
        # 如果预测到EOS标记,表示输出序列已完成
        if pred_token == EOS:
            break
        else:
            output_tokens.append(pred_token) # 将预测的token添加到输出序列中
            dec_input = pred  # 更新解码器的输入为当前预测的token
    return output_tokens

测试模型,输入法语句子“ils regardent.”,翻译后的英语句子应该是“they are watching.”

input_seq = 'ils regardent .'
translate(encoder, decoder, input_seq, max_seq_len)#测试翻译模型

 翻译结果:

3.6 模型评估

评价机器翻译结果通常使用BLEU(Bilingual Evaluation Understudy)。对于模型预测序列中任意的子序列,BLEU考察这个子序列是否出现在标签序列中。BLEU的工作原理是将机器生成的翻译与人工参考翻译进行比较,并根据它们之间的相似性分配一个分数。

BLEU的计算方式如下:

(1)对于翻译结果(机器生成的翻译),它会计算参考翻译(目标值)之间的 n-gram(连续的 n 个词或字符序列)匹配度。

(2)对每个 n-gram 匹配,BLEU将其与候选翻译中的 n-gram 数量相比较,并采用一种修正的精确匹配度度量来计算得分。

(3)然后,BLEU将各个 n-gram 匹配的得分合并,通过计算几何平均值来得出最终的 BLEU 分数。通常,BLEU 的分数在0到1之间,表示翻译的质量,越接近1表示越好。

公式如下:
                            b l e u = e x p ∑ w e i g h t ∗ l o g P ∗ 惩罚系数 

实现BLEU的计算函数:

def bleu(pred_tokens, label_tokens, k):
    len_pred, len_label = len(pred_tokens), len(label_tokens)
     # 计算长度惩罚,如果预测序列长度小于参考序列长度,则长度惩罚为1,否则按照1 - len_label / len_pred计算
    score = math.exp(min(0, 1 - len_label / len_pred))
     # 对于每个n-gram长度,计算匹配的n-gram数量并计算累积分数
    for n in range(1, k + 1):
        num_matches, label_subs = 0, collections.defaultdict(int)
        for i in range(len_label - n + 1):
            label_subs[''.join(label_tokens[i: i + n])] += 1
        # 计算预测序列中匹配的n-gram数量
        for i in range(len_pred - n + 1):
            if label_subs[''.join(pred_tokens[i: i + n])] > 0:
                num_matches += 1
                label_subs[''.join(pred_tokens[i: i + n])] -= 1
         # 计算该n-gram长度下的精确度得分,并进行累积乘积
        score *= math.pow(num_matches / (len_pred - n + 1), math.pow(0.5, n))
    return score

结果打印函数: 

def score(input_seq, label_seq, k):
    pred_tokens = translate(encoder, decoder, input_seq, max_seq_len)
    label_tokens = label_seq.split(' ')
    print('bleu %.3f, predict: %s' % (bleu(pred_tokens, label_tokens, k),
                                      ' '.join(pred_tokens)))

 预测正确则结果为1:

score('ils regardent .', 'they are watching .', k=2)

score('ils sont canadienne .', 'they are canadian .', k=2)

四、分析总结

5.1 思考

5.1.1 问题一

如果编码器和解码器的隐藏单元个数不同或层数不同,我们该如何改进解码器的隐藏状态初始化方法?

有三种方法:

1.线性映射初始化:使用线性映射将编码器的最终隐藏状态映射到解码器的隐藏状态空间。这可以通过一个线性层或全连接层来实现,将编码器的最终隐藏状态转换为解码器隐藏状态的维度。

2.复制和填充:如果编码器和解码器的隐藏单元个数不同但层数相同,可以使用填充方法将编码器隐藏状态的最后一层复制到解码器的隐藏状态初始化。

3.零填充:如果编码器的隐藏单元个数多于解码器,可以使用零填充来初始化解码器的隐藏状态。即在解码器的隐藏状态向量中,多余的部分初始化为零。

 5.1.2 问题二

在训练中,将强制教学替换为使用解码器在上一时间步的输出作为解码器在当前时间步的输入。结果有什么变化吗?

       使用解码器自身的输出作为下一步的输入会引入自回归性质,即解码器在生成序列时会依赖于之前生成的部分。这可以提高模型在生成流畅和连贯翻译时的能力。
使用自回归方法可以增加训练时间和计算复杂度,因为需要逐步生成每个单词或符号。同时,模型的训练过程可能更加不稳定,需要更细致的调整。

 5.1.3 问题三

试着使用更大的翻译数据集来训练模型,例如 WMT [2] 和 Tatoeba Project [3]:

这里有两个数据集下载链接,

[1] WMT. http://www.statmt.org/wmt14/translation-task.html

[2] Tatoeba Project. http://www.manythings.org/anki/

我在链接二下载了新的数据集,是阿尔巴尼亚语和英语句子对数据集,因为文档格式不同,为了能够正确读取数据需要先对下载的数据集进行处理,处理后的数据集保存并命名formatted_sqi.txt:

#更大的数据集通常能够提升模型的泛化能力和翻译质量,因为模型有更多的示例来学习从源语言到目标语言的映射关系。
#大数据集能够帮助模型更好地捕捉不同语言之间的语法、语义和文化差异,从而提高翻译的准确性和流畅度。
# 读取 sqi.txt 文件,并将其格式转换为可以训练的形式
input_file = 'sqi.txt'
output_file = 'formatted_sqi.txt'
with open(input_file, 'r', encoding='utf-8') as f_in, open(output_file, 'w', encoding='utf-8') as f_out:
    for line in f_in:
        line = line.strip()
        if line:
            # 假设每行以制表符分隔
            parts = line.split('\t')
            if len(parts) >= 2:
                albanian_sentence = parts[0].strip()
                french_translation = parts[1].strip()
                # 写入新的格式到输出文件
                f_out.write(f"{albanian_sentence}\t{french_translation}.\n")
            else:
                print(f"Ignoring invalid line format: {line}")
                
def read_data(max_seq_len):
    # 初始化存储token和sequence的列表
    in_tokens, out_tokens, in_seqs, out_seqs = [], [], [], []
    # 打开文件并逐行读取数据
    with io.open('formatted_sqi.txt') as f:
        lines = f.readlines()
    # 遍历每一行数据
    for line in lines:
        # 拆分输入和输出序列
        in_seq, out_seq = line.rstrip().split('\t')
        # 拆分输入和输出序列中的单词(token)
        in_seq_tokens = in_seq.split(' ')
        out_seq_tokens = out_seq.split(' ')
        #in_seq_tokens, out_seq_tokens = in_seq.split(' '), out_seq.split(' ')
        # 如果加上EOS后的长度超过max_seq_len,则忽略此样本
        if max(len(in_seq_tokens), len(out_seq_tokens)) > max_seq_len - 1:
            continue
        # 处理输入序列和输出序列的token列表,将处理结果添加到对应列表中
        process_one_seq(in_seq_tokens, in_tokens, in_seqs, max_seq_len)
        process_one_seq(out_seq_tokens, out_tokens, out_seqs, max_seq_len)
    # 构建输入和输出的词汇表(vocab)和数据集(TensorDataset)
    in_vocab, in_data = build_data(in_tokens, in_seqs)
    out_vocab, out_data = build_data(out_tokens, out_seqs)
    # 返回输入词汇表、输出词汇表和数据集(TensorDataset)
    return in_vocab, out_vocab, Data.TensorDataset(in_data, out_data)

max_seq_len=7
in_vocab1, out_vocab1, dataset = read_data(max_seq_len)
dataset[0] 

#进行训练
embed_size, num_hiddens, num_layers = 64, 64, 2
attention_size, drop_prob, lr, batch_size, num_epochs = 10, 0.5, 0.01, 2, 30
encoder = Encoder(len(in_vocab1), embed_size, num_hiddens, num_layers,
                  drop_prob)
decoder = Decoder(len(out_vocab1), embed_size, num_hiddens, num_layers,
                  attention_size, drop_prob)
train(encoder, decoder, dataset, lr, batch_size, num_epochs)

初始数据集(sqi.txt)示例:

 修改格式后数据集(formatted_sqi.txt)示例:

 在新数据集上的模型结果(只设置了30轮):

5.2 总结

(1)通过本次实验将编码器—解码器和注意力机制应用于机器翻译中,让我对Transformer模型的结构与其在机器学习中的应用有了更深度的理解和掌握; 

(2)另外,通过本次实验学习到了BLEU可以用来评价翻译结果。

  • 23
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值