Перейти к содержимому
Ступень 44.5 Архитектура трансформера

Энкодеры (BERT) и рабочий цикл Hugging Face

Как устроен и обучается BERT — маскирование 80/10/10, токены [CLS] и [SEP], отказ от NSP в RoBERTa — и как дообучить энкодер под классификацию через Hub, AutoModel, datasets и Trainer. Когда маленький энкодер выгоднее LLM.

УРОКstage-4/4-5-transformer/04-bert-and-hf.mdx2 чУрок 4 из 4
После урока вы сможете
  • Объяснить цель MLM, схему маскирования 80/10/10 и почему loss считается только по выбранным позициям
  • Описать вход BERT ([CLS], [SEP], сегменты) и как поверх энкодера строятся головы для классификации и NER
  • Пройти рабочий цикл Hugging Face — Hub и карточка модели, AutoTokenizer и AutoModel, datasets, Trainer — и дообучить русский энкодер
  • Сравнить «дообучить энкодер» и «спросить LLM без примеров» по качеству, цене и задержке

В уроке 4.5.1 вы узнали, что из одного блока трансформера выросли три семейства, и победил decoder-only. Но энкодеры никуда не делись. Каждый раз, когда поисковик находит документ по смыслу, почтовый фильтр отсеивает спам, а RAG-система (ступень 8) подбирает фрагменты для ответа, почти наверняка работает encoder-onlyencoder-only (только энкодер)EN encoder-only architectureДвунаправленное внимание, обучение через маскированное языковое моделирование (BERT, RoBERTa). Сегодня — модели эмбеддингов, реранкеры, классификаторы.-модель семейства BERTBERTEN BERT (Bidirectional Encoder Representations from Transformers)Энкодерный трансформер Google (2018) с двунаправленной маской, предобученный на MLM. Основа классификаторов, NER, эмбеддеров и реранкеров (BGE, multilingual-e5 — его потомки).. Она в сотни раз меньше LLM, отвечает за миллисекунды и легко дообучается под конкретную задачу.

Второй повод для урока практический. Почти всё, что вы будете делать дальше на своих видеокартах — LoRA для Qwen (ступень 6), квантование (ступень 7), эмбеддинги для RAG (ступень 8), — начинается с одних и тех же строк: AutoTokenizer.from_pretrained, load_dataset, Trainer. Удобнее всего освоить их на маленьком энкодере, где обучение занимает минуты, а ошибки видны сразу.

BERT (Google, 2018) — это стопка блоков трансформера с двунаправленной маской: каждый токен видит весь текст. В базовой версии 12 блоков, ширина 768, 12 голов и около 110 млн параметров; в большой — 24 блока, ширина 1024 и около 340 млн. Обучали её на английской Википедии и корпусе книг BooksCorpus, словарь WordPieceWordPieceEN WordPieceПодсловный токенизатор BERT, родственник BPE. Продолжение слова помечается «##»: playing → play ##ing. Словарь BERT — около 30 тысяч подслов. из 30 тысяч подслов. WordPiece похож на BPE из урока 4.2.2, а продолжение слова помечается двумя решётками: «playing» → play ##ing.

Вход всегда собирается по одному шаблону:

[CLS] мама мыла раму [SEP] рама была чистая [SEP]
0 0 0 0 0 1 1 1 1 ← номер сегмента
  • токен [CLS]токен [CLS]EN [CLS] tokenСпецтокен в начале входа BERT. Через внимание собирает информацию со всего текста; его итоговый вектор используют для классификации и как эмбеддинг текста (BGE). Фрагменты разделяет и закрывает токен [SEP]. стоит первым. Своего смысла у него нет, но за 12 блоков внимания он собирает информацию со всего входа, и его итоговый вектор служит представлением текста целиком. Поверх него ставят классификатор.
  • [SEP] (separator) разделяет фрагменты и закрывает вход. Многие задачи устроены как пара текстов: вопрос и абзац, два предложения для сравнения, запрос и документ для реранкера (ступень 8).
  • Сегментные эмбеддинги — третья таблица рядом с таблицей токенов и позиций: вектор «я из фрагмента A» или «я из фрагмента B» прибавляется к эмбеддингу каждого токена. В Hugging Face их номера называются token_type_ids.

Итак, эмбеддинг на входе — сумма трёх строк из трёх таблиц: токен + позиция + сегмент. Дальше — обычные блоки из урока 4.5.1, только без причинной маски.

Маскированное языковое моделирование и 80/10/10

Заголовок раздела «Маскированное языковое моделирование и 80/10/10»

GPT учится предсказывать следующий токен. Для BERT такая цель бессмысленна: при двунаправленной маске токен видит самого себя, и угадать его тривиально. Поэтому BERT учат маскированному языковому моделированиюмаскированное языковое моделирование (MLM)EN masked language modeling (MLM)Цель обучения BERT — закрыть около 15% токенов и угадать их по контексту с обеих сторон. Сигнал дают только замаскированные позиции. (MLM): часть токенов портят, а модель восстанавливает исходные по контексту с обеих сторон. Как в школьном упражнении «вставьте пропущенное слово».

Рецепт из статьи:

  1. Среди обычных токенов (не [CLS], не [SEP], не [PAD]) случайно выбираем 15% — это цели.
  2. Из выбранных 80% заменяем на [MASK], 10% — на случайный токен словаря, 10% оставляем как есть.
  3. Loss — cross-entropy только по выбранным позициям. Остальным позициям ставят метку −100, и функция потерь их пропускает (так договорились PyTorch и Hugging Face).

Пример. В тексте 200 обычных токенов. Целей будет около 0.15⋅200=300.15 \cdot 200 = 30: около 24 станут [MASK], 3 — случайными словами, 3 останутся прежними. Loss усредняется по этим 30 позициям, остальные 170 сигнала не дают.

Зачем такие сложности, почему не 100% [MASK]? На дообучении и в работе токена [MASK] во входе не бывает. Если бы цель всегда пряталась под [MASK], модели было бы достаточно хорошо понимать позиции с [MASK], а для обычных токенов строить представления «как получится». С заменами модель не знает, какой из видимых токенов испорчен, и вынуждена держать осмысленный вектор для каждого. В приложении к статье авторы сравнили несколько пропорций, и 80/10/10 оказалась удачным компромиссом.

Цена. Из каждых 100 токенов сигнал дают только 15. Decoder-only получает сигнал со всех ста — один из доводов в его пользу из урока 4.5.1.

Вторая цель BERT — предсказание следующего предложенияпредсказание следующего предложения (NSP)EN next sentence prediction (NSP)Вторая цель предобучения BERT — угадать, идёт ли фрагмент B в тексте сразу после A. RoBERTa (2019) показала, что она не нужна, и от неё отказались. (NSP). В половине примеров фрагмент B действительно шёл в тексте после A, в половине — взят из случайного документа. Классификатор поверх [CLS] угадывает, какой случай. Идея была научить модель связям между предложениями.

В 2019 году Facebook AI выпустил RoBERTa: тот же BERT, но обученный «как следует». Авторы убрали NSP и подавали непрерывные куски текста до 512 токенов, маскировали динамически (новый набор масок при каждом проходе по данным, а не один раз при подготовке), взяли большие батчи и примерно в десять раз больше текста (160 ГБ против 16 ГБ). Модель заметно обошла BERT, а отказ от NSP качества не ухудшил. С тех пор NSP почти не используют: от неё отказались и RoBERTa, и XLM-R, на которой построена multilingual-e5.

Предобученный энкодер выдаёт по вектору на каждый токен. Чтобы решить задачу, сверху ставят маленькую голову и дообучают всё вместе (дообучение, тонкая настройка, файнтюнингдообучение, тонкая настройка, файнтюнингEN fine-tuning):

flowchart TB
T["[CLS] отличный товар [SEP]"] --> E["Энкодер BERT (предобучен)"]
E --> C["вектор [CLS]"] --> H1["Linear(768 → 3)<br/>негатив / нейтр. / позитив"]
E --> TOK["вектор каждого токена"] --> H2["Linear(768 → число тегов)<br/>B-PER, I-PER, O, …"]
Две типовые головы поверх одного энкодера: классификация текста по [CLS] и разметка каждого токена (NER)
  • Классификация текста (тональность, тема, спам). Голова классификацииголова классификацииEN classification headНебольшой слой (обычно линейный) поверх предобученного энкодера, который превращает вектор [CLS] в логиты классов. Инициализируется случайно и обучается при дообучении. — линейный слой поверх вектора [CLS] с числом выходов, равным числу классов. В Hugging Face это AutoModelForSequenceClassification.
  • Разметка токенов, например распознавание именованных сущностейраспознавание именованных сущностей (NER)EN named entity recognition (NER)Разметка каждого токена тегом: имя, организация, место или «не сущность». Схема BIO: B-PER — начало имени, I-PER — продолжение, O — вне сущности. (NER): найти в тексте имена, организации, места. Линейный слой применяют к вектору каждого токена и предсказывают тег: B-PER (начало имени), I-PER (продолжение), O (не сущность). «Анна Каренина приехала в Москву» → B-PER I-PER O O B-LOC. Тонкость: WordPiece может разбить «Каренина» на три подслова, а тег один. Обычно тег ставят первому подслову, остальным — −100. В Hugging Face это AutoModelForTokenClassification.

Голова инициализируется случайно, энкодер — предобученными весами. Учат всё вместе с маленьким learning rate (обычно 2⋅10−52 \cdot 10^{-5} – 5⋅10−55 \cdot 10^{-5}) 2–4 эпохи: большой шаг быстро «сотрёт» то, что энкодер выучил на претрейне.

Тональность отзыва можно определить двумя способами: дообучить маленький энкодер на размеченных примерах или спросить LLM без примеров (zero-shotzero-shot (без примеров)EN zero-shotРешение задачи без обучающих примеров и без дообучения — только по описанию задачи в промпте. С несколькими примерами в промпте — few-shot.). Сравним по существу:

Дообученный энкодер (rubert-tiny2, ruBERT)LLM без дообучения (Qwen3-8B через Ollama)
Данныенужны размеченные примеры, обычно от тысячне нужны, достаточно описать задачу словами
Качество на своей узкой задачеобычно выше: модель выучила именно вашу разметкухорошее «из коробки», но разметку понимает по-своему
Размер и железодесятки–сотни млн параметров, работает на CPUмиллиарды параметров, нужна видеокарта
Задержка на один текстмиллисекундыот сотен миллисекунд до секунд
Смена задачиновая разметка и дообучениепереписать промпт

Практическое правило: прототип — на LLM, продакшн с миллионами однотипных запросов — на энкодере. А разметку для энкодера нередко делает та же LLM: это дистилляция, о ней — в уроке 6.4.4.

Hugging Face — компания и одновременно экосистема. Нам нужны четыре части.

1. Hub (huggingface.co) — хранилище моделей и датасетов, своего рода GitHub для весов. У каждой модели есть карточка моделикарточка модели (model card)EN model cardДокумент к модели: назначение, где применять нельзя, данные, метрики (в том числе по группам), ограничения (Mitchell и др., 2019).: для чего обучена, на каких данных, как вызывать, какие ограничения и лицензия. Лицензию читайте до того, как строить на модели продукт: Apache-2.0 и MIT разрешают почти всё, CC-BY-NC запрещает коммерческое использование, у Llama — собственная лицензия с условиями. Модель называют по схеме организация/имя: cointegrated/rubert-tiny2, BAAI/bge-m3, Qwen/Qwen3-8B.

2. transformers: AutoTokenizer и AutoModel. В репозитории модели лежит config.json с названием архитектуры. Auto-классы transformersAuto-классы transformersEN Auto classes (AutoTokenizer, AutoModel)Классы библиотеки transformers, которые читают config.json модели и сами создают нужную архитектуру: AutoTokenizer, AutoModel, AutoModelForSequenceClassification, AutoModelForCausalLM и др. читают его и сами создают нужный класс:

from transformers import AutoTokenizer, AutoModel, AutoModelForSequenceClassification
tok = AutoTokenizer.from_pretrained("cointegrated/rubert-tiny2") # скачает словарь и настройки токенизатора
enc = tok("Отличный товар, быстрая доставка!")
print(enc.keys()) # input_ids, token_type_ids, attention_mask
print(tok.convert_ids_to_tokens(enc["input_ids"])) # [CLS] … [SEP] — шаблон добавлен сам
encoder = AutoModel.from_pretrained("cointegrated/rubert-tiny2") # только энкодер: скрытые состояния
clf = AutoModelForSequenceClassification.from_pretrained(
"cointegrated/rubert-tiny2", num_labels=3) # энкодер + новая голова; transformers предупредит,
# что веса classifier инициализированы случайно — так и надо

Первый вызов скачивает файлы в кэш (~/.cache/huggingface), следующие берут их с диска. attention_mask — единицы на настоящих токенах и нули на [PAD]: при батчевой обработке короткие тексты дополняются, и внимание не должно смотреть на заполнитель.

3. datasets. load_dataset("организация/имя") скачивает датасет и хранит его на диске в формате Arrow: даже миллионы строк не занимают оперативную память. Главные операции:

  • ds.map(f, batched=True) — применить функцию (обычно токенизацию) ко всем строкам пачками; результат кэшируется, второй запуск мгновенный;
  • ds.filter(...), ds.train_test_split(test_size=0.1, seed=0) — отбор и разбиение;
  • load_dataset(..., streaming=True) — не скачивать целиком, а читать по мере надобности. Так работают с корпусами в сотни гигабайт для претрейна (урок 5.1.2).

4. Trainer. Готовый цикл обучения: батчи, AdamW, разогрев learning rate, клиппинг, оценка на валидации, сохранение чекпоинтов, смешанная точность. Всё это вы напишете руками в уроке 4.6.3, а пока достаточно знать, что Trainer делает это за вас. Настраивается он через TrainingArguments, а метрики считает ваша функция compute_metrics. Для метрик есть библиотека evaluate, но evaluate.load("accuracy") скачивает код метрики с Hub, поэтому простую точность мы посчитаем сами.

Реализуйте сборку входа BERT для пары фрагментов, маскирование MLM и loss только по маскированным позициям. Тесты проверяют шаблон [CLS]/[SEP], что спецтокены не трогаются, частоты 15% и 80/10/10 на большом батче и что логиты в позициях с меткой −100 не влияют на loss.

Маскирование MLM 80/10/10numpy · нужен интернет7 тестов
import numpy as np

# Игрушечный словарь в стиле BERT: первые пять номеров — спецтокены, дальше обычные слова.
SPECIAL = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]
PAD_ID, UNK_ID, CLS_ID, SEP_ID, MASK_ID = range(5)
WORDS = "мама мыла раму рама была чистая кот сидел на окне а пёс спал у двери".split()
VOCAB = SPECIAL + WORDS
STOI = {w: i for i, w in enumerate(VOCAB)}
IGNORE = -100   # метка «не считать loss в этой позиции» — как в PyTorch и Hugging Face


def build_pair(words_a, words_b, max_len):
    """Собирает вход BERT для пары фрагментов: [CLS] A [SEP] B [SEP], затем [PAD] до max_len.

    Возвращает три списка длины max_len:
      input_ids       — номера токенов (STOI; неизвестное слово → UNK_ID);
      token_type_ids  — 0 для [CLS], A и первого [SEP]; 1 для B и второго [SEP]; 0 для [PAD];
      special_mask    — 1 там, где стоит спецтокен ([CLS], [SEP], [PAD]), иначе 0.
    Если пара не помещается в max_len, бросьте ValueError.
    """
    # TODO: ваш код
    ...


def mlm_mask(input_ids, special_mask, rng, p=0.15):
    """Маскирование BERT 80/10/10. input_ids и special_mask — numpy-массивы одной формы (любой).

    1) Каждая НЕспециальная позиция выбирается с вероятностью p: rng.random(shape) < p.
    2) labels = исходный номер в выбранных позициях и IGNORE (−100) во всех остальных.
    3) Среди выбранных позиций: 80% заменяем на MASK_ID, 10% — на случайное ОБЫЧНОЕ слово
       (равномерно из номеров len(SPECIAL) … len(VOCAB) − 1), 10% оставляем как есть.
    Возвращает пару (inputs, labels) — НОВЫЕ массивы; исходный input_ids не меняйте.
    rng — np.random.Generator; используйте только его.
    """
    # TODO: ваш код
    ...


def mlm_loss(logits, labels):
    """Средняя cross-entropy только по позициям, где labels != IGNORE.

    logits — массив (..., V), labels — массив (...). Считайте устойчиво (вычитайте максимум перед exp).
    Если ни одной позиции для loss нет, верните 0.0.
    """
    # TODO: ваш код
    ...


# ---------- Кнопка «Запустить» ----------
def demo():
    ids, types, special = build_pair("мама мыла раму".split(), "рама была чистая".split(), max_len=12)
    print("токены:  ", [VOCAB[i] for i in ids])
    print("сегменты:", types)
    rng = np.random.default_rng(0)
    batch = np.array([ids] * 4)
    inputs, labels = mlm_mask(batch, np.array([special] * 4), rng, p=0.3)
    for row_in, row_lab in zip(inputs, labels):
        print(" ".join(VOCAB[i] for i in row_in), "| цели:", [VOCAB[l] for l in row_lab if l != IGNORE])
    logits = np.zeros(inputs.shape + (len(VOCAB),))                # «модель», которая ничего не знает
    print("loss необученной модели:", round(mlm_loss(logits, labels), 3), " ln V =", round(float(np.log(len(VOCAB))), 3))


try:
    demo()
except Exception as e:  # пока функции не дописаны
    print("Допишите функции:", type(e).__name__, e)

То же самое в Hugging Face делает DataCollatorForLanguageModeling(tokenizer, mlm_probability=0.15): у него есть параметры mask_replace_prob=0.8 и random_replace_prob=0.1, а флаг whole_word_mask=True маскирует слова целиком (см. «Китайский угол»). Поскольку коллатор маскирует батч при каждой выдаче, маскирование получается динамическим, как в RoBERTa.

На вашей машине: дообучаем русский энкодер на отзывах

Заголовок раздела «На вашей машине: дообучаем русский энкодер на отзывах»

Возьмём маленький русский BERT cointegrated/rubert-tiny2 и датасет отзывов о товарах ai-forever/ru-reviews-classification из бенчмарка MTEB: три класса тональности, 45 000 обучающих отзывов по 15 000 на класс, в среднем около 130 символов. Поставьте библиотеки (pip install transformers datasets accelerate requests; про версии — урок 4.6.5) и запустите:

"""finetune_rubert.py — дообучение rubert-tiny2 на тональность отзывов. python finetune_rubert.py"""
import numpy as np
from datasets import load_dataset
from transformers import (AutoModelForSequenceClassification, AutoTokenizer, DataCollatorWithPadding,
Trainer, TrainingArguments)
MODEL = "cointegrated/rubert-tiny2"
ds = load_dataset("ai-forever/ru-reviews-classification")
print(ds) # какие части и колонки есть: text, label
for lab in range(3): # посмотрите глазами, какой номер какому классу
print(lab, ds["train"].filter(lambda r: r["label"] == lab)[0]["text"][:100])
split = ds["train"].train_test_split(test_size=0.1, seed=0) # 10% train → валидация для выбора эпохи
train_ds, val_ds, test_ds = split["train"], split["test"], ds["test"]
tok = AutoTokenizer.from_pretrained(MODEL)
def tokenize(batch):
return tok(batch["text"], truncation=True, max_length=256) # длинные отзывы обрезаем
train_ds, val_ds, test_ds = (d.map(tokenize, batched=True) for d in (train_ds, val_ds, test_ds))
id2label = {0: "негатив", 1: "нейтрально", 2: "позитив"} # проверьте порядок по выводу выше!
model = AutoModelForSequenceClassification.from_pretrained(
MODEL, num_labels=3, id2label=id2label, label2id={v: k for k, v in id2label.items()})
def compute_metrics(eval_pred):
logits, labels = eval_pred
return {"accuracy": float((np.argmax(logits, axis=-1) == labels).mean())}
args = TrainingArguments(
output_dir="rubert-reviews",
per_device_train_batch_size=64,
per_device_eval_batch_size=256,
learning_rate=5e-5,
num_train_epochs=3,
warmup_steps=0.1, # дробное число = доля шагов на разогрев lr (подробно — урок 4.6.3)
weight_decay=0.01,
eval_strategy="epoch", # оценка на val после каждой эпохи
save_strategy="epoch",
save_total_limit=1,
load_best_model_at_end=True, # в конце вернуть веса лучшей эпохи по val
metric_for_best_model="accuracy",
bf16=True, # смешанная точность на RTX 4080; на CPU уберите
logging_steps=100,
)
trainer = Trainer(model=model, args=args, train_dataset=train_ds, eval_dataset=val_ds,
processing_class=tok, # нужен для DataCollatorWithPadding и сохранения
data_collator=DataCollatorWithPadding(tok), # дополняет батч до самого длинного текста в нём
compute_metrics=compute_metrics)
trainer.train()
print(trainer.evaluate(test_ds)) # test смотрим один раз, в конце
trainer.save_model("rubert-reviews/best")

Что увидите. Каждые 100 шагов Trainer печатает loss, grad_norm и learning_rate, после каждой эпохи — eval_loss и eval_accuracy на валидации, в конце — словарь метрик на test. Мы этот скрипт на видеокарте не запускали, поэтому дадим ориентиры, а не точные числа. Случайное угадывание даёт 33%. Нейтральный класс у отзывов размыт («нормально, но доставка долгая»), поэтому ждите точность заметно ниже 100%; разумный результат для маленькой модели — порядка 70–80%. Время на одной RTX 4080 — несколько минут на эпоху (оценка), на CPU — десятки минут.

Готовую модель удобно вызывать через pipeline:

from transformers import pipeline
clf = pipeline("text-classification", model="rubert-reviews/best")
print(clf(["Пришло быстро, всё работает", "Сломался через неделю", "Обычный чайник, ничего особенного"]))
# [{'label': 'позитив', 'score': …}, {'label': 'негатив', 'score': …}, …] — метки взяты из id2label

Без id2label метки назывались бы LABEL_0, LABEL_1, LABEL_2.

Теперь спросим Qwen3-8B, ничего не дообучая. Ollama — программа, которая скачивает и запускает LLM одной командой; подробно о ней и её родственниках — в уроке 4.6.5 и в уроке 7.2.2. Установите и скачайте модель (около 5 ГБ):

Окно терминала
curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen3:8b
"""zero_shot_qwen.py — та же тональность силами LLM без дообучения. python zero_shot_qwen.py"""
import re
import requests
from datasets import load_dataset
NAMES = ["негатив", "нейтрально", "позитив"] # тот же порядок, что в id2label
test = load_dataset("ai-forever/ru-reviews-classification", split="test").shuffle(seed=0).select(range(300))
def ask(text):
prompt = ("Определи тональность отзыва о товаре. Ответь одним словом: негатив, нейтрально или позитив.\n\n"
f"Отзыв: {text}\n/no_think") # /no_think — выключить рассуждения у Qwen3
r = requests.post("http://localhost:11434/v1/chat/completions", json={
"model": "qwen3:8b", "temperature": 0,
"messages": [{"role": "user", "content": prompt}]})
answer = r.json()["choices"][0]["message"]["content"]
answer = re.sub(r"<think>.*?</think>", "", answer, flags=re.S).strip().lower()
return next((i for i, name in enumerate(NAMES) if name[:5] in answer), -1) # −1: модель ответила не по формату
preds = [ask(row["text"]) for row in test]
acc = sum(p == row["label"] for p, row in zip(preds, test)) / len(test)
print(f"zero-shot Qwen3-8B: точность {acc:.1%}, ответов не по формату: {preds.count(-1)}")

Сравните с eval_accuracy энкодера на тех же 300 отзывах (в скрипте дообучения: trainer.evaluate(test_ds.shuffle(seed=0).select(range(300)))). Обратите внимание на три вещи. Качество: LLM неплохо отличает позитив от негатива, но «нейтральный» класс понимает не так, как разметчики датасета. Скорость: засеките время — энкодер обрабатывает тысячи отзывов в секунду, LLM — единицы. Формат: часть ответов LLM может не уложиться в одно слово, их приходится разбирать. Попробуйте добавить в промпт пять размеченных примеров (few-shot, урок 8.1.2) и посмотрите, сократится ли разрыв.

МодельЧто этоГде пригодится
DeepPavlov/rubert-base-casedBERT-base, дообученный на русской Википедии и новостях (МФТИ)классификация, NER на русском
ai-forever/ruBert-base, ruBert-largeрусские BERT от Сбера (организация ai-forever на Hub)классификация, NER на русском
cointegrated/rubert-tiny2маленький русский BERT, быстрый даже на CPUпрототипы, продакшн на CPU
intfloat/multilingual-e5-baseмногоязычный энкодер на базе XLM-R, обучен для эмбеддинговпоиск и RAG (урок 8.2.1)
BAAI/bge-m3многоязычный энкодер-эмбеддер Пекинской академии ИИ (BAAI)RAG на русском и китайском (урок 8.2.1)
Проверь себя6 вопросов
1 В тексте 400 обычных токенов. Сколько позиций в среднем дадут вклад в MLM-loss при стандартном рецепте BERT?
2 Зачем 10% выбранных токенов оставляют без изменений?
3 Что изменила RoBERTa по сравнению с BERT? Выберите все верные варианты.Можно выбрать несколько ответов
4 Какой класс transformers вы возьмёте для поиска имён и названий городов в тексте (NER)?
5 Скрипт из старого туториала падает с ошибкой про неизвестный аргумент evaluation_strategy. В чём дело?
6 Сервису нужно классифицировать 5 млн коротких обращений в сутки по 12 стабильным темам, есть 20 тысяч размеченных примеров. Что разумнее?
  • BERT — двунаправленный энкодер. Вход: [CLS] A [SEP] B [SEP], эмбеддинг = токен + позиция + сегмент.
  • MLM: выбираем 15% обычных токенов, из них 80% → [MASK], 10% → случайный токен, 10% без изменений; loss только по выбранным (метка −100 у остальных). NSP оказалась лишней: RoBERTa без неё, но с динамическим маскированием и большим объёмом данных обошла BERT.
  • Дообучение: голова поверх [CLS] для классификации, поверх каждого токена — для NER; маленький learning rate, 2–4 эпохи.
  • Рабочий цикл Hugging Face: Hub и карточка модели (читайте лицензию) → AutoTokenizer/AutoModel... → load_dataset и map → Trainer с TrainingArguments и compute_metrics → pipeline.
  • Узкая стабильная задача с разметкой и большим потоком запросов — энкодер; без разметки и для быстрых прототипов — LLM.

Дальше: модуль 4.6 — собираем GPT своими руками, от микро-GPT на чистом Python до модели на PyTorch — Микро-GPT на чистом Python.