Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Гибридный поиск: векторный + BM25
hybrid_search

Гибридный поиск: векторный + BM25

Комбинируем семантический поиск с keyword-поиском. BM25, Reciprocal Rank Fusion, практические примеры улучшения точности.

Гибридный поиск: векторный + BM25

Комбинируем семантический поиск с keyword-поиском. BM25, Reciprocal Rank Fusion, практические примеры улучшения точности.

#Что вы сделаете в этой теме

За 60 минут вы:

  1. Поймёте ограничения чистого векторного поиска
  2. Научитесь использовать BM25 для keyword-поиска
  3. Реализуете гибридный поиск
  4. Освоите Reciprocal Rank Fusion для слияния результатов

#Почему гибридный поиск?

Векторный поиск:

  • ✅ Находит по смыслу
  • ❌ Может пропустить точные совпадения терминов

Keyword поиск (BM25):

  • ✅ Находит точные совпадения
  • ❌ Не понимает синонимы

Гибридный поиск: лучшее из обоих миров!

#Шаг 1: Установка BM25

pip install rank-bm25

#Шаг 2: Базовый BM25

from rank_bm25 import BM25Okapi # Документы documents = [ "Python язык программирования", "Django веб фреймворк на Python", "Flask микрофреймворк для веб" ] # Токенизация tokenized_docs = [doc.lower().split() for doc in documents] # Создание индекса bm25 = BM25Okapi(tokenized_docs) # Поиск query = "веб Python" query_tokens = query.lower().split() scores = bm25.get_scores(query_tokens) print(f"BM25 scores: {scores}") # Топ результат top_idx = scores.argmax() print(f"Лучший: {documents[top_idx]}")

#Шаг 3: Гибридный поиск

import faiss import numpy as np from rank_bm25 import BM25Okapi from sentence_transformers import SentenceTransformer class HybridSearch: """Гибридный поиск: векторный + BM25.""" def __init__(self, documents: list[str]): self.documents = documents self.model = SentenceTransformer('all-MiniLM-L6-v2') # BM25 индекс tokenized_docs = [doc.lower().split() for doc in documents] self.bm25 = BM25Okapi(tokenized_docs) # Векторный индекс embeddings = self.model.encode(documents) faiss.normalize_L2(embeddings) self.index = faiss.IndexFlatIP(embeddings.shape[1]) self.index.add(embeddings) def search( self, query: str, k: int = 10, alpha: float = 0.5 ) -> list[dict]: """ Гибридный поиск. Args: query: Поисковый запрос k: Количество результатов alpha: Вес векторного поиска (0 = только BM25, 1 = только векторный) """ # BM25 поиск query_tokens = query.lower().split() bm25_scores = self.bm25.get_scores(query_tokens) # Векторный поиск query_embedding = self.model.encode([query]) faiss.normalize_L2(query_embedding) distances, indices = self.index.search(query_embedding, k=len(self.documents)) vector_scores = np.zeros(len(self.documents)) vector_scores[indices[0]] = distances[0] # Нормализация к [0, 1] bm25_norm = self._normalize(bm25_scores) vector_norm = self._normalize(vector_scores) # Комбинирование combined_scores = alpha * vector_norm + (1 - alpha) * bm25_norm # Топ-k top_indices = np.argsort(combined_scores)[::-1][:k] return [ { "index": int(idx), "score": float(combined_scores[idx]), "text": self.documents[idx] } for idx in top_indices ] def _normalize(self, scores: np.ndarray) -> np.ndarray: """Нормализация к [0, 1].""" min_s, max_s = scores.min(), scores.max() if max_s - min_s < 1e-6: return np.zeros_like(scores) return (scores - min_s) / (max_s - min_s) # Использование documents = [ "Python язык программирования общего назначения", "Питон это змея из семейства удавов", "Django веб фреймворк на Python для быстрой разработки", "Flask микрофреймворк для создания веб приложений", "FastAPI современный фреймворк для создания API" ] searcher = HybridSearch(documents) results = searcher.search("веб разработка на Python", k=3, alpha=0.7) for r in results: print(f"[{r['score']:.3f}] {r['text']}")

#Шаг 4: Reciprocal Rank Fusion (RRF)

Альтернативный способ слияния результатов:

from collections import defaultdict def reciprocal_rank_fusion( results_lists: list[list], k: int = 60 ) -> list[tuple]: """ Слияние результатов через Reciprocal Rank Fusion. Args: results_lists: Список ранжированных списков результатов k: Константа для сглаживания (обычно 60) """ rrf_scores = defaultdict(float) for results in results_lists: for rank, item in enumerate(results): doc_id = item["id"] if isinstance(item, dict) else item rrf_scores[doc_id] += 1 / (k + rank + 1) # Сортировка по RRF score sorted_results = sorted( rrf_scores.items(), key=lambda x: x[1], reverse=True ) return sorted_results # Пример def hybrid_search_rrf(query, vector_index, bm25, model, documents, k=10): """Гибридный поиск с RRF.""" # Векторный поиск query_embedding = model.encode([query]) faiss.normalize_L2(query_embedding) _, vector_indices = vector_index.search(query_embedding, k=50) vector_results = [{"id": idx} for idx in vector_indices[0]] # BM25 поиск query_tokens = query.lower().split() bm25_scores = bm25.get_scores(query_tokens) bm25_indices = np.argsort(bm25_scores)[::-1][:50] bm25_results = [{"id": idx} for idx in bm25_indices] # Слияние через RRF fused = reciprocal_rank_fusion([vector_results, bm25_results], k=60) return [ {"id": doc_id, "rrf_score": score, "text": documents[doc_id]} for doc_id, score in fused[:k] ]

#Чеклист

  • Понимаете преимущества гибридного поиска
  • Умеете использовать BM25
  • Реализовали гибридный поиск с alpha
  • Попробовали Reciprocal Rank Fusion

#Что дальше

Следующая тема — RAG-приложение.

Далее: RAG-приложение: поиск + генерация ответов