《动手学深度学习 Pytorch版》 9.7 序列到序列学习(seq2seq)

循环神经网络编码器使用长度可变的序列作为输入,将其编码到循环神经网络编码器固定形状的隐状态中。

为了连续生成输出序列的词元,独立的循环神经网络解码器是基于输入序列的编码信息和输出序列已经看见的或者生成的词元来预测下一个词元。

在这里插入图片描述

要点:

  • “<eos>”表示序列结束词元,一旦输出序列生成此词元,模型就会停止预测。

  • “<bos>”表示序列开始词元,它是解码器的输入序列的第一个词元。

  • 使用循环神经网络编码器最终的隐状态来初始化解码器的隐状态。

  • 允许标签成为原始的输出序列

import collections
import math
import torch
from torch import nn
from d2l import torch as d2l

9.7.1 编码器

使用函数 f f f 描述循环神经网络的循环层所做的变换:

h t = f ( x t , h t − 1 ) \boldsymbol{h}_t=f(\boldsymbol{x}_t,\boldsymbol{h}_{t-1}) ht=f(xt,ht1)

参数字典:

  • x t \boldsymbol{x}_t xt 表示词元 x t x_t xt 的输入特征向量

  • h t − 1 \boldsymbol{h}_{t-1} ht1 是词元 x t x_t xt 的另一个输入向量,即上一时间步的隐状态

  • h t \boldsymbol{h}_t ht 表示当前步的隐状态

总之,编码器通过选定的函数 q q q 将所有时间步的隐状态转换为上下文变量:

c = q ( h t , … , h T ) \boldsymbol{c}=q(\boldsymbol{h}_t,\dots,\boldsymbol{h}_T) c=q(ht,,hT)

到目前为止使用单向循环神经网络设计的编码器中的隐状态只依赖于由输入序列的开始位置到隐状态所在的时间步的位置 (包括隐状态所在的时间步)组成的输入子序列。

使用双向循环神经网络构造的编码器中隐状态依赖于由隐状态所在的时间步的位置之前的序列和之后的序列(包括隐状态所在的时间步)组成的两个输入子序列,因此隐状态对整个序列的信息都进行了编码。

以下实现的循环神经网络编码器使用了嵌入层(embedding layer)来获得输入序列中每个词元的特征向量。

  • 嵌入层的权重是一个矩阵,其行数等于输入词表的大小(vocab_size),其列数等于特征向量的维度(embed_size)。

  • 对于任意输入词元的索引 i i i, 嵌入层获取权重矩阵的第 i i i 行(从 0 开始)以返回其特征向量。

  • 另外,本文选择了一个多层门控循环单元来实现编码器。

#@save
class Seq2SeqEncoder(d2l.Encoder):
    """用于序列到序列学习的循环神经网络编码器"""
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                 dropout=0, **kwargs):
        super(Seq2SeqEncoder, self).__init__(**kwargs)
        self.embedding = nn.Embedding(vocab_size, embed_size)  # 嵌入层
        self.rnn = nn.GRU(embed_size, num_hiddens, num_layers,
                          dropout=dropout)

    def forward(self, X, *args):
        # 输出'X'的形状:(batch_size,num_steps,embed_size)
        X = self.embedding(X)
        # 在循环神经网络模型中,第一个轴对应于时间步
        X = X.permute(1, 0, 2)  # 前两个轴互换
        # 如果未提及状态,则默认为0
        output, state = self.rnn(X)
        # output的形状:(num_steps,batch_size,num_hiddens)
        # state的形状:(num_layers,batch_size,num_hiddens)
        return output, state
# 实例化编码器

encoder = Seq2SeqEncoder(vocab_size=10, embed_size=8, num_hiddens=16,  # 隐藏单元数为 16
                         num_layers=2)
encoder.eval()  # 不启用 Batch Normalization 和 Dropout
X = torch.zeros((4, 7), dtype=torch.long)  # 批量大小为 4,时间步为7
output, state = encoder(X)
output.shape  # 形状为(时间步数,批量大小,隐藏单元数)
torch.Size([7, 4, 16])
state.shape  # 最后一个时间步的多层隐状态的形状是(隐藏层的数量,批量大小,隐藏单元的数量)
torch.Size([2, 4, 16])

9.7.2 解码器

对于解码器的输出来说,概率取决于:

P ( y t ′ ∣ y 1 , … , y t ′ − 1 , c ) P(y_{t'}|y_1,\dots,y_{t'-1},\boldsymbol{c}) P(yty1,,yt1,c)

参数字典:

  • y t ′ y_{t'} yt 表示时间步 t ′ t' t 的输出(用 ’ 是为了和编码器的量区分)

  • y 1 , y 2 , … , y T ′ y_1,y_2,\dots,y_{T'} y1,y2,,yT 表示训练数据集的输出序列

  • c \boldsymbol{c} c 表示上下文变量

简言之,码器输出的概率取决于先前的输出子序列和上下文变量。

为了在序列上模型化这种条件概率,需要使用另一个循环神经网络作为解码器。在输出序列上的任意时间步 t ′ t' t,循环神经网络将来自上一时间步的输出 y t ′ − 1 y_{t'-1} yt1 和上下文变量 c \boldsymbol{c} c 作为其输入,然后在当前时间步将它们和上一隐状态 s t ′ − 1 \boldsymbol{s}_{t'-1} st1 转换为隐状态 s t \boldsymbol{s}_t st。 因此,可以使用函数 g g g 来表示解码器的隐藏层的变换:

s t ′ = g ( y t ′ − 1 , c , s t ′ − 1 ) \boldsymbol{s}_{t'}=g(y_{t'-1},\boldsymbol{c},\boldsymbol{s}_{t'-1}) st=g(yt1,c,st1)

获得解码器的隐状态之后,可以使用输出层和 softmax 操作来计算在时间步 t ′ t' t 时输出 y t ′ y_{t'} yt 的条件概率分布 P ( y t ′ ∣ y 1 , … , y t ′ − 1 , c ) P(y_{t'}|y_1,\dots,y_{t'-1},\boldsymbol{c}) P(yty1,,yt1,c)

实现要点:

  • 直接使用编码器最后一个时间步的隐状态来初始化解码器的隐状态。

    • 因此要求使用循环神经网络实现的编码器和解码器具有相同数量的层和隐藏单元。
  • 上下文变量在所有的时间步与解码器的输入进行拼接(concatenate),以进一步包含经过编码的输入序列的信息。

  • 在循环神经网络解码器的最后一层使用全连接层来变换隐状态,以预测输出词元的概率分布。

class Seq2SeqDecoder(d2l.Decoder):
    """用于序列到序列学习的循环神经网络解码器"""
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                 dropout=0, **kwargs):
        super(Seq2SeqDecoder, self).__init__(**kwargs)
        self.embedding = nn.Embedding(vocab_size, embed_size)  # 嵌入层
        self.rnn = nn.GRU(embed_size + num_hiddens, num_hiddens, num_layers,
                          dropout=dropout)
        self.dense = nn.Linear(num_hiddens, vocab_size)  # 解码器是有输出层的

    def init_state(self, enc_outputs, *args):
        return enc_outputs[1]  # 获取 states

    def forward(self, X, state):
        # 输出'X'的形状:(batch_size,num_steps,embed_size)
        X = self.embedding(X).permute(1, 0, 2)  # 时间步放在前面
        # 广播context,使其具有与X相同的num_steps
        context = state[-1].repeat(X.shape[0], 1, 1)  # 最后一次的隐藏状态拿出来按解码器单元数复制
        X_and_context = torch.cat((X, context), 2)  # 拼接输入和上下文变量
        output, state = self.rnn(X_and_context, state)
        output = self.dense(output).permute(1, 0, 2)
        # output的形状:(batch_size,num_steps,vocab_size)
        # state的形状:(num_layers,batch_size,num_hiddens)
        return output, state
# 与前文编码器超参数一样

decoder = Seq2SeqDecoder(vocab_size=10, embed_size=8, num_hiddens=16,
                         num_layers=2)
decoder.eval()
state = decoder.init_state(encoder(X))
output, state = decoder(X, state)
output.shape, state.shape
(torch.Size([4, 7, 10]), torch.Size([2, 4, 16]))

总结构图:

在这里插入图片描述

9.7.3 损失函数

使用 softmax 来获得分布,并通过计算交叉熵损失函数来进行优化。需要注意,应该将填充词元的预测排除在损失函数的计算之外。

下面的 sequence_mask 函数通过零值化屏蔽不相关的项实现。

#@save
def sequence_mask(X, valid_len, value=0):
    """在序列中屏蔽不相关的项"""
    maxlen = X.size(1)
    mask = torch.arange((maxlen), dtype=torch.float32,
                        device=X.device)[None, :] < valid_len[:, None]  # 优雅,比较 arange 生成张量(即列号序列)的列和 valid_len 的行
    X[~mask] = value  # 按位反转 仅有效位赋值
    return X

X = torch.tensor([[1, 2, 3], [4, 5, 6]])
sequence_mask(X, torch.tensor([1, 2]))
tensor([[1, 0, 0],
        [4, 5, 0]])
X = torch.ones(2, 3, 4)
sequence_mask(X, torch.tensor([1, 2]), value=-1)  # 用非零值替代也可以
tensor([[[ 1.,  1.,  1.,  1.],
         [-1., -1., -1., -1.],
         [-1., -1., -1., -1.]],

        [[ 1.,  1.,  1.,  1.],
         [ 1.,  1.,  1.,  1.],
         [-1., -1., -1., -1.]]])
#@save
class MaskedSoftmaxCELoss(nn.CrossEntropyLoss):
    """带遮蔽的softmax交叉熵损失函数"""
    # pred的形状:(batch_size,num_steps,vocab_size)
    # label的形状:(batch_size,num_steps)
    # valid_len的形状:(batch_size,)
    def forward(self, pred, label, valid_len):
        weights = torch.ones_like(label)  # 同型全一掩码矩阵
        weights = sequence_mask(weights, valid_len)  # 生成过滤填充词元的掩码矩阵
        self.reduction='none'  # 不进行值归并,原样输出
        unweighted_loss = super(MaskedSoftmaxCELoss, self).forward(
            pred.permute(0, 2, 1), label)  # 计算交叉熵损失
        weighted_loss = (unweighted_loss * weights).mean(dim=1)  # 过滤并求均值
        return weighted_loss
loss = MaskedSoftmaxCELoss()
loss(torch.ones(3, 4, 10), torch.ones((3, 4), dtype=torch.long),  # 使用三个相同的序列进行检查
     torch.tensor([4, 2, 0]))  # 设定有效长度为 4,2,0 则第一个序列的损失应为第二个序列的两倍,而第三个序列的损失应为零
tensor([2.3026, 1.1513, 0.0000])

9.7.4 训练

#@save
def train_seq2seq(net, data_iter, lr, num_epochs, tgt_vocab, device):
    """训练序列到序列模型"""
    def xavier_init_weights(m):  # xavier 初始化
        if type(m) == nn.Linear:
            nn.init.xavier_uniform_(m.weight)
        if type(m) == nn.GRU:
            for param in m._flat_weights_names:
                if "weight" in param:
                    nn.init.xavier_uniform_(m._parameters[param])

    net.apply(xavier_init_weights)
    net.to(device)
    optimizer = torch.optim.Adam(net.parameters(), lr=lr)  # 使用 Adam 优化器
    loss = MaskedSoftmaxCELoss()  # 使用改造的交叉熵损失
    net.train()
    animator = d2l.Animator(xlabel='epoch', ylabel='loss',
                     xlim=[10, num_epochs])
    for epoch in range(num_epochs):
        timer = d2l.Timer()
        metric = d2l.Accumulator(2)  # 设置两个累加器:训练损失总和,词元数量
        for batch in data_iter:
            optimizer.zero_grad()
            X, X_valid_len, Y, Y_valid_len = [x.to(device) for x in batch]  # 加载数据
            bos = torch.tensor([tgt_vocab['<bos>']] * Y.shape[0],  # 获取特定的开始词元
                          device=device).reshape(-1, 1)
            dec_input = torch.cat([bos, Y[:, :-1]], 1)  # 强制教学,拼接开始词元和原始输出序列
            Y_hat, _ = net(X, dec_input, X_valid_len)  # 前向传播
            l = loss(Y_hat, Y, Y_valid_len)  # 计算损失
            l.sum().backward()  # 损失函数的标量进行“反向传播”
            d2l.grad_clipping(net, 1)  # 梯度裁剪
            num_tokens = Y_valid_len.sum()
            optimizer.step()  # 优化
            with torch.no_grad():
                metric.add(l.sum(), num_tokens)
        if (epoch + 1) % 10 == 0:
            animator.add(epoch + 1, (metric[0] / metric[1],))
    print(f'loss {metric[0] / metric[1]:.3f}, {metric[1] / timer.stop():.1f} '
        f'tokens/sec on {str(device)}')
embed_size, num_hiddens, num_layers, dropout = 32, 32, 2, 0.1
batch_size, num_steps = 64, 10
lr, num_epochs, device = 0.005, 300, d2l.try_gpu()

train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)
encoder = Seq2SeqEncoder(len(src_vocab), embed_size, num_hiddens, num_layers,
                        dropout)
decoder = Seq2SeqDecoder(len(tgt_vocab), embed_size, num_hiddens, num_layers,
                        dropout)
net = d2l.EncoderDecoder(encoder, decoder)
train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)
loss 0.020, 15597.5 tokens/sec on cuda:0

在这里插入图片描述

9.7.5 预测

序列开始词元(“<bos>”)在初始时间步被输入到解码器中。当输出序列的预测遇到序列结束词元(“<eos>”)时,预测就结束了。

在这里插入图片描述

#@save
def predict_seq2seq(net, src_sentence, src_vocab, tgt_vocab, num_steps,
                    device, save_attention_weights=False):
    """序列到序列模型的预测"""
    net.eval()  # 在预测时将net设置为评估模式 不启用 Batch Normalization 和 Dropout
    src_tokens = src_vocab[src_sentence.lower().split(' ')] + [
        src_vocab['<eos>']]  # 预处理源语言
    enc_valid_len = torch.tensor([len(src_tokens)], device=device)
    src_tokens = d2l.truncate_pad(src_tokens, num_steps, src_vocab['<pad>'])  # 进行截断与填充
    # 添加批量轴
    enc_X = torch.unsqueeze(
        torch.tensor(src_tokens, dtype=torch.long, device=device), dim=0)
    enc_outputs = net.encoder(enc_X, enc_valid_len)  # 进行编码
    dec_state = net.decoder.init_state(enc_outputs, enc_valid_len)  # 初始化解码器
    # 添加批量轴
    dec_X = torch.unsqueeze(torch.tensor(
        [tgt_vocab['<bos>']], dtype=torch.long, device=device), dim=0)
    output_seq, attention_weight_seq = [], []
    for _ in range(num_steps):
        Y, dec_state = net.decoder(dec_X, dec_state)  # 解码
        dec_X = Y.argmax(dim=2)  # 使用具有预测最高可能性的词元,作为解码器在下一时间步的输入
        pred = dec_X.squeeze(dim=0).type(torch.int32).item()
        if save_attention_weights:  # 保存注意力权重(稍后讨论)
            attention_weight_seq.append(net.decoder.attention_weights)
        if pred == tgt_vocab['<eos>']:  # 一旦序列结束词元被预测,输出序列的生成就完成了
            break
        output_seq.append(pred)
    return ' '.join(tgt_vocab.to_tokens(output_seq)), attention_weight_seq

9.7.6 预测序列的评估

BLEU(bilingual evaluation understudy)最先是用于评估机器翻译的结果,但现在它已经被广泛用于测量许多应用的输出序列的质量。

原则上说,对于预测序列中的任意 n 元语法(n-grams),BLEU 的评估都是这个 n 元语法是否出现在标签序列中。BLEU 定义为:

exp ⁡ ( min ⁡ ( 0 , 1 − l e n l a b e l l e n p r e d ) ) ∏ n = 1 k p n 1 / 2 n \exp{\left(\min{\left(0,1-\frac{len_{label}}{len_{pred}}\right)}\right)}\prod^k_{n=1}p_n^{1/2^n} exp(min(0,1lenpredlenlabel))n=1kpn1/2n

参数字典:

  • l e n l a b e l len_{label} lenlabel 表示标签序列中的词元数

  • l e n p r e d len_{pred} lenpred 表示预测序列中的词元数

  • k k k 用于匹配的最长的 n 元语法

  • p n p_n pn 表示 n 元语法的精确度它是两个数量的比值:

    • 第一个是预测序列与标签序列中匹配的 n 元语法的数量

    • 第二个是预测序列中 n 元语法的数量的比率。

设计要点:

  • 当预测序列与标签序列完全相同时,BLEU 为 1。

  • 此外,由于 n 元语法越长则匹配难度越大,所以 BLEU 为更长的元语法的精确度分配更大的权重。具体来说,当 p n p_n pn 固定时, p n 1 / 2 n p_n^{1/2^n} pn1/2n 会随着 n 的增长而增加(原始论文使用 p n 1 / n p_n^{1/n} pn1/n)。

  • 由于预测的序列越短获得的 p n p_n pn 值越高,所以 BLEU 定义式中乘法项之前的系数用于惩罚较短的预测序列。

def bleu(pred_seq, label_seq, k):  #@save
    """计算BLEU"""
    pred_tokens, label_tokens = pred_seq.split(' '), label_seq.split(' ')
    len_pred, len_label = len(pred_tokens), len(label_tokens)
    score = math.exp(min(0, 1 - len_label / len_pred))  # 计算惩罚项
    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  # 对各词元进行计数
        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  # 减去已经匹配过的词元,防止重复匹配
        score *= math.pow(num_matches / (len_pred - n + 1), math.pow(0.5, n))
    return score
engs = ['go .', "i lost .", 'he\'s calm .', 'i\'m home .']
fras = ['va !', 'j\'ai perdu .', 'il est calme .', 'je suis chez moi .']
for eng, fra in zip(engs, fras):
    translation, attention_weight_seq = predict_seq2seq(
        net, eng, src_vocab, tgt_vocab, num_steps, device)
    print(f'{eng} => {translation}, bleu {bleu(translation, fra, k=2):.3f}')
go . => va <unk> !, bleu 0.000
i lost . => j'ai perdu perdu ., bleu 0.783
he's calm . => il est <unk> ., bleu 0.658
i'm home . => je suis calme ., bleu 0.512

练习

(1)试着通过调整超参数来改善翻译效果。

embed_size1, num_hiddens1, num_layers1, dropout1 = 64, 64, 2, 0.2
batch_size1, num_steps1 = 128, 10
lr1, num_epochs1, device1 = 0.01, 500, d2l.try_gpu()

train_iter1, src_vocab1, tgt_vocab1 = d2l.load_data_nmt(batch_size1, num_steps1)
encoder1 = Seq2SeqEncoder(len(src_vocab1), embed_size1, num_hiddens1, num_layers1,
                        dropout1)
decoder1 = Seq2SeqDecoder(len(tgt_vocab1), embed_size1, num_hiddens1, num_layers1,
                        dropout1)
net1 = d2l.EncoderDecoder(encoder1, decoder1)
train_seq2seq(net1, train_iter1, lr1, num_epochs1, tgt_vocab1, device1)
loss 0.020, 18634.0 tokens/sec on cuda:0

在这里插入图片描述

engs = ['go .', "i lost .", 'he\'s calm .', 'i\'m home .']
fras = ['va !', 'j\'ai perdu .', 'il est calme .', 'je suis chez moi .']
for eng, fra in zip(engs, fras):
    translation, attention_weight_seq = predict_seq2seq(
        net1, eng, src_vocab, tgt_vocab, num_steps, device)
    print(f'{eng} => {translation}, bleu {bleu(translation, fra, k=2):.3f}')
go . => va !, bleu 1.000
i lost . => j'ai perdu ., bleu 1.000
he's calm . => il est paresseux ., bleu 0.658
i'm home . => je suis chez moi ., bleu 1.000

(2)重新运行实验并在计算损失时不使用遮蔽,可以观察到什么结果?为什么会有这个结果?

翻译效果变差,可能是填充词元使翻译的逻辑更困难了。

class MaskedSoftmaxCELoss_test(nn.CrossEntropyLoss):
    def forward(self, pred, label, valid_len):
        self.reduction='none'
        return super(MaskedSoftmaxCELoss_test, self).forward(
            pred.permute(0, 2, 1), label).mean(dim=1)

def train_seq2seq_test(net, data_iter, lr, num_epochs, tgt_vocab, device):
    """训练序列到序列模型"""
    def xavier_init_weights(m):  # xavier 初始化
        if type(m) == nn.Linear:
            nn.init.xavier_uniform_(m.weight)
        if type(m) == nn.GRU:
            for param in m._flat_weights_names:
                if "weight" in param:
                    nn.init.xavier_uniform_(m._parameters[param])

    net.apply(xavier_init_weights)
    net.to(device)
    optimizer = torch.optim.Adam(net.parameters(), lr=lr)  # 使用 Adam 优化器
    loss = MaskedSoftmaxCELoss_test()  # 使用改造的交叉熵损失
    net.train()
    animator = d2l.Animator(xlabel='epoch', ylabel='loss',
                     xlim=[10, num_epochs])
    for epoch in range(num_epochs):
        timer = d2l.Timer()
        metric = d2l.Accumulator(2)  # 设置两个累加器:训练损失总和,词元数量
        for batch in data_iter:
            optimizer.zero_grad()
            X, X_valid_len, Y, Y_valid_len = [x.to(device) for x in batch]  # 加载数据
            bos = torch.tensor([tgt_vocab['<bos>']] * Y.shape[0],  # 获取特定的开始词元
                          device=device).reshape(-1, 1)
            dec_input = torch.cat([bos, Y[:, :-1]], 1)  # 强制教学,拼接开始词元和原始输出序列
            Y_hat, _ = net(X, dec_input, X_valid_len)  # 前向传播
            l = loss(Y_hat, Y, Y_valid_len)  # 计算损失
            l.sum().backward()  # 损失函数的标量进行“反向传播”
            d2l.grad_clipping(net, 1)  # 梯度裁剪
            num_tokens = Y_valid_len.sum()
            optimizer.step()  # 优化
            with torch.no_grad():
                metric.add(l.sum(), num_tokens)
        if (epoch + 1) % 10 == 0:
            animator.add(epoch + 1, (metric[0] / metric[1],))
    print(f'loss {metric[0] / metric[1]:.3f}, {metric[1] / timer.stop():.1f} '
        f'tokens/sec on {str(device)}')
    
embed_size2, num_hiddens2, num_layers2, dropout2 = 32, 32, 2, 0.1
batch_size2, num_steps2 = 64, 10
lr2, num_epochs2, device2 = 0.005, 300, d2l.try_gpu()

train_iter2, src_vocab2, tgt_vocab2 = d2l.load_data_nmt(batch_size2, num_steps2)
encoder2 = Seq2SeqEncoder(len(src_vocab2), embed_size2, num_hiddens2, num_layers2,
                        dropout2)
decoder2 = Seq2SeqDecoder(len(tgt_vocab2), embed_size2, num_hiddens2, num_layers2,
                        dropout2)
net2 = d2l.EncoderDecoder(encoder2, decoder2)
train_seq2seq_test(net2, train_iter2, lr2, num_epochs2, tgt_vocab, device2)
loss 0.019, 14341.4 tokens/sec on cuda:0

在这里插入图片描述

engs = ['go .', "i lost .", 'he\'s calm .', 'i\'m home .']
fras = ['va !', 'j\'ai perdu .', 'il est calme .', 'je suis chez moi .']
for eng, fra in zip(engs, fras):
    translation, attention_weight_seq = predict_seq2seq(
        net2, eng, src_vocab, tgt_vocab, num_steps, device)
    print(f'{eng} => {translation}, bleu {bleu(translation, fra, k=2):.3f}')
go . => va !, bleu 1.000
i lost . => j'ai perdu perdu ., bleu 0.783
he's calm . => attrapez tom ., bleu 0.000
i'm home . => je suis chez moi mouvement de tom ., bleu 0.640

(3)如果编码器和解码器的层数或者隐藏单元数不同,那么如何初始化解码器的隐状态?

不会,略。


(4)在训练中,如果用前一时间步的预测输入到解码器来代替强制教学,对性能有何影响?

预测会越来越偏吧。


(5)用长短期记忆网络替换门控循环单元重新运行实验。

class Seq2SeqEncoder_test(d2l.Encoder):
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                 dropout=0, **kwargs):
        super(Seq2SeqEncoder_test, self).__init__(**kwargs)
        self.embedding = nn.Embedding(vocab_size, embed_size)
        self.lstm = nn.LSTM(embed_size, num_hiddens, num_layers,  # 更换为 LSTM
                          dropout=dropout)

    def forward(self, X, *args):
        X = self.embedding(X)
        X = X.permute(1, 0, 2)
        output, state = self.lstm(X)
        return output, state
    
class Seq2SeqDecoder_test(d2l.Decoder):
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                 dropout=0, **kwargs):
        super(Seq2SeqDecoder_test, self).__init__(**kwargs)
        self.embedding = nn.Embedding(vocab_size, embed_size)
        self.lstm = nn.LSTM(embed_size + num_hiddens, num_hiddens, num_layers,  # 更换为 LSTM
                          dropout=dropout)
        self.dense = nn.Linear(num_hiddens, vocab_size)

    def init_state(self, enc_outputs, *args):
        return enc_outputs[1]

    def forward(self, X, state):
        X = self.embedding(X).permute(1, 0, 2)
        # context = state[-1].repeat(X.shape[0], 1, 1)
        context = state[-1][0].repeat(X.shape[0], 1, 1)  # 注意 LSTM 有 hidden state 和 cell state,这里使用 hidden state
        X_and_context = torch.cat((X, context), 2)
        output, state = self.lstm(X_and_context, state)
        output = self.dense(output).permute(1, 0, 2)
        return output, state
    
embed_size3, num_hiddens3, num_layers3, dropout3 = 32, 32, 2, 0.1
batch_size3, num_steps3 = 64, 10
lr3, num_epochs3, device3 = 0.005, 300, d2l.try_gpu()

train_iter3, src_vocab3, tgt_vocab3 = d2l.load_data_nmt(batch_size3, num_steps3)
encoder3 = Seq2SeqEncoder_test(len(src_vocab3), embed_size3, num_hiddens3, num_layers3,
                        dropout3)
decoder3 = Seq2SeqDecoder_test(len(tgt_vocab3), embed_size3, num_hiddens3, num_layers3,
                        dropout3)
net3 = d2l.EncoderDecoder(encoder3, decoder3)
train_seq2seq(net3, train_iter3, lr3, num_epochs3, tgt_vocab3, device3)
loss 0.019, 14508.5 tokens/sec on cuda:0

在这里插入图片描述

engs = ['go .', "i lost .", 'he\'s calm .', 'i\'m home .']
fras = ['va !', 'j\'ai perdu .', 'il est calme .', 'je suis chez moi .']
for eng, fra in zip(engs, fras):
    translation, attention_weight_seq = predict_seq2seq(
        net2, eng, src_vocab, tgt_vocab, num_steps, device)
    print(f'{eng} => {translation}, bleu {bleu(translation, fra, k=2):.3f}')
go . => va !, bleu 1.000
i lost . => j'ai perdu perdu ., bleu 0.783
he's calm . => attrapez tom ., bleu 0.000
i'm home . => je suis chez moi mouvement de tom ., bleu 0.640

(6)有没有其他方法来设计解码器的输出层?

不会,略。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/104065.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

重测序基因组:Pi核酸多样性计算

如何计算核酸多样性 Pi 本期笔记分享关于核酸多样性pi计算的方法和相关技巧&#xff0c;主要包括原始数据整理、分组文件设置、计算原理、操作流程、可视化绘图等步骤。 基因组Pi核酸多样性&#xff08;Pi nucleic acid diversity&#xff09;是一种遗传学研究中用来描述种群内…

H5前端开发——BOM

H5前端开发——BOM BOM&#xff08;Browser Object Model&#xff09;是指浏览器对象模型&#xff0c;它提供了一组对象和方法&#xff0c;用于与浏览器窗口进行交互。 通过 BOM 对象&#xff0c;开发人员可以操作浏览器窗口的行为和状态&#xff0c;实现与用户的交互和数据传…

设计模式之命令模式

文章目录 一、介绍二、命令模式中的角色三、案例1. 命令的抽象接口Command2. 进攻AttackCommand3. 意大利炮cannonCommand4. 开炮FireCommand5. 李云龙LiYunLong6. 运行案例 四、优缺点 一、介绍 命令模式(Command Pattern)&#xff0c;属于行为型设计模式。指的是把方法调用封…

系统架构设计师之RUP软件开发生命周期

系统架构设计师之RUP软件开发生命周期

自建的离散傅里叶变换matlab程序实现及其与matlab自带函数比较举例

自建的离散傅里叶变换matlab程序实现及其与matlab自带函数比较举例 在matlab中有自带的离散傅里叶变换程序&#xff0c;即fft程序&#xff0c;但该程序是封装的&#xff0c;无法看到源码。为了比较清楚的了解matlab自带的实现过程&#xff0c;本文通过自建程序实现matlab程序&…

IntelliJ IDEA 2023.2正式发布,新UI和Profiler转正

你好&#xff0c;我是YourBatman&#xff1a;做爱做之事❣交配交之人。 &#x1f4da;前言 北京时间2023年7月26日&#xff0c;IntelliJ IDEA 2023.2正式发布。老规矩&#xff0c;吃肉之前&#xff0c;可以先把这几碗汤干了&#xff0c;更有助于消化&#xff08;每篇都很顶哦…

排序-表排序

当我们需要对一个很大的结构体进行排序时&#xff0c;因为正常的排序需要大量的交换&#xff0c;这就会造成时间复杂度的浪费 因此&#xff0c;我们引入指针&#xff0c;通过指针临时变量的方式来避免时间复杂度的浪费 间接排序-排序思路&#xff1a;通过开辟一个指针数组&…

十个最常用的计算机视觉数据集

如今&#xff0c;人工智能和机器学习领域中最振奋人心的一个分支是计算机视觉&#xff08;Computer Vision&#xff0c;简称CV&#xff09;。CV应用于多种场景&#xff0c;以改善我们的日常生活&#xff0c;并推进科学技术研究。其中包括&#xff1a; 自动驾驶自动生成图像描述…

重入漏洞EtherStore

重入漏洞 // SPDX-License-Identifier: MIT pragma solidity ^0.8.13;contract EtherStore {mapping(address > uint) public balances;function deposit() public payable {balances[msg.sender] msg.value;}function withdraw() public {uint bal balances[msg.sender]…

Linux 函数调用的用户态与内核态

在用户态中&#xff0c;程序的执行往往是一个函数调用另一个函数。函数调用都是通过栈来进行的。 在进程的内存空间里面&#xff0c;栈是一个从高地址到低地址&#xff0c;往下增长的结构&#xff0c;也就是上面是栈底&#xff0c;下面是栈顶&#xff0c;入栈和出栈的操作都是…

ModbusTCP 转 Profinet 主站网关在博图配置案例

兴达易控ModbusTCP转Profinet网关&#xff0c;在 Profinet 侧做为 Profinet 主站控制器&#xff0c;接 Profinet 设备&#xff0c;如伺服驱动器&#xff1b;兴达易控ModbusTCP 和 Profinet网关在 ModbusTCP 侧做为 ModbusTCP 从站&#xff0c;接 PLC、上位机、wincc 屏等。 拓…

k8s kubeadm配置

master 192.168.41.30 docker、kubeadm、kubelet、kubectl、flannel node01 192.168.41.31 docker、kubeadm、kubelet、kubectl、flannel node02 192.168.41.32 do…

python 字典dict和列表list的读取速度问题, range合并

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 python 字典和列表的读取速度问题 最近在进行基因组数据处理的时候&#xff0c;需要读取较大数据&#xff08;2.7G&#xff09;存入字典中&#xff0c; 然后对被处理数据进行字典key值的匹配&#xff0c;在被处理文件中每次…

Python:实现日历到excel文档

背景 日历是一种常见的工具,用于记录事件和显示日期。在编程中,可以使用Python编码来制作日历。 Python提供了一些内置的模块和函数,使得制作日历变得更加简单。 在本文,我们将探讨如何使用Python制作日历,并将日历输出到excel文档中。 效果展示 实现 在代码中会用到cale…

TypeScript学习 | 泛型

简介 泛型是指在定义函数、接口或类的时候&#xff0c;不预先指定具体的类型&#xff0c;而在使用的时候再指定类型的一种特性 作用 可以保证类型安全的前提下&#xff0c;让函数、接口或类与多种类型一起工作&#xff0c;从而实现复用 基本使用 举个例子&#xff1a; 创…

各品牌PLC存储器寻址的规则

在PLC编程时&#xff0c;字节或多字节的变量一般支持绝对地址寻址&#xff08;比如&#xff0c;IW0、MD4等&#xff09;。要想正确寻址&#xff0c;则必须要搞清楚寻址的规则。目前常见的规则有两种&#xff1a;字节寻址和字寻址。下图清晰地表达了两种规则的编号情况&#xff…

【C++】stackqueue

适配器是一种设计模式 &#xff0c; 该种模式是将一个类的接口转换成客户希望的另外一个接口 。 虽然 stack 和 queue 中也可以存放元素&#xff0c;但在 STL 中并没有将其划分在容器的行列&#xff0c;而是将其称为 容器适配 器 &#xff0c;这是因为 stack 和队列只是对其他容…

上传和下载文件到google drive/Local pc

1 上传 参考&#xff1a;使用 Python 将文件上传到 Google 云端硬盘_迹忆客 Upload file to google drive using Python - CodeSpeedy (没起作用&#xff0c;但可以参考一下) 第 1 步&#xff1a;Google API Playground 我们可以通过搜索 Google 找到更多关于 Google API Pla…

vue路径中“@/“代表什么

举例&#xff1a; <img src"/../static/imgNew/adv/tupian.jpg"/>其中&#xff0c;/是webpack设置的路径别名&#xff0c;代表什么路径&#xff0c;要看webpack的build文件夹下webpack.base.conf.js里面对于是如何配置&#xff1a; 上图中代表src,上述代码就…

出海路上离不开的Email营销,教你这样来优化!

随着互联网的不断发展&#xff0c;Email已经成为人们工作和生活中不可或缺的一部分。尤其是对于我们这些跨境企业而言&#xff0c;发送Email是一个促进销售和维护客户关系的良好渠道。而且邮件的价格也是比较低廉的&#xff0c;很适合用于日常推广营销&#xff0c;所以人手几个…