Токенизатор и данные
BPETokenizer
Заголовок раздела «BPETokenizer»llm.tokenizers.BPETokenizer — символьный BPE: словарь начинается с символов корпуса, слияния учатся внутри слов (текст заранее разбивается на слова, как в GPT-2: пробел прикрепляется к началу следующего слова, пунктуация — отдельно). Новый текст кодируется применением выученных слияний по порядку ранга, как в GPT-2 и HuggingFace. Как это работает — в главе Токенизация.
from llm.tokenizers import BPETokenizer
texts = ["Нейронные сети учатся на данных.", "Трансформеры обрабатывают последовательности."]
tokenizer = BPETokenizer()tokenizer.train(texts=texts, vocab_size=300, special_tokens=["<pad>", "<unk>", "<bos>", "<eos>"])
ids = tokenizer.encode("Нейронные сети") # список intids_with_bos_eos = tokenizer.encode("Нейронные сети", add_special_tokens=True)text = tokenizer.decode(ids) # "Нейронные сети"
tokenizer.save("bpe_tokenizer.json")tokenizer = BPETokenizer.load("bpe_tokenizer.json")print(tokenizer.get_vocab_size(), tokenizer.pad_token_id, tokenizer.eos_token_id)Что важно знать:
vocab_sizeвtrain— число обученных токенов без специальных; итоговый словарь —tokenizer.get_vocab_size(). Его и передавайте в конфиг модели, иначе id специальных токенов выйдут за пределы матрицы эмбеддингов.- На маленьком корпусе обучение останавливается раньше
vocab_size, когда каждое слово уже стало одним токеном. - Специальные токены добавляются в конец словаря, поэтому
pad_token_id— не 0. Внутри текста они не распознаются: строка"<eos>"кодируется как обычные символы; вставить специальный токен можно только по id. - Неизвестный символ (которого не было в корпусе) кодируется как
<unk>. Без<unk>вspecial_tokens—ValueErrorс перечнем таких символов.decodeпо умолчанию выбрасывает специальные токены, включая<unk>, какskip_special_tokensв HF; увидеть их —decode(ids, skip_special_tokens=False). - Модель и токенизатор — пара. Модель, обученная с одним словарём, бессмысленна с другим. Для чужих весов нужен их токенизатор (например, из
transformers).
Датасеты
Заголовок раздела «Датасеты»Три датасета из llm.datasets принимают список строк, токенизатор и block_size и возвращают словари с тремя тензорами формы [block_size]:
| Ключ | Что внутри |
|---|---|
input_ids | токены строки; длиннее block_size — обрезаются, короче — дополняются pad_token_id справа |
attention_mask | 1 для настоящих токенов, 0 для паддинга |
labels | копия input_ids, на паддинге -100 — такие позиции не входят в loss |
| Класс | Когда токенизирует | Особенность |
|---|---|---|
TextDataset | один раз, в конструкторе | — |
StreamingTextDataset | при каждом обращении | строки по-прежнему лежат в памяти списком |
TextWithSpecialTokensDataset | в конструкторе | add_bos=True / add_eos=True добавляют BOS и EOS; при обрезке для них оставляется место |
from llm.datasets.text_dataset import TextDataset
dataset = TextDataset(texts, tokenizer, block_size=32)item = dataset[0]print(item["input_ids"].shape, item["attention_mask"].sum(), (item["labels"] == -100).sum())- Каждая строка — один пример. Датасеты не склеивают тексты и не режут длинный текст на блоки: всё после
block_sizeтокенов теряется. Для длинных документов разбейте их на строки сами. - Сдвиг меток делает не датасет, а
Trainer: метки — копия входа, логит позиции t сравнивается с меткой t + 1. Не сдвигайте метки сами, иначе модель будет учиться предсказывать токен через один. - Свой датасет должен возвращать
input_idsиlabels(и, если есть паддинг,attention_mask), с-100на позициях, которые не должны входить в loss. Паддинг отмечайте по месту, а не сравнением сpad_token_id: pad может совпадать с настоящим токеном.
Учебный корпус
Заголовок раздела «Учебный корпус»В experiments/shared/configs.py лежит встроенный корпус TRAIN_TEXTS — 15 коротких русских предложений; load_training_data() из experiments/shared/data.py делит его 80/20. Он нужен, чтобы проверить, что обучение и генерация работают, а не чтобы получить качество: на 12 строках модель запоминает обучающие данные, валидационный loss остаётся около ln V. Для настоящих экспериментов подставьте свой корпус — список строк.