Включение механизма копирования в модели от последовательности к последовательности
В этом сообщении объясняются детали, лежащие в основе модели CopyNet от Gu et al. (1). Если вы просто хотите увидеть код, вы можете ознакомиться с моей реализацией в AllenNLP. У меня также есть рабочий эксперимент с игрушкой здесь, на github.com/epwalsh/nlp-models.
В контексте НЛП модели последовательность-последовательность на уровне токенов (seq2seq) обучаются отображать последовательность токенов в исходном словаре на последовательность токенов в целевом словаре. Эти словари могут быть такими же (или, по крайней мере, иметь значительное перекрытие), что и при резюмировании текста, или полностью отдельными, как при машинном переводе.
Тем не менее, многие такие задачи требуют, чтобы модель могла создавать токены в целевой последовательности, которые появляются в исходной последовательности, но находятся вне словаря (OOV) по отношению к целевому словарю. Иногда эти токены являются частью исходного словаря, а иногда нет (т.е. OOV по отношению к исходному словарю). Эта концепция называется копированием.
Один из способов решить эту проблему - использовать архитектуру уровня символа (или части слова), которая генерирует целевую последовательность посимвольно. Однако эти модели обычно труднее обучать, и для них требуется гораздо больше данных. Грубо говоря, модель уровня персонажа должна изучить все, что делает уровень жетона, а также научиться писать по буквам.
Поэтому вместо этого мы собираемся узнать, как включить механизм копирования непосредственно в модель уровня токена, следуя архитектуре CopyNet от Гу и др. (1). На высоком уровне механизм копирования предоставляет модели переключатель для выбора между двумя режимами на каждом этапе декодирования: режим генерации и режим копирования. В режиме генерации декодер создает токен из целевого словаря, в то время как в режиме копирования декодер выбирает токен из исходной последовательности (не обязательно из исходного словаря, поскольку токен может быть OOV).
Математика
В частности, CopyNet работает, вычисляя вероятность по каждому токену в целевом словаре и каждому токену в исходной последовательности на каждом этапе декодирования. Таким образом, CopyNet действует так, как будто данная исходная последовательность расширяет целевой словарь, позволяя модели потенциально генерировать токены, которые являются OOV по отношению к целевому словарю.
Распределение по расширенному словарю
Чтобы формализовать это, пусть V = {v₁,…, vₙ} обозначает целевой словарь, а X обозначает набор уникальных слов в исходной последовательности x = ( x₁,…, xₘ). Также предположим, что у нас есть специальный токен UNK для слов OOV. Сам токен UNK не является частью V.
Тогда расширенный словарь, соответствующий каждой исходной последовательности, имеет вид V ∪ X ∪ {UNK}, т.е. модель сможет создать любой токен, который находится либо в целевом словаре, либо в исходной последовательности, а также токен UNK. В частности, вероятность токена yₜ из расширенного словаря на этапе декодирования t ᵗʰ дается выражением
где «» представляет состояние декодера, которое включает в себя закодированный источник, скрытое состояние RNN и предыдущий прогноз. Вероятность генерации равна
а вероятность копирования равна
куда
Функция оценки генерации производит оценку для каждого токена в целевом словаре, а также для UNK-токена, а функция оценки копирования производит оценку для каждого токена в исходной последовательности. Более подробно они описаны ниже.
Gu et al. (1) обеспечивают хорошую визуализацию, чтобы помочь вам понять, когда оценки копирования и генерации влияют на вероятность:
Таким образом, если yₜ находится в целевом словаре, но не в исходной последовательности, то только оценка поколения влияет на вероятность yₜ. Если yₜ находится в целевом словаре, а также в исходной последовательности, тогда будет учитываться оценка поколения и все соответствующие оценки копии. Если yₜ присутствует только в исходной последовательности, тогда будут учитываться только оценки копии. И если yₜ не входит ни в исходную последовательность, ни в целевой словарь, то вклад будет иметь только оценка генерации UNK-токена.
Создание и копирование партитур
Функция оценки генерации Ψg является функцией состояния декодера на текущем временном шаге. В простейшей форме это может быть просто линейная функция, которая проецирует состояние декодера на целевой словарь (включая токен UNK). Именно так это и делается в реализации AllenNLP:
Точно так же функция оценки копирования Ψc является функцией состояния декодера и полного закодированного источника, который выводит оценку для каждого токена в исходном предложении (кроме специальных токенов START, END или PAD). . В AllenNLP это реализовано следующим образом:
Состояние декодера
Вычисление скрытого состояния декодера на каждом временном шаге немного сложнее, чем с типичной моделью seq2seq, основанной на внимании, поскольку она включает в себя два механизма внимания по кодированному источнику вместо одного. Эти механизмы внимания называются внимательным чтением и выборочным чтением.
Внимательное чтение - это обычный механизм внимания, который производит взвешенную сумму по закодированным состояниям источника в соответствии с тем, насколько каждое закодированное состояние источника похоже на последнее состояние декодера.
Выборочное чтение также представляет собой взвешенную сумму по кодированным состояниям источника, но веса - это просто соответствующие перенормированные вероятности копирования с предыдущего временного шага. Другими словами, мы берем вероятности копирования, присвоенные каждому исходному токену из последнего шага декодера, как рассчитано выше, и нормализуем их, чтобы их сумма была равна 1, а затем используем их в качестве весовых коэффициентов внимания, чтобы получить другую взвешенную сумму закодированных состояний.
Затем декодированное состояние обновляется в зависимости от внимательного чтения, выборочного чтения и целевого токена (или прогнозируемого целевого токена) с предыдущего временного шага. Вот реализация в AllenNLP:
Попробуй это
Я создал игрушечный пример, который можно сразу использовать. Предположим, мы хотим обучить модель seq2seq принимать вводное «вводное» предложение, такое как
Hi, my name is <NAME>
и сгенерируйте ответ, в котором говорится
Nice to meet you, <NAME>!
где <NAME>
- любое мыслимое имя, состоящее из одного или нескольких токенов. Таким образом, очевидно, что токены в <NAME>
, вероятно, будут OOV по отношению к целевому словарю, и поэтому модель должна будет полагаться на копирование, чтобы создать правильную целевую последовательность.
Используя такие простые шаблоны, я создал фактический набор данных для этой задачи: https://github.com/epwalsh/nlp-models/blob/master/data/greetings.tar.gz. Набор данных состоит из набора для обучения и проверки, каждый из которых представляет собой файл с разделителями табуляции и двумя столбцами. Первый столбец - это исходное предложение, а второй столбец - это целевое предложение. Например, первые несколько строк train.tsv
выглядят так:
Hi, I’m Celeste Busard.<tab>Nice to meet you, Celeste Busard! Its Juliana Stever<tab>Nice to meet you, Juliana Stever! Hey, it’s Erna Lundquist<tab>Nice to meet you, Erna Lundquist! Hey call me Etsuko.<tab>Nice to meet you, Etsuko! Pura is my name.<tab>Nice to meet you, Pura!
Набор проверки, конечно, похож, но содержит совершенно разные названия.
Чтобы запустить этот пример, начните с клонирования репозитория:
git clone https://github.com/epwalsh/nlp-models.git cd nlp-models
Теперь распакуйте набор данных и обучите модель:
make data/greetings.tar.gz make train
При запросе файла модели введите experiments/greetings/copynet.json
. Эта конфигурация модели предполагает, что у вас есть один графический процессор. Если у вас нет графического процессора, измените строку "cuda_device": 0
на "cuda_device": -1
в copynet.json
.
На моем графическом процессоре GTX 1070 это займет всего пару минут, чтобы достичь 100% точности на наборе для проверки.
Примечания по реализации
Я обнаружил, что самой сложной частью реализации CopyNet было правильное вычисление объединенной (логарифмической) вероятности для каждого токена в расширенном словаре (целевой словарь + исходные токены) численно стабильным способом.
Чтобы отслеживать, какие токены в целевой последовательности эффективно соответствуют токенам в исходной последовательности, мне нужно было создать поле данных, которое будет содержать эту информацию и передаваться на прямой проход модели вместе с исходными и целевыми токенами. Я решил назвать это поле source_to_target
.
Поле source_to_target
представляет собой массив беззнаковых целых чисел формы batch_size x source_length
, который дает индекс целевого словаря каждого токена в исходном предложении (который является индексом токена UNK, если исходный токен является OOV по отношению к целевому словарю).
Это поле используется в _get_ll_contrib(self, ...)
методе CopyNetSeq2Seq
, который объединяет оценки генерации и копирования для получения распределения по расширенному словарю:
Когда я впервые реализовал _get_ll_contrib
, я совершил ошибку, не выполнив все вычисления в пространстве журнала. Это приводило к случайным NaN
во время тренировки. К счастью, я нашел способ избежать этого и с тех пор не видел никаких проблем.
Надеюсь, вы нашли этот пост и мою реализацию CopyNet полезными. Я много работал, чтобы модель была готова к исследованиям и производству, и хотел бы узнать, как вы ее используете!