Фаза 05 · урок 04

GloVe, fastText и подсловные представления

Цель урока: Во-первых, параллельное направление исследований напрямую факторизовало матрицу совместной встречаемости (LSA, HAL), а не выполняло онлайн-обновления skip-gram. Был ли итеративный подход Word2Vec принципиально лучше или различие было…

Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.

Курс
AI Engineering from Scratch
Фаза
NLP: от основ к продвинутым темам
Чтение
10 мин.
Проверено
Содержание урока
  1. Проблема
  2. Концепция
  3. Соберите это
  4. GloVe: факторизация матрицы совместной встречаемости
  5. FastText: представления с учётом подслов
  6. BPE: обучаемый подсловный словарь
  7. Примените это
  8. Какой вариант выбрать
  9. Подготовьте к поставке
  10. Упражнения
  11. Ключевые термины
  12. Дополнительное чтение

Word2Vec обучал одно представление на слово. GloVe факторизовал матрицу совместной встречаемости. FastText встраивал части слов. BPE проложил мост к трансформерам.

Тип: Сборка Языки: Python Предварительные требования: Фаза 5 · 03 (Word2Vec с нуля) Время: ~45 минут

Схема к уроку «GloVe, fastText и подсловные представления»

Проблема

Word2Vec оставил два открытых вопроса.

Во-первых, параллельное направление исследований напрямую факторизовало матрицу совместной встречаемости (LSA, HAL), а не выполняло онлайн-обновления skip-gram. Был ли итеративный подход Word2Vec принципиально лучше или различие было артефактом того, как два метода обрабатывали счётчики? GloVe ответил на это: факторизация матрицы с продуманно выбранной функцией потерь соответствует Word2Vec или превосходит его и требует меньше затрат на обучение.

Во-вторых, ни у одного метода не было ответа для слов, которых он никогда не видел. Zoomer-approved, dogecoin, любое имя собственное, придуманное на прошлой неделе, каждая флективная форма редкого корня. FastText исправил это, встраивая символьные n-граммы: слово — это сумма его частей, включая морфемы, поэтому даже слова вне словаря получают осмысленный вектор.

В-третьих, с появлением трансформеров вопрос вновь сместился. Словари на уровне слов ограничиваются примерно миллионом записей; реальный язык более открыт. Кодирование пар байтов (byte-pair encoding, BPE) и родственные ему методы решили проблему, обучая словарь частых подсловных единиц, который покрывает всё. Каждый современный токенизатор для каждой современной LLM — подсловный токенизатор.

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

Концепция

GloVe (Global Vectors, глобальные векторы). Постройте матрицу совместной встречаемости слово—слово X, где X[i][j] — число появлений слова j в контексте слова i. Обучите векторы так, чтобы v_i · v_j + b_i + b_j ≈ log(X[i][j]). Взвесьте функцию потерь, чтобы частые пары не доминировали. Готово.

FastText. Слово — это сумма его символьных n-грамм плюс само слово. where превращается в <wh, whe, her, ere, re>, <where>. Вектор слова — сумма векторов этих компонентов. Обучайте как Word2Vec. Преимущество: невидимые слова (whereupon) составляются из известных n-грамм.

BPE (Byte-Pair Encoding, кодирование пар байтов). Начните со словаря отдельных байтов (или символов). Посчитайте каждую соседнюю пару в корпусе. Объедините наиболее частую пару в новый токен. Повторите для k итераций. В результате получится словарь из k + 256 токенов, где частые последовательности (ing, tion, the) являются одиночными токенами, а редкие слова разбиваются на знакомые части. Любое предложение токенизируется во что-нибудь.

Соберите это

GloVe: факторизация матрицы совместной встречаемости

import numpy as np
from collections import Counter


def build_cooccurrence(docs, window=5):
    pair_counts = Counter()
    vocab = {}
    for doc in docs:
        for token in doc:
            if token not in vocab:
                vocab[token] = len(vocab)
    for doc in docs:
        indexed = [vocab[t] for t in doc]
        for i, center in enumerate(indexed):
            for j in range(max(0, i - window), min(len(indexed), i + window + 1)):
                if i != j:
                    distance = abs(i - j)
                    pair_counts[(center, indexed[j])] += 1.0 / distance
    return vocab, pair_counts


def glove_train(vocab, pair_counts, dim=16, epochs=100, lr=0.05, x_max=100, alpha=0.75, seed=0):
    n = len(vocab)
    rng = np.random.default_rng(seed)
    W = rng.normal(0, 0.1, size=(n, dim))
    W_tilde = rng.normal(0, 0.1, size=(n, dim))
    b = np.zeros(n)
    b_tilde = np.zeros(n)

    for epoch in range(epochs):
        for (i, j), x_ij in pair_counts.items():
            weight = (x_ij / x_max) ** alpha if x_ij < x_max else 1.0
            diff = W[i] @ W_tilde[j] + b[i] + b_tilde[j] - np.log(x_ij)
            coef = weight * diff

            grad_W_i = coef * W_tilde[j]
            grad_W_tilde_j = coef * W[i]
            W[i] -= lr * grad_W_i
            W_tilde[j] -= lr * grad_W_tilde_j
            b[i] -= lr * coef
            b_tilde[j] -= lr * coef

    return W + W_tilde

Стоит назвать два движущихся элемента. Весовая функция f(x) = (x/x_max)^alpha понижает вес очень частых пар (таких как (the, and)), чтобы они не доминировали в функции потерь. Итоговое представление — сумма таблиц W (центральные слова) и W_tilde (контекст). Суммирование обеих — опубликованный приём, который обычно превосходит использование только одной из них.

FastText: представления с учётом подслов

def char_ngrams(word, n_min=3, n_max=6):
    wrapped = f"<{word}>"
    grams = {wrapped}
    for n in range(n_min, n_max + 1):
        for i in range(len(wrapped) - n + 1):
            grams.add(wrapped[i:i + n])
    return grams
>>> char_ngrams("where")
{'<where>', '<wh', 'whe', 'her', 'ere', 're>', '<whe', 'wher', 'here', 'ere>', '<wher', 'where', 'here>'}

Каждое слово представляется набором своих n-грамм (обычно от 3 до 6 символов). Вектор слова — сумма векторов его n-грамм. При обучении skip-gram подставьте это туда, где Word2Vec использовал единственный вектор.

def fasttext_vector(word, ngram_table):
    grams = char_ngrams(word)
    vecs = [ngram_table[g] for g in grams if g in ngram_table]
    if not vecs:
        return None
    return np.sum(vecs, axis=0)

Для невидимого слова вы всё равно получите вектор, пока известна хотя бы часть его n-грамм. whereupon разделяет <wh, her, ere и <where со словом where, поэтому оба оказываются рядом друг с другом.

BPE: обучаемый подсловный словарь

def learn_bpe(corpus, k_merges):
    vocab = Counter()
    for word, freq in corpus.items():
        tokens = tuple(word) + ("</w>",)
        vocab[tokens] = freq

    merges = []
    for _ in range(k_merges):
        pair_freq = Counter()
        for tokens, freq in vocab.items():
            for a, b in zip(tokens, tokens[1:]):
                pair_freq[(a, b)] += freq
        if not pair_freq:
            break
        best = pair_freq.most_common(1)[0][0]
        merges.append(best)

        new_vocab = Counter()
        for tokens, freq in vocab.items():
            new_tokens = []
            i = 0
            while i < len(tokens):
                if i + 1 < len(tokens) and (tokens[i], tokens[i + 1]) == best:
                    new_tokens.append(tokens[i] + tokens[i + 1])
                    i += 2
                else:
                    new_tokens.append(tokens[i])
                    i += 1
            new_vocab[tuple(new_tokens)] = freq
        vocab = new_vocab
    return merges


def apply_bpe(word, merges):
    tokens = list(word) + ["</w>"]
    for a, b in merges:
        new_tokens = []
        i = 0
        while i < len(tokens):
            if i + 1 < len(tokens) and tokens[i] == a and tokens[i + 1] == b:
                new_tokens.append(a + b)
                i += 2
            else:
                new_tokens.append(tokens[i])
                i += 1
        tokens = new_tokens
    return tokens
>>> corpus = Counter({"low": 5, "lower": 2, "newest": 6, "widest": 3})
>>> merges = learn_bpe(corpus, k_merges=10)
>>> apply_bpe("lowest", merges)
['low', 'est</w>']

На первой итерации объединяется наиболее частая соседняя пара. После достаточного числа итераций частые подстроки (low, est, tion) становятся одиночными токенами, а редкие слова аккуратно разбиваются на части.

Настоящие токенизаторы GPT / BERT / T5 обучают 30k–100k слияний. Результат: любой текст токенизируется в последовательность известным идентификаторов ограниченной длины, OOV больше не бывает.

Примените это

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

import fasttext.util
fasttext.util.download_model("en", if_exists="ignore")
ft = fasttext.load_model("cc.en.300.bin")
print(ft.get_word_vector("whereupon").shape)
print(ft.get_word_vector("zoomerapproved").shape)

Для подсловной токенизации в стиле BPE в эпоху трансформеров:

from transformers import AutoTokenizer

tok = AutoTokenizer.from_pretrained("gpt2")
print(tok.tokenize("unbelievably tokenized"))
['un', 'bel', 'iev', 'ably', 'Ġtoken', 'ized']

Префикс Ġ обозначает границы слов (соглашение GPT-2). Каждый современный токенизатор — это вариант BPE, WordPiece (BERT) или SentencePiece (T5, LLaMA).

Какой вариант выбрать

Ситуация Выбор
Предобученные универсальные векторы слов, устойчивость к OOV не нужна GloVe 300d
Предобученные универсальные векторы слов, нужно обрабатывать опечатки / неологизмы / морфологически богатые языки FastText
Всё, что поступает в трансформер (обучение или инференс) Токенизатор, поставляемый вместе с моделью. Никогда не заменяйте его.
Обучение собственной языковой модели с нуля Сначала обучите токенизатор BPE или SentencePiece на своём корпусе
Производственная классификация текста линейной моделью Всё ещё TF-IDF. Урок 02.

Подготовьте к поставке

Сохраните как outputs/skill-embeddings-picker.md:

---
name: tokenizer-picker
description: Pick a tokenization approach for a new language model or text pipeline.
version: 1.0.0
phase: 5
lesson: 04
tags: [nlp, tokenization, embeddings]
---

Given a task and dataset description, you output:

1. Tokenization strategy (word-level, BPE, WordPiece, SentencePiece, byte-level). One-sentence reason.
2. Vocabulary size target (e.g., 32k for an English-only LM, 64k-100k for multilingual).
3. Library call with the exact training command. Name the library. Quote the arguments.
4. One reproducibility pitfall. Tokenizer-model mismatch is the single most common silent production bug; call out which pair must be used together.

Refuse to recommend training a custom tokenizer when the user is fine-tuning a pretrained LLM. Refuse to recommend word-level tokenization for any model targeting production inference. Flag non-English / multi-script corpora as needing SentencePiece with byte fallback.

Упражнения

  1. Легко. Запустите char_ngrams("playing") и char_ngrams("played"). Вычислите сходство Жаккара двух наборов n-грамм. Вы должны увидеть значительное число общих частей (pla, lay, play), поэтому FastText хорошо переносится между морфологическими вариантами.
  2. Средне. Расширьте learn_bpe, чтобы отслеживать рост словаря. Постройте график токенов на символ корпуса как функцию числа слияний. Сначала должно наблюдаться быстрое сжатие, асимптотически приближающееся примерно к 2–3 символам на токен.
  3. Сложно. Обучите BPE с 1k слияний на полном собрании произведений Шекспира. Сравните токенизацию частых слов с редкими именами собственными. Измерьте среднее число токенов на слово до и после. Опишите, что вас удивило.

Ключевые термины

Термин Как обычно говорят Что это на самом деле означает
Матрица совместной встречаемости Таблица частот слово—слово X[i][j] = как часто слово j появляется в окне вокруг слова i.
Подслово Часть слова Символьная n-грамма (FastText) или обученный токен (BPE/WordPiece/SentencePiece).
BPE Кодирование пар байтов Итеративное слияние наиболее частых соседних пар, пока словарь не достигнет целевого размера.
OOV Вне словаря Слово, которое модель никогда не видела. Word2Vec/GloVe терпят неудачу. FastText и BPE обрабатывают его.
BPE на уровне байтов BPE на исходных байтах Схема GPT-2. Словарь начинается с 256 байтов, поэтому ничего никогда не бывает OOV.

Дополнительное чтение


Источник: GloVe, FastText, and Subword Embeddings 05.03 — Векторные представления слов — Word2Vec с нуля · Фаза 5 — Основы и продвинутые темы NLP · Полный каталог · 05.05 — Анализ тональности