Как работает Text Similarity через эмбеддинги (cosine similarity) против BM25 (keyword overlap)?
Краткий тезис
Text Similarity — ключевая метрика для поиска, ранжирования и сравнения текстов. Два принципиально разных подхода: Cosine similarity на основе эмбеддингов (семантическая близость) и BM25 на основе TF-IDF (лексическое совпадение терминов). Cosine similarity улавливает смысл, но требует дорогих моделей; BM25 быстр и точен для точных ключевых слов, но не понимает синонимов. Гибридные схемы объединяют сильные стороны обоих методов.
2. BM25: overlap терминов
BM25 (Best Matching 25) — вероятностная модель ранжирования, эволюция TF-IDF. Оценивает релевантность документа запросу на основе частоты терминов и длины документа.
Формула (упрощённо): [ [text](/wiki/text){BM25}(q, d) = \sum_{t \in q} [text](/wiki/text){IDF}(t) \cdot \frac{f(t, d) \cdot (k_1 + 1)}{f(t, d) + k_1 \cdot (1 - b + b \cdot \frac{|d|}{[text](/wiki/text){avgdl}})} ] Где:
- ( f(t, d) ) — частота термина в документе,
- ( [text](/wiki/text){IDF}(t) ) — обратная документная частота,
- ( k_1, b ) — параметры насыщения и нормализации длины.
Особенности:
- Лексический: учитывает только точные совпадения слов.
- Быстрый: работает на инвертированном индексе (Elasticsearch, Lucene).
- Насыщение: высокая частота термина не даёт бесконечного роста веса.
Пример (через Whoosh):
from whoosh.index import create_in
from whoosh.fields import Schema, TEXT
from whoosh.qparser import QueryParser
from whoosh.scoring import BM25F
schema = Schema(title=TEXT(stored=True), content=TEXT)
ix = create_in("indexdir", schema)
writer = ix.writer()
writer.add_document(title="Doc1", content="BM25 для поиска текстов")
writer.commit()
with ix.searcher(weighting=BM25F()) as searcher:
query = QueryParser("content", ix.schema).parse("поиск")
results = searcher.search(query)
print(results[0].score) # 0.42
Недостатки:
- Не понимает синонимы: «автомобиль» и «машина» — разные термины.
- Чувствителен к стеммингу/лемматизации: без нормализации «бег» и «бегать» не совпадут.
3. Семантическая vs лексическая близость
| Аспект | Cosine similarity (эмбеддинги) | BM25 (keyword overlap) |
|---|---|---|
| Принцип | Векторное представление смысла | Частотный анализ терминов |
| Понимание синонимов | Да (например, «купить» ≈ «приобрести») | Нет (только точные совпадения) |
| Работа с опечатками | Частично (если модель обучена на шум) | Нет (нужна fuzzy matching) |
| Скорость | Медленно (инференс + поиск по векторам) | Быстро (инвертированный индекс) |
| Масштабирование | Требует FAISS или Pinecone | Готовые решения (Elasticsearch) |
| Длина текста | Устойчив (нормализация) | Требует настройки параметра b |
| Пример сценария | Поиск по смыслу: «как приготовить пасту» → рецепты | Поиск по точному названию: «BM25 алгоритм» |
Когда что выбирать:
- Cosine similarity: вопросно-ответные системы, рекомендации, поиск по описаниям (например, «найди похожие статьи»).
- BM25: поиск по каталогам, юридические документы, где важны точные термины (например, «статья 228 УК РФ»).
4. Гибрид: BM25 + эмбеддинги
Гибридный подход объединяет лексическую точность BM25 и семантическую гибкость эмбеддингов. Популярные схемы:
a) Ранжирование с перевесом (Reciprocal Rank Fusion, RRF):
def rrf(scores_bm25, scores_cosine, k=60):
combined = {}
for doc_id in set(scores_bm25) | set(scores_cosine):
rank_bm25 = scores_bm25.get(doc_id, 0)
rank_cosine = scores_cosine.get(doc_id, 0)
combined[doc_id] = 1/(k + rank_bm25) + 1/(k + rank_cosine)
return sorted(combined.items(), key=lambda x: -x[1])
b) Двухэтапный поиск (Hybrid search):
- Stage 1 (BM25): быстрый отбор кандидатов (top-1000).
- Stage 2 (Cosine similarity): переранжирование эмбеддингами (top-10).
c) Взвешенная сумма: [ [text](/wiki/text){score} = [alpha](/wiki/alpha) \cdot [text](/wiki/text){BM25}(q, d) + (1 - [alpha](/wiki/alpha)) \cdot [text](/wiki/text){cosine_sim}(q, d) ] Где ([alpha](/wiki/alpha)) — гиперпараметр (обычно 0.3–0.7).
Пример реализации (Elasticsearch с ELSER):
{
"query": {
"bool": {
"should": [
{ "match": { "content": "поиск текстов" } },
{ "knn": { "field": "embedding", "query_vector": [0.1, 0.2, ...] } }
]
}
}
}
Преимущества:
- Робастность: если BM25 не находит точных совпадений, эмбеддинги «спасают» семантикой.
- Производительность: BM25 отсекает шум, уменьшая нагрузку на векторный поиск.
Недостатки:
- Сложность настройки: выбор ([alpha](/wiki/alpha)), порогов, моделей.
- Дополнительное хранилище: и инвертированный индекс, и векторная БД.
5. Пет-проект для закрепления
Задача: Реализовать гибридный поиск по коллекции новостей (например, датасет Lenta.ru).
Инструменты:
- Python:
sentence-transformers,rank_bm25,scikit-learn. - Данные: 1000 новостей (тексты + заголовки).
- Векторная БД:
faiss(in-memory) илиchromadb.
Шаги:
- Предобработка: очистка, лемматизация (pymorphy2), удаление стоп-слов.
- Индексация BM25:
BM25Okapiизrank_bm25. - Генерация эмбеддингов:
model.encode(docs). - Поиск:
- BM25:
bm25.get_scores(query)→ top-100. - Cosine:
cosine_similarity(query_emb, doc_embs)→ top-100.
- BM25:
- Гибрид: RRF или взвешенная сумма → top-10.
- Оценка: NDCG@10 на размеченных запросах (например, 10 запросов с релевантными документами).
Ожидаемый результат:
- Таблица метрик: BM25 (NDCG@10 = 0.65), Cosine (0.72), Hybrid (0.81).
- Вывод: гибрид даёт прирост 10–15% за счёт комбинации методов.
Связь с другими вопросами
Навигация
- Предыдущий: 948
- Следующий: 950
- Индекс: 00. Индекс разборов