Как реализовать модель Word2vec с Python и NumPy

Вступление

В последнее время я работал с несколькими проектами, связанными с НЛП. Некоторые из них были как-то связаны с обучением встраиванию слов в компании. В работе задачи в основном решались с помощью библиотеки Python: gensim. Однако я решил реализовать модель Word2vec с нуля только с помощью Python и NumPy, потому что изобретение колеса обычно является отличным способом глубоко изучить что-то.

Вложение слов

Встраивание слов - это не что иное, как методы числового представления слов. В частности, методы сопоставления словарей с векторами.

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

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

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

Word2vec (Скип-грамм)

В Mikolov et al., 2013 были представлены две модели архитектуры: модель непрерывного мешка слов и модель скип-грамм. Я расскажу о последнем в этой статье.

Чтобы объяснить модель пропуска граммов, я наугад цитирую отрывок текста из книги, которую я сейчас читаю, Маленькая книга здравого смысла инвестирования Джона Богла:

После вычета затрат на инвестирование победа на фондовом рынке - игра для проигравших.

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

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

Обзор модели

Ниже представлена ​​оригинальная диаграмма, представленная в статье Миколова и др., 2013.

Я сделал еще один график с чуть более подробной информацией

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

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

Реализация с нуля на Python

Подготовка к тренировочным данным

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

Затем мы присваиваем каждому слову целое число в качестве его идентификатора. Кроме того, использование word_to_id и id_to_word для записи отношений сопоставления.

В конце концов, мы генерируем обучающие данные для модели. Для каждого контекстного слова tokens[i] сгенерируйте: (tokens[i], tokens[i-window_size]), ..., (tokens[i], tokens[i-1]), (tokens[i], tokens[i+1]), ..., (tokens[i], tokens[i+window_size]). Возьмем, например, контекстное слово investing с размером окна 5, мы сгенерируем(investing, deduction), (investing, of), (investing, the), (investing, costs), (investing, of), (investing, beating), (investing, the), (investing, stock), (investing, market), (investing, is). Примечание. В коде обучающие пары (x, y) представлены идентификаторами слов.

Ниже приведен код для генерации обучающих данных:

Обзор учебного процесса

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

Инициализация параметров для обучения

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

Форма вложения слова будет (vocab_size, emb_size). Это почему? Если мы хотим использовать вектор с emb_size элементами для представления словаря, а общее количество словарей в нашем корпусе равно vocab_size,, тогда мы можем представить все словари с помощью матрицы vocab_size x emb_size, где каждая строка представляет слово .

Форма плотного слоя будет (vocab_size, emb_size). Как придешь? Операция, которая будет выполняться на этом уровне, - это умножение матриц. Ввод этого слоя будет (emb_size, # of training instances), и мы хотим, чтобы результат был (vocab_size, # of training instances) (для каждого слова мы хотели бы знать, какова вероятность того, что слово появится с заданным входным словом). Примечание: я не включаю смещения в плотный слой.

Ниже приведен код для инициализации:

Вперед пас

Это три шага в прямом распространении: получение векторного представления входного слова из встраивания слов, передача вектора в плотный слой и затем применение функции softmax к выходу плотного слоя.

В некоторых литературных источниках входные данные представлены в виде горячего вектора (скажем, горячего вектора с i-м элементом, равным 1). Умножая матрицу вложения слов и один-горячий вектор, мы можем получить вектор, представляющий входное слово. Однако результат выполнения умножения матриц по существу такой же, как и при выборе i-й строки матрицы вложения слов. Мы можем сэкономить много вычислительного времени, просто выбрав строку, связанную с входным словом.

Остальная часть процесса - это просто модель линейной регрессии с несколькими классами.

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

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

Ниже приведен код для прямого распространения:

Расчет стоимости (L)

Здесь мы будем использовать кросс-энтропию для расчета стоимости:

Ниже приведен код для расчета стоимости:

Обратный проход (обратное распространение)

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

Мы хотим тренировать веса в плотном слое и слое встраивания слов. Поэтому нам нужно рассчитать градиенты для этих весов:

Следующим шагом является обновление весов по следующей формуле:

Ниже приведен код для обратного распространения:

Примечание. Возможно, вам было интересно, почему коэффициент 1/m в dL_dW, а не в dL_dword_vec. На каждом этапе мы вместе обрабатываем m обучающих примеров. Для весов в плотном слое мы хотели бы обновить их с помощью среднего m спусков градиента. Для весов в векторе слов каждый вектор имеет свои собственные веса, которые приводят к его собственному градиентному спуску, поэтому нам не нужно агрегировать m градиентных спусков при обновлении.

Модельное обучение

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

Ниже приведен код для обучения модели:

Оценка

После обучения модели с данными, сгенерированными из приведенного выше примера предложения, с размером окна 3 для 5000 эпох (с простым снижением скорости обучения), мы видим, что модель может выводить большинство соседних слов, учитывая каждое слово в качестве входного слова.

В этой тетради вы можете найти сквозной процесс. Не стесняйтесь скачивать и экспериментировать.

Это мой первый пост на Medium. Спасибо, ребята, что прочитали. Не стесняйтесь присылать мне отзывы или задавать вопросы.

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

Оптимизация (Space)

Когда я попытался обучить модель выше с большим набором данных, я обнаружил, что потребление памяти продолжало расти во время процесса обучения, и ядро ​​python, наконец, отключилось. Позже я понял, что проблема связана с тем, как я вставил метки Y в модель.

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

Проблема с потреблением памяти исчезнет после того, как я начну кормить этикетку только связанным с ней словом ind. Мы уменьшили пространство с O (размер словаря * m) до O (m).

Ниже приведена реализация моего кода (нужно изменить только 2 места):

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

Спасибо, что прочитали больше. Опять же, не стесняйтесь оставлять отзывы или задавать мне вопросы.

Обновления