Как добавить декодер и слой внимания к двунаправленному кодировщику с помощью tensorflow 2.0

Вопрос или проблема

Я начинающий в машинном обучении и пытаюсь создать модель для коррекции орфографии, которая проверяет правописание для небольшого количества словарных фраз (примерно 1000 фраз). В настоящее время я обращаюсь к урокам tensorflow 2.0 по 1. NMT с вниманием и 2. Генерации текста. Я завершил изучение до уровня кодирования, но сейчас у меня возникают проблемы с приведением формы следующих слоев (декодер и внимание) в соответствие с предыдущими (кодировщиком). Кодировщик в уроке не является двунаправленным, тогда как я пытаюсь реализовать двунаправленный кодировщик. Ниже приведен мой код для кодировщика и слоя внимания.

class Encoder(tf.keras.Model):
  def __init__(self, vocab_size, embedding_dim, enc_units, batch_sz):
    super(Encoder, self).__init__()
    self.batch_sz = batch_sz
    self.enc_units = enc_units
    self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
    self.bigru = tf.keras.layers.Bidirectional(
        tf.keras.layers.GRU(self.enc_units, 
                            return_sequences=True, 
                            return_state=True, 
                            recurrent_initializer="glorot_uniform",
                            dropout=0.2,
                            recurrent_dropout=0.2))

  def call(self, x, hidden):
    x = self.embedding(x)
    output, forward_state, backward_state = self.bigru(x, initial_state = hidden)
    hidden_state = tf.convert_to_tensor([forward_state, backward_state])
    return output, hidden_state

  def initialize_hidden_state(self):
      init_state = [tf.zeros((self.batch_sz, self.enc_units)) for i in range(2)]
      return init_state

embedding_dim = 10
enc_units = 100
batch_size = 64
encoder = Encoder(vocab_size, embedding_dim, enc_units, batch_size)

# пример ввода
sample_hidden = encoder.initialize_hidden_state()
sample_output, sample_hidden = encoder(example_input_batch, sample_hidden)
print ('Форма выхода кодировщика: (размер партии, длина последовательности, единицы) {}'.format(sample_output.shape))
print ('Форма скрытого состояния кодировщика: (размер партии, единицы) {}'.format(sample_hidden.shape))

WARNING:tensorflow:Слой gru_20 не будет использовать ядро cuDNN, так как оно не соответствует критериям ядра cuDNN. Он будет использовать общее ядро GPU в качестве резервного варианта при выполнении на GPU

WARNING:tensorflow:Слой gru_20 не будет использовать ядро cuDNN, так как оно не соответствует критериям ядра cuDNN. Он будет использовать общее ядро GPU в качестве резервного варианта при выполнении на GPU

WARNING:tensorflow:Слой gru_20 не будет использовать ядро cuDNN, так как оно не соответствует критериям ядра cuDNN. Он будет использовать общее ядро GPU в качестве резервного варианта при выполнении на GPU

Форма выхода кодировщика: (размер партии, длина последовательности, единицы) (64, 27, 200)

Форма скрытого состояния кодировщика: (размер партии, единицы) (2, 64, 100)

Форма скрытого состояния кодировщика (обратного направления): (размер партии, единицы) (64, 100)

class BahdanauAttention(tf.keras.layers.Layer):
  def __init__(self, units):
    super(BahdanauAttention, self).__init__()
    self.W1 = tf.keras.layers.Dense(units)
    self.W2 = tf.keras.layers.Dense(units)
    self.V = tf.keras.layers.Dense(1)

  def call(self, query, values):
    # форма скрытого состояния запроса == (batch_size, hidden size)
    # query_with_time_axis форма == (batch_size, 1, hidden size)
    # значения форма == (batch_size, max_len, hidden size)
    # мы делаем это для того, чтобы выполнить операцию сложения вдоль временной оси для вычисления оценки
    query_with_time_axis = tf.expand_dims(query, 1)

    # оценка форма == (batch_size, max_length, 1)
    # мы получаем 1 на последней оси, потому что мы применяем оценку к self.V
    # форма тензора перед применением self.V — (batch_size, max_length, units)
    score = self.V(tf.nn.tanh(
        self.W1(query_with_time_axis) + self.W2(values)))

    # attention_weights форма == (batch_size, max_length, 1)
    attention_weights = tf.nn.softmax(score, axis=1)

    # форма вектор контекста после суммы == (batch_size, hidden_size)
    context_vector = attention_weights * values
    context_vector = tf.reduce_sum(context_vector, axis=1)

    return context_vector, attention_weights

attention_layer = BahdanauAttention(10)
attention_result, attention_weights = attention_layer(sample_hidden, sample_output)

print("Форма результата внимания: (размер партии, единицы) {}".format(attention_result.shape))
print("Форма весов внимания: (размер партии, длина последовательности, 1) {}".format(attention_weights.shape))

InvalidArgumentError Traceback (most recent call last)
in ()
1 attention_layer = BahdanauAttention(10)
—-> 2 attention_result, attention_weights = attention_layer(sample_hidden, sample_output)
3
4 print(“Форма результата внимания: (размер партии, единицы) {}”.format(attention_result.shape))
5 print(“Форма весов внимания: (размер партии, длина последовательности, 1) {}”.format(attention_weights.shape))

6 фреймов
/usr/local/lib/python3.6/dist-packages/six.py in raise_from(value, from_value)

InvalidArgumentError: Несовпадающие размеры: [2,1,64,10] vs. [64,27,10] [Op:AddV2]

В этом учебнике они создали новый класс BahdanauAttention(), который должен быть встроен в объект Decoder(). Внимание – это то, что использует декодер, а не кодировщик. Модель, как она есть, не полна. Добавьте декодер и измените его входную форму, чтобы он заработал.

Тем не менее, позвольте мне подвести итоги некоторыми мыслями об этой реализации:

Я не думаю, что вам нужна двунаправленность, когда вы используете механизмы внимания. Цель внимания – позволить полезному сигналу, который находится далеко во времени, быстрее добраться до выхода (не проходя через все ячейки RNN и не теряясь на полпути). По этой причине, до появления механизмов внимания, двунаправленные RNN слои были лучшим инструментом для достижения этой повышенной “полной связности” Seq2seq сетей. Но теперь, когда у вас есть внимание, вам это уже не нужно, поскольку у вас есть инструмент, который делает это более гибко и эффективно.

Добавляя двунаправленность, вы заставляете модель распределять внимание по дублирующей тенденции, декодер будет получать две копии одного и того же сигнала (слева направо и справа налево), но с одним вниманием, которое нужно распределить по всем. Я считаю, что это противоречит интуиции и нежелательно, сам концепт внимания искажается. Я предлагаю отказаться от двунаправленности и использовать просто обычное внимание. Было бы интересно сравнить его производительность с моделью с двунаправленностью и без внимания (я уверен, что модели с вниманием выиграют с легкостью).

-.-.-.-.-.-

PS: Начиная с tensorflow 2.1, класс BahdanauAttention() теперь упакован в слой keras под названием AdditiveAttention(), который вы можете вызвать как любой другой слой и вставить его в класс Decoder(). Также есть еще один слой keras, просто называющийся Attention(), который реализует внимание Лионга; было бы интересно сравнить их производительность.

Ответ или решение

Для создания модели коррекции орфографии с использованием слоя расшифровки и внимания в TensorFlow 2.0, необходимо учитывать ряд важных аспектов, особенно когда речь идет о направлении кодировщика и механизме внимания. Давайте рассмотрим, как это можно реализовать шаг за шагом.

Шаг 1: Кодировщик

Вы уже реализовали кодировщик с использованием двунаправленного RNN (в данном случае – GRU). Основная идея здесь заключается в том, чтобы захватить контекст из обоих направлений последовательности:

class Encoder(tf.keras.Model):
    def __init__(self, vocab_size, embedding_dim, enc_units, batch_sz):
        super(Encoder, self).__init__()
        self.batch_sz = batch_sz
        self.enc_units = enc_units
        self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
        self.bigru = tf.keras.layers.Bidirectional(
            tf.keras.layers.GRU(self.enc_units, 
                                return_sequences=True, 
                                return_state=True, 
                                recurrent_initializer="glorot_uniform",
                                dropout=0.2,
                                recurrent_dropout=0.2))

    def call(self, x, hidden):
        x = self.embedding(x)
        output, forward_state, backward_state = self.bigru(x, initial_state=hidden)
        hidden_state = tf.concat([forward_state, backward_state], axis=-1)  # Объединение состояний
        return output, hidden_state

    def initialize_hidden_state(self):
        return [tf.zeros((self.batch_sz, self.enc_units)) for _ in range(2)]

# Пример использования
embedding_dim = 10
enc_units = 100
batch_size = 64
encoder = Encoder(vocab_size, embedding_dim, enc_units, batch_size)

Шаг 2: Слой внимания

Слой внимания будет принимать выходы кодировщика и текущее состояние декодера для генерации контекстного вектора:

class BahdanauAttention(tf.keras.layers.Layer):
    def __init__(self, units):
        super(BahdanauAttention, self).__init__()
        self.Wa = tf.keras.layers.Dense(units)
        self.Ua = tf.keras.layers.Dense(units)
        self.Va = tf.keras.layers.Dense(1)

    def call(self, query, values):
        query_with_time_axis = tf.expand_dims(query, 1)  # Изменяем форму для сложения
        score = self.Va(tf.nn.tanh(self.Wa(query_with_time_axis) + self.Ua(values)))
        attention_weights = tf.nn.softmax(score, axis=1)
        context_vector = attention_weights * values
        context_vector = tf.reduce_sum(context_vector, axis=1)
        return context_vector, attention_weights

Шаг 3: Декодер

Декодер будет использовать слой внимания для генерации выходных последовательностей на основе контекстного вектора, извлеченного из кодировщика:

class Decoder(tf.keras.Model):
    def __init__(self, vocab_size, embedding_dim, dec_units, batch_sz):
        super(Decoder, self).__init__()
        self.batch_sz = batch_sz
        self.dec_units = dec_units
        self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
        self.gru = tf.keras.layers.GRU(self.dec_units, 
                                       return_sequences=True, 
                                       return_state=True, 
                                       recurrent_initializer="glorot_uniform")
        self.fc = tf.keras.layers.Dense(vocab_size)
        self.attention = BahdanauAttention(dec_units)

    def call(self, x, hidden, encoder_output):
        context_vector, attention_weights = self.attention(hidden, encoder_output)
        x = self.embedding(x)
        x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1)  # Объединяем контекст и вход
        output, hidden = self.gru(x)
        output = self.fc(output)
        return output, hidden, attention_weights

Шаг 4: Обучение модели

Теперь, когда вы добавили все необходимые компоненты, можно обучать модель. Следите за тем, чтобы корректно настраивать размеры входных данных и обновлять веса.

Заключение

Создание модели с механизмом внимания и декодером требует внимательности к деталям, особенно для соответствия форматов входных данных. Разумное использование двунаправленного кодировщика в сочетании с вниманием может значительно улучшить качество вашей модели коррекции орфографии, но важно отметить, что гиперпараметры и архитектура должны быть оптимизированы в соответствии с вашими данными.

Не забывайте тестировать различные конфигурации: как с двунаправленным кодировщиком, так и без него, чтобы найти наилучшее решение для вашей задачи. Удачи в реализации вашего проекта!

Оцените материал
Добавить комментарий

Капча загружается...