4.11.seq2seq 序列到序列学习

news2024/9/21 12:42:40

序列到序列学习(seq2seq)

​ 使用两个循环神经网络的编码器和解码器,应用于序列到薛烈类的学习任务。

在这里插入图片描述

​ 在图中,特定的"<eos>"表示序列结束词元。一旦输出序列生成此词元,模型就会停止预测。在循环神经网络解码器的初始化时间步中,有两个特定的设计决定:

​ 首先,特定的"<bos>"表示序列开始词元,它是解码器的输入序列的第一个词元。

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

​ 编码器是一个RNN,读取输入句子(可以是双向,因为encode不用预测,有上下文的)

​ 解码器使用另一个RNN来输出

1.衡量生成序列的好坏的BLEU

p n p_n pn是预测所有 n − g r a m n-gram ngram的精度,标签序列A B C D E F 和预测序列A B B C D,有 p 1 = 4 5 , p 2 = 3 4 , p 3 = 1 3 , p 4 = 0 p_1=\frac 45,p_2 =\frac 34 ,p_3 = \frac 13 ,p_4 =0 p1=54,p2=43,p3=31,p4=0

n-gram就是连续几个字母预测对,比如 p 2 p_2 p2,就是连续2个,那么有4个匹配 AB,BB,BC,BD,其中有三个是对的,则为3/4

​ BLEU定义为:
e x p ( m i n ( 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(min(0,1-\frac{len_{label}}{len_{pred}})){\large\Pi ^k_{n=1}p_n^{\frac{1}{2^n}}} exp(min(0,1lenpredlenlabel))Πn=1kpn2n1
m i n min min部分是用于惩罚过短的预测,而指数为 1 2 n \frac {1}{2^n} 2n1意味着长匹配有更高的权重

2.代码实现

在这里插入图片描述

2.1 编码器

​ 使用嵌入层(embedding layer) 来获得输入序列中每个词元的特征向量。 嵌入层的权重是一个矩阵, 其行数等于输入词表的大小(vocab_size), 其列数等于特征向量的维度(embed_size)。 对于任意输入词元的索引i, 嵌入层获取权重矩阵的第i行(从0开始)以返回其特征向量。并选择使用多层门控循环单元来实现编码器

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,
                         num_layers=2)
encoder.eval()
X = torch.zeros((4, 7), dtype=torch.long)
output, state = encoder(X)
print(output.shape) #(时间步,batch_size,隐藏层大小)
print(state.shape) #(num_layers,时间步,隐藏层大小)

2.2 解码器

​ 编码器输出的上下文变量 c c c对整个输入序列及进行编码。来自训练数据集的输出序列 y 1 , y 2 , ⋯   , y T ′ y_1,y_2,\cdots,y_{T'} y1,y2,,yT,对于每个时间步 t ′ t' t(与输入序列或编码器的时间步 t t t不同),解码器输出 y t ′ y_{t'} yt的概率取决于先前的输出子序列 y 1 , ⋯   , y t ′ − 1 y_1,\cdots,y_{t'-1} y1,,yt1和上下文变量 c c c,即 P ( y t ′ ∣ y 1 , ⋯   , y t ′ − 1 , c ) P(y_{t'}|y_1,\cdots,y_{t'-1},c) P(yty1,,yt1,c)

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

​ 即 s t ′ = g ( y t ′ − 1 , c , s t ′ − 1 ) s_{t'} =g(y_{t'-1},c,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,\cdots,y_{t'-1},c) P(yty1,,yt1,c)

​ 由于我们将直接使用编码器最后一个隐藏状态来初始化解码器的隐状态,所以要求编码器和解码器有相同数量的层和隐藏单元。

​ 为了更好的包含编码后的输入序列的信息,上下文变量(编码器的输出)在所有的时间步都要与解码器的输入进行拼接。最后使用全连接层来变换隐状态。

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)
        # 输入是embed_size,还有编码器的输出num_hiddens,两个cat在一起作为rnn的输入
        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] # 0是编码器的output,1是state,用编码器的隐状态来初始化

    def forward(self, X, state):
        # 输出'X'的形状:(batch_size,num_steps,embed_size)
        X = self.embedding(X).permute(1, 0, 2)
        # 广播context,使其具有与X相同的num_steps
        # state[-1]是最后一层的隐状态输出,repeat时间步数次,变成三维的
        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)
print(output.shape, state.shape)

在这里插入图片描述

​ 说得很对,seq2seq这样更新就使得encoder的输出发生了改变,随意我们简单处理一下,将decoder的state和encoder输出状态分开,只更新state,就对了

2.3 损失函数

​ 在每个时间步,解码器都预测了输出词元的概率分布,随后可以使用softmax来获得分布,并通过交叉熵损失函数来进行优化。

​ 但注意到,某些特定的填充词被添加了,这可以使得不同长度的序列可以以相同形状的小批量加载,但我们应该将这些填充词元的预测排除在损失函数的计算之外。

​ 使用零值化屏蔽不相关的项:

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]
    X[~mask] = value
    return X

X = torch.tensor([[1, 2, 3], [4, 5, 6]])
sequence_mask(X, torch.tensor([1, 2])) # 保留前1项,前两项

'''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.]]])
'''

​ 随后,通过扩展softmax交叉损失函数来遮蔽不相关的预测:

#@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) #生成weight,0是不要的
        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()
print(loss(torch.ones(3, 4, 10), torch.ones((3, 4), dtype=torch.long),
     torch.tensor([4, 2, 0])))

2.4 训练

#@save
def train_seq2seq(net, data_iter, lr, num_epochs, tgt_vocab, device):
    """训练序列到序列模型"""
    def xavier_init_weights(m):
        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)
    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)

2.5 预测

#@save
def predict_seq2seq(net, src_sentence, src_vocab, tgt_vocab, num_steps,
                    device, save_attention_weights=False):
    """序列到序列模型的预测"""
    # 在预测时将net设置为评估模式
    net.eval()
    #将源句子转换为小写并分词,然后查找每个词元在词汇表中的索引,并添加 <eos> 标志。
    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

2.5 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}')

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

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

相关文章

Go语言中gin+gorm开发前端端分离博客时遇到的问题,gorm执行查询时如何选中特定字段?

代码获取 本篇文章的代码放在了Github上&#xff0c;可以免费获取。 https://github.com/zhangdapeng520/zdpgo_gin_examples 概述 在查询用户信息的时候&#xff0c;由于密码这个字段比较敏感&#xff0c;需要进行处理&#xff0c;不要返回给前端。 我一开始的解决方案是直…

统计学:条件概率模型

照片由Edge2Edge Media在Unsplash上拍摄 一、介绍 在概率的许多应用中&#xff0c;不可能直接观察实验的结果&#xff1b;而是观察与结果相关的事件。因此&#xff0c;条件概率模型对于考虑和利用从观察到的事件中获得的信息至关重要。此外&#xff0c;条件概率模型与贝叶斯定理…

【vue3】【elementPlus】【黑暗模式】

从创建vue3项目到引入elementPlus组件并设置黑暗模式 1.创建vue3项目&#xff1a; npm init vuelatest1.1 根据需求定制项目插件&#xff1a; 2.引入elementPlus组件&#xff1a; npm install element-plus --save2.1 如图注册全局elementPlus组件&#xff1a; ------------…

充电不再难,高质量充电体系‘智’领绿色出行新时代

充电不再难&#xff0c;高质量充电体系‘智’领绿色出行新时代 国家发展改革委新闻发言人近日在新闻发布会上郑重声明&#xff0c;将持续强化统筹协调&#xff0c;协同各相关部门加速构建高质量充电基础设施体系&#xff0c;以更有效地满足人民群众对绿色出行的需求。 新能源汽…

C语言:文件处理

文件处理 一、文件的类型&#xff08;一&#xff09;文本文件和二进制文件 &#xff08;二&#xff09;程序文件和数据文件数据文件按照二进制储存 二、文件的打开和关闭&#xff08;一&#xff09;文件指针&#xff08;二&#xff09;文件的打开和关闭1、fopen2、fclose &…

webshell管理工具-中国蚁剑

中国蚁剑 版本说明&#xff1a;中国蚁剑 下载地址&#xff1a;GitHub - AntSwordProject/AntSword-Loader: AntSword 加载器AntSword 加载器. Contribute to AntSwordProject/AntSword-Loader development by creating an account on GitHub.https://github.com/AntSwordProj…

数值分析【2】

目录 第三章 求解三角方程组​编辑 高斯消元​编辑 乘除次数&#xff1a;系数阵k^2,每行系数计算1&#xff0c;右边那列1 乘除总次数&#xff1a;​编辑 平方和 公式 列主元消去法 ​编辑 目的&#xff1a;舍入误差不扩散​编辑 直接LU分解​编辑 改进平方…

C#开发常见面试题三(浅复制和深复制的区别)

C#开发常见面试题三(浅复制和深复制的区别) 一.浅复制和深复制定义 &#xff08;1&#xff09;浅复制&#xff1a;复制一个对象的时候&#xff0c;仅仅复制原始对象中所有的非静态类型成员和所有的引用类型成员的引用。&#xff08;新对象和原对象将共享所有引用类型成员的实…

django如何更新数据库字段并与数据库保持同步?

关键步骤&#xff1a; 第一步&#xff1a; 执行&#xff1a;python manage.py makemigrations 你的项目名称第二步&#xff1a;它会提示你选1还是2&#xff0c;这里因为添加字段&#xff0c;所以选1第三步&#xff1a;出现>>>这个&#xff0c;直接输入这个第四步&am…

Win11+docker+vscode配置anomalib并训练自己的数据(3)

在前两篇博文中,我使用Win11+docker配置了anomalib,并成功的调用了GPU运行了示例程序。这次我准备使用anomalib训练我自己的数据集。 数据集是我在工作中收集到的火腿肠缺陷数据,与MVTec等数据不同,我的火腿肠数据来源于多台设备和多个品种,因此,它们表面的纹理与颜色差异…

C语言 | Leetcode C语言题解之第329题矩阵中的最长递增路径

题目&#xff1a; 题解&#xff1a; const int dirs[4][2] {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; int rows, columns;typedef struct point {int x, y; } point;int longestIncreasingPath(int** matrix, int matrixSize, int* matrixColSize) {if (matrixSize 0 || matrixC…

基于双PI+EKF扩展卡尔曼滤波的PMSM速度控制simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 PMSM数学模型 4.2 双PI控制器设计 4.3 扩展卡尔曼滤波器(EKF) 4.4 控制系统实现 5.完整工程文件 1.课题概述 基于双PIEKF扩展卡尔曼滤波的PMSM速度控制simulink建模与仿真。对比基于双PI的扩展卡…

【C/C++笔记】:易错难点3 (二叉树)

选择题 &#x1f308;eg1 一棵有15个节点的完全二叉树和一棵同样有15个节点的普通二叉树&#xff0c;叶子节点的个数最多会差多少个&#xff08;&#xff09;&#xff1f; 正确答案&#xff1a; C A. 3 B. 5 C. 7 D. 9 解析&#xff1a;普通二叉树的叶子节…

Java刷题:轮转数组

目录 题目 解题思路 完整代码 题目 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解题思路 主要的解题思路是&#xff0c;先把整个数组翻转过来&a…

如何解决.NET8 类库Debug时,Debug文件夹中不包含Packages中引入的文件

最近在开发中使用了.NET8 Razor类库项目&#xff0c;但是惊讶的发现Debug时&#xff0c;Debug文件夹中不包含Packages中引入的文件&#xff0c;本以为是非常小的问题&#xff0c;但是被困住了竟然足足4个小时。 其实它也本就是个非常非常小的问题…… 只需引入<CopyLocalL…

智驭灌区,科技领航—— 高效灌区信息化系统管理平台

在水资源日益珍贵的今天&#xff0c;传统灌区的粗放式管理模式已难以满足现代农业的发展需求。我们自豪地推出——灌区信息化系统管理平台&#xff0c;以科技赋能水利&#xff0c;引领灌溉管理进入智能化、精细化新时代。 【智能决策&#xff0c;精准灌溉】 告别传统灌溉的盲目…

84.WEB渗透测试-信息收集-框架组件识别利用(8)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;83.WEB渗透测试-信息收集-框架组件识别利用&#xff08;7&#xff09; 识别的作用&#…

【初阶数据结构题目】15.有效的括号

有效的括号 点击链接答题 思路&#xff1a; 定义一个指针ps遍历字符串 若ps遍历到的字符为左括号&#xff0c;入栈 若ps遍历到的字符为右括号&#xff0c;取栈顶元素与ps进行比较&#xff0c; 栈顶元素 匹配 *ps&#xff0c;出栈&#xff0c;ps栈顶元素 不匹配 *ps&#xff0c…

【Python】nn.CTCLoss()函数详解与示例

前言 在深度学习领域&#xff0c;特别是在处理序列到序列的预测任务时&#xff0c;如语音识别和手写识别&#xff0c;nn.CTCLoss函数是一个非常重要的工具。本文将详细解析PyTorch中的nn.CTCLoss函数&#xff0c;包括其原理、原型和示例。 前言函数原理CTC算法简介CTC Loss函数…

Golang在整洁架构基础上实现事务

前言 大家好&#xff0c;这里是白泽&#xff0c;这篇文章在 go-kratos 官方的 layout 项目的整洁架构基础上&#xff0c;实现优雅的数据库事务操作。 视频讲解 &#x1f4fa;&#xff1a;B站&#xff1a;白泽talk 本期涉及的学习资料&#xff1a; 我的开源Golang学习仓库&am…