Обработка естественного языка

Sentence и Document Embeddings

2019 год. Берлин. Nils Reimers публикует одну статью - и семантический поиск становится доступен каждому разработчику. До SBERT: 65 часов GPU на 10 000 предложений через BERT cross-encoder. После: 5 секунд на CPU. Разница в 47 000 раз - не за счёт железа, а за счёт одной архитектурной идеи.

  • Каждый RAG-пайплайн в Perplexity, Notion AI, Linear AI работает на sentence embeddings - именно SBERT или его потомках
  • GitHub Copilot использует code embeddings по тем же принципам для семантического поиска по репозиторию
  • OpenAI text-embedding-3-small - 1536 dim, обучен с Matryoshka loss, 20 USD за миллион токенов
  • Qdrant, Pinecone, Weaviate - весь рынок vector databases создан под хранение sentence embeddings
  • CLIP (OpenAI) использует тот же NT-Xent contrastive loss для связывания текст-изображение

Предварительные знания

  • Эмбеддинги слов: плотные векторы и косинусное сходство
  • Контекстные представления и архитектура BERT
  • Понимание задачи семантического поиска по близости векторов
  • Word Embeddings: Word2Vec, GloVe
  • Контекстные эмбеддинги: ELMo

Путь от doc2vec к Sentence-BERT

Представление целого предложения или документа одним вектором развивалось в три шага. В 2014 году Куок Ле и Томас Миколов предложили Paragraph Vector, более известный как doc2vec: он расширял word2vec, добавляя обучаемый вектор для всего абзаца. В 2018 году Google выпустил Universal Sentence Encoder, который давал универсальные эмбеддинги предложений для переноса на разные задачи. Решающий шаг сделали Нильс Раймерс и Ирина Гуревич в 2019 году в работе про Sentence-BERT: они переделали BERT в сиамскую сеть, так что сравнение предложений по близости векторов стало занимать миллисекунды вместо часов. Это превратило семантический поиск из дорогого исследовательского приёма в инструмент, доступный любому разработчику.

SBERT: Sentence-BERT и siamese-сети

2019 год. Nils Reimers публикует SBERT. До этого семантический поиск по 10 000 предложений через BERT cross-encoder занимал 65 часов - каждую пару нужно было скормить в BERT отдельно. После - 5 секунд. Качество почти не изменилось. Разрыв в 47 000 раз.

Проблема оригинального BERT: он умеет сравнивать два предложения, но только если оба одновременно в одном форварде. Формат `[CLS] A [SEP] B [SEP]` требует $O(n^2)$ пар. 10 000 предложений = 50 млн пар. SBERT ломает это ограничение.

Ключевая идея SBERT: siamese-архитектура. Два одинаковых BERT (с общими весами) обрабатывают предложения независимо. Каждое предложение превращается в один вектор. Сравнение - косинусное сходство между векторами. $O(n)$ вместо $O(n^2)$.

Siamese - от сиамских близнецов. Две нейросети с одинаковыми весами, которые смотрят на разные входы. После BERT-encoder применяется pooling - и предложение стягивается в один вектор фиксированной размерности (обычно 768). Этот вектор - семантический отпечаток предложения.

Обучается SBERT на задаче Natural Language Inference (NLI). Пара предложений - entailment, contradiction или neutral. Лосс - softmax по трём классам с разницей векторов, произведением и конкатенацией. Позже переобучают на Semantic Textual Similarity (STS) напрямую через regression loss.

Результат: библиотека `sentence-transformers` в PyPI, 350+ предобученных моделей, 30 млн загрузок в месяц. Каждый RAG-пайплайн в 2024-2025 годах использует SBERT или его прямых потомков. `all-MiniLM-L6-v2` - 80 MB, работает на CPU, выдаёт 384-мерные векторы за 5 мс.

Почему оригинальный BERT не подходит для семантического поиска по большой базе?

Bi-encoder vs Cross-encoder: скорость против качества

Два мира, два компромисса. Bi-encoder (SBERT) - предвычисляет вектор каждого документа независимо, хранит в индексе, при поиске делает одно матричное умножение. Cross-encoder - скармливает пару (запрос, документ) в BERT целиком, видит взаимодействие токенов. Первый - быстрый. Второй - точный.

Реальные системы используют оба. Сначала bi-encoder отбирает топ-100 из миллионов документов за 10 мс - ANN-поиск через HNSW в Qdrant или Pinecone. Потом cross-encoder переранжирует топ-100 за 200 мс - смотрит на каждую пару детально. Итог: качество cross-encoder, скорость bi-encoder. Это называется retrieve-and-rerank.

Cross-encoder лучше потому, что видит взаимодействие токенов запроса и документа через self-attention. Bi-encoder сжимает смысл в один вектор - информация теряется. Особенно на длинных или неоднозначных текстах разрыв в качестве достигает 10-15% NDCG@10.

Популярные bi-encoders: `all-MiniLM-L6-v2` (384 dim, быстрый), `all-mpnet-base-v2` (768 dim, точнее), `multilingual-e5-large` (для 100+ языков). Популярные cross-encoders: `cross-encoder/ms-marco-MiniLM-L-6-v2` - обучен на 500K запрос-документ пар из Microsoft MARCO.

Есть и гибриды. Late interaction - ColBERT. Вместо одного вектора на предложение - матрица токен-векторов. MaxSim между матрицами запроса и документа. Качество ближе к cross-encoder, скорость - к bi-encoder. Используется в RAGatouille и ряде enterprise search-систем.

Bi-encoder и cross-encoder используются последовательно в реальной поисковой системе. Что делает каждый?

Contrastive Learning: SimCSE и NT-Xent

Задача: научить модель, что похожие предложения должны быть близко в пространстве, а непохожие - далеко. Без размеченных пар. Contrastive learning решает это провокационно просто: взять одно предложение, прогнать через модель дважды с разными dropout-масками - получить два слегка разных вектора. Это и есть позитивная пара.

SimCSE (2021, Gao et al.) - Simple Contrastive Learning of Sentence Embeddings. Один форвард с dropout 0.1, второй с другим dropout-шумом. Одно предложение, два аугментированных вида. Все остальные предложения в батче - негативы. Лосс: NT-Xent (Normalized Temperature-scaled Cross-Entropy), более известный как InfoNCE.

Температура $\tau$ (обычно 0.05 в SimCSE) критична. Малая температура - жёсткие границы между кластерами, быстрее учится различать. Большая - мягче, терпимее к шуму. В CLIP (OpenAI) $\tau$ - обучаемый параметр. В SimCSE фиксирована.

Зачем это работает? Dropout как data augmentation для текста. Два прогона одного предложения дают семантически идентичные, но численно разные векторы. Модель учится инварианту: смысл не должен зависеть от dropout-шума. Этот принцип - основа всего самообучения в NLP: от BERT-MLM до contrastive pretraining в CLIP, ALIGN, CoCa.

Supervised SimCSE ещё лучше: позитивная пара - entailment из NLI, хард-негатив - contradiction. Модель учится на человеческой логике - что значит "то же самое" и "противоположное". На STS-B Spearman корреляция прыгает с 74% (unsupervised) до 81% (supervised). BERT без fine-tuning - около 53%.

Document embeddings - та же история, другой масштаб. Для длинных документов mean pooling по всем токенам часто лучше CLS. CLS токен BERT предназначен для sentence-level задач при fine-tuning - без fine-tuning он несёт мало семантики. Mean pooling усредняет информацию всех токенов. Для SBERT исследования показали: mean > max > CLS по корреляции с STS.

Matryoshka Representation Learning (MRL, 2022) - одна модель, разные размерности по запросу. Обучает так, чтобы первые 64 измерения уже несли семантику, первые 128 - больше, и так далее. `text-embedding-3-small` от OpenAI обучена по MRL - можно обрезать до 256 dim и потерять только 3% качества на MTEB.

Как unsupervised SimCSE создаёт позитивные пары для contrastive learning?

Итоги

  • SBERT: siamese BERT с shared weights, mean pooling, cosine similarity - O(n) вместо O(n^2)
  • Bi-encoder быстр (предвычислен индекс), cross-encoder точен (видит взаимодействие токенов) - в проде используют оба последовательно
  • SimCSE: dropout как аугментация, NT-Xent loss, температура $\tau = 0.05$ - SOTA sentence embeddings без разметки
  • Mean pooling > CLS для document embeddings - CLS без fine-tuning несёт мало семантики
  • Matryoshka loss (MRL) - одна модель на все размерности, OpenAI text-embedding-3 обучена по нему

Связанные темы

Sentence embeddings соединяют классический NLP с современным поиском и RAG.

  • BERT и Transformer — SBERT надстраивается над BERT, используя его encoder как backbone
  • Contextual Embeddings (ELMo) — Исторический предшественник - контекстные embeddings на уровне слова, SBERT делает это для предложений
  • RAG и векторный поиск — Sentence embeddings - основа retrieval компонента в RAG-системах
  • KL-дивергенция и InfoNCE — NT-Xent loss математически связан с минимизацией KL и максимизацией mutual information

Вопросы для размышления

  • SBERT получает один вектор на предложение через mean pooling. Какую информацию этот вектор теряет по сравнению с полным BERT-output (матрица токен-векторов)? В каких сценариях эта потеря критична?

Связанные уроки

  • nlp-05 — ELMo и контекстные embeddings - предшественник BERT/SBERT
  • nlp-12 — SBERT надстраивается над BERT с siamese-архитектурой
  • aie-09-embeddings — sentence-transformers в продакшне - прямое применение SBERT
  • aie-12-rag-fundamentals — RAG-поиск работает на sentence embeddings через bi-encoder
  • it-03 — NT-Xent / InfoNCE loss - специальный случай InfoNCE через KL/MI
  • la-15-svd
Sentence и Document Embeddings

0

1

Войти