Реализация Luong Attention в PyTorch

Я пытаюсь реализовать внимание, описанное в Luong et al. 2015 в PyTorch, но я не мог заставить его работать. Ниже мой код, меня пока интересует только «общий» случай внимания. Интересно, не упускаю ли я какой-нибудь очевидной ошибки. Он работает, но, похоже, не учится.

class AttnDecoderRNN(nn.Module):
    def __init__(self, hidden_size, output_size, dropout_p=0.1):
        super(AttnDecoderRNN, self).__init__()
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.dropout_p = dropout_p

        self.embedding = nn.Embedding(
            num_embeddings=self.output_size,
            embedding_dim=self.hidden_size
        )
        self.dropout = nn.Dropout(self.dropout_p)
        self.gru = nn.GRU(self.hidden_size, self.hidden_size)
        self.attn = nn.Linear(self.hidden_size, self.hidden_size)
        # hc: [hidden, context]
        self.Whc = nn.Linear(self.hidden_size * 2, self.hidden_size)
        # s: softmax
        self.Ws = nn.Linear(self.hidden_size, self.output_size)

    def forward(self, input, hidden, encoder_outputs):
        embedded = self.embedding(input).view(1, 1, -1)
        embedded = self.dropout(embedded)

        gru_out, hidden = self.gru(embedded, hidden)

        # [0] remove the dimension of directions x layers for now
        attn_prod = torch.mm(self.attn(hidden)[0], encoder_outputs.t())
        attn_weights = F.softmax(attn_prod, dim=1) # eq. 7/8
        context = torch.mm(attn_weights, encoder_outputs)

        # hc: [hidden: context]
        out_hc = F.tanh(self.Whc(torch.cat([hidden[0], context], dim=1)) # eq.5
        output = F.log_softmax(self.Ws(out_hc), dim=1) eq. 6

        return output, hidden, attn_weights

Я изучил внимание, реализованное в

https://pytorch.org/tutorials/intermediate/seq2seq_translation_tutorial.html

а также

https://github.com/spro/practical-pytorch/blob/master/seq2seq-translation/seq2seq-translation.ipynb

  • Первый не тот механизм внимания, который я ищу. Основным недостатком является то, что его внимание зависит от длины последовательности (self.attn = nn.Linear(self.hidden_size * 2, self.max_length)), что может быть дорогостоящим для длинных последовательностей.
  • Второй больше похож на то, что описано в статье, но все же не такой, как нет tanh. Кроме того, после обновления до последней версии pytorch он работает очень медленно (ref) . Также я не знаю, почему он принимает последний контекст (ref).

person zyxue    schedule 28.05.2018    source источник


Ответы (1)


Эта версия работает и полностью соответствует определению Luong Attention (общее). Основное отличие от вопроса - разделение embedding_size и hidden_size, что кажется важным для обучения после экспериментов. Раньше я делал их одинакового размера (256), что создает проблемы для обучения, и кажется, что сеть может выучить только половину последовательности.

class EncoderRNN(nn.Module):
    def __init__(self, input_size, embedding_size, hidden_size,
                 num_layers=1, bidirectional=False, batch_size=1):
        super(EncoderRNN, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        self.bidirectional = bidirectional
        self.batch_size = batch_size

        self.embedding = nn.Embedding(input_size, embedding_size)

        self.gru = nn.GRU(embedding_size, hidden_size, num_layers,
                          bidirectional=bidirectional)

    def forward(self, input, hidden):
        embedded = self.embedding(input).view(1, 1, -1)
        output, hidden = self.gru(embedded, hidden)
        return output, hidden

    def initHidden(self):
        directions = 2 if self.bidirectional else 1
        return torch.zeros(
            self.num_layers * directions,
            self.batch_size,
            self.hidden_size,
            device=DEVICE
        )


class AttnDecoderRNN(nn.Module):
    def __init__(self, embedding_size, hidden_size, output_size, dropout_p=0):
        super(AttnDecoderRNN, self).__init__()
        self.embedding_size = embedding_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.dropout_p = dropout_p

        self.embedding = nn.Embedding(
            num_embeddings=output_size,
            embedding_dim=embedding_size
        )
        self.dropout = nn.Dropout(self.dropout_p)
        self.gru = nn.GRU(embedding_size, hidden_size)
        self.attn = nn.Linear(hidden_size, hidden_size)
        # hc: [hidden, context]
        self.Whc = nn.Linear(hidden_size * 2, hidden_size)
        # s: softmax
        self.Ws = nn.Linear(hidden_size, output_size)

    def forward(self, input, hidden, encoder_outputs):
        embedded = self.embedding(input).view(1, 1, -1)
        embedded = self.dropout(embedded)

        gru_out, hidden = self.gru(embedded, hidden)

        attn_prod = torch.mm(self.attn(hidden)[0], encoder_outputs.t())
        attn_weights = F.softmax(attn_prod, dim=1)
        context = torch.mm(attn_weights, encoder_outputs)

        # hc: [hidden: context]
        hc = torch.cat([hidden[0], context], dim=1)
        out_hc = F.tanh(self.Whc(hc))
        output = F.log_softmax(self.Ws(out_hc), dim=1)

        return output, hidden, attn_weights
person zyxue    schedule 28.05.2018
comment
@dead_poet, размер встраивания вроде зависит от размера словаря. Мой словарь - это маленькие символы, поэтому я использовал размер вложения 7 и скрытый размер 256. - person zyxue; 01.06.2018
comment
Поправьте меня, если я ошибаюсь, я думаю, это неправильно. вы должны умножать предыдущее скрытое состояние декодера на каждом временном шаге на выходные данные кодировщика! здесь вы передаете ему предыдущее скрытое состояние из предыдущей итерации. вы должны были использовать ячейку lstm вместо слоя lstmb, чтобы у вас был доступ к каждому временному шагу - person Rika; 24.10.2019