Seq2Seq模型实现(Decoder部分)

  • Post author:
  • Post category:其他




0、引言:

承接上一篇,现在继续对于seq2seq模型进行讲解,decoder部分是和encoder部分对应的,层数、隐藏层、单元数都要对应。



1、LSTM Seq2Seq Decoder

Decoder只执行一个解码步骤。第一层将从前一个时间步接收隐藏和单元状态,并通过将当前的token 传给LSTM,进一步产生一个新的隐藏和单元状态。

Decoder的初始隐藏和单元状态是我们的上下文向量,它们是来自同一层的Encoder的最终隐藏和单元状态。接下来将隐藏状态传递给Linear层,预测目标序列下一个标记应该是什么。

Encoder输入参数:

  • output_dim将要输入到Decoder的one-hot向量,这个和输出词汇大小一致,就是输出字典长度
  • emb_dim嵌入层的维度,这一层将one-hot向量转为密度向量,256

    词嵌入在 pytorch 中只需要调用 torch.nn.Embedding(m, n) 就可以了,m 表示单词的总数目,n 表示词嵌入的维度,是一种降维,相当于是一个大矩阵,矩阵的每一行表示一个单词。
  • hid_dim隐藏和cell的状态维度,512
  • n_layers RNN层数,这里就是2
  • dropout是要使用的丢失量。这是一个防止过度拟合的正则化参数,0.5

Encoder返回参数:

  • prediction,预测
  • hidden,隐藏状态
  • cell,单元状态

    看一下实现
class Decoder(nn.Module):
    def __init__(self, output_dim, emb_dim, hid_dim, n_layers, dropout):
        super(Decoder,self).__init__()
        
        self.emb_dim=emb_dim
        self.hid_dim=hid_dim
        self.output_dim=output_dim
        self.n_layers=n_layers
        self.dropout=dropout
        
        self.embedding=nn.Embedding(output_dim,emb_dim)
        self.rnn=nn.LSTM(emb_dim,hid_dim,n_layers,dropout=dropout)
        self.out=nn.Linear(hid_dim,output_dim)
        self.dropout=nn.Dropout(dropout)
    def forward(self, input,hidden,cell):
        # torch.unsqueeze()这个函数主要是对数据维度进行扩充。给指定位置加上维数为一的维度,比如原本有个三行的数据(3),在0的位置加了一维就变成一行三列(1,3)
        input=input.unsqueeze(0)
        embedded=self.dropout(self.embedding(input))
        output, (hidden,cell)=self.rnn(embedded,(hidden,cell))
        prediction=self.out(output.squeeze(0))
        
        #output = [1, batch size, hid dim]
        #hidden = [n layers, batch size, hid dim]
        #cell = [n layers, batch size, hid dim]
        return prediction,hidden ,cell



2、GRUSeq2Seq Decoder

GRU的Seq2Seq和LSTM有很大不同,减少了信息压缩,GRU获取目标token y(t)、上一个隐藏状态s(t-1),上下文向量z。这里的上下文向量其实就是Encoder的上下文状态,就是隐藏状态s(t-1)。这里就是输入了两个相同的上下文向量。

Encoder输入参数:

  • output_dim将要输入到Decoder的one-hot向量,这个和输出词汇大小一致,就是输出字典长度
  • emb_dim嵌入层的维度,这一层将one-hot向量转为密度向量,256

    词嵌入在 pytorch 中只需要调用 torch.nn.Embedding(m, n) 就可以了,m 表示单词的总数目,n 表示词嵌入的维度,是一种降维,相当于是一个大矩阵,矩阵的每一行表示一个单词。
  • hid_dim隐藏和cell的状态维度,512
  • dropout是要使用的丢失量。这是一个防止过度拟合的正则化参数,0.5

Encoder返回参数:

  • prediction,预测
  • hidden,隐藏状态

    看一下实现
class Decoder(nn.Module):
    def __init__(self, output_dim,emb_dim,hid_dim,dropout):
        super(Decoder,self).__init__()
        
        self.output_dim=output_dim
        self.emb_dim=emb_dim
        self.hid_dim=hid_dim
        self.dropout=dropout
        
        self.embedding=nn.Embedding(output_dim,emb_dim)
        # 在实现的时候,通过将$y_t$和$z$串联传入GRU,所以输入的维度应该是emb_dim+ hid_dim
        self.rnn=nn.GRU(emb_dim+ hid_dim,hid_dim)
        # linear层输入的是 $y_t, s_t$ 和 $z$串联,而隐藏状态和上下文向量都是$h$维度相同,所以输入的维度是emb_dim+hid_dim*2  
        self.out=nn.Linear(emb_dim+hid_dim*2, output_dim)
        self.dropout=nn.Dropout(dropout)
        
    def forward(self, input, hidden, context):
        input =input.unsqueeze(0)
        embedded=self.dropout(self.embedding(input))
        #embedded = [1, batch size, emb dim]
        
        emb_con = torch.cat((embedded, context), dim = 2)
        #emb_con = [1, batch size, emb dim + hid dim]
        output,hidden=self.rnn(emb_con,hidden)
        
        #input = [batch size]
        #hidden = [n layers * n directions, batch size, hid dim]
        #context = [n layers * n directions, batch size, hid dim]
        
        #在Decoder中层数和方向始终是1,因此hidden和context要做压缩处理:
        #hidden = [1, batch size, hid dim]
        #context = [1, batch size, hid dim]
        output=torch.cat((embedded.squeeze(0), hidden.squeeze(0), context.squeeze(0)), dim = 1)
        
        prediction=self.out(output)
        return prediction,hidden



3、Attention Seq2Seq Decoder

这个是目前为止看到最复杂的,因为增加了attention。



3.1、attention

class Attention(nn.Module):
    def __init__(self, enc_hid_dim, dec_hid_dim):
        super(Attention,self).__init__()
        self.enc_hid_dim=enc_hid_dim
        self.dec_hid_dim=dec_hid_dim
        self.attn=nn.Linear((enc_hid_dim*2)+dec_hid_dim,dec_hid_dim)
        self.v=nn.Parameter(torch.rand(dec_hid_dim))
        
    def forward(self, hidden, encoder_outputs):
        batch_size=encoder_outputs.shape[1]
        src_len=encoder_outputs.shape[0]
        hidden=hidden.unsqueeze(1).repeat(1,src_len,1)
        encoder_outputs=encoder_outputs.permute(1,0,2)
        energy = torch.tanh(self.attn(torch.cat((hidden, encoder_outputs), dim = 2))) 
        energy = energy.permute(0, 2, 1)
        v = self.v.repeat(batch_size, 1).unsqueeze(1)
        attention = torch.bmm(v, energy).squeeze(1)
        return F.softmax(attention, dim=1)



3.2、decoder

Decoder包括了注意力层,含有上一个隐藏状态s(t-1),所有Encoder的隐藏状态H,返回注意力向量。

接下来使用注意力向量创建加权源向量功能W(t),含有Encoder隐藏状态的加权和H,并使用注意力向量a(t)作为权重。

class Decoder(nn.Module):
    def __init__(self,output_dim,emb_dim,enc_hid_dim,dec_hid_dim,dropout, attention):
        super(Decoder,self).__init__()
        self.emb_dim = emb_dim
        self.enc_hid_dim = enc_hid_dim
        self.dec_hid_dim = dec_hid_dim
        self.output_dim = output_dim
        self.dropout = dropout
        self.attention = attention
        
        self.embedding=nn.Embedding(output_dim,emb_dim)
        self.rnn=nn.GRU((enc_hid_dim*2)+emb_dim,dec_hid_dim)
        self.out=nn.Linear((enc_hid_dim*2)+dec_hid_dim+emb_dim,output_dim)
        self.dropout=nn.Dropout(dropout)
        
    def forward(self,input,hidden,encoder_outputs):
        input=input.unsqueeze(0)
        embedded=self.dropout(self.embedding(input))
        a=self.attention(hidden,encoder_outputs)
        a=a.unsqueeze(1)
        encoder_outputs=encoder_outputs.permute(1,0,2)
        weighted = torch.bmm(a, encoder_outputs)
        weighted = weighted.permute(1, 0, 2)
        rnn_input = torch.cat((embedded, weighted), dim = 2)
        output, hidden = self.rnn(rnn_input, hidden.unsqueeze(0))
        assert (output == hidden).all()
        
        embedded = embedded.squeeze(0)
        output = output.squeeze(0)
        weighted = weighted.squeeze(0)
        
        output = self.out(torch.cat((output, weighted, embedded), dim = 1))
        
        #output = [bsz, output dim]
        return output, hidden.squeeze(0)                                    



版权声明:本文为m0_46201444原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。