中文翻译暂不可用,显示俄语原文。

Как работают современные tokenizers (BPE, Unigram, SentencePiece) и их ограничения?

Краткий тезис

Современные токенизаторы преобразуют текст в последовательность токенов, понятных LLM. BPE (Byte Pair Encoding) итеративно сливает самые частые пары символов, Unigram использует вероятностную модель для выбора лучшей сегментации, а SentencePiece работает напрямую с сырыми байтами, не требуя предварительной токенизации на слова. Главные ограничения — неэффективная обработка чисел, пробелов, редких символов и недетерминизм при декодировании, что может приводить к неожиданным ошибкам в генерации.

1. Зачем нужна токенизация?

Токенизация — это разбиение текста на минимальные единицы (токены), которые модель может обработать. LLM работают с фиксированным словарём токенов, каждый токен имеет свой embedding (векторное представление). Без токенизации модель не может понять текст.

Ранние подходы (word-level) имели огромные словари и не справлялись с незнакомыми словами (OOV — out-of-vocabulary). Символьный уровень давал слишком длинные последовательности. Современные subword-токенизаторы находят компромисс: частые слова остаются целыми, редкие разбиваются на подслова.

2. BPE (Byte Pair Encoding)

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

Алгоритм

  1. Начать с словаря всех уникальных символов (байтов) в корпусе.
  2. Подсчитать частоты всех пар соседних символов/токенов.
  3. Найти самую частую пару и слить её в новый токен.
  4. Повторять шаги 2–3, пока не достигнут заданный размер словаря.

Пример на Python (упрощённо):

from collections import Counter
import re

def bpe(corpus, vocab_size):
    # Инициализация: словарь из символов
    vocab = set()
    for word in corpus:
        vocab.update(word)
    vocab = {ch: i for i, ch in enumerate(vocab)}
    # Разбиваем слова на символы
    words = [list(w) for w in corpus]
    while len(vocab) < vocab_size:
        # Считаем пары
        pairs = Counter()
        for word in words:
            for i in range(len(word)-1):
                pairs[(word[i], word[i+1])] += 1
        if not pairs:
            break
        # Самая частая пара
        best_pair = max(pairs, key=pairs.get)
        new_token = ''.join(best_pair)
        vocab[new_token] = len(vocab)
        # Сливаем пару во всех словах
        new_words = []
        for word in words:
            new_word = []
            i = 0
            while i < len(word):
                if i < len(word)-1 and (word[i], word[i+1]) == best_pair:
                    new_word.append(new_token)
                    i += 2
                else:
                    new_word.append(word[i])
                    i += 1
            new_words.append(new_word)
        words = new_words
    return vocab

# Пример: корпус ["low", "lower", "newest"]
corpus = ["low", "lower", "newest"]
vocab = bpe(corpus, vocab_size=20)
print(vocab)

Плюсы прост, эффективен, хорошо работает для большинства языков. Минусы не гарантирует оптимальную сегментацию, может создавать токены, которые редко встречаются.

3. Unigram Language Model

Unigram — вероятностный подход, используемый в SentencePiece (как опция) и некоторых моделях (ALBERT, XLNet). В отличие от BPE, он не сливает пары, а выбирает лучшую сегментацию на основе вероятности.

Идея

  • Каждый токен имеет вероятность (оценивается по корпусу).
  • Для данного слова выбирается сегментация, максимизирующая сумму логарифмов вероятностей токенов.
  • Обучение: итеративно удаляются токены с наименьшей вероятностью (loss-based pruning).

Алгоритм обучения

  1. Начать с большого словаря (все возможные подслова из корпуса).
  2. Вычислить вероятности токенов (MLE).
  3. Для каждого токена оценить loss (ухудшение likelihood при его удалении).
  4. Удалить токены с наименьшим loss (обычно 10–20% за итерацию).
  5. Повторять, пока словарь не уменьшится до нужного размера.

Пример сегментации Слово "unhappiness" может быть разбито как ["un", "happiness"] или ["un", "happi", "ness"]. Unigram выберет вариант с наибольшей вероятностью.

Плюсы более гибкий, чем BPE, может давать более естественные разбиения. Минусы сложнее в реализации, требует больше вычислений при обучении.

4. SentencePiece

SentencePiece — библиотека от Google, которая реализует и BPE, и Unigram, но с ключевым отличием: она работает с сырыми байтами (raw bytes), а не с предварительно токенизированными словами.

Особенности

  • Не требует pre-tokenization (разбиения на слова по пробелам). Это важно для языков без явных разделителей (китайский, японский).
  • Использует Unicode нормализацию (NFKC) по умолчанию.
  • Может работать в режиме BPE или Unigram.
  • Выходные токены — это байтовые последовательности, которые могут быть декодированы обратно в текст.

Пример использования

import sentencepiece as spm

# Обучение модели на корпусе
spm.SentencePieceTrainer.train(input='corpus.txt', model_prefix='m', vocab_size=8000)

# Загрузка и токенизация
sp = spm.SentencePieceProcessor(model_file='m.model')
tokens = sp.encode('Hello world!', out_type=str)
print(tokens)  # ['▁Hello', '▁world', '!']

Символ (underscore) обозначает начало слова (пробел). SentencePiece явно кодирует пробелы как часть токенов, что позволяет восстановить исходный текст без потери информации.

Плюсы универсальность, поддержка многих языков, отсутствие зависимости от пробелов. Минусы токены могут быть длинными (байтовые последовательности), сложность отладки.

5. Сравнительная таблица

ХарактеристикаBPEUnigramSentencePiece
ПринципСлияние частых парВероятностная сегментацияBPE или Unigram на байтах
Pre-tokenizationТребуется (обычно по пробелам)ТребуетсяНе требуется
СловарьФиксированный размерФиксированный размерФиксированный размер
Обработка OOVРазбивает на подсловаРазбивает на подсловаРазбивает на байты
Скорость обученияБыстроМедленнееЗависит от режима
ИспользованиеGPT, BERT, RoBERTaALBERT, XLNetT5, Llama, Gemma

6. Ограничения современных токенизаторов

6.1 Проблема с числами

Числа часто разбиваются на отдельные цифры (например, "123" → ["1", "2", "3"]), что теряет семантику. Модель не видит "123" как единое целое. Это может приводить к ошибкам в арифметике и понимании дат.

6.2 Пробелы и пунктуация

SentencePiece явно кодирует пробелы, но в BPE пробелы часто привязываются к предыдущему токену. Это может вызывать недетерминизм при декодировании (например, "▁Hello" vs "Hello" без пробела).

6.3 Редкие символы и Unicode

Токенизаторы могут плохо обрабатывать редкие символы (эмодзи, математические знаки, иероглифы). Они либо разбиваются на байты (увеличивая длину последовательности), либо попадают в OOV.

6.4 Недетерминизм декодирования

При декодировании (токены → текст) возможны разные варианты из-за неоднозначности границ токенов. Например, токены ["a", "bc"] и ["ab", "c"] могут дать один и тот же текст "abc". Это может приводить к невоспроизводимости.

6.5 Языковой и культурный bias

Словарь токенов формируется на основе корпуса. Если корпус содержит много английского текста, токены для других языков будут длиннее и менее эффективны. Это увеличивает стоимость инференса для носителей редких языков.

6.6 Влияние на длину контекста

Разные токенизаторы дают разную длину последовательности для одного и того же текста. Например, китайский текст может занимать в 2–3 раза больше токенов, чем английский, что сокращает эффективное контекстное окно модели.

7. Как токенизация влияет на LLM

  • Качество генерации: плохая токенизация (например, разбиение чисел) ухудшает способность модели к рассуждению.
  • Perplexity: метрика зависит от токенизатора. Сравнивать perplexity разных моделей можно только при одинаковом токенизаторе.
  • Стоимость: чем больше токенов, тем дороже инференс (пропорционально длине последовательности).
  • Fine-tuning: при дообучении важно использовать тот же токенизатор, что и при предобучении, иначе эмбеддинги будут несовместимы.

8. Современные альтернативы

  • Byte-level tokenization (ByT5): работает с отдельными байтами, полностью избегает OOV, но последовательности становятся длиннее.
  • MegaByte: использует патчи байтов для ускорения.
  • Мультимодальные токенизаторы (Image tokenizers для Vision-Language моделей) — отдельная область.

9. Пет-проект для закрепления

Задача Реализовать BPE с нуля и сравнить его поведение с SentencePiece на небольшом корпусе.

Инструменты Python, библиотека sentencepiece, collections.Counter.

Шаги:

  1. Собрать корпус из 100–200 предложений на разных языках (русский, английский, китайский).
  2. Реализовать BPE (как в разделе 2) с размером словаря 500.
  3. Обучить SentencePiece на том же корпусе с размером словаря 500 (режим BPE).
  4. Для 10 тестовых предложений вывести токены обоих токенизаторов.
  5. Посчитать среднюю длину последовательности (в токенах) для каждого языка.
  6. Проанализировать, как токенизируются числа и редкие символы.

Ожидаемый результат

  • Вы увидите, что SentencePiece даёт более стабильные результаты для языков без пробелов.
  • BPE может разбивать числа по цифрам, а SentencePiece — оставлять их целыми, если они часты в корпусе.
  • Вы получите практическое понимание ограничений каждого подхода.

10. Связь с другими вопросами

ВопросТема
283Как работают эмбеддинги и почему они важны для LLM?
285Что такое контекстное окно и как оно связано с токенизацией?
286Как устроена архитектура Transformer?
287Что такое механизм attention и как он зависит от токенов?
288Как обучают LLM и как токенизация влияет на loss?
289Что такое perplexity и как её интерпретировать?

Навигация