Что такое SentencePiece: токенизатор без словаря слов — это библиотека токенизации и детокенизации текста, которая обучается прямо на исходных предложениях и работает с текстом как с последовательностью символов Unicode. В документации проекта на GitHub она описана как быстрый и лёгкий инструмент для систем генерации текста, где размер словаря задаётся до обучения. Разберём, как она устроена и что от неё ждать на практике.
Библиотека, BPE и Unigram: где что лежит
SentencePiece — это библиотека, внутри которой работают два алгоритма сегментации на подcлова: byte-pair encoding (BPE) и униграммная языковая модель. В оригинальной работе Таку Кудо и Джона Ричардсона, опубликованной в материалах EMNLP 2018, сказано, что SentencePiece реализует оба алгоритма с расширением в виде прямого обучения на исходных предложениях. Это разделение важно для практики: вы выбираете алгоритм параметром тренера, а библиотека берёт на себя нормализацию, обучение, кодирование и декодирование.
BPE идёт снизу вверх. По документации Hugging Face, BPE продолжает изучать правила слияния, пока не достигнет целевого размера словаря, который равен размеру базового словаря плюс число слияний. Базой служат отдельные символы; на каждом шаге самая частая пара соседних единиц склеивается в новую. При обычном кодировании без семплирования фиксированная модель применяет уже обученные правила слияния.
Unigram идёт в обратную сторону. В курсе Hugging Face по LLM сказано, что Unigram стартует с большого словаря и удаляет из него токены, пока не дойдёт до нужного размера. На каждом шаге алгоритм считает потерю на корпусе при текущем словаре, оценивает, насколько вырастет потеря при удалении каждого символа, и ищет те, что дают наименьший прирост. Удаляется не один символ, а доля с наименьшим приростом потери — гиперпараметр обычно равен 10 или 20 процентам, и процесс повторяется до достижения целевого размера. В документации Transformers отмечено, что Unigram выбирает сегментацию с наибольшей вероятностью и, в отличие от детерминированного BPE, может сэмплировать разные варианты во время обучения.
| Свойство | BPE | Unigram |
|---|---|---|
| Направление | слияние снизу вверх | удаление сверху вниз |
| База | символы и правила слияния | большой набор кандидатов |
| Итог | детерминированная сегментация | вероятностная, допускает сэмплирование |
| Размер словаря | база плюс число слияний | задаётся напрямую |
Обычное кодирование фиксированной моделью воспроизводимо для обоих алгоритмов. Семплирование включается отдельно: SentencePiece поддерживает его для Unigram и BPE-Dropout для BPE.
Обучение на исходном тексте без словарного разбиения
Ключевое отличие SentencePiece от более ранних инструментов — обучение прямо на сырых предложениях. В документации проекта на GitHub указано, что библиотека обучается непосредственно на исходных предложениях и трактует входной текст как последовательность символов Unicode, что даёт сквозной и языконезависимый конвейер. В работе 2018 года та же мысль сформулирована так: существующие инструменты сегментации предполагают, что вход уже разбит на слова, а SentencePiece умеет обучать модели подcлов прямо на сырых предложениях.
Это снимает зависимость от внешних предтокенизаторов вроде Moses, MeCab или KyTea. В документации проекта отмечено, что такие предтокенизаторы требуют языковых правил, а SentencePiece эффективен для языков без явных границ слов — китайского, японского и тайского. В документации Transformers та же мысль: стандартные BPE и Unigram предполагают, что слова разделены пробелами, что плохо работает для китайского и японского.
Порядок работы выглядит так. Вы готовите текстовый файл, где каждая строка — одно предложение. Запускаете тренер с указанием входного файла, префикса модели и целевого размера словаря. Тренер нормализует корпус, обучает модель сегментации и сохраняет два файла. Дальше вы загружаете модель в процессор и вызываете кодирование и декодирование.
Полный учебный train.txt:
я читаю
ты читаешь
Это условный вход; модель на нём не обучалась. После обучения вы получите модель, которая сегментирует текст на части. Для строки «я читаю» учебная явно заданная сегментация выглядит как [▁я, ▁чи, таю], а декодирование возвращает «я читаю». Сегментация задана вручную для иллюстрации маркера пробела.
Маркер пробела и части слова на учебной сегментации
Пробел в SentencePiece — обычный символ, который кодируется метасимволом. Библиотека экранирует пробел метасимволом ▁ (U+2581), затем разбивает текст на части. Ниже используется только явно заданная учебная сегментация.
Разберём учебную сегментацию «я читаю» → [▁я, ▁чи, таю]. Здесь ▁ отмечает начало слова: ▁я — это «я» с предшествующим пробелом, ▁чи — начало следующего слова, таю — продолжение того же слова. Декодирование собирает части обратно и даёт «я читаю». Такая запись показывает, что маркер пробела сохраняется в последовательности частей.
Для декодирования в SentencePiece предусмотрен собственный декодер. В работе 2018 года он описан как обратная операция кодировщика: Decode(Encode(Normalize(text))) = Normalize(text). Этот принцип назван lossless tokenization — вся информация для воспроизведения нормализованного текста сохраняется в выходе кодировщика. Наивная склейка токенов с заменой подчёркивания на пробел не учитывает dummy-prefix и служебные символы, поэтому для реальных задач используйте штатный декодер библиотеки.
Что это даёт на практике: вы можете восстановить нормализованный вход из последовательности токенов без языковых правил. Ограничение в том, что восстанавливается именно нормализованный текст. Если нормализатор изменил вход, декодирование вернёт изменённую версию, и это ожидаемое поведение.
Назначение tokenizer.model и tokenizer.vocab
После обучения вы получаете два файла. tokenizer.model хранит обученные правила: нормализацию, отображение словаря и модель сегментации. В документации проекта на GitHub сказано, что файл модели содержит все правила нормализации, отображение словаря и модель сегментации, и одинаковые результаты токенизации гарантированы в любом окружении при использовании одного и того же файла модели. В работе 2018 года модель описана как самодостаточная: она включает не только словарь и параметры сегментации, но и предкомпилированный преобразователь для нормализации символов, а поведение определяется только файлом модели.
tokenizer.vocab — список частей словаря для просмотра и проверки состава. Идентификаторы получайте через API загруженной модели. В работе 2018 года отмечено, что SentencePiece управляет отображением словаря в идентификаторы, чтобы напрямую переводить текст в последовательность ID и обратно, а размер словаря задаётся флагом —vocab_size при обучении.
| Артефакт | Что содержит | Зачем нужен |
|---|---|---|
| tokenizer.model | правила нормализации, отображение словаря, модель сегментации | загрузка в процессор, воспроизводимая токенизация |
| tokenizer.vocab | список частей словаря | чтение, проверка состава словаря |
Практическое правило: распространяйте tokenizer.model вместе с моделью. Именно он обеспечивает одинаковую токенизацию в C++, Python, Go и других окружениях. Файл vocab полезен для инспекции, но для загрузки процессора нужен model.
Encode, IDs и decode без придуманных ID
Работа с обученной моделью сводится к трём операциям. Кодирование возвращает части: в работе 2018 года показан вызов EncodeAsPieces, который для «Hello world.» даёт последовательность частей. Кодирование в идентификаторы возвращает числа: EncodeAsIds для той же строки даёт последовательность ID. Декодирование принимает ID и возвращает текст: DecodeIds собирает строку обратно.
В документации TensorFlow для SentencepieceTokenizer перечислены методы, которые стоит знать. tokenize разбивает тензор строк UTF-8 на токены. detokenize собирает токены обратно в текст. id_to_string переводит идентификатор словаря в токен, а string_to_id — токен в идентификатор; эти методы полезны для специальных токенов, чей ID не определяется обычной токенизацией. vocab_size возвращает размер словаря, переданного при инициализации. tokenize_with_offsets возвращает кортеж из токенов и байтовых смещений начала и конца каждого токена.
Здесь важно удержаться от соблазна придумать конкретные ID. Числовые ID зависят от конкретной модели. Чужие значения не являются результатом сегментации нашего учебного ввода. Реальные ID получают через API своей обученной модели.
Функция ниже показывает обучение с переданным размером словаря. Его нужно подобрать под корпус до вызова train_tokenizer. После успешного обучения загрузите tokenizer.model и выполните encode/decode:
import sentencepiece as spm
def train_tokenizer(vocab_size):
spm.SentencePieceTrainer.Train(
input="train.txt",
model_prefix="tokenizer",
vocab_size=vocab_size,
)
sp = spm.SentencePieceProcessor()
sp.Load("tokenizer.model")
pieces = sp.EncodeAsPieces("я читаю")
ids = sp.EncodeAsIds("я читаю")
text = sp.DecodeIds(ids)
Этот код показывает последовательность train → load → encode → decode. Значения ID здесь не печатаются намеренно: их нужно получить из своей обученной модели. Если вы хотите увидеть части, выведите pieces; если хотите увидеть числа, выведите ids, но не подставляйте их в другой проект.
Нормализация и предел побайтовой обратимости
Нормализация — отдельный этап, который проходит до сегментации. В работе 2018 года сказано, что по умолчанию SentencePiece нормализует вход с помощью Unicode NFKC, а правила задаются флагом —normalization_rule_name=nfkc при обучении. Там же отмечено, что нормализация реализована как отображение строка-в-строку с выбором самого длинного совпадения слева, а правила компилируются в конечный преобразователь для эффективной работы. Пользовательские правила можно задать TSV-файлом через флаг —normalization_rule_tsv.
Принцип lossless tokenization относится к нормализованному входу. В работе 2018 года формула записана как Decode(Encode(Normalize(text))) = Normalize(text). Следовательно, декодирование воспроизводит нормализованный текст. Если нормализатор свёл несколько пробелов к одному, декодирование вернёт строку с одним пробелом.
Учебный контроль нормализации: если конфигурация сводит несколько пробелов к одному, decode(encode(«я читаю»)) может дать «я читаю». Эталон зависит от настроек нормализатора. Покажите исходный и нормализованный текст рядом и объясните, что отличие само по себе не означает сбой токенизатора. Модель в этом примере не обучалась, а значения условны.
Практический вывод: перед обучением зафиксируйте правила нормализации и сохраните их вместе с моделью. Тогда поведение будет воспроизводимым, а расхождения в декодировании получат объяснение.
Unknown, byte fallback и зависимость от конфигурации
Поведение на незнакомых символах зависит от настроек модели. В документации PyPI для пакета sentencepiece сказано, что при включённом byte fallback неизвестные символы, например эмодзи или неподдерживаемые письменности, разлагаются в последовательность сырых байтовых токенов UTF-8. Там же отмечено, что первые N-1 байтовых токенов в последовательности fallback получают нулевую ширину и указывают на начало символа, что позволяет аккуратно резать и восстанавливать текст без появления некорректных последовательностей байтов Unicode.
Специальные символы получают зарезервированные идентификаторы. В работе 2018 года сказано, что SentencePiece резервирует ID для специальных метасимволов: неизвестного (), конца предложения () и дополнения (
Отсюда практический вывод: одинаковый текст в двух моделях может дать разные результаты, если различаются настройки. Проверяйте три вещи: включён ли byte fallback, какие ID назначены специальным символам и какие правила нормализации применены.
| Ситуация | Что происходит при byte fallback | Что проверить |
|---|---|---|
| Эмодзи или редкий символ | разложение в байтовые токены UTF-8 | включён ли fallback в модели |
| Неизвестный символ без fallback | подстановка |
назначен ли ID для |
| Специальные токены | зарезервированные ID | флаги при обучении |
Частые вопросы
Чем SentencePiece отличается от BPE?
SentencePiece — это библиотека, внутри которой BPE работает как один из алгоритмов. BPE изучает правила слияния частых пар и даёт детерминированную сегментацию. Библиотека добавляет нормализацию, обучение на сырых предложениях, управление словарём и идентификаторами, а также собственный декодер.
Можно ли обучить SentencePiece на своём тексте?
Да. Вы готовите текстовый файл со строками предложений и запускаете тренер с указанием входного файла, префикса модели и размера словаря. На выходе получаете tokenizer.model и tokenizer.vocab. Обучение идёт прямо на исходном тексте без предварительного разбиения на слова.
Почему декодирование вернуло текст с одним пробелом вместо двух?
Сработала нормализация. По умолчанию применяется Unicode NFKC, и правила могут сводить несколько пробелов к одному. Принцип lossless tokenization воспроизводит нормализованный вход, поэтому такое отличие в пробелах может соответствовать настройкам нормализатора.
Что делать с эмодзи и редкими символами?
Включите byte fallback в конфигурации модели. Тогда неизвестные символы разложатся в последовательность сырых байтовых токенов UTF-8, и текст можно будет аккуратно восстановить. Без fallback такие символы заменяются на токен
Как использовать SentencePiece в Python?
Установите пакет через pip, загрузите tokenizer.model в SentencePieceProcessor и вызывайте EncodeAsPieces, EncodeAsIds и DecodeIds. Для работы с TensorFlow есть отдельный класс SentencepieceTokenizer с методами tokenize, detokenize, id_to_string и string_to_id. Идентификаторы берите из своей модели.
