Что такое рекуррентные нейронные сети? Как мы используем глубокое обучение для перевода текста с одного языка на другой?

Это четвертый пост во вводной серии Intuitive Deep Learning, где мы применяем специальный тип нейронной сети, называемый RNN, для работы с текстом в приложениях обработки естественного языка! Если вы хотите получить больше из этой серии, следите за публикацией «Интуитивное глубокое обучение».

На чем мы остановились: в части 1 мы познакомились с нейронными сетями и рассказали, как заставить их работать. В Части 1а мы начали с краткого изложения того, что ставит перед собой цель машинное обучение:

Машинное обучение определяет шаблон и находит наилучшие параметры для этого шаблона вместо того, чтобы жестко кодировать параметры самостоятельно. Нейронная сеть - это просто сложный «шаблон», который мы определяем, который, как оказалось, обладает гибкостью для моделирования многих сложных отношений между вводом и выводом. Указание функции потерь и выполнение градиентного спуска помогает найти параметры, которые дают наилучшие прогнозы для нашего обучающего набора.

В Части 2 мы рассмотрели, как применить нейронные сети к изображениям с помощью CNN:

Изображения представляют собой трехмерный массив функций: каждый пиксель в двумерном пространстве содержит три числа от 0 до 255 (включительно), соответствующих красному, зеленому и синему каналам. Часто данные изображения содержат множество входных функций. Слой, общий для CNN в слое Conv, который определяется размером фильтра, stride, depth и padding . Слой Conv использует одни и те же параметры и применяет одни и те же нейроны к разным областям изображения, тем самым уменьшая количество необходимых параметров. Другой общий уровень в CNN - это уровень максимального объединения, определяемый размером фильтра и шагом, который уменьшает пространственный размер, принимая максимальное из чисел в своем фильтре. Мы также обычно используем наши традиционные полностью подключенные уровни в конце наших CNN. AlexNet была CNN, которая произвела революцию в области глубокого обучения, и была построена из сверточных слоев, слоев максимального объединения и уровней FC. Когда много слоев собраны вместе, более ранние слои изучают низкоуровневые функции и объединяют их в более поздних слоях для более сложных представлений.

Однако для того, чтобы наши нейронные сети работали до сих пор, обратите внимание, что мы принимаем входные данные фиксированной длины / размера, а также выдаем выходные данные фиксированного размера. Размер входных и выходных данных определяет количество параметров, которые нам нужны, поэтому нам нужно, чтобы они были зафиксированы заранее, прежде чем мы сможем выполнять какое-либо обучение модели. Когда мы переходим к приложениям для обработки естественного языка и других данных последовательности, мы сталкиваемся с проблемой: предложения имеют разную длину; как применить нейронные сети к предложениям?

Прежде чем мы начнем знакомство с RNN, важно понять, как слова понимаются машиной. В конце концов, наш нейрон воспринимает числа как функции, а не слова. Как тогда наш нейрон (и алгоритмы машинного обучения в целом) понимали бы слова?

Во-первых, давайте разберемся с проблемой. В английском словаре слишком много слов, добавьте слова, не входящие в словарь (например, имена), и все усложняется. Итак, что мы можем сделать, так это взять 30 000 наиболее распространенных слов и сказать, что наш компьютер может понимать только эти 30 000 слов. Мы называем это нашим словарём. Любое слово за пределами этих 30 000 слов будет пониматься как «Неизвестно» или «НЕИЗВЕСТНО». Обратите внимание, что UNK - это также слово, которое мы добавим в наш словарь.

Как только мы нашли наш словарный запас, это один из подходов, с помощью которого мы можем преобразовывать слова в числа. Давайте проясним ситуацию на примере. Предположим, мы создали наш словарный запас из 30 000 слов, как показано ниже:

Каждому слову соответствует индекс, который является позицией этого слова в словаре. Предположим, мы хотим найти представление для слова «привет», которое является 13 941-м словом в нашем словаре.

Что мы можем сделать, так это представить слово в виде набора из 30 000 чисел (x1, x2,… x30000). Мы называем этот набор вектором. Что мы задаем числам для слова «привет»? Мы устанавливаем x13941 = 1, так как слово «привет» в нашем словаре имеет индекс 13 941. Все остальные функции (x1, x2,…, x13940, x13942,…, x30000) будут установлены на 0.

Интуитивно этот метод можно рассматривать как функцию «подсчета». Мы берем входное слово (в нашем примере «привет») и подсчитываем, сколько раз мы видим соответствующие слова в нашем словаре? Итак, мы начинаем с «а», нашего первого слова в словаре, соответствующего признаку x1. Сколько раз мы видим слово «а» во входном предложении «привет»? Нет, поэтому мы устанавливаем значение x1 равным 0. Мы продолжаем, пока не достигнем x13941. Сколько раз мы видим слово «привет» (13 941-е слово в нашем словаре) во входной последовательности «привет»? Мы видим это один раз, поэтому мы устанавливаем x13941 = 1. И продолжаем вниз наш словарный запас.

Этот метод преобразования слова в вектор с «1» для характеристики, соответствующей этому слову, и «0» в противном случае называется горячим кодированием.

Теперь у нас есть метод преобразования слов в числа. Но так ли это лучший способ? Предположим, у нас есть два слова: «счастливый» и «радостный». Эти два слова будут представлены следующим образом:

Обратите внимание: нигде в нашем представлении мы не знаем, что «счастливый» и «радостный» на самом деле являются синонимами и имеют одинаковое значение! Слова представляют смысл, а не просто произвольные последовательности символов, но это не было представлено в нашем методе. Другими словами, горячая кодировка была успешной в преобразовании наших слов в набор чисел, но числа не кодируют значение, стоящее за словом.

Популярный метод решения этой проблемы называется word2vec. Точные сведения о том, как создается word2vec, можно найти в сноске 1, но если вас это не интересует, просто примите это за то, что люди создали эту волшебную функцию, чтобы вы могли преобразовать слово в набор из 300 чисел (ну, размер вектора может варьироваться - но 300 - самое распространенное). Эти 300 чисел кодируют часть значения слова, поэтому такие слова, как «счастливый» и «радостный», будут похожи друг на друга, поскольку имеют схожие значения.

Одно интересное свойство word2vec заключается в том, что вы можете фактически «вычитать» или «добавлять» значения слов из значений других слов. После преобразования всех слов в вектор из 300 чисел можно задать один интересный эксперимент: что, если я вычту или сложу векторы, представляющие эти слова?

Например, что, если я выполню операцию с векторами, которые представляют слова как таковые:

Исследователи обнаружили, что это привело к вектору, соответствующему слову «королева». По сравнению с нашим горячим кодированием, в этих векторах word2vec закодирована информация о том, что означает слово, включая такие атрибуты, как пол.

Альтернативный способ думать о кодировании значения - это извлекать признаки самостоятельно. Например, мы можем постулировать, что значение слова определяется его полом (мужской = 1, женский = -1), его «королевской властью» (королевский = 1, а не королевский = -1) и тем, является ли оно человеком ( person = 1, not person = -1) среди прочего. Мы могли бы думать о сложении / вычитании векторов так:

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

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

Резюме: в word2vec слова преобразуются в вектор из 300 чисел, который пытается закодировать значение слова.

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

Одно из решений этой проблемы - указать максимальное количество слов, которое может содержать предложение. Предположим, что это число - 90. Если в нашем фактическом предложении 10 слов, мы заполняем все остальные 80 позиций специальным маркером под названием ‹pad›. Этот метод называется заполнением, и вы, возможно, видели нечто подобное с CNN. После заполнения наших входных данных у нас всегда будет последовательность слов фиксированного размера, поэтому здесь мы можем использовать наши традиционные алгоритмы (например, CNN).

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

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

  1. Кодировщик: закодируйте предложение «Я кот» в некоторый набор чисел (вектор). Вы можете думать об этом наборе чисел как о значении слова «Я кот».
  2. Декодер: расшифруйте этот набор чисел в соответствующее французское предложение «Je suis un chat».

Резюме: мы можем использовать архитектуру кодировщика-декодера RNN для задачи машинного перевода.

Начнем с кодировщика.

Мы вводим еще один вектор, который мы называем «скрытым вектором». На данный момент воспринимайте это как «вектор значения предложения», хотя эта терминология технически неточна. Но ради интуиции мы пока поработаем с этим. Тогда задача RNN состоит в том, чтобы научиться кодировать новые слова в скрытый вектор (вектор, означающий предложение), а не напрямую отображать входные данные в выходные.

RNN обрабатывает признаки, объединяя два вектора вместе. Помните, что каждое число в скрытом векторе - это просто функция, и каждое число в векторе слов, представляющее новое слово, также является просто функцией. Если у нашего скрытого вектора было 1000 функций, а у нашего нового вектора слов было 300 функций, тогда RNN принимает в качестве входных 1300 функций. А с помощью нашей RNN узнайте, как эти функции могут «сочетаться» вместе.

Используя эту RNN, которая принимает старый скрытый вектор и новый вектор слова для вывода нового скрытого вектора, мы можем читать английское предложение слово за словом, чтобы закодировать полное предложение «Я кошка» в окончательный скрытый вектор. Для ясности мы рассмотрим пошаговый анализ:

скрытый вектор начинается со всех нулей, так как в начале нет смысла. Мы называем это h0. Затем мы смотрим на первое слово «Я». Наша нейронная сеть берет старый скрытый вектор (h0) и объединяет его с вектором слов, который представляет «I» (например, word2vec). Теперь наш скрытый вектор h1 - это набор чисел, который представляет предложение «I».

Затем мы смотрим на второе слово «я». Мы используем ту же самую нейронную сеть, которая берет скрытый вектор h1 и объединяет его со словом «am», чтобы создать скрытый вектор h2, который представляет предложение «Я есть». Это еще одна форма совместного использования параметров: мы используем одну и ту же функцию для кодирования каждого слова, но входные данные на каждом этапе разные. Мы называем здесь каждый шаг временным шагом.

Мы продолжаем. Мы смотрим на третье слово, «а», и используем ту же самую нейронную сеть, чтобы объединить скрытый вектор h2 со словом «а», чтобы создать скрытый вектор h3, который представляет предложение «Я есть».

Наконец, мы смотрим на четвертое слово «кошка» и используем ту же нейронную сеть, чтобы объединить скрытый вектор h3 со словом «кошка», чтобы получить скрытый вектор h4, который представляет предложение «Я кошка». Рисунок для кодирования слов по порядку показан ниже:

Теперь, когда у нас есть вектор, представляющий «Я кот», мы воспользуемся этим вектором, чтобы преобразовать его во французские слова.

Но прежде чем мы это сделаем, давайте еще раз выделим подход RNN. С помощью RNN мы решаем проблему изменения входных данных не путем заполнения, а путем изменения определения проблемы того, что делает наша нейронная сеть. Традиционная нейронная сеть может рассматривать проблему как:

Ввод: предложение вводимых английских слов; Выход: Предложение французских слов.

RNN (на этапе кодирования) вместо этого видит эту проблему как:

Вход: предыдущий скрытый вектор и новый вектор слова; Результат: Новый скрытый вектор, который сочетает в себе значение старого скрытого вектора и этого нового слова.

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

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

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

Теперь перейдем к декодеру.

Теперь, когда у нас есть скрытый вектор, представляющий «Я кот», пришло время преобразовать этот скрытый вектор во французскую последовательность слов с помощью нашего декодера! На этот раз мы используем другую RNN, которая отличается от нашего кодировщика.

На каждом шаге декодера здесь происходят две вещи:

  1. Выведите французское слово из скрытого вектора,
  2. Удалите это слово из скрытого вектора, чтобы новый скрытый вектор представлял слова «для перевода».

Шаг 1: выведите французское слово из скрытого вектора. Для этого у нас есть еще один слой, который берет скрытый вектор на этом шаге и преобразует его в распределение вероятностей по словам в словаре (используя softmax).

Обратите внимание, что уровень нейронной сети для преобразования скрытого вектора в распределение вероятностей по французскому словарю одинаков на всех этапах. В этом случае наша нейронная сеть учится переводить скрытые векторы на французские слова.

Шаг 2: мы можем вывести одно слово, но как вывести несколько слов? Одного вывода могло быть достаточно для предсказания настроения, но не для перевода полных предложений.

Наша RNN здесь, на шаге 2 нашего декодера, пытается решить проблему, очень похожую на RNN в нашем кодировщике: учитывая предыдущий скрытый вектор и предыдущее увиденное слово, вывести следующий скрытый вектор.

Интуиция, однако, совсем другая. Мы пытаемся «вычесть» значение предыдущего слова из скрытого вектора. Полученный в результате скрытый вектор представляет слова, которые еще предстоит перевести. Когда нечего переводить, слово, которое мы получим из скрытого вывода в этот момент, будет просто токеном конца предложения (‹EOS›), сообщающим нам, что наша работа выполнена. и нам не нужно продолжать последовательность декодирования.

На этом этапе вы, возможно, задаетесь вопросом: как нам увидеть предыдущее слово? На выходе из Шага 1! Вот как объединяются Шаг 1 и Шаг 2:

Это может быть самая сложная диаграмма, которую вы увидите в этой серии публикаций, но она описывает общую архитектуру декодирования. Подобно кодировщику, мы делаем различие между традиционной нейронной сетью и нашей RNN. Традиционная нейронная сеть может рассматривать проблему как:

Ввод: предложение вводимых английских слов; Выход: Предложение французских слов.

RNN (на этапе декодирования) вместо этого видит эту проблему как:

Ввод: предыдущий скрытый вектор и предсказанное предыдущее слово; Результат: Новый скрытый вектор, который вычитает слово из «слов, которые еще не переведены».

Это не самая интуитивно понятная концепция, но я думаю, что это гениальный способ работы с входами и выходами переменного размера. И по большей части работает хорошо! Есть несколько проблем с ванильной RNN, о которых нам еще предстоит поговорить, и для этого нам потребуется немного подправить нашу RNN, прежде чем она будет готова. В более сложных сообщениях мы поговорим об этих проблемах и настройках, таких как LSTM и Attention.

Резюме. Декодер делает две вещи, чтобы развернуть скрытый вектор в последовательность французских слов. Во-первых, мы выводим распределение вероятностей французского слова на этом временном шаге. Во-вторых, мы создаем новый скрытый вектор, который представляет слова, оставшиеся для перевода. Это продолжается до тех пор, пока мы не спрогнозируем токен ‹EOS›.

Сводная сводка: в word2vec слова преобразуются в вектор из 300 чисел, который пытается закодировать значение слова. Мы можем использовать архитектуру кодировщика-декодера RNN для задачи машинного перевода. RNN в кодировщике изучает, как комбинировать предыдущие скрытые векторы с новыми встречающимися словами, чтобы сформировать новые скрытые векторы. Скрытый вектор в конце предложения представляет значение только что увиденного предложения. Декодер делает две вещи, чтобы развернуть скрытый вектор в последовательность французских слов. Во-первых, мы выводим распределение вероятностей французского слова на этом временном шаге. Во-вторых, мы создаем новый скрытый вектор, который представляет слова, оставшиеся для перевода. Это продолжается до тех пор, пока мы не спрогнозируем токен ‹EOS›.

Что дальше?. Теперь, когда мы рассмотрели, как нейронные сети применяются к тексту, в части 4 (ссылка) мы перейдем к использованию нейронных сетей для обучения с подкреплением. Обучение с подкреплением лежит в основе многих достижений, о которых вы, возможно, слышали в новостях, таких как AlphaGo и AlphaZero, победившие лучших игроков в го, или достижение DeepMind в том, что научились играть в игры Atari, опираясь только на данные только о пикселях.

Сноски:

  1. word2vec

word2vec на самом деле не отдельная модель, а программный пакет, который содержит группу моделей, преобразующих слова в векторы. Одна из таких моделей называется моделью скип-грамма.

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

Мне нравится пить сок _______.

Что вы можете сказать о пустом слове из его контекста? Скорее всего, это фрукт, поскольку в нем есть слово «сок». Скорее всего, это съедобный фрукт, поскольку мы не хотели бы пить сок из ядовитых фруктов. Скорее всего, это фрукт, сок которого люди тоже любят пить.

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

Мы используем это понимание для создания нашей модели скип-грамм. Настройка следующая:

Посмотрите на пары слов в определенном окне. Допустим, наше окно состоит из двух слов слева и справа. Если мы возьмем предложение «Мне нравится пить яблочный сок», пары слов со словом «яблоко» будут: (яблоко, наслаждаться) (яблоко, пить) и (яблоко, сок).

Затем наша модель пытается предсказать, что могут быть окружающими словами. Таким образом, если мы введем в модель слово «яблоко», выходные данные должны дать относительно высокую вероятность словам «наслаждаться», «пить» и «сок».

Архитектура нашей модели следующая:

  • Входной слой: горячая кодировка слова. Размер горячего вектора - это размер словаря, V
  • Скрытый слой: 300 нейронов
  • Выходной слой: распределение вероятностей по словам в словаре.

Как вы думаете, как должно выглядеть выходное распределение слов «яблоко» и «апельсин»? Они должны выглядеть примерно одинаково, поскольку они могут предсказывать окружающие слова, такие как «сок», а не «зигота». Если у них похожие распределения в выходном слое, то они, вероятно, также будут иметь аналогичный выходной скрытый слой. Это наша основная идея!

К настоящему времени вы можете подумать, что это слишком большое совпадение, что мы указали 300 нейронов скрытого слоя в нашей архитектуре выше. Поскольку слова, имеющие схожее значение, будут иметь похожие скрытые слои (как утверждалось выше), мы можем взять скрытый слой в качестве вектора слова!

Предсказание окружающих слов на самом деле не является основной целью выполнения задачи. Основная цель - запустить модель, а затем извлечь скрытые слои, чтобы они действовали как наши векторы word2vec! Круто, да?