Рефакторинг и улучшение компонентов

Основные изменения в коде:

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:
Sergey Penkovsky
2025-07-24 12:58:59 +03:00
parent 6ce048d4ad
commit cc4138aba8
19 changed files with 515 additions and 338 deletions

View File

@@ -1,4 +1,5 @@
import dill
from tqdm import tqdm
class BPE:
"""Реализация алгоритма Byte Pair Encoding (BPE) для токенизации текста.
@@ -35,24 +36,30 @@ class BPE:
>>> tokenizer = BPE(vocab_size=100)
>>> tokenizer.fit("Это текст для обучения токенизатора")
"""
# Инициализируем прогресс-бар
pbar = tqdm(total=self.vocab_size, desc="Building vocabulary")
# 1. Получаем уникальные токены (символы)
unique_tokens = sorted(set(text))
tokens = unique_tokens.copy()
pbar.update(len(tokens)) # Обновляем прогресс начальными токенами
# 2. Разбиваем текст на токены-символы
sequence = list(text)
# 3. Объединяем токены до достижения нужного размера словаря
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}')
# Считаем частоты пар
pair_freq = {}
for i in range(len(sequence) - 1):
pair = (sequence[i], sequence[i + 1])
#print(f'pair = {pair}')
if pair not in pair_freq:
pair_freq[pair] = 0
pair_freq[pair] += 1
print(f"Найдено {len(pair_freq)} уникальных пар")
#print(f'pair_freq = {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]
#print(most_frequent_pair)
print(f"Самая частая пара: {most_frequent_pair} (встречается {pair_freq[most_frequent_pair]} раз)")
# Создаем новый токен
new_token = most_frequent_pair[0] + most_frequent_pair[1]
#print(f"new token={new_token}")
print(f"Добавлен новый токен: '{new_token}'")
tokens.append(new_token)
#print(f"tokens={tokens}")
i = 0
new_sequence = []
@@ -88,6 +94,7 @@ class BPE:
self.vocab = tokens.copy()
self.token2id = dict(zip(tokens, range(self.vocab_size)))
self.id2token = dict(zip(range(self.vocab_size), tokens))
pbar.close() # Закрываем прогресс-бар
def _pair_first_index(self, sequence, pair):
for i in range(len(sequence) - 1):

View File

@@ -1,3 +1,4 @@
import dill
from abc import ABC, abstractmethod
from typing import List, Dict
@@ -84,4 +85,37 @@ class BPE(ABC):
tokens.append(self.id2token[id])
else:
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

View File

@@ -1,5 +1,5 @@
from .bpe_interface import BPE
from tqdm import tqdm
from collections import Counter
from typing import List, Tuple, Dict
@@ -18,19 +18,34 @@ class OptimizeBPE(BPE):
self._init_vocab(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)
new_token = pair_to_merge[0] + pair_to_merge[1]
# Инициализация прогресс-бара
with tqdm(total=self.vocab_size, desc="Building vocabulary") as pbar:
pbar.update(len(self.vocab)) # Учитываем начальные токены
if new_token in self.vocab:
# Защита от зацикливания: пара уже была добавлена как новый токен.
del pair_freq[pair_to_merge]
continue
while len(self.vocab) < self.vocab_size and pair_freq:
pair_to_merge = self._select_pair_to_merge(pair_freq, pair_first_occurrence)
new_token = pair_to_merge[0] + pair_to_merge[1]
# Обновляем прогресс и логируем
pbar.update(1)
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}'")
self.vocab.append(new_token)
sequence, pair_freq, pair_first_occurrence = self._merge_pair(
sequence, pair_to_merge, new_token, pair_freq
)
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()