首页 > 其他分享 >《动手学深度学习 Pytorch版》 9.7 序列到序列学习(seq2seq)

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

时间:2023-10-20 11:58:00浏览次数:44  
标签:vocab self seq2seq len Pytorch num 序列 size

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

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

image

要点:

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

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

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

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

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

9.7.1 编码器

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

\[\boldsymbol{h}_t=f(\boldsymbol{x}_t,\boldsymbol{h}_{t-1}) \]

参数字典:

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

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

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

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

\[\boldsymbol{c}=q(\boldsymbol{h}_t,\dots,\boldsymbol{h}_T) \]

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

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

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

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

  • 对于任意输入词元的索引 \(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,\dots,y_{t'-1},\boldsymbol{c}) \]

参数字典:

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

  • \(y_1,y_2,\dots,y_{T'}\) 表示训练数据集的输出序列

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

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

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

\[\boldsymbol{s}_{t'}=g(y_{t'-1},\boldsymbol{c},\boldsymbol{s}_{t'-1}) \]

获得解码器的隐状态之后,可以使用输出层和 softmax 操作来计算在时间步 \(t'\) 时输出 \(y_{t'}\) 的条件概率分布 \(P(y_{t'}|y_1,\dots,y_{t'-1},\boldsymbol{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]))

总结构图:

image

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

image

9.7.5 预测

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

image

#@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{\left(\min{\left(0,1-\frac{len_{label}}{len_{pred}}\right)}\right)}\prod^k_{n=1}p_n^{1/2^n} \]

参数字典:

  • \(len_{label}\) 表示标签序列中的词元数

  • \(len_{pred}\) 表示预测序列中的词元数

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

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

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

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

设计要点:

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

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

  • 由于预测的序列越短获得的 \(p_n\) 值越高,所以 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

image

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

image

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

image

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

不会,略。

标签:vocab,self,seq2seq,len,Pytorch,num,序列,size
From: https://www.cnblogs.com/AncilunKiang/p/17776747.html

相关文章

  • 使用Pytorch Geometric 进行链接预测代码示例
    PyTorchGeometric(PyG)是构建图神经网络模型和实验各种图卷积的主要工具。在本文中我们将通过链接预测来对其进行介绍。链接预测答了一个问题:哪两个节点应该相互链接?我们将通过执行“转换分割”,为建模准备数据。为批处理准备专用的图数据加载器。在TorchGeometric中构建一个......
  • PyTorch大更新,编译代码速度暴增35倍!视觉模型一键部署,头显Quest 3可用
    前言 最近,在Pytorch发布会上,发布移动端Pytorch解决方案ExecuTorch,实现在移动端设备上大范围地部署AI工具,并推出最新版本Pytorch2.1,推理速度大幅提升。本文转载自新智元仅用于学术分享,若侵权请联系删除欢迎关注公众号CV技术指南,专注于计算机视觉的技术总结、最新技术跟踪、经典......
  • 【Java】Vert.x Jackson 序列化后日期数据正常展示
    有段时间没有更新了,年尾嘛大家都懂的。其实最近有个想法,想将自己的vtx_fw框架给开源了。但开源之前还是有很多收尾的工作需要做的(总不能让各位笑话吧o(╥﹏╥)o),这不今天就发现了一个问题,立刻就归纳一下给各位分享。这个问题就是Vert.x框架中日期类型数据在Jackson序列化下的......
  • Acwing 最长上升子序列
    题目给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。输入格式第一行包含整数N。第二行包含N个整数,表示完整序列。输出格式输出一个整数,表示最大长度。数据范围1≤N≤1000−10^9≤数列中的数≤10^9输入样例:73121856输出样例:4题解......
  • gson如何序列化子类
    需求目前有一个需求,不同对象有一些公共属性,分别也有一些不同的属性。对方传过来的json字符串中,把这些对象组成了一个数组返回过来的。这样该如何反序列化呢?举例定义Person类、Student类、Worker类;@Data@ToStringpublicclassPerson{//姓名privateStringname;......
  • 《动手学深度学习 Pytorch版》 9.5 机器翻译与数据集
    机器翻译(machinetranslation)指的是将序列从一种语言自动翻译成另一种语言,基于神经网络的方法通常被称为神经机器翻译(neuralmachinetranslation)。importosimporttorchfromd2limporttorchasd2l9.5.1下载和预处理数据集“Tab-delimitedBilingualSentencePairs”......
  • 由Django-Session配置引发的反序列化安全问题
    漏洞成因漏洞成因位于目标配置文件settings.py下关于这两个配置项SESSION_ENGINE:在Django中,SESSION_ENGINE 是一个设置项,用于指定用于存储和处理会话(session)数据的引擎。SESSION_ENGINE 设置项允许您选择不同的后端引擎来存储会话数据,例如:数据库后端 (django.contrib.sessions.b......
  • 《动手学深度学习 Pytorch版》 9.4 双向循环神经网络
    之前的序列学习中假设的目标是在给定观测的情况下对下一个输出进行建模,然而也存在需要后文预测前文的情况。9.4.1隐马尔可夫模型中的动态规划数学推导太复杂了,略。9.4.2双向模型双向循环神经网络(bidirectionalRNNs)添加了反向传递信息的隐藏层,以便更灵活地处理此类信息。9......
  • LSTM-CRF模型详解和Pytorch代码实现
    在快速发展的自然语言处理领域,Transformers已经成为主导模型,在广泛的序列建模任务中表现出卓越的性能,包括词性标记、命名实体识别和分块。在Transformers之前,条件随机场(CRFs)是序列建模的首选工具,特别是线性链CRFs,它将序列建模为有向图,而CRFs更普遍地可以用于任意图。本文中crf......
  • 《动手学深度学习 Pytorch版》 9.2 长短期记忆网络(LSTM)
    解决隐变量模型长期信息保存和短期输入缺失问题的最早方法之一是长短期存储器(longshort-termmemory,LSTM)。它与门控循环单元有许多一样的属性。长短期记忆网络的设计比门控循环单元稍微复杂一些,却比门控循环单元早诞生了近20年。9.2.1门控记忆元为了记录附加的信息,长短期记......