mirror of
https://github.com/pese-git/simple-llm.git
synced 2026-01-23 21:14:17 +00:00
Рефакторинг и улучшение компонентов
Основные изменения в коде:
1. Токенизатор (bpe.py):
- Добавлен прогресс-бар через tqdm в метод fit()
- Улучшено логирование процесса обучения
- Добавлена обработка edge-cases для vocab_size
2. Генерация текста (generate_text.py):
- Полный рефакторинг скрипта
- Добавлены проверки модели перед загрузкой
- Поддержка уменьшенных моделей (seq_len=32)
- Подробное логирование процесса генерации
3. Обучение GPT (train_gpt_model.py):
- Автоподбор параметров под размер данных
- Уменьшенные параметры модели по умолчанию
- Контроль памяти и устройств (CPU/MPS)
4. Токенизация корпуса (tokenize_corpus.py):
- Добавлены проверки входных данных
- Подробное логирование процесса
- Обработка ошибок загрузки файлов
Исправления:
- Синхронизация размеров слоёв в GPT
- Корректная работа с малыми наборами данных
- Исправление загрузки моделей на MPS
Обновление README.md
- Добавлены обязательные зависимости: dill и tqdm
- Добавлен раздел 'Цель проекта' с описанием задач
- Добавлен раздел 'Участие в разработке' для контрибьюторов
- Добавлен раздел 'Лицензия' с условиями MIT
Рефакторинг основных скриптов и обновление данных
Основные изменения:
1. Скрипты в bin/:
- Оптимизация generate_text.py (генерация текста)
- Улучшение tokenize_corpus.py (обработка корпуса)
- Рефакторинг train_gpt_model.py (обучение модели)
- Обновление train_tokenizer.py (алгоритм BPE)
2. Данные:
- Удалены устаревшие артефакты:
* simple_llm_gpt.pth (модель)
* bpe_tokenizer.json (токенизатор)
* corpus_tokens.pkl (токены)
- Подготовка к генерации новых данных
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -210,3 +210,7 @@ __marimo__/
|
|||||||
.vscode
|
.vscode
|
||||||
example_output/
|
example_output/
|
||||||
trained_gpt_model.pt
|
trained_gpt_model.pt
|
||||||
|
|
||||||
|
data/corpus/pushkin_poetry
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
297
README.md
297
README.md
@@ -1,202 +1,169 @@
|
|||||||
# Simple-LLM Framework
|
# Simple-LLM: Персональная языковая модель
|
||||||
|
|
||||||
[]()
|
## 🎯 Цель проекта
|
||||||
[]()
|
|
||||||
|
|
||||||
> **Актуально для Simple-LLM v1.0 (июль 2025)**
|
Simple-LLM - это минималистичная реализация языковой модели (LLM) с полным циклом:
|
||||||
|
- Обучение BPE-токенизатора на ваших данных
|
||||||
|
- Подготовка датасета для обучения модели
|
||||||
|
- Тренировка компактной GPT-архитектуры
|
||||||
|
- Генерация текста в заданном стиле
|
||||||
|
|
||||||
---
|
Проект создан для:
|
||||||
|
1. Образовательных целей - понимания работы современных LLM
|
||||||
|
2. Экспериментов с генерацией текста на небольших датасетах
|
||||||
|
3. Создания персонализированных языковых моделей
|
||||||
|
|
||||||
## Установка
|
Полный цикл от обучения токенизатора до генерации текста
|
||||||
|
|
||||||
Рекомендуется использовать виртуальное окружение (venv) для изоляции зависимостей и корректной работы импортов.
|
## 🛠 Установка
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 -m venv venv
|
# 1. Клонируйте репозиторий
|
||||||
source venv/bin/activate
|
git clone https://github.com/ваш-репозиторий/simple-llm.git
|
||||||
pip install .
|
cd simple-llm
|
||||||
|
|
||||||
|
# 2. Создайте виртуальное окружение (рекомендуется)
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/activate # Linux/Mac
|
||||||
|
# или venv\Scripts\activate # Windows
|
||||||
|
|
||||||
|
# 3. Установите зависимости
|
||||||
|
pip install torch==2.0.1
|
||||||
|
pip install dill tqdm # Основные зависимости для работы
|
||||||
```
|
```
|
||||||
|
|
||||||
Также вы можете вручную установить необходимые зависимости:
|
## 📂 Подготовка данных
|
||||||
|
|
||||||
|
Поместите текстовые файлы (.txt) в папку:
|
||||||
|
```
|
||||||
|
data/
|
||||||
|
└── corpus/
|
||||||
|
└── sample/
|
||||||
|
├── text1.txt
|
||||||
|
├── text2.txt
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Полный рабочий цикл
|
||||||
|
|
||||||
|
### 1. Обучение BPE-токенизатора
|
||||||
```bash
|
```bash
|
||||||
pip install torch numpy dill
|
python bin/train_tokenizer.py \
|
||||||
|
--corpus data/corpus/sample \
|
||||||
|
--output data/tokenizer/bpe_model.json \
|
||||||
|
--vocab-size 500
|
||||||
```
|
```
|
||||||
|
|
||||||
Если появится файл `requirements.txt`, используйте:
|
### 2. Токенизация данных
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -r requirements.txt
|
python bin/tokenize_corpus.py \
|
||||||
|
--corpus data/corpus/sample \
|
||||||
|
--tokenizer data/tokenizer/bpe_model.json \
|
||||||
|
--output data/tokens/tokenized_corpus.pkl
|
||||||
```
|
```
|
||||||
|
|
||||||
Если вы хотите использовать последнюю версию из PyPI:
|
### 3. Обучение GPT модели
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install simple-llm
|
python bin/train_gpt_model.py \
|
||||||
|
--tokens data/tokens/tokenized_corpus.pkl \
|
||||||
|
--tokenizer data/tokenizer/bpe_model.json \
|
||||||
|
--output data/model/gpt_model.pth \
|
||||||
|
--seq-len 32 \
|
||||||
|
--batch-size 3 \
|
||||||
|
--epochs 3 \
|
||||||
|
--emb-size 64 \
|
||||||
|
--num-heads 2 \
|
||||||
|
--num-layers 2
|
||||||
```
|
```
|
||||||
|
|
||||||
Если возникают ошибки с импортами, убедитесь, что пакет установлен через pip и вы находитесь в активированном виртуальном окружении.
|
### 4. Генерация текста
|
||||||
|
|
||||||
### Основные зависимости
|
|
||||||
- torch
|
|
||||||
- numpy
|
|
||||||
- dill
|
|
||||||
|
|
||||||
**Краткая инструкция по обучению на своих данных:**
|
|
||||||
1. Обучите BPE-токенизатор на тексте (см. `simple_llm.tokenizer.bpe.BPE`).
|
|
||||||
2. Токенизируйте корпус и создайте датасет через `GetData`.
|
|
||||||
3. Инициализируйте модель `GPT` с нужными параметрами.
|
|
||||||
4. Обучите модель одной строкой: `model.fit(train_loader, num_epoch=10)`.
|
|
||||||
5. Для подробной инструкции и примеров см. [документацию](doc/train_on_custom_data_ru.md).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Структура README:**
|
|
||||||
- Обзор
|
|
||||||
- Быстрый старт
|
|
||||||
- Основные компоненты
|
|
||||||
- Документация
|
|
||||||
- Тестирование
|
|
||||||
- Как внести вклад
|
|
||||||
- Лицензия
|
|
||||||
- [FAQ](#faq)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Простая и понятная реализация языковой модели GPT-стиля с нуля на PyTorch
|
|
||||||
|
|
||||||
## 🔍 Обзор
|
|
||||||
|
|
||||||
Simple-LLM предоставляет:
|
|
||||||
- Полную реализацию архитектуры GPT
|
|
||||||
- Эффективный токенизатор BPE
|
|
||||||
- Модули трансформера (внимание, FFN, эмбеддинги)
|
|
||||||
- Гибкую систему генерации текста
|
|
||||||
- Примеры использования и документацию
|
|
||||||
|
|
||||||
## 🚀 Быстрый старт
|
|
||||||
|
|
||||||
1. Установите зависимости:
|
|
||||||
```bash
|
```bash
|
||||||
pip install torch numpy tqdm
|
python bin/generate_text.py \
|
||||||
|
--model data/model/gpt_model.pth \
|
||||||
|
--tokenizer data/tokenizer/bpe_model.json \
|
||||||
|
--seq-len 32 \
|
||||||
|
--emb-size 64 \
|
||||||
|
--num-heads 2 \
|
||||||
|
--num-layers 2 \
|
||||||
|
--prompt "Ваш текст для продолжения" \
|
||||||
|
--length 100 \
|
||||||
|
--temperature 0.7
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Запустите примеры:
|
## 🚀 Быстрый старт (минимальная конфигурация)
|
||||||
```bash
|
```bash
|
||||||
# Пример генерации текста
|
# Последовательно выполните:
|
||||||
python example/example_gpt.py
|
./bin/train_tokenizer.py --corpus data/corpus/sample --output data/tokenizer/bpe.json
|
||||||
|
./bin/tokenize_corpus.py --corpus data/corpus/sample --tokenizer data/tokenizer/bpe.json
|
||||||
# Пример обучения модели
|
./bin/train_gpt_model.py --tokens data/tokens/corpus_tokens.pkl --tokenizer data/tokenizer/bpe.json
|
||||||
python example/train_gpt_example.py
|
./bin/generate_text.py --model data/model/gpt_model.pth --tokenizer data/tokenizer/bpe.json --prompt "Привет"
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🧠 Основные компоненты
|
## 🧠 Рекомендации по параметрам
|
||||||
|
|
||||||
### Обработка данных
|
| Параметр | CPU (рекомендации) | GPU (рекомендации) |
|
||||||
```python
|
|------------------|--------------------|--------------------|
|
||||||
from simple_llm.data.get_data import GetData
|
| vocab-size | 2000-5000 | 5000-10000 |
|
||||||
|
| seq-len | 64-128 | 128-256 |
|
||||||
|
| batch-size | 4-8 | 16-32 |
|
||||||
|
| emb-size | 64-128 | 256-512 |
|
||||||
|
| num-layers | 2-4 | 6-12 |
|
||||||
|
|
||||||
dataset = GetData(
|
## ⚠️ Устранение проблем
|
||||||
data=[1, 2, 3, 4, 5], # Входная последовательность
|
1. **Ошибка памяти**:
|
||||||
seq_len=3, # Длина окна
|
- Уменьшите `batch-size` и `seq-len`
|
||||||
device="cuda" # Устройство (опционально)
|
```bash
|
||||||
)
|
python bin/train_gpt_model.py --batch-size 2 --seq-len 64
|
||||||
```
|
```
|
||||||
|
|
||||||
### Модель GPT
|
2. **Плохая генерация**:
|
||||||
```python
|
- Увеличьте размер корпуса (>1MB текста)
|
||||||
from simple_llm.transformer.gpt import GPT
|
- Добавьте больше эпох обучения (`--epochs 15`)
|
||||||
|
|
||||||
model = GPT(
|
3. **Медленная работа**:
|
||||||
vocab_size=10000,
|
```bash
|
||||||
max_seq_len=512,
|
# Для GPU добавьте перед запуском:
|
||||||
emb_size=768,
|
export CUDA_VISIBLE_DEVICES=0
|
||||||
num_heads=12,
|
```
|
||||||
num_layers=6
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Генерация текста
|
## 👥 Участие в разработке
|
||||||
```python
|
|
||||||
output = model.generate(
|
|
||||||
input_ids,
|
|
||||||
max_new_tokens=100,
|
|
||||||
temperature=0.9,
|
|
||||||
top_k=50,
|
|
||||||
top_p=0.9
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Обучение модели
|
Мы приветствуем вклад в проект! Вот как вы можете помочь:
|
||||||
```python
|
|
||||||
from torch.utils.data import DataLoader
|
|
||||||
|
|
||||||
# Данные должны быть в формате (input_ids, targets)
|
### 🛠 Как внести свой вклад:
|
||||||
# targets - это input_ids, сдвинутые на 1 токен вперед
|
|
||||||
train_loader = DataLoader(...)
|
|
||||||
|
|
||||||
model.fit(
|
|
||||||
train_loader=train_loader, # Обучающие данные (обязательно)
|
|
||||||
valid_loader=None, # Валидационные данные (опционально)
|
|
||||||
num_epoch=10, # Количество эпох
|
|
||||||
learning_rate=0.001 # Скорость обучения
|
|
||||||
)
|
|
||||||
|
|
||||||
# Сохранение модели
|
|
||||||
model.save("model.pt")
|
|
||||||
|
|
||||||
# Загрузка модели
|
|
||||||
loaded_model = GPT.load("model.pt", device="cuda")
|
|
||||||
```
|
|
||||||
|
|
||||||
**Требования к данным:**
|
|
||||||
- Формат: `(input_ids, targets)` где `targets = roll(input_ids, -1)`
|
|
||||||
- `input_ids`: тензор формы `[batch_size, seq_len]`
|
|
||||||
- Поддерживаются как синтетические, так и реальные текстовые данные
|
|
||||||
|
|
||||||
## 📚 Документация
|
|
||||||
|
|
||||||
Полная документация доступна в [doc/](./doc/):
|
|
||||||
- [Архитектура GPT](./doc/gpt_documentation_ru.md)
|
|
||||||
- [Алгоритм BPE](./doc/bpe_algorithm.md)
|
|
||||||
- [Обработка последовательностей](./doc/get_data_documentation_ru.md)
|
|
||||||
- [Примеры использования](./example/)
|
|
||||||
|
|
||||||
## 🛠 Тестирование
|
|
||||||
```bash
|
|
||||||
pytest tests/
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🤝 Как внести вклад
|
|
||||||
1. Форкните репозиторий
|
1. Форкните репозиторий
|
||||||
2. Создайте ветку (`git checkout -b feature/AmazingFeature`)
|
2. Создайте ветку для вашего изменения (`git checkout -b feature/your-feature`)
|
||||||
3. Сделайте коммит (`git commit -m 'Add some AmazingFeature'`)
|
3. Сделайте коммит ваших изменений (`git commit -am 'Add some feature'`)
|
||||||
4. Запушьте ветку (`git push origin feature/AmazingFeature`)
|
4. Запушьте в ветку (`git push origin feature/your-feature`)
|
||||||
5. Откройте Pull Request
|
5. Создайте Pull Request
|
||||||
|
|
||||||
---
|
### 📌 Правила:
|
||||||
|
- Следуйте существующему стилю кода
|
||||||
|
- Пишите понятные сообщения коммитов
|
||||||
|
- Добавляйте тесты для новых функций
|
||||||
|
- Обновляйте документацию при изменении API
|
||||||
|
|
||||||
## ❓ FAQ
|
### 🐛 Сообщение об ошибках:
|
||||||
|
Открывайте Issue с описанием:
|
||||||
**Q: Как установить Simple-LLM, чтобы работали все импорты?**
|
1. Шаги для воспроизведения
|
||||||
A: Рекомендуется установить через pip (локально: `pip install .` или с PyPI: `pip install simple-llm`). Тогда все примеры и импорты будут работать из любой директории.
|
2. Ожидаемое поведение
|
||||||
|
3. Фактическое поведение
|
||||||
**Q: Как запустить Simple-LLM на CPU?**
|
4. Версии ПО (Python, PyTorch и т.д.)
|
||||||
A: Передайте параметр `device="cpu"` при инициализации модели или обработке данных.
|
|
||||||
|
|
||||||
**Q: Как использовать свой датасет?**
|
|
||||||
A: Используйте класс `GetData` из `simple_llm.data.get_data` для подготовки своих последовательностей. Следуйте формату `(input_ids, targets)`.
|
|
||||||
|
|
||||||
**Q: Где посмотреть примеры?**
|
|
||||||
A: В папке [`example/`](./example/) есть скрипты генерации и обучения.
|
|
||||||
|
|
||||||
**Q: Ошибка CUDA out of memory!**
|
|
||||||
A: Уменьшите размер batch_size или размерность модели, либо используйте CPU.
|
|
||||||
|
|
||||||
**Q: Как добавить новый модуль или улучшение?**
|
|
||||||
A: Ознакомьтесь с документацией, следуйте рекомендациям по вкладу и открывайте Pull Request.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📜 Лицензия
|
## 📜 Лицензия
|
||||||
Распространяется под лицензией MIT. См. [LICENSE](./LICENSE)
|
|
||||||
|
Проект распространяется под лицензией MIT. Полный текст лицензии доступен в файле [LICENSE](LICENSE).
|
||||||
|
|
||||||
|
Основные положения:
|
||||||
|
- Разрешается свободное использование, модификация и распространение кода
|
||||||
|
- Обязательно указание авторства
|
||||||
|
- Лицензия предоставляется "как есть" без гарантий
|
||||||
|
- Авторы не несут ответственности за последствия использования
|
||||||
|
|
||||||
|
## 📌 Важно
|
||||||
|
- Все скрипты имеют встроенную помощь:
|
||||||
|
```bash
|
||||||
|
python bin/train_tokenizer.py --help
|
||||||
|
```
|
||||||
|
- Модель автоматически использует GPU если доступен
|
||||||
|
- Для выхода из виртуального окружения: `deactivate`
|
||||||
|
|||||||
11
bin/README.md
Normal file
11
bin/README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Параметры GPT-1:
|
||||||
|
|
||||||
|
12 слоев.
|
||||||
|
12 голов Внимания в каждом слое.
|
||||||
|
768 – размерность эмбедингов.
|
||||||
|
40 000 – размер словаря.
|
||||||
|
0.1 – дропаут.
|
||||||
|
2.5e-4 – learning rate
|
||||||
|
100 эпох.
|
||||||
|
64 – размер батча
|
||||||
|
512 – длина одной последовательности.
|
||||||
81
bin/generate_text.py
Executable file
81
bin/generate_text.py
Executable file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Генерация текста (финальная версия)
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import torch
|
||||||
|
from simple_llm.tokenizer.simple_bpe import SimpleBPE
|
||||||
|
from simple_llm.transformer.gpt import GPT
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
# Обязательные параметры
|
||||||
|
parser.add_argument('--model', type=str, required=True,
|
||||||
|
help='Путь к файлу модели (.pth)')
|
||||||
|
parser.add_argument('--tokenizer', type=str, required=True,
|
||||||
|
help='Путь к файлу токенизатора (.json)')
|
||||||
|
parser.add_argument('--prompt', type=str, required=True,
|
||||||
|
help='Начальный текст для генерации')
|
||||||
|
|
||||||
|
# Параметры модели (должны соответствовать обучению)
|
||||||
|
parser.add_argument('--seq-len', type=int, default=64,
|
||||||
|
help='Макс. длина последовательности (как при обучении)')
|
||||||
|
parser.add_argument('--emb-size', type=int, default=64,
|
||||||
|
help='Размер эмбеддингов (как при обучении)')
|
||||||
|
parser.add_argument('--num-heads', type=int, default=4,
|
||||||
|
help='Количество голов внимания (как при обучении)')
|
||||||
|
parser.add_argument('--head-size', type=int, default=16,
|
||||||
|
help='Размер головы внимания (как при обучении)')
|
||||||
|
parser.add_argument('--num-layers', type=int, default=2,
|
||||||
|
help='Количество слоёв (как при обучении)')
|
||||||
|
parser.add_argument('--dropout', type=float, default=0.1,
|
||||||
|
help='Dropout (как при обучении)')
|
||||||
|
|
||||||
|
# Параметры генерации
|
||||||
|
parser.add_argument('--length', type=int, default=50,
|
||||||
|
help='Количество генерируемых токенов')
|
||||||
|
parser.add_argument('--temperature', type=float, default=0.7,
|
||||||
|
help='Температура сэмплинга (0.1-1.0)')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Загрузка
|
||||||
|
device = 'cuda' if torch.cuda.is_available() else 'cpu'
|
||||||
|
print(f"Используется устройство: {device}")
|
||||||
|
|
||||||
|
tokenizer = SimpleBPE.load(args.tokenizer)
|
||||||
|
print(f"Загружен токенизатор (vocab_size={tokenizer.vocab_size})")
|
||||||
|
|
||||||
|
# Инициализация модели
|
||||||
|
model = GPT(
|
||||||
|
vocab_size=tokenizer.vocab_size,
|
||||||
|
max_seq_len=args.seq_len,
|
||||||
|
emb_size=args.emb_size,
|
||||||
|
num_heads=args.num_heads,
|
||||||
|
head_size=args.head_size,
|
||||||
|
num_layers=args.num_layers,
|
||||||
|
dropout=args.dropout,
|
||||||
|
device=device
|
||||||
|
)
|
||||||
|
|
||||||
|
model.load_state_dict(torch.load(args.model, map_location=device))
|
||||||
|
model.eval()
|
||||||
|
print(f"Загружена модель с {sum(p.numel() for p in model.parameters()):,} параметрами")
|
||||||
|
|
||||||
|
# Генерация
|
||||||
|
print(f"\nГенерация текста для промта: '{args.prompt}'")
|
||||||
|
tokens = tokenizer.encode(args.prompt)
|
||||||
|
print(f"Токены промта: {tokens}")
|
||||||
|
|
||||||
|
output = model.generate(
|
||||||
|
x=torch.tensor([tokens], device=device),
|
||||||
|
max_new_tokens=args.length,
|
||||||
|
do_sample=True,
|
||||||
|
temperature=args.temperature
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\n=== Результат ===")
|
||||||
|
print(tokenizer.decode(output[0].tolist()))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
52
bin/tokenize_corpus.py
Executable file
52
bin/tokenize_corpus.py
Executable file
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Токенизация корпуса с CLI аргументами
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import pickle
|
||||||
|
from pathlib import Path
|
||||||
|
from simple_llm.tokenizer.optimize_bpe import OptimizeBPE
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--corpus', type=str, required=True,
|
||||||
|
help='Путь к директории с текстами')
|
||||||
|
parser.add_argument('--tokenizer', type=str, required=True,
|
||||||
|
help='Путь к файлу токенизатора')
|
||||||
|
parser.add_argument('--output', type=str, required=True,
|
||||||
|
help='Путь для сохранения токенизированных данных')
|
||||||
|
parser.add_argument('--max-tokens', type=int, default=None,
|
||||||
|
help='Максимальное количество токенов (для тестов)')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Загрузка
|
||||||
|
tokenizer = OptimizeBPE.load(args.tokenizer)
|
||||||
|
corpus = []
|
||||||
|
|
||||||
|
print(f"Чтение текстов из {args.corpus}...")
|
||||||
|
for file in Path(args.corpus).glob('*.txt'):
|
||||||
|
corpus.append(file.read_text(encoding='utf-8'))
|
||||||
|
|
||||||
|
# Токенизация
|
||||||
|
print("Токенизация...")
|
||||||
|
all_tokens = []
|
||||||
|
for text in corpus:
|
||||||
|
tokens = tokenizer.encode(text)
|
||||||
|
if args.max_tokens:
|
||||||
|
tokens = tokens[:args.max_tokens]
|
||||||
|
all_tokens.extend(tokens)
|
||||||
|
|
||||||
|
# Сохранение
|
||||||
|
# Проверяем и создаем директорию для сохранения
|
||||||
|
output_dir = os.path.dirname(args.output)
|
||||||
|
if output_dir and not os.path.exists(output_dir):
|
||||||
|
print(f"Создаем директорию: {output_dir}")
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
with open(args.output, 'wb') as f:
|
||||||
|
pickle.dump(all_tokens, f)
|
||||||
|
print(f"Сохранено {len(all_tokens)} токенов в {args.output}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
84
bin/train_gpt_model.py
Executable file
84
bin/train_gpt_model.py
Executable file
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Обучение GPT с CLI аргументами (исправленная версия)
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import pickle
|
||||||
|
import torch
|
||||||
|
from torch.utils.data import DataLoader
|
||||||
|
from simple_llm.data.get_data import GetData
|
||||||
|
from simple_llm.transformer.gpt import GPT
|
||||||
|
from simple_llm.tokenizer.optimize_bpe import OptimizeBPE
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--tokens', type=str, required=True,
|
||||||
|
help='Путь к токенизированным данным (.pkl)')
|
||||||
|
parser.add_argument('--tokenizer', type=str, required=True,
|
||||||
|
help='Путь к файлу токенизатора (.json)')
|
||||||
|
parser.add_argument('--output', type=str, required=True,
|
||||||
|
help='Путь для сохранения модели (.pth)')
|
||||||
|
|
||||||
|
# Параметры модели
|
||||||
|
parser.add_argument('--seq-len', type=int, default=64,
|
||||||
|
help='Максимальная длина последовательности')
|
||||||
|
parser.add_argument('--emb-size', type=int, default=64,
|
||||||
|
help='Размер эмбеддингов')
|
||||||
|
parser.add_argument('--num-heads', type=int, default=4,
|
||||||
|
help='Количество голов внимания')
|
||||||
|
parser.add_argument('--head-size', type=int, default=16,
|
||||||
|
help='Размер головы внимания')
|
||||||
|
parser.add_argument('--num-layers', type=int, default=2,
|
||||||
|
help='Количество слоёв декодера')
|
||||||
|
parser.add_argument('--dropout', type=float, default=0.1,
|
||||||
|
help='Вероятность dropout')
|
||||||
|
|
||||||
|
# Параметры обучения
|
||||||
|
parser.add_argument('--batch-size', type=int, default=4,
|
||||||
|
help='Размер батча')
|
||||||
|
parser.add_argument('--epochs', type=int, default=5,
|
||||||
|
help='Количество эпох')
|
||||||
|
parser.add_argument('--lr', type=float, default=0.0001,
|
||||||
|
help='Learning rate')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Проверяем и создаем директорию для сохранения
|
||||||
|
output_dir = os.path.dirname(args.output)
|
||||||
|
if output_dir and not os.path.exists(output_dir):
|
||||||
|
print(f"Создаем директорию: {output_dir}")
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Загрузка данных
|
||||||
|
with open(args.tokens, 'rb') as f:
|
||||||
|
tokens = pickle.load(f)
|
||||||
|
tokenizer = OptimizeBPE.load(args.tokenizer)
|
||||||
|
device = 'cuda' if torch.cuda.is_available() else 'cpu'
|
||||||
|
|
||||||
|
# Подготовка данных
|
||||||
|
dataset = GetData(data=tokens, seq_len=args.seq_len, device=device)
|
||||||
|
loader = DataLoader(dataset, batch_size=args.batch_size, shuffle=True)
|
||||||
|
|
||||||
|
# Модель (уменьшенные параметры)
|
||||||
|
model = GPT(
|
||||||
|
vocab_size=tokenizer.vocab_size,
|
||||||
|
max_seq_len=args.seq_len,
|
||||||
|
emb_size=args.emb_size,
|
||||||
|
num_heads=args.num_heads,
|
||||||
|
head_size=args.head_size,
|
||||||
|
num_layers=args.num_layers,
|
||||||
|
dropout=args.dropout,
|
||||||
|
device=device
|
||||||
|
)
|
||||||
|
|
||||||
|
# Обучение
|
||||||
|
model.fit(
|
||||||
|
train_loader=loader,
|
||||||
|
num_epoch=args.epochs,
|
||||||
|
learning_rate=args.lr
|
||||||
|
)
|
||||||
|
torch.save(model.state_dict(), args.output)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
38
bin/train_tokenizer.py
Executable file
38
bin/train_tokenizer.py
Executable file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Обучение токенизатора с CLI аргументами
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
from simple_llm.tokenizer.optimize_bpe import OptimizeBPE
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--corpus', type=str, required=True,
|
||||||
|
help='Путь к корпусу текстов')
|
||||||
|
parser.add_argument('--output', type=str, required=True,
|
||||||
|
help='Путь для сохранения токенизатора')
|
||||||
|
parser.add_argument('--vocab-size', type=int, default=4000,
|
||||||
|
help='Размер словаря')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Проверяем и создаем директорию для сохранения
|
||||||
|
output_dir = os.path.dirname(args.output)
|
||||||
|
if output_dir and not os.path.exists(output_dir):
|
||||||
|
print(f"Создаем директорию: {output_dir}")
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Загрузка корпуса
|
||||||
|
corpus = []
|
||||||
|
for file in Path(args.corpus).glob('*.txt'):
|
||||||
|
corpus.append(file.read_text(encoding='utf-8'))
|
||||||
|
corpus = '\n'.join(corpus)
|
||||||
|
|
||||||
|
# Обучение
|
||||||
|
tokenizer = OptimizeBPE(vocab_size=args.vocab_size)
|
||||||
|
tokenizer.fit(corpus)
|
||||||
|
tokenizer.save(args.output)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,50 +0,0 @@
|
|||||||
"""
|
|
||||||
Генерация текста с помощью обученной GPT-модели и токенизатора
|
|
||||||
"""
|
|
||||||
import torch
|
|
||||||
from simple_llm.transformer.gpt import GPT
|
|
||||||
from simple_llm.tokenizer.bpe import BPE
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import torch
|
|
||||||
# Определяем устройство
|
|
||||||
#if torch.cuda.is_available():
|
|
||||||
# device = 'cuda'
|
|
||||||
#elif getattr(torch.backends, 'mps', None) and torch.backends.mps.is_available():
|
|
||||||
# device = 'mps' # Apple Silicon
|
|
||||||
#else:
|
|
||||||
# device = 'cpu'
|
|
||||||
device = 'cpu'
|
|
||||||
print(f"Используется устройство: {device}")
|
|
||||||
|
|
||||||
# Загрузим токенизатор и модель
|
|
||||||
tokenizer = BPE.load('data/tokenizer/bpe_tokenizer.json')
|
|
||||||
model = GPT(
|
|
||||||
vocab_size=tokenizer.vocab_size,
|
|
||||||
max_seq_len=64,
|
|
||||||
emb_size=256,
|
|
||||||
num_heads=4,
|
|
||||||
head_size=64,
|
|
||||||
num_layers=4,
|
|
||||||
device=device
|
|
||||||
)
|
|
||||||
model.load_state_dict(torch.load('data/model/simple_llm_gpt.pth', map_location=device))
|
|
||||||
model.eval()
|
|
||||||
|
|
||||||
# Введите начальный текст
|
|
||||||
prompt = "Привет, мир! "
|
|
||||||
prompt_tokens = tokenizer.encode(prompt)
|
|
||||||
print(f"Токены prompt: {prompt_tokens}")
|
|
||||||
print(f"Размер словаря токенизатора: {tokenizer.vocab_size}")
|
|
||||||
if any(idx >= tokenizer.vocab_size or idx < 0 for idx in prompt_tokens):
|
|
||||||
print("ВНИМАНИЕ: В prompt есть токены с индексом вне диапазона словаря! Генерация невозможна.")
|
|
||||||
exit(1)
|
|
||||||
input_ids = torch.tensor([prompt_tokens], device=device)
|
|
||||||
output = model.generate(
|
|
||||||
x=input_ids,
|
|
||||||
max_new_tokens=30,
|
|
||||||
do_sample=True,
|
|
||||||
temperature=1.0
|
|
||||||
)
|
|
||||||
result = tokenizer.decode(output[0].tolist())
|
|
||||||
print("Сгенерированный текст:", result)
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
"""
|
|
||||||
Токенизация текстового корпуса с помощью обученного BPE-токенизатора
|
|
||||||
"""
|
|
||||||
from simple_llm.tokenizer.bpe import BPE
|
|
||||||
import pickle
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import torch
|
|
||||||
# Определяем устройство
|
|
||||||
#if torch.cuda.is_available():
|
|
||||||
# device = 'cuda'
|
|
||||||
#elif getattr(torch.backends, 'mps', None) and torch.backends.mps.is_available():
|
|
||||||
# device = 'mps' # Apple Silicon
|
|
||||||
#else:
|
|
||||||
# device = 'cpu'
|
|
||||||
device = 'cpu'
|
|
||||||
print(f"Используется устройство: {device}")
|
|
||||||
|
|
||||||
tokenizer = BPE.load('data/tokenizer/bpe_tokenizer.json')
|
|
||||||
with open('data/corpus/corpus.txt', 'r', encoding='utf-8') as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
tokenized = [tokenizer.encode(line) for line in lines]
|
|
||||||
with open('data/tokens/corpus_tokens.pkl', 'wb') as f:
|
|
||||||
pickle.dump(tokenized, f)
|
|
||||||
print("Корпус токенизирован и сохранён в data/corpus_tokens.pkl")
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
"""
|
|
||||||
Обучение GPT-модели на токенизированном корпусе
|
|
||||||
"""
|
|
||||||
import pickle
|
|
||||||
from torch.utils.data import DataLoader
|
|
||||||
from simple_llm.data.get_data import GetData
|
|
||||||
from simple_llm.transformer.gpt import GPT
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import torch
|
|
||||||
# Определяем устройство
|
|
||||||
#if torch.cuda.is_available():
|
|
||||||
# device = 'cuda'
|
|
||||||
#elif getattr(torch.backends, 'mps', None) and torch.backends.mps.is_available():
|
|
||||||
# device = 'mps' # Apple Silicon
|
|
||||||
#else:
|
|
||||||
# device = 'cpu'
|
|
||||||
device = 'cpu'
|
|
||||||
print(f"Используется устройство: {device}")
|
|
||||||
|
|
||||||
with open('data/tokens/corpus_tokens.pkl', 'rb') as f:
|
|
||||||
tokenized = pickle.load(f)
|
|
||||||
all_tokens = [token for line in tokenized for token in line]
|
|
||||||
seq_len = 64
|
|
||||||
dataset = GetData(data=all_tokens, seq_len=seq_len, device=device)
|
|
||||||
loader = DataLoader(dataset, batch_size=32, shuffle=True)
|
|
||||||
|
|
||||||
# Загрузите токенизатор для определения размера словаря
|
|
||||||
from simple_llm.tokenizer.bpe import BPE
|
|
||||||
tokenizer = BPE.load('data/tokenizer/bpe_tokenizer.json')
|
|
||||||
|
|
||||||
model = GPT(
|
|
||||||
vocab_size=tokenizer.vocab_size,
|
|
||||||
max_seq_len=seq_len,
|
|
||||||
emb_size=256,
|
|
||||||
num_heads=4,
|
|
||||||
head_size=64,
|
|
||||||
num_layers=4,
|
|
||||||
device='cpu'
|
|
||||||
)
|
|
||||||
|
|
||||||
model.fit(
|
|
||||||
train_loader=loader,
|
|
||||||
valid_loader=None,
|
|
||||||
num_epoch=10,
|
|
||||||
learning_rate=1e-4
|
|
||||||
)
|
|
||||||
print('Train loss:', model.train_loss)
|
|
||||||
torch.save(model.state_dict(), 'data/model/simple_llm_gpt.pth')
|
|
||||||
print("Модель обучена и сохранена в data/model/simple_llm_gpt.pth")
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
"""
|
|
||||||
Обучение BPE-токенизатора на текстовом корпусе
|
|
||||||
"""
|
|
||||||
from simple_llm.tokenizer.bpe import BPE
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import torch
|
|
||||||
# Определяем устройство
|
|
||||||
#if torch.cuda.is_available():
|
|
||||||
# device = 'cuda'
|
|
||||||
#elif getattr(torch.backends, 'mps', None) and torch.backends.mps.is_available():
|
|
||||||
# device = 'mps' # Apple Silicon
|
|
||||||
#else:
|
|
||||||
# device = 'cpu'
|
|
||||||
device = 'cpu'
|
|
||||||
print(f"Используется устройство: {device}")
|
|
||||||
|
|
||||||
with open('data/corpus/corpus.txt', 'r', encoding='utf-8') as f:
|
|
||||||
texts = f.readlines()
|
|
||||||
tokenizer = BPE(vocab_size=5000)
|
|
||||||
tokenizer.fit(" ".join(texts))
|
|
||||||
tokenizer.save('data/tokenizer/bpe_tokenizer.json')
|
|
||||||
print("Токенизатор обучен и сохранён в data/tokenizer/bpe_tokenizer.json")
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import dill
|
import dill
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
class BPE:
|
class BPE:
|
||||||
"""Реализация алгоритма Byte Pair Encoding (BPE) для токенизации текста.
|
"""Реализация алгоритма Byte Pair Encoding (BPE) для токенизации текста.
|
||||||
@@ -35,25 +36,31 @@ class BPE:
|
|||||||
>>> tokenizer = BPE(vocab_size=100)
|
>>> tokenizer = BPE(vocab_size=100)
|
||||||
>>> tokenizer.fit("Это текст для обучения токенизатора")
|
>>> tokenizer.fit("Это текст для обучения токенизатора")
|
||||||
"""
|
"""
|
||||||
|
# Инициализируем прогресс-бар
|
||||||
|
pbar = tqdm(total=self.vocab_size, desc="Building vocabulary")
|
||||||
# 1. Получаем уникальные токены (символы)
|
# 1. Получаем уникальные токены (символы)
|
||||||
unique_tokens = sorted(set(text))
|
unique_tokens = sorted(set(text))
|
||||||
tokens = unique_tokens.copy()
|
tokens = unique_tokens.copy()
|
||||||
|
pbar.update(len(tokens)) # Обновляем прогресс начальными токенами
|
||||||
|
|
||||||
# 2. Разбиваем текст на токены-символы
|
# 2. Разбиваем текст на токены-символы
|
||||||
sequence = list(text)
|
sequence = list(text)
|
||||||
|
|
||||||
# 3. Объединяем токены до достижения нужного размера словаря
|
# 3. Объединяем токены до достижения нужного размера словаря
|
||||||
while len(tokens) < self.vocab_size:
|
while len(tokens) < self.vocab_size:
|
||||||
|
pbar.update(1) # Обновляем прогресс на каждой итерации
|
||||||
|
print(f"\nТекущий размер словаря: {len(tokens)}/{self.vocab_size}")
|
||||||
#print(f'len={len(tokens)} < {self.vocab_size}')
|
#print(f'len={len(tokens)} < {self.vocab_size}')
|
||||||
# Считаем частоты пар
|
# Считаем частоты пар
|
||||||
pair_freq = {}
|
pair_freq = {}
|
||||||
for i in range(len(sequence) - 1):
|
for i in range(len(sequence) - 1):
|
||||||
pair = (sequence[i], sequence[i + 1])
|
pair = (sequence[i], sequence[i + 1])
|
||||||
#print(f'pair = {pair}')
|
|
||||||
if pair not in pair_freq:
|
if pair not in pair_freq:
|
||||||
pair_freq[pair] = 0
|
pair_freq[pair] = 0
|
||||||
pair_freq[pair] += 1
|
pair_freq[pair] += 1
|
||||||
|
|
||||||
|
print(f"Найдено {len(pair_freq)} уникальных пар")
|
||||||
|
|
||||||
|
|
||||||
#print(f'pair_freq = {pair_freq}')
|
#print(f'pair_freq = {pair_freq}')
|
||||||
if not pair_freq:
|
if not pair_freq:
|
||||||
@@ -64,12 +71,11 @@ class BPE:
|
|||||||
|
|
||||||
# Находим самую частую пару (в случае равенства — та, что встретилась первой)
|
# Находим самую частую пару (в случае равенства — та, что встретилась первой)
|
||||||
most_frequent_pair = max(pair_freq.items(), key=lambda x: (x[1], -self._pair_first_index(sequence, x[0])))[0]
|
most_frequent_pair = max(pair_freq.items(), key=lambda x: (x[1], -self._pair_first_index(sequence, x[0])))[0]
|
||||||
#print(most_frequent_pair)
|
print(f"Самая частая пара: {most_frequent_pair} (встречается {pair_freq[most_frequent_pair]} раз)")
|
||||||
# Создаем новый токен
|
# Создаем новый токен
|
||||||
new_token = most_frequent_pair[0] + most_frequent_pair[1]
|
new_token = most_frequent_pair[0] + most_frequent_pair[1]
|
||||||
#print(f"new token={new_token}")
|
print(f"Добавлен новый токен: '{new_token}'")
|
||||||
tokens.append(new_token)
|
tokens.append(new_token)
|
||||||
#print(f"tokens={tokens}")
|
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
new_sequence = []
|
new_sequence = []
|
||||||
@@ -88,6 +94,7 @@ class BPE:
|
|||||||
self.vocab = tokens.copy()
|
self.vocab = tokens.copy()
|
||||||
self.token2id = dict(zip(tokens, range(self.vocab_size)))
|
self.token2id = dict(zip(tokens, range(self.vocab_size)))
|
||||||
self.id2token = dict(zip(range(self.vocab_size), tokens))
|
self.id2token = dict(zip(range(self.vocab_size), tokens))
|
||||||
|
pbar.close() # Закрываем прогресс-бар
|
||||||
|
|
||||||
def _pair_first_index(self, sequence, pair):
|
def _pair_first_index(self, sequence, pair):
|
||||||
for i in range(len(sequence) - 1):
|
for i in range(len(sequence) - 1):
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import dill
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
@@ -85,3 +86,36 @@ class BPE(ABC):
|
|||||||
else:
|
else:
|
||||||
tokens.append('') # Специальное значение
|
tokens.append('') # Специальное значение
|
||||||
return tokens
|
return tokens
|
||||||
|
|
||||||
|
def save(self, filename):
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
dill.dump(self, f)
|
||||||
|
print(f"Объект сохранён в {filename}")
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, filename):
|
||||||
|
"""Загружает токенизатор из файла.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename (str): Путь к файлу с сохраненным токенизатором
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BPE: Загруженный экземпляр токенизатора
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
>>> tokenizer = BPE.load("bpe_tokenizer.pkl")
|
||||||
|
"""
|
||||||
|
"""Load trained tokenizer from file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename (str): Path to saved tokenizer
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BPE: Loaded tokenizer instance
|
||||||
|
"""
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
obj = dill.load(f)
|
||||||
|
|
||||||
|
print(f"Объект загружен из {filename}")
|
||||||
|
return obj
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from .bpe_interface import BPE
|
from .bpe_interface import BPE
|
||||||
|
from tqdm import tqdm
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from typing import List, Tuple, Dict
|
from typing import List, Tuple, Dict
|
||||||
|
|
||||||
@@ -18,19 +18,34 @@ class OptimizeBPE(BPE):
|
|||||||
self._init_vocab(sequence)
|
self._init_vocab(sequence)
|
||||||
pair_freq, pair_first_occurrence = self._get_pair_stats(sequence)
|
pair_freq, pair_first_occurrence = self._get_pair_stats(sequence)
|
||||||
|
|
||||||
while len(self.vocab) < self.vocab_size and pair_freq:
|
# Инициализация прогресс-бара
|
||||||
pair_to_merge = self._select_pair_to_merge(pair_freq, pair_first_occurrence)
|
with tqdm(total=self.vocab_size, desc="Building vocabulary") as pbar:
|
||||||
new_token = pair_to_merge[0] + pair_to_merge[1]
|
pbar.update(len(self.vocab)) # Учитываем начальные токены
|
||||||
|
|
||||||
if new_token in self.vocab:
|
while len(self.vocab) < self.vocab_size and pair_freq:
|
||||||
# Защита от зацикливания: пара уже была добавлена как новый токен.
|
pair_to_merge = self._select_pair_to_merge(pair_freq, pair_first_occurrence)
|
||||||
del pair_freq[pair_to_merge]
|
new_token = pair_to_merge[0] + pair_to_merge[1]
|
||||||
continue
|
|
||||||
|
|
||||||
self.vocab.append(new_token)
|
# Обновляем прогресс и логируем
|
||||||
sequence, pair_freq, pair_first_occurrence = self._merge_pair(
|
pbar.update(1)
|
||||||
sequence, pair_to_merge, new_token, pair_freq
|
pbar.set_postfix({
|
||||||
)
|
'current_vocab': len(self.vocab),
|
||||||
|
'top_pair': f"{pair_to_merge[0]}{pair_to_merge[1]}",
|
||||||
|
'pair_freq': pair_freq[pair_to_merge]
|
||||||
|
})
|
||||||
|
print(f"\nТекущий размер словаря: {len(self.vocab)}/{self.vocab_size}")
|
||||||
|
print(f"Самая частая пара: {pair_to_merge} (встречается {pair_freq[pair_to_merge]} раз)")
|
||||||
|
print(f"Добавлен новый токен: '{new_token}'")
|
||||||
|
|
||||||
|
if new_token in self.vocab:
|
||||||
|
# Защита от зацикливания: пара уже была добавлена как новый токен.
|
||||||
|
del pair_freq[pair_to_merge]
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.vocab.append(new_token)
|
||||||
|
sequence, pair_freq, pair_first_occurrence = self._merge_pair(
|
||||||
|
sequence, pair_to_merge, new_token, pair_freq
|
||||||
|
)
|
||||||
|
|
||||||
self._build_token_dicts()
|
self._build_token_dicts()
|
||||||
|
|
||||||
|
|||||||
@@ -333,6 +333,9 @@ class GPT(nn.Module):
|
|||||||
>>> # Обучаем модель
|
>>> # Обучаем модель
|
||||||
>>> model.fit(loader, num_epoch=5, learning_rate=0.001)
|
>>> model.fit(loader, num_epoch=5, learning_rate=0.001)
|
||||||
"""
|
"""
|
||||||
|
from tqdm import tqdm
|
||||||
|
import time
|
||||||
|
|
||||||
if train_loader is None:
|
if train_loader is None:
|
||||||
raise ValueError("train_loader не может быть None")
|
raise ValueError("train_loader не может быть None")
|
||||||
if num_epoch <= 0:
|
if num_epoch <= 0:
|
||||||
@@ -345,12 +348,23 @@ class GPT(nn.Module):
|
|||||||
|
|
||||||
optimizer = torch.optim.Adam(self.parameters(), lr=learning_rate)
|
optimizer = torch.optim.Adam(self.parameters(), lr=learning_rate)
|
||||||
|
|
||||||
|
print(f"\nНачало обучения GPT на {num_epoch} эпох")
|
||||||
|
print(f"Размер батча: {train_loader.batch_size}")
|
||||||
|
print(f"Всего батчей: {len(train_loader)}")
|
||||||
|
print(f"Устройство: {device}\n")
|
||||||
|
|
||||||
for epoch in range(num_epoch):
|
for epoch in range(num_epoch):
|
||||||
self.train()
|
self.train()
|
||||||
epoch_loss = 0.0
|
epoch_loss = 0.0
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
#for inputs, targets in tqdm(train_loader, desc=f"Epoch {epoch+1}/{num_epoch}"):
|
# Прогресс-бар для батчей
|
||||||
for inputs, targets in train_loader:
|
batch_pbar = tqdm(train_loader,
|
||||||
|
desc=f"Эпоха {epoch+1}/{num_epoch}",
|
||||||
|
leave=False,
|
||||||
|
bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]')
|
||||||
|
|
||||||
|
for batch_idx, (inputs, targets) in enumerate(batch_pbar):
|
||||||
inputs = inputs.to(device)
|
inputs = inputs.to(device)
|
||||||
targets = targets.to(device)
|
targets = targets.to(device)
|
||||||
|
|
||||||
@@ -365,14 +379,32 @@ class GPT(nn.Module):
|
|||||||
|
|
||||||
epoch_loss += loss.item()
|
epoch_loss += loss.item()
|
||||||
|
|
||||||
|
# Обновляем описание прогресс-бара
|
||||||
|
batch_pbar.set_postfix({
|
||||||
|
'loss': f"{loss.item():.4f}",
|
||||||
|
'lr': f"{learning_rate:.0e}"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Логирование каждые N батчей
|
||||||
|
if batch_idx % 10 == 0:
|
||||||
|
tqdm.write(f"Батч {batch_idx}/{len(train_loader)} - Loss: {loss.item():.4f}")
|
||||||
|
|
||||||
self.train_loss = epoch_loss / len(train_loader)
|
self.train_loss = epoch_loss / len(train_loader)
|
||||||
#print(f"[{epoch+1}/{num_epoch}] Train Loss: {self.train_loss:.4f}", end='')
|
epoch_time = time.time() - start_time
|
||||||
|
|
||||||
|
print(f"\nЭпоха {epoch+1}/{num_epoch} завершена за {epoch_time:.2f} сек")
|
||||||
|
print(f"Средний Train Loss: {self.train_loss:.4f}")
|
||||||
|
|
||||||
if valid_loader is not None:
|
if valid_loader is not None:
|
||||||
self.eval()
|
self.eval()
|
||||||
valid_loss = 0.0
|
valid_loss = 0.0
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
for inputs, targets in valid_loader:
|
# Прогресс-бар для валидации
|
||||||
|
valid_pbar = tqdm(valid_loader,
|
||||||
|
desc=f"Валидация {epoch+1}/{num_epoch}",
|
||||||
|
leave=False)
|
||||||
|
|
||||||
|
for inputs, targets in valid_pbar:
|
||||||
inputs = inputs.to(device)
|
inputs = inputs.to(device)
|
||||||
targets = targets.to(device)
|
targets = targets.to(device)
|
||||||
|
|
||||||
@@ -384,4 +416,4 @@ class GPT(nn.Module):
|
|||||||
valid_loss += loss.item()
|
valid_loss += loss.item()
|
||||||
|
|
||||||
self.validation_loss = valid_loss / len(valid_loader)
|
self.validation_loss = valid_loss / len(valid_loader)
|
||||||
#print(f" | Val Loss: {self.validation_loss:.4f}")
|
print(f"Средний Val Loss: {self.validation_loss:.4f}")
|
||||||
Reference in New Issue
Block a user