【问题标题】:LSTM with Attention带注意力的 LSTM
【发布时间】:2018-08-11 16:15:35
【问题描述】:

我正在尝试将注意力机制添加到堆叠 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

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

【问题讨论】:

  • 你能简单解释一下你的需求而不是显示你的代码吗?你的问题有点令人困惑。如果您能说出您在堆叠 RNN 之上添加注意力机制的想法,我将能够为您提供帮助。另外,这种注意力的实现缺少一个维度是什么意思?为什么你需要那个额外的维度?顺便说一句,您不需要使用编码器-解码器架构来使用注意力。如果您了解注意力的含义,您可以在任何地方使用它。
  • @WasiAhmad 我需要修改问题链接中的代码(我将其用于语言建模)以包含注意机制,以便我可以比较训练模型的质量。在原始代码中,forward 返回 5x5x831 张量(batchesXlengthXdictionary)。如果我使用我的问题中的注意力,我会得到 5x831 维度张量,它缺少一维。我想知道是否可以修改注意力函数以恢复第三维,但我认为 patapouf_ai 建议对“emb”张量中的每个单词应用注意力
  • @WasiAhmad 我已经更改了前向函数,将逐字输入注意力,但我的结果比没有注意力的模型差。我已经编辑了我的问题

标签: neural-network deep-learning pytorch tensor attention-model


【解决方案1】:

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

请注意,如果您以正确的方式使用特定的技巧/机制,它会很有用。 您尝试使用注意力机制的方式,我不确定它是否正确。所以,不要指望因为你在模型中使用了注意力技巧,你会得到很好的结果!你应该想,为什么注意力机制会为你想要的任务带来优势?


你没有明确提到你的目标是什么?由于您已指向包含语言建模代码的存储库,我猜任务是:给定一系列标记,预测下一个标记。

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


现在,让我先回答您的问题,然后逐步说明如何构建您想要的 NN 架构。

是否需要使用编码器-解码器架构才能使用注意力机制?

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


您在图中呈现的结构有点模棱两可,但应该很容易实现。由于您的实施对我来说并不明确,因此我试图指导您以更好的方式实施它。对于以下讨论,我假设我们正在处理文本输入。

假设我们有一个形状为16 x 10 的输入,其中16batch_size10seq_len。我们可以假设我们在一个 mini-batch 中有 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))

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

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

embedding = nn.Embedding(100, 50)
embed = embedding(input_var)

嵌入后,我们得到一个形状为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))

然后,我们可以将输入提供给这个 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)

现在,您可以简单地使用hid 来预测下一个单词。我建议你这样做。这里,hid 的形状是batch_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)

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

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

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

预测下一个单词很简单,因为您使用简单的线性层就可以了。


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

decoder = nn.Linear(nlayers * hidden_size * 2, vocab_size)
dec_out = decoder(combined)

这里,dec_out 的形状是batch_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))

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

criterion = nn.NLLLoss()
loss = criterion(dec_out, target)
print(loss)

打印的损失值为:

Variable containing:
 4.6278
[torch.FloatTensor of size 1]

希望整个解释对您有所帮助!

【讨论】:

  • 感谢您的扩展回答。我现在正在尝试您的建议,但遇到了与最初遇到的问题相同的问题:我的嵌入尺寸为 5x80x400,而隐藏 rnn 状态的尺寸为 80x400,其中 5 是作为输入的单词数量(这是下一个单词预测任务),80 是批量大小,400 是嵌入维度。因此, weights = torch.bmm(...) 无法计算,因为它需要相同的批量大小。这就是为什么我尝试了 patapouf_ai 的建议 - 遍历嵌入
  • 要使用 bmm,我需要 rnn_output=(batch, seq_len, cell_size) 和 hidden=(batch, cell_size, 1)。就我而言, rnn_output batch != hidden batch
  • 似乎在 LSTM 定义中添加“batch_first=True”可以解决问题...
  • 我想我需要一些帮助来计算损失。 github.com/salesforce/awd-lstm-lm/blob/master/model.py#L94 通过线性解码器传递 rnn 输出,然后将输出重塑为 (batches, batch_size, words_size)。 hid 和 con 都具有在所有 rnn 通道中累积的维度(批次、embedding_dimension)。老实说,我迷路了。
  • 我能看到的唯一方法是创建大小批次的线性层batch_sizevocabulary_size 然后重塑它,这有意义吗?
【解决方案2】:

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

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

所以基本上你需要的是:

rnn: 接受形状为MBxninpinput 和形状为MBxnhidhidden 并输出形状为MBxnhidh

h, next_hidden = rnn(input, hidden)

注意:它按照h 的顺序和最后一个h_last 通过给每个w 赋予权重来决定它们的重要性。

w = attention(hs, h_last)

其中w 的形状为seq_len x MB x 1hs 的形状为seq_len x MB x nhidh_last 的形状为MB x nhid

现在你将hs 加权为w

h_att = torch.sum(w*hs, dim=0) #shape MB x n_hid

现在的重点是您需要在每个时间步都这样做:

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)

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

【讨论】:

  • 我已经使用你的建议将逐字输入注意力层,但模型的性能相当差。我已经用新结果更新了我的问题
  • 虽然,在我的方法中,我只将一个 rnn 输出传递给注意力层,而不是列表,就像你展示的那样。如果我传递一个列表,我会在注意力中做 bmm 的维度问题
  • 整个注意点在于它需要选择现在需要注意哪些词。然后它输出每个单词的权重,以说明它们对下一步的重要性。如果你只关注一个词,那就完全没有意义了。
  • 我建议你尝试实现这个架构:arxiv.org/abs/1706.03762 你的输出序列是你的输入序列(这样你基本上是在做下一步预测)。上面的答案是对您当前代码的最简单修改,我认为这很有意义,但是这篇注意力论文应该会给出更好的结果。
猜你喜欢
  • 2017-11-10
  • 2020-11-06
  • 2021-08-24
  • 1970-01-01
  • 1970-01-01
  • 2020-03-16
  • 1970-01-01
  • 2020-02-09
  • 2019-02-08
相关资源
最近更新 更多