LSTM注意

Bor*_*lov 2 neural-network deep-learning attention-model pytorch tensor

我正在尝试将注意机制添加到堆叠的LSTM实现中https://github.com/salesforce/awd-lstm-lm

所有在线示例都使用编码器 - 解码器架构,我不想使用(我必须注意机制吗?).

基本上,我使用https://webcache.googleusercontent.com/search?q=cache:81Q7u36DRPIJ:https://github.com/zhedongzheng/finch/blob/master/nlp-models/pytorch/rnn_attn_text_clf.py+&cd = 2&HL = EN&CT = clnk&GL = UK

def __init__(self, rnn_type, ntoken, ninp, nhid, nlayers, dropout=0.5, dropouth=0.5, dropouti=0.5, dropoute=0.1, wdrop=0, tie_weights=False):
    super(RNNModel, self).__init__()
    self.encoder = nn.Embedding(ntoken, ninp)
    self.rnns = [torch.nn.LSTM(ninp if l == 0 else nhid, nhid if l != nlayers - 1 else (ninp if tie_weights else nhid), 1, dropout=0) for l in range(nlayers)]
    for rnn in self.rnns:
        rnn.linear = WeightDrop(rnn.linear, ['weight'], dropout=wdrop)
    self.rnns = torch.nn.ModuleList(self.rnns)
    self.attn_fc = torch.nn.Linear(ninp, 1)
    self.decoder = nn.Linear(nhid, ntoken)

    self.init_weights()

def attention(self, rnn_out, state):
    state = torch.transpose(state, 1,2)
    weights = torch.bmm(rnn_out, state)# torch.bmm(rnn_out, state)
    weights = torch.nn.functional.softmax(weights)#.squeeze(2)).unsqueeze(2)
    rnn_out_t = torch.transpose(rnn_out, 1, 2)
    bmmed = torch.bmm(rnn_out_t, weights)
    bmmed = bmmed.squeeze(2)
    return bmmed

def forward(self, input, hidden, return_h=False, decoder=False, encoder_outputs=None):
    emb = embedded_dropout(self.encoder, input, dropout=self.dropoute if self.training else 0)
    emb = self.lockdrop(emb, self.dropouti)

    new_hidden = []
    raw_outputs = []
    outputs = []
    for l, rnn in enumerate(self.rnns):
        temp = []
        for item in emb:
            item = item.unsqueeze(0)
            raw_output, new_h = rnn(item, hidden[l])

            raw_output = self.attention(raw_output, new_h[0])

            temp.append(raw_output)
        raw_output = torch.stack(temp)
        raw_output = raw_output.squeeze(1)

        new_hidden.append(new_h)
        raw_outputs.append(raw_output)
        if l != self.nlayers - 1:
            raw_output = self.lockdrop(raw_output, self.dropouth)
            outputs.append(raw_output)
    hidden = new_hidden

    output = self.lockdrop(raw_output, self.dropout)
    outputs.append(output)

    outputs = torch.stack(outputs).squeeze(0)
    outputs = torch.transpose(outputs, 2,1)
    output = output.transpose(2,1)
    output = output.contiguous()
    decoded = self.decoder(output.view(output.size(0)*output.size(1), output.size(2)))
    result = decoded.view(output.size(0), output.size(1), decoded.size(1))
    if return_h:
        return result, hidden, raw_outputs, outputs
    return result, hidden
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

这个模型是训练,但与没有注意力模型的模型相比,我的损失相当高.

Was*_*mad 11

我理解你的问题,但是跟随你的代码并找出损失没有减少的原因有点困难.此外,还不清楚为什么要将RNN的最后隐藏状态与每个时间步的所有隐藏状态进行比较.

请注意,如果以正确的方式使用它,则特定的技巧/机制非常有用.你试图使用注意机制的方式,我不确定它是否是正确的方法.所以,不要指望因为你在你的模型中使用注意力技巧,你将获得良好的结果!您应该想一想,为什么注意机制会为您所期望的任务带来好处?


你没有明确提到你的目标是什么?既然您已经指向包含语言建模代码的repo,我猜测任务是:给定一系列标记,预测下一个标记.

我可以在你的代码中看到的一个可能的问题是:在for item in emb:循环中,你将始终使用embedddings作为每个LSTM层的输入,因此堆叠LSTM对我来说没有意义.


现在,让我先回答您的问题,然后逐步展示如何构建所需的NN架构.

我是否需要使用编码器 - 解码器架构来使用注意机制?

编码器 - 解码器架构更好地称为学习的序列到序列,并且它广泛用于许多生成任务,例如机器翻译.你的问题的答案是否定的,你不需要使用任何特定的神经网络架构来使用注意机制.


您在图中显示的结构有点模棱两可,但应该易于实现.由于您的实施对我来说并不清楚,因此我试图引导您更好地实施它.对于以下讨论,我假设我们正在处理文本输入.

比方说,我们有形状的输入16 x 10那里16batch_size10seq_len.我们可以假设我们在一个小批量中有16个句子,每个句子长度为10.

batch_size, vocab_size = 16, 100
mat = np.random.randint(vocab_size, size=(batch_size, 10))
input_var = Variable(torch.from_numpy(mat))
Run Code Online (Sandbox Code Playgroud)

在这里,100可以视为词汇量大小.重要的要注意,在我提供的整个示例中,我假设batch_size是所有相应张量/变量中的第一个维度.

现在,让我们嵌入输入变量.

embedding = nn.Embedding(100, 50)
embed = embedding(input_var)
Run Code Online (Sandbox Code Playgroud)

嵌入后,我们得到了一个形状变量,16 x 10 x 50其中50嵌入大小.

现在,让我们定义一个2层单向LSTM,每层有100个隐藏单元.

rnns = nn.ModuleList()
nlayers, input_size, hidden_size = 2, 50, 100
for i in range(nlayers):
    input_size = input_size if i == 0 else hidden_size
    rnns.append(nn.LSTM(input_size, hidden_size, 1, batch_first=True))
Run Code Online (Sandbox Code Playgroud)

然后,我们可以将输入提供给这个2层LSTM以获得输出.

sent_variable = embed
outputs, hid = [], []
for i in range(nlayers):
    if i != 0:
        sent_variable = F.dropout(sent_variable, p=0.3, training=True)
    output, hidden = rnns[i](sent_variable)
    outputs.append(output)
    hid.append(hidden[0].squeeze(0))
    sent_variable = output

rnn_out = torch.cat(outputs, 2)
hid = torch.cat(hid, 1)
Run Code Online (Sandbox Code Playgroud)

现在,您可以简单地使用它hid来预测下一个单词.我建议你那样做.这里的形状hidbatch_size x (num_layers*hidden_size).

但是,由于您希望使用注意力计算最后隐藏状态与LSTM图层生成的每个隐藏状态之间的软对齐分数,让我们这样做.

sent_variable = embed
hid, con = [], []
for i in range(nlayers):
    if i != 0:
        sent_variable = F.dropout(sent_variable, p=0.3, training=True)
    output, hidden = rnns[i](sent_variable)
    sent_variable = output

    hidden = hidden[0].squeeze(0) # batch_size x hidden_size
    hid.append(hidden)
    weights = torch.bmm(output[:, 0:-1, :], hidden.unsqueeze(2)).squeeze(2)  
    soft_weights = F.softmax(weights, 1)  # batch_size x seq_len
    context = torch.bmm(output[:, 0:-1, :].transpose(1, 2), soft_weights.unsqueeze(2)).squeeze(2)
    con.append(context)

hid, con = torch.cat(hid, 1), torch.cat(con, 1)
combined = torch.cat((hid, con), 1)
Run Code Online (Sandbox Code Playgroud)

在这里,我们计算最后状态与每个时间步的所有状态之间的软对齐分数.然后我们计算一个上下文向量,它只是所有隐藏状态的线性组合.我们将它们组合成一个表示形式.

请注意,我已经删除了最后隐藏的状态output:output[:, 0:-1, :]因为您正在与上一个隐藏状态本身进行比较.

最终combined表示存储在每一层产生的最后隐藏状态和上下文向量.您可以直接使用此表示来预测下一个单词.

预测下一个单词是直截了当的,因为你使用简单的线性层就好了.


编辑:我们可以执行以下操作来预测下一个单词.

decoder = nn.Linear(nlayers * hidden_size * 2, vocab_size)
dec_out = decoder(combined)
Run Code Online (Sandbox Code Playgroud)

这里的形状dec_outbatch_size x vocab_size.现在,我们可以计算负对数似然丢失,这将用于以后反向传播.

在计算负对数似然丢失之前,我们需要应用于log_softmax解码器的输出.

dec_out = F.log_softmax(dec_out, 1)
target = np.random.randint(vocab_size, size=(batch_size))
target = Variable(torch.from_numpy(target))
Run Code Online (Sandbox Code Playgroud)

而且,我们还定义了计算损失所需的目标.有关详细信息,请参阅NLLLoss.所以,现在我们可以按如下方式计算损失.

criterion = nn.NLLLoss()
loss = criterion(dec_out, target)
print(loss)
Run Code Online (Sandbox Code Playgroud)

印刷的损失价值是:

Variable containing:
 4.6278
[torch.FloatTensor of size 1]
Run Code Online (Sandbox Code Playgroud)

希望整个解释能帮到你!!


pat*_*_ai 5

值得注意的是,不同语言中的词序是不同的,因此在解码目标语言中的第 5 个单词时,您可能需要注意源语言中的第 3 个单词(或第 3 个单词的编码),因为这些是彼此对应的词。这就是为什么你经常看到注意力机制与编码器解码器结构一起使用。

如果我理解正确的话,你正在做下一个单词预测?在这种情况下,使用注意力可能仍然有意义,因为下一个单词可能高度依赖于过去 4 个步骤的单词。

所以基本上你需要的是:

rnn:接收input形状MBxninphidden形状MBxnhid,输出h形状MBxnhid

h, next_hidden = rnn(input, hidden)
Run Code Online (Sandbox Code Playgroud)

注意:它接受 的序列h,最后一个h_last通过赋予每个 的权重来决定它们的重要性w

w = attention(hs, h_last)
Run Code Online (Sandbox Code Playgroud)

其中w是形状seq_len x MB x 1hs 是形状seq_len x MB x nhid,并且h_last是形状MB x nhid

现在你权重hsw

h_att = torch.sum(w*hs, dim=0) #shape MB x n_hid
Run Code Online (Sandbox Code Playgroud)

现在的重点是您需要为每个时间步骤执行此操作:

h_att_list = []
h_list = []
hidden = hidden_init
for word in embedded_words:
    h, hidden = rnn(word, hidden)
    h_list.append(h)
    h_att = attention(torch.stack(h_list), h)
    h_att_list.append(h_att)
Run Code Online (Sandbox Code Playgroud)

然后您可以在 上应用解码器(可能需要是 MLP 而不仅仅是线性变换)h_att_list