Как понять замаскированное внимание нескольких голов в трансформаторе

Я сейчас изучаю код преобразователя, но не могу понять замаскированную многоголовку декодера. В документе говорилось, что это сделано для того, чтобы вы не увидели генерирующее слово, но я не могу понять, если слова после генерирования слова не были сгенерированы, как их можно увидеть?

Пытаюсь прочитать код трансформатора (ссылка: https://github.com/Kyubyong/transformer) . Маска полученного кода показана ниже. Он использует нижнюю треугольную матрицу для маскировки, я не могу понять почему.

padding_num = -2 ** 32 + 1
diag_vals = tf.ones_like(inputs[0, :, :])  # (T_q, T_k)
tril = tf.linalg.LinearOperatorLowerTriangular(diag_vals).to_dense()  # (T_q, T_k)
masks = tf.tile(tf.expand_dims(tril, 0), [tf.shape(inputs)[0], 1, 1])  # (N, T_q, T_k)
paddings = tf.ones_like(masks) * padding_num
outputs = tf.where(tf.equal(masks, 0), paddings, inputs)

person Neptuner    schedule 27.09.2019    source источник


Ответы (1)


У меня возник тот же вопрос после прочтения статьи Transformer paper. Я не нашел полного и подробного ответа на вопрос в Интернете, поэтому постараюсь объяснить свое понимание Masked Multi-Head Attention.

Короткий ответ - нам нужна маскировка, чтобы сделать обучение параллельным. А распараллеливание - это хорошо, так как позволяет модели быстрее обучаться.

Вот пример, объясняющий эту идею. Допустим, мы тренируемся переводить «Я люблю тебя» на немецкий язык. Кодировщик работает в параллельном режиме - он может создавать векторное представление входной последовательности («Я люблю тебя») с постоянным количеством шагов (т.е. количество шагов не зависит от длины входной последовательности).

Допустим, кодировщик выдает числа 11, 12, 13 как векторные представления входной последовательности. На самом деле эти векторы будут намного длиннее, но для простоты мы используем короткие. Также для простоты мы игнорируем служебные токены, такие как - начало последовательности, - конец последовательности и другие.

Во время обучения мы знаем, что перевод должен быть «Ich liebe dich» (мы всегда знаем ожидаемый результат во время обучения). Скажем, ожидаемые векторные представления слов "Ich liebe dich" равны 21, 22, 23.

Если мы сделаем обучение декодера в последовательном режиме, это будет похоже на обучение рекуррентной нейронной сети. Будут выполнены следующие последовательные шаги:

  • Sequential operation #1. Input: 11, 12, 13.
    • Trying to predict 21.
    • Прогнозируемый результат будет не совсем 21, допустим, будет 21.1.
  • Sequential operation #2. Input: 11, 12, 13, and also 21.1 as the previous output.
    • Trying to predict 22.
    • Прогнозируемый результат не будет точно 22, скажем, будет 22.3.
  • Sequential operation #3. Input 11, 12, 13, and also 22.3 as the previous output.
    • Trying to predict 23.
    • Прогнозируемый результат не будет точно 23, скажем, будет 23.5.

Это означает, что нам потребуется выполнить 3 последовательных операции (в общем случае - последовательную операцию на каждый вход). Также на каждой следующей итерации будет накапливаться ошибка. Также мы не привлекаем внимания, поскольку мы смотрим только на один предыдущий результат.

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

  • Parallel operation #A. Inputs: 11, 12, 13.
    • Trying to predict 21.
  • Parallel operation #B. Inputs: 11, 12, 13, and also 21.
    • Trying to predict 22.
  • Parallel operation #C. Inputs: 11, 12, 13, and also 21, 22.
    • Trying to predict 23.

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

И здесь нам понадобится маскировка. Алгоритм обучения знает весь ожидаемый результат (21, 22, 23). Он скрывает (маскирует) часть этой известной выходной последовательности для каждой из параллельных операций.

  • Когда он выполняет #A - он скрывает (маскирует) весь вывод.
  • Когда выполняет #B - скрывает 2-й и 3-й выходы.
  • Когда он выполняет #C - он скрывает 3-й вывод.

Само маскирование реализовано следующим образом (из исходной статьи):

Мы реализуем это внутри масштабированного внимания скалярного произведения, маскируя (устанавливая на -∞) все значения на входе softmax, которые соответствуют недопустимым соединениям.

Примечание: во время вывода (не обучения) декодер работает в последовательном (не параллельном) режиме, так как изначально он не знает выходной последовательности. Но он отличается от подхода RNN, поскольку вывод трансформатора по-прежнему использует самовнимание и смотрит на все предыдущие результаты (но не только на самый предыдущий).

Примечание 2: в некоторых материалах я видел, что маскирование можно использовать по-разному для приложений, не связанных с переводом. Например, для языкового моделирования маскирование можно использовать, чтобы скрыть некоторые слова из входного предложения, и модель будет пытаться предсказать их во время обучения, используя другие, немаскированные слова (т.е. научиться понимать контекст).

person artoby    schedule 13.01.2020
comment
Я рекомендую эту статью с вашим объяснением и статья очень помогает. - person ; 05.01.2021
comment
+1 за действительно полезный пример. С этим я ясно понимаю src_mark. Тем не менее, src_key_padding_mask для меня немного нечеткий. Игнорировать несколько маркеров заполнения в конце одной последовательности? - person root163; 09.03.2021