July 23, 2023

Как токенезировать текст и не охуеть

Я не буду рассказывать про миллион имплементаций, но расскажу немного про то что может вам пригодиться/то что меня самого заинтересовало.

код гугл прекрасен.....

Начнем с простого, есть два основных вида: unigram, bpe

Unigram

Униграммная модель - это тип языковой модели, которая рассматривает каждый токен как независимый от предыдущих. Это самая простая языковая модель, в смысле того, что вероятность токена X, учитывая предыдущий контекст, является просто вероятностью токена X. Так, если бы мы использовали униграммную модель для генерации текста, мы всегда бы предсказывали наиболее частый токен.

Вероятность данного токена равна его частоте (количество раз, когда мы его находим) в исходном корпусе, делённой на сумму всех частот всех токенов в словаре (чтобы убедиться, что вероятности в сумме дают 1). Например, "ug" присутствует в "hug", "pug" и "hugs", поэтому его частота в нашем корпусе составляет 20.

Предположим что у нас такой словарь для токенезации:

("h", 15) ("u", 36) ("g", 20) ("hu", 15) ("ug", 20) ("p", 17) ("pu", 17) ("n", 16)
("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5)

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

Например, при токенизации слова "pug" у нас может быть вариант ["p", "u", "g"] или ["pu", "g"]. В общем случае, вероятнее будут те варианты, в которых меньше токенов, потому что мы хотим разбить слово на наименьшее возможное количество токенов.

Таким образом, токенизация слова в униграммной модели - это токенизация с наивысшей вероятностью. В примере с "pug" варианты могут быть следующими:

["p", "u", "g"]
["p", "ug"]
["pu", "g"]

Мы выбираем вариант с наибольшей вероятностью исходя из частот токена в словаре.

Собственно идея обучения такой модели такая:
1. Сначала мы подсчитываем количество вхождений каждого слова в корпус. Для этого используется словарь, где ключи - это слова, а значения - это количество их вхождений.

2. Затем мы инициализируем наш словарь так, чтобы он был больше, чем окончательный размер словаря. Нам нужно включить все базовые символы (иначе мы не сможем токенизировать каждое слово), но для больших подстрок мы оставим только самые частые, поэтому мы сортируем их по частоте.

3. Следующим шагом группируем символы с лучшими подстроками, чтобы получить исходный словарь размером 300.

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

5. Главной функцией здесь является функция, которая токенизирует слова с использованием алгоритма Витерби. Этот алгоритм вычисляет наилучшее разбиение каждой подстроки слова, которое мы будем хранить в переменной с именем best_segmentations.

6. Заполнение списка выполняется с помощью двух циклов: основной цикл проходит по каждой начальной позиции, а второй цикл пробует все подстроки, начинающиеся с этой начальной позиции. Если подстрока есть в словаре, у нас есть новое разбиение слова до этой конечной позиции, которое мы сравниваем с тем, что есть в best_segmentations.

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

ладно это суфиксное дерево

Для BPE и bbpe будет такой алгоритм

1. Вначале вычисляем уникальный набор слов/байт, используемых в корпусе (после завершения нормализации и предварительной токенизации), затем строим словарь, принимая все символы, используемые для написания этих слов.

2. После получения базового словаря мы добавляем новые токены до достижения желаемого размера словаря, изучая правила слияния, которые объединяют два элемента существующего словаря в новый.

3. На любом этапе обучения токенизатора алгоритм BPE будет искать наиболее частую пару существующих токенов (под "парой" здесь мы имеем в виду два последовательных токена в слове). Эта самая частая пара будет объединена, и мы повторяем процесс для следующего шага.

4. Затем применяем это правило слияния в нашем словаре. Мы продолжаем этот процесс до достижения желаемого размера словаря.

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

preProcessing

Очев что современные токенайзеры это даже не просто lm, это lm с препроцессингом, пост процессингом и щепоткой магии,

Что может быть внутри нормализации

- `BertNormalizer`: Этот нормализатор подготавливает исходный текст для модели Bert. Он может очищать текст, обрабатывать китайские символы, удалять ударения и преобразовывать текст в нижний регистр.
- `Lowercase`: Просто преобразует весь текст в нижний регистр.
- `NFC`: Нормализатор Unicode, применяющий форму нормализации C (NFC), которая компонует символы для канонической эквивалентности.
- `NFD`: Нормализатор Unicode, применяющий форму нормализации D (NFD), которая декомпозирует символы для обеспечения канонической эквивалентности.
- `NFKC`: Нормализатор Unicode, применяющий форму нормализации KC (NFKC), которая компонует символы для эквивалентности совместимости.
- `NFKD`: Нормализатор Unicode, применяющий форму нормализации KD (NFKD), которая декомпозирует символы для эквивалентности совместимости.
- `Nmt`: Это нормализатор для моделей нейромашинного перевода (NMT).
- `Normalizer`: Это базовый класс для всех нормализаторов, он не должен инстанцироваться напрямую.
- `Precompiled`: Этот нормализатор используется для совместимости с SentencePiece и не должен использоваться вручную.
- `Replace`: Этот нормализатор заменяет заданный шаблон в тексте на другое содержимое.
- `Sequence`: Позволяет объединять несколько других нормализаторов в заданную последовательность.
- `Strip`: Этот нормализатор может удалять пробелы с левого, правого или обоих концов текста.
- `StripAccents`: Этот нормализатор удаляет все знаки ударения из символов текста.

Что может быть внутри претокенизации

- `BertPreTokenizer`: Разделяет лексемы на пробелы и знаки препинания. Каждое вхождение знака препинания рассматривается отдельно.
- `ByteLevel`: Заменяет все байты строки на соответствующее представление и разделяет на слова. Имеет опцию добавления пробела к первому слову, если его еще нет.
- `CharDelimiterSplit`: Разделяет вводимые данные по заданному символу, аналогично функции .split(delimiter).
- `Digits`: Разделяет вводимые данные с использованием цифр в отдельных лексемах, с возможностью рассматривать отдельные цифры как отдельные лексемы или группировать их вместе.
- `Metaspace`: Заменяет все пробельные символы на указанный символ замены и пытается разделить на эти пробелы. Имеется опция добавления пробела к первому слову, если его еще нет.
- `PreTokenizer`: Это базовый класс для всех претокенизаторов, он не должен инстанцироваться напрямую.
- `Punctuation`: Разделяет вводимый текст на знаки препинания как отдельные символы. Поведение разбиения может быть настроено.
- `Последовательность`: Позволяет объединять несколько других претокенизаторов в заданную последовательность.
- `Split`: Разделяет вводимые данные на основе заданного шаблона.

Model я пропущу, основные описаны выше

Пост процессоры

Они уже не так интересны, в современных моделях их вообще обычно нет, либо самые простые и служат для добавления ведущего пробела(ну те между словами)

Почитать на досуге

https://huggingface.co/learn/nlp-course/chapter6/

https://github.com/google/sentencepiece/tree/master/python тут оч кекные issues внутри