赞
踩
序列到序列(Sequence-to-Sequence,简称Seq2Seq)模型是一种深度学习架构,主要用于处理输入和输出都是变长序列的任务,例如机器翻译、文本摘要、语音识别转文字、聊天机器人对话等自然语言处理任务。Seq2Seq模型的核心思想是使用两个主要部分组成的神经网络框架:编码器(Encoder)和解码器(Decoder)。
在Seq2Seq模型中,注意力机制通常被整合到解码器端的设计中,用来解决原始seq2seq模型在处理长输入序列时所遇到的问题:即解码器在生成每个输出元素时只能依赖于编码器输出的一个固定长度的上下文向量(通常是编码器最后一层的隐藏状态),这可能无法充分捕捉到输入序列的所有重要信息。
在解码器端添加注意力机制后,解码器在生成每个输出词时能够动态地关注输入序列的不同部分。具体来说,解码器会在每一步生成过程中根据当前生成的状态和之前的上下文信息计算一个权重分布,这个分布代表了对输入序列各位置的关注程度。通过加权求和的方式,解码器可以根据这些权重实时重新组合编码器的所有隐藏状态,从而得到一个更灵活且针对当前生成步骤更有针对性的上下文向量。
相比之下,将注意力机制放在编码器端并不常见,因为标准的seq2seq模型中编码器的主要职责是,尽可能完整地捕获输入序列的信息并将其压缩进一个或多个固定维度的向量中。如果要在编码器端引入注意力机制,可能是为了增强编码器内部不同时间步之间信息的交互或聚焦,但这并非注意力机制在seq2seq模型中最典型的应用场景。
然而,也有一些研究探索了在编码阶段使用注意力机制的方法,例如自注意力(self-attention,也称为内部注意力),这种机制允许输入序列内的各个元素相互比较和影响彼此的表示,这是Transformer模型等现代架构的核心组件。在这样的情况下,编码器端的注意力机制主要用于改进输入序列的编码过程,使其能够更加有效地提取全局依赖关系,而不是像解码器端那样用于动态参考输入序列。
总结来说,解码器端的注意力机制关注的是如何在生成输出时动态适应输入信息,而编码器端若采用注意力机制则更多是为了改进输入信息的编码质量或捕获输入序列内部复杂的依赖结构。
下面使用代码构建了一个由GRU编码器、含注意力的GRU解码器组成的Seq2Seq模型,用于完成机器翻译任务(N vs M类型):
# 构建基于GRU的编码器 class GRU_Encoder(nn.Module): def __init__(self, vocab_size,embed_dim,hidden_size): super().__init__() # 初始化属性 self.vocab_size = vocab_size # 输入词汇总数 self.embed_dim = embed_dim # 输入词汇的词嵌入维度 self.hidden_size = hidden_size # 隐藏层向量维度 # 实例化embed层 self.embed = nn.Embedding(vocab_size, embed_dim) # 实例化GRU层 self.gru = nn.GRU(embed_dim, hidden_size, num_layers=1, batch_first=True) def forward(self, input, hidden): # 输入数据经过embed层 out_embed = self.embed(input) # embed处理后的数据经过GRU层 output, hn = self.gru(out_embed, hidden) return output, hn def init_hidden(self): # batch_size=1 hidden = torch.zeros(1, 1, self.hidden_size) return hidden
# 构建加入attention的GRU解码器 class Attention_GRU_Decoder(nn.Module): def __init__(self,vocab_size,embed_dim,encoder_hidden_size,gru_hidden_size,max_length=10): super().__init__() # 定义一些属性:模型参数 self.vocab_size = vocab_size # 输出词汇总数 self.embed_dim= embed_dim # 输出词汇的词嵌入维度 self.encoder_hidden_size = encoder_hidden_size # encoder隐藏层张量维度 self.gru_hidden_size = gru_hidden_size # GRU层解码时的隐藏层张量维度,这里使用encoder的最终隐藏状态作为初始化s0 self.max_length = max_length # 翻译语句的最大长度 # 模型结构 # 实例化embed层 self.embed = nn.Embedding(vocab_size ,embed_dim) # droupout策略 self.droupout = nn.Dropout(p=0.1) # 注意力计算第1步的线性层:Linear([Q,K]) self.attn = nn.Linear(embed_dim + encoder_hidden_size,max_length) # 注意力计算第3步的线性层:对attn与Q拼接后的张量做线性变换,改变形状,得到对Q的注意力表示 self.attn_combine = nn.Linear(embed_dim + encoder_hidden_size, gru_hidden_size) # 实例化GRU层 self.gru = nn.GRU(gru_hidden_size,gru_hidden_size,num_layers=1,batch_first=True) # 实例化全连接层 self.out = nn.Linear(gru_hidden_size,vocab_size) # 输出层out的softmax激活 self.softmax = nn.LogSoftmax(dim=-1) # 前向传播:定义输入数据Q,K,V的计算过程 def forward(self,input,hidden,encoder_output_c): # input=Q # hidden=K # encoder_ouput_c=V: 中间语义张量 # 输入数据进入embed层 input = self.embed(input) # droupout处理:缓解过拟合 embedded = self.droupout(input) # 注意力分配系数计算 # 1.与K拼接后,经过第一个线性层,再进行softmax激活 attn_weights = F.softmax(self.attn(torch.cat((embedded[0],hidden[0]),dim=-1)),dim=-1) # 2.将attn_weights与V相乘 attn_applied = torch.bmm(attn_weights.unsqueeze(0),encoder_output_c.unsqueeze(0)) # 3.将attn_applied与Q拼接,经过线性层输出,准备送入GRU gru_input = self.attn_combine(torch.cat((attn_applied,input),dim=-1)) # 进行relu激活 gru_input = F.relu(gru_input) # gru模型 gru_output, hn = self.gru(gru_input,hidden) # 对gru模型的输出进行分类: 经过out全连接层,并且对分类结果进行softmax激活 output = self.softmax(self.out(gru_output[0])) return output, hn, attn_weights # GRU模型测隐藏状态初始化 # 这里因为直接使用encoder最后输出的隐藏状态作为decoder的初始化隐藏状态,所以这里没有用到 def init_hidden(self): hidden = torch.zeros(1, 1, self.hidden_size, device=device) return hidden
上面的代码中,解码器部分使用的注意力机制为 “加型Attention”,除此之外还有一种常用的注意力机制为 “乘型Attention"。“加型Attention”的注意力计算规则如下:
第一步:
A
t
t
e
n
t
i
o
n
(
Q
,
K
,
V
)
=
S
o
f
t
m
a
x
(
L
i
n
e
a
r
(
[
Q
,
K
]
)
)
∗
V
Attention(Q, K, V) = Softmax(Linear([Q, K])) * V
Attention(Q,K,V)=Softmax(Linear([Q,K]))∗V
第二步:
将
Q
Q
Q 与第一步的计算结果进行拼接。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。