Embedding что это word
Обзор методов создания эмбедингов предложений, Часть1
Представте себе, как было бы удобно, написать предложение и найти похожее к нему по смыслу. Для этого нужно уметь векторизовать всё предложение, что может быть очень не тривиальной задачей.
По специфике своей работы, я должен искать похожие запросы в службу поддержки и даже имея достаточно большую разметку, бывает тяжело собрать необходимое количество сообщений подходящих по тематике, но написанных другими словами.
Ниже обзорное исследование на способы векторизации всего предложения и не просто векторизации, а попытка векторизовать предложение с учётом его смысла.
Например две фразы ‘эпл лучше самсунг’ от ‘самсунг лучше эпл’, должны быть на противоположном конце по одному из значений вектора, но при этом совпадать по другим.
Можно привести аналогию с картинкой ниже. По шкале от кекса до собаки они находятся на разных концах, а по количеству чёрных точек и цвету объекта на одном.
Методы в статьях очень не тривиальные и интересны для изучения, но минусы в том, что:
Поэтому ниже обзорно — сравнительный анализ 7 разных методов векторизации предложений на одном датасете.
Содержание
Подготовка
Функция оценки
Подготовка
База знаний
Выбираем из базы знаний три темы с примерно равным количество сообщений в них, которые будут использоваться для оценки методов и удалим их из базы знаний.
Для оценки нужна только одна функция от каждого метода get_similarity_values, которая будет принимать на вход сообщения и возвращать расстояния от каждого к каждому. Сообщения сортируются по увеличению расстояния и рассчитывается сколько баллов набрал этот метод.
Теоретически максимально возможное значение = sum(214… 214 — 72) — sum(214-72… 0) + 7626 = 10395
1. Методы BOW
1.1 BOW
Будем определять расстояни между предложеними по словам, которые находятся в предложении без предварительной обработки (сохраняя все знаки препинания).
1.2 BOW с леммами слов
Тот же алгоритм, но теперь будут использоваться леммы слов.
‘BOW с леммами слов: 1645.8’
1.3 BOW с леммами с очисткой стоп слов и знаков препинания
‘BOW с леммами и без стоп слов: 1917.6’
1.4 LDA
LDA: 344.7
LDA с леммами: 1092.1
LDA с леммами и без стоп слов: 1077.2
2. Методы, использующие эмбединги токенов
Будем использовать предобученные эмбединги и добавим возможность выбирать метод векторизации и фунцию расстояния.
Здесь можно скачать fasttext.
А вот тут ссылка для скачивания и инструкция использования gensim модели word2vec.
2.1 Среднее по эмбедингу слов
среднее по embedings с euclidean_distances с word2vec + fast_text: 1833.6
среднее по embedings с euclidean_distances с fast_text: 913.5
среднее по embedings с euclidean_distances с word2vec: 1941.6
среднее по embedings с cosine_distance с word2vec + fast_text: 2278.1
cреднее по embedings с cosine_distance с fast_text: 829.2
среднее по embedings с cosine_distance с word2vec: 2437.7
2.2 Среднее по эмбедингу с предварительной очисткой стоп слов
среднее по embedings без стопслов с euclidean_distances с word2vec + fast_text: 2116.9
среднее по embedings без стопслов с euclidean_distances с fast_text: 1314.5
среднее по embedings без стопслов с euclidean_distances с word2vec: 2159.1
среднее по embedings без стопслов с cosine_distance с word2vec + fast_text: 2779.7
среднее по embedings без стопслов с cosine_distance с fast_text: 2199.0
среднее по embedings без стопслов с cosine_distance с word2vec: 2814.4
2.3 Среднее по эмбедингу с весами tf-idf
Методы без учителя
Следующие несколько моделей потребуют потоковой генерации данных, поэтому сделаем универсальные генераторы.
Если кажтся, что потоковая генерация слишком дорогая, то оцените время, которое занимает генерация 100 батчей с размером батча 32, получается:
448 ms ± 65.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
5.77 s ± 115 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
3. Languade Models
Нейронная сеть угадывает следующее слово в преложении. Предсказание по всей длине текста, является эмбедингом предложения.
Угадывать будем на основании предыдущих слов: максимум 20 и минимум 5.
3.1 Language Model on embedings
0 1644.6
3 148.7
6 274.8
9 72.3
12 186.8
15 183.7
18 415.8
21 138.9
3.2 Language Model on token index
0 1700.6
3 404.7
6 255.3
9 379.8
12 195.2
15 160.1
18 530.7
21 701.9
24 536.9
Анализ текстов нейронными сетями. Bag Of Words vs. Embedding.
Анализ текстовой информации — весьма полезный инструмент. Я ранее публиковал уже статьи на тему анализа текстов. Например, анализ авторства текста, генеративные сети с условием (в этих GAN-ах использовался слой embedding), чатботы и пр. Пора систематизировать способы представления текстов в числовой форме и попробовать понять что «под капотом», т.е. как это все работает.
One-hot encoding (OHE)
Самый простой подход :
Например, есть текст «The cat sat on the mat». Чтобы представить каждое слово для обучения нейронной сети, создадим вектор из нулей с длиной равной размеру словаря. Единица будет на позиции соответствующей слову.
Чтобы создать вектор, который кодировал бы всю фразу достаточно объединить (concatenate) one-hot вектор каждого из слов. Очевидно, что при этом полностью теряется информация о последовательности слов в тексте. Остается только информация о наборе слов («bag of words»).
Подход крайне неэффективен. Слово закодированное с помощью one-hot кодирования — это гигантский (длина равна количеству слов в тексте) разреженный (sparse — большая часть 0 и в одной позиции 1-ца) вектор. Под него уходит очень много памяти.
Кодирование словаря уникальными индексами
Другой распространенный подход — кодировать каждое слово некоторым уникальным числом. В примере выше можно было-бы слову «cat» сопоставить с 1, 2-ку присвоить для «mat» и т.д.
В этом случае можно было-бы закодировать фразу «The cat sat on the mat» плотным (dense) вектором, скажем, [5, 1, 4, 3, 5, 2]. Этот подход эффективен с т.з. использования памяти.
Однако, этот подход имеет несколько минусов:
Чтобы нейронная сеть корректно воспринимала выходные значения представленные в числовом виде без предварительного кодирования в OHE используется специальная функция loss: sparse_categorical_crossentropy. Она работает только для выходных значений и вычисляет cross-entropy между реальными и предсказанными сетью значениями.
Bag Of Words
Кодирование входных данных в OHE приводит к серьезной проблемой с нехваткой памяти при обработке данных. Например, рассмотрим для примера задачу эмоционального анализа текстов (sentiment snalysis). Например, есть отзывы клиентов на продукт. Они могут быть положительные, отрицательные и нейтральные.
Типовой механизм преобразования текстов для обработки нейронной сетью будет следующим:
Размерность полученной OHE матрицы:
OHE_shape = 500 * 20000 x 10000 = 10 000 000 x 10000.
Визуализация такого способа подачи текстового представления на нейронную сеть:
Исходная матрица подается на Dense слой и веса этого слоя в процессе обучения принимают значения, которые некоторым образом описывают фразу в слове.
Сравнительно небольшой текст преобразовался в гигантскую матрицу, где каждая фраза представлена вектором из 0 и одной 1. Длина вектора 10 млн. и количество таких векторов 10 000. Под такую матрицу расходовалось бы гигантское количество памяти. На практике для подачи текста на нейронную сеть используют подход Bag of Words [BOW].
Визуализация Bag of Words [BOW]
При Bag Of Words последовательность слов в фразе убирается, поэтому фраза (на картинке Document1, Document2) представляется в виде вектора. Его длина равной размеру общего словаря всех анализируюмых текстов. В полученном векторе на позициях, соответствующих словам присутствующим в тексте стоят 1, а если слово в тексте не встретилось — 0.
В результате размерность матрицы, которая подается на нейронную сеть равна размер словаря х количество фраз (документов) или для нашего примера с отзывами 20000 х 10000.
Embedding
Ранее при обработке текстов, например, при рассмотрении рекуррентных сетей использовалась следующая преобраотка входной последовательности:
Рассмотрим как это выглядит в коде. Поскольку Dense слой — это матрица весов, сгенерируем такую рандомную матрицу:
На входе dense слоя (матрицы весов) подаются входные данные представленные в виде OHE. Например, для кодирования 10 слов используется следующая матрица OHE — по-сути, единичная квадратная матрица. У неё по диагонали стоят единицы:
Для обратного преобразования из матрицы OHE в порядковый номер слова в словаре (индекс) используется код:
Соответственно, чтобы получить OHE соответствующий произвольному индексу, нужно взять из матрицы строку или столбец по нему:
Демонстрация устройства матриц embedding_а.
где @ — матричное умножение, аналог numpy.dot.
В параметрах dense слоя задается только размерность hidden size (в примере = 5). Вторую размерность (в данном случае = 10) dense слой берет из предыдущего слоя, выступающего для него в качестве входа.
Поскольку перемножение OHE на матрицу весов описывающих dense слой эквивалентно взятию из матрицы весов строки соответствующей индексу, то операция получения вектора (embedding) описыващего слово с выбранным индексом упрощается:
По-сути, получаем словарь где слову с определенным индексом поставлен в соотвествие вектор с размерностью заданной для embedding-а.
Представление слов в виде embedding-ов — способ эффективно с точки зрения использования памяти представить слово в виде понятном для нейронной сети. При этом одинаковые слова имеют одинаковый вектор их представляющий.
Размер вектора задается исследователем и чаще всего варьируется от 8-ми мерного для небольших датасетов (текстов) до 1024 в случае больших текстовых баз. Embedding-и с высокой размерностью позволяют вычленять тонкие нюансы во взаимосвязях между словами, но требуют очень больших баз для обучения.
Физический смысл embedding
Помимо значительно большей эффективности использования оперативной памяти embedding-и обладают ещё одним полезным свойством.
Поскольку каждое слово из словаря раскладывается в виде многомерного вектора размерностью embedding_size, то можно предположить, что величина по каждой из осей что-то означает.
Важный момент, в embedding слову может соотвествовать только один вектор. Т.е. не смысловое значение для слова может быть только одно.
Например, был взят embedding_size = 2 и после тренировки embedding получен словарь двумерных векторов соответствующих слову. Их разместили на графике, отложив по оси X — одну координату вектора, а по Y — другую. После анализа слов получили, например, что ось X определяет определяет степень новизны слова, а по Y — насколько слово эмоционально положительное.
Для вектора работают математические операции сложения и вычитания. Кроме того можно вычислить угол между векторами для определения насколько они сонаправлены или ортогональны.
Но, что важно, вектора в многомерном пространстве можно сравнивать, используя косинусную меру.
Косинусную меру можно использовать и как метрику и как loss. Как метрика она используется в исходном виде, как задан по формуле, поскольку метрика должна максимизироваться, т.е. стремится к 1.
В случае с loss его минимизируют, поэтому косинусную меру нужно вычесть из 1. В этом случае при максимальном сходстве векторов значение будет стремится к 0, когда вектора ортогональны, то к 1, а когда противоположно направлены = 2.
Если косинусная мера показывает меру смыслового сходства, то длина вектора дает степень окраски. Например, красивый, прекрасный, прекраснейший и пр. эпитеты ранжируются по степени.
Поскольку embedding может быть предобучен на больших корпусах (объемах) текстов с использованием специальных моделей, а затем выгружен в виде словаря, где слову ставится в соответствие вектор, то появляется возможность получать качественные предобученные embedding-и без необходимости их повторного обучения (веса слоя можно заморозить trainable = False).
Тренировка embedding в зависимости от его размера занимает немало времени, поэтому такая оптимизация крайне полезна.
Word2Vec
Теперь проверим насколько теория о том, что полученные нейронкой вектора embedding-а действительно отражают что-то. Для этого воспользуемся моделью word2vec. При тренировке модели использовалось два подхода. Первый использовал метод continuous bag of words (CBOW), который по контексту пытается предсказать слово. Второй — использовать слово для предсказания контекста — skip-gram.
Word2Vec — это не предобученные эмбеддинги (файл с векторами), а подход к их формированию. Этот подход реализован, например, в библиотеке Gensim. Используя этот подход и большие корпуса текстов разного типа (например, новости) были созданы готовые файлы embedding-ов. Если посмотреть такой файл, то вначале идет слово, а затем набор чисел описывающих его векторное представление. Размерность embedding чаще всего от 100 до 1024.
Выше была дана формула косинусного расстояния. В Python она выглядит следующим образом:
Подгрузим embedding-и. Файл порядка 1,6 Гб, там только английские слова. При желании легко найти предобученные embedding-и для русского языка. В наименовании embedding указано 300 — это размерность векторов в embedding.
Широко известный культовый пример для модели Word2Vec.
Посмотрим косинусное расстояние между
Действительно получаем высокое значение косинусного расстояния. Найдем меру сходства между walking и walked:
или то-же самое вычислим с помощью встроенной функции Word2Vec:
Найдем слово, которое выбивается из ряда слов:
Или найдем слова, наиболее близкие по смыслу с точки зрения нейронной сети:
С русскими словами результат сходный:
Встраивание embedding в нейронную сеть
При работе с готовыми embedding-ами нужно их как-то подать в нейронную сеть. Используется два основных подхода. В первом исходные фразы конвертируются в массив векторов и уже этот массив подается на вход нейронной сети вместо комбинации OHE + Dense. В этом случае полученные вектора не будут тренироваться при тренировки нейронной сети. Они уже подготовлены и считается, что обучение произведено качественно, поскольку для этого использовались гигантские корпуса текстов и тренировка проводилась долго.
Сконвертируем слова в векторное представление:
Обращаю внимание на проверку if word in wv.vocab. Например, в примере частица to является стоп словом (stop-word) с минимальной для модели смысловой нагрузкой. Поэтому эта частица отсуствует в скаченном эмбеддинге.
Если запустить код без этой проверки, то он выпадет с ошибкой, что слово не найдено. Поэтому при трансформации я это слово пропускаю и поэтому в shape-е только 6, а не 7 векторов размерностью 300.
Второй подход — это встроить embedding в качестве слоя в архитектуру нейронной сети Keras. Для этого используется метод:
Чтобы преобразовать слово в индекс в скаченном embedding используется следующий метод:
Встраивание предоубченного embedding в нейронную сетью (Keras)
Есть два способа подать анализируемый текст на нейронку, используя предобученный embedding. В первом подходе входные данные адаптируются под слой embedding-а, а во втором — слой embedding-а подстраивается под входные индексы.
Первый способ состоит в конвертации входного текста с помощью словаря embedding-а и подаче подготовленного текста на слой с весами embedding в модели:
Второй способ состоит в том, чтобы для индексов полученных токенизатором, например, Keras-а, поменять веса в матрице embedding-а, чтобы индекс определенного слова в embedding-е соответствовал индексу слова после токенизатора:
Первый способ подготовки данных я рассмотрю в этой статье, а второй — в следующей.
Слой полученный get_keras_embedding() принимает на вход индексы из скаченного embedding-а. Для преобразования текст в последовательность индексов на которых тренировался скаченный embedding:
В некоторых случаях нужно подгрузить текст убрав лишние символы и пр. Если делать это Keras токенизатором, то для передачи индексов на embedding слой word2vec нужно слегка извратится, поскольку я не нашел нормального метода у Tokenizator для получения списка слов.
Или без использования Tokenizer, взяв функцию text_to_word_sequence:
Если объем текста большой, а памяти GPU мало, то нужно использовать вариант с генератором. В этом случае данные будут подгружаться в нейронку batch-ами:
Чудесный мир Word Embeddings: какие они бывают и зачем нужны?
Начать стоит от печки, то есть с постановки задачи. Откуда берется сама задача word embedding?
Лирическое отступление: К сожалению, русскоязычное сообщество еще не выработало единого термина для этого понятия, поэтому мы будем использовать англоязычный.
Сам по себе embedding — это сопоставление произвольной сущности (например, узла в графе или кусочка картинки) некоторому вектору.
Сегодня мы говорим про слова и стоит обсудить, как делать такое сопоставление вектора слову.
Вернемся к предмету: вот у нас есть слова и есть компьютер, который должен с этими словами как-то работать. Вопрос — как компьютер будет работать со словами? Ведь компьютер не умеет читать, и вообще устроен сильно иначе, чем человек. Самая первая идея, приходящая в голову — просто закодировать слова цифрами по порядку следования в словаре. Идея очень продуктивна в своей простоте — натуральный ряд бесконечен и можно перенумеровать все слова, не опасаясь проблем. (На секунду забудем про ограничения типов, тем более, в 64-битное слово можно запихнуть числа от 0 до 2^64 — 1, что существенно больше количества всех слов всех известных языков.)
Но у этой идеи есть и существенный недостаток: слова в словаре следуют в алфавитном порядке, и при добавлении слова нужно перенумеровывать заново большую часть слов. Но даже это не является настолько важным, а важно то, буквенное написание слова никак не связано с его смыслом (эту гипотезу еще в конце XIX века высказал известный лингвист Фердинанд де Соссюр). В самом деле слова “петух”, “курица” и “цыпленок” имеют очень мало общего между собой и стоят в словаре далеко друг от друга, хотя очевидно обозначают самца, самку и детеныша одного вида птицы. То есть мы можем выделить два вида близости слов: лексический и семантический. Как мы видим на примере с курицей, эти близости не обязательно совпадают. Можно для наглядности привести обратный пример лексически близких, но семантически далеких слов — «зола» и «золото». (Если вы никогда не задумывались, то имя Золушка происходит именно от первого.)
Чтобы получить возможность представить семантическую близость, было предложено использовать embedding, то есть сопоставить слову некий вектор, отображающий его значение в “пространстве смыслов”.
Какой самый простой способ получить вектор из слова? Кажется, что естественно будет взять вектор длины нашего словаря и поставить только одну единицу в позиции, соответствующей номеру слова в словаре. Этот подход называется one-hot encoding (OHE). OHE все еще не обладает свойствами семантической близости:
Значит нам нужно найти другой способ преобразования слов в вектора, но OHE нам еще пригодится.
Отойдем немного назад — значение одного слова нам может быть и не так важно, т.к. речь (и устная, и письменная) состоит из наборов слов, которые мы называем текстами. Так что если мы захотим как-то представить тексты, то мы возьмем OHE-вектор каждого слова в тексте и сложим вместе. Т.е. на выходе получим просто подсчет количества различных слов в тексте в одном векторе. Такой подход называется “мешок слов” (bag of words, BoW), потому что мы теряем всю информацию о взаимном расположении слов внутри текста.
Но несмотря на потерю этой информации так тексты уже можно сравнивать. Например, с помощью косинусной меры.
Мы можем пойти дальше и представить наш корпус (набор текстов) в виде матрицы “слово-документ” (term-document). Стоит отметить, что в области информационного поиска (information retrieval) эта матрица носит название «обратного индекса» (inverted index), в том смысле, что обычный/прямой индекс выглядит как «документ-слово» и очень неудобен для быстрого поиска. Но это опять же выходит за рамки нашей статьи.
Эта матрица приводит нас к тематическим моделям, где матрицу “слово-документ” пытаются представить в виде произведения двух матриц “слово-тема” и “тема-документ”. В самом простом случае мы возьмем матрицу и с помощью SVD-разложения получим представление слов через темы и документов через темы:
Здесь — слова,
— документы. Но это уже будет предметом другой статьи, а сейчас мы вернемся к нашей главной теме — векторному представлению слов.
Пусть у нас есть такой корпус:
С помощью SVD-преобразования, выделим только первые две компоненты, и нарисуем:
Что интересного на этой картинке? То, что Титан и Ио — далеко друг от друга, хотя они оба являются спутниками Сатурна, но в нашем корпусе про это ничего нет. Слова «атмосфера» и «Сатурн» очень близко друг другу, хотя не являются синонимами. В то же время «два» и «много» стоят рядом, что логично. Но общий смысл этого примера в том, что результаты, которые вы получите очень сильно зависят от корпуса, с которым вы работаете. Весь код для получения картинки выше можно посмотреть здесь.
Логика повествования выводит на следующую модификацию матрицы term-document — формулу TF-IDF. Эта аббревиатура означает «term frequency — inverse document frequency».
Давайте попробуем разобраться, что это такое. Итак, TF — это частота слова в тексте
, здесь нет ничего сложного. А вот IDF — существенно более интересная вещь: это логарифм обратной частоты распространенности слова
в корпусе
. Распространенностью называется отношение числа текстов, в которых встретилось искомое слово, к общему числу текстов в корпусе. С помощью TF-IDF тексты также можно сравнивать, и делать это можно с меньшей опаской, чем при использовании обычных частот.
Новая эпоха
Описанные выше подходы были (и остаются) хороши для времен (или областей), где количество текстов мало и словарь ограничен, хотя, как мы видели, там тоже есть свои сложности. Но с приходом в нашу жизнь интернета все стало одновременно и сложнее и проще: в доступе появилось великое множество текстов, и эти тексты с изменяющимся и расширяющимся словарем. С этим надо было что-то делать, а ранее известные модели не могли справиться с таким объемом текстов. Количество слов в английском языке очень грубо составляет миллион — матрица совместных встречаемостей только пар слов будет 10^6 x 10^6. Такая матрица даже сейчас не очень лезет в память компьютеров, а, скажем, 10 лет назад про такое можно было не мечтать. Конечно, были придуманы множество способов, упрощающих или распараллеливающих обработку таких матриц, но все это были паллиативные методы.
И тогда, как это часто бывает, был предложен выход по принципу “тот, кто нам мешает, тот нам поможет!” А именно, в 2013 году тогда мало кому известный чешский аспирант Томаш Миколов предложил свой подход к word embedding, который он назвал word2vec. Его подход основан на другой важной гипотезе, которую в науке принято называть гипотезой локальности — “слова, которые встречаются в одинаковых окружениях, имеют близкие значения”. Близость в данном случае понимается очень широко, как то, что рядом могут стоять только сочетающиеся слова. Например, для нас привычно словосочетание «заводной будильник». А сказать “заводной апельсин” мы не можем* — эти слова не сочетаются.
Основываясь на этой гипотезе Томаш Миколов предложил новый подход, который не страдал от больших объемов информации, а наоборот выигрывал [1].
Модель, предложенная Миколовым очень проста (и потому так хороша) — мы будем предсказывать вероятность слова по его окружению (контексту). То есть мы будем учить такие вектора слов, чтобы вероятность, присваиваемая моделью слову была близка к вероятности встретить это слово в этом окружении в реальном тексте.
Здесь — вектор целевого слова,
— это некоторый вектор контекста, вычисленный (например, путем усреднения) из векторов окружающих нужное слово других слов. А
— это функция, которая двум векторам сопоставляет одно число. Например, это может быть упоминавшееся выше косинусное расстояние.
Приведенная формула называется softmax, то есть “мягкий максимум”, мягкий — в смысле дифференцируемый. Это нужно для того, чтобы наша модель могла обучиться с помощью backpropagation, то есть процесса обратного распространения ошибки.
Процесс тренировки устроен следующим образом: мы берем последовательно (2k+1) слов, слово в центре является тем словом, которое должно быть предсказано. А окружающие слова являются контекстом длины по k с каждой стороны. Каждому слову в нашей модели сопоставлен уникальный вектор, который мы меняем в процессе обучения нашей модели.
В целом, этот подход называется CBOW — continuous bag of words, continuous потому, что мы скармливаем нашей модели последовательно наборы слов из текста, a BoW потому что порядок слов в контексте не важен.
Также Миколовым сразу был предложен другой подход — прямо противоположный CBOW, который он назвал skip-gram, то есть “словосочетание с пропуском”. Мы пытаемся из данного нам слова угадать его контекст (точнее вектор контекста). В остальном модель не претерпевает изменений.
Что стоит отметить: хотя в модель не заложено явно никакой семантики, а только статистические свойства корпусов текстов, оказывается, что натренированная модель word2vec может улавливать некоторые семантические свойства слов. Классический пример из работы автора:
Слово «мужчина» относится к слову «женщина» так же, как слово «дядя» к слову «тётя», что для нас совершенно естественно и понятно, но в других моделям добиться такого же соотношения векторов можно только с помощью специальных ухищрений. Здесь же — это происходит естественно из самого корпуса текстов. Кстати, помимо семантических связей, улавливаются и синтаксические, справа показано соотношение единственного и множественного числа.
Более сложные вещи
На самом деле, за прошедшее время были предложены улучшения ставшей уже также классической модели Word2Vec. Два самых распространенных будут описаны ниже. Но этот раздел может быть пропущен без ущерба для понимания статьи в целом, если покажется слишком сложным.
Negative Sampling
В стандартной модели CBoW, рассмотренной выше, мы предсказываем вероятности слов и оптимизируем их. Функцией для оптимизации (минимизации в нашем случае) служит дивергенция Кульбака-Лейблера:
Здесь — распределение вероятностей слов, которое мы берем из корпуса,
— распределение, которое порождает наша модель. Дивергенция — это буквально «расхождение», насколько одно распределение не похоже на другое. Т.к. наши распределения на словах, т.е. являются дискретными, мы можем заменить в этой формуле интеграл на сумму:
Оказалось так, что оптимизировать эту формулу достаточно сложно. Прежде всего из-за того, что рассчитывается с помощью softmax по всему словарю. (Как мы помним, в английском сейчас порядка миллиона слов.) Здесь стоит отметить, что многие слова вместе не встречаются, как мы уже отмечали выше, поэтому большая часть вычислений в softmax является избыточной. Был предложен элегантный обходной путь, который получил название Negative Sampling. Суть этого подхода заключается в том, что мы максимизируем вероятность встречи для нужного слова в типичном контексте (том, который часто встречается в нашем корпусе) и одновременно минимизируем вероятность встречи в нетипичном контексте (том, который редко или вообще не встречается). Формулой мысль выше записывается так:
Здесь — точно такой же, что и в оригинальной формуле, а вот остальное несколько отличается. Прежде всего стоит обратить внимание на то, что формуле теперь состоит из двух частей: позитивной (
) и негативной (
). Позитивная часть отвечает за типичные контексты, и
здесь — это распределение совместной встречаемости слова
и остальных слов корпуса. Негативная часть — это, пожалуй, самое интересное — это набор слов, которые с нашим целевым словом встречаются редко. Этот набор порождается из распределения
, которое на практике берется как равномерное по всем словам словаря корпуса. Было показано, что такая функция приводит при своей оптимизации к результату, аналогичному стандартному softmax [2].
Hierarchical SoftMax
Также люди зашли и с другой стороны — можно не менять исходную формулу, а попробовать посчитать сам softmax более эффективно. Например, используя бинарное дерево [3]. По всем словам в словаре строится дерево Хаффмана. В полученном дереве слов располагаются на листьях дерева.
На рисунке изображен пример такого бинарного дерева. Жирным выделен путь от корня до слова . Длину пути обозначим
, а
-ую вершину на пути к слову
обозначим через
. Можно доказать, что внутренних вершин (не листьев)
.
С помощью иерархического softmax вектора предсказывается для
внутренних вершин. А вероятность того, что слово
будет выходным словом (в зависимости от того, что мы предсказываем: слово из контекста или заданное слово по контексту) вычисляется по формуле:
где — функция softmax;
;
— левый сын вершины
;
, если используется метод skip-gram,
, то есть, усредненный вектор контекста, если используется CBOW.
Формулу можно интуитивно понять, представив, что на каждом шаге мы можем пойти налево или направо с вероятностями:
Затем на каждом шаге вероятности перемножаются ( шагов) и получается искомая формула.
При использовании простого softmax для подсчета вероятности слова, приходилось вычислять нормирующую сумму по всем словам из словаря, требовалось операций. Теперь же вероятность слова можно вычислить при помощи последовательных вычислений, которые требуют
.
Другие модели
Помимо word2vec были, само собой, предложены и другие модели word embedding. Стоит отметить модель, предложенную лабораторией компьютерной лингвистики Стенфордского университета, под названием Global Vectors (GloVe), сочетающую в себе черты SVD разложения и word2vec [4].
Также надо упомянуть о том, что т.к. изначально все описанные модели были предложены для английского языка, то там не так остро стоит проблема словоизменения, характерная для синтетических языков (это — лингвистический термин), вроде русского. Везде выше по тексту неявно предполагалось, что мы либо считаем разные формы одного слова разными словами — и тогда надеяться, что нашего корпуса будет достаточно модели, чтобы выучить их синтаксическую близость, либо используем механизмы стеммирования или лемматизации. Стеммирование — это обрезание окончания слова, оставление только основы (например, “красного яблока” превратится в “красн яблок”). А лемматизация — замена слова его начальной формой (например, “мы бежим” превратится в “я бежать”). Но мы можем и не терять эту информацию, а использовать ее — закодировав OHE в новый вектор, и сконкатинировать его с вектором для основы или леммы.
Еще стоит сказать, что то, с чем мы начинали — буквенное представление слова — тоже не кануло в Лету: предложены модели по использованию буквенного представления слова для word embedding [5].
Практическое применение
Мы поговорили о теории, пришло время посмотреть, к чему все вышеописанное применимо на практике. Ведь любая самая красивая теория без практического применения — не более чем игра ума. Рассмотрим применение Word2Vec в двух задачах:
1) Задача классификации, необходимо по последовательности посещенных сайтов определять пользователя;
2) Задача регрессии, необходимо по тексту статьи определить ее рейтинг на Хабрахабре.
Классификация
Cкачать данные для первой задачи можно со страницы соревнования «Catch Me If You Can»
Т.к. сейчас мы каждому слову сопоставили вектор, то нужно решить, что сопоставить целому предложению из слов.
Один из возможных вариантов это просто усреднить все слова в предложении и получить некоторый смысл всего предложения (если слова нет в тексте, то берем нулевой вектор).
Т.к. мы получили distributed representation, то никакое число по отдельности ничего не значит, а значит лучше всего покажут себя линейные алгоритмы. Попробуем нейронные сети, LogisticRegression и проверим нелинейный метод XGBoost.
Получили неплохой результат. Значит Word2Vec смог выявить зависимости между сессиями.
Посмотрим, что произойдет с алгоритмом XGBoost.
Видим, что алгоритм сильно подстраивается под обучающую выборку, поэтому возможно наше предположение о необходимости использовать линейные алгоритмы подтверждено.
Посмотрим, что покажет обычный LogisticRegression.
Попробуем улучшить результаты.
Теперь вместо обычного среднего, чтобы учесть частоту с которой слово встречается в тексте, возьмем взвешенное среднее. В качестве весов возьмем IDF. Учёт IDF уменьшает вес широко употребительных слов и увеличивает вес более редких слов, которые могут достаточно точно указать на то, к какому классу относится текст. В нашем случае, кому принадлежит последовательность посещенных сайтов.
Проверим изменилось ли качество LogisticRegression.
видим прирост на 0.07, значит скорее всего взвешенное среднее помогает лучше отобразить смысл всего предложения через word2vec.
Предсказание популярности
Попробуем Word2Vec уже в текстовой задаче — предсказании популярности статьи на Хабрхабре.
Испробуем силы алгоритма непосредственно на текстовых данных статей Хабра. Мы преобразовали данные в csv таблицы. Скачать их вы можете здесь: train, test.
‘Доброго хабрадня!
\r\n
\r\nПерейду сразу к сути. С недавнего времени на меня возложилась задача развития контекстной сети текстовых объявлений. Задача возможно кому-то покажется простой, но есть несколько нюансов. Страна маленькая, 90% интернет-пользователей сконцентрировано в одном городе. С одной стороны легко охватить, с другой стороны некуда развиваться.
\r\n
\r\nТак как развитие интернет-проектов у нас слабое, и недоверие клиентов к местным проектам преобладает, то привлечь рекламодателей тяжело. Но самое страшное это привлечь площадки, которые знают и Бегун и AdSense, но абсолютно не знают нас. В целом проблема такая: площадки не регистрируются, потому что нет рекламодателей с деньгами, а рекламодатели не дают объявления, потому что список площадок слаб.
\r\n
\r\nКак выходят из такого положения Хабраспециалисты?’
Будем обучать модель на всем содержании статьи. Для этого совершим некоторые преобразования над текстом.
Напишем функцию, которая будет преобразовывать тестовую статью в лист из слов необходимый для обучения Word2Vec.
Функция получает строку, в которой содержится весь текстовый документ.
1) Сначала функция будет удалять все символы кроме букв верхнего и нижнего регистра;
2) Затем преобразовывает слова к нижнему регистру;
3) После чего удаляет стоп слова из текста, т.к. они не несут никакой информации о содержании;
4) Лемматизация, процесс приведения словоформы к лемме — её нормальной (словарной) форме.
Функция возвращает лист из слов.
Лемматизация занимает много времени, поэтому ее можно убрать в целях более быстрых подсчетов.
Будем обучаться на 2015 году, а валидироваться по первым 4 месяцам 2016, т.к. в нашей тестовой выборке представлены данные за первые 4 месяца 2017 года. Более правдивую валидацию можно сделать, идя по годам, увеличивая нашу обучающую выборку и смотря качество на первых четырех месяцах следующего года
Посмотрим чему выучилась модель:
[(‘massive’, 0.6958945393562317),
(‘mining’, 0.6796239018440247),
(‘scientist’, 0.6742461919784546),
(‘visualization’, 0.6403135061264038),
(‘centers’, 0.6386666297912598),
(‘big’, 0.6237790584564209),
(‘engineering’, 0.6209672689437866),
(‘structures’, 0.609510600566864),
(‘knowledge’, 0.6094595193862915),
(‘scientists’, 0.6050446629524231)]
Модель обучилась достаточно неплохо, посмотрим на результаты алгоритмов:
Попробуем нейронные сети.
Train on 23425 samples, validate on 7556 samples
Epoch 1/20
1s — loss: 1.7292 — val_loss: 0.7336
Epoch 2/20
0s — loss: 1.2382 — val_loss: 0.6738
Epoch 3/20
0s — loss: 1.1379 — val_loss: 0.6916
Epoch 4/20
0s — loss: 1.0785 — val_loss: 0.6963
Epoch 5/20
0s — loss: 1.0362 — val_loss: 0.6256
Epoch 6/20
0s — loss: 0.9858 — val_loss: 0.6393
Epoch 7/20
0s — loss: 0.9508 — val_loss: 0.6424
Epoch 8/20
0s — loss: 0.9066 — val_loss: 0.6231
Epoch 9/20
0s — loss: 0.8819 — val_loss: 0.6207
Epoch 10/20
0s — loss: 0.8634 — val_loss: 0.5993
Epoch 11/20
1s — loss: 0.8401 — val_loss: 0.6093
Epoch 12/20
1s — loss: 0.8152 — val_loss: 0.6006
Epoch 13/20
0s — loss: 0.8005 — val_loss: 0.5931
Epoch 14/20
0s — loss: 0.7736 — val_loss: 0.6245
Epoch 15/20
0s — loss: 0.7599 — val_loss: 0.5978
Epoch 16/20
1s — loss: 0.7407 — val_loss: 0.6593
Epoch 17/20
1s — loss: 0.7339 — val_loss: 0.5906
Epoch 18/20
1s — loss: 0.7256 — val_loss: 0.5878
Epoch 19/20
1s — loss: 0.7117 — val_loss: 0.6123
Epoch 20/20
0s — loss: 0.7069 — val_loss: 0.5948
Получили более хороший результат по сравнению с гребневой регрессией.
Заключение
Word2Vec показал свою пользу на практических задачах анализа текстов, все-таки не зря на текущий момент на практике используется в основном именно он и — гораздо менее популярный — GloVe. Тем не менее, может быть в вашей конкретной задаче, вам пригодятся подходы, которым для эффективной работы не требуются такие объемы данных, как для word2vec.
Код ноутбуков с примерами можно взять здесь. Код практического применения — вот тут.
Пост написан совместно с demonzheg.
Литература
* Да, это специальная пасхалка для любителей творчества Энтони Бёрджеса.