Когда-то мне было нужно узнать кое-что про то, как устроен word2vec и — удивительно — нигде нормальной информации я не нашёл. В половине статей просто демонстрировали формулы и умные слова (я тоже так могу), в другой половине информация была не очень полной. То, что мне было нужно, я собирал по крупицам. настала пора поделиться этими крупицами, особенно, если учесть, что уже почти полгода я ничего не публиковал.
Поэтому здесь я немного расскажу о том, что такое word2vec, зачем он нужен, как он работает — и прочие мелочи. Статья теоретическая (про сам word2vec, а не про векторное пространство, если что), написана достаточно простым языком, но все же содержит в себе практические идеи. Enjoy!
Что такое word2vec?
word2vec — это инструмент (набор алгоритмов) для расчета векторных представлений слов, реализует две основные архитектуры — Continuous Bag of Words (CBOW) и Skip-gram. На вход подается корпус текста, а на выходе получается набор векторов слов.
Принцип работы:
Нахождение связей между контекстами слов согласно предположению, что слова, находящиеся в похожих контекстах, имеют тенденцию значить похожие вещи, т.е. быть семантически близкими. Более формально задача стоит так: максимизация косинусной близости между векторами слов (скалярное произведение векторов), которые появляются рядом друг с другом, и минимизация косинусной близости между векторами слов, которые не появляются друг рядом с другом. Рядом друг с другом в данном случае значит в близких контекстах.
Например, слова «анализ» и «исследование» часто встречаются в похожих контекстах, вроде «Ученые провели анализ алгоритмов» или «Ученые провели исследование алгоритмов». Word2vec анализирует эти контексты и делает вывод, что слова «анализ» и «исследование» являются близкими по смыслу. Так как подобные выводы word2vec делает на основании большого количества текста, выводы оказываются вполне адекватными. Скажем, когда я тренировал word2vec на коллекции из 10 000 небольших научных текстов (что, вообще-то, маловато), то для слова «Испания» на готовой модели получил следующий список наиболее близких по смыслу слов — Италия, Австралия, Нидерланды, Португалия, Франция. То есть, всё вполне четко, за исключением, разве что, Австралии.
На основании вышесказанного можно сделать вывод: для обучения модели word2vec хорошего качества нужен большой-пребольшой корпус. Википедия, например, вполне подойдёт.
Зачем это надо?
word2vec можно отличнейшим образом применять для различных задач обработки естественного языка, как-то:
- Кластеризация слов по принципу их семантической близости
- Выявление семантической близости слов (например, к чему семантически ближе слово кот — к еде или космическим пиратам)
- Некоторые товарищи используют word2vec для анализа тональности, впрочем, не очень успешно. То есть, вполне успешно, но все-таки не очень.
И так далее, в общем-то.
В своей работе я использую word2vec для генерации контекстно близких слов для запроса в поисковую систему, которую мы разрабатываем, чтобы получать более полные результаты относительно запроса. А еще для сравнения документов по смыслу. Много для чего использую 🙂
Немного занудства
Довольно часто word2vec называют алгоритмом. А это ошибка, ведь word2vec — это не один алгоритм. Там есть два типа алгоритмов — на основе моделей Continuous Bag of Words и Skip-gram, в каждой из которых может применяться или не применяться негативное сэмплирование (которое Negative Sampling), а может еще быть прикручена и функция активации Hierarchical Softmax. То есть называть word2vec алгоритмом — не совсем логично. word2vec — это инструмент, набор алгоритмов или фреймворк.
Многие считают, что word2vec — это алгоритм глубокого обучения (которое Deep Learning). Спешу расстроить — word2vec это не глубокое обучение. Потому как там применяется вполне себе обычная, а не «глубокая» нейросеть прямого распространения (Feed-forward Neural Network).
Как всё это работает?
- Читается корпус, и рассчитывается встречаемость каждого слова в корпусе (т.е. количество раз, когда слово встретилось в корпусе — и так для каждого слова)
- Массив слов сортируется по частоте (слова сохраняются в хэш-таблице), и удаляются редкие слова (их еще зовут гапаксами)
- Строится дерево Хаффмана. Дерево Хаффмана (Huffman Binary Tree) часто применяется для кодирования словаря — это значительно снижает вычислительную и временную сложность алгоритма.
- Из корпуса читается т.н. субпредложение (sub-sentence) и проводится субсэмплирование наиболее частотных слов (sub-sampling). Субпредложение — это некий базовый элемент корпуса, обычно — просто предложение, но это может быть и абзац, например, или даже целая статья. Субсэмплирование — это процесс изъятия наиболее частотных слов из анализа, что ускоряет процесс обучения алгоритма и способствует значительному увеличению качества получающейся модели.
- По субпредложению проходим окном (размер окна задается алгоритму в качестве параметра). В данном случае под окном подразумевается максимальная дистанция между текущим и предсказываемым словом в предложении. То есть, если окно равно трем, то для предложения «Я твой дом труба шатал» анализ (то есть применение алгоритмов на базе одной из архитектур CBOW или Skip-gram) будет проходит внутри блока в три слова — для «Я твой дом», «твой дом труба» и т.д. Окно по умолчанию равно пяти, рекомендуемым значением является десять.
- Применяется нейросеть прямого распространения (Feedforward Neural Network) с функцией активации иерархический софтмакс (Hierarchical Softmax) и/или негативное сэмплирование (Negative Sampling). Hierarchical softmax лучше ведет себя при работе с не очень частотными словами, но работает помедленнее, negative sampling лучше работает с частотными словами и любит вектора слов небольшой размерности (скажем, 50-100), зато побыстрее.
CBOW и Skip-gram — это что такое?
Вообще, CBOW и Skip-gram — это нейросетевые архитектуры, которые описывают, как именно нейросеть «учится» на данных и «запоминает» представления слов. Принципы у обоих архитектур разные. Принцип работы CBOW — предсказывание слова при данном контексте, а skip-gram наоборот — предсказывается контекст при данном слове.
Continuous Bag of Words (CBOW) – обычная модель мешка слов с учётом четырёх ближайших соседей термина (два предыдущих и два последующих слова) без учёта порядка следования. Кажется, что вполне понятно, и пример не нужен 🙂
k-skip-n-gram — это последовательность длиной n, где элементы находятся на расстоянии не более, чем k друг от друга. Например, дано предложение *Я твой дом труба шатал*. Для этого предложения 1-skip-2-gram будет выглядеть, как набор биграмм («Я твой», «твой дом», «дом труба», «труба шатал») плюс следующие последовательности: *Я дом, твой труба, дом шатал* — то есть, пропускаем одно (1-skip) слово, а из оставшихся справа и слева от пропущенного делаем биграмм (2-gram).
Что такое негативное сэмплирование, и зачем оно нужно?
Как мы помним, задача построения модели word2vec выглядит так: максимизация близости векторов слов (скалярное произведение векторов), которые появляются рядом друг с другом, и минимизация близости векторов слов, которые не появляются друг рядом с другом.
Представим упрощенное уравнение этой идеи:
(1)
В числителе мы имеем близость слов контекста () и целевого слова (
), в знаменателе — близость всех других контекстов (
) и целевого слова (
). Проблема в том, что считать все это долго и сложно — контекстов может быть огромное множество для каждого слова. Негативное сэмплирование — один из способов справиться с этой проблемой. Принцип — мы не считаем ВСЕ возможные контексты, а выбираем случайным образом НЕСКОЛЬКО контекстов w_c1. И в результате если слово кошка появляется в контексте еды, то вектор слова еда будет ближе к вектору слова кошка, чем вектора некоторых иных случайно выбранных слов (например, косяк, снаряд или Гитлер), и необязательно привлекать вообще все слова из обучающего корпуса. Такой подход значительно облегчает процесс тренировки word2vec. Работает оно приблизительно как на картинке ниже:
Немного практических советов
На что влияют параметры и какие параметры — оптимальные?
- CBOW работает быстрее, зато Skip-gram работает лучше, особенно для относительно редких слов
- Иерархический софтмакс хорошо подходит для создания лучшей модели относительно редких слов, негативное сэмплирование же лучше моделирует более частотные слова.
- Применение суб-сэмплирования улучшает производительность. Рекомендуемый параметр субсэмплирования от 1e-3 до 1е-5
- Размер вектора чем больше, тем лучше (впрочем, не всегда, это от корпуса зависит)
- Размер окна — для Skip-gram оптимальный размер около 10, для CBOW — в районе 5
И как все-таки лучше тренировать word2vec?
Сам товарищ Миколов (изобретатель word2vec, если что) считает, что модель CBOW лучше подходит для больших корпусов текстов (сто миллионов, миллиард слов и больше), так как быстрее и чуть лучше работает с более частотными словами, а Skip-gram лучше для небольших корпусов текстов (меньше ста миллионов слов), так как лучше учитывает редкие слова и работает медленнее. Но в общем случае лучше применять схему skip-gram + negative sampling + окно 10 слов + субсэмплирование 1е-5 + размер вектора 300, так как по моему опыту она показывает наилучшие результаты.
В одной из следующих статей я планирую коснуться практики работы с этим чудесным инструментом. Как обычно, буду рад критическим комментариям. Впредь постараюсь почаще выкладывать новые статьи.
121,001 просмотров всего, 4 просмотров сегодня
Огонь, спасибо большое
Вопрос автору, а есть, что-то подобное, но применимо на php , чтобы также можно было обучать или готовых решений нет?
За статью спасибо, все понятно!
Очень интересный вопрос 🙂
Если говорить о word2vec, полностью написанном на PHP, то такого, конечно же, нет. Самый оптимальный вариант использовать word2vec в PHP — это вызывать его инстанс из кода на PHP. Ну и соответствующим образом передавать туда все необходимые данные с параметрами, а потом забирать результаты.
Подобную штуку для оригинального сишного w2v достаточно просто реализовали вот тут: https://github.com/donlinglok/C-word2vec-php
По мотивам можно организовать то же самое для других версий w2v, например, питоновского, который отлично реализован в библиотеке gensim
Приветствую. Вопрос не по теме, но не нашел, как еще к вам обратиться за советом.
В общем, в университете получил задачку, обнаруживать автоматически сгенерированные тексты. Сам я ничего про это не знаю, и все эти нейронные сети, n-граммы и т.д. для меня пока просто слова. Уже четыре дня пытаюсь разобраться во всем многообразии технологий, но все равно не пойму, в каком направлении двигаться и с чего стоит начать. Подскажите, если не затруднит.
Привет-привет! Вопрос достаточно масштабный и одним коротким комментарием не отделаться.
Прежде чем что-то подсказывать, нужно понять:
1. Какого рода эти автогенерённые тексты? Просто наборы слов? Осмысленные предложения? Бессмысленные, но грамматически связные предложения? Склеенные куски реальных текстов?
2. Есть ли определенная тематика у текстов? Это научные тексты? Записи в блогах?
3. Размеры текстов — это большие тексты? Или, может, твиты?
4. Есть ли обучающая выборка нормальных текстов и автоматически сгенерированных?
Если ответить на эти вопросы, станет более-менее понятно, в какую сторону идти 🙂
Изумительная статья, очень простая, понятная и по делу. Вопрос: как работаете с морфологией в русском языке при использовании word2vec?
Обожаю комплименты! 🙂 В общем случае с морфологией работаю достаточно просто: лемматизация + частеречное тэгирование для снятия омонимии. Т.е. на вход подаются слова типа «топор_N». Здесь можно экспериментировать со степенью детализации частеречных тэгов — то есть, можно, например, деепричастие приводить к глаголу, а можно отдельно маркировать как деепричастие в соответствующей начальной форме. Я обычно к глаголу привожу, ради унификации. Один раз работал в режиме «as-is», когда никаких преобразований со словоформами не производил (кроме приведения всего в lowercase и подобных простых манипуляций) — результаты были вполне приличные. Но такая штука подойдёт только для совсем больших корпусов, а такие нечасто попадаются. В… Read more »
А как быть с отрицаниями слов в предложении? Частица не с глаголами например.
Не совсем понял вопроса, если честно 🙂
Если долго повторять «кошка — не еда», word2vec вскоре научится тому, что кошка — это еда
А, вот в чем вопрос. Чтобы такого не произошло, можно применить т.н. квазилемму — объединить частицу «не» со словом, с которым она употребляется по контексту. И в данном случае у нас получится две леммы — «еда» и «не_еда». Таким образом, проблема решится — кошка будет не_еда. Понятно, что данный подход не решит всех проблем с отрицаниями, но в подобном случае будет работать на ура.