Содержание курса 03 / 37

03 / 37 · Основы работы с текстом

Токенизация

Почему не годятся ни слова, ни символы; как byte-pair encoding выучивает словарь подслов, сливая частые пары, и почему это жадное сжатие; как обученный токенизатор кодирует новый текст; чем отличаются WordPiece и SentencePiece; и какой компромисс в выборе размера словаря наследует каждая следующая модель.

Что нужно знать

Текст как данные

На этой странице 10

Предыдущая заметка закончилась двумя проблемами словных единиц: слово, которого не было в обучающем корпусе, может стать лишь [UNK], а большинство типов слов в словаре с длинным хвостом встречается слишком редко, чтобы выучить вектор для каждого. В этой заметке мы выводим стандартное решение — токенизацию по подсловам (subword tokenization), — отталкиваясь от двух крайностей, между которыми она лежит; основной алгоритм — byte-pair encoding.

Обозначения. Токен — единица словаря VV размера ∣V∣|V|; токенизатор переводит строку в токены и обратно. Корпус, разбитый на слова, описывается счётчиком c(w)c(w) каждого типа слова (word type) ww. Сегментация seg(w)=(t1,…,tm)\mathrm{seg}(w) = (t_1, \dots, t_m) — последовательность токенов, конкатенация которых равна ww. Для единиц a,ba, b величина c(a)c(a) считает вхождения aa, а c(a,b)c(a, b) — вхождения aa, за которым непосредственно следует bb; оба счётчика взвешены частотами слов. Длина корпуса в токенах — L=∑wc(w) ∣seg(w)∣L = \sum_w c(w)\,|\mathrm{seg}(w)| (после kk слияний пишем LkL_k), а L/∑wc(w)L / \sum_w c(w) — среднее число токенов на слово. Модель видит последовательность из TT идентификаторов токенов и переводит каждый в вектор ширины dd.

Маршрут чтения. Ядро заметки — отправная точка, разобранный пример BPE, кодирование нового текста, что определяет число слияний и специальные токены. Аргумент о сжатии, оценка WordPiece (поточечная взаимная информация) и униграммная модель (обучается EM-алгоритмом, декодируется динамическим программированием) дают глубину, и при первом чтении их можно пропустить.

Отправная точка: слова или символы#

Каждая следующая модель получает целочисленные идентификаторы x1,…,xTx_1, \dots, x_T, где xi∈{0,…,∣V∣−1}x_i \in \{0, \dots, |V| - 1\}, поэтому токенизатор фиксирует два числа, которые тянут в противоположные стороны: ∣V∣|V| и длину TT данного текста.

Слова. Последовательности короткие, и каждый токен несёт смысл, но словарь открыт: каждая опечатка, имя, число и словоформа — новый тип, поэтому в отложенном (held-out) тексте всегда найдутся слова, которые схлопнутся в [UNK]. Родственные формы вроде sing, sings и singer получают не связанные между собой идентификаторы, а стемминг из предыдущей заметки латает это для каждого языка отдельно, выбрасывая информацию. Сильнее всего страдают флективные языки: у русского существительного до двенадцати форм падежа и числа.

Символы. Словарь крошечный и закрытый, поэтому в пределах алфавита неизвестных токенов нет. Но средняя длина английского слова около пяти букв, поэтому TT вырастает примерно в пять раз, за что каждая следующая модель платит вычислениями (квадратично — в self-attention), а отдельный символ почти не несёт смысла.

Уровень словУровень символов
СловарьОткрытый, 10510^5–10610^6Закрытый, 10210^2–10310^3
Неизвестные токеныЧастоНикогда (в пределах алфавита)
Длина последовательностиКороткаяПримерно в 5 раз длиннее
Смысл на токенВысокийПочти никакого
Рисунок

Одна фраза, четыре словаря

Одна и та же фраза в виде символов, двух словарей BPE и словЧетыре ряда прямоугольников-токенов для фразы the decoder learns tokenization. Символы: 28 токенов при словаре из 26. BPE со 100 слияниями: 13 токенов, словарь 126. BPE с 2000 слияниями: 7 токенов, словарь 2026; tokenization превращается в to, k, en, ization. Слова: 4 токена, словарь 2790, и tokenization превращается в неизвестный токен.Символы|V| = 26 · T = 28 · отложенный текст: 4.92 токена на словоthedecoderlearnstokenizationBPE, 100 слияний|V| = 126 · T = 13 · отложенный текст: 2.84 токена на словоthedecoderlearnstokenizationBPE, 2000 слияний|V| = 2026 · T = 7 · отложенный текст: 1.32 токена на словоthedecoderlearnstokenizationСлова|V| = 2790 · T = 4 · отложенный текст: 9.4% слов — [UNK]thedecoderlearns[UNK]Одна и та же фраза в виде символов, двух словарей BPE и словЧетыре ряда прямоугольников-токенов для фразы the decoder learns tokenization. Символы: 28 токенов при словаре из 26. BPE со 100 слияниями: 13 токенов, словарь 126. BPE с 2000 слияниями: 7 токенов, словарь 2026; tokenization превращается в to, k, en, ization. Слова: 4 токена, словарь 2790, и tokenization превращается в неизвестный токен.Символы|V| = 26 · T = 28 · отложенный текст: 4.92 на словоthedecoderlearnstokenizationBPE, 100 слияний|V| = 126 · T = 13 · отложенный текст: 2.84 на словоthedecoderlearnstokenizationBPE, 2000 слияний|V| = 2026 · T = 7 · отложенный текст: 1.32 на словоthedecoderlearnstokenizationСлова|V| = 2790 · T = 4 · отложенный текст: 9.4% [UNK]thedecoderlearns[UNK]
Фраза «the decoder learns tokenization», разрезанная на токены словарями возрастающего размера; все они выучены на одном и том же английском обучающем тексте (26 990 словоупотреблений). Каждый прямоугольник — один токен; синие прямоугольники — многосимвольные токены, выученные BPE. T — число токенов во фразе, а значение для отложенного текста в каждой подписи — среднее число токенов на слово на другом тексте из того же источника. Символы дают длинные последовательности; словарь слов даёт один токен на слово, но в нём нет записи для «tokenization»: это слово ни разу не встретилось при обучении, и ту же участь разделяют 9.4% слов отложенного текста. BPE находится посередине: при 2000 слияниях частые слова остаются целыми, а слово, не встречавшееся при обучении, собирается из четырёх известных кусков.

Byte-pair encoding#

Byte-pair encoding (BPE) появился как алгоритм сжатия, который заменяет самую частую пару соседних байтов неиспользованным байтом (Gage, 1994); Sennrich, Haddow and Birch (2016) применили этот цикл к символам внутри слов.

Обучение начинается с нормализованного текста, предварительно токенизированного (pre-tokenization) на слова, чтобы ни один токен не пересекал границу слова; важны только счётчики слов:

  1. Разбиваем каждое слово на символы; это seg0\mathrm{seg}_0. Начальный словарь — алфавит.

  2. На kk-м шаге слияния (merge) считаем каждую пару соседних единиц на сегментациях segk−1\mathrm{seg}_{k-1}, взвешивая по частоте слова:

    ck(a,b)=∑wc(w)  #{i:(ti,ti+1)=(a,b) в segk−1(w)}.c_k(a, b) = \sum_w c(w)\; \#\big\{ i : (t_i, t_{i+1}) = (a, b) \text{ в } \mathrm{seg}_{k-1}(w) \big\}.
  3. Выбираем самую частую пару (a∗,b∗)=arg⁡max⁡(a,b)ck(a,b)(a^*, b^*) = \arg\max_{(a,b)} c_k(a, b) с фиксированным правилом для равных счётчиков.

  4. Добавляем a∗b∗a^*b^* в словарь, записываем правило a∗+b∗→a∗b∗a^* + b^* \to a^*b^* под номером kk и заменяем каждое вхождение пары, проходя слева направо; получаем segk\mathrm{seg}_k.

  5. Повторяем, пока словарь не достигнет целевого размера или пока ни одна пара не будет встречаться больше одного раза.

Результат обучения — словарь и упорядоченный список слияний; порядок — часть модели. Sennrich et al. (2016) также добавляют маркер конца слова, чтобы можно было восстановить пробелы.

Разобранный пример#

Возьмём счётчики слов sing 6, singer 3, sings 4, ring 5, rings 2, song 3, для наглядности без маркера конца слова. Это 23 словоупотребления и L0=4⋅6+6⋅3+5⋅4+4⋅5+5⋅2+4⋅3=104L_0 = 4 \cdot 6 + 6 \cdot 3 + 5 \cdot 4 + 4 \cdot 5 + 5 \cdot 2 + 4 \cdot 3 = 104 символа над алфавитом {e,g,i,n,o,r,s}\{e, g, i, n, o, r, s\}, так что в начале ∣V∣=7|V| = 7.

Каждое слово содержит n g, поэтому c1(n,g)=6+3+4+5+2+3=23c_1(n, g) = 6 + 3 + 4 + 5 + 2 + 3 = 23. Каждое слово, кроме song, содержит i n, поэтому c1(i,n)=23−3=20c_1(i, n) = 23 - 3 = 20. Остальные пары реже: s i 13, r i 7, g s 6, а e r, g e, o n, s o — по 3. Первое слияние — n + g → ng, оно сокращает корпус со 104 до 81 токена:

Рисунок

Восемь слияний BPE на корпусе из шести слов

Трассировка обучения BPEТаблица, по строке на каждый шаг слияния от 0 до 8. Столбцы: правило слияния, его счётчик, длина корпуса в токенах и сегментации шести типов слов. Слияния: n плюс g со счётчиком 23; i плюс ng со счётчиком 20; s плюс ing со счётчиком 13; r плюс ing со счётчиком 7; sing плюс s со счётчиком 4; e плюс r со счётчиком 3; o плюс ng со счётчиком 3; s плюс ong со счётчиком 3. Длина корпуса уменьшается от 104 до 28.kслияниеcLsing ×6singer ×3sings ×4ring ×5rings ×2song ×30—104s i n gs i n g e rs i n g sr i n gr i n g ss o n g1n + g → ng2381s i ngs i ng e rs i ng sr i ngr i ng ss o ng2i + ng → ing2061s ings ing e rs ing sr ingr ing ss o ng3s + ing → sing1348singsing e rsing sr ingr ing ss o ng4r + ing → ring741singsing e rsing sringring ss o ng5sing + s → sings437singsing e rsingsringring ss o ng6e + r → er334singsing ersingsringring ss o ng7o + ng → ong331singsing ersingsringring ss ong8s + ong → song328singsing ersingsringring ssongТрассировка обучения BPEДля каждого шага слияния от 0 до 8: правило слияния, его счётчик, длина корпуса в токенах и сегментации шести типов слов в две строки по три. Длина корпуса уменьшается от 104 до 28.sing ×6singer ×3sings ×4ring ×5rings ×2song ×30—L = 104s i n gs i n g e rs i n g sr i n gr i n g ss o n g1n + g → ngc = 23 · L = 81s i ngs i ng e rs i ng sr i ngr i ng ss o ng2i + ng → ingc = 20 · L = 61s ings ing e rs ing sr ingr ing ss o ng3s + ing → singc = 13 · L = 48singsing e rsing sr ingr ing ss o ng4r + ing → ringc = 7 · L = 41singsing e rsing sringring ss o ng5sing + s → singsc = 4 · L = 37singsing e rsingsringring ss o ng6e + r → erc = 3 · L = 34singsing ersingsringring ss o ng7o + ng → ongc = 3 · L = 31singsing ersingsringring ss ong8s + ong → songc = 3 · L = 28singsing ersingsringring ssong
Корпус: sing ×6, singer ×3, sings ×4, ring ×5, rings ×2, song ×3 (23 словоупотребления, 104 символа, алфавит из 7 букв). Каждая строка — одно слияние k: пара с наибольшим счётчиком c, взвешенным частотами слов, её объединённый токен, длина корпуса L в токенах после слияния и текущая сегментация каждого типа слова; новый токен выделен синим. Словарь растёт на одну запись за строку, с 7 до 15. В янтарных строках наибольший счётчик был у нескольких пар (4 пары на шаге 6, 3 пары на шаге 7, 2 пары на шаге 8); сливается первая по алфавиту пара. После 8 слияний корпус занимает 28 токенов на 23 слова.

Итоговый словарь — e g i n o r s ng ing sing ring sings er ong song, а корпус — 28 токенов на 23 слова: 1.22 токена на слово против 4.52 символа на слово. Решает только частота: sings (4 вхождения) стало одним токеном, а rings (2) осталось ring s. Общие куски появляются сами (ing, sing er). Когда счётчики становятся маленькими, равенства между ними встречаются часто; подходит любое детерминированное правило их разрешения, а другое правило на шаге 6 дало бы другой, но столь же допустимый словарь.

Почему самая частая пара#

Дополнительный материал: почему BPE — жадный компрессор своего обучающего корпуса. Остальная часть заметки на него не опирается.

Каждое вхождение слитой пары превращает два токена в один, поэтому

Lk=Lk−1−ck(a∗,b∗).L_k = L_{k-1} - c_k(a^*, b^*).

Тождество требует, чтобы вхождения не перекрывались, а это нарушается только при a=ba = b: в e e e пара e e посчитана дважды, но слита один раз. В разобранном примере каждое уменьшение равно счётчику: 104−23=81104 - 23 = 81, 81−20=6181 - 20 = 61, и так до 31−3=2831 - 3 = 28.

Рисунок

Каждое слияние укорачивает корпус на счётчик слитой пары

Длина корпуса после каждого слияния BPEСтолбчатая диаграмма длины корпуса по шагам слияния. Высоты столбцов: 104, 81, 61, 48, 41, 37, 34, 31, 28. Красная шапка на каждом столбце показывает токены, убранные этим слиянием; их число равно его счётчику. Пунктирная линия на уровне 104 отмечает символы, пунктирная линия на уровне 23 — один токен на слово. Под каждым столбцом — новый токен и размер словаря, который растёт от 7 до 15.050100L0104—7−23181ng8−20261ing9−13348sing10−7441ring11−4537sings12−3634er13−3731ong14−3828song15kLновый|V|символы (104)один токенна слово (23)Длина корпуса после каждого слияния BPEСтолбчатая диаграмма длины корпуса по шагам слияния. Высоты столбцов: 104, 81, 61, 48, 41, 37, 34, 31, 28. Красная шапка на каждом столбце показывает токены, убранные этим слиянием; их число равно его счётчику. Пунктирная линия на уровне 104 отмечает символы, пунктирная линия на уровне 23 — один токен на слово. Под каждым столбцом — новый токен и размер словаря, который растёт от 7 до 15.050100L01047−23181ng8−20261ing9−13348sing10−7441ring11−4537sings12−3634er13−3731ong14−3828song15kLновый|V|10423
Длина корпуса L в токенах после k слияний из прогона на предыдущем рисунке. Красная шапка на каждом столбце — то, что убрало слияние: каждое вхождение пары превращает два токена в один, поэтому L падает ровно на счётчик пары c (23, 20, 13, 7, 4, 3, 3, 3). Первые слияния убирают больше всего; более поздние — по три токена. Под осью: длина L после шага k, добавленный токен и размер словаря |V|. Пунктирные линии отмечают две крайности: 104 токена в виде символов и 23 — по одному токену на слово.

Итак, BPE — жадный компрессор: каждый шаг добавляет токен, который сильнее всего укорачивает обучающий корпус. Жадность не означает оптимальности: раннее слияние может забрать символы, которые понадобились бы более выгодному позднему слиянию.

Кодирование нового текста#

Обученный токенизатор BPE кодирует новое слово, повторяя слияния по порядку на его символах. Реализации вроде GPT-2 вместо этого многократно сливают соседнюю пару с наименьшим номером правила. Обе процедуры дают одно и то же: слияние создаёт новые соседние пары только вокруг новой единицы abab, а каждое правило с участием abab выучено позже самой abab, поэтому наименьший присутствующий номер никогда не уменьшается и цикл встречает правила в возрастающем порядке. Так слово, которого не было в корпусе, получает осмысленные куски: singers становится sing er s.

Рисунок

Кодирование слов, которых не было в корпусе

Применение выученных слияний к новым словамСлева: слово singers проходит по порядку через 8 правил слияния и превращается в sing, er, s. Справа: sneering превращается в s, n, e, e, ring при повторе слияний, но в s, n, e, er, ing при поиске самого длинного совпадения; king превращается в [UNK], ing с символьной базой и в k, ing с байтовой; rising превращается в r, i, sing.Повтор слияний для «singers»началоs i n g e r s1n + g → ngs i ng e r s2i + ng → ings ing e r s3s + ing → singsing e r s4r + ing → ringнет пары5sing + s → singsнет пары6e + r → ersing er s7o + ng → ongнет пары8s + ong → songнет парыsingersДва кодировщика, один словарь: sneeringслияния по порядкуsneeringсамое длинноесовпадениеsneeringБуква вне базового алфавита: king7 букв[UNK]ing256 байтовkingКуски — не морфемы: risingслияния по порядкуrisingПрименение выученных слияний к новым словамСлева: слово singers проходит по порядку через 8 правил слияния и превращается в sing, er, s. Справа: sneering превращается в s, n, e, e, ring при повторе слияний, но в s, n, e, er, ing при поиске самого длинного совпадения; king превращается в [UNK], ing с символьной базой и в k, ing с байтовой; rising превращается в r, i, sing.Повтор слияний для «singers»началоs i n g e r s1n + g → ngs i ng e r s2i + ng → ings ing e r s3s + ing → singsing e r s4r + ing → ringнет пары5sing + s → singsнет пары6e + r → ersing er s7o + ng → ongнет пары8s + ong → songнет парыsingersДва кодировщика, один словарь: sneeringслияния по порядкуsneeringсамое длинноесовпадениеsneeringБуква вне базового алфавита: king7 букв[UNK]ing256 байтовkingКуски — не морфемы: risingслияния по порядкуrising
Во всех примерах используются 8 слияний и словарь из 15 токенов из разобранного примера. Панель повтора: singers разбивается на символы, и слияния повторяются в том порядке, в котором были выучены; правила 1, 2, 3, 6 срабатывают (полный цвет), остальные не находят подходящей пары (бледные), и результат — sing · er · s. Два кодировщика, один словарь: тот же словарь с жадным поиском самого длинного совпадения слева даёт другую сегментацию sneering, потому что при повторе слияний правило 4 (r + ing) забирает r раньше, чем дело доходит до правила 6 (e + r), а поиск самого длинного совпадения идёт слева и первым берёт er. Буква вне базового алфавита: k нет среди 7 букв обучающего текста, поэтому символьная база вынуждена выдать [UNK]; байтовая база содержит все значения байта и никогда этого не делает. Последняя панель: слияния следуют частоте, а не смыслу, поэтому rising содержит кусок sing.

Три следствия:

  • Способ кодирования — часть словаря. Жадный поиск самого длинного совпадения (greedy longest match) по тем же 15 записям разбивает sneering как s n e er ing; повторение слияний даёт s n e e ring.
  • Неизвестные символы по-прежнему возможны. Символ вне базового алфавита всё равно становится [UNK]. Байтовый BPE (byte-level BPE) (Radford et al., 2019) снимает проблему, беря за основу 256 значений байта UTF-8: 50 257 записей GPT-2 — это 256 байтов, 50 000 слияний и один токен конца текста.
  • Куски — частые строки, а не морфемы. Они часто совпадают с суффиксами (ing, er, s), потому что суффиксы часты, но rising становится r i sing.

Что определяет число слияний#

Число слияний — главный гиперпараметр: ∣V∣=∣алфавит∣+#слияний+#спецтокенов|V| = |\text{алфавит}| + \#\text{слияний} + \#\text{спецтокенов}, и чем больше слияний, тем короче последовательности. У обеих сторон есть цена:

  • Таблица эмбеддингов E∈R∣V∣×dE \in \mathbb R^{|V| \times d} содержит ∣V∣⋅d|V| \cdot d параметров: 38.4 млн при ∣V∣=50 000|V| = 50\,000 и d=768d = 768 и 263 млн у Llama 3.2 1B (128 256 токенов, d=2048d = 2048).
  • Выходной слой вычисляет ∣V∣|V| логитов в каждой позиции, O(∣V∣⋅d)O(|V| \cdot d) работы на токен, и добавляет ещё одну матрицу d×∣V∣d \times |V|, если её веса не связаны с EE (weight tying).
  • Записи поздних слияний редки, поэтому их эмбеддинги видят мало данных.
  • Более длинные последовательности — это TT последовательных шагов рекуррентной модели, O(T2d)O(T^2 d) на слой self-attention и меньше слов в контекстном окне фиксированной длины.
Рисунок

Число токенов на слово падает с ростом словаря

Число токенов на слово в зависимости от размера словаряДве убывающие кривые на логарифмической оси размера словаря от 20 до 4000. Обучающий текст, синяя: 4.995 при 26, 4.679 при 29, 4.193 при 36, 3.823 при 46, 3.584 при 56, 3.269 при 76, 2.832 при 126, 2.38 при 226, 2.119 при 326, 1.807 при 526, 1.614 при 726, 1.43 при 1026, 1.261 при 1526, 1.168 при 2026. Отложенный текст, красная: 4.924 при 26, 4.627 при 29, 4.176 при 36, 3.801 при 46, 3.589 при 56, 3.268 при 76, 2.839 при 126, 2.43 при 226, 2.172 при 326, 1.904 при 526, 1.737 при 726, 1.567 при 1026, 1.414 при 1526, 1.318 при 2026. Начиная примерно с 200 записей красная кривая лежит выше синей. Отдельная точка при размере словаря 2790 и одном токене на слово подписана долей неизвестных слов, 9.4 процента.123453010030010003000размер словаря |V| (логарифмическая шкала)токенов на словослова: 9.4% [UNK]обучающий текст: 1.17 при |V| = 2026отложенный текст: 1.32символы 5.00Число токенов на слово в зависимости от размера словаряДве убывающие кривые на логарифмической оси размера словаря от 20 до 4000. Обучающий текст, синяя: 4.995 при 26, 4.679 при 29, 4.193 при 36, 3.823 при 46, 3.584 при 56, 3.269 при 76, 2.832 при 126, 2.38 при 226, 2.119 при 326, 1.807 при 526, 1.614 при 726, 1.43 при 1026, 1.261 при 1526, 1.168 при 2026. Отложенный текст, красная: 4.924 при 26, 4.627 при 29, 4.176 при 36, 3.801 при 46, 3.589 при 56, 3.268 при 76, 2.839 при 126, 2.43 при 226, 2.172 при 326, 1.904 при 526, 1.737 при 726, 1.567 при 1026, 1.414 при 1526, 1.318 при 2026. Начиная примерно с 200 записей красная кривая лежит выше синей. Отдельная точка при размере словаря 2790 и одном токене на слово подписана долей неизвестных слов, 9.4 процента.123453010030010003000размер словаря |V| (логарифмическая шкала)токенов на словослова: 9.4% [UNK]обучающий текст: 1.17отложенный текст: 1.32символы 5.00
BPE, обученный на прозе 11 заметок курса «Генеративные модели» этого сайта (только буквы в нижнем регистре, 26 990 словоупотреблений, 2790 типов слов) и проверенный на остальных четырёх заметках (8908 слов). По горизонтальной оси — размер словаря |V| = 26 букв + слияния в логарифмической шкале; по вертикальной — среднее число токенов на слово. На обучающем тексте (синяя кривая) оно падает с 5.00 на уровне символов до 1.17 после 2000 слияний; на отложенном тексте (красная) доходит до 1.32, потому что поздние слияния строят целые слова, частые при обучении и отсутствующие в другом тексте. Каждое удвоение |V| даёт всё меньший выигрыш. Отдельная точка на уровне одного токена на слово — словарь из типов слов обучающего текста: ровно один токен на слово, но 9.4% слов отложенного текста в нём нет. Это наше собственное небольшое измерение, иллюстрация, а не бенчмарк.

Каждое удвоение ∣V∣|V| даёт всё меньший выигрыш. Практические словари гораздо больше, чем в этом небольшом эксперименте: 30 522 записи у BERT, 50 257 у GPT-2, 100 000–250 000 у современных многоязычных моделей.

WordPiece и SentencePiece#

WordPiece (Schuster and Nakajima, 2012; Wu et al., 2016) — токенизатор BERT (Devlin et al., 2019). Он выполняет цикл слияний BPE с двумя отличиями.

Первое — критерий слияния. Исходный метод добавляет единицу, которая сильнее всего увеличивает правдоподобие обучающих данных при языковой модели над единицами; широко используемая реконструкция (Hugging Face, LLM Course, ch. 6) ранжирует пары по величине

score(a,b)=c(a,b)c(a) c(b).\text{score}(a, b) = \frac{c(a, b)}{c(a)\,c(b)} .

Если всего NN единиц, а вероятности оцениваются относительными частотами p(a,b)=c(a,b)/Np(a, b) = c(a, b)/N, p(a)=c(a)/Np(a) = c(a)/N, то

score(a,b)=1N⋅p(a,b)p(a) p(b).\text{score}(a, b) = \frac{1}{N} \cdot \frac{p(a, b)}{p(a)\,p(b)} .

С точностью до 1/N1/N это отношение, логарифм которого — поточечная взаимная информация (pointwise mutual information) aa и bb. BPE предпочитает частые пары; WordPiece — связные (cohesive), части которых редко встречаются порознь. BERT помечает каждый неначальный кусок префиксом ##. На корпусе разобранного примера два критерия расходятся с первого шага:

Рисунок

BPE и WordPiece выбирают разные первые слияния

Ранжирование пар по счётчику и по величине WordPieceДва ранжированных списка одних и тех же 9 пар со столбиками. По счётчику: ##n ##g 23, ##i ##n 20, s ##i 13, r ##i 7, ##g ##s 6, ##e ##r 3, ##g ##e 3, ##o ##n 3, s ##o 3. По величине: ##e ##r 0.333, s ##o 0.063, r ##i 0.050, ##n ##g 0.043, ##i ##n 0.043, ##g ##s 0.043, ##g ##e 0.043, ##o ##n 0.043, s ##i 0.041. Линии соединяют две позиции каждой пары; лидер по счётчику опускается на 4-е место по величине, а у лидера по величине наименьший счётчик, 3.BPEсчётчик c(a, b)WordPiecec(a, b) / (c(a) c(b))##n ##g23##i ##n20s ##i13r ##i7##g ##s6##e ##r3##g ##e3##o ##n3s ##o3##e ##r0.333s ##o0.063r ##i0.050##n ##g0.043##i ##n0.043##g ##s0.043##g ##e0.043##o ##n0.043s ##i0.041Ранжирование пар по счётчику и по величине WordPieceДва ранжированных списка одних и тех же 9 пар со столбиками. По счётчику: ##n ##g 23, ##i ##n 20, s ##i 13, r ##i 7, ##g ##s 6, ##e ##r 3, ##g ##e 3, ##o ##n 3, s ##o 3. По величине: ##e ##r 0.333, s ##o 0.063, r ##i 0.050, ##n ##g 0.043, ##i ##n 0.043, ##g ##s 0.043, ##g ##e 0.043, ##o ##n 0.043, s ##i 0.041. Линии соединяют две позиции каждой пары; лидер по счётчику опускается на 4-е место по величине, а у лидера по величине наименьший счётчик, 3.BPEсчётчик c(a, b)WordPiecec(a, b) / (c(a) c(b))##n ##g23##i ##n20s ##i13r ##i7##g ##s6##e ##r3##g ##e3##o ##n3s ##o3##e ##r0.333s ##o0.063r ##i0.050##n ##g0.043##i ##n0.043##g ##s0.043##g ##e0.043##o ##n0.043s ##i0.041
Все пары соседних единиц игрушечного корпуса до первого слияния, записанные в стиле WordPiece: символ, который не начинает слово, несёт ##, так что s в начале слова и ##s внутри слова — разные единицы. Слева — ранжирование по счётчику c(a, b), взвешенному частотами слов, который максимизирует BPE; побеждает ##n ##g со значением 23. Справа — ранжирование по реконструированной величине WordPiece c(a, b) / (c(a) c(b)); побеждает ##e ##r: 3 / (3 · 3) = 0.333, потому что ##e и ##r никогда не встречаются порознь, тогда как для ##n ##g получается 23 / (23 · 23) = 0.043. Линии соединяют одну и ту же пару в двух ранжированиях; пары с равными значениями величины упорядочены по счётчику.

Процедура обучения WordPiece в библиотеке tokenizers игнорирует эту величину и сливает пары по частоте, как BPE.

Второе отличие — кодирование. WordPiece берёт самую длинную запись словаря, совпадающую с началом слова, затем самое длинное совпадение с ## для остатка и так далее; если в какой-то позиции подходящего куска нет, всё слово становится [UNK].

from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("bert-base-uncased")   # |V| = 30,522
tok("Random text to encode").tokens()
# ['[CLS]', 'random', 'text', 'to', 'en', '##code', '[SEP]']

Токенизатор BERT с учётом регистра (cased) так же разбивает stationery на station + ##ery, а 10,925 — на 10, ,, 92, ##5.

SentencePiece (Kudo and Richardson, 2018) убирает предварительную токенизацию. Он обучается на сыром тексте, в котором пробел — обычный символ ▁, поэтому ему не нужен разделитель на слова, он работает с письменностями без пробелов, такими как китайская, и восстанавливает текст при детокенизации в точности, с поправкой лишь на нормализацию. Помимо BPE, SentencePiece поддерживает униграммную языковую модель (unigram language model) (Kudo, 2018).

Униграммная модель работает в противоположном направлении: она прореживает большой словарь кандидатов. У каждого токена tt есть вероятность p(t)p(t), а вероятность сегментации считается так, как будто её токены независимы:

P(t1,…,tm)=∏i=1mp(ti).P(t_1, \dots, t_m) = \prod_{i=1}^{m} p(t_i).

Обучение чередует повторное оценивание pp EM-алгоритмом с удалением тех токенов, без которых правдоподобие корпуса снижается меньше всего, всегда сохраняя одиночные символы. Кодирование выбирает самую вероятную сегментацию динамическим программированием (алгоритм Витерби): лучшее значение для префикса w1:jw_{1:j} — максимум по i<ji < j с wi+1:j∈Vw_{i+1:j} \in V от лучшего значения для w1:iw_{1:i}, умноженного на p(wi+1:j)p(w_{i+1:j}). Взяв −log⁡-\log, мы превращаем произведение в сумму, так что это самый дешёвый путь через решётку (lattice), дуги которой — записи словаря стоимостью −log⁡p(t)-\log p(t). С игрушечными вероятностями на singer:

Рисунок

Самая вероятная сегментация — кратчайший путь

Решётка Витерби для слова singerСемь узлов для позиций от 0 до 6 в слове singer. Дуги со стоимостями: s от 0 до 1, 3.2; sin от 0 до 3, 5.5; sing от 0 до 4, 3.5; singe от 0 до 5, 6.2; i от 1 до 2, 3.5; in от 1 до 3, 3.9; ing от 1 до 4, 3.2; n от 2 до 3, 3.2; ng от 2 до 4, 3.9; g от 3 до 4, 3.5; ge от 3 до 5, 5.3; ger от 3 до 6, 5.8; e от 4 до 5, 3.0; er от 4 до 6, 3.0; r от 5 до 6, 3.2. Лучшая стоимость в каждом узле: 0: 0.0, 1: 3.2, 2: 6.7, 3: 5.5, 4: 3.5, 5: 6.2, 6: 6.5. Лучший путь, выделенный цветом, — sing · er стоимостью 6.5; жадный поиск самого длинного совпадения даёт singe · r стоимостью 9.4.sin5.5sing3.5singe6.2in3.9ing3.2ng3.9ge5.3ger5.8er3.0s3.2i3.5n3.2g3.5e3.0r3.20123456лучшаястоимость0.03.26.75.53.56.26.5черезsisinsingsingeerРешётка Витерби для слова singerСемь узлов для позиций от 0 до 6 в слове singer. Дуги со стоимостями: s от 0 до 1, 3.2; sin от 0 до 3, 5.5; sing от 0 до 4, 3.5; singe от 0 до 5, 6.2; i от 1 до 2, 3.5; in от 1 до 3, 3.9; ing от 1 до 4, 3.2; n от 2 до 3, 3.2; ng от 2 до 4, 3.9; g от 3 до 4, 3.5; ge от 3 до 5, 5.3; ger от 3 до 6, 5.8; e от 4 до 5, 3.0; er от 4 до 6, 3.0; r от 5 до 6, 3.2. Лучшая стоимость в каждом узле: 0: 0.0, 1: 3.2, 2: 6.7, 3: 5.5, 4: 3.5, 5: 6.2, 6: 6.5. Лучший путь, выделенный цветом, — sing · er стоимостью 6.5; жадный поиск самого длинного совпадения даёт singe · r стоимостью 9.4.sin5.5sing3.5singe6.2in3.9ing3.2ng3.9ge5.3ger5.8er3.0s3.2i3.5n3.2g3.5e3.0r3.20123456лучшая стоимость пути до каждого узла0.03.26.75.53.56.26.5
Слово singer в виде решётки: узел j — позиция после j символов, а каждая подстрока, которая есть в словаре, — дуга от её начала до её конца, подписанная токеном и его стоимостью −ln p(t). Вероятности — игрушечные значения для 15 токенов. Одиночные символы идут вдоль базовой линии, более длинные токены выгибаются над ней, тем выше, чем длиннее токен. Сегментация — путь от узла 0 до узла 6, и его стоимость равна −ln P, поэтому самая вероятная сегментация — самый дешёвый путь; всего путей здесь 19. Под каждым узлом — лучшая стоимость пути до него, вычисляемая слева направо как минимум по входящим дугам от суммы (лучшая стоимость в начале дуги + стоимость дуги). Синий путь sing · er стоит 6.5. Жадный поиск самого длинного совпадения берёт singe · r стоимостью 9.4: до узла 5 дешевле всего дойти через singe, но этот префикс не продолжается до самого дешёвого полного пути.

Лучший путь, sing er, — не жадное самое длинное совпадение singe r: алгоритм Витерби хранит одну лучшую стоимость для каждой позиции и никогда не фиксирует префикс заранее, а работа составляет O(n⋅ℓ)O(n \cdot \ell) для nn символов и токенов длиной не более ℓ\ell символов.

Поскольку униграммная модель оценивает каждую сегментацию, во время обучения она может их сэмплировать; такая аугментация данных называется регуляризацией подслов (subword regularization). BPE-dropout (Provilkov, Emelianenko and Voita, 2020) получает тот же эффект для BPE, пропуская каждое слияние с вероятностью qq только во время обучения.

Три алгоритма рядом (Hugging Face, Tokenization algorithms):

BPEWordPieceUnigram (SentencePiece)
ОбучениеСливать самую частую паруСливать пару с наибольшим c(a,b)/(c(a)c(b))c(a,b)/(c(a)c(b)) (реконструкция); по частоте в HF tokenizersПрореживать кандидатов по потере правдоподобия
КодированиеПовторять слияния по порядкуЖадный поиск самого длинного совпаденияСамая вероятная сегментация (Витерби)
Метка границы словаМаркер конца слова или Ġ для пробела перед словом (байтовый)## на неначальных кусках (BERT)▁ для пробела перед словом
Где используетсяGPT-2 (байтовый), Llama, Qwen2BERT, DistilBERT, ELECTRAT5, BigBird, Pegasus

Специальные токены, паддинг и маски#

Токенизатор — это конвейер, в котором обучается только этап модели, и его результат — больше, чем список идентификаторов:

Рисунок

От строки к батчу идентификаторов

Конвейер токенизатораШесть этапов сверху вниз. Сырой текст Singers SING; в нижнем регистре singers sing; разбиение на слова singers, sing; выученные слияния превращают их в sing er s | sing; постобработка даёт [CLS] sing er s sing [SEP] с идентификаторами 2 13 16 10 13 3; батч добавляет [CLS] song s [SEP] [PAD] [PAD] с идентификаторами 2 18 10 3 0 0 и маски внимания 1 1 1 1 1 1 и 1 1 1 1 0 0.сырой текстSingers SINGнормализациянижний регистрsingers singпредтокенизацияпо пробеламsingerssingмодель8 слиянийsingerssingпостобработкаспецтокены, id[CLS]2sing13er16s10sing13[SEP]3idбатчпаддинг, маска[CLS]21sing131er161s101sing131[SEP]31[CLS]21song181s101[SEP]31[PAD]00[PAD]00ididмаскамаскаКонвейер токенизатораШесть этапов сверху вниз. Сырой текст Singers SING; в нижнем регистре singers sing; разбиение на слова singers, sing; выученные слияния превращают их в sing er s | sing; постобработка даёт [CLS] sing er s sing [SEP] с идентификаторами 2 13 16 10 13 3; батч добавляет [CLS] song s [SEP] [PAD] [PAD] с идентификаторами 2 18 10 3 0 0 и маски внимания 1 1 1 1 1 1 и 1 1 1 1 0 0.сырой текстSingers SINGнормализациянижний регистрsingers singпредтокенизацияпо пробеламsingerssingмодель8 слиянийsingerssingпостобработкаспецтокены, id[CLS]2sing13er16s10sing13[SEP]3idбатчпаддинг, маска[CLS]21sing131er161s101sing131[SEP]31[CLS]21song181s101[SEP]31[PAD]00[PAD]00ididмаскамаска
Токенизатор разобранного примера внутри полного конвейера. Обучается только этап модели; нормализация (здесь — приведение к нижнему регистру) и предварительная токенизация (здесь — разбиение на слова по пробелам) — фиксированные правила, применяемые до неё, а постобработка добавляет граничные токены [CLS] и [SEP] и отображает каждый токен в его строку таблицы эмбеддингов. Идентификаторы (id): [PAD] = 0, [UNK] = 1, [CLS] = 2, [SEP] = 3, затем 15 токенов словаря по порядку (e = 4, g = 5, …, song = 18). В батче более короткая последовательность (songs) дополняется токеном [PAD] = 0, а маска внимания отмечает настоящие токены единицей. Декодирование проходит этапы в обратном порядке лишь частично: приведение к нижнему регистру нельзя отменить, а для склейки кусков нужна метка границы слова (## в WordPiece у BERT, Ġ или ▁ для пробела перед словом), которой у этого игрушечного токенизатора нет.
  • [CLS] / <s> отмечает начало последовательности, а [SEP] / </s> — её конец или границу сегментов (заметка о BERT). Единственный токен GPT-2 <|endoftext|> разделяет обучающие документы и завершает генерацию (заметка о GPT).
  • [UNK] заменяет слово, которое словарь не может записать своими кусками. Словари, выпущенные вместе с контрольными точками BERT от Google, держат специальные токены на фиксированных идентификаторах: [UNK] — 100, [CLS] — 101, [SEP] — 102, [MASK] — 103. Токенизатор многоязычного BERT без учёта регистра (uncased) со словарём из 105 879 записей превращает We are very happy to show you the 🤗 Transformers library. в 14 идентификаторов, которые начинаются со 101 и заканчиваются на 102; для эмодзи в словаре нет куска, и оно становится 100. Модель видит, что на этом месте что-то стояло, но не видит что.
  • token_type_ids задают сегмент каждого токена для входа [CLS] A [SEP] B [SEP] из заметки о BERT: 0 для первого сегмента, 1 для второго и одни нули для одного текста.
  • [PAD] дополняет короткие последовательности до самой длинной в батче — тензоре формы [batch, max_len]; маска внимания (attention mask) той же формы содержит 1 для настоящих токенов и 0 для паддинга, который последующие слои игнорируют.
  • truncation=True, max_length=T_max обрезает более длинные последовательности: Tmax⁡=32T_{\max} = 32 в заметке о переводе, 128 для AG News в заметке о трансформерах.
tok(["First text to encode", "Second text"], padding=True,
    max_length=3, truncation=True,
    return_token_type_ids=False, return_tensors="pt")
# input_ids:      tensor([[101, 2034, 102], [101, 2117, 102]])
# attention_mask: tensor([[1, 1, 1],        [1, 1, 1]])

Остаётся только первое слово, потому что ограничение учитывает специальные токены (у [CLS] идентификатор 101, у [SEP] — 102). Токенизатор также запоминает, из какого слова пришёл каждый токен; распознавание именованных сущностей использует это, чтобы перенести одну метку слова на все токены этого слова.

Что изменилось#

В сравнении с конвейером на уровне слов из предыдущей заметки:

Конвейер на уровне словТокенизатор подслов
Размер словаряСвойство корпусаГиперпараметр
Новые слова[UNK]Разбиваются на известные куски; на уровне байтов [UNK] не возникает
МорфологияТеряется при стемминге или хранится как отдельные типыОбщие куски (sing, sing er, sings)
Зависимость от языкаСтоп-слова и стеммеры для каждого языкаОбучается по данным; байтовому BPE алфавит не нужен
Длина последовательности1 токен на слово1–2 на английское слово при обычных размерах; больше для других языков

Заметка об n-граммных моделях обучает n-граммные модели на словных токенах и на токенах подслов русского корпуса и сообщает перплексии на токен около 40 000 и 2700 для униграммных моделей (около 30 000 и 3900 для биграммных). Эти числа несравнимы: в тексте из подслов больше токенов, и каждый из них предсказать легче. Суммарный логарифм правдоподобия нужно нормировать на общую единицу — слова, символы или байты. Если в тестовом тексте NtokN_{\text{tok}} токенов и NwordN_{\text{word}} слов, то

PPLword=exp⁡(1Nword∑i=1Ntok−log⁡p(xi∣x<i))=PPLtok Ntok/Nword.\mathrm{PPL}_{\text{word}} = \exp\Big(\frac{1}{N_{\text{word}}}\sum_{i=1}^{N_{\text{tok}}} -\log p(x_i \mid x_{<i})\Big) = \mathrm{PPL}_{\text{tok}}^{\,N_{\text{tok}}/N_{\text{word}}}.

Игрушечный пример: перплексия на токен 20 при 1.5 токена на слово — это перплексия на слово 201.5≈89.420^{1.5} \approx 89.4.

Пересчёт может поменять порядок моделей. Один анекдот корпуса содержит 11 слов и 16 кусков подслов помимо знаков препинания; при таком отношении 2700 на токен — это 2 70016/11≈98 0002\,700^{16/11} \approx 98\,000 на слово против примерно 40 000 у словной модели. Это лишь приближение (приведённые перплексии — средние перплексий отдельных анекдотов, и знаки препинания сохраняют только токены подслов), поэтому эти числа не решают, какой токенизатор лучше.

Ограничения#

  • Жадность и зависимость от частоты. Изменение одной буквы может поменять всю сегментацию, а в байтовых схемах слово получает разные токены с пробелом перед ним и без. Униграммная модель не уступает BPE или превосходит его в экспериментах с предобучением (pretraining) (Bostrom and Durrett, 2020); Mielke et al. (2021) делают обзор альтернатив.
  • Смещение в пользу одних языков и письменностей. Языки, редкие в обучающем корпусе токенизатора, режутся на большее число кусков, поэтому то же содержание стоит больше вычислений и контекста, и модели справляются с ним хуже (Rust et al., 2021; Petrov et al., 2023). Байтовый BPE закладывает это смещение ещё до первого слияния: token занимает 5 байтов UTF-8, а токен — 10.
  • Числа и символы. Цифры разбиваются по частоте, а не по разрядам (10 , 92 ##5), а буквы внутри многосимвольного токена модели не видны, поэтому арифметика, написание слов и подсчёт символов сложнее, чем кажется.
  • Токенизатор и модель связаны. Модель понимает только идентификаторы своего токенизатора. Добавленные токены получают необученные строки эмбеддингов, как токен [PAD], который заметка о дообучении (fine-tuning) добавляет к GPT-2.
  • Нормализация вшита в токенизатор. bert-base-uncased не может отличить US от us.

Что нужно запомнить#

  • Токенизатор выбирает компромисс между размером словаря ∣V∣|V| (стоимость эмбеддингов и softmax, данные на токен) и длиной последовательности TT (вычисления и контекст).
  • BPE многократно сливает самую частую пару соседних единиц и кодирует, повторяя слияния по порядку. Каждое слияние укорачивает обучающий корпус на счётчик пары, поэтому BPE — жадное сжатие.
  • WordPiece кодирует жадным поиском самого длинного совпадения с ##; униграммная модель выбирает самую вероятную сегментацию — кратчайший путь, найденный алгоритмом Витерби.
  • Байтовый BPE никогда не выдаёт [UNK]. SentencePiece обходится без предварительной токенизации, считая пробел символом ▁.
  • Языковые модели с разными токенизаторами сравнивают в расчёте на слово, символ или байт, но никогда не на токен.
  • Специальные токены, паддинг и маска внимания — часть контракта токенизатора с моделью.

Ноутбук#

К этой заметке относятся четыре ноутбука курса NLP. Номера ячеек считают все ячейки, включая текстовые, начиная с 1. Приведённые числа взяты из прогона, сохранённого в курсе; у ячеек, изменённых с тех пор под текущие версии библиотек, сохранённого выхода нет.

  • week03_lm.ipynb токенизирует корпус коротких русских анекдотов с помощью пакета bpe (ячейки 7–11). Несмотря на название, пакет не выучивает слияний: он оставляет целыми 2498 самых частых слов, берёт в качестве подслов 47 500 самых частых символьных n-грамм длины 1–6 и кодирует остальные слова жадным поиском самого длинного совпадения, как WordPiece. Если переписать его маркеры __sow/__eow как ##, один анекдот сокращается с 27 до 19 токенов (ячейки 12–16): 16 кусков подслов на его 11 слов и три знака препинания — то самое отношение, по которому в разделе Что изменилось перплексия пересчитывается на слово. Ячейки 42–43 обучают n-граммные модели, сравнённые в том же разделе; словарь идентификаторов корпуса содержит 37 580 записей (ячейка 49).
  • week04_lstm_attention_transformer.ipynb, ячейки 13–35, загружает предобученные токенизаторы WordPiece для английского (bert-base-uncased, 30 522 записи) и немецкого (31 102) и показывает граничные токены, маски, паддинг и обрезку (en + ##code — в ячейке 21, батч с max_length=3 — в ячейке 25). Гистограммы длин фиксируют максимальную длину 32.
  • week05_transformer.ipynb, ячейки 17–27, обучает на AG News токенизатор WordPiece на 4000 записей (слияния по частоте, кодирование поиском самого длинного совпадения, специальные токены с идентификаторами 0–4) и сохраняет батчи формы [256, 128]. Ячейка 29 берёт PAD_ID из pad_token_id — идентификатора [PAD]: это 0, потому что [PAD] указан первым среди специальных токенов. Первая версия ноутбука читала pad_token_type_id — идентификатор сегмента для паддинга, — который здесь тоже равен 0, поэтому ошибка ни на что не влияла.
  • week07_nlp_tasks.ipynb, ячейки 11–16: bert-base-cased превращает предложение CoNLL-2003 из 35 слов в 44 токена, а word_ids() сопоставляет каждому токену его слово.

Пример с [UNK] есть в ноутбуке о LLM курса HSE-LAMBDA Deep Generative Models, Transformers-LangChain.ipynb (ячейки нумеруются так же, выходы сохранены). Ячейка 26 кодирует это предложение токенизатором nlptown/bert-base-multilingual-uncased-sentiment и печатает input_ids [101, 11312, 10320, 12495, 19308, 10114, 11391, 10855, 10103, 100, 58263, 13299, 119, 102], token_type_ids из одних нулей и attention_mask из одних единиц; десятый идентификатор, 100, — это эмодзи. Ячейка 28 кодирует два предложения одним батчем с padding=True, truncation=True, max_length=512 и return_tensors="pt".

Дальше#

Теперь токены — целые числа из фиксированного словаря. Прежде чем строить модели, нужно договориться, что значит «модель работает»: на каких данных её можно обучать и настраивать, какие числа подытоживают её ошибки и какие ловушки делают хорошее число бессмысленным. Следующая заметка один раз задаёт эти правила, чтобы каждое последующее сравнение было честным.

Литература#