一点点总结
回过头看看这三天的模型,从一般LSTM Seq2Seq -> GRU Seq2Seq -> 基于注意力机制的 Seq2Seq
GRU Seq2Seq.png
基于注意力机制的 Seq2Seq.png
2. 三个模型的Decoder部分
Decoder部分和Encoder是对应的,层数、隐藏层、单元数都要对应。
2.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.2 GRU Seq2Seq Decoder
GRU的Seq2Seq和LSTM有很大不同,减少了信息压缩,GRU获取目标token 、上一个隐藏状态 ,上下文向量。这里的上下文向量其实就是Encoder的上下文状态,就是隐藏状态。这里就是输入了两个相同的上下文向量。
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
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包括了注意力层,含有上一个隐藏状态,所有Encoder的隐藏状态,返回注意力向量。
接下来使用注意力向量创建加权源向量功能,含有Encoder隐藏状态的加权和,并使用注意力向量作为权重。
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)
网友评论