机器学习100天Machine Learning & Recommendation & NLP & DL

Pytorch学习记录-Seq2Seq模型实现(Decoder部

2019-04-24  本文已影响6人  我的昵称违规了

一点点总结
回过头看看这三天的模型,从一般LSTM Seq2Seq -> GRU Seq2Seq -> 基于注意力机制的 Seq2Seq

LSTM Seq2Seq.png
GRU Seq2Seq.png
基于注意力机制的 Seq2Seq.png

2. 三个模型的Decoder部分

Decoder部分和Encoder是对应的,层数、隐藏层、单元数都要对应。

2.1 LSTM Seq2Seq Decoder

Decoder只执行一个解码步骤。第一层将从前一个时间步接收隐藏和单元状态,并通过将当前的token 传给LSTM,进一步产生一个新的隐藏和单元状态。
Decoder的初始隐藏和单元状态是我们的上下文向量,它们是来自同一层的Encoder的最终隐藏和单元状态。接下来将隐藏状态传递给Linear层,预测目标序列下一个标记应该是什么。

Encoder输入参数:

Encoder返回参数:

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.2 GRU Seq2Seq Decoder

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

Encoder输入参数:

Encoder返回参数:

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

2.3 Attention Seq2Seq Decoder

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

2.3.1 Attention

放置在这里,反正以后都要用到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)

2.3.2 Decoder

Decoder包括了注意力层,含有上一个隐藏状态s_{t-1},所有Encoder的隐藏状态H,返回注意力向量a_t
接下来使用注意力向量创建加权源向量功能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)                                    
上一篇 下一篇

猜你喜欢

热点阅读