Урок 44. Эмбеддинги слов (Word2Vec, GloVe)
Зачем тебе этот урок
Урок 43 закончился на неприятном: BoW и TF-IDF считают слова, но про смысл не знают ничего. Для них «чайник» и «самовар» — две разные колонки, такие же чужие друг другу, как «чайник» и «асфальт».
Эмбеддинги закрывают эту дыру: слово становится вектором из пары сотен чисел, и расстояние между векторами начинает что-то значить.
Как применять готовые эмбеддинги, ты уже умеешь — это Урок 8 про semantic search. Здесь другое: откуда эти векторы берутся и почему у них вообще получается.
К концу урока ты:
- Поймёшь дистрибутивную гипотезу — единственную идею, на которой всё держится.
- Разберёшь Word2Vec и GloVe изнутри: какая задача решается и почему её ответ никому не нужен.
- Обучишь свои векторы на русском тексте, найдёшь ближайших соседей и проверишь знаменитую аналогию.
- Увидишь, где метод ломается — и зачем дальше понадобятся контекстные представления (Уроки 47 и 48).
1. Дистрибутивная гипотеза
Небольшой эксперимент. Прочитай три фразы: «налил в кружку горячий шурпак», «шурпак остыл, пришлось разогревать», «завари шурпак покрепче». Слова «шурпак» не существует, но ты уже знаешь про него почти всё: горячий напиток, что-то вроде чая. Определения никто не давал — хватило соседей.
Это и есть дистрибутивная гипотеза: значение слова задаётся тем, в каком окружении оно встречается. Сформулировал её лингвист Джон Ферс ещё в 1950-х, задолго до машинного обучения.
Ценность гипотезы в том, что она превращает смысл в статистику. Смысл померить нечем, а частоту совместной встречаемости — легко: нужен корпус текстов и счётчик. Всё дальнейшее — разные способы упаковать эту статистику в компактные векторы.
Гипотеза сильная, но не абсолютная. У антонимов контексты почти совпадают: «горячий чай» и «холодный чай», «сегодня жарко» и «сегодня холодно». Поэтому «горячий» и «холодный» окажутся близкими соседями. Метод ловит взаимозаменяемость, а не тождество значений — помни об этом всякий раз, когда смотришь на список соседей.
2. От разреженного представления к плотному
В Уроке 43 слово было колонкой в огромной таблице: словарь на 50 000 слов — вектор из 50 000 чисел, из которых 49 999 нулей. И главное: любые два разных слова там одинаково далеки друг от друга. Расстояние между «чайник» и «самовар» ровно то же, что между «чайник» и «асфальт».
| One-hot / TF-IDF | Эмбеддинг | |
|---|---|---|
| Размерность | Размер словаря, десятки тысяч | 100–300 чисел |
| Заполненность | Почти сплошные нули | Все числа работают |
| Похожесть слов | Не выражается никак | Косинус между векторами |
| Откуда числа | Счётчики частот | Обучение на корпусе |
Схему ты уже видел. В Уроке 39 encoder сжимал картинку из 784 пикселей в 16 чисел, и близкие точки латентного пространства оказывались похожими цифрами. Здесь то же самое: объект → вектор чисел → близость векторов означает похожесть объектов. Меняется объект (слово вместо картинки) и способ обучения — не реконструкция входа, а предсказание соседей.
Сразу сними лишние ожидания: 300 чисел не подписаны. Никто не задавал ось «одушевлённость». Направления выпали из данных сами, и большинство человеку не интерпретировать — ровно как оси латентного пространства автоэнкодера.
3. Word2Vec: модель обучают ради её весов
Главный фокус метода: задача, которую решает сеть, никому не нужна. Нужны веса, оставшиеся после обучения.
Word2Vec (Mikolov и коллеги, 2013) обучается на фиктивной задаче в одном из двух вариантов:
- CBOW — по окружению предсказать пропущенное слово: «завари ___ покрепче» → «чай».
- Skip-gram — наоборот, по слову предсказать его соседей: «чай» → «завари», «покрепче».
Архитектура до смешного простая, никаких скрытых нелинейностей:
слово (one-hot, V чисел)
→ матрица W размера V x d # V = размер словаря, d = 300
→ вектор слова (d чисел)
→ матрица W2 размера d x V
→ softmax по всему словарю → вероятность каждого слова быть соседом
Умножение one-hot на матрицу W — это просто выбор одной строки. Значит, строка матрицы W и есть эмбеддинг слова. Когда обучение закончилось, вторую матрицу и все предсказания выбрасывают, а W оставляют. Модель была лесами, а не зданием.
Почему это работает: если у двух слов окружения похожи, модель обязана выдавать по ним похожие предсказания, а единственный способ этого добиться — дать им похожие строки в W. Похожесть контекстов протекает в похожесть векторов прямо через loss.
Что выбирать. CBOW быстрее и устойчивее на частых словах: он усредняет контекст, а усреднение сглаживает шум. Skip-gram медленнее, но из каждого вхождения делает несколько обучающих пар, поэтому лучше вытягивает редкие слова и небольшие корпуса. На своём корпусе бери skip-gram.
4. Окно контекста решает, какую похожесть ты получишь
Окно — сколько соседей слева и справа считаются контекстом. Обычно от 2 до 10, по умолчанию 5. Это не настройка скорости, а выбор типа близости:
- Узкое окно (2). Рядом окажутся слова одной роли, взаимозаменяемые в позиции: «чайник» — «кастрюля», «понедельник» — «вторник».
- Широкое окно (10). Рядом окажутся слова одной темы: «чайник» — «кухня», «кипяток», «заварка».
Одна неочевидная деталь: на практике окно на каждой позиции случайно укорачивают, отчего ближние соседи попадают в пары чаще дальних и весят больше. Вручную задавать это не надо, но знать полезно: «окно 5» на деле значит «в среднем около трёх».
5. Negative sampling — иначе обучение не запустится
В схеме из раздела 3 есть убийственный шаг: softmax по всему словарю. Каждое обновление требует нормировки по 300 000 слов, а обновлений — миллиарды пар. Обучение не закончится никогда.
Приём меняет постановку задачи. Вместо «какое из 300 000 слов стоит рядом» спрашиваем «эта пара настоящая или я её выдумал?»:
- Берём реальную пару из текста — (чай, заварить), метка 1.
- Добавляем k пар со случайно вытянутыми словами — (чай, бетон), (чай, семнадцать), метка 0.
- Обучаем обычную логистическую регрессию на скалярном произведении двух векторов.
Теперь за шаг обновляются k+1 векторов вместо всего словаря. Разумные k: 5–20 на маленьком корпусе, 2–5 на большом.
Две детали, сильно влияющие на результат. Отрицательные слова тянут не равномерно, а из частотного распределения в степени 3/4: чистые частоты завалили бы обучение предлогами, равномерное распределение — редким мусором. И subsampling — слишком частые слова («и», «в», «не») с некоторой вероятностью выбрасывают из текста ещё до нарезки пар; информации в них мало, места занимают много.
Запомни главное: negative sampling — не улучшение качества, а способ сделать обучение вообще выполнимым. Второй известный вариант с тем же мотивом называется hierarchical softmax.
6. GloVe: та же идея с другого конца
Word2Vec бежит окном по тексту и учится на локальных парах, накапливая статистику неявно. GloVe (Stanford, 2014) собирает её честно и заранее.
Шаг 1 — матрица совстречаемости. Считаем X[i][j] — сколько раз слово j встретилось в окне вокруг слова i по всему корпусу. Матрица размера V на V, гигантская, но сильно разреженная.
Шаг 2 — факторизация. Ищем такие векторы, чтобы их скалярное произведение приближало логарифм счётчика:
u_i · v_j + b_i + b_j ≈ log X[i][j]
loss = сумма по парам: f(X[i][j]) · (u_i · v_j + b_i + b_j − log X[i][j])²
Функция f — веса пар: редкие совстречаемости почти шум и весят мало, у сверхчастых вес перестаёт расти, чтобы они не задавили остальное.
| Word2Vec | GloVe | |
|---|---|---|
| Что видит за шаг | Одну пару из окна | Одну клетку матрицы |
| Статистика | Локальная, копится неявно | Глобальная, собрана заранее |
| Память | Почти не нужна, поток текста | Матрица совстречаемости целиком |
| Добавить новый текст | Дообучить на нём | Пересчитать матрицу |
Ответ на «что лучше»: разница мала и зависит от корпуса и настроек сильнее, чем от метода. Знать надо оба, потому что готовые наборы векторов встречаются и те и другие. Понимать надо общее: оба сводятся к факторизации статистики совместной встречаемости, просто заходят с разных сторон.
7. Арифметика векторов и косинусная близость
Похожесть меряют косинусом угла между векторами: от −1 (противоположны) до 1 (сонаправлены). Длина игнорируется, важно направление. Механику ты разбирал в Уроке 8, здесь она просто инструмент.
Знаменитый эффект: если посчитать
вектор("король") − вектор("мужчина") + вектор("женщина")
то ближайшим к результату часто оказывается «королева». Интуиция такая: разность двух векторов — направление, кодирующее отношение (здесь «пол»), и его можно перенести на другое слово. Работают и другие отношения: «Москва» − «Россия» + «Франция» ≈ «Париж», единственное число → множественное, настоящее время → прошедшее.
Теперь честно про пределы — этот пример растиражирован до состояния магии:
- Из ответа принудительно исключают три исходных слова. Не исключишь — ближайшим окажется сам «король», он к результату ближе всех.
- Работает на частых словах и хорошо представленных отношениях. На редких — шум, красивых примеров там не найдёшь.
- Русский тяжелее английского: падежи и род размазывают слово по формам, и «королева», «королевы», «королеве» для модели — три разных слова. Лечится лемматизацией (Урок 43) или подсловными моделями (раздел 10).
- Часть эффекта — свойство метрики, а не понимание отношений. Аналогии — эффектная демонстрация, но плохой критерий качества.
8. Главное ограничение: одно слово — один вектор
Возьми слово «ключ». От двери, родник в лесу, скрипичный, гаечный, ключ к разгадке. Word2Vec выдаст один вектор. Он не выберет значение — он усреднит все, взвесив по частоте в корпусе. Получится точка, не соответствующая ни одному из смыслов, а в соседях окажутся вперемешку «замок», «родник» и «отвёртка».
То же с «лук», «коса», «среда», «мир». И проблема шире омонимов: «быстрый ответ» и «быстрая река» — разные оттенки, а вектор один на двоих. Обратная сторона того же изъяна: контекст вообще не влияет на вектор. В предложениях «он повернул ключ» и «мы набрали воды из ключа» эмбеддинг буквально одинаковый — он взят из таблицы, а таблица про предложение ничего не знает.
Отсюда следующий шаг: представление, которое вычисляется для слова в конкретном предложении, а не берётся готовым. Такие представления называют контекстными. Механизм, который это позволил, — внимание (Урок 47), а модели на нём — трансформеры (Урок 48). Разница в одной фразе: Word2Vec — словарь, куда заглядывают; трансформер — функция, которую считают на лету.
9. Смещения: модель наследует стереотипы корпуса
Аналогия «врач» − «мужчина» + «женщина» в классических векторах даёт «медсестра». Не потому что так устроен мир, а потому что так устроены тексты, на которых обучались.
Нюанс, который часто проговаривают неверно: это не грязные данные, а свойство метода. Мы явно поставили задачу — воспроизведи статистику совместной встречаемости, — и модель её добросовестно выполнила. Стереотип входит в эту статистику, и внутри метода нечем отделить «правильную» часть от «неправильной»: обе выглядят одинаково.
Практический вывод: как только эмбеддинги попадают в систему, принимающую решения о людях — отбор резюме, скоринг, модерация, — смещение переходит прямиком в решения. Приёмы ослабления существуют, но проблему не снимают. Проверяй на своей задаче, а не надейся, что «модель нейтральная».
10. Эмбеддинги не только слов
- Подсловные (fastText). Слово режут на символьные n-граммы, вектор слова — сумма векторов кусочков. Для русского это два выигрыша: формы одного слова автоматически близки (общие куски), и слово, которого не было при обучении — опечатка, неологизм, фамилия — всё равно получает осмысленный вектор.
- Предложения и документы. Простейший приём — усреднить векторы слов: грубо, порядок теряется, но как быстрый ориентир держится удивительно хорошо. Дальше — doc2vec, где документ участвует в обучении как ещё одно «слово», и современные модели предложений, которыми ты пользовался в Уроке 8.
- Товары, пользователи, треки. Замени «предложение» на последовательность действий пользователя, «слово» — на товар. Алгоритм не меняется, а на выходе товары, покупаемые в похожих контекстах, стоят рядом. На этом устроена изрядная часть рекомендательных систем.
Шаблон общий: есть последовательности объектов и в соседстве есть смысл → можно строить эмбеддинги. Тексты — просто первый и самый удобный случай.
11. Как понять, что векторы хорошие
- Аналогии. Готовый набор четвёрок вида «Москва : Россия = Париж : ?», считаем долю верных. Наглядно, но узко — см. раздел 7.
- Корреляция с людьми. Людям дают пары слов и просят оценить похожесть по шкале, затем считают ранговую корреляцию оценок с косинусами. Ближе к делу, но упирается в вопрос, что люди понимали под похожестью — ассоциацию или взаимозаменяемость.
- Своя конечная задача. Подставь векторы в свой классификатор, поиск или рекомендации и померь метрику, которая тебя реально волнует.
Решай по третьему пункту. Векторы, выигравшие на аналогиях, легко проигрывают на твоей классификации отзывов — и наоборот.
12. Код: обучаем свои векторы на русском тексте
Библиотека gensim. Корпус — любой текстовый файл; чем больше, тем осмысленнее результат.
import re
import numpy as np
from gensim.models import Word2Vec, KeyedVectors
# --- 1. Корпус: список предложений, каждое предложение — список слов -------
raw = open("corpus.txt", encoding="utf-8").read().lower()
sentences = [re.findall(r"[а-яё]+", s) for s in re.split(r"[.!?\n]", raw)]
sentences = [s for s in sentences if len(s) >= 3]
print("предложений:", len(sentences), "| словоупотреблений:", sum(map(len, sentences)))
# --- 2. Обучение: skip-gram + negative sampling ----------------------------
model = Word2Vec(
sentences,
vector_size=200, # длина вектора слова
window=5, # окно контекста (раздел 4)
min_count=5, # слова реже 5 раз выбрасываем
sg=1, # 1 = skip-gram, 0 = CBOW
negative=10, # k отрицательных примеров (раздел 5)
sample=1e-3, # subsampling частых слов
epochs=10,
workers=4, # с несколькими потоками результат от запуска к запуску
seed=42, # немного плавает; для строгой воспроизводимости workers=1
)
print("слов в словаре:", len(model.wv))
# --- 3. Ближайшие соседи — главная проверка на вменяемость -----------------
for word in ["король", "кошка", "город", "ключ"]:
if word in model.wv:
neighbors = [w for w, _ in model.wv.most_similar(word, topn=5)]
print(word, "→", ", ".join(neighbors))
# --- 4. Косинусная близость пары -------------------------------------------
print("кошка / собака:", model.wv.similarity("кошка", "собака"))
print("кошка / трактор:", model.wv.similarity("кошка", "трактор"))
# --- 5. Аналогия: король − мужчина + женщина -------------------------------
print(model.wv.most_similar(positive=["король", "женщина"],
negative=["мужчина"], topn=3))
# --- 6. Лишнее слово в ряду ------------------------------------------------
print(model.wv.doesnt_match(["кошка", "собака", "хомяк", "трактор"]))
# --- 7. Визуализация: 200 измерений на плоскость через PCA -----------------
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
probe = ["король", "королева", "мужчина", "женщина",
"кошка", "собака", "москва", "париж"]
probe = [w for w in probe if w in model.wv]
xy = PCA(n_components=2).fit_transform(np.array([model.wv[w] for w in probe]))
plt.figure(figsize=(7, 6))
plt.scatter(xy[:, 0], xy[:, 1])
for w, (x, y) in zip(probe, xy):
plt.annotate(w, (x, y), fontsize=11)
plt.savefig("words.png", dpi=120)
# --- 8. Сохранить свои векторы ---------------------------------------------
model.wv.save_word2vec_format("my_vectors.txt")
# --- 9. Загрузить готовые векторы (текстовый формат, первые 200 тыс. слов) --
pre = KeyedVectors.load_word2vec_format("ru_vectors.vec", limit=200_000)
print(pre.most_similar("ключ", topn=10)) # смыслы перемешаны — раздел 8
На корпусе в пару сотен килобайт соседи будут выглядеть случайными — это нормальный результат, а не ошибка в коде. Word2Vec голоден: осмысленные векторы начинаются от десятков миллионов словоупотреблений. Пункт 9 существует ровно поэтому — в реальной работе почти всегда берут готовые векторы, а свои обучают под специфичную лексику и при большом объёме текстов.
13. Тонкости и подводные камни
- Маленький корпус даёт красивый мусор. Соседи выглядят убедительно, потому что мозг достраивает связь. Прежде чем радоваться списку, проверь его на словах, ответ по которым знаешь заранее.
- Не занижай min_count. С min_count=1 словарь забьётся словами, встреченными однажды: вектор по одному вхождению бессмыслен, а память и время съедает.
- Близко не значит синоним. Рядом стоят антонимы (раздел 1) и просто слова одного поля: «кошка» и «собака» близки, хотя это разные животные. Косинус как детектор синонимов не работает.
- Согласуй предобработку. Векторы обучены на леммах — на вход в работе тоже подавай леммы. Расхождение между обучением и применением — самая частая причина «модель почему-то ничего не находит».
- Слова вне словаря. У Word2Vec и GloVe слова, не попавшего в словарь, просто нет — обращение уронит код с ошибкой. Либо проверяй наличие, либо бери fastText, который соберёт вектор из кусочков.
- Не тащи векторы между доменами не глядя. Обученные на новостях плохо ложатся на медицинские тексты или чаты поддержки: другая лексика, другие контексты у общих слов.
14. Глоссарий
Дистрибутивная гипотеза
Значение слова определяется его окружением. Слова с похожими контекстами имеют похожие значения.
Эмбеддинг
Плотный вектор из сотни-другой чисел, представляющий объект так, что близость векторов означает похожесть объектов.
Skip-gram
Вариант Word2Vec: по слову предсказываем соседей. Лучше работает на редких словах и небольших корпусах.
CBOW
Обратный вариант Word2Vec: по окружению предсказываем пропущенное слово. Быстрее, устойчивее на частых словах.
Negative sampling
Замена softmax по всему словарю на бинарную задачу «настоящая пара или выдуманная». Делает обучение выполнимым.
Матрица совстречаемости
Таблица, где в клетке — сколько раз два слова встретились рядом. Основа GloVe: векторы получают её факторизацией.
15. Практика (50 минут)
- Собери корпус мегабайт на десять: тексты книг, выгрузка своих заметок, статьи из открытых источников. Приведи к нижнему регистру и нарежь на предложения, как в разделе 12.
- Обучи модель по разделу 12 и посмотри соседей для десяти слов, значения которых знаешь наверняка. Отдельно проверь омоним («ключ», «лук», «коса») — соседи должны оказаться из разных смыслов сразу.
- Обучи вторую модель с window=2 и третью с window=10, остальное не меняй. Сравни соседей одного слова: узкое окно даст взаимозаменяемые слова, широкое — тематически связанные.
- Проверь аналогию «король − мужчина + женщина». Промахнулась — не спеши чинить код: скорее всего, корпус мал или слова редкие. Возьми пару, которая в твоих текстах точно частая.
- Загрузи готовые векторы для русского и повтори пункты 2 и 4 на них. Разница с твоей моделью и есть цена размера корпуса. Заодно нарисуй карту из пункта 7 кода на 15–20 своих словах.
- Запиши в
progress.md: какое окно дало более полезных соседей на твоей задаче, что вышло с омонимом и насколько готовые векторы обошли твои.
16. Проверь себя
1. На какой идее держатся все эмбеддинги слов?
На дистрибутивной гипотезе: значение слова задаётся его окружением, поэтому слова с похожими контекстами получают похожие векторы. Смысл сводится к статистике совместной встречаемости.
2. Что именно в Word2Vec является эмбеддингом?
Веса, а не выход: строки матрицы, на которую умножается входное слово. Задача предсказания соседей — лишь повод обучить эти веса, её результат выбрасывают.
3. Зачем нужен negative sampling?
Softmax по словарю в сотни тысяч слов делает обучение невозможным по времени. Вместо него решают бинарную задачу «пара настоящая или выдуманная» на нескольких случайных словах, обновляя единицы векторов за шаг.
4. Чем GloVe отличается от Word2Vec?
Порядком работы: GloVe сначала собирает глобальную матрицу совстречаемости, потом факторизует её. Word2Vec накапливает ту же статистику неявно, бегая окном по тексту. По качеству сопоставимы.
5. Почему Word2Vec не справляется со словом «ключ» и что пришло дальше?
Одно слово — один вектор: все значения усредняются в одну точку, контекст предложения ни на что не влияет. Дальше появились контекстные представления, где вектор вычисляется под конкретное предложение — внимание (Урок 47) и трансформеры (Урок 48).
17. Что должно остаться в голове
- Дистрибутивная гипотеза: слово определяется соседями. Это превращает неизмеримый смысл в измеримую статистику.
- Эмбеддинг — плотный вектор из 100–300 чисел вместо разреженной колонки на десятки тысяч. Близость векторов означает близость смыслов.
- Это то же латентное пространство, что в автоэнкодерах (Урок 39): объект → вектор → близость векторов = похожесть объектов.
- Word2Vec обучают на фиктивной задаче (CBOW или skip-gram), а забирают веса. Модель — леса, эмбеддинги — здание.
- Окно контекста выбирает тип похожести: узкое даёт взаимозаменяемые слова, широкое — тематически связанные.
- Negative sampling заменяет неподъёмный softmax бинарной задачей и делает обучение возможным.
- GloVe заходит с другой стороны: матрица совстречаемости и её факторизация. Оба метода сводятся к одной статистике.
- Арифметика векторов работает избирательно: на частых словах, с исключением исходных, и на русском хуже из-за падежей.
- Одно слово — один вектор: омонимы усредняются, контекст игнорируется. Это потолок метода и мостик к Урокам 47 и 48.
- Смещения корпуса воспроизводятся точно, потому что модель ровно об этом и просили. Проверяй их на своей задаче.