mirror of
https://github.com/pese-git/simple-llm.git
synced 2026-01-23 21:14:17 +00:00
235 lines
9.7 KiB
Markdown
235 lines
9.7 KiB
Markdown
# Byte Pair Encoding (BPE) Algorithm
|
||
|
||
> **Документ актуален для Simple-LLM v1.0 (июль 2025)**
|
||
|
||
---
|
||
|
||
**Краткое summary:**
|
||
Этот документ подробно описывает алгоритм Byte Pair Encoding (BPE) — как он используется для токенизации текста, как устроен процесс обучения словаря и как происходит энкодинг/декодинг текста. Документ предназначен для пользователей Simple-LLM и всех, кто хочет понять внутреннюю механику BPE.
|
||
|
||
---
|
||
|
||
**Структура документа:**
|
||
- Введение
|
||
- Основные понятия
|
||
- Алгоритм работы (обучение словаря)
|
||
- Псевдокод
|
||
- Пример работы
|
||
- Алгоритм энкодинга (токенизации)
|
||
- Алгоритм декодирования
|
||
- Типовые ошибки и их решения
|
||
|
||
---
|
||
|
||
## Введение
|
||
|
||
Byte Pair Encoding (BPE) - это алгоритм компрессии данных, адаптированный для токенизации текста в обработке естественного языка. В контексте языковых моделей BPE используется для создания эффективного словаря подстрок (токенов).
|
||
|
||
## Основные понятия
|
||
|
||
- **Токен** - элементарная единица текста (символ или последовательность символов)
|
||
- **Словарь** - набор уникальных токенов, используемых для представления текста
|
||
- **Частота пары** - количество раз, когда два токена встречаются вместе в тексте
|
||
|
||
## Алгоритм работы
|
||
|
||
### 1. Инициализация
|
||
```python
|
||
Исходный текст → Разбить на символы → Первоначальный словарь
|
||
```
|
||
Пример:
|
||
```
|
||
"мама" → ['м', 'а', 'м', 'а']
|
||
```
|
||
|
||
### 2. Основной цикл
|
||
```mermaid
|
||
graph TD
|
||
A[Подсчет частот пар] --> B[Выбор наиболее частой пары]
|
||
B --> C[Создание нового токена]
|
||
C --> D[Обновление последовательности]
|
||
D --> E{Достигнут лимит словаря?}
|
||
E -->|Нет| A
|
||
E -->|Да| F[Конец]
|
||
```
|
||
|
||
### 3. Детализация шагов
|
||
|
||
#### Шаг 1: Подсчет частот пар
|
||
Для текущей последовательности токенов подсчитываем все пары соседних токенов:
|
||
```
|
||
Текст: "мама мыла"
|
||
Токены: ['м', 'а', 'м', 'а', ' ', 'м', 'ы', 'л', 'а']
|
||
Пары: ('м','а'), ('а','м'), ('м','а'), ('а',' '), (' ','м'), ('м','ы'), ('ы','л'), ('л','а')
|
||
```
|
||
|
||
#### Шаг 2: Выбор пары для слияния
|
||
Находим пару с максимальной частотой. При равенстве частот выбираем пару, которая встречается раньше в тексте.
|
||
|
||
#### Шаг 3: Слияние
|
||
Объединяем выбранную пару в новый токен и заменяем все её вхождения в тексте:
|
||
```
|
||
Выбранная пара: ('м', 'а')
|
||
Новый токен: 'ма'
|
||
Обновленная последовательность: ['ма', 'ма', ' ', 'м', 'ы', 'л', 'а']
|
||
```
|
||
|
||
#### Шаг 4: Обновление словаря
|
||
Добавляем новый токен в словарь:
|
||
```
|
||
Словарь: ['м', 'а', ' ', 'ы', 'л', 'ма']
|
||
```
|
||
|
||
### 4. Критерии остановки
|
||
|
||
1. Достижение заданного размера словаря
|
||
2. Отсутствие пар для слияния (все возможные пары уже добавлены)
|
||
3. Достижение максимального числа итераций
|
||
|
||
## Псевдокод
|
||
|
||
```python
|
||
def train_bpe(text, vocab_size):
|
||
# Инициализация
|
||
tokens = list(text)
|
||
vocab = set(tokens)
|
||
|
||
while len(vocab) < vocab_size:
|
||
# Подсчет пар
|
||
pairs = get_pairs(tokens)
|
||
if not pairs:
|
||
break
|
||
|
||
# Выбор наиболее частой пары
|
||
best_pair = max(pairs, key=pairs.get)
|
||
|
||
# Слияние
|
||
new_tokens = []
|
||
i = 0
|
||
while i < len(tokens):
|
||
if i < len(tokens)-1 and (tokens[i], tokens[i+1]) == best_pair:
|
||
new_tokens.append(best_pair[0] + best_pair[1])
|
||
i += 2
|
||
else:
|
||
new_tokens.append(tokens[i])
|
||
i += 1
|
||
tokens = new_tokens
|
||
|
||
# Обновление словаря
|
||
vocab.add(best_pair[0] + best_pair[1])
|
||
|
||
return vocab
|
||
```
|
||
|
||
## Пример работы
|
||
|
||
**Исходный текст**: "мама мыла раму"
|
||
|
||
**Итерация 1**:
|
||
- Пара ('м','а') встречается 2 раза
|
||
- Новый токен: 'ма'
|
||
- Текст: ['ма', 'ма', ' ', 'м', 'ы', 'л', 'а', ' ', 'р', 'а', 'м', 'у']
|
||
|
||
**Итерация 2**:
|
||
- Пара ('ма',' ') встречается 1 раз
|
||
- Новый токен: 'ма '
|
||
- Текст: ['ма ', 'ма', 'мы', 'л', 'а', ' ', 'р', 'а', 'м', 'у']
|
||
|
||
**Результирующий словарь** (частично):
|
||
['м', 'а', ' ', 'ы', 'л', 'р', 'у', 'ма', 'ма ', 'мы']
|
||
|
||
## Алгоритм энкодинга (токенизации)
|
||
|
||
После обучения BPE-модели (создания словаря), энкодинг текста происходит по следующему алгоритму:
|
||
|
||
```mermaid
|
||
graph TD
|
||
A[Начало] --> B[Разбить текст на символы]
|
||
B --> C[Инициализировать пустой список токенов]
|
||
C --> D[Установить i=0 'начало последовательности']
|
||
D --> E{i < длины текста?}
|
||
E -->|Нет| F[Заменить токены на их ID]
|
||
E -->|Да| G[Найти все токены в словаре, начинающиеся с text_i]
|
||
G --> H[Выбрать самый длинный подходящий токен]
|
||
H --> I[Добавить токен в результат]
|
||
I --> J[Увеличить i на длину токена]
|
||
J --> E
|
||
F --> K[Конец]
|
||
```
|
||
|
||
Пошаговое описание:
|
||
1. **Инициализация**:
|
||
- Исходный текст разбивается на символы
|
||
- Создается пустой список для результата
|
||
- Указатель `i` устанавливается в 0
|
||
|
||
2. **Основной цикл**:
|
||
- Для текущей позиции `i` находим все токены в словаре, которые:
|
||
- Начинаются с символа `text[i]`
|
||
- Совпадают с подстрокой `text[i:i+len(token)]`
|
||
- Из подходящих токенов выбираем самый длинный
|
||
- Добавляем найденный токен в результат
|
||
- Сдвигаем указатель `i` на длину добавленного токена
|
||
|
||
3. **Завершение**:
|
||
- Когда весь текст обработан, заменяем токены на их ID из словаря
|
||
|
||
Пример:
|
||
```python
|
||
Словарь: ['ма', 'мама', ' ', 'мыл', 'а', 'раму']
|
||
Текст: "мама мыла раму"
|
||
|
||
Энкодинг:
|
||
1. Находим самый длинный токен, начинающийся с 'м' -> 'мама'
|
||
2. Добавляем 'мама', i += 4
|
||
3. Токен ' ' (пробел), i += 1
|
||
4. Токен 'мыл', i += 3
|
||
5. Токен 'а', i += 1
|
||
6. Токен ' ', i += 1
|
||
7. Токен 'раму', i += 4
|
||
|
||
Результат: ['мама', ' ', 'мыл', 'а', ' ', 'раму']
|
||
```
|
||
|
||
## Алгоритм декодирования
|
||
|
||
### Принцип работы
|
||
1. Преобразование списка ID обратно в текст
|
||
2. Замена каждого ID на соответствующий токен
|
||
3. Обработка неизвестных ID (`[UNK]`)
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A[Список ID] --> B{ID ∈ словарь?}
|
||
B -->|Да| C[Добавить токен]
|
||
B -->|Нет| D[Добавить UNK]
|
||
C --> E[Следующий ID]
|
||
D --> E
|
||
E --> F{Конец списка?}
|
||
F -->|Нет| B
|
||
F -->|Да| G[Объединить токены]
|
||
```
|
||
|
||
Пример:
|
||
```python
|
||
decode([0,1,2]) → "абра" # Для словаря {0:"а", 1:"б", 2:"ра"}
|
||
```
|
||
|
||
## Применение в языковых моделях
|
||
|
||
1. Эффективное представление редких слов
|
||
2. Снижение размерности входных данных
|
||
3. Возможность обработки OOV (Out-of-Vocabulary) слов
|
||
|
||
## Ограничения
|
||
|
||
1. Чувствительность к регистру (можно решить предварительной нормализацией)
|
||
2. Зависимость от обучающего корпуса
|
||
3. Не всегда выделяет лингвистически осмысленные морфемы
|
||
|
||
## Дополнительные материалы
|
||
|
||
1. [Original BPE paper](https://arxiv.org/abs/1508.07909)
|
||
2. [BPE in HuggingFace](https://huggingface.co/docs/transformers/tokenizer_summary)
|
||
3. [Practical guide to BPE](https://towardsdatascience.com/byte-pair-encoding-subword-based-tokenization-algorithm-77828a70bee0)
|