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

news2024/11/19 5:56:41

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

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

在这里插入图片描述

要点:

  • “<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)有没有其他方法来设计解码器的输出层?

不会,略。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于使用 vxe-table 时设置了 show-overflow tooltip 不展示的问题(Dialog 组件和 table 同时使用)

众所周知&#xff0c;vxe-table 是可以支撑万级数据渲染的表格组件&#xff0c;本质上还是用了虚拟滚动的实现。之前一直知道vxe-table, 但是基本没有机会用的上这个组件&#xff0c;最近在开发埋点数据的统计&#xff0c;后端一次性返回了上千条数据&#xff0c;elementui 的 …

【Shell】环境变量 自定义变量 特殊变量

Shell变量&#xff1a;环境变量 目标 1、理解什么是系统环境变量&#xff1f; 2、掌握常用的系统环境变量都有哪些&#xff1f; Shell变量的介绍 变量用于存储管理临时的数据, 这些数据都是在运行内存中的. 变量类型 系统环境变量 自定义变量 特殊符号变量 系统环境变…

golang查看CPU使用率与内存及源码中的//go:指令

golang查看CPU使用率与内存 1 psutil 1.1 概念与应用场景 psutil是业内一个用于监控OS资源的软件包&#xff0c;目前已经多种语言&#xff0c;包括但不限于Python、Go。 gopsutil是 Python 工具库psutil 的 Golang 移植版&#xff0c;可以帮助我们方便地获取各种系统和硬件信…

基于Java的实验室设备管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

如何在电脑上设置新的蓝牙耳机

本文介绍如何将蓝牙耳机连接到Windows或Mac电脑。 如何在Windows上设置新的蓝牙耳机 蓝牙耳机的设置过程因平台而异&#xff0c;但以下是Windows 11的步骤&#xff1a; 1、选择“开始”&#xff0c;然后在搜索框中输入蓝牙&#xff0c;以显示蓝牙和其他设备。 2、选择添加设…

【LeetCode:2316. 统计无向图中无法互相到达点对数 | BFS + 乘法原理】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

施密特正交化

相信大家在平时的期末考试中一定少不了对某某向量组执行标准正交化类型的题目。今天我们从这个题目入手&#xff0c;说明这个如何执行施密特正交化&#xff0c;以及为什么要进行正交化。 一、例子 例子&#xff1a;设 a 1 [ 1 2 − 1 ] a_1\begin{bmatrix}1\\2\\-1\end{bmat…

阿里妈妈Union Lab全量公测,你会用吗?

Union Lab是一种智能化的选品推荐、推广内容创作工具&#xff0c;它内置了大语言模型&#xff08;LLMs&#xff09;&#xff0c;使得选品、推广更加智能和简单。通过联盟底层货品服务&#xff0c;Union Lab可以实现智能化货品发现、分析、选品、投放、创意生产&#xff0c;基于…

嵌入式mqtt总线架构方案mosquitto+paho

一 mqtt通信模型 MQTT 协议提供一对多的消息发布&#xff0c;可以降低应用程序的耦合性&#xff0c;用户只需要编写极少量的应用代码就能完成一对多的消息发布与订阅&#xff0c;该协议是基于<客户端-服务器>模型&#xff0c;在协议中主要有三种身份&#xff1a;发布者&…

力扣第37题 解数独 c++ 难~ 回溯

题目 37. 解数独 困难 相关标签 数组 哈希表 回溯 矩阵 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫…

基于Java的文物管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

GRASP 、SOLID 与 GoF 设计模式

一、GRASP GRASP&#xff1a;通用职责分配软件设计模式(General Responsibility Assignment Software Patterns)&#xff0c;其主要思想是基于单一职责设计软件对象。 思考软件对象设计以及大型构件的流行方式是&#xff0c;考虑其职责、角色和协作。这是被称为职责驱动设计&a…

C++:无法查找或打开 PDB 文件?? 如何解决呢?以及产生原因是什么?

C:无法查找或打开 PDB 文件?? 如何解决呢&#xff1f;以及产生原因是什么&#xff1f; 前言解决办法原因 前言 最近博主在写C时&#xff0c;明明代码都正确&#xff0c;但编译失败。查看原因后发现显示&#xff1a;无法查找或打开 PDB 文件。&#xff08;先介绍解决办法&…

【高等数学】微分中值定理

文章目录 1、极值2、费马引理3、罗尔定理4、拉格朗日中值定理4.1用拉格朗日定理证明基本结论 5、柯西中值定理6、微分中值定理的意义7、三大中值定理的意义 1、极值 若 ∃ δ > 0 ∃δ>0 ∃δ>0&#xff0c;使得 ∀ x ∈ U ( x 0 , δ ) ∀x\in U(x_0,δ) ∀x∈U(x0…

DDD与微服务的千丝万缕

一、软件设计发展过程二、什么是DDD&#xff1f;2.1 战略设计2.2 战术设计2.3 名词扫盲1. 领域和子域2. 核心域、通用域和支撑域3. 通用语言4. 限界上下文5. 实体和值对象6. 聚合和聚合根 2.4 事件风暴2.5 领域事件 三、DDD与微服务3.1 DDD与微服务的关系3.2 基于DDD进行微服务…

2023年中国煤气节能器产量及市场规模分析[图]

煤气节能器行业是指生产和销售用于煤气燃烧系统中的节能器的行业。煤气节能器是一种能够有效提高煤气燃烧效率、降低燃烧过程中的热损失的装置&#xff0c;广泛应用于工业生产、民用燃烧等领域。煤气节能器行业的发展与煤气燃烧技术的发展密切相关&#xff0c;随着煤气燃烧技术…

操作系统期末复习题版详解(含解析)

操作系统期末复习 第一章 操作系统引论 一、Os具有哪几大特征?它们之间有何关系? 【参考答案】OS具有并发、共享、虚拟和异步这4个基本特征。它们之间的关系包含以下几个方面。 并发和共享是OS最基本的特征。为了提高计算机资源的利用率&#xff0c;OS必然要采用多道程序设…

Excel 5s内导入20w条简单数据(ExecutorType.BATCH)Mybatis批处理的应用

文章目录 Excel 5s内导入20w条数据1. 生成20w条数据1.1 使用Excel 宏生成20w条数据1.2 生成成功 2. ExecutorType&#xff1a;批量操作执行器类型2.1 ExecutorType.SIMPLE2.2 ExecutorType.BATCH2.3 ExecutorType.REUSE 3. 20w条数据直接插入数据库3.1 使用ExecutorType.SIMPLE…

成功解决ModuleNotFoundError: No module named ‘docx.text.hyperlink‘

成功解决ModuleNotFoundError: No module named docx.text.hyperlink 目录 解决问题 解决思路 解决方法 解决问题 ModuleNotFoundError: No module named ‘docx.text.hyperlink‘ 解决思路 No module named docx.text.hyperlink"。这个错误通常表示你的代码中缺少了…

react 中ref 属性的三种写法

目录 1. 字符串 ref 2.dom节点上使用回调函数ref 3.React.createRef() 1. 字符串 ref 最早的ref用法。&#xff08;由于效率问题&#xff0c;现在官方不推荐使用这种写法。&#xff09; 1.dom节点上使用&#xff0c;通过this.refs.xxxx来引用真实的dom节点 <input ref&q…