Seq2seq进阶,双向GRU
文章目录主要使用attentionEncoderAttention一般编码器中得到的向量包含所有的上下文信息, 是高度压缩的信息, 怎么样来避免这种压缩呢?这是原来的seq2seq, 当然有些小的改进在此不做介绍了。主要使用attentionattention 向量a, 长度是源向量的长度, ai在0~1之间, 总和为1, 可以得到加权后的向量w在解码的每个时间步计算一个加权向量, 把它作为解码器
一般编码器中得到的向量包含所有的上下文信息, 是高度压缩的信息, 怎么样来避免这种压缩呢?
- 这是原来的seq2seq, 当然有些小的改进在此不做介绍了。
主要使用attention
attention 向量a, 长度是源向量的长度, ai在0~1之间, 总和为1, 可以得到加权后的向量w
在解码的每个时间步计算一个加权向量, 把它作为解码器和线性层的输入来进行预测
Encoder
使用双向GRU构建编码器
-
此时output的大小为
src_len, batch_size, hid_dim*num_directions
, 其中第三轴的hidden的元素是两个分开的, 一个是前向的一个是后向的, 如下图
可以把所有的hidden前向和后向串联表示, 如下图:
-
此时hidden的大小是
n_layers*num directions, batch_size, hid_dim
其中[-2, :, :]给出顶层RNN在最后一个时间步长后的隐藏状态(即在它看到句子中的最后一个单词后),[-1, :, :]给出顶层RNN在最后一个时间步长后的隐藏状态(即在它看到句子中的第一个单词后)。 -
由于解码器不是双向的,它只需要一个上下文向量z来作为它的初始隐藏状态s0,而我们目前有两个,一个向前的,一个向后的( z → = h → T \overrightarrow z=\overrightarrow h_T z=hT 和 z ← = h ← T \overleftarrow z=\overleftarrow h_T z=hT,分别)。我们通过将两个上下文向量连接在一起,通过一个线性层g,并应用tanh激活函数来解决这个问题。公式如下:
class Encoder(nn.Module):
def __init__(self, input_dim, emb_dim, enc_hid_dim, dec_hid_dim, dropout):
super().__init__()
self.embedding = nn.Embedding(input_dim, emb_dim)
self.rnn = nn.GRU(emb_dim, enc_hid_dim, bidirectional = True)
self.fc = nn.Linear(enc_hid_dim * 2, dec_hid_dim)
self.dropout = nn.Dropout(dropout)
def forward(self, src):
#src = [src len, batch size]
embedded = self.dropout(self.embedding(src))
#embedded = [src len, batch size, emb dim]
outputs, hidden = self.rnn(embedded)
#outputs = [src len, batch size, hid dim * num directions]
#hidden = [n layers * num directions, batch size, hid dim]
#hidden is stacked [forward_1, backward_1, forward_2, backward_2, ...]
#outputs are always from the last layer
#hidden [-2, :, : ] is the last of the forwards RNN
#hidden [-1, :, : ] is the last of the backwards RNN
#initial decoder hidden is final hidden state of the forwards and backwards
# encoder RNNs fed through a linear layer
hidden = torch.tanh(self.fc(
torch.cat((hidden[-2,:,:], hidden[-1,:,:]), dim = 1)))
#outputs = [src len, batch size, enc hid dim * 2]
#hidden = [batch size, dec hid dim]
return outputs, hidden
Attention
注意力层将解码器的先前隐藏状态 st−1 以及来自编码器的所有堆叠的前向和后向隐藏状态 H
首先,我们计算之前解码器隐藏状态和编码器隐藏状态之间的energy。由于我们的编码器隐藏状态是一个T序列的张量,而我们之前的解码器隐藏状态是一个单一的张量,我们做的第一件事就是**重复(repeat)**之前的解码器隐藏状态T次。然后我们计算它们之间的能量Et,方法是将它们串联起来,并通过一个线性层(attn)和一个tanh激活函数
这可以被认为是计算每个编码器隐藏状态"匹配"previous的解码器隐藏状态的程度。目前,我们有一个[dec hid dim, src len]张量,用于批处理中的每个例子。我们希望该批中的每个例子都是[src len],因为注意力应该覆盖在源句的长度上。这是通过将 energy乘以[1, dec hid dim]张量v来实现的。
我们可以认为v是所有编码器隐藏状态的能量加权和的权重。这些权重告诉我们,我们应该对源序列中的每个符号关注多少。v的参数是随机初始化的,但通过反向传播与模型的其他部分一起学习。请注意v是如何不依赖于时间的,并且在解码的每个时间步中使用相同的v。我们将v实现为一个没有偏置的线性层。
最后,我们确保注意力矢量符合所有元素都在0和1之间的约束条件,并通过softmax层使矢量之和为1。
从图形上看,这看起来像下面的东西。这是用来计算第一个注意力向量的,其中 s t − 1 = s 0 = z s_{t-1}=s_0=z st−1=s0=z。绿色/茶色块代表前向和后向RNN的隐藏状态,而注意力的计算都是在粉色块中完成的。
class Attention(nn.Module):
def __init__(self, enc_hid_dim, dec_hid_dim):
super().__init__()
self.attn = nn.Linear((enc_hid_dim * 2) + dec_hid_dim, dec_hid_dim)
self.v = nn.Linear(dec_hid_dim, 1, bias = False)
def forward(self, hidden, encoder_outputs):
#hidden = [batch size, dec hid dim]
#encoder_outputs = [src len, batch size, enc hid dim * 2]
batch_size = encoder_outputs.shape[1]
src_len = encoder_outputs.shape[0]
#repeat decoder hidden state src_len times
hidden = hidden.unsqueeze(1).repeat(1, src_len, 1)
encoder_outputs = encoder_outputs.permute(1, 0, 2)
#hidden = [batch size, src len, dec hid dim]
#encoder_outputs = [batch size, src len, enc hid dim * 2]
energy = torch.tanh(self.attn(torch.cat((hidden, encoder_outputs), dim = 2)))
#energy = [batch size, src len, dec hid dim]
attention = self.v(energy).squeeze(2)
#attention= [batch size, src len]
return F.softmax(attention, dim=1)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)