Эта статья призвана дать интуитивное представление о вложениях слов с использованием информации из некоторых из самых известных статей в этой области.

Зачем нам нужны векторы слов?

Другой вариант, который у нас есть, - это представить каждое слово в документе как вектор с горячим кодированием. Попробуем подумать над некоторыми проблемами с этим.

  1. Размер словарного запаса: каждое слово будет представлено фигурой, равной вашему словарному запасу. Это могут быть миллионы.

2. Семантика. Помимо проблемы с масштабом, мы теряем «сходство между словами, которые должны быть близко друг к другу». Другими словами, в этом представлении все слова ортогональны друг другу. В многомерном пространстве все они указывают в разных направлениях.

В идеале, в приведенном выше примере «город» и «город» должны иметь несколько схожие векторные представления. Но в этом случае их косинусное сходство равно 0 (они расположены под углом 90 градусов друг к другу).

Таким образом, в идеале мы хотели бы представить каждое слово вектором, размер которого намного меньше размера словаря. Кроме того, мы также хотели бы, чтобы эти векторы сами по себе придали "значение" слову. Интересно, что на практике добавление или вычитание двух или более этих слов дает интригующие результаты. Например, вектор (Германия) + вектор (Заголовок) может привести к вектору (Берлин).

Как нам построить эти словесные векторы?

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

Интуиция (известная как распределительная семантика) заключается в том, что слова, близкие к определенному слову в документе, передают значение этого слова. Например, если у нас есть предложение вроде «Учитель довольно часто ругает ученика». Для центрального слова студент и размера окна 5 (по обе стороны от слова) у нас будут слова 'учитель', 'ругает', 'тот', 'справедливо' , "часто" в своем контексте. Теперь, если у нас есть достаточно большой корпус, идея состоит в том, что мы могли бы придумать представления для слова, глядя на все слова в контексте каждого слова.

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

Word2vec:

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

  1. Непрерывный набор слов: центральное слово предсказывается с учетом слов контекста.
  2. Skip-Gram: контекстные слова предсказываются с учетом центрального слова.

Как показано на рисунке, с моделью скип-граммы нам нужно найти P (контекст | центр). Но нашей первоначальной целью было найти слова-векторы, верно? А вот и наша разработка целевой функции.

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

И для модели skip-gram, и для модели CBOW нам требуется два вектора для представления каждого слова. Один для случаев, когда слово находится в контексте, а другой, когда оно находится в центре. Мы называем эти векторы U и V. Чтобы представить его как распределение, мы берем скалярное произведение обоих этих векторов, используем экспоненту, чтобы сделать скалярное произведение положительным, и нормализовали его со всеми векторами в словаре. Наша цель должна состоять в том, чтобы найти векторы U и V, которые максимизируют вероятность того, что правильные слова будут в контексте, заданном центральным словом. Определяя вероятность, Vцентр задается умножением нашей V-матрицы (N x V), где N - размер встраивания, а V - размер Vocalab с вектором ввода с горячим кодированием. Само это умножается на ВСЕ векторы контекста в U-матрице размера (V X N). В конечном итоге мы получаем «баллы» для каждого слова в словаре, которые, в свою очередь, передаются в функцию softmax. В некотором смысле это похоже на классификацию по нескольким классам, когда мы предсказываем, какое «слово» находится в контексте, заданном центральным словом.

Чтобы упростить получение производных, мы прикрепляем журнал перед вероятностями. Кроме того, мы добавляем знак минус, чтобы мы «минимизировали» функцию вместо того, чтобы брать максимальное значение. Мы также добавляем (1 / T), чтобы вещи оставались неизменными в масштабе и не зависели от размера корпуса.

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

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

Первоначально текст является одним горячим кодированием, и одно из слов передается в сеть. В этом случае 'Легкость' передается как центральное слово (отсортировано по словарю, 2-е слово), и из предложения мы можем видеть, что контекстные слова для удобства (с окном размер 2) с символом "&".

Мы случайным образом инициализируем контекст и центральную матрицу слов V & U.

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

#### U matrix as a dict with vocab as the key
{'dangerous': array([ 1.2398147 , -0.87909626,  0.38028566]),  'ease':       array([0.08540021, 0.18544306, 0.07096524]), 
'man':        array([ 0.14981179, -1.40251029,  1.02452393]),  
'passed':     array([0.10055589, 1.09979612, 1.63213385]),  
'the':        array([-0.22917658,  1.32505777, -0.74246023]), 
'through':    array([0.21855831, 1.05448857, 0.71948246]),  
'town':       array([-0.17041045, -1.60960955, -0.56273196]),  
'with':       array([-1.52561797,  0.00701094, -0.46619808])}

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

##Predicted probability distribution
{'dangerous': 0.0006688799877958782,  
'ease':       0.024971817920074583,  
'man':        0.004299169023935694,  
'passed':     0.9438920599330834, 
 'the':       0.004081941089464888,  
'through':    0.0027767995527184113,  
'town':       0.009752776322407003,  
'with':       0.009556556170520179}

С нашей бредовой нормальной инициализацией сеть предсказывает «пройдено» как слово с наивысшей вероятностью в контексте с центром «легкость» с вероятностью 0,94. Но истинное распределение для центрального слова «легкость» имеет вероятность 1 для с и города и 0 для всех остальных.

###True Distribution
{'dangerous': 0,
 'ease': 0,
 'man': 0,
 'passed': 0,
 'the': 0,
 'through': 0,
 'town': 1,
 'with': 1}

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

Есть еще одна проблема с этим методом для word2vec, каждый раз, когда нам приходится проходить через весь словарь, что, как обсуждалось ранее, не дает больших вычислительных возможностей. Если вы думаете о сложности, она находится в порядке O (V + V), где V - размер вашего словарного запаса.

Мы решаем эту проблему с помощью так называемой отрицательной выборки, которая была представлена ​​во второй упомянутой выше статье . Вместо использования функции softmax мы используем сигмовидную функцию и превращаем ее в задачу биноминальной классификации (2 класса, положительный и отрицательный).

Мы отбираем слова, не входящие в контекст, и используем их как отрицательные классы. В нашем текущем примере для центрального слова «легкость» мы выбираем некоторые другие слова, такие как «the», и используем их в качестве отрицательных классов. Мы выбираем K из этих классов. Таким образом, вместо обновления всего словаря мы получаем сложность ~ O (K).

Результаты

Если вы хотите обучить их на собственном корпусе, это довольно просто на фреймворках глубокого обучения. В исходной статье word2vec была обучена модель на новостных данных из Google. Позже, с годами, люди обучили свои модели на наборах данных Википедии и показали лучшие результаты. Вероятно, это связано с тем, что в одном документе Википедии больше слов, относящихся к определенной теме, тогда как в новостях может быть больше разнообразных слов. Тем не менее, в этом разделе я буду использовать исходные векторы слов.

Мы смотрим на обученные векторы слов для таких ярлыков, как король, королева, кошка, собака и животное. Затем мы берем аппроксимацию 2-го ранга для векторов с помощью SVD. (Подробнее о СВД вы можете узнать из моей предыдущей статьи здесь)

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

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

Некоторые из выявленных сходств заключались в следующем:

#Analogy
similarity(w['king']-w['man']+ w['woman'],w['queen']) ---> 0.73 
#Dissimilar words
similarity(w['king'], w['car'])  ---> 0.03
#Similar words
similarity(w['man'], w['woman']) ---> 0.76