Word2Vec: слова как векторы

Модель никогда не видела определений слов и не знала грамматики — только статистику соседей. Тем не менее разность векторов «Токио» и «Японии» примерно совпадает с разностью «Берлина» и «Германии»: одна и та же длина, одно и то же направление. В 2013 году Томаш Миколов из Google показал, что если обучить простую нейросеть предсказывать контекст слова, то побочный продукт обучения — векторные представления слов — кодирует смысловые отношения как направления. Вычитаем из «короля» «мужчину», прибавляем «женщину» — получаем «королеву».

Word2Vec: параллельные стрелки = одинаковые семантические отношения

Дистрибутивная гипотеза

«Вы узнаете слово по его окружению.» — Дж. Р. Фёрс, 1957

Идея восходит к лингвисту Фёрсу: значение слова определяется тем, какие слова стоят рядом с ним в текстах. «Кошка» и «собака» появляются в похожих контекстах (любит, ест, спит, хозяин), а «кошка» и «интеграл» — в совершенно разных.

До Word2Vec эту идею считали в лоб: сколько раз какие слова встречаются рядом (матрица совместной встречаемости), а дальше — SVD этой матрицы и латентно-семантический анализ (LSA, Deerwester et al., 1990). Но SVD матрицы |V| \times |V| при словаре |V| = 100\,000 — дорого.

Архитектуры Word2Vec

Миколов предложил две архитектуры1:

1 Mikolov, Chen, Corrado, Dean. Efficient Estimation of Word Representations in Vector Space, 2013.

CBOW (Continuous Bag of Words)

По контексту (окружающим словам) предсказываем центральное слово:

\hat{w}_t = \arg\max_w P(w \mid w_{t-c}, \ldots, w_{t-1}, w_{t+1}, \ldots, w_{t+c})

Модель усредняет векторы контекстных слов и пропускает их через линейный слой и softmax.

Skip-gram

Наоборот: по центральному слову предсказываем каждое из контекстных:

\max_\theta \sum_{t=1}^{T} \sum_{-c \leq j \leq c,\, j \neq 0} \log P(w_{t+j} \mid w_t; \theta)

Skip-gram работает лучше для редких слов, CBOW — быстрее и лучше для частых.

Negative Sampling

Считать softmax по словарю из 10^5 слов — тоже дорого. Трюк Миколова: вместо полного softmax обучаем бинарный классификатор, который отличает настоящие пары (слово, контекст) от случайных «негативных» пар:

\log \sigma(v_w^T v_c) + \sum_{i=1}^{k} \mathbb{E}_{w_i \sim P_n} \left[\log \sigma(-v_{w_i}^T v_c)\right],

где \sigma — сигмоида, k \approx 5{-}20 негативных примеров, P_n(w) \propto f(w)^{3/4} — сглажённое распределение частот.

Магия аналогий

После обучения на большом корпусе (Google News, около 100 миллиардов слов) у векторов слов появляется структура: смысловые отношения кодируются как направления.

Король − Мужчина + Женщина ≈ Королева

\vec{v}_{\text{king}} - \vec{v}_{\text{man}} + \vec{v}_{\text{woman}} \approx \vec{v}_{\text{queen}}

Это работает, потому что вычитание \vec{v}_{\text{man}} убирает из «короля» признак пола и оставляет саму роль — «монарх». Прибавление \vec{v}_{\text{woman}} возвращает пол обратно, но уже женский, — и получается «королева».

Выберите слова a − b + c и тащите любую точку-слово мышью: параллелограмм аналогии и ближайшее к результату слово перестраиваются сразу.

Париж − Франция + Германия ≈ Берлин

\vec{v}_{\text{Paris}} - \vec{v}_{\text{France}} + \vec{v}_{\text{Germany}} \approx \vec{v}_{\text{Berlin}}

Разность «Париж − Франция» — это направление «столица своей страны», и для всех подобных пар оно примерно одно и то же.

СоветЗакон сюжета

Нейросеть не учили понимать аналогии. Чтобы предсказывать соседей, ей пришлось сжать язык в пространство, где работает арифметика, — это следствие дистрибутивной гипотезы в непрерывном пространстве.

Почему именно около 300 измерений? Теорема Джонсона–Линденштрауса говорит: n точек можно уложить в \mathbb{R}^d так, что попарные расстояния сохранятся с погрешностью \varepsilon, если d = O(\varepsilon^{-2} \log n). Главное здесь — под логарифмом стоит именно число точек: увеличьте словарь в десять раз, и нужная размерность подрастёт лишь чуть-чуть. Поэтому словарь в 3 миллиона слов и не требует миллионов измерений.

А вот выводить из теоремы само число 300 не стоит. Множитель \varepsilon^{-2} дорогой: уложиться в 300 измерений её гарантия позволяет только при довольно грубой \varepsilon, а для по-настоящему точного сохранения расстояний потребовалась бы размерность намного больше. Да и константа под O(\cdot) не названа. 300 — значение, подобранное на практике; теорема объясняет не его, а то, почему размерность вообще не обязана расти вместе со словарём.

Таблица аналогий

Каждая строка читается как a - b + c \approx d — та же арифметика, что в форме виджета выше.

Отношение a b c d (найдено)
Пол king man woman queen
Столица Paris France Germany Berlin
Пол (рус.) король мужчина женщина королева
Время глагола walking walked swimming swam
Сравнительная степень bigger big small smaller
Валюта yen Japan Russia ruble
import gensim.downloader as api

# Загрузка предобученной модели (300-мерные вектора)
model = api.load("word2vec-google-news-300")

# Классическая аналогия
result = model.most_similar(
    positive=["king", "woman"],
    negative=["man"],
    topn=3
)
print("king - man + woman =")
for word, score in result:
    print(f"  {word}: {score:.4f}")
# king - man + woman =
#   queen: 0.7118
#   monarch: 0.6189
#   princess: 0.5902

# Географическая аналогия
result = model.most_similar(
    positive=["Paris", "Germany"],
    negative=["France"],
    topn=3
)
print("\nParis - France + Germany =")
for word, score in result:
    print(f"  {word}: {score:.4f}")
# Paris - France + Germany =
#   Berlin: 0.7331
#   Munich: 0.5624
#   Hamburg: 0.5114

Почему это работает: геометрия

Levy и Goldberg2 показали, что Word2Vec с negative sampling неявно факторизует сдвинутую матрицу поточечной взаимной информации (PMI) — ту же PMI-матрицу, из которой вычли постоянный сдвиг \log k, где k — число негативных примеров:

\vec{v}_w \cdot \vec{v}_c \approx \text{PMI}(w, c) - \log k,

где \text{PMI}(w, c) = \log \frac{P(w, c)}{P(w)P(c)}.

Аналогии работают, потому что в пространстве PMI отношения типа «столица — страна» статистически регулярны. Слова «Париж» и «Берлин» появляются в похожих контекстах (правительство, парламент, столица), слова «Франция» и «Германия» — тоже (страна, экономика, население). Разность PMI для таких пар получается примерно одинаковой.

Визуализация: проекция t-SNE

300-мерные векторы можно спроецировать на плоскость методом t-SNE (van der Maaten, Hinton, 2008) — он сохраняет локальные расстояния:

import numpy as np
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt

words = [
    # Страны и столицы
    "France", "Paris", "Germany", "Berlin",
    "Italy", "Rome", "Spain", "Madrid",
    "Japan", "Tokyo", "Russia", "Moscow",
    # Королевская семья
    "king", "queen", "prince", "princess",
    "man", "woman", "boy", "girl",
    # Еда
    "pizza", "sushi", "burger", "pasta",
]

vectors = np.array([model[w] for w in words])
tsne = TSNE(n_components=2, random_state=42, perplexity=8)
coords = tsne.fit_transform(vectors)

plt.figure(figsize=(12, 8))
for i, word in enumerate(words):
    plt.scatter(coords[i, 0], coords[i, 1], s=50, c='steelblue')
    plt.annotate(word, (coords[i, 0] + 0.5, coords[i, 1] + 0.5),
                 fontsize=11)

# Стрелки аналогий
for a, b in [("France", "Paris"), ("Germany", "Berlin"),
             ("king", "queen"), ("man", "woman")]:
    ia, ib = words.index(a), words.index(b)
    plt.annotate("", xy=coords[ib], xytext=coords[ia],
                 arrowprops=dict(arrowstyle="->", color="crimson",
                                 lw=1.5))
plt.title("t-SNE проекция Word2Vec")
plt.axis("off")
УведомлениеОграничения аналогий

Не все аналогии работают одинаково хорошо. Mikolov et al. предложили тест из 19 544 вопросов в 14 категориях (смысловых и грамматических). Доля верных ответов на нём — около 60–75% у лучших моделей. Аналогии вида «муж − жена + бабушка = ?» часто дают неправильный ответ, потому что отношения не всегда линейны.

От Word2Vec к современным эмбеддингам

Word2Vec — статический эмбеддинг: каждому слову соответствует ровно один вектор, вне зависимости от контекста. Слово «банк» в «денежный банк» и «берег реки» получает одно и то же представление.

Контекстуальные эмбеддинги (ELMo, 2018; BERT, 2019; GPT) решают эту проблему: вектор слова зависит от всего предложения. Каждый слой трансформера порождает свой набор эмбеддингов, и один и тот же токен в разных контекстах получает разные координаты.

Идея Word2Vec — слова живут в геометрическом пространстве, где близость означает близость по смыслу — осталась и в BERT, и в GPT.

Численный эксперимент: обучение с нуля

from gensim.models import Word2Vec
from gensim.utils import simple_preprocess

# Корпус: русская Википедия (упрощённый пример)
sentences = [
    simple_preprocess("Москва столица России"),
    simple_preprocess("Берлин столица Германии"),
    simple_preprocess("Париж столица Франции"),
    simple_preprocess("Лондон столица Великобритании"),
    simple_preprocess("Россия большая страна"),
    simple_preprocess("Германия европейская страна"),
    simple_preprocess("Франция европейская страна"),
    # ... нужны миллионы предложений для хороших эмбеддингов
]

model = Word2Vec(
    sentences,
    vector_size=100,   # размерность вектора
    window=5,          # окно контекста
    min_count=1,       # минимальная частота слова
    sg=1,              # 1 = skip-gram, 0 = CBOW
    negative=5,        # число негативных примеров
    epochs=100,
)

# Для осмысленных аналогий нужен корпус ≥ 100M слов
# На малом корпусе результаты будут шумные

Связи

  • SVD — Word2Vec — это почти факторизация сдвинутой матрицы PMI
  • Внимание — следующий шаг: эмбеддинг перестаёт быть фиксированным и начинает зависеть от контекста
  • Ландшафт функции потерь — почему 300 измерений хватает на словарь в миллионы слов
  • t-SNE — нелинейная проекция, чтобы посмотреть на векторы глазами
Наверх