Рекуррентные нейронные сети в обработке естественных языков

Векторное представление слов и рекуррентные нейронные сети

Здравствуйте и вновь добро пожаловать на занятия по теме «Рекуррентные нейронные сети на языке Python: глубокое обучение, часть 5».

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

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

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

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

К примеру, рассмотрим предложение «Собаки любят кошек и я». Это предложение имеет почти правильную грамматическое строение, но его смысл очень отличается от первоначального предложения «Я люблю кошек и собак». Таким образом, есть большое количество информации, которая теряется при использовании мешка слов.

Чтобы быть предельно понятным, что понимается под мешком слов? Если вы изучали мой первый курс по обработке естественных языков или по логистической регрессии, мы разбирали пример, где использовали логистическую регрессию для анализа тональности текста. Задачей было выяснить, носит ли предложение положительный или отрицательный оттенок. Примером положительного предложения может служить «Сегодня чудесный день», а отрицательного – «Это худший фильм, который я когда-либо видел». Чтобы преобразовать каждое предложение во входящий вектор для классификатора, мы начинали с вектора нулей размера V, служившего размером словаря, так что имели запись для каждого отдельного слова. С помощью словаря отслеживалось, с каким индексом идёт какое слово. Таким образом, для каждого слова в предложении соответствующий индекс в векторе увеличивался на единицу (или какой-то другой показатель частоты использования). Тогда в векторе получалось ненулевое значение для каждого появлявшегося в предложении слова, остальные были равны нулю. Как вы понимаете, с учётом этого обстоятельства было нелегко определить правильный порядок слов в предложении. Это не является совершенно невозможным, если слова в предложении таковы, что допускают только один возможный порядок. Но, вообще говоря, некоторая информация терялась.

Но что, если у нас будут два предложения «Сегодня удачный день» и «Сегодня не удачный день»? Для модели мешка слов это почти один и тот же вектор, и она, как выяснилось, не способна справиться с отрицанием. Можете представить, что рекуррентная сеть была бы идеальной для такой задачи, поскольку отслеживает состояние. Рекуррентная нейронная сеть, увидев частицу «не», инвертировала бы всё, что идёт после неё.

Как же в глубоком обучении обычно рассматриваются слова? В настоящее время популярным методом, продемонстрировавшем впечатляющие результаты, является векторное представление слов, или слова-векторы. Это значит, что при заданном размере словаря V мы выбираем намного меньшую размерность D, а затем указываем каждое слово-вектор в D-размерном пространстве. Обучая модель выполнять определённые действия вроде попытки предсказать следующее слово или окружающие данное слово другие слова, мы получаем векторы, с которыми можно проводить арифметические операции для получения аналогий вроде «король – мужчина = королева – женщина».

Вопрос начинает звучать так: как использовать векторное представление слов в рекуррентной нейронной сети? Для этого в рекуррентной сети мы просто создаём слой векторного представления, так что на входе появляется слово, полученное методом прямого кодирования (one-hot encoded), а в следующем слое он преобразуется в D-размерный вектор. Как видите, это требует матрицы преобразования векторного представления слов размерностью VxD, где i-я строка представляет собой слово-вектор i-го слова.

Тут возникают два вопроса. Первый: как обучать эту модель? Ответ: конечно же, с помощью градиентного спуска. Позже, когда мы будем займёмся методом word2vec, вы познакомитесь с вариантами кросс-энтропийной функции ошибок, которые помогут нам ускорить обучение.

Второй вопрос: каковы целевые переменные? Это очень хороший вопрос, поскольку языковые модели не обязательно нуждаются в целевых переменных. Можно попытаться изучить векторное предложение слов на задаче анализа тональности текста, так чтобы целевыми переменными были рейтинги фильмов или какой-то другой вид оценки тональности. Или можно сделать прогноз слова, как это обсуждалось выше. Когда мы будем использовать word2vec, вы увидите, что целевые переменные могут меняться в зависимости от конкретного используемого метода word2vec.

Словесные аналогии и векторное представление слов

Поговорим о том, как на самом деле проводить вычисления вроде «король – мужчина + женщина = королева». Это довольно просто, но всё равно стоит изучения.

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

Второй этап – найти ближайшее фактическое слово в словаре в уравнении слева. Зачем это нужно? Затем, что результат операции «король – мужчина + женщина» даёт нам лишь вектор. Невозможно отобразить слово из вектора, поскольку векторное пространство непрерывно и потребуется бесконечное количество слов. Так что суть в том, что мы просто находим ближайшее слово.

Есть разные способы нахождения расстояния. Иногда можно встретить обычное расстояние на плоскости:

||a-b||^{2}.

Также распространено косинусное расстояние:

В последнем случае, поскольку во время обучения значение имеет только угол, все слова-векторы нормализуются, чтобы их длина была равна 1. Тогда все векторные представления будут лежать на единичной сфере.

Имея функцию расстояния, как найти нужное слово? Простейший способ – просмотреть все слова в словаре и узнать расстояние между каждым вектором и рассматриваемым, отыскать наименьшее и вернуть соответствующее слово:

min_dist = Infinity; best_word = ‘’;

for word, idx in word2idx.iteritems():

    v1 = We[idx]

    if dist(v0, v1) < min_dist:

        min_dist = dist(v0, v1)

        best_word = word

print ‘’Лучшее слово:’’, best_word

При этом можно пропустить слова из левой части уравнения: «король», «мужчина», «женщина».

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

Представление последовательности слов как последовательности векторных представлений

Есть одна маленькая деталь кода, которая может поставить в тупик. У нас есть матрица векторных представлений слов We размерности VxD, и нам нужно получить последовательность слов-векторов, представляющих предложение, представленное вектором TxD. Но нам нужно обновлять векторные представления слов для метода обратного распространения ошибки, так что матрица размерности TxD, получаемая после подставления слов-векторов, не может служить входными данными для нейронной сети.

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

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

word_vector = []

for index in input_sequence:

    word_vector = We[index, :]

    word_vectors.append(word_vector)

return word_vectors

Говоря математическим языком, слово-вектор получается путём умножения вектора индекса слова, полученного прямым кодированием, на матрицу векторного представления слов. Получается 1xV, умноженный на матрицу размерностью VxD, что даёт вектор 1xD, что и требовалось. Но поскольку все векторы прямого кодирования могут иметь только одну единицу в своём размещении, а всё остальное должно быть нулями, мы можем пойти более коротким путём. Суть в понимании того, что умножение векторных представлений слов на вектор прямого кодирования даёт нам лишь строку, в которой и находится векторное представление. Следовательно, каждая строка матрицы векторных представлений слов представляет соответствующее слово. Это должно быть достаточно очевидно, учитывая, что матрица векторных представлений слов имеет V строк.

Кроме того, Numpy и Theano позволяют очень легко извлечь строку из матрицы, как индекс в списке. Более того, они также позволяют индексировать массивы, используя массивы, как вы могли заметить в многих моих примерах кода.

Так что если у нас есть матрица векторных представлений слов, представляющих слова «я», «люблю», «мороженное», «и», «пирожное» и нужно представить предложение «Я люблю мороженное», то входными данными для нейронной сети будет X = [0, 1, 2]. Мы индексируем We для каждого слова-вектора для предложения «Я люблю мороженное», представленное матрицей размерностью 3xD.

Если же нужно представить предложение «Я люблю пирожное», то входными данными для нейронной сети будет X = [0, 1, 5], это матрица размерностью 3xD. А если нужно представить предложение «Пирожное я люблю», то входом для нейронной сети будет X = [5, 0, 1] и так далее.

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

Понравилась статья? Поделить с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: